Unstable 0.17.5.0

This commit is contained in:
Markus Isberg
2022-03-30 01:20:59 +09:00
parent c1b8e5a341
commit 44ded0225a
88 changed files with 2033 additions and 1430 deletions
@@ -0,0 +1,187 @@
#nullable enable
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text.RegularExpressions;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
namespace Barotrauma.Steam
{
abstract partial class WorkshopMenu
{
protected readonly struct BBWord
{
[Flags]
public enum TagType
{
None = 0x0,
Bold = 0x1,
Italic = 0x2,
Header = 0x4,
List = 0x8,
NewLine = 0x10
}
public readonly string Text;
public readonly Vector2 Size;
public readonly TagType TagTypes;
public readonly GUIFont Font;
public BBWord(string text, TagType tagTypes)
{
Text = text;
TagTypes = tagTypes;
Font = tagTypes.HasFlag(TagType.Header)
? GUIStyle.LargeFont
: tagTypes.HasFlag(TagType.Bold)
? GUIStyle.SubHeadingFont
: GUIStyle.Font;
Size = Font.MeasureString(Text);
}
}
protected static readonly Regex bbTagRegex = new Regex(@"\[(.+?)\]",
RegexOptions.Compiled | RegexOptions.IgnoreCase | RegexOptions.CultureInvariant);
protected static GUICustomComponent CreateBBCodeElement(string bbCode, GUIListBox container)
{
Point cachedContainerSize = Point.Zero;
List<BBWord> bbWords = new List<BBWord>();
Stack<BBWord.TagType> tagStack = new Stack<BBWord.TagType>();
void recalculate()
{
if (cachedContainerSize == container.Content.RectTransform.NonScaledSize) { return; }
bbWords.Clear();
cachedContainerSize = container.Content.RectTransform.NonScaledSize;
var matches = new Stack<Match>(bbTagRegex.Matches(bbCode).Reverse());
Match? nextTag = null;
matches.TryPop(out nextTag);
int wordStart = 0;
BBWord.TagType currTagType;
for (int i = 0; i < bbCode.Length; i++)
{
char currChar = bbCode[i];
currTagType = tagStack.TryPeek(out var t) ? t : BBWord.TagType.None;
bool charIsCJK = TextManager.IsCJK($"{currChar}");
bool wordEnd = char.IsWhiteSpace(currChar) || charIsCJK;
int reachedTagLength = 0;
if (nextTag is { Index: int tagIndex, Length: int tagLength }
&& i == tagIndex)
{
reachedTagLength = tagLength;
string tagStr = nextTag.Value.Replace("[", "").Replace("]", "").Trim();
bool isClosing = tagStr.StartsWith("/");
tagStr = tagStr.Replace("/", "").Trim().ToLowerInvariant();
BBWord.TagType tagType = tagStr switch
{
"b" => BBWord.TagType.Bold,
"i" => BBWord.TagType.Italic,
"h1" => BBWord.TagType.Header,
_ => BBWord.TagType.None
};
if (tagType != BBWord.TagType.None)
{
if (isClosing)
{
if (currTagType == tagType)
{
tagStack.Pop();
}
}
else
{
tagStack.Push(tagType);
}
}
}
if (wordEnd || reachedTagLength > 0)
{
string word = bbCode[wordStart..i];
if (charIsCJK) { word = bbCode[wordStart..(i + 1)]; }
else if (char.IsWhiteSpace(currChar) && currChar != '\n') { word += " "; }
if (!word.IsNullOrEmpty())
{
bbWords.Add(new BBWord(word, currTagType));
}
else if (currChar == '\n')
{
bbWords.Add(new BBWord("", BBWord.TagType.NewLine));
}
if (reachedTagLength > 0)
{
i += reachedTagLength - 1;
nextTag = matches.TryPop(out var tag) ? tag : null;
}
wordStart = i + 1;
}
}
currTagType = tagStack.TryPeek(out var ft) ? ft : BBWord.TagType.None;
string finalWord = bbCode[wordStart..];
if (!finalWord.IsNullOrEmpty())
{
bbWords.Add(new BBWord(finalWord, currTagType));
}
}
void draw(SpriteBatch spriteBatch, GUICustomComponent component)
{
recalculate();
Vector2 currPos = Vector2.Zero;
Vector2 rectPos = component.Rect.Location.ToVector2();
for (int i = 0; i < bbWords.Count; i++)
{
var bbWord = bbWords[i];
if (currPos.X > 0.0f
&& currPos.X + bbWord.Size.X >= component.Rect.Width)
{
//wrap because we went over width limit
currPos = (0.0f, currPos.Y + bbWord.Size.Y);
}
bbWord.Font.DrawString(
spriteBatch,
bbWord.Text,
(currPos + rectPos).ToPoint().ToVector2(),
GUIStyle.TextColorNormal,
forceUpperCase: ForceUpperCase.No,
italics: bbWord.TagTypes.HasFlag(BBWord.TagType.Italic));
bool breakLine
= bbWord.TagTypes.HasFlag(BBWord.TagType.NewLine)
|| (i < bbWords.Count - 1 &&
bbWords[i + 1].TagTypes.HasFlag(BBWord.TagType.Header) !=
bbWord.TagTypes.HasFlag(BBWord.TagType.Header));
if (breakLine)
{
//break line because of a header change or newline was found
currPos = (0.0f, currPos.Y + bbWord.Size.Y);
}
else
{
currPos.X += bbWord.Size.X;
}
}
component.RectTransform.NonScaledSize
= (component.RectTransform.NonScaledSize.X,
(int)(currPos.Y + bbWords.LastOrDefault().Size.Y));
component.RectTransform.RelativeSize
= component.RectTransform.NonScaledSize.ToVector2() / component.Parent.Rect.Size.ToVector2();
}
return new GUICustomComponent(new RectTransform(Vector2.One, container.Content.RectTransform),
onDraw: draw);
}
}
}
@@ -0,0 +1,41 @@
#nullable enable
using Microsoft.Xna.Framework;
namespace Barotrauma.Steam
{
sealed class ImmutableWorkshopMenu : WorkshopMenu
{
public ImmutableWorkshopMenu(GUIFrame parent) : base(parent)
{
var mainLayout
= new GUILayoutGroup(new RectTransform((0.5f, 1.0f), parent.RectTransform, Anchor.Center), isHorizontal: false);
Label(mainLayout, TextManager.Get("enabledcore"), GUIStyle.SubHeadingFont);
var coreBox = new GUIButton(
NewItemRectT(mainLayout), style: "GUITextBoxNoIcon", text: ContentPackageManager.EnabledPackages.Core!.Name, textAlignment: Alignment.CenterLeft)
{
CanBeFocused = false,
CanBeSelected = false
};
coreBox.TextBlock.Padding = new Vector4(10.0f, 0.0f, 10.0f, 0.0f);
Label(mainLayout, TextManager.Get("enabledregular"), GUIStyle.SubHeadingFont);
var regularList = new GUIListBox(
NewItemRectT(mainLayout, heightScale: 12f))
{
OnSelected = (component, o) => false,
HoverCursor = CursorState.Default
};
foreach (var p in ContentPackageManager.EnabledPackages.Regular)
{
var regularBox = new GUITextBlock(
new RectTransform((1.0f, 0.07f), regularList.Content.RectTransform), text: p.Name)
{
CanBeFocused = false
};
}
Label(mainLayout, TextManager.Get("CannotChangeMods"), GUIStyle.Font);
}
}
}
@@ -0,0 +1,748 @@
#nullable enable
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Barotrauma.Extensions;
using Barotrauma.IO;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using ItemOrPackage = Barotrauma.Either<Steamworks.Ugc.Item, Barotrauma.ContentPackage>;
namespace Barotrauma.Steam
{
sealed partial class MutableWorkshopMenu : WorkshopMenu
{
private string ExtractTitle(ItemOrPackage itemOrPackage)
=> itemOrPackage.TryGet(out ContentPackage package)
? package.Name
: ((Steamworks.Ugc.Item)itemOrPackage).Title;
private void CreateWorkshopItemDetailContainer(
GUIFrame parent,
out GUIListBox outerContainer,
Action<ItemOrPackage, GUIFrame> onSelected,
Action onDeselected,
out Action<ItemOrPackage> select,
out Action deselect)
{
ItemOrPackage? selectedItemOrPackage = null;
GUIListBox outContainer = new GUIListBox(new RectTransform(Vector2.One, parent.RectTransform),
isHorizontal: true,
style: null)
{
ScrollBarEnabled = false,
ScrollBarVisible = false,
HoverCursor = CursorState.Default
};
outerContainer = outContainer;
var selectedLayout =
new GUILayoutGroup(new RectTransform(Vector2.One, outerContainer.Content.RectTransform));
var selectedHeaderLayout =
new GUILayoutGroup(new RectTransform((1.0f, 0.05f), selectedLayout.RectTransform),
isHorizontal: true,
childAnchor: Anchor.CenterLeft);
void deselectMethod()
{
if (selectedItemOrPackage is null) { return; }
selectedItemOrPackage = null;
onDeselected();
}
deselect = deselectMethod;
var backButton =
new GUIButton(new RectTransform((0.04f, 1.0f), selectedHeaderLayout.RectTransform),
style: "GUIButtonToggleLeft")
{
OnClicked = (button, o) =>
{
deselectMethod();
return false;
}
};
var padding = new GUIFrame(new RectTransform((1.0f, 0.005f), selectedLayout.RectTransform), style: null);
var selectedFrame = new GUIFrame(new RectTransform((1.0f, 0.945f), selectedLayout.RectTransform),
style: null);
var selectionScroller = new GUICustomComponent(
new RectTransform(Vector2.Zero, outerContainer.Parent.RectTransform),
onUpdate: (deltaTime, component) =>
{
float targetScroll = selectedItemOrPackage is null
? 0.0f
: 1.0f;
outContainer.ScrollBar.BarScroll
= MathUtils.NearlyEqual(targetScroll, outContainer.ScrollBar.BarScroll)
? targetScroll
: MathHelper.Lerp(outContainer.ScrollBar.BarScroll, targetScroll, 0.3f);
});
select = itemOrPackage =>
{
//showInSteamButton.Visible = itemOrPackage.TryGet(out Steamworks.Ugc.Item _);
//selectedItem = itemOrPackage;
//selectedTitle.Text = ExtractTitle(itemOrPackage);
selectedFrame.ClearChildren();
//Jank to fix mouserect not clamping properly
//when shifting all elements to the left
var dropdowns = outContainer.Content.GetAllChildren<GUIDropDown>().ToArray();
var allChildren = outContainer.Content.GetAllChildren()
.Concat(selectedFrame.GetAllChildren());
allChildren.ForEach(c =>
{
//c.CascadingMouseRectClamp = !dropdowns.Any(dd => dd.IsParentOf(c) || dd.ListBox.IsParentOf(c));
//c.CanBeFocused = c.CanBeFocused || !c.CascadingMouseRectClamp;
c.ClampMouseRectToParent = !(c.Parent?.Parent is GUIDropDown);
}
);
selectedItemOrPackage = itemOrPackage;
onSelected(itemOrPackage, selectedFrame);
};
}
private void CreateWorkshopItemList(
GUIFrame parent,
out GUIListBox outerContainer,
out GUIListBox workshopItemList,
Action<Steamworks.Ugc.Item, GUIFrame> onSelected)
=> CreateWorkshopItemOrPackageList(
parent,
out outerContainer,
out workshopItemList,
onSelected: (ItemOrPackage itemOrPackage, GUIFrame frame)
=> onSelected((Steamworks.Ugc.Item)itemOrPackage, frame));
private GUIButton CreateShowInSteamButton(Steamworks.Ugc.Item workshopItem, RectTransform rectT)
=> new GUIButton(
rectT,
TextManager.Get("WorkshopShowItemInSteam"), style: "GUIButtonSmall")
{
OnClicked = (button, o) =>
{
SteamManager.OverlayCustomURL(workshopItem.Url);
return false;
}
};
private GUIButton? CreateShowInSteamButton(ItemOrPackage itemOrPackage)
=> itemOrPackage.TryGet(out Steamworks.Ugc.Item workshopItem)
? CreateShowInSteamButton(workshopItem)
: null;
private void CreateWorkshopItemOrPackageList(
GUIFrame parent,
out GUIListBox outerContainer,
out GUIListBox workshopItemList,
Action<ItemOrPackage, GUIFrame> onSelected)
{
GUIListBox? itemList = null;
CreateWorkshopItemDetailContainer(
parent,
out outerContainer,
onSelected: onSelected,
onDeselected: () => itemList?.Deselect(),
out var select, out var deselect);
itemList = new GUIListBox(new RectTransform(Vector2.One, outerContainer.Content.RectTransform));
itemList.RectTransform.SetAsFirstChild();
workshopItemList = itemList;
var deselectCarrier
= CreateActionCarrier(outerContainer.Content, nameof(deselect).ToIdentifier(), deselect);
itemList.OnSelected = (component, userData) =>
{
//Don't select if hitting the subscribe button
if (GUI.MouseOn.Parent != itemList.Content) { return false; }
if (!(userData is ItemOrPackage itemOrPackage)) { return false; }
select(itemOrPackage);
return true;
};
}
private void AddUnpublishedMods(ISet<Steamworks.Ugc.Item> workshopItems)
{
//Users that don't have a proper license cannot publish Workshop items
//(see https://partner.steamgames.com/doc/features/workshop#15)
void clearWithMessage(LocalizedString message)
{
selfModsList.ClearChildren();
var messageFrame = new GUIFrame(new RectTransform(Vector2.One, selfModsList.Content.RectTransform),
style: null)
{
CanBeFocused = false
};
new GUITextBlock(new RectTransform((0.5f, 1.0f), messageFrame.RectTransform, Anchor.Center),
text: message,
textAlignment: Alignment.Center,
wrap: true,
font: GUIStyle.Font);
}
if (SteamManager.IsFreeWeekend())
{
clearWithMessage(TextManager.Get("FreeWeekendCantPublish"));
return;
}
if (SteamManager.IsFamilyShared())
{
clearWithMessage(TextManager.Get("FamilySharedCantPublish"));
return;
}
DateTime getEditTime(ContentPackage p)
=> File.GetLastWriteTime(Path.GetDirectoryName(p.Path)!);
//Find local packages associated with the Workshop items if available
(Steamworks.Ugc.Item WorkshopItem, ContentPackage? LocalPackage)[] publishedItems = workshopItems
.Select(item => (item,
(ContentPackage?)ContentPackageManager.LocalPackages.FirstOrDefault(p
=> p.SteamWorkshopId != 0 && p.SteamWorkshopId == item.Id)))
//Sort the pairs by last local edit time if available
.OrderBy(t => t.Item2 == null)
.ThenByDescending(t => t.Item2 is { } p ? getEditTime(p) : t.Item1.LatestUpdateTime)
.ToArray();
int indexOfUserDataInPublishedItemsArray(object userData)
=> publishedItems.IndexOf(t
=> t.WorkshopItem.Id == ((Steamworks.Ugc.Item)(userData as ItemOrPackage)).Id);
//Take the existing GUI items that are in the list and sort to match the order of publishedItems
var publishedGuiComponents = selfModsList.Content.Children.OrderBy(c => indexOfUserDataInPublishedItemsArray(c.UserData)).ToArray();
//Get mods that haven't been published and add them to the list
var unpublishedMods = ContentPackageManager.LocalPackages
.Where(p => p.SteamWorkshopId == 0 || !publishedItems.Any(item => item.WorkshopItem.Id == p.SteamWorkshopId))
.OrderByDescending(getEditTime).ToArray();
if (unpublishedMods.Any())
{
var unpublishedHeader
= new GUITextBlock(new RectTransform((1.0f, 1.0f / 11.0f), selfModsList.Content.RectTransform),
TextManager.Get("UnpublishedModsHeader"), font: GUIStyle.SubHeadingFont) { CanBeFocused = false };
}
foreach (var unpublishedMod in unpublishedMods)
{
var unpublishedFrame = new GUIFrame(
new RectTransform((1.0f, 1.0f / 5.5f), selfModsList.Content.RectTransform),
style: "ListBoxElement")
{
UserData = (ItemOrPackage)unpublishedMod
};
var unpublishedLayout
= new GUILayoutGroup(new RectTransform(Vector2.One, unpublishedFrame.RectTransform),
isHorizontal: true)
{
Stretch = true,
RelativeSpacing = 0.02f
};
var unpublishedPadding
= new GUIFrame(
new RectTransform(Vector2.One, unpublishedLayout.RectTransform,
scaleBasis: ScaleBasis.BothHeight), style: null)
{
CanBeFocused = false
};
var unpublishedTextBlock
= new GUITextBlock(new RectTransform(Vector2.One, unpublishedLayout.RectTransform),
$"{unpublishedMod.Name}\n\n" +
TextManager.GetWithVariable("LastLocalEditTime",
"[datetime]",
getEditTime(unpublishedMod).ToString()),
font: GUIStyle.Font)
{
CanBeFocused = false
};
}
if (publishedGuiComponents.Any())
{
var publishedHeader
= new GUITextBlock(new RectTransform((1.0f, 1.0f / 11.0f), selfModsList.Content.RectTransform),
TextManager.Get("PublishedModsHeader"), font: GUIStyle.SubHeadingFont) { CanBeFocused = false };
}
foreach (var c in publishedGuiComponents)
{
c.SetAsLastChild();
var textBlock = (c.FindChild(b => b is GUITextBlock, recursive: true) as GUITextBlock)!;
textBlock.Text += $"\n";
int index = indexOfUserDataInPublishedItemsArray(c.UserData);
(Steamworks.Ugc.Item workshopItem, ContentPackage? localMod) = publishedItems[index];
if (localMod != null)
{
textBlock.Text += $"\n" + TextManager.GetWithVariable("LastLocalEditTime", "[datetime]", getEditTime(localMod).ToString());
}
textBlock.Text += $"\n" + TextManager.GetWithVariable("LatestPublishTime", "[datetime]", workshopItem.LatestUpdateTime.ToLocalTime().ToString());
}
}
private static (GUIButton Button, GUIFrame Sprite) CreatePaddedButton(RectTransform rectT, string style, float spriteScale)
{
var button = new GUIButton(
rectT,
style: null);
var sprite = new GUIFrame(
new RectTransform(Vector2.One * spriteScale, button.RectTransform, Anchor.Center),
style: style)
{
CanBeFocused = false
};
return (button, sprite);
}
private static void CreateSubscribeButton(Steamworks.Ugc.Item workshopItem, RectTransform rectT, float spriteScale)
{
const string plusButton = "GUIPlusButton";
const string minusButton = "GUIMinusButton";
LocalizedString subscribeTooltip = TextManager.Get("DownloadButton");
LocalizedString unsubscribeTooptip = TextManager.Get("WorkshopItemUnsubscribe");
var (subscribeButton, subscribeButtonSprite) = CreatePaddedButton(rectT, plusButton, spriteScale);
subscribeButton.ToolTip = subscribeTooltip;
subscribeButton.OnClicked = (button, o) =>
{
if (!workshopItem.IsSubscribed)
{
workshopItem.Subscribe();
TaskPool.Add($"DownloadSubscribedItem{workshopItem.Id}",
SteamManager.Workshop.ForceRedownload(workshopItem),
t => { });
}
else
{
workshopItem.Unsubscribe();
SteamManager.Workshop.Uninstall(workshopItem);
}
return false;
};
var buttonStyleUpdater = new GUICustomComponent(
new RectTransform(Vector2.Zero, subscribeButton.RectTransform),
onUpdate: (deltaTime, component) =>
{
if (subscribeButtonSprite.Style is { Identifier: { } styleId })
{
if (workshopItem.IsSubscribed && styleId != minusButton)
{
subscribeButtonSprite.ApplyStyle(GUIStyle.GetComponentStyle(minusButton));
subscribeButton.ToolTip = unsubscribeTooptip;
}
if (!workshopItem.IsSubscribed && styleId != plusButton)
{
subscribeButtonSprite.ApplyStyle(GUIStyle.GetComponentStyle(plusButton));
subscribeButton.ToolTip = subscribeTooltip;
}
}
});
float displayedDownloadAmount = workshopItem.DownloadAmount;
var downloadProgressBar = new GUICustomComponent(
new RectTransform((1.22f, 1.22f), subscribeButtonSprite.RectTransform, Anchor.Center),
onDraw: (spriteBatch, component) =>
{
bool visible = workshopItem.IsSubscribed
&& (workshopItem.IsDownloading
|| workshopItem.IsDownloadPending
|| !MathUtils.NearlyEqual(workshopItem.DownloadAmount, displayedDownloadAmount));
if (!visible) { return; }
void drawSection(float amount, Color color, float thickness)
=> GUI.DrawDonutSection(
spriteBatch,
component.Rect.Center.ToVector2() + (0, 1),
new Range<float>(component.Rect.Width * 0.55f - thickness * 0.5f, component.Rect.Width * 0.55f + thickness * 0.5f),
amount * MathF.PI * 2.0f,
color);
void drawSectionFuzzy(float amount, Color color, float thickness)
{
drawSection(amount, color, thickness);
drawSection(amount, color * 0.6f, thickness + 0.5f);
drawSection(amount, color * 0.3f, thickness + 1.0f);
}
drawSectionFuzzy(1.0f, Color.Lerp(Color.Black, GUIStyle.Blue, 0.2f), component.Rect.Width * 0.25f);
drawSectionFuzzy(1.0f, Color.Black, component.Rect.Width * 0.15f);
drawSectionFuzzy(displayedDownloadAmount, GUIStyle.Green, component.Rect.Width * 0.08f);
},
onUpdate: (deltaTime, component) =>
{
displayedDownloadAmount = Math.Min(
workshopItem.DownloadAmount,
MathHelper.Lerp(displayedDownloadAmount, workshopItem.DownloadAmount, 0.05f));
})
{
CanBeFocused = false
};
}
private void PopulateItemList(GUIListBox itemListBox, Task<ISet<Steamworks.Ugc.Item>> items, bool includeSubscribeButton, Action<ISet<Steamworks.Ugc.Item>>? onFill = null)
{
itemListBox.ClearChildren();
itemListBox.Deselect();
itemListBox.ScrollBar.BarScroll = 0.0f;
TaskPool.Add("PopulateTabWithItemList", items,
(t) =>
{
taskCancelSrc = taskCancelSrc.IsCancellationRequested ? new CancellationTokenSource() : taskCancelSrc;
itemListBox.ClearChildren();
var workshopItems = ((Task<ISet<Steamworks.Ugc.Item>>)t).Result;
foreach (var workshopItem in workshopItems)
{
var itemFrame = new GUIFrame(
new RectTransform((1.0f, 1.0f / 5.5f), itemListBox.Content.RectTransform),
style: "ListBoxElement")
{
UserData = (ItemOrPackage)workshopItem
};
var itemLayout = new GUILayoutGroup(
new RectTransform(Vector2.One, itemFrame.RectTransform),
isHorizontal: true, childAnchor: Anchor.CenterLeft)
{
Stretch = true
};
var thumbnailContainer
= CreateThumbnailContainer(itemLayout, Vector2.One, ScaleBasis.BothHeight);
CreateItemThumbnail(workshopItem, taskCancelSrc.Token, thumbnailContainer);
thumbnailContainer.CanBeFocused = false;
thumbnailContainer.GetAllChildren().ForEach(c => c.CanBeFocused = false);
var title = new GUITextBlock(
new RectTransform(Vector2.One, itemLayout.RectTransform),
workshopItem.Title, font: GUIStyle.Font)
{
CanBeFocused = false
};
if (includeSubscribeButton)
{
CreateSubscribeButton(workshopItem, new RectTransform(Vector2.One, itemLayout.RectTransform, scaleBasis: ScaleBasis.BothHeight), spriteScale: 0.4f);
}
}
onFill?.Invoke(workshopItems);
});
}
private GUIFrame CreateThumbnailContainer(
GUIComponent parent,
Vector2 relativeSize,
ScaleBasis scaleBasis)
=> new GUIFrame(new RectTransform(relativeSize, parent.RectTransform, scaleBasis: scaleBasis),
style: "GUIFrameListBox");
private SteamManager.Workshop.ItemThumbnail CreateItemThumbnail(
in Steamworks.Ugc.Item workshopItem,
CancellationToken cancellationToken,
GUIFrame thumbnailContainer)
{
var thumbnail = new SteamManager.Workshop.ItemThumbnail(workshopItem, cancellationToken);
itemThumbnails.Add(thumbnail);
CreateAsyncThumbnailComponent(thumbnailContainer, () => thumbnail.Texture, () => thumbnail.Loading);
return thumbnail;
}
private GUICustomComponent CreateAsyncThumbnailComponent(GUIFrame thumbnailContainer, Func<Texture2D?> textureGetter, Func<bool> throbberEnabled)
{
int randomThrobberOffset = Rand.Range(0, 10, Rand.RandSync.Unsynced);
return new GUICustomComponent(
new RectTransform(Vector2.One, thumbnailContainer.RectTransform, Anchor.Center),
onDraw: (spriteBatch, component) =>
{
Rectangle rect = component.Rect;
Texture2D? texture = textureGetter();
if (texture != null)
{
rect.Location += (4, 4);
rect.Size -= (8, 8);
Point destinationSizeMaxWidth = (rect.Width, rect.Width * texture.Height / texture.Width);
Point destinationSizeMaxHeight = (rect.Height * texture.Width / texture.Height, rect.Height);
Point destinationSize = destinationSizeMaxHeight.X > rect.Width
? destinationSizeMaxWidth
: destinationSizeMaxHeight;
Rectangle destinationRectangle = new Rectangle(
rect.Center.X - destinationSize.X / 2,
rect.Center.Y - destinationSize.Y / 2,
destinationSize.X,
destinationSize.Y);
spriteBatch.Draw(texture, destinationRectangle, Color.White);
}
else if (throbberEnabled())
{
var sheet = GUIStyle.GenericThrobber;
Vector2 pos = rect.Center.ToVector2() - Vector2.One * rect.Height * 0.4f;
sheet.Draw(spriteBatch, ((int)Math.Floor(Timing.TotalTime * 24.0f) + randomThrobberOffset) % sheet.FrameCount, pos, Color.White,
origin: Vector2.Zero, rotate: 0.0f,
scale: Vector2.One * component.Rect.Height / sheet.FrameSize.ToVector2() * 0.8f);
}
});
}
private GUIListBox CreateTagsList(IEnumerable<Identifier> tags, RectTransform rectT, bool canBeFocused)
{
var tagsList
= new GUIListBox(rectT, style: null, isHorizontal: false)
{
UseGridLayout = true,
ScrollBarEnabled = false,
ScrollBarVisible = false,
HideChildrenOutsideFrame = false,
Spacing = GUI.IntScale(4)
};
tagsList.Content.ClampMouseRectToParent = false;
foreach (Identifier tag in tags)
{
var tagBtn = new GUIButton(
new RectTransform(new Vector2(0.25f, 1.0f / 8.0f), tagsList.Content.RectTransform,
anchor: Anchor.TopLeft),
TextManager.Get($"workshop.contenttag.{tag.Value.RemoveWhitespace()}")
.Fallback(tag.Value.CapitaliseFirstInvariant()), style: "GUIButtonRound")
{
CanBeFocused = canBeFocused,
Selected = !canBeFocused,
UserData = tag
};
tagBtn.RectTransform.NonScaledSize
= tagBtn.Font.MeasureString(tagBtn.Text).ToPoint() + new Point(GUI.IntScale(15), GUI.IntScale(5));
tagBtn.RectTransform.IsFixedSize = true;
tagBtn.ClampMouseRectToParent = false;
}
return tagsList;
}
private void PopulateFrameWithItemInfo(Steamworks.Ugc.Item workshopItem, GUIFrame parentFrame)
{
taskCancelSrc = taskCancelSrc.IsCancellationRequested ? new CancellationTokenSource() : taskCancelSrc;
var contentPackage
= ContentPackageManager.WorkshopPackages.FirstOrDefault(p => p.SteamWorkshopId == workshopItem.Id);
var verticalLayout = new GUILayoutGroup(new RectTransform(Vector2.One, parentFrame.RectTransform));
var headerLayout = new GUILayoutGroup(new RectTransform((1.0f, 0.1f), verticalLayout.RectTransform),
isHorizontal: true) { Stretch = true };
var titleAndAuthorLayout = new GUILayoutGroup(new RectTransform(Vector2.One, headerLayout.RectTransform));
var selectedTitle =
new GUITextBlock(new RectTransform((1.0f, 0.5f), titleAndAuthorLayout.RectTransform), workshopItem.Title,
font: GUIStyle.LargeFont);
var author = workshopItem.Owner;
var authorButton = new GUIButton(new RectTransform((1.0f, 0.5f),
titleAndAuthorLayout.RectTransform),
style: null,
textAlignment: Alignment.CenterLeft)
{
ForceUpperCase = ForceUpperCase.No,
Font = GUIStyle.SubHeadingFont,
TextColor = GUIStyle.TextColorNormal,
HoverTextColor = Color.White,
SelectedTextColor = GUIStyle.TextColorNormal,
OnClicked = (button, o) =>
{
SteamManager.OverlayCustomURL(
$"https://steamcommunity.com/profiles/{author.Id}/myworkshopfiles/?appid={SteamManager.AppID}");
return false;
}
};
var authorPadding = authorButton.GetChild<GUITextBlock>().Padding;
RectTransform rightSideButtonRectT()
=> new RectTransform(Vector2.One, headerLayout.RectTransform, scaleBasis: ScaleBasis.BothHeight);
bool reinstallAction(GUIButton button, object o)
{
TaskPool.Add($"Reinstall{workshopItem.Id}", SteamManager.Workshop.Reinstall(workshopItem), t =>
{
ContentPackageManager.WorkshopPackages.Refresh();
ContentPackageManager.EnabledPackages.RefreshUpdatedMods();
});
return false;
}
var (updateButton, updateSprite) = CreatePaddedButton(
rightSideButtonRectT(),
"GUIUpdateButton",
spriteScale: 0.8f);
updateButton.ToolTip = TextManager.Get("WorkshopItemUpdate");
updateButton.Visible = false;
updateButton.OnClicked = reinstallAction;
if (contentPackage != null)
{
TaskPool.Add(
$"DetermineUpdateRequired{contentPackage.SteamWorkshopId}",
contentPackage.IsUpToDate(),
t =>
{
if (!t.TryGetResult(out bool isUpToDate)) { return; }
updateButton.Visible = !isUpToDate;
});
}
var (reinstallButton, reinstallSprite) = CreatePaddedButton(
rightSideButtonRectT(),
"GUIReloadButton",
spriteScale: 0.8f);
reinstallButton.ToolTip = TextManager.Get("WorkshopItemReinstall");
reinstallButton.OnClicked = reinstallAction;
var reinstallButtonUpdater = new GUICustomComponent(
new RectTransform(Vector2.Zero, reinstallButton.RectTransform),
onUpdate: (f, component) =>
{
reinstallButton.Visible = workshopItem.IsSubscribed || workshopItem.Owner.Id == SteamManager.GetSteamID();
reinstallButton.Enabled = !workshopItem.IsDownloading && !workshopItem.IsDownloadPending &&
!SteamManager.Workshop.IsInstalling(workshopItem);
reinstallSprite.Color = reinstallButton.Enabled
? reinstallSprite.Style.Color
: Color.DimGray;
updateButton.Enabled = reinstallButton.Enabled && contentPackage != null && ContentPackageManager.WorkshopPackages.Contains(contentPackage);
updateSprite.Color = reinstallSprite.Color;
if (contentPackage != null
&& !ContentPackageManager.WorkshopPackages.Contains(contentPackage)
&& ContentPackageManager.WorkshopPackages.Any(p => p.SteamWorkshopId == workshopItem.Id))
{
updateButton.Visible = false;
}
});
CreateSubscribeButton(workshopItem,
rightSideButtonRectT(),
spriteScale: 0.8f);
var padding = new GUIFrame(
new RectTransform((0.15f, 1.0f), headerLayout.RectTransform, scaleBasis: ScaleBasis.BothHeight),
style: null);
padding = new GUIFrame(new RectTransform((1.0f, 0.015f), verticalLayout.RectTransform), style: null);
var horizontalLayout = new GUILayoutGroup(new RectTransform((1.0f, 0.45f), verticalLayout.RectTransform),
isHorizontal: true)
{
Stretch = true
};
TaskPool.Add($"Request username for {author.Id}", author.RequestInfoAsync(), (t) =>
{
authorButton.Text = author.Name;
authorButton.RectTransform.NonScaledSize =
((int)(authorButton.Font.MeasureString(author.Name).X + authorPadding.X + authorPadding.Z),
authorButton.RectTransform.NonScaledSize.Y);
});
var thumbnailSuperContainer = new GUIFrame(
new RectTransform(Vector2.One, horizontalLayout.RectTransform, scaleBasis: ScaleBasis.BothHeight),
style: null);
GUIFrame thumbnailContainer = CreateThumbnailContainer(thumbnailSuperContainer, Vector2.One,
scaleBasis: ScaleBasis.BothHeight);
CreateItemThumbnail(workshopItem, taskCancelSrc.Token, thumbnailContainer);
thumbnailContainer.RectTransform.Anchor = Anchor.Center;
thumbnailContainer.RectTransform.Pivot = Pivot.Center;
var statsBox = new GUIFrame(new RectTransform((0.6f, 1.0f), horizontalLayout.RectTransform),
style: "GUIFrameListBox");
#region Stats box
var statsHorizontalLayout = new GUILayoutGroup(new RectTransform(Vector2.One, statsBox.RectTransform), isHorizontal: true);
var statsVertical0
= new GUILayoutGroup(new RectTransform((1.0f, 1.0f), statsHorizontalLayout.RectTransform), childAnchor: Anchor.TopCenter);
statFrame("", ""); //padding
var scoreFrame = new GUIFrame(new RectTransform((1.0f, 0.12f), statsVertical0.RectTransform), style: null);
var scoreLabel = new GUITextBlock(new RectTransform((0.4f, 1.0f), scoreFrame.RectTransform),
TextManager.Get("WorkshopItemScore"), font: GUIStyle.SubHeadingFont);
var scoreStarContainer
= new GUILayoutGroup(
new RectTransform((0.6f, 1.0f), scoreFrame.RectTransform, Anchor.CenterRight),
isHorizontal: true,
childAnchor: Anchor.CenterLeft) { Stretch = true };
var starColor = Color.Lerp(
Color.Lerp(Color.White, Color.Yellow, Math.Min(workshopItem.Score * 2.0f, 1.0f)),
Color.Lime, Math.Max(0.0f, (workshopItem.Score - 0.5f) * 2.0f));
for (int i = 0; i < 5; i++)
{
bool isStarLit = i <= Round(workshopItem.Score * 5.0f);
var star = new GUIFrame(new RectTransform(Vector2.One, scoreStarContainer.RectTransform, scaleBasis: ScaleBasis.BothHeight),
style: isStarLit ? "GUIStarIconBright" : "GUIStarIconDark");
if (isStarLit)
{
star.Color = starColor;
star.HoverColor = starColor;
star.SelectedColor = starColor;
}
}
var scoreTextPadding = new GUIFrame(new RectTransform((0.5f, 1.0f), scoreStarContainer.RectTransform, scaleBasis: ScaleBasis.BothHeight),
style: null);
var scoreTextContainer = new GUIFrame(new RectTransform(Vector2.One, scoreStarContainer.RectTransform),
style: null);
var scoreVoteCount = new GUITextBlock(
new RectTransform((1.0f, 1.5f), scoreTextContainer.RectTransform, Anchor.Center),
TextManager.GetWithVariable("WorkshopItemVotes", "[VoteCount]",
(workshopItem.VotesUp + workshopItem.VotesDown).ToString()), textAlignment: Alignment.BottomLeft)
{
Padding = Vector4.Zero
};
var subscriptionCount = new GUITextBlock(
new RectTransform((1.0f, 1.5f), scoreTextContainer.RectTransform, Anchor.Center),
TextManager.GetWithVariable("WorkshopItemSubscriptions", "[SubscriptionCount]",
workshopItem.NumUniqueSubscriptions.ToString()), textAlignment: Alignment.TopLeft)
{
Padding = Vector4.Zero
};
void statFrame(LocalizedString labelText, LocalizedString dataText)
{
var frame = new GUIFrame(new RectTransform((1.0f, 0.12f), statsVertical0!.RectTransform), style: null);
var label = new GUITextBlock(new RectTransform((0.4f, 1.0f), frame.RectTransform),
labelText, font: GUIStyle.SubHeadingFont);
var data = new GUITextBlock(new RectTransform((0.6f, 1.0f), frame.RectTransform, Anchor.CenterRight),
dataText, font: GUIStyle.Font)
{
Padding = Vector4.Zero
};
}
statFrame(TextManager.Get("WorkshopItemFileSize"), MathUtils.GetBytesReadable(workshopItem.SizeOfFileInBytes));
statFrame(TextManager.Get("WorkshopItemCreationDate"), workshopItem.Created.ToShortDateString());
statFrame(TextManager.Get("WorkshopItemModificationDate"), workshopItem.Updated.ToShortDateString());
var tagsLabel = new GUITextBlock(new RectTransform((1.0f, 0.12f), statsVertical0.RectTransform),
TextManager.Get("WorkshopItemTags"), font: GUIStyle.SubHeadingFont);
CreateTagsList(workshopItem.Tags.ToIdentifiers(), new RectTransform((0.97f, 0.3f), statsVertical0.RectTransform), canBeFocused: false);
#endregion
var descriptionListBox = new GUIListBox(new RectTransform((1.0f, 0.38f), verticalLayout.RectTransform));
CreateBBCodeElement(workshopItem.Description, descriptionListBox);
var showInSteamContainer
= new GUIFrame(new RectTransform((1.0f, 0.05f), verticalLayout.RectTransform), style: null);
CreateShowInSteamButton(workshopItem, new RectTransform((0.2f, 1.0f), showInSteamContainer.RectTransform, Anchor.CenterRight));
}
}
}
@@ -0,0 +1,474 @@
#nullable enable
using Barotrauma.Extensions;
using Microsoft.Xna.Framework;
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using System.Threading;
using ItemOrPackage = Barotrauma.Either<Steamworks.Ugc.Item, Barotrauma.ContentPackage>;
namespace Barotrauma.Steam
{
sealed partial class MutableWorkshopMenu : WorkshopMenu
{
public enum Tab
{
InstalledMods,
//Overrides, //TODO: implement later
PopularMods,
Publish
}
protected readonly GUILayoutGroup tabber;
protected readonly Dictionary<Tab, (GUIButton Button, GUIFrame Content)> tabContents;
protected readonly GUIFrame contentFrame;
private CorePackage EnabledCorePackage => enabledCoreDropdown.SelectedData as CorePackage ?? throw new Exception("Valid core package not selected");
private readonly GUIDropDown enabledCoreDropdown;
private readonly GUIListBox enabledRegularModsList;
private readonly GUIListBox disabledRegularModsList;
private readonly Action<ItemOrPackage> onInstalledInfoButtonHit;
private readonly GUITextBox modsListFilter;
private CancellationTokenSource taskCancelSrc = new CancellationTokenSource();
private readonly HashSet<SteamManager.Workshop.ItemThumbnail> itemThumbnails = new HashSet<SteamManager.Workshop.ItemThumbnail>();
private readonly GUIListBox popularModsList;
private readonly GUIListBox selfModsList;
public MutableWorkshopMenu(GUIFrame parent) : base(parent)
{
var mainLayout
= new GUILayoutGroup(new RectTransform(Vector2.One, parent.RectTransform), isHorizontal: false);
tabber = new GUILayoutGroup(new RectTransform((1.0f, 0.05f), mainLayout.RectTransform), isHorizontal: true)
{ Stretch = true };
tabContents = new Dictionary<Tab, (GUIButton Button, GUIFrame Content)>();
contentFrame = new GUIFrame(new RectTransform((1.0f, 0.95f), mainLayout.RectTransform), style: null);
CreateInstalledModsTab(
out enabledCoreDropdown,
out enabledRegularModsList,
out disabledRegularModsList,
out onInstalledInfoButtonHit,
out modsListFilter);
CreatePopularModsTab(out popularModsList);
CreatePublishTab(out selfModsList);
SelectTab(Tab.InstalledMods);
}
private void SwitchContent(GUIFrame newContent)
{
contentFrame.Children.ForEach(c => c.Visible = false);
newContent.Visible = true;
}
public void SelectTab(Tab tab)
{
SwitchContent(tabContents[tab].Content);
tabber.Children.ForEach(c =>
{
if (c is GUIButton btn) { btn.Selected = btn == tabContents[tab].Button; }
});
if (!taskCancelSrc.IsCancellationRequested) { taskCancelSrc.Cancel(); }
itemThumbnails.ForEach(t => t.Dispose());
itemThumbnails.Clear();
switch (tab)
{
case Tab.InstalledMods:
PopulateInstalledModLists();
break;
case Tab.PopularMods:
PopulateItemList(popularModsList, SteamManager.Workshop.GetPopularItems(), includeSubscribeButton: true);
break;
case Tab.Publish:
PopulateItemList(selfModsList, SteamManager.Workshop.GetPublishedItems(), includeSubscribeButton: false, onFill: AddUnpublishedMods);
break;
}
}
private void AddButtonToTabber(Tab tab, GUIFrame content)
{
var button = new GUIButton(new RectTransform(Vector2.One, tabber.RectTransform, Anchor.BottomCenter, Pivot.BottomCenter), TextManager.Get($"workshopmenutab.{tab}"), style: "GUITabButton")
{
OnClicked = (b, _) =>
{
SelectTab(tab);
return false;
}
};
button.RectTransform.MaxSize = RectTransform.MaxPoint;
button.Children.ForEach(c => c.RectTransform.MaxSize = RectTransform.MaxPoint);
tabContents.Add(tab, (button, content));
}
private GUIFrame CreateNewContentFrame(Tab tab)
{
var content = new GUIFrame(new RectTransform(Vector2.One * 0.98f, contentFrame.RectTransform, Anchor.Center, Pivot.Center), style: null);
AddButtonToTabber(tab, content);
return content;
}
private static (GUILayoutGroup Left, GUIFrame center, GUILayoutGroup Right) CreateSidebars(
GUIComponent parent,
float leftWidth = 0.3875f,
float centerWidth = 0.025f,
float rightWidth = 0.5875f,
bool split = false,
float height = 1.0f)
{
GUILayoutGroup layout = new GUILayoutGroup(new RectTransform((1.0f, height), parent.RectTransform), isHorizontal: true);
GUILayoutGroup left = new GUILayoutGroup(new RectTransform((leftWidth, 1.0f), layout.RectTransform), isHorizontal: false);
var center = new GUIFrame(new RectTransform((centerWidth, 1.0f), layout.RectTransform), style: null);
if (split)
{
new GUICustomComponent(new RectTransform(Vector2.One, center.RectTransform),
onDraw: (sb, c) =>
{
sb.DrawLine((c.Rect.Center.X, c.Rect.Top), (c.Rect.Center.X, c.Rect.Bottom), GUIStyle.TextColorDim, 2f);
});
}
GUILayoutGroup right = new GUILayoutGroup(new RectTransform((rightWidth, 1.0f), layout.RectTransform), isHorizontal: false);
return (left, center, right);
}
private void HandleDraggingAcrossModLists(GUIListBox from, GUIListBox to)
{
if (to.Rect.Contains(PlayerInput.MousePosition) && from.DraggedElement != null)
{
//move the dragged elements to the index determined previously
var draggedElement = from.DraggedElement;
var selected = from.AllSelected.ToList();
selected.Sort((a, b) => from.Content.GetChildIndex(a) - from.Content.GetChildIndex(b));
float oldCount = to.Content.CountChildren;
float newCount = oldCount + selected.Count;
var offset = draggedElement.RectTransform.AbsoluteOffset;
offset += from.Content.Rect.Location;
offset -= to.Content.Rect.Location;
for (int i = 0; i < selected.Count; i++)
{
var c = selected[i];
c.Parent.RemoveChild(c);
c.RectTransform.Parent = to.Content.RectTransform;
c.RectTransform.RepositionChildInHierarchy((int)oldCount+i);
}
from.DraggedElement = null;
from.Deselect();
from.RecalculateChildren();
from.RectTransform.RecalculateScale(true);
to.RecalculateChildren();
to.RectTransform.RecalculateScale(true);
to.Select(selected);
//recalculate the dragged element's offset so it doesn't jump around
draggedElement.RectTransform.AbsoluteOffset = offset;
to.DraggedElement = draggedElement;
to.BarScroll *= (oldCount / newCount);
}
}
private void CreateInstalledModsTab(
out GUIDropDown enabledCoreDropdown,
out GUIListBox enabledRegularModsList,
out GUIListBox disabledRegularModsList,
out Action<ItemOrPackage> onInstalledInfoButtonHit,
out GUITextBox modsListFilter)
{
GUIFrame content = CreateNewContentFrame(Tab.InstalledMods);
CreateWorkshopItemDetailContainer(
content,
out var outerContainer,
onSelected: (itemOrPackage, selectedFrame) =>
{
if (itemOrPackage.TryGet(out Steamworks.Ugc.Item item)) { PopulateFrameWithItemInfo(item, selectedFrame); }
},
onDeselected: PopulateInstalledModLists,
out onInstalledInfoButtonHit, out var deselect);
GUILayoutGroup mainLayout =
new GUILayoutGroup(new RectTransform(Vector2.One, outerContainer.Content.RectTransform), childAnchor: Anchor.TopCenter);
mainLayout.RectTransform.SetAsFirstChild();
GUILayoutGroup coreSelectionLayout =
new GUILayoutGroup(new RectTransform((0.5f, 0.15f), mainLayout.RectTransform));
Label(coreSelectionLayout, TextManager.Get("enabledcore"), GUIStyle.SubHeadingFont, heightScale: 1.0f / 0.15f);
enabledCoreDropdown = Dropdown<CorePackage>(coreSelectionLayout,
(p) => p.Name,
ContentPackageManager.CorePackages.ToArray(),
ContentPackageManager.EnabledPackages.Core!,
(p) => { },
heightScale: 1.0f / 0.15f);
var (left, center, right) = CreateSidebars(mainLayout, centerWidth: 0.05f, leftWidth: 0.475f, rightWidth: 0.475f, height: 0.78f);
right.ChildAnchor = Anchor.TopRight;
Action swapFunc(GUIListBox from, GUIListBox to)
{
return () =>
{
to.Deselect();
var selected = from.AllSelected.ToArray();
foreach (var frame in selected)
{
frame.Parent.RemoveChild(frame);
frame.RectTransform.Parent = to.Content.RectTransform;
}
from.RecalculateChildren();
from.RectTransform.RecalculateScale(true);
to.RecalculateChildren();
to.RectTransform.RecalculateScale(true);
to.Select(selected);
};
}
Action? currentCenterCallback = null;
//enabled mods
Label(left, TextManager.Get("enabledregular"), GUIStyle.SubHeadingFont);
var enabledModsList = new GUIListBox(new RectTransform((1.0f, 0.92f), left.RectTransform))
{
CurrentDragMode = GUIListBox.DragMode.DragOutsideBox,
CurrentSelectMode = GUIListBox.SelectMode.RequireShiftToSelectMultiple,
HideDraggedElement = true
};
enabledRegularModsList = enabledModsList;
//disabled mods
Label(right, TextManager.Get("disabledregular"), GUIStyle.SubHeadingFont);
var disabledModsList = new GUIListBox(new RectTransform((1.0f, 0.92f), right.RectTransform))
{
CurrentDragMode = GUIListBox.DragMode.DragOutsideBox,
CurrentSelectMode = GUIListBox.SelectMode.RequireShiftToSelectMultiple,
HideDraggedElement = true
};
disabledRegularModsList = disabledModsList;
var centerButton =
new GUIButton(
new RectTransform(Vector2.One * 0.95f, center.RectTransform, scaleBasis: ScaleBasis.BothWidth,
anchor: Anchor.Center),
style: "GUIButtonToggleLeft")
{
Visible = false,
OnClicked = (button, o) =>
{
currentCenterCallback?.Invoke();
return false;
}
};
enabledModsList.OnSelected = (frame, o) =>
{
disabledModsList.Deselect();
centerButton.Visible = true;
centerButton.ApplyStyle(GUIStyle.GetComponentStyle("GUIButtonToggleRight"));
currentCenterCallback = swapFunc(enabledModsList, disabledModsList);
return true;
};
disabledModsList.OnSelected = (frame, o) =>
{
enabledModsList.Deselect();
centerButton.Visible = true;
centerButton.ApplyStyle(GUIStyle.GetComponentStyle("GUIButtonToggleLeft"));
currentCenterCallback = swapFunc(disabledModsList, enabledModsList);
return true;
};
var searchRectT = NewItemRectT(mainLayout, heightScale: 1.0f);
searchRectT.RelativeSize = (0.5f, searchRectT.RelativeSize.Y);
var searchHolder = new GUIFrame(searchRectT, style: null);
var searchBox = new GUITextBox(new RectTransform(Vector2.One, searchHolder.RectTransform), "", createClearButton: true);
var searchTitle = new GUITextBlock(new RectTransform(Vector2.One, searchHolder.RectTransform) {Anchor = Anchor.TopLeft},
textColor: Color.DarkGray * 0.6f,
text: TextManager.Get("Search") + "...",
textAlignment: Alignment.CenterLeft)
{
CanBeFocused = false
};
searchBox.OnSelected += (sender, userdata) => { searchTitle.Visible = false; };
searchBox.OnDeselected += (sender, userdata) => { searchTitle.Visible = searchBox.Text.IsNullOrWhiteSpace(); };
searchBox.OnTextChanged += (sender, str) =>
{
UpdateModListItemVisibility();
return true;
};
modsListFilter = searchBox;
new GUICustomComponent(new RectTransform(Vector2.Zero, content.RectTransform),
onUpdate: (f, component) =>
{
HandleDraggingAcrossModLists(enabledModsList, disabledModsList);
HandleDraggingAcrossModLists(disabledModsList, enabledModsList);
},
onDraw: (spriteBatch, component) =>
{
enabledModsList.DraggedElement?.DrawManually(spriteBatch, true, true);
disabledModsList.DraggedElement?.DrawManually(spriteBatch, true, true);
});
}
private void UpdateModListItemVisibility()
{
string str = modsListFilter.Text;
enabledRegularModsList.Content.Children.Concat(disabledRegularModsList.Content.Children)
.ForEach(c => c.Visible = str.IsNullOrWhiteSpace()
|| (c.UserData is ContentPackage p
&& p.Name.Contains(str, StringComparison.OrdinalIgnoreCase)));
}
private void PopulateInstalledModLists()
{
ContentPackageManager.UpdateContentPackageList();
SwapDropdownValues<CorePackage>(enabledCoreDropdown,
(p) => p.Name,
ContentPackageManager.CorePackages.ToArray(),
ContentPackageManager.EnabledPackages.Core!,
(p) => { });
void addRegularModToList(RegularPackage mod, GUIListBox list)
{
var modFrame = new GUIFrame(new RectTransform((1.0f, 0.08f), list.Content.RectTransform),
style: "ListBoxElement")
{
UserData = mod
};
var frameContent = new GUILayoutGroup(new RectTransform((0.95f, 0.9f), modFrame.RectTransform, Anchor.Center), isHorizontal: true, childAnchor: Anchor.CenterLeft)
{
Stretch = true,
RelativeSpacing = 0.02f
};
var dragIndicator = new GUIButton(new RectTransform((0.5f, 0.5f), frameContent.RectTransform, scaleBasis: ScaleBasis.BothHeight),
style: "GUIDragIndicator")
{
CanBeFocused = false
};
var modNameScissor = new GUIScissorComponent(new RectTransform((0.8f, 1.0f), frameContent.RectTransform))
{
CanBeFocused = false
};
var modName = new GUITextBlock(new RectTransform(Vector2.One, modNameScissor.Content.RectTransform),
text: mod.Name)
{
CanBeFocused = false
};
if (ContentPackageManager.LocalPackages.Contains(mod))
{
var editButton = new GUIButton(new RectTransform(Vector2.One, frameContent.RectTransform, scaleBasis: ScaleBasis.Smallest), "",
style: "WorkshopMenu.EditButton")
{
OnClicked = (button, o) =>
{
ToolBox.OpenFileWithShell(mod.Dir);
return false;
},
ToolTip = TextManager.Get("OpenLocalModInExplorer")
};
}
else if (ContentPackageManager.WorkshopPackages.Contains(mod))
{
var infoButton = new GUIButton(
new RectTransform(Vector2.One, frameContent.RectTransform, scaleBasis: ScaleBasis.Smallest), "",
style: "WorkshopMenu.InfoButton")
{
OnClicked = (button, o) =>
{
TaskPool.Add($"PrepareToShow{mod.SteamWorkshopId}Info", SteamManager.Workshop.GetItem(mod.SteamWorkshopId),
t =>
{
if (!t.TryGetResult(out Steamworks.Ugc.Item? item)) { return; }
if (item is null) { return; }
onInstalledInfoButtonHit(item.Value);
});
return false;
},
ToolTip = TextManager.Get("ViewModDetails")
};
if (!SteamManager.IsInitialized)
{
infoButton.Enabled = false;
}
TaskPool.Add(
$"DetermineUpdateRequired{mod.SteamWorkshopId}",
mod.IsUpToDate(),
t =>
{
if (!t.TryGetResult(out bool isUpToDate)) { return; }
if (!isUpToDate)
{
infoButton.ApplyStyle(GUIStyle.ComponentStyles["WorkshopMenu.InfoButtonUpdate"]);
infoButton.ToolTip = TextManager.Get("ViewModDetailsUpdateAvailable");
}
});
}
}
enabledRegularModsList.ClearChildren();
for (int i = 0; i < ContentPackageManager.EnabledPackages.Regular.Count; i++)
{
var mod = ContentPackageManager.EnabledPackages.Regular[i];
addRegularModToList(mod, enabledRegularModsList);
}
disabledRegularModsList.ClearChildren();
foreach (var mod in ContentPackageManager.RegularPackages)
{
if (ContentPackageManager.EnabledPackages.Regular.Contains(mod)) { continue; }
addRegularModToList(mod, disabledRegularModsList);
}
UpdateModListItemVisibility();
}
private void CreatePopularModsTab(out GUIListBox popularModsList)
{
GUIFrame content = CreateNewContentFrame(Tab.PopularMods);
if (!SteamManager.IsInitialized)
{
tabContents[Tab.PopularMods].Button.Enabled = false;
}
CreateWorkshopItemList(content, out _, out popularModsList, onSelected: PopulateFrameWithItemInfo);
}
private void CreatePublishTab(out GUIListBox selfModsList)
{
GUIFrame content = CreateNewContentFrame(Tab.Publish);
if (!SteamManager.IsInitialized)
{
tabContents[Tab.Publish].Button.Enabled = false;
}
CreateWorkshopItemOrPackageList(content, out _, out selfModsList, onSelected: PopulatePublishTab);
}
public void Apply()
{
ContentPackageManager.EnabledPackages.SetCore(EnabledCorePackage);
ContentPackageManager.EnabledPackages.SetRegular(enabledRegularModsList.Content.Children
.Where(c => c.UserData is RegularPackage).Select(c => (RegularPackage)c.UserData).ToArray());
}
}
}
@@ -0,0 +1,566 @@
#nullable enable
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Net.Mime;
using System.Threading.Tasks;
using Barotrauma.Extensions;
using Barotrauma.IO;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Directory = Barotrauma.IO.Directory;
using ItemOrPackage = Barotrauma.Either<Steamworks.Ugc.Item, Barotrauma.ContentPackage>;
using Path = Barotrauma.IO.Path;
namespace Barotrauma.Steam
{
sealed partial class MutableWorkshopMenu : WorkshopMenu
{
private class LocalThumbnail : IDisposable
{
public Texture2D? Texture { get; private set; } = null;
public bool Loading = true;
public LocalThumbnail(string path)
{
TaskPool.Add($"LocalThumbnail {path}",
Task.Run(async () =>
{
await Task.Yield();
return TextureLoader.FromFile(path, compress: false, mipmap: false);
}),
(t) =>
{
Loading = false;
Task<Texture2D?> texTask = (t as Task<Texture2D?>)!;
if (disposed)
{
texTask.Result?.Dispose();
}
else
{
Texture = texTask.Result;
}
});
}
~LocalThumbnail()
{
Dispose();
}
private bool disposed = false;
public void Dispose()
{
if (disposed) { return; }
disposed = true;
Texture?.Dispose();
}
}
private LocalThumbnail? localThumbnail = null;
private void CreateLocalThumbnail(string path, GUIFrame thumbnailContainer)
{
thumbnailContainer.ClearChildren();
localThumbnail?.Dispose();
localThumbnail = new LocalThumbnail(path);
CreateAsyncThumbnailComponent(thumbnailContainer, () => localThumbnail?.Texture, () => localThumbnail is { Loading: true });
}
private static async Task<(int FileCount, int ByteCount)> GetModDirInfo(string dir, GUITextBlock label)
{
int fileCount = 0;
int byteCount = 0;
var files = Directory.GetFiles(dir, pattern: "*", option: System.IO.SearchOption.AllDirectories);
foreach (var file in files)
{
await Task.Yield();
fileCount++;
byteCount += (int)(new Barotrauma.IO.FileInfo(file).Length);
label.Text = TextManager.GetWithVariables(
"ModDirInfo",
("[filecount]", fileCount.ToString(CultureInfo.InvariantCulture)),
("[size]", MathUtils.GetBytesReadable(byteCount)));
}
return (fileCount, byteCount);
}
private void DeselectPublishedItem()
{
var deselectCarrier = selfModsList.Parent.FindChild(c => c.UserData is ActionCarrier { Id: var id } && id == "deselect");
Action? deselectAction = deselectCarrier.UserData is ActionCarrier { Action: var action }
? action
: null;
deselectAction?.Invoke();
SelectTab(Tab.Publish);
}
private void PopulatePublishTab(ItemOrPackage itemOrPackage, GUIFrame parentFrame)
{
ContentPackageManager.LocalPackages.Refresh();
ContentPackageManager.WorkshopPackages.Refresh();
parentFrame.ClearChildren();
GUILayoutGroup mainLayout = new GUILayoutGroup(new RectTransform(Vector2.One, parentFrame.RectTransform),
childAnchor: Anchor.TopCenter);
Steamworks.Ugc.Item workshopItem = itemOrPackage.TryGet(out Steamworks.Ugc.Item item) ? item : default;
ContentPackage? localPackage = itemOrPackage.TryGet(out ContentPackage package)
? package
: ContentPackageManager.LocalPackages.FirstOrDefault(p => p.SteamWorkshopId == workshopItem.Id);
ContentPackage? workshopPackage
= ContentPackageManager.WorkshopPackages.FirstOrDefault(p => p.SteamWorkshopId == workshopItem.Id);
if (localPackage is null)
{
new GUIFrame(new RectTransform((1.0f, 0.15f), mainLayout.RectTransform), style: null);
//Local copy does not exist; check for Workshop copy
bool workshopCopyExists =
ContentPackageManager.WorkshopPackages.Any(p => p.SteamWorkshopId == workshopItem.Id);
new GUITextBlock(new RectTransform((0.7f, 0.4f), mainLayout.RectTransform),
TextManager.Get(workshopCopyExists ? "LocalCopyRequired" : "ItemInstallRequired"),
wrap: true);
var buttonLayout = new GUILayoutGroup(new RectTransform((0.6f, 0.1f), mainLayout.RectTransform),
isHorizontal: true);
var yesButton = new GUIButton(new RectTransform((0.5f, 1.0f), buttonLayout.RectTransform),
text: TextManager.Get("Yes"))
{
OnClicked = (button, o) =>
{
CoroutineManager.StartCoroutine(MessageBoxCoroutine((currentStepText, messageBox)
=> CreateLocalCopy(currentStepText, workshopItem, parentFrame)),
$"CreateLocalCopy {workshopItem.Id}");
return false;
}
};
var noButton = new GUIButton(new RectTransform((0.5f, 1.0f), buttonLayout.RectTransform),
text: TextManager.Get("No"))
{
OnClicked = (button, o) =>
{
DeselectPublishedItem();
return false;
}
};
}
else
{
if (!ContentPackageManager.LocalPackages.Contains(localPackage))
{
throw new Exception($"Content package \"{localPackage.Name}\" is not a local package!");
}
var selectedTitle =
new GUITextBlock(new RectTransform((1.0f, 0.05f), mainLayout.RectTransform), workshopItem.Title ?? localPackage.Name,
font: GUIStyle.LargeFont);
if (workshopItem.Id != 0)
{
var showInSteamButton = CreateShowInSteamButton(workshopItem, new RectTransform((0.2f, 1.0f), selectedTitle.RectTransform, Anchor.CenterRight));
}
Spacer(mainLayout, height: 0.03f);
var (leftTop, _, rightTop)
= CreateSidebars(mainLayout, leftWidth: 0.2f, centerWidth: 0.01f, rightWidth: 0.79f,
height: 0.4f);
leftTop.Stretch = true;
rightTop.Stretch = true;
Label(leftTop, TextManager.Get("WorkshopItemPreviewImage"), GUIStyle.SubHeadingFont);
string? thumbnailPath = null;
var thumbnailContainer = CreateThumbnailContainer(leftTop, Vector2.One, ScaleBasis.BothWidth);
if (workshopItem.Id != 0)
{
CreateItemThumbnail(workshopItem, taskCancelSrc.Token, thumbnailContainer);
}
var browseThumbnail =
new GUIButton(NewItemRectT(leftTop),
TextManager.Get("WorkshopItemBrowse"), style: "GUIButtonSmall")
{
OnClicked = (button, o) =>
{
FileSelection.ClearFileTypeFilters();
FileSelection.AddFileTypeFilter("PNG", "*.png");
FileSelection.AddFileTypeFilter("JPEG", "*.jpg, *.jpeg");
FileSelection.AddFileTypeFilter("All files", "*.*");
FileSelection.SelectFileTypeFilter("*.png");
FileSelection.CurrentDirectory
= Path.GetFullPath(Path.GetDirectoryName(localPackage.Path)!);
FileSelection.OnFileSelected = (fn) =>
{
thumbnailPath = fn;
CreateLocalThumbnail(thumbnailPath, thumbnailContainer);
};
FileSelection.Open = true;
return false;
}
};
Label(rightTop, TextManager.Get("WorkshopItemTitle"), GUIStyle.SubHeadingFont);
var titleTextBox = new GUITextBox(NewItemRectT(rightTop), workshopItem.Title ?? localPackage.Name);
Label(rightTop, TextManager.Get("WorkshopItemDescription"), GUIStyle.SubHeadingFont);
var descriptionTextBox
= ScrollableTextBox(rightTop, 6.0f, workshopItem.Description ?? string.Empty);
var (leftBottom, _, rightBottom)
= CreateSidebars(mainLayout, leftWidth: 0.49f, centerWidth: 0.01f, rightWidth: 0.5f, height: 0.5f);
leftBottom.Stretch = true;
rightBottom.Stretch = true;
Label(leftBottom, TextManager.Get("WorkshopItemVersion"), GUIStyle.SubHeadingFont);
var modVersion = localPackage.ModVersion;
if (workshopPackage is { ModVersion: { } workshopVersion } &&
modVersion.Equals(workshopVersion, StringComparison.OrdinalIgnoreCase))
{
modVersion = ModProject.IncrementModVersion(modVersion);
}
char[] forbiddenVersionCharacters = { ';', '=' };
var versionTextBox = new GUITextBox(NewItemRectT(leftBottom), modVersion);
versionTextBox.OnTextChanged += (box, text) =>
{
if (text.Any(c => forbiddenVersionCharacters.Contains(c)))
{
foreach (var c in forbiddenVersionCharacters)
{
text = text.Replace($"{c}", "");
}
box.Text = text;
box.Flash(GUIStyle.Red);
}
return true;
};
Label(leftBottom, TextManager.Get("WorkshopItemChangeNote"), GUIStyle.SubHeadingFont);
var changeNoteTextBox = ScrollableTextBox(leftBottom, 5.0f, "");
Label(rightBottom, TextManager.Get("WorkshopItemTags"), GUIStyle.SubHeadingFont);
var tagsList = CreateTagsList(SteamManager.Workshop.Tags, NewItemRectT(rightBottom, heightScale: 4.0f),
canBeFocused: true);
Dictionary<Identifier, GUIButton> tagButtons = tagsList.Content.Children.Cast<GUIButton>()
.Select(b => ((Identifier)b.UserData, b)).ToDictionary();
if (workshopItem.Tags != null)
{
foreach (Identifier tag in workshopItem.Tags.ToIdentifiers())
{
if (tagButtons.TryGetValue(tag, out var button)) { button.Selected = true; }
}
}
GUILayoutGroup visibilityLayout = new GUILayoutGroup(NewItemRectT(rightBottom), isHorizontal: true);
var visibilityLabel = Label(visibilityLayout, TextManager.Get("WorkshopItemVisibility"), GUIStyle.SubHeadingFont);
visibilityLabel.RectTransform.RelativeSize = (0.6f, 1.0f);
visibilityLabel.TextAlignment = Alignment.CenterRight;
Steamworks.Ugc.Visibility visibility = workshopItem.Visibility;
var visibilityDropdown = DropdownEnum(
visibilityLayout,
(v) => TextManager.Get($"WorkshopItemVisibility.{v}"),
visibility,
(v) => visibility = v);
visibilityDropdown.RectTransform.RelativeSize = (0.4f, 1.0f);
var fileInfoLabel = Label(rightBottom, "", GUIStyle.Font, heightScale: 1.0f);
fileInfoLabel.TextAlignment = Alignment.CenterRight;
TaskPool.Add($"FileInfoLabel{workshopItem.Id}", GetModDirInfo(localPackage.Dir, fileInfoLabel), t => { });
GUILayoutGroup buttonLayout = new GUILayoutGroup(NewItemRectT(rightBottom), isHorizontal: true, childAnchor: Anchor.CenterRight);
RectTransform newButtonRectT()
=> new RectTransform((0.4f, 1.0f), buttonLayout.RectTransform);
var publishItemButton = new GUIButton(newButtonRectT(), TextManager.Get(workshopItem.Id != 0 ? "WorkshopItemUpdate" : "WorkshopItemPublish"))
{
OnClicked = (button, o) =>
{
//Reload the package to force hash recalculation
string packageName = localPackage.Name;
localPackage = ContentPackageManager.ReloadContentPackage(localPackage);
if (localPackage is null)
{
throw new Exception($"\"{packageName}\" was removed upon reload");
}
//Set up the Ugc.Editor object that we'll need to publish
Steamworks.Ugc.Editor ugcEditor =
workshopItem.Id == 0
? Steamworks.Ugc.Editor.NewCommunityFile
: new Steamworks.Ugc.Editor(workshopItem.Id);
ugcEditor = ugcEditor.WithTitle(titleTextBox.Text)
.WithDescription(descriptionTextBox.Text)
.WithTags(tagButtons.Where(kvp => kvp.Value.Selected).Select(kvp => kvp.Key.Value))
.WithChangeLog(changeNoteTextBox.Text)
.WithMetaData($"gameversion={localPackage.GameVersion};modversion={versionTextBox.Text}")
.WithVisibility(visibility)
.WithPreviewFile(thumbnailPath);
CoroutineManager.StartCoroutine(
MessageBoxCoroutine((currentStepText, messageBox)
=> PublishItem(currentStepText, messageBox, versionTextBox.Text, ugcEditor, localPackage)));
return false;
}
};
if (workshopItem.Id != 0)
{
var deleteItemButton = new GUIButton(newButtonRectT(), TextManager.Get("WorkshopItemDelete"), color: GUIStyle.Red)
{
OnClicked = (button, o) =>
{
var confirmDeletion = new GUIMessageBox(
headerText: TextManager.Get("WorkshopItemDelete"),
text: TextManager.GetWithVariable("WorkshopItemDeleteVerification", "[itemname]", workshopItem.Title!),
buttons: new[] { TextManager.Get("Yes"), TextManager.Get("No") });
confirmDeletion.Buttons[0].OnClicked = (yesBuffer, o1) =>
{
TaskPool.Add($"Delete{workshopItem.Id}", Steamworks.SteamUGC.DeleteFileAsync(workshopItem.Id),
t =>
{
SteamManager.Workshop.Uninstall(workshopItem);
confirmDeletion.Close();
DeselectPublishedItem();
});
return false;
};
confirmDeletion.Buttons[1].OnClicked = (noButton, o1) =>
{
confirmDeletion.Close();
return false;
};
return false;
},
HoverColor = Color.Lerp(GUIStyle.Red, Color.White, 0.3f),
PressedColor = Color.Lerp(GUIStyle.Red, Color.Black, 0.3f),
};
deleteItemButton.TextBlock.TextColor = Color.Black;
deleteItemButton.TextBlock.HoverTextColor = Color.Black;
}
}
}
private IEnumerable<CoroutineStatus> MessageBoxCoroutine(Func<GUITextBlock, GUIMessageBox, IEnumerable<CoroutineStatus>> subcoroutine)
{
var messageBox = new GUIMessageBox("", "...", buttons: new [] { TextManager.Get("Cancel") });
messageBox.Buttons[0].OnClicked = (button, o) =>
{
messageBox.Close();
return false;
};
var coroutineEval = subcoroutine(messageBox.Text, messageBox).GetEnumerator();
while (true)
{
var status = coroutineEval.Current;
if (messageBox.Closed)
{
yield return CoroutineStatus.Success;
yield break;
}
else if (status == CoroutineStatus.Failure || status == CoroutineStatus.Success)
{
messageBox.Close();
yield return status;
yield break;
}
else
{
yield return status;
}
bool moveNext = true;
try
{
moveNext = coroutineEval.MoveNext();
}
catch (Exception e)
{
DebugConsole.ThrowError($"{e.Message} {e.StackTrace.CleanupStackTrace()}");
messageBox.Close();
}
if (!moveNext)
{
messageBox.Close();
}
}
}
private IEnumerable<CoroutineStatus> CreateLocalCopy(GUITextBlock currentStepText, Steamworks.Ugc.Item workshopItem, GUIFrame parentFrame)
{
ContentPackage? workshopCopy =
ContentPackageManager.WorkshopPackages.FirstOrDefault(p => p.SteamWorkshopId == workshopItem.Id);
if (workshopCopy is null)
{
if (!SteamManager.Workshop.CanBeInstalled(workshopItem))
{
SteamManager.Workshop.NukeDownload(workshopItem);
}
SteamManager.Workshop.DownloadModThenEnqueueInstall(workshopItem);
TaskPool.Add($"Install {workshopItem.Title}",
SteamManager.Workshop.WaitForInstall(workshopItem),
(t) =>
{
ContentPackageManager.WorkshopPackages.Refresh();
});
while (!ContentPackageManager.WorkshopPackages.Any(p => p.SteamWorkshopId == workshopItem.Id))
{
currentStepText.Text = SteamManager.Workshop.CanBeInstalled(workshopItem)
? TextManager.Get("PublishPopupInstall")
: TextManager.GetWithVariable("PublishPopupDownload", "[percentage]", Percentage(workshopItem.DownloadAmount));
yield return new WaitForSeconds(0.5f);
}
workshopCopy =
ContentPackageManager.WorkshopPackages.First(p => p.SteamWorkshopId == workshopItem.Id);
}
bool localCopyMade = false;
TaskPool.Add($"Create local copy {workshopItem.Title}",
SteamManager.Workshop.CreateLocalCopy(workshopCopy),
(t) =>
{
ContentPackageManager.LocalPackages.Refresh();
localCopyMade = true;
});
while (!localCopyMade)
{
currentStepText.Text = TextManager.Get("PublishPopupCreateLocal");
yield return new WaitForSeconds(0.5f);
}
PopulatePublishTab(workshopItem, parentFrame);
yield return CoroutineStatus.Success;
}
private IEnumerable<CoroutineStatus> PublishItem(
GUITextBlock currentStepText, GUIMessageBox messageBox,
string modVersion, Steamworks.Ugc.Editor editor, ContentPackage localPackage)
{
if (!SteamManager.IsInitialized)
{
yield return CoroutineStatus.Failure;
}
bool stagingReady = false;
Exception? stagingException = null;
TaskPool.Add("CreatePublishStagingCopy",
SteamManager.Workshop.CreatePublishStagingCopy(modVersion, localPackage),
(t) =>
{
stagingReady = true;
stagingException = t.Exception?.GetInnermost();
});
currentStepText.Text = TextManager.Get("PublishPopupStaging");
while (!stagingReady) { yield return new WaitForSeconds(0.5f); }
if (stagingException != null)
{
throw new Exception($"Failed to create staging copy: {stagingException.Message} {stagingException.StackTrace.CleanupStackTrace()}");
}
editor = editor
.WithContent(SteamManager.Workshop.PublishStagingDir)
.ForAppId(SteamManager.AppID);
messageBox.Buttons[0].Enabled = false;
Steamworks.Ugc.PublishResult? result = null;
Exception? resultException = null;
TaskPool.Add($"Publishing {localPackage.Name} ({localPackage.SteamWorkshopId})",
editor.SubmitAsync(),
t =>
{
if (t.TryGetResult(out Steamworks.Ugc.PublishResult publishResult))
{
result = publishResult;
}
resultException = t.Exception?.GetInnermost();
});
currentStepText.Text = TextManager.Get("PublishPopupSubmit");
while (!result.HasValue && resultException is null) { yield return new WaitForSeconds(0.5f); }
if (result is { Success: true })
{
var resultId = result.Value.FileId;
Steamworks.Ugc.Item resultItem = new Steamworks.Ugc.Item(resultId);
Task downloadTask = SteamManager.Workshop.ForceRedownload(resultItem);
while (!resultItem.IsInstalled && !downloadTask.IsCompleted)
{
currentStepText.Text = TextManager.GetWithVariable("PublishPopupDownload", "[percentage]", Percentage(resultItem.DownloadAmount));
yield return new WaitForSeconds(0.5f);
}
if (!resultItem.IsInstalled)
{
throw new Exception($"Failed to install item: download task ended with status {downloadTask.Status}, " +
$"exception was {downloadTask.Exception?.GetInnermost()?.ToString().CleanupStackTrace() ?? "[NULL]"}");
}
ContentPackage? pkgToNuke
= ContentPackageManager.WorkshopPackages.FirstOrDefault(p => p.SteamWorkshopId == resultId);
if (pkgToNuke != null)
{
Directory.Delete(pkgToNuke.Dir, recursive: true);
ContentPackageManager.WorkshopPackages.Refresh();
}
bool installed = false;
TaskPool.Add(
"InstallNewlyPublished",
SteamManager.Workshop.WaitForInstall(resultItem),
(t) =>
{
installed = true;
});
while (!installed)
{
currentStepText.Text = TextManager.Get("PublishPopupInstall");
yield return new WaitForSeconds(0.5f);
}
ContentPackageManager.WorkshopPackages.Refresh();
ContentPackageManager.EnabledPackages.RefreshUpdatedMods();
var localModProject = new ModProject(localPackage)
{
SteamWorkshopId = resultId
};
localModProject.DiscardHashAndInstallTime();
localModProject.Save(localPackage.Path);
ContentPackageManager.ReloadContentPackage(localPackage);
DeselectPublishedItem();
if (result.Value.NeedsWorkshopAgreement)
{
SteamManager.OverlayCustomURL(resultItem.Url);
}
new GUIMessageBox(string.Empty, TextManager.GetWithVariable("workshopitempublished", "[itemname]", localPackage.Name));
}
else if (resultException != null)
{
throw new Exception($"Failed to publish item: {resultException.Message} {resultException.StackTrace.CleanupStackTrace()}");
}
else
{
new GUIMessageBox(TextManager.Get("error"), TextManager.GetWithVariable("workshopitempublishfailed", "[itemname]", localPackage.Name));
}
SteamManager.Workshop.DeletePublishStagingCopy();
messageBox.Close();
}
}
}
@@ -0,0 +1,109 @@
#nullable enable
using System;
using System.Collections.Generic;
using System.Linq;
using Barotrauma.Extensions;
using Microsoft.Xna.Framework;
namespace Barotrauma.Steam
{
abstract partial class WorkshopMenu
{
protected static RectTransform NewItemRectT(GUILayoutGroup parent, float heightScale = 1.0f)
=> new RectTransform((1.0f, 0.06f * heightScale), parent.RectTransform, Anchor.CenterLeft);
protected static void Spacer(GUILayoutGroup parent, float height = 0.03f)
{
new GUIFrame(new RectTransform((1.0f, height), parent.RectTransform, Anchor.CenterLeft), style: null);
}
protected static GUITextBlock Label(GUILayoutGroup parent, LocalizedString str, GUIFont font, float heightScale = 1.0f)
{
return new GUITextBlock(NewItemRectT(parent, heightScale), str, font: font);
}
protected static GUITextBox ScrollableTextBox(GUILayoutGroup parent, float heightScale, string text)
{
var containingListBox = new GUIListBox(NewItemRectT(parent, heightScale));
var textBox = new GUITextBox(
new RectTransform(Vector2.One, containingListBox.Content.RectTransform),
"", style: "GUITextBoxNoBorder", wrap: true,
textAlignment: Alignment.TopLeft);
textBox.OnTextChanged += (textBox, text) =>
{
string wrappedText = textBox.TextBlock.WrappedText.Value;
int measuredHeight = (int)textBox.Font.MeasureString(wrappedText).Y;
textBox.RectTransform.NonScaledSize =
(containingListBox.Content.Rect.Width,
Math.Max(measuredHeight, containingListBox.Content.Rect.Height));
containingListBox.UpdateScrollBarSize();
return true;
};
textBox.OnEnterPressed += (textBox, text) =>
{
string str = textBox.Text;
int cursorPos = textBox.CaretIndex;
textBox.Text = $"{str[..cursorPos]}\n{str[cursorPos..]}";
textBox.CaretIndex = cursorPos + 1;
return true;
};
textBox.Text = text;
return textBox;
}
protected static GUIDropDown DropdownEnum<T>(
GUILayoutGroup parent, Func<T, LocalizedString> textFunc, T currentValue,
Action<T> setter) where T : Enum
=> Dropdown(parent, textFunc, (T[])Enum.GetValues(typeof(T)), currentValue, setter);
protected static GUIDropDown Dropdown<T>(
GUILayoutGroup parent, Func<T, LocalizedString> textFunc, IReadOnlyList<T> values, T currentValue,
Action<T> setter, float heightScale = 1.0f)
{
var dropdown = new GUIDropDown(NewItemRectT(parent, heightScale));
SwapDropdownValues(dropdown, textFunc, values, currentValue, setter);
return dropdown;
}
protected static void SwapDropdownValues<T>(
GUIDropDown dropdown, Func<T, LocalizedString> textFunc, IReadOnlyList<T> values, T currentValue,
Action<T> setter)
{
if (dropdown.ListBox.Content.Children.Any(c => !(c.UserData is T)))
{
throw new Exception("SwapValues must preserve the type of the dropdown's userdata");
}
dropdown.OnSelected = null;
dropdown.ClearChildren();
values.ForEach(v => dropdown.AddItem(text: textFunc(v), userData: v));
dropdown.Select(values.IndexOf(currentValue));
dropdown.OnSelected = (dd, obj) =>
{
setter((T)obj);
return true;
};
}
protected static int Round(float v) => (int)MathF.Round(v);
protected static string Percentage(float v) => $"{Round(v * 100)}";
protected struct ActionCarrier
{
public readonly Identifier Id;
public readonly Action Action;
public ActionCarrier(Identifier id, Action action)
{
Id = id;
Action = action;
}
}
protected GUIComponent CreateActionCarrier(GUIComponent parent, Identifier id, Action action)
=> new GUIFrame(new RectTransform(Vector2.Zero, parent.RectTransform), style: null)
{ UserData = new ActionCarrier(id, action) };
}
}
@@ -0,0 +1,12 @@
#nullable enable
namespace Barotrauma.Steam
{
abstract partial class WorkshopMenu
{
public WorkshopMenu(GUIFrame parent)
{
}
}
}