Unstable 0.17.5.0
This commit is contained in:
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
+41
@@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
+474
@@ -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)
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user