451 lines
20 KiB
C#
451 lines
20 KiB
C#
#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
|
|
{
|
|
public partial class WorkshopMenu
|
|
{
|
|
public enum Tab
|
|
{
|
|
InstalledMods,
|
|
//Overrides, //TODO: implement later
|
|
PopularMods,
|
|
Publish
|
|
}
|
|
|
|
private readonly GUILayoutGroup tabber;
|
|
private readonly Dictionary<Tab, (GUIButton Button, GUIFrame Content)> tabContents;
|
|
|
|
private 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 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 WorkshopMenu(GUIFrame 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);
|
|
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)
|
|
{
|
|
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), "");
|
|
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) =>
|
|
{
|
|
enabledModsList.Content.Children.Concat(disabledModsList.Content.Children)
|
|
.ForEach(c => c.Visible = str.IsNullOrWhiteSpace()
|
|
|| (c.UserData is ContentPackage p
|
|
&& p.Name.Contains(str, StringComparison.OrdinalIgnoreCase)));
|
|
return true;
|
|
};
|
|
|
|
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 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.1f, 0.5f), frameContent.RectTransform, scaleBasis: ScaleBasis.BothHeight),
|
|
style: "GUIDragIndicator")
|
|
{
|
|
CanBeFocused = false
|
|
};
|
|
|
|
var modNameScissor
|
|
= new GUIScissorComponent(new RectTransform((0.8f, 1.0f), frameContent.RectTransform));
|
|
var modName = new GUITextBlock(new RectTransform(Vector2.One, modNameScissor.Content.RectTransform), text: mod.Name);
|
|
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);
|
|
}
|
|
}
|
|
|
|
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());
|
|
}
|
|
}
|
|
}
|