Client LuaCsForBarotrauma
InstalledTab.cs
1 #nullable enable
3 using Microsoft.Xna.Framework;
4 using System;
5 using System.Collections.Generic;
6 using System.Linq;
7 using System.Threading.Tasks;
8 using ItemOrPackage = Barotrauma.Either<Steamworks.Ugc.Item, Barotrauma.ContentPackage>;
9 
10 namespace Barotrauma.Steam
11 {
12  sealed partial class MutableWorkshopMenu : WorkshopMenu
13  {
14  private CorePackage EnabledCorePackage => enabledCoreDropdown.SelectedData as CorePackage ?? throw new Exception("Valid core package not selected");
15 
16  public bool ViewingItemDetails { get; private set; }
17 
18  private readonly GUIDropDown enabledCoreDropdown;
19  private readonly GUIListBox enabledRegularModsList;
20  private readonly GUIListBox disabledRegularModsList;
21  private readonly Action<ItemOrPackage> onInstalledInfoButtonHit;
22  private readonly GUITextBox modsListFilter;
23  private readonly Dictionary<Filter, GUITickBox> modsListFilterTickboxes;
24  private readonly Option<GUIButton> bulkUpdateButtonOption;
25 
26  private GUIComponent? draggedElement = null;
27  private GUIListBox? draggedElementOrigin = null;
28 
29  private void UpdateSubscribedModInstalls()
30  {
31  if (!EnableWorkshopSupport) { return; }
32 
33  uint numSubscribedMods = SteamManager.GetNumSubscribedItems();
34  if (numSubscribedMods == memSubscribedModCount) { return; }
35  memSubscribedModCount = numSubscribedMods;
36 
37  var subscribedIds = SteamManager.Workshop.GetSubscribedItemIds();
38  var installedIds = ContentPackageManager.WorkshopPackages
39  .Select(p => p.UgcId)
40  .NotNone()
41  .OfType<SteamWorkshopId>()
42  .Select(id => id.Value)
43  .ToHashSet();
44  foreach (var id in subscribedIds.Where(id2 => !installedIds.Contains(id2)))
45  {
46  Steamworks.Ugc.Item item = new Steamworks.Ugc.Item(id);
47  if (!item.IsDownloading && !SteamManager.Workshop.IsInstalling(item))
48  {
49  SteamManager.Workshop.DownloadModThenEnqueueInstall(item);
50  }
51  }
52 
53  SteamManager.Workshop.DeleteUnsubscribedMods(removedPackages =>
54  {
55  if (removedPackages.Any())
56  {
58  }
59  });
60  }
61 
62  private static (GUILayoutGroup Left, GUIFrame center, GUILayoutGroup Right) CreateSidebars(
63  GUIComponent parent,
64  float leftWidth = 0.3875f,
65  float centerWidth = 0.025f,
66  float rightWidth = 0.5875f,
67  bool split = false,
68  float height = 1.0f)
69  {
70  GUILayoutGroup layout = new GUILayoutGroup(new RectTransform((1.0f, height), parent.RectTransform), isHorizontal: true);
71  GUILayoutGroup left = new GUILayoutGroup(new RectTransform((leftWidth, 1.0f), layout.RectTransform), isHorizontal: false);
72  var center = new GUIFrame(new RectTransform((centerWidth, 1.0f), layout.RectTransform), style: null);
73  if (split)
74  {
75  new GUICustomComponent(new RectTransform(Vector2.One, center.RectTransform),
76  onDraw: (sb, c) =>
77  {
78  sb.DrawLine((c.Rect.Center.X, c.Rect.Top), (c.Rect.Center.X, c.Rect.Bottom), GUIStyle.TextColorDim, 2f);
79  });
80  }
81  GUILayoutGroup right = new GUILayoutGroup(new RectTransform((rightWidth, 1.0f), layout.RectTransform), isHorizontal: false);
82  return (left, center, right);
83  }
84 
85  private static void HandleDraggingAcrossModLists(GUIListBox from, GUIListBox to)
86  {
87  if (to.Rect.Contains(PlayerInput.MousePosition) && from.DraggedElement != null)
88  {
89  //move the dragged elements to the index determined previously
90  var draggedElement = from.DraggedElement;
91 
92  var selected = from.AllSelected.ToList();
93  selected.Sort((a, b) => from.Content.GetChildIndex(a) - from.Content.GetChildIndex(b));
94 
95  float oldCount = to.Content.CountChildren;
96  float newCount = oldCount + selected.Count;
97 
98  var offset = draggedElement.RectTransform.AbsoluteOffset;
99  offset += from.Content.Rect.Location;
100  offset -= to.Content.Rect.Location;
101 
102  for (int i = 0; i < selected.Count; i++)
103  {
104  var c = selected[i];
105  c.Parent.RemoveChild(c);
106  c.RectTransform.Parent = to.Content.RectTransform;
107  c.RectTransform.RepositionChildInHierarchy((int)oldCount+i);
108  }
109 
110  from.DraggedElement = null;
111  from.Deselect();
112  from.RecalculateChildren();
113  from.RectTransform.RecalculateScale(true);
114  to.RecalculateChildren();
115  to.RectTransform.RecalculateScale(true);
116  to.Select(selected);
117 
118  //recalculate the dragged element's offset so it doesn't jump around
119  draggedElement.RectTransform.AbsoluteOffset = offset;
120 
121  to.DraggedElement = draggedElement;
122 
123  to.BarScroll *= (oldCount / newCount);
124  }
125  }
126 
127  private Action? currentSwapFunc = null;
128  private GUISoundType? swapSoundType = null;
129 
130  private void PlaySwapSound()
131  {
132  SoundPlayer.PlayUISound(swapSoundType);
133  }
134 
135  private void SetSwapFunc(GUIListBox from, GUIListBox to)
136  {
137  currentSwapFunc = () =>
138  {
139  to.Deselect();
140  var selected = from.AllSelected.ToArray();
141  foreach (var frame in selected)
142  {
143  frame.Parent.RemoveChild(frame);
144  frame.RectTransform.Parent = to.Content.RectTransform;
145  }
146  from.RecalculateChildren();
147  from.RectTransform.RecalculateScale(true);
148  to.RecalculateChildren();
149  to.RectTransform.RecalculateScale(true);
150  to.Select(selected);
151  };
152 
153  if (to == enabledRegularModsList)
154  {
155  swapSoundType = GUISoundType.Increase;
156  }
157  else if (to == disabledRegularModsList)
158  {
159  swapSoundType = GUISoundType.Decrease;
160  }
161  else
162  {
163  swapSoundType = null;
164  }
165  }
166 
167  private void CreateInstalledModsTab(
168  out GUIDropDown enabledCoreDropdown,
169  out GUIListBox enabledRegularModsList,
170  out GUIListBox disabledRegularModsList,
171  out Action<ItemOrPackage> onInstalledInfoButtonHit,
172  out GUITextBox modsListFilter,
173  out Dictionary<Filter, GUITickBox> modsListFilterTickboxes,
174  out Option<GUIButton> bulkUpdateButton)
175  {
176  GUIFrame content = CreateNewContentFrame(Tab.InstalledMods);
177 
178  CreateWorkshopItemDetailContainer(
179  content,
180  out var outerContainer,
181  onSelected: (itemOrPackage, selectedFrame) =>
182  {
183  if (itemOrPackage.TryGet(out Steamworks.Ugc.Item item)) { PopulateFrameWithItemInfo(item, selectedFrame); }
184  },
185  onDeselected: () => PopulateInstalledModLists(),
186  out onInstalledInfoButtonHit, out var deselect);
187 
188  GUILayoutGroup mainLayout =
189  new GUILayoutGroup(new RectTransform(Vector2.One, outerContainer.Content.RectTransform), childAnchor: Anchor.TopCenter)
190  {
191  Stretch = true,
192  AbsoluteSpacing = GUI.IntScale(5)
193  };
194  mainLayout.RectTransform.SetAsFirstChild();
195 
196  var (topLeft, _, topRight) = CreateSidebars(mainLayout, centerWidth: 0.05f, leftWidth: 0.475f, rightWidth: 0.475f, height: 0.13f);
197  topLeft.Stretch = true;
198  Label(topLeft, TextManager.Get("enabledcore"), GUIStyle.SubHeadingFont, heightScale: 1.0f);
199  enabledCoreDropdown = Dropdown<CorePackage>(topLeft,
200  (p) => p.Name,
201  ContentPackageManager.CorePackages.ToArray(),
202  ContentPackageManager.EnabledPackages.Core!,
203  (p) => { },
204  heightScale: 1.0f / 13.0f);
205  enabledCoreDropdown.AllowNonText = true;
206  Label(topLeft, "", GUIStyle.SubHeadingFont, heightScale: 1.0f);
207  topRight.ChildAnchor = Anchor.CenterLeft;
208 
209  var topRightButtons = new GUILayoutGroup(new RectTransform((1.0f, 0.5f), topRight.RectTransform),
210  isHorizontal: true, childAnchor: Anchor.CenterLeft)
211  {
212  Stretch = true,
213  RelativeSpacing = 0.05f
214  };
215 
216  void padTopRight(float width=1.0f)
217  {
218  new GUIFrame(new RectTransform((width, 1.0f), topRightButtons.RectTransform), style: null);
219  }
220 
221  padTopRight();
222  //TODO: put stuff here
223  padTopRight(width: 3.0f);
224  var refreshListsButton
225  = new GUIButton(
226  new RectTransform(Vector2.One, topRightButtons.RectTransform, scaleBasis: ScaleBasis.BothHeight),
227  text: "", style: "GUIReloadButton")
228  {
229  OnClicked = (b, o) =>
230  {
232  return false;
233  },
234  ToolTip = TextManager.Get("RefreshModLists")
235  };
236 
237  bulkUpdateButton = EnableWorkshopSupport
238  ? Option.Some(
239  new GUIButton(
240  new RectTransform(Vector2.One, topRightButtons.RectTransform, scaleBasis: ScaleBasis.BothHeight),
241  text: "", style: "GUIUpdateButton")
242  {
243  OnClicked = (b,
244  o) =>
245  {
246  BulkDownloader.PrepareUpdates();
247  return false;
248  },
249  Enabled = false
250  })
251  : Option.None;
252  padTopRight(width: 0.1f);
253 
254  var (left, center, right) = CreateSidebars(mainLayout, centerWidth: 0.05f, leftWidth: 0.475f, rightWidth: 0.475f, height: 0.8f);
255  right.ChildAnchor = Anchor.TopRight;
256 
257  //enabled mods
258  var label = Label(left, TextManager.Get("enabledregular"), GUIStyle.SubHeadingFont);
259  new GUIImage(new RectTransform(new Point(label.Rect.Height), label.RectTransform, Anchor.CenterRight), style: "GUIButtonInfo")
260  {
261  ToolTip = TextManager.Get("ModLoadOrderExplanation")
262  };
263 
264  var enabledModsList = new GUIListBox(new RectTransform((1.0f, 0.93f), left.RectTransform))
265  {
266  CurrentDragMode = GUIListBox.DragMode.DragOutsideBox,
267  CurrentSelectMode = GUIListBox.SelectMode.RequireShiftToSelectMultiple,
268  HideDraggedElement = true,
269  PlaySoundOnSelect = true,
270  SoundOnDragStart = GUISoundType.Select,
271  SoundOnDragStop = GUISoundType.Increase,
272  };
273  enabledRegularModsList = enabledModsList;
274 
275  //disabled mods
276  Label(right, TextManager.Get("disabledregular"), GUIStyle.SubHeadingFont);
277  var disabledModsList = new GUIListBox(new RectTransform((1.0f, 0.93f), right.RectTransform))
278  {
279  CurrentDragMode = GUIListBox.DragMode.DragOutsideBox,
280  CurrentSelectMode = GUIListBox.SelectMode.RequireShiftToSelectMultiple,
281  HideDraggedElement = true,
282  PlaySoundOnSelect = true,
283  SoundOnDragStart = GUISoundType.Select,
284  SoundOnDragStop = GUISoundType.Decrease,
285  };
286  disabledRegularModsList = disabledModsList;
287 
288  var centerButton =
289  new GUIButton(
290  new RectTransform(Vector2.One * 0.95f, center.RectTransform, scaleBasis: ScaleBasis.BothWidth,
291  anchor: Anchor.Center),
292  style: "GUIButtonToggleLeft")
293  {
294  PlaySoundOnSelect = false,
295  Visible = false,
296  OnClicked = (button, o) =>
297  {
298  if (currentSwapFunc != null)
299  {
300  PlaySwapSound();
301  currentSwapFunc.Invoke();
302  }
303  return false;
304  }
305  };
306 
307  enabledModsList.OnSelected = (frame, o) =>
308  {
309  disabledModsList.Deselect();
310 
311  centerButton.Visible = true;
312  centerButton.ApplyStyle(GUIStyle.GetComponentStyle("GUIButtonToggleRight"));
313 
314  SetSwapFunc(enabledModsList, disabledModsList);
315 
316  return true;
317  };
318  disabledModsList.OnSelected = (frame, o) =>
319  {
320  enabledModsList.Deselect();
321 
322  centerButton.Visible = true;
323  centerButton.ApplyStyle(GUIStyle.GetComponentStyle("GUIButtonToggleLeft"));
324 
325  SetSwapFunc(disabledModsList, enabledModsList);
326 
327  return true;
328  };
329 
330  var filterContainer = new GUILayoutGroup(NewItemRectT(mainLayout, heightScale: 1.0f), isHorizontal: true)
331  { Stretch = true, RelativeSpacing = 0.01f };
332 
333  void padFilterContainer(float width = 0.25f)
334  => new GUIFrame(new RectTransform((width, 1.0f), filterContainer!.RectTransform), style: null);
335 
336  GUIButton filterLayoutButton(string style)
337  => new GUIButton(
338  new RectTransform(Vector2.One, filterContainer!.RectTransform, scaleBasis: ScaleBasis.BothHeight),
339  "", style: style);
340 
341  padFilterContainer(width: 0.2f);
342  var loadPresetBtn = filterLayoutButton("OpenButton");
343  loadPresetBtn.ToolTip = TextManager.Get("LoadModListPresetHeader");
344  loadPresetBtn.OnClicked = OpenLoadPreset;
345  var savePresetBtn = filterLayoutButton("SaveButton");
346  savePresetBtn.ToolTip = TextManager.Get("SaveModListPresetHeader");
347  savePresetBtn.OnClicked = OpenSavePreset;
348  padFilterContainer(width: 0.05f);
349  var searchRectT = new RectTransform((0.5f, 1.0f), filterContainer.RectTransform);
350  var searchBox = CreateSearchBox(searchRectT);
351  modsListFilter = searchBox;
352 
353  var filterTickboxes = new Dictionary<Filter, GUITickBox>();
354  modsListFilterTickboxes = filterTickboxes;
355 
356  var filterTickboxesDropdown
357  = filterLayoutButton("SetupVisibilityButton");
358  var filterTickboxesContainer
359  = new GUIFrame(new RectTransform((0.3f, 0.2f), content.RectTransform,
360  scaleBasis: ScaleBasis.BothWidth), style: "InnerFrame");
361  var filterTickboxesUpdater
362  = new GUICustomComponent(new RectTransform(Vector2.Zero, content.RectTransform),
363  onUpdate: (f, component) =>
364  {
365  filterTickboxesContainer.Visible = filterTickboxesDropdown.Selected;
366  filterTickboxesContainer.RectTransform.AbsoluteOffset
367  = (filterTickboxesDropdown.Rect.Location - content.Rect.Location)
368  + (filterTickboxesDropdown.Rect.Width / 2, 0)
369  - (filterTickboxesContainer.Rect.Size.ToVector2() * (0.5f, 1.0f)).ToPoint();
370  filterTickboxesContainer.RectTransform.NonScaledSize
371  = new Point(filterTickboxes.Select(tb => (int)tb.Value.Font.MeasureString(tb.Value.GetChild<GUITextBlock>().Text).X).Max(),
372  filterTickboxes.Select(tb => tb.Value.Rect.Height).Aggregate((a,b) => a+b))
373  +(filterTickboxes.Values.First().Rect.Height * 4, filterTickboxes.Values.First().Rect.Height / 2);
374  if (PlayerInput.PrimaryMouseButtonClicked()
375  && !GUI.IsMouseOn(filterTickboxesDropdown)
376  && !GUI.IsMouseOn(filterTickboxesContainer))
377  {
378  filterTickboxesDropdown.Selected = false;
379  }
380  });
381 
382  var filterTickboxesLayout
383  = new GUILayoutGroup(new RectTransform(Vector2.One * 0.95f, filterTickboxesContainer.RectTransform, Anchor.Center));
384 
385  void addFilterTickbox(Filter filter, string? style, bool selected)
386  {
387  var tickbox = new GUITickBox(NewItemRectT(filterTickboxesLayout!, heightScale: 0.5f), "")
388  {
389  Selected = selected,
390  OnSelected = _ =>
391  {
392  UpdateModListItemVisibility();
393  return true;
394  }
395  };
396  filterTickboxes!.Add(filter, tickbox);
397  var text = new GUITextBlock(new RectTransform((1.0f, 1.0f), tickbox.RectTransform, Anchor.CenterRight)
398  {
399  AbsoluteOffset = (-tickbox.Box.Rect.Width * 2, 0),
400  },
401  TextManager.Get($"ModFilter.{filter}"))
402  {
403  CanBeFocused = false
404  };
405  var icon = new GUIFrame(
406  new RectTransform(Vector2.One, text.RectTransform, Anchor.CenterLeft, Pivot.CenterRight,
407  scaleBasis: ScaleBasis.BothHeight), style: style)
408  {
409  CanBeFocused = false
410  };
411  }
412 
413  if (EnableWorkshopSupport)
414  {
415  addFilterTickbox(Filter.ShowLocal, "WorkshopMenu.EditButton", selected: true);
416  addFilterTickbox(Filter.ShowWorkshop, "WorkshopMenu.DownloadedIcon", selected: true);
417  addFilterTickbox(Filter.ShowPublished, "WorkshopMenu.PublishedIcon", selected: true);
418  }
419  addFilterTickbox(Filter.ShowOnlySubs, null, selected: false);
420  addFilterTickbox(Filter.ShowOnlyItemAssemblies, null, selected: false);
421 
422  padFilterContainer();
423 
424  new GUICustomComponent(new RectTransform(Vector2.Zero, content.RectTransform),
425  onUpdate: (f, component) =>
426  {
427  HandleDraggingAcrossModLists(enabledModsList, disabledModsList);
428  HandleDraggingAcrossModLists(disabledModsList, enabledModsList);
429  UpdateDraggingSounds();
430 
431  if (PlayerInput.PrimaryMouseButtonClicked()
432  && !GUI.IsMouseOn(enabledModsList)
433  && !GUI.IsMouseOn(disabledModsList)
434  && GUIContextMenu.CurrentContextMenu is null)
435  {
436  enabledModsList.Deselect();
437  disabledModsList.Deselect();
438  }
439  else if (!PlayerInput.IsCtrlDown() && !PlayerInput.IsShiftDown() && PlayerInput.DoubleClicked())
440  {
441  currentSwapFunc?.Invoke();
442  }
443  },
444  onDraw: (spriteBatch, component) =>
445  {
446  enabledModsList.DraggedElement?.DrawManually(spriteBatch, true, true);
447  disabledModsList.DraggedElement?.DrawManually(spriteBatch, true, true);
448  });
449 
450  void UpdateDraggingSounds()
451  {
452  if (draggedElement != null)
453  {
454  if (enabledModsList.DraggedElement == null && disabledModsList.DraggedElement == null)
455  {
456  SetDragOrigin(null);
457  }
458  CheckDragStopSound(enabledModsList);
459  CheckDragStopSound(disabledModsList);
460  }
461  else if (enabledModsList.DraggedElement != null)
462  {
463  SetDragOrigin(enabledModsList);
464  }
465  else if (disabledModsList.DraggedElement != null)
466  {
467  SetDragOrigin(disabledModsList);
468  }
469 
470  void SetDragOrigin(GUIListBox? listBox)
471  {
472  draggedElement = listBox?.DraggedElement;
473  draggedElementOrigin = listBox;
474  }
475 
476  void CheckDragStopSound(GUIListBox listBox)
477  {
478  listBox.PlaySoundOnDragStop = listBox.DraggedElement != null && draggedElementOrigin != listBox;
479  }
480  }
481  }
482 
483  protected override void UpdateModListItemVisibility()
484  {
485  string str = modsListFilter.Text;
486  enabledRegularModsList.Content.Children.Concat(disabledRegularModsList.Content.Children)
487  .ForEach(c
488  => c.Visible = c.UserData is not ContentPackage p
489  || ModNameMatches(p, str) && ModMatchesTickboxes(p, c));
490  }
491 
492  private bool ModMatchesTickboxes(ContentPackage p, GUIComponent guiItem)
493  {
494  var iconBtn = guiItem.GetChild<GUILayoutGroup>()?.GetAllChildren<GUIButton>().Last();
495 
496  bool matches = false;
497 
498  if (EnableWorkshopSupport)
499  {
500  matches |= modsListFilterTickboxes[Filter.ShowLocal].Selected
501  && ContentPackageManager.LocalPackages.Contains(p);
502 
503  matches |= modsListFilterTickboxes[Filter.ShowPublished].Selected
504  && (ContentPackageManager.WorkshopPackages.Contains(p)
505  && iconBtn?.Style?.Identifier == "WorkshopMenu.PublishedIcon");
506  matches |= modsListFilterTickboxes[Filter.ShowWorkshop].Selected
507  && (ContentPackageManager.WorkshopPackages.Contains(p)
508  && iconBtn?.Style?.Identifier != "WorkshopMenu.PublishedIcon");
509  }
510  else
511  {
512  matches = true;
513  }
514 
515  if (modsListFilterTickboxes[Filter.ShowOnlySubs].Selected
516  && modsListFilterTickboxes[Filter.ShowOnlyItemAssemblies].Selected
517  && p.Files.All(f => f is BaseSubFile || f is ItemAssemblyFile))
518  {
519  //Both the subs-only tickbox and the item-assembly-only tickbox
520  //are enabled, and all files match either of them so show this mod
521  }
522  else if (modsListFilterTickboxes[Filter.ShowOnlySubs].Selected
523  && p.Files.Any(f => f is not BaseSubFile))
524  {
525  matches = false;
526  }
527  else if (modsListFilterTickboxes[Filter.ShowOnlyItemAssemblies].Selected
528  && p.Files.Any(f => f is not ItemAssemblyFile))
529  {
530  matches = false;
531  }
532 
533  return matches;
534  }
535 
536  private void PrepareToShowModInfo(ContentPackage mod)
537  {
538  if (!mod.UgcId.TryUnwrap(out var ugcId)
539  || ugcId is not SteamWorkshopId workshopId) { return; }
540  TaskPool.Add($"PrepareToShow{mod.UgcId}Info", SteamManager.Workshop.GetItem(workshopId.Value),
541  t =>
542  {
543  if (!t.TryGetResult(out Option<Steamworks.Ugc.Item> itemOption)) { return; }
544  if (!itemOption.TryUnwrap(out var item)) { return; }
545  onInstalledInfoButtonHit(item);
546  });
547  }
548 
549  public void PopulateInstalledModLists(bool forceRefreshEnabled = false, bool refreshDisabled = true)
550  {
551  ViewingItemDetails = false;
552  if (bulkUpdateButtonOption.TryUnwrap(out var bulkUpdateButton))
553  {
554  bulkUpdateButton.Enabled = false;
555  bulkUpdateButton.ToolTip = "";
556  }
557  ContentPackageManager.UpdateContentPackageList();
558 
559  var corePackages = ContentPackageManager.CorePackages.ToArray();
560  var currentCore = ContentPackageManager.EnabledPackages.Core!;
561  SwapDropdownValues<CorePackage>(enabledCoreDropdown,
562  (p) => p.Name,
563  corePackages,
564  currentCore,
565  (p) =>
566  {
567  // Manually set dropdown text because
568  // adding buttons to the elements breaks
569  // this part of the dropdown code
570  enabledCoreDropdown.Text = p.Name;
571  enabledCoreDropdown.ButtonTextColor =
572  p.HasAnyErrors
573  ? GUIStyle.Red
574  : GUIStyle.TextColorNormal;
575  });
576 
577  void addButtonForMod(ContentPackage mod, GUILayoutGroup parent)
578  {
579  if (ContentPackageManager.LocalPackages.Contains(mod))
580  {
581  var editButton = new GUIButton(new RectTransform(Vector2.One, parent.RectTransform, scaleBasis: ScaleBasis.Smallest), "",
582  style: "WorkshopMenu.EditButton")
583  {
584  OnClicked = (button, o) =>
585  {
586  ToolBox.OpenFileWithShell(mod.Dir);
587  return false;
588  },
589  ToolTip = TextManager.Get("OpenLocalModInExplorer")
590  };
591  }
592  else if (ContentPackageManager.WorkshopPackages.Contains(mod))
593  {
594  var infoButton = new GUIButton(
595  new RectTransform(Vector2.One, parent.RectTransform, scaleBasis: ScaleBasis.Smallest), "",
596  style: null)
597  {
598  CanBeSelected = false,
599  OnClicked = (button, o) =>
600  {
601  PrepareToShowModInfo(mod);
602  return false;
603  }
604  };
605  if (!EnableWorkshopSupport)
606  {
607  infoButton.Enabled = false;
608  }
609  TaskPool.AddIfNotFound(
610  $"DetermineUpdateRequired{mod.UgcId}",
611  mod.IsUpToDate(),
612  t =>
613  {
614  if (!t.TryGetResult(out bool isUpToDate)) { return; }
615 
616  if (isUpToDate) { return; }
617 
618  infoButton.CanBeSelected = true;
619  infoButton.ApplyStyle(GUIStyle.ComponentStyles["WorkshopMenu.InfoButtonUpdate"]);
620  infoButton.ToolTip = TextManager.Get("ViewModDetailsUpdateAvailable");
621  if (bulkUpdateButtonOption.TryUnwrap(out var bulkUpdateButton))
622  {
623  bulkUpdateButton.Enabled = true;
624  bulkUpdateButton.ToolTip = TextManager.Get("ModUpdatesAvailable");
625  }
626  });
627  }
628  }
629 
630  GUILayoutGroup createBaseModListUi(ContentPackage mod, GUIListBox listBox, float height)
631  {
632  var modFrame = new GUIFrame(new RectTransform((1.0f, height), listBox.Content.RectTransform),
633  style: "ListBoxElement")
634  {
635  UserData = mod
636  };
637 
638  var frameContent = new GUILayoutGroup(new RectTransform((0.95f, 0.9f), modFrame.RectTransform, Anchor.Center), isHorizontal: true, childAnchor: Anchor.CenterLeft)
639  {
640  Stretch = true,
641  RelativeSpacing = 0.02f
642  };
643 
644  var modNameScissor = new GUIScissorComponent(new RectTransform((0.8f, 1.0f), frameContent.RectTransform))
645  {
646  CanBeFocused = false
647  };
648  var modName = new GUITextBlock(new RectTransform(Vector2.One, modNameScissor.Content.RectTransform),
649  text: mod.Name)
650  {
651  CanBeFocused = false
652  };
653  CreateModErrorInfo(mod, modFrame, modName);
654  addButtonForMod(mod, frameContent);
655 
656  return frameContent;
657  }
658 
659  foreach (var element in enabledCoreDropdown.ListBox.Content.Children.ToArray())
660  {
661  enabledCoreDropdown.ListBox.RemoveChild(element);
662  if (element.UserData is not ContentPackage mod) { continue; }
663 
664  createBaseModListUi(mod, enabledCoreDropdown.ListBox, 0.24f);
665  }
666  enabledCoreDropdown.Select(corePackages.IndexOf(currentCore));
667 
668  void addRegularModToList(RegularPackage mod, GUIListBox list)
669  {
670  var frameContent = createBaseModListUi(mod, list, 0.08f);
671 
672  var modFrame = frameContent.Parent;
673 
674  var contextMenuHandler = new GUICustomComponent(new RectTransform(Vector2.Zero, modFrame.RectTransform),
675  onUpdate: (f, component) =>
676  {
677  if (modFrame.Parent?.Parent?.Parent is not GUIListBox parentList || GUI.MouseOn != modFrame || parentList.DraggedElement is not null || !PlayerInput.SecondaryMouseButtonClicked()) { return; }
678  if (!parentList.AllSelected.Contains(modFrame))
679  {
680  parentList.Select(parentList.Content.GetChildIndex(modFrame));
681  }
682  List<ContextMenuOption> contextMenuOptions = new List<ContextMenuOption>();
683  ContentPackage[] selectedMods = parentList.AllSelected.Select(it => it.UserData).OfType<ContentPackage>().ToArray();
684  Identifier swapLabel = ((parentList == enabledRegularModsList, selectedMods.Length > 1) switch
685  {
686  (false, true) => "EnableSelectedWorkshopMods",
687  (false, false) => "EnableWorkshopMod",
688  (true, true) => "DisableSelectedWorkshopMods",
689  (true, false) => "DisableWorkshopMod"
690  }).ToIdentifier();
691  contextMenuOptions.Add(new(swapLabel, isEnabled: true, currentSwapFunc ?? NoOp));
692  if (ContentPackageManager.WorkshopPackages.Contains(mod))
693  {
694  if (selectedMods.Length == 1)
695  {
696  contextMenuOptions.Add(new("ViewWorkshopModDetails".ToIdentifier(), isEnabled: true, () => PrepareToShowModInfo(mod)));
697  contextMenuOptions.Add(new("CopyWorkshopToLocal".ToIdentifier(), isEnabled: true, () => CopyToLocal()));
698  }
699  if (selectedMods.All(ContentPackageManager.WorkshopPackages.Contains))
700  {
701  if (parentList.AllSelected.All(c => c.GetChild<GUILayoutGroup>()?.GetAllChildren<GUIButton>().Last()?.Style?.Identifier == "WorkshopMenu.DownloadedIcon") && selectedMods.Length > 0 && SteamManager.IsInitialized)
702  {
703  contextMenuOptions.Add(new((selectedMods.Length > 1 ? "UnsubscribeFromAllSelected" : "WorkshopItemUnsubscribe").ToIdentifier(), true, () =>
704  {
705  TaskPool.AddIfNotFound("UnsubFromSelected", Task.WhenAll(selectedMods.Select(m => m.UgcId).NotNone().OfType<SteamWorkshopId>().Select(id => SteamManager.Workshop.GetItem(id.Value))), t =>
706  {
707  if (!t.TryGetResult(out Option<Steamworks.Ugc.Item>[]? itemOptions)) { return; }
708  itemOptions.ForEach(io =>
709  {
710  if (!io.TryUnwrap(out Steamworks.Ugc.Item item) || !item.IsSubscribed) { return; }
711  item.Unsubscribe();
712  SteamManager.Workshop.Uninstall(item);
713  PopulateInstalledModLists();
714  });
715  });
716  }));
717  }
718  }
719  }
720  else if (ContentPackageManager.LocalPackages.Contains(mod))
721  {
722  if (selectedMods.Length == 1)
723  {
724  contextMenuOptions.Add(new ContextMenuOption("RenamePackage".ToIdentifier(), isEnabled: true, AskToRenameLocal));
725  }
726  if (selectedMods.All(ContentPackageManager.LocalPackages.Contains))
727  {
728  if (selectedMods.Length > 1)
729  {
730  contextMenuOptions.Add(new("MergeSelectedMods".ToIdentifier(), isEnabled: true, () => ModMerger.AskMerge(selectedMods)));
731  }
732  contextMenuOptions.Add(new("Delete".ToIdentifier(), isEnabled: true, AskToDeleteLocal));
733  }
734  }
735  GUIContextMenu.CreateContextMenu(PlayerInput.MousePosition, ToolBox.LimitString(mod.Name, GUIStyle.SubHeadingFont, GUI.IntScale(300)), null, contextMenuOptions.ToArray());
736  static void NoOp() { }
737  void AskToRenameLocal()
738  {
739  GUIMessageBox msgBox = new(TextManager.Get("RenamePackage"), mod.Name, new LocalizedString[] { TextManager.Get("Confirm"), TextManager.Get("Cancel") }, minSize: new Point(0, GUI.IntScale(195)));
740  GUITextBox textBox = new(new(Vector2.One, msgBox.Content.RectTransform), mod.Name)
741  {
742  OnEnterPressed = (textBox, text) =>
743  {
744  textBox.Text = text.Trim();
745  return true;
746  }
747  };
748  msgBox.Buttons[0].OnClicked += msgBox.Close;
749  msgBox.Buttons[1].OnClicked += (_, _) =>
750  {
751  if (textBox.Text == mod.Name)
752  {
753  msgBox.Close();
754  return true;
755  }
756  if (mod.TryRenameLocal(textBox.Text))
757  {
758  PopulateInstalledModLists();
759  msgBox.Close();
760  return true;
761  }
762  else
763  {
764  textBox.Flash();
765  return false;
766  }
767  };
768  }
769  void CopyToLocal()
770  {
771  if (mod.TryCreateLocalFromWorkshop())
772  {
773  PopulateInstalledModLists();
774  }
775  }
776  void AskToDeleteLocal()
777  {
778  GUIMessageBox msgBox = new(TextManager.Get("DeleteMods"), TextManager.GetWithVariables("DeleteModsConfirm", ("[amount]", selectedMods.Length.ToString())),
779  new LocalizedString[] { TextManager.Get("Confirm"), TextManager.Get("Cancel")}, textAlignment: Alignment.TopCenter);
780  msgBox.Buttons[0].OnClicked += msgBox.Close;
781  msgBox.Buttons[1].OnClicked += (_, _) =>
782  {
783  foreach (ContentPackage mod in selectedMods)
784  {
785  mod.TryDeleteLocal();
786  PopulateInstalledModLists();
787  }
788  msgBox.Close();
789  return true;
790  };
791  }
792  });
793 
794  var dragIndicator = new GUIButton(new RectTransform((0.5f, 0.5f), frameContent.RectTransform, scaleBasis: ScaleBasis.BothHeight),
795  style: "GUIDragIndicator")
796  {
797  CanBeFocused = false
798  };
799  dragIndicator.RectTransform.SetAsFirstChild();
800  }
801 
802  void addRegularModsToList(IEnumerable<RegularPackage> mods, GUIListBox list)
803  {
804  list.ClearChildren();
805  foreach (var mod in mods)
806  {
807  addRegularModToList(mod, list);
808  }
809  }
810 
811  var enabledMods =
812  (forceRefreshEnabled || (enabledRegularModsList.Content.CountChildren + disabledRegularModsList.Content.CountChildren == 0)
813  ? ContentPackageManager.EnabledPackages.Regular
814  : enabledRegularModsList.Content.Children
815  .Select(c => c.UserData)
816  .OfType<RegularPackage>()
817  .Where(p => ContentPackageManager.RegularPackages.Contains(p)))
818  .ToArray();
819  var disabledMods = ContentPackageManager.RegularPackages.Where(p => !enabledMods.Contains(p));
820 
821  addRegularModsToList(enabledMods, enabledRegularModsList);
822  if (refreshDisabled) { addRegularModsToList(disabledMods, disabledRegularModsList); }
823 
824  TaskPool.AddIfNotFound(
825  $"DetermineWorkshopModIcons",
826  SteamManager.Workshop.GetPublishedItems(),
827  t =>
828  {
829  if (!t.TryGetResult(out ISet<Steamworks.Ugc.Item>? items)) { return; }
830  var ids = items.Select(it => it.Id).ToHashSet();
831 
832  foreach (var child in enabledRegularModsList.Content.Children
833  .Concat(disabledRegularModsList.Content.Children))
834  {
835  var mod = child.UserData as RegularPackage;
836  if (mod is null || !ContentPackageManager.WorkshopPackages.Contains(mod)) { continue; }
837  if (!mod.UgcId.TryUnwrap(out var ugcId)) { continue; }
838  if (ugcId is not SteamWorkshopId workshopId) { continue; }
839 
840  var btn = child.GetChild<GUILayoutGroup>()?.GetAllChildren<GUIButton>().Last();
841  if (btn is null) { continue; }
842  if (btn.Style != null) { continue; }
843 
844  btn.ApplyStyle(
845  GUIStyle.GetComponentStyle(
846  ids.Contains(workshopId.Value)
847  ? "WorkshopMenu.PublishedIcon"
848  : "WorkshopMenu.DownloadedIcon"));
849  btn.ToolTip = TextManager.Get(
850  ids.Contains(workshopId.Value)
851  ? "PublishedWorkshopMod"
852  : "DownloadedWorkshopMod");
853  btn.HoverCursor = CursorState.Default;
854  }
855  });
856 
857  UpdateModListItemVisibility();
858  }
859  }
860 }
ImmutableArray< ContentFile > Files
async Task< bool > IsUpToDate()
GUIComponent GetChild(int index)
Definition: GUIComponent.cs:54
RectTransform RectTransform
GUIFrame Content
A frame that contains the contents of the listbox. The frame itself is not rendered.
Definition: GUIListBox.cs:42
void PopulateInstalledModLists(bool forceRefreshEnabled=false, bool refreshDisabled=true)
static GUITextBlock Label(GUILayoutGroup parent, LocalizedString str, GUIFont font, float heightScale=1.0f)
Definition: UiUtil.cs:20
static RectTransform NewItemRectT(GUILayoutGroup parent, float heightScale=1.0f)
GUITextBox CreateSearchBox(RectTransform searchRectT)
Definition: UiUtil.cs:112
GUISoundType
Definition: GUI.cs:21
CursorState
Definition: GUI.cs:40