Improve collection graph controls, version rules and help

This commit is contained in:
cthangover committed 2026-10-26 20:22:58 +03:00
1 parent b6c61ea3df
commit 7ae31f180c
24 files changed
+2201 -311

No files matched your search

+375 -42
View File
@@ -11,6 +11,7 @@ using Godot;
using Cthangover.Mods.Interface.Base.Lists;
using Cthangover.Mods.Interface.Theming;
using Cthangover.Mods.Interface.Tool;
namespace Mods.ModManager
{
/// <summary>
@@ -39,7 +40,7 @@ namespace Mods.ModManager
private Button _newGame;
private Button _apply;
private OptionButton _gameSelect;
private CheckBox _edgesToggle;
private OptionButton _edgesToggle;
private Button _pillProblems;
private Button _pillPlan;
private Label _modsCount;
@@ -86,6 +87,17 @@ namespace Mods.ModManager
private Button _revert;
private Button _delete;
private Label _savesInfo;
private Button _addMod;
private LineEdit _canvasSearch;
private Label _zoomLabel;
private VBoxContainer _versionEditor;
private OptionButton _versionPolicy;
private LineEdit _versionValue;
private Label _versionRule;
private Label _versionFeedback;
private ModCollectionModRef _versionReference;
private bool _fitAfterBuild = true;
private bool _fitQueued;
/// <summary>Drafts created by "Duplicate", mapped to the game they were copied from (offer to copy its saves).</summary>
private readonly Dictionary<string, string> _duplicatedFrom = new(StringComparer.OrdinalIgnoreCase);
@@ -134,6 +146,7 @@ namespace Mods.ModManager
public void OpenCollection(string collectionId)
{
_currentId = collectionId;
_fitAfterBuild = true;
_selectedGroupKey = null;
_selectedModId = null;
// The gallery re-reads the store on every refresh; re-read it here
@@ -163,6 +176,254 @@ namespace Mods.ModManager
UnsubscribeEvents();
}
private void BindGraphiteControls()
{
var tools = "Margin/Root/Tools/";
_addMod = GetNode<Button>(tools + "AddMod");
_addMod.Text = TranslationServer.Translate("mod_manager/collection_add_mod");
_addMod.Pressed += OnAddModPressed;
_canvasSearch = GetNode<LineEdit>(tools + "Search");
_canvasSearch.PlaceholderText = TranslationServer.Translate("mod_manager/collection_canvas_search");
_canvasSearch.TextChanged += _canvas.SetSearch;
_zoomLabel = GetNode<Label>(tools + "Zoom");
GetNode<Button>(tools + "ZoomOut").Pressed += _canvas.ZoomOut;
GetNode<Button>(tools + "ZoomIn").Pressed += _canvas.ZoomIn;
var fit = GetNode<Button>(tools + "Fit");
fit.Text = TranslationServer.Translate("mod_manager/collection_canvas_fit");
fit.Pressed += _canvas.FitWidth;
var arrange = GetNode<Button>(tools + "Arrange");
arrange.Text = TranslationServer.Translate("mod_manager/collection_canvas_arrange");
arrange.Pressed += _canvas.ArrangeGroups;
var focus = GetNode<Button>(tools + "Focus");
focus.Text = TranslationServer.Translate("mod_manager/collection_canvas_focus");
focus.Pressed += _canvas.ShowSelection;
_canvas.SetMiniMap(GetNode<CollectionMiniMap>("Margin/Root/Body/CanvasHost/MiniMap"));
var path = "Margin/Root/Body/Details/DetailScroll/Detail/VersionEditor";
_versionEditor = GetNode<VBoxContainer>(path);
_versionPolicy = GetNode<OptionButton>(path + "/Fields/Policy");
_versionValue = GetNode<LineEdit>(path + "/Fields/Value");
_versionRule = GetNode<Label>(path + "/Rule");
_versionFeedback = GetNode<Label>(path + "/Feedback");
GetNode<Label>(path + "/Caption").Text = TranslationServer.Translate("mod_manager/collection_version_caption");
GetNode<Label>(path + "/Hint").Text = TranslationServer.Translate("mod_manager/collection_version_hint");
var save = GetNode<Button>(path + "/SaveRule");
save.Text = TranslationServer.Translate("mod_manager/collection_version_edit");
save.Pressed += OnSaveVersionPressed;
_versionPolicy.ItemSelected += OnVersionPolicySelected;
_versionValue.TextChanged += OnVersionValueChanged;
_versionRule.AddThemeColorOverride("font_color", UiTokens.Accent);
_versionFeedback.AddThemeColorOverride("font_color", UiTokens.DangerText);
var policies = new[]
{
"mod_manager/collection_version_any", "mod_manager/collection_version_minimum",
"mod_manager/collection_version_exact", "mod_manager/collection_version_branch",
"mod_manager/collection_version_maximum", "mod_manager/collection_version_greater",
"mod_manager/collection_version_less",
};
var operators = new[] { "", ">=", "=", "^", "<=", ">", "<" };
for (var i = 0; i < policies.Length; i++)
{
_versionPolicy.AddItem(TranslationServer.Translate(policies[i]));
_versionPolicy.SetItemMetadata(i, operators[i]);
}
}
private void OnGalleryPressed() => GalleryRequested?.Invoke();
private void OnBuildManagerPressed() => SelectTab("plan");
private void OnAddModPressed()
{
var group = _selectedGroupKey == null ? null : FindGroup(_selectedGroupKey);
if (group != null)
OpenModPicker(group);
}
private void OnCanvasViewChanged()
{
if (_zoomLabel != null)
_zoomLabel.Text = Mathf.RoundToInt(_canvas.Zoom * 100f) + "%";
}
private void OnCanvasFirstFrame()
{
GetTree().ProcessFrame -= OnCanvasFirstFrame;
_fitQueued = false;
_fitAfterBuild = false;
_canvas.FitWidth();
while (_canvas.Zoom < 0.75f)
_canvas.ZoomIn();
_canvas.ShowSelection();
}
private void BindVersionRule(ModCollectionGroup group, string modId)
{
foreach (var reference in group.Mods)
{
if (string.Equals(reference.ModId, modId, StringComparison.OrdinalIgnoreCase))
{
_versionReference = reference;
break;
}
}
if (_versionReference == null)
return;
var owner = EffectiveCollection(_versionReference.CollectionId);
foreach (var mechanic in owner?.Mechanics ?? new List<ModCollectionMechanic>())
{
if (!string.Equals(mechanic.Id, _versionReference.MechanicId, StringComparison.OrdinalIgnoreCase))
continue;
foreach (var raw in mechanic.Mods ?? new List<string>())
{
var requirement = ModDependency.Parse(raw);
if (!requirement.IsValid || !string.Equals(requirement.ModId, modId, StringComparison.OrdinalIgnoreCase))
continue;
for (var i = 0; i < _versionPolicy.ItemCount; i++)
{
if (_versionPolicy.GetItemMetadata(i).AsString() == requirement.Operator)
_versionPolicy.Selected = i;
}
_versionValue.Text = requirement.VersionRaw ?? string.Empty;
if (requirement.VersionRaw == null
&& ModRegistry.Instance.AllDiscoveredMods.TryGetValue(modId, out var installed))
_versionValue.Text = installed.Version ?? string.Empty;
_versionEditor.Visible = true;
_versionEditor.GetNode<Label>("Caption").Text = TranslationServer.Translate("mod_manager/collection_version_caption")
+ " · " + group.Name;
OnVersionPolicySelected(_versionPolicy.Selected);
return;
}
}
}
private void OnVersionPolicySelected(long index)
{
var op = _versionPolicy.GetItemMetadata((int)index).AsString();
_versionValue.Editable = op.Length > 0;
OnVersionValueChanged(_versionValue.Text);
}
private void OnVersionValueChanged(string value)
{
if (_versionReference == null || _versionPolicy.Selected < 0)
return;
var op = _versionPolicy.GetItemMetadata(_versionPolicy.Selected).AsString();
_versionRule.Text = _versionReference.ModId + op + (op.Length > 0 ? value.Trim() : string.Empty);
_versionFeedback.Text = op == "^"
? TranslationServer.Translate("mod_manager/collection_version_branch_hint") : string.Empty;
_versionFeedback.AddThemeColorOverride("font_color", UiTokens.Muted);
foreach (var node in _graph?.Nodes ?? new List<ModCollectionNode>())
{
if (node.ModId != _versionReference.ModId || node.State != ModCollectionNodeState.VersionMismatch)
continue;
var requirements = new List<string>();
foreach (var requirement in node.Requirements)
{
if (!string.IsNullOrEmpty(requirement.Operator))
requirements.Add(requirement.Operator + requirement.VersionRaw);
}
_versionFeedback.Text = string.Format(TranslationServer.Translate("mod_manager/installed_version"), node.InstalledVersion)
+ "\n" + string.Format(TranslationServer.Translate("mod_manager/collection_requires"), string.Join(", ", requirements));
_versionFeedback.AddThemeColorOverride("font_color", UiTokens.DangerText);
break;
}
}
private void OnSaveVersionPressed()
{
if (_versionReference == null || _versionPolicy.Selected < 0)
return;
var group = FindGroup(_selectedGroupKey);
if (group == null)
return;
var reference = _versionReference;
var op = _versionPolicy.GetItemMetadata(_versionPolicy.Selected).AsString();
var owner = EffectiveCollection(reference.CollectionId);
var valid = false;
var changed = false;
foreach (var mechanic in owner?.Mechanics ?? new List<ModCollectionMechanic>())
{
if (!string.Equals(mechanic.Id, reference.MechanicId, StringComparison.OrdinalIgnoreCase))
continue;
var preview = new ModCollectionMechanic { Mods = new List<string>(mechanic.Mods ?? new List<string>()) };
valid = preview.TrySetModVersion(reference.ModId, op, _versionValue.Text);
for (var i = 0; i < preview.Mods.Count; i++)
{
if (preview.Mods[i] != mechanic.Mods[i])
changed = true;
}
break;
}
if (!valid)
{
_versionFeedback.Text = TranslationServer.Translate("mod_manager/collection_version_invalid");
_versionFeedback.AddThemeColorOverride("font_color", UiTokens.DangerText);
return;
}
if (!changed)
return;
var collection = DraftForEdit(group, reference.CollectionId);
if (collection == null)
return;
foreach (var mechanic in collection.Mechanics ?? new List<ModCollectionMechanic>())
{
if (!string.Equals(mechanic.Id, reference.MechanicId, StringComparison.OrdinalIgnoreCase))
continue;
if (!mechanic.TrySetModVersion(reference.ModId, op, _versionValue.Text))
{
_versionFeedback.Text = TranslationServer.Translate("mod_manager/collection_version_invalid");
_versionFeedback.AddThemeColorOverride("font_color", UiTokens.DangerText);
return;
}
Rebuild();
return;
}
}
private void AddModRelationships(string modId)
{
AddInfoLine(TranslationServer.Translate("mod_manager/collection_role_hint"));
foreach (var edge in _graph.Edges)
{
if (string.Equals(edge.FromModId, modId, StringComparison.OrdinalIgnoreCase))
{
var consumer = LookupModTitle(edge.ToModId);
AddInfoLine(string.Format(TranslationServer.Translate("mod_manager/collection_used_by"), consumer));
}
}
foreach (var collection in _collections)
{
if (string.Equals(collection.Id, _currentId, StringComparison.OrdinalIgnoreCase)
|| collection.Kind != ModCollectionKind.Game)
continue;
var effective = EffectiveCollection(collection.Id);
foreach (var mechanic in effective?.Mechanics ?? new List<ModCollectionMechanic>())
{
foreach (var raw in mechanic.Mods ?? new List<string>())
{
if (!string.Equals(ParseModId(raw), modId, StringComparison.OrdinalIgnoreCase))
continue;
AddInfoLine(string.Format(TranslationServer.Translate("mod_manager/collection_other_role"),
CollectionText.Resolve(effective.GetLocalizedName(TranslationServer.GetLocale())),
CollectionText.Resolve(mechanic.GetLocalizedName(TranslationServer.GetLocale()))));
break;
}
}
}
}
private string LookupModTitle(string modId)
{
foreach (var node in _graph.Nodes)
{
if (string.Equals(node.ModId, modId, StringComparison.OrdinalIgnoreCase))
return node.Title;
}
return modId;
}
private void BindSceneNodes()
{
_close = GetNode<Button>("Margin/Root/Header/Close");
@@ -170,7 +431,7 @@ namespace Mods.ModManager
_addMechanic = GetNode<Button>("Margin/Root/Header/AddMechanic");
_newGame = GetNode<Button>("Margin/Root/Header/NewGame");
_gameSelect = GetNode<OptionButton>("Margin/Root/Header/GameSelect");
_edgesToggle = GetNode<CheckBox>("Margin/Root/Header/EdgesToggle");
_edgesToggle = GetNode<OptionButton>("Margin/Root/Header/EdgesToggle");
_pillProblems = GetNode<Button>("Margin/Root/Header/Problems");
_pillPlan = GetNode<Button>("Margin/Root/Header/Plan");
_modsCount = GetNode<Label>("Margin/Root/Header/ModsCount");
@@ -180,7 +441,7 @@ namespace Mods.ModManager
_save = GetNode<Button>("Margin/Root/Header/Save");
_bannerPanel = GetNode<PanelContainer>("Margin/Root/Banner");
_bannerLabel = GetNode<Label>("Margin/Root/Banner/BannerLabel");
_canvas = GetNode<CollectionSchemeCanvas>("Margin/Root/Body/CanvasScroll/SchemeCanvas");
_canvas = GetNode<CollectionSchemeCanvas>("Margin/Root/Body/CanvasHost/CanvasScroll/SchemeCanvas");
var detail = "Margin/Root/Body/Details/DetailScroll/Detail/";
_detailTitle = GetNode<Label>(detail + "DetailTitle");
@@ -199,10 +460,6 @@ namespace Mods.ModManager
_newGame.Text = TranslationServer.Translate("mod_manager/collection_new_game");
_pillProblems.Text = TranslationServer.Translate("mod_manager/collection_problems");
_pillPlan.Text = TranslationServer.Translate("mod_manager/collection_plan");
_close.TooltipText = _close.Text;
_refresh.TooltipText = _refresh.Text;
_addMechanic.TooltipText = _addMechanic.Text;
_newGame.TooltipText = _newGame.Text;
_gameSelect.TooltipText = TranslationServer.Translate("mod_manager/collection_game_tooltip");
_pillProblems.TooltipText = TranslationServer.Translate("mod_manager/collection_problems_tooltip");
_pillPlan.TooltipText = TranslationServer.Translate("mod_manager/collection_plan_tooltip");
@@ -215,21 +472,34 @@ namespace Mods.ModManager
_tabPlan.Text = TranslationServer.Translate("mod_manager/collection_tab_plan");
_json.TooltipText = TranslationServer.Translate("mod_manager/collection_json_tooltip");
_reset.TooltipText = TranslationServer.Translate("mod_manager/collection_reset_tooltip");
_save.TooltipText = _save.Text;
_tabInspector.TooltipText = _tabInspector.Text;
_tabProblems.TooltipText = _tabProblems.Text;
_tabPlan.TooltipText = _tabPlan.Text;
_apply.Text = TranslationServer.Translate("mod_manager/collection_apply");
_edgesToggle.Text = TranslationServer.Translate("mod_manager/collection_links");
_apply.TooltipText = _apply.Text;
_edgesToggle.TooltipText = _edgesToggle.Text;
_edgesToggle.AddItem(TranslationServer.Translate("mod_manager/collection_links_selected"));
_edgesToggle.AddItem(TranslationServer.Translate("mod_manager/collection_links_all"));
_edgesToggle.AddItem(TranslationServer.Translate("mod_manager/collection_links_none"));
var orderLegend = GetNode<Label>("Margin/Root/OrderLegend");
orderLegend.Text = TranslationServer.Translate("mod_manager/collection_order_legend");
orderLegend.AddThemeColorOverride("font_color", UiTokens.Faint);
orderLegend.SetMeta(ModHelpPanel.HintTranslationMetadata, "collection_dependencies");
ModHelpPanel.Attach(_refresh, "refresh");
ModHelpPanel.Attach(_addMechanic, "collection_add_mechanic");
ModHelpPanel.Attach(_newGame, "collection_new");
ModHelpPanel.Attach(_gameSelect, "collection_game");
ModHelpPanel.Attach(_pillProblems, "collection_problems");
ModHelpPanel.Attach(_pillPlan, "collection_plan");
ModHelpPanel.Attach(_json, "collection_json");
ModHelpPanel.Attach(_reset, "collection_reset");
ModHelpPanel.Attach(_apply, "collection_apply");
ModHelpPanel.Attach(_edgesToggle, "collection_links");
ModHelpPanel.Attach(GetNode<Label>("Margin/Root/OrderLegend"), "load_order");
var header = _json.GetParent();
_savesInfo = new Label { VerticalAlignment = VerticalAlignment.Center };
_savesInfo.AddThemeColorOverride("font_color", UiTokens.Muted);
_duplicate = new Button { Text = TranslationServer.Translate("mod_manager/collection_duplicate"), TooltipText = TranslationServer.Translate("mod_manager/collection_duplicate") };
_revert = new Button { Text = TranslationServer.Translate("mod_manager/collection_revert"), TooltipText = TranslationServer.Translate("mod_manager/collection_revert") };
_delete = new Button { Text = TranslationServer.Translate("mod_manager/collection_delete"), TooltipText = TranslationServer.Translate("mod_manager/collection_delete") };
_duplicate = new Button { Text = TranslationServer.Translate("mod_manager/collection_duplicate") };
_revert = new Button { Text = TranslationServer.Translate("mod_manager/collection_revert") };
_delete = new Button { Text = TranslationServer.Translate("mod_manager/collection_delete") };
ModHelpPanel.Attach(_delete, "collection_delete");
foreach (var control in new Control[] { _savesInfo, _duplicate, _revert, _delete })
{
header.AddChild(control);
@@ -252,6 +522,7 @@ namespace Mods.ModManager
_bannerLabel.AddThemeColorOverride("font_color", UiTokens.Warning);
_bannerPanel.Visible = false;
BindGraphiteControls();
BuildCrumbs();
BuildBuildBar();
if (Embedded)
@@ -271,7 +542,7 @@ namespace Mods.ModManager
_crumbCollections = UiKit.Button(TranslationServer.Translate("mod_manager/hub_tab_collections"));
_crumbCollections.AddThemeFontSizeOverride("font_size", UiTokens.FontBodySmall);
_crumbCollections.Pressed += () => GalleryRequested?.Invoke();
_crumbCollections.Pressed += OnGalleryPressed;
_crumbCollections.TooltipText = TranslationServer.Translate("mod_manager/gallery_crumb");
_crumbs.AddChild(_crumbCollections);
_crumbs.AddChild(UiKit.Label("›", UiTokens.FontBodySmall, UiTokens.Faint));
@@ -281,10 +552,19 @@ namespace Mods.ModManager
_crumbs.AddChild(UiKit.Label(TranslationServer.Translate("mod_manager/collection_crumb_scheme"), UiTokens.FontBodySmall, UiTokens.Body));
_crumbs.AddChild(UiKit.Spacer());
GetNode("Margin/Root/Header/AddMechanic").Reparent(_crumbs);
GetNode("Margin/Root/Header/EdgesToggle").Reparent(_crumbs);
GetNode("Margin/Root/Header/Problems").Reparent(_crumbs);
GetNode("Margin/Root/Header/ModsCount").Reparent(_crumbs);
var header = GetNode<HBoxContainer>("Margin/Root/Header");
_crumbs.Reparent(header);
header.MoveChild(_crumbs, 0);
_crumbs.SizeFlagsHorizontal = SizeFlags.ExpandFill;
GetNode<Control>("Margin/Root/Header/Spacer").Visible = false;
var tools = GetNode<HBoxContainer>("Margin/Root/Tools");
_addMechanic.Reparent(tools);
tools.MoveChild(_addMechanic, 0);
_edgesToggle.Reparent(tools);
tools.MoveChild(_edgesToggle, 2);
_pillProblems.Reparent(tools);
tools.MoveChild(_pillProblems, 3);
}
/// <summary>
@@ -317,13 +597,13 @@ namespace Mods.ModManager
row.AddChild(_buildHint);
row.AddChild(UiKit.Spacer());
_buildManager = UiKit.Button(TranslationServer.Translate("mod_manager/build_open_manager"));
_buildManager.TooltipText = _buildManager.Text;
_buildManager.Pressed += () => OpenManagerRequested?.Invoke(null);
_buildManager = UiKit.Button(TranslationServer.Translate("mod_manager/collection_tab_problems"));
ModHelpPanel.Attach(_buildManager, "collection_plan");
_buildManager.Pressed += OnBuildManagerPressed;
row.AddChild(_buildManager);
_buildApply = UiKit.Button(TranslationServer.Translate("mod_manager/collection_apply"), ButtonKind.Primary);
_buildApply.TooltipText = _buildApply.Text;
ModHelpPanel.Attach(_buildApply, "collection_apply");
_buildApply.Pressed += OnApplyPressed;
row.AddChild(_buildApply);
}
@@ -345,6 +625,7 @@ namespace Mods.ModManager
_close.Visible = false;
_refresh.Visible = false;
_newGame.Visible = false;
_pillPlan.Visible = false;
}
private void SubscribeEvents()
@@ -353,7 +634,7 @@ namespace Mods.ModManager
_refresh.Pressed += OnRefreshPressed;
_addMechanic.Pressed += OnAddMechanicPressed;
_newGame.Pressed += OnNewGamePressed;
_edgesToggle.Toggled += _canvas.SetEdgesVisible;
_edgesToggle.ItemSelected += _canvas.SetEdgeMode;
_pillProblems.Pressed += () => SelectTab("problems");
_pillPlan.Pressed += () => SelectTab("plan");
_json.Pressed += OpenJsonDialog;
@@ -369,6 +650,7 @@ namespace Mods.ModManager
_canvas.ModSelected += OnModSelected;
_canvas.GroupCommand += OnGroupCommand;
_canvas.LayoutChanged += OnLayoutChanged;
_canvas.ViewChanged += OnCanvasViewChanged;
ModDownloader.OnProgress += OnInstallProgress;
CatalogService.OnRefreshed += OnCatalogRefreshed;
@@ -378,6 +660,8 @@ namespace Mods.ModManager
{
ModDownloader.OnProgress -= OnInstallProgress;
CatalogService.OnRefreshed -= OnCatalogRefreshed;
if (_fitQueued)
GetTree().ProcessFrame -= OnCanvasFirstFrame;
}
/* ═══════════════ Data flow ═══════════════ */
@@ -396,12 +680,37 @@ namespace Mods.ModManager
private void Rebuild()
{
_graph = Resolve();
foreach (var node in _graph.Nodes)
{
if (ModRegistry.Instance.AllDiscoveredMods.TryGetValue(node.ModId, out var info))
node.Title = info.DisplayTitle;
}
_layout = _currentId == null ? new ModCollectionLayout() : CollectionLayoutStore.Load(_currentId);
_plan = ModCollectionPlan.Build(_graph, CatalogService.CachedEntries ?? new List<CatalogExportEntry>());
if (_selectedGroupKey == null && _graph.Groups.Count > 0)
{
var initial = _graph.Groups[0];
foreach (var group in _graph.Groups)
{
if (group.Role == "battle" && group.Selected && group.Mods.Count > 0)
{
initial = group;
break;
}
}
_selectedGroupKey = initial.Key;
_selectedModId = initial.Mods.Count > 0 ? initial.Mods[0].ModId : null;
}
_canvas.Build(_graph, _layout);
_canvas.SetSelection(_selectedGroupKey, _selectedModId);
_canvas.SetEdgesVisible(_edgesToggle.ButtonPressed);
_canvas.SetEdgeMode(_edgesToggle.Selected);
if (_fitAfterBuild && !_fitQueued)
{
_fitQueued = true;
GetTree().ProcessFrame += OnCanvasFirstFrame;
}
UpdateHeader();
UpdateInspector();
@@ -530,7 +839,7 @@ namespace Mods.ModManager
var current = EffectiveCollection(_currentId);
var isGame = current?.Kind == ModCollectionKind.Game;
_savesInfo.Visible = isGame;
_savesInfo.Visible = isGame && !Embedded;
if (isGame)
_savesInfo.Text = string.Format(TranslationServer.Translate("mod_manager/collection_saves"), SaveService.ListSaves(_currentId).Count);
_duplicate.Disabled = current == null;
@@ -559,6 +868,11 @@ namespace Mods.ModManager
_pillPlan.Text = TranslationServer.Translate("mod_manager/collection_plan");
return;
}
if (errors > 0)
{
_pillPlan.Text = $"{TranslationServer.Translate("mod_manager/collection_problems")}: {errors}";
return;
}
var parts = new List<string>();
if (_plan.Install.Count > 0)
@@ -571,7 +885,7 @@ namespace Mods.ModManager
}
private int CountErrors()
=> _graph?.Problems?.Count ?? 0;
=> (_graph?.Problems?.Count ?? 0) + (_plan?.Missing.Count ?? 0) + (_plan?.Mismatches.Count ?? 0);
private void OnGameSelected(long index)
{
@@ -580,6 +894,7 @@ namespace Mods.ModManager
return;
_currentId = id;
_fitAfterBuild = true;
_selectedGroupKey = null;
_selectedModId = null;
Rebuild();
@@ -587,6 +902,7 @@ namespace Mods.ModManager
private void OnNewGamePressed()
{
_fitAfterBuild = true;
var id = UniqueGameId();
var draft = new ModCollection
{
@@ -753,6 +1069,8 @@ namespace Mods.ModManager
_tabPlan.ButtonPressed = _tab == "plan";
ClearChildren(_detailExtra);
_versionEditor.Visible = false;
_versionReference = null;
_apply.Visible = !Embedded && _tab == "plan";
if (_tab == "problems")
@@ -801,6 +1119,7 @@ namespace Mods.ModManager
_detailMeta.Text = string.Join(" · ", metaParts);
_detailDescription.Text = string.IsNullOrEmpty(group.Description) ? "—" : group.Description;
AddInfoLine(TranslationServer.Translate("mod_manager/collection_role_hint"));
var collection = EffectiveCollection(group.CollectionId);
if (collection != null)
@@ -836,18 +1155,23 @@ namespace Mods.ModManager
{
var node = _graph?.Nodes?.FirstOrDefault(n => string.Equals(n.ModId, modId, StringComparison.OrdinalIgnoreCase));
_detailTitle.Text = node?.Title ?? modId;
_detailMeta.Text = $"{modId} · {CollectionStateNames.Translate(node?.State ?? ModCollectionNodeState.Missing)}";
_detailMeta.Text = $"{modId} · {node?.InstalledVersion ?? "—"} · {CollectionStateNames.Translate(node?.State ?? ModCollectionNodeState.Missing)}";
_detailDescription.Text = LookupModDescription(modId);
if (node != null && !string.IsNullOrEmpty(node.RequiredVersion))
AddInfoLine($"{TranslationServer.Translate("mod_manager/collection_requires")}: {node.RequiredVersion}");
if (node != null && !string.IsNullOrEmpty(node.InstalledVersion))
AddInfoLine($"{TranslationServer.Translate("mod_manager/installed_version")}: {node.InstalledVersion}");
var group = _selectedGroupKey == null ? null : FindGroup(_selectedGroupKey);
if (group == null && node != null)
{
foreach (var requirement in node.Requirements)
{
if (!string.IsNullOrEmpty(requirement.Operator))
AddInfoLine(string.Format(TranslationServer.Translate("mod_manager/collection_requires"), requirement.ToString()));
}
}
if (group != null)
{
AddInfoLine($"{TranslationServer.Translate("mod_manager/collection_in_group")}: {group.Name}");
BindVersionRule(group, modId);
AddModRelationships(modId);
var remove = MakeInspectorButton(TranslationServer.Translate("mod_manager/collection_remove_mod"),
() => RemoveModFromGroup(group, modId), true);
@@ -888,13 +1212,16 @@ namespace Mods.ModManager
_detailMeta.Text = string.Empty;
_detailDescription.Text = string.Empty;
if (_graph == null || _graph.Problems.Count == 0)
if (_graph == null || CountErrors() == 0)
{
_detailExtra.AddChild(UiKit.Label(TranslationServer.Translate("mod_manager/collection_problems_none"), UiTokens.FontBody, UiTokens.SuccessText));
UpdateApplyButton();
return;
}
AddPlanSection("mod_manager/collection_missing", _plan?.Missing, UiTokens.DangerText, findInCatalog: true);
AddPlanSection("mod_manager/collection_mismatch", _plan?.Mismatches, UiTokens.DangerText, findInCatalog: true);
foreach (var problem in _graph.Problems)
{
var row = UiKit.Column(UiTokens.Space1);
@@ -975,7 +1302,7 @@ namespace Mods.ModManager
AddPlanSection("mod_manager/collection_install_count", _plan.Install.Select(i => i.ModId), UiTokens.Info);
AddPlanSection("mod_manager/collection_enable_count", _plan.Enable, UiTokens.Warning);
AddPlanSection("mod_manager/collection_missing", _plan.Missing, UiTokens.Danger, findInCatalog: true);
AddPlanSection("mod_manager/collection_mismatch", _plan.Mismatches, UiTokens.Warning);
AddPlanSection("mod_manager/collection_mismatch", _plan.Mismatches, UiTokens.Warning, findInCatalog: true);
UpdateApplyButton();
}
@@ -1013,7 +1340,7 @@ namespace Mods.ModManager
var canApply = !_installing
&& _plan != null
&& !_plan.IsEmpty
&& _plan.Missing.Count == 0
&& !_plan.HasProblems
&& CountErrors() == 0;
_apply.Disabled = !canApply;
if (_buildApply != null)
@@ -1048,7 +1375,7 @@ namespace Mods.ModManager
_buildHint.Text = TranslationServer.Translate(blocked
? "mod_manager/build_hint_problems"
: "mod_manager/collection_plan_ready");
: _overrides.Count > 0 ? "mod_manager/collection_unsaved" : "mod_manager/collection_plan_ready");
_buildHint.AddThemeColorOverride("font_color", blocked ? UiTokens.Warning : UiTokens.SuccessText);
}
@@ -1095,7 +1422,6 @@ namespace Mods.ModManager
private Button MakeInspectorButton(string text, Action action, bool danger = false)
{
var button = UiKit.Button(text, danger ? ButtonKind.Danger : ButtonKind.Secondary);
button.TooltipText = text;
button.AddThemeFontSizeOverride("font_size", UiTokens.FontSmall);
button.Pressed += action;
return button;
@@ -1606,12 +1932,17 @@ namespace Mods.ModManager
if (collection == null)
return;
var view = new TextEdit
var view = new CodeEdit
{
Text = JsonSerializer.Serialize(collection, JsonOptionsProvider.WriteIndentedOptions),
Editable = true,
GuttersDrawLineNumbers = true,
SyntaxHighlighter = SourceSyntax.CreateJsonHighlighter(),
IndentUseSpaces = true,
IndentSize = 2,
CustomMinimumSize = new Vector2(620, 420),
};
view.AddThemeFontOverride("font", ToolWindow.GetMonospaceFont());
var dialog = new ConfirmationDialog
{
@@ -1798,6 +2129,8 @@ namespace Mods.ModManager
}
include.Ref = forkId;
if (string.Equals(_selectedGroupKey, group.Key, StringComparison.OrdinalIgnoreCase))
_selectedGroupKey = $"i:{group.ParentCollectionId}:{forkId}";
return Draft(forkId);
}
@@ -1849,7 +2182,7 @@ namespace Mods.ModManager
private async void OnApplyPressed()
{
if (_installing || _plan == null || _plan.IsEmpty
|| _plan.Missing.Count > 0 || CountErrors() > 0)
|| _plan.HasProblems || CountErrors() > 0)
return;
var plan = _plan;