From 677c713cff483ecd4ce4d7c4ed14786f870189d2 Mon Sep 17 00:00:00 2001
From: MgAl2O4 <51430403+MgAl2O4@users.noreply.github.com>
Date: Wed, 29 Sep 2021 16:22:51 -0400
Subject: [PATCH 1/5] added plugin categories
---
.../Internal/PluginCategoryManager.cs | 480 ++++++++++++++++++
.../Internal/Windows/PluginInstallerWindow.cs | 178 ++++++-
.../Plugin/Internal/Types/PluginManifest.cs | 6 +
3 files changed, 662 insertions(+), 2 deletions(-)
create mode 100644 Dalamud/Interface/Internal/PluginCategoryManager.cs
diff --git a/Dalamud/Interface/Internal/PluginCategoryManager.cs b/Dalamud/Interface/Internal/PluginCategoryManager.cs
new file mode 100644
index 000000000..152e2e7f3
--- /dev/null
+++ b/Dalamud/Interface/Internal/PluginCategoryManager.cs
@@ -0,0 +1,480 @@
+using System;
+using System.Collections.Generic;
+
+using CheapLoc;
+using Dalamud.Plugin.Internal.Types;
+
+namespace Dalamud.Interface.Internal
+{
+ ///
+ /// Manage category filters for PluginInstallerWindow.
+ ///
+ internal class PluginCategoryManager
+ {
+ ///
+ /// First categoryId for tag based categories.
+ ///
+ public const int FirstTagBasedCategoryId = 100;
+
+ private readonly CategoryInfo[] categoryList =
+ {
+ new(0, "special.all", () => Locs.Category_All),
+ new(10, "special.devInstalled", () => Locs.Category_DevInstalled),
+ new(11, "special.devIconTester", () => Locs.Category_IconTester),
+ new(FirstTagBasedCategoryId + 0, "other", () => Locs.Category_Other),
+ new(FirstTagBasedCategoryId + 1, "jobs", () => Locs.Category_Jobs),
+ new(FirstTagBasedCategoryId + 2, "ui", () => Locs.Category_UI),
+ new(FirstTagBasedCategoryId + 3, "minigame", () => Locs.Category_MiniGames),
+ new(FirstTagBasedCategoryId + 4, "inventory", () => Locs.Category_Inventory),
+ new(FirstTagBasedCategoryId + 5, "sound", () => Locs.Category_Sound),
+ new(FirstTagBasedCategoryId + 6, "social", () => Locs.Category_Social),
+
+ // order doesn't matter, all tag driven categories should have Id >= FirstTagBasedCategoryId
+ };
+
+ private GroupInfo[] groupList =
+ {
+ new(GroupKind.DevTools, () => Locs.Group_DevTools, 10, 11),
+ new(GroupKind.Installed, () => Locs.Group_Installed, 0),
+ new(GroupKind.Available, () => Locs.Group_Available, 0),
+
+ // order important, used for drawing, keep in sync with defaults for currentGroupIdx
+ };
+
+ private int currentGroupIdx = 2;
+ private int currentCategoryIdx = 0;
+ private bool isContentDirty;
+
+ private Dictionary mapPluginCategories = new();
+ private List highlightedCategoryIds = new();
+
+#if DEBUG
+ // temp - hardcode some tag values for testing, idk what most of them does so it's probably not very accurate :D
+ private Dictionary mapPluginTagTesting = new()
+ {
+ ["accuratecountdown"] = new string[] { "UI" },
+ ["adventurerinneed"] = new string[] { "UI" },
+ ["aethersense"] = new string[] { "Other" },
+ ["autovisor"] = new string[] { "UI" },
+ ["betterpartyfinder"] = new string[] { "UI" },
+ ["browserhost.plugin"] = new string[] { "UI" },
+ ["burnttoast"] = new string[] { "UI" },
+ ["chatalerts"] = new string[] { "social" },
+ ["chatbubbles"] = new string[] { "social" },
+ ["chatcoordinates"] = new string[] { "social" },
+ ["chatextender"] = new string[] { "social" },
+ ["chattranslator"] = new string[] { "social" },
+ ["compass"] = new string[] { "UI" },
+ ["dalamud.charactersync"] = new string[] { "other" },
+ ["dalamud.discordbridge"] = new string[] { "other" },
+ ["dalamud.loadingimage"] = new string[] { "other" },
+ ["dalamud.richpresence"] = new string[] { "other" },
+ ["dalamudvox"] = new string[] { "Other" },
+ ["damageinfoplugin"] = new string[] { "UI" },
+ ["deepdungeondex"] = new string[] { "UI" },
+ ["easyeyes"] = new string[] { "UI" },
+ ["engagetimer"] = new string[] { "jobs" },
+ ["expandedsearchinfo"] = new string[] { "UI" },
+ ["fantasyplayer.dalamud"] = new string[] { "UI" },
+ ["fauxhollowssolver"] = new string[] { "minigames" },
+ ["fcnamecolor"] = new string[] { "UI", "social" },
+ ["fpsplugin"] = new string[] { "UI" },
+ ["gatherbuddy"] = new string[] { "jobs" },
+ ["gentletouch"] = new string[] { "UI" },
+ ["globetrotter"] = new string[] { "UI" },
+ ["goodmemory"] = new string[] { "UI" },
+ ["harphero"] = new string[] { "jobs" },
+ ["housemate"] = new string[] { "UI" },
+ ["itemsearchplugin"] = new string[] { "inventory" },
+ ["jobbars"] = new string[] { "jobs" },
+ ["jobicons"] = new string[] { "jobs" },
+ ["kapture"] = new string[] { "UI" },
+ ["kingdomheartsplugin"] = new string[] { "UI" },
+ ["macrochain"] = new string[] { "jobs" },
+ ["maplinker"] = new string[] { "UI" },
+ ["marketboardplugin"] = new string[] { "UI" },
+ ["minicactpotsolver"] = new string[] { "minigames" },
+ ["moaction"] = new string[] { "jobs" },
+ ["namingway"] = new string[] { "jobs", "UI" },
+ ["neatnoter"] = new string[] { "UI" },
+ ["nosoliciting"] = new string[] { "UI", "social" },
+ ["oopsalllalafells"] = new string[] { "other" },
+ ["orchestrion"] = new string[] { "sound" },
+ ["owofy"] = new string[] { "social" },
+ ["peepingtom"] = new string[] { "UI" },
+ ["pennypincher"] = new string[] { "UI", "inventory" },
+ ["pingplugin"] = new string[] { "UI" },
+ ["pixelperfect"] = new string[] { "UI" },
+ ["playertrack"] = new string[] { "UI", "social" },
+ ["prefpro"] = new string[] { "UI" },
+ ["pricecheck"] = new string[] { "UI", "inventory" },
+ ["qolbar"] = new string[] { "UI" },
+ ["quest map"] = new string[] { "UI" },
+ ["remindme"] = new string[] { "UI" },
+ ["rezpls"] = new string[] { "UI" },
+ ["sillychat"] = new string[] { "other" },
+ ["simpletweaksplugin"] = new string[] { "UI" },
+ ["skillswap"] = new string[] { "jobs" },
+ ["slidecast"] = new string[] { "jobs" },
+ ["sonarplugin"] = new string[] { "UI" },
+ ["soundfilter"] = new string[] { "sound" },
+ ["soundsetter"] = new string[] { "sound" },
+ ["teleporterplugin"] = new string[] { "UI" },
+ ["textboxstyler"] = new string[] { "UI" },
+ ["texttotalk"] = new string[] { "UI" },
+ ["thegreatseparator"] = new string[] { "UI" },
+ ["titleedit"] = new string[] { "UI" },
+ ["tourist"] = new string[] { "UI" },
+ ["triadbuddy"] = new string[] { "minigames" },
+ ["vfxeditor"] = new string[] { "UI" },
+ ["visibility"] = new string[] { "UI" },
+ ["voidlist"] = new string[] { "UI" },
+ ["waymarkpresetplugin"] = new string[] { "UI" },
+ ["wintitle"] = new string[] { "UI" },
+ ["woldo"] = new string[] { "UI" },
+ ["wondroustailssolver"] = new string[] { "minigames" },
+ ["xivchat"] = new string[] { "social" },
+ ["xivcombo"] = new string[] { "jobs" },
+ };
+ #endif // DEBUG
+
+ ///
+ /// Type of category group.
+ ///
+ public enum GroupKind
+ {
+ ///
+ /// UI group: dev mode only.
+ ///
+ DevTools,
+
+ ///
+ /// UI group: installed plugins.
+ ///
+ Installed,
+
+ ///
+ /// UI group: plugins that can be installed.
+ ///
+ Available,
+ }
+
+ ///
+ /// Gets the list of all known categories.
+ ///
+ public CategoryInfo[] CategoryList => this.categoryList;
+
+ ///
+ /// Gets the list of all known UI groups.
+ ///
+ public GroupInfo[] GroupList => this.groupList;
+
+ ///
+ /// Gets or sets current group.
+ ///
+ public int CurrentGroupIdx
+ {
+ get => this.currentGroupIdx;
+ set
+ {
+ if (this.currentGroupIdx != value)
+ {
+ this.currentGroupIdx = value;
+ this.currentCategoryIdx = 0;
+ this.isContentDirty = true;
+ }
+ }
+ }
+
+ ///
+ /// Gets or sets current category.
+ ///
+ public int CurrentCategoryIdx
+ {
+ get => this.currentCategoryIdx;
+ set
+ {
+ if (this.currentCategoryIdx != value)
+ {
+ this.currentCategoryIdx = value;
+ this.isContentDirty = true;
+ }
+ }
+ }
+
+ ///
+ /// Gets a value indicating whether category content needs to be rebuild with BuildCategoryContent() function.
+ ///
+ public bool IsContentDirty => this.isContentDirty;
+
+ ///
+ /// Gets a value indicating whether and are valid.
+ ///
+ public bool IsSelectionValid =>
+ (this.currentGroupIdx >= 0) &&
+ (this.currentGroupIdx < this.groupList.Length) &&
+ (this.currentCategoryIdx >= 0) &&
+ (this.currentCategoryIdx < this.categoryList.Length);
+
+ ///
+ /// Rebuild available categories based on currently available plugins.
+ ///
+ /// list of all available plugin manifests to install.
+ public void BuildCategories(IEnumerable availablePlugins)
+ {
+ // rebuild map plugin name -> categoryIds
+ this.mapPluginCategories.Clear();
+
+ var categoryList = new List();
+ var allCategoryIndices = new List();
+
+ foreach (var plugin in availablePlugins)
+ {
+ categoryList.Clear();
+ if (plugin.Tags != null)
+ {
+ IEnumerable pluginCategoryTags = plugin.CategoryTags;
+#if DEBUG
+ if (pluginCategoryTags == null)
+ {
+ var nameKey = plugin.InternalName.ToLowerInvariant().Replace(" ", string.Empty);
+
+ if (this.mapPluginTagTesting.TryGetValue(nameKey, out var dummyTags))
+ {
+ pluginCategoryTags = dummyTags;
+ }
+ }
+#endif // DEBUG
+ if (pluginCategoryTags == null)
+ {
+ continue;
+ }
+
+ foreach (var tag in pluginCategoryTags)
+ {
+ // only tags from whitelist can be accepted
+ int matchIdx = Array.FindIndex(this.CategoryList, x => x.Tag.Equals(tag, StringComparison.InvariantCultureIgnoreCase));
+ if (matchIdx >= 0)
+ {
+ int categoryId = this.CategoryList[matchIdx].CategoryId;
+ if (categoryId >= FirstTagBasedCategoryId)
+ {
+ categoryList.Add(categoryId);
+
+ if (!allCategoryIndices.Contains(matchIdx))
+ {
+ allCategoryIndices.Add(matchIdx);
+ }
+ }
+ }
+ }
+ }
+
+ // always add, even if empty
+ this.mapPluginCategories.Add(plugin, categoryList.ToArray());
+ }
+
+ // sort all categories by their loc name
+ allCategoryIndices.Sort((idxX, idxY) => this.CategoryList[idxX].Name.CompareTo(this.CategoryList[idxY].Name));
+
+ // rebuild all categories in group, leaving first entry = All intact and always on top
+ var groupAvail = Array.Find(this.groupList, x => x.GroupKind == GroupKind.Available);
+ if (groupAvail.Categories.Count > 1)
+ {
+ groupAvail.Categories.RemoveRange(1, groupAvail.Categories.Count - 1);
+ }
+
+ foreach (var categoryIdx in allCategoryIndices)
+ {
+ groupAvail.Categories.Add(this.CategoryList[categoryIdx].CategoryId);
+ }
+
+ this.isContentDirty = true;
+ }
+
+ ///
+ /// Filters list of available plugins based on currently selected category.
+ /// Resets .
+ ///
+ /// List of available plugins to install.
+ /// Filtered list of plugins.
+ public List GetCurrentCategoryContent(IEnumerable plugins)
+ {
+ var result = new List();
+
+ if (this.IsSelectionValid)
+ {
+ var groupInfo = this.groupList[this.currentGroupIdx];
+
+ bool includeAll = (this.currentCategoryIdx == 0) || (groupInfo.GroupKind != GroupKind.Available);
+ if (includeAll)
+ {
+ result.AddRange(plugins);
+ }
+ else
+ {
+ var selectedCategoryInfo = Array.Find(this.categoryList, x => x.CategoryId == groupInfo.Categories[this.currentCategoryIdx]);
+
+ foreach (var plugin in plugins)
+ {
+ if (this.mapPluginCategories.TryGetValue(plugin, out var pluginCategoryIds))
+ {
+ int matchIdx = Array.IndexOf(pluginCategoryIds, selectedCategoryInfo.CategoryId);
+ if (matchIdx >= 0)
+ {
+ result.Add(plugin);
+ }
+ }
+ }
+ }
+ }
+
+ this.isContentDirty = false;
+ return result;
+ }
+
+ ///
+ /// Sets category highlight based on list of plugins. Used for searching.
+ ///
+ /// List of plugins whose categories should be highlighted.
+ public void SetCategoryHighlightsForPlugins(IEnumerable plugins)
+ {
+ this.highlightedCategoryIds.Clear();
+
+ if (plugins != null)
+ {
+ foreach (var entry in plugins)
+ {
+ if (this.mapPluginCategories.TryGetValue(entry, out var pluginCategories))
+ {
+ foreach (var categoryId in pluginCategories)
+ {
+ if (!this.highlightedCategoryIds.Contains(categoryId))
+ {
+ this.highlightedCategoryIds.Add(categoryId);
+ }
+ }
+ }
+ }
+ }
+ }
+
+ ///
+ /// Checks if category should be highlighted.
+ ///
+ /// CategoryId to check.
+ /// true if highlight is needed.
+ public bool IsCategoryHighlighted(int categoryId) => this.highlightedCategoryIds.Contains(categoryId);
+
+ ///
+ /// Plugin installer category info.
+ ///
+ public struct CategoryInfo
+ {
+ ///
+ /// Unique Id number of category, tag match based should be greater of equal .
+ ///
+ public int CategoryId;
+
+ ///
+ /// Tag from plugin manifest to match.
+ ///
+ public string Tag;
+
+ private Func nameFunc;
+
+ ///
+ /// Initializes a new instance of the struct.
+ ///
+ /// Unique id of category.
+ /// Tag to match.
+ /// Function returning localized name of category.
+ public CategoryInfo(int categoryId, string tag, Func nameFunc)
+ {
+ this.CategoryId = categoryId;
+ this.Tag = tag;
+ this.nameFunc = nameFunc;
+ }
+
+ ///
+ /// Gets the name of category.
+ ///
+ public string Name => this.nameFunc();
+ }
+
+ ///
+ /// Plugin installer UI group, a container for categories.
+ ///
+ public struct GroupInfo
+ {
+ ///
+ /// Type of group.
+ ///
+ public GroupKind GroupKind;
+
+ ///
+ /// List of categories in container.
+ ///
+ public List Categories;
+
+ private Func nameFunc;
+
+ ///
+ /// Initializes a new instance of the struct.
+ ///
+ /// Type of group.
+ /// Function returning localized name of category.
+ /// List of category Ids to hardcode.
+ public GroupInfo(GroupKind groupKind, Func nameFunc, params int[] categories)
+ {
+ this.GroupKind = groupKind;
+ this.nameFunc = nameFunc;
+
+ this.Categories = new();
+ this.Categories.AddRange(categories);
+ }
+
+ ///
+ /// Gets the name of UI group.
+ ///
+ public string Name => this.nameFunc();
+ }
+
+ private static class Locs
+ {
+ #region UI groups
+
+ public static string Group_DevTools => Loc.Localize("InstallerDevTools", "Dev Tools");
+
+ public static string Group_Installed => Loc.Localize("InstallerInstalledPlugins", "Installed Plugins");
+
+ public static string Group_Available => Loc.Localize("InstallerAvailablePlugins", "Available Plugins");
+
+ #endregion
+
+ #region Categories
+
+ public static string Category_All => Loc.Localize("InstallerCategoryAll", "All");
+
+ public static string Category_DevInstalled => Loc.Localize("InstallerInstalledDevPlugins", "Installed Dev Plugins");
+
+ public static string Category_IconTester => "Image/Icon Tester";
+
+ public static string Category_Other => Loc.Localize("InstallerCategoryOther", "Other");
+
+ public static string Category_Jobs => Loc.Localize("InstallerCategoryJobs", "Jobs");
+
+ public static string Category_UI => Loc.Localize("InstallerCategoryUI", "UI");
+
+ public static string Category_MiniGames => Loc.Localize("InstallerCategoryMiniGames", "Mini games");
+
+ public static string Category_Inventory => Loc.Localize("InstallerCategoryInventory", "Inventory");
+
+ public static string Category_Sound => Loc.Localize("InstallerCategorySound", "Sound");
+
+ public static string Category_Social => Loc.Localize("InstallerCategorySocial", "Social");
+
+ #endregion
+ }
+ }
+}
diff --git a/Dalamud/Interface/Internal/Windows/PluginInstallerWindow.cs b/Dalamud/Interface/Internal/Windows/PluginInstallerWindow.cs
index cd4380e20..2849cd891 100644
--- a/Dalamud/Interface/Internal/Windows/PluginInstallerWindow.cs
+++ b/Dalamud/Interface/Internal/Windows/PluginInstallerWindow.cs
@@ -53,6 +53,7 @@ namespace Dalamud.Interface.Internal.Windows
private readonly TextureWrap updateIcon;
private readonly HttpClient httpClient = new();
+ private readonly PluginCategoryManager categoryManager = new();
#region Image Tester State
@@ -194,7 +195,8 @@ namespace Dalamud.Interface.Internal.Windows
public override void Draw()
{
this.DrawHeader();
- this.DrawPluginTabBar();
+ // this.DrawPluginTabBar();
+ this.DrawPluginCategories();
this.DrawFooter();
this.DrawErrorModal();
this.DrawFeedbackModal();
@@ -242,7 +244,10 @@ namespace Dalamud.Interface.Internal.Windows
ImGui.SetCursorPosX(windowSize.X - sortSelectWidth - style.ItemSpacing.X - searchInputWidth);
ImGui.SetNextItemWidth(searchInputWidth);
- ImGui.InputTextWithHint("###XlPluginInstaller_Search", Locs.Header_SearchPlaceholder, ref this.searchText, 100);
+ if (ImGui.InputTextWithHint("###XlPluginInstaller_Search", Locs.Header_SearchPlaceholder, ref this.searchText, 100))
+ {
+ this.UpdateCategoriesOnSearchChange();
+ }
ImGui.SameLine();
ImGui.SetCursorPosX(windowSize.X - sortSelectWidth);
@@ -603,6 +608,152 @@ namespace Dalamud.Interface.Internal.Windows
}
}
+ private void DrawPluginCategories()
+ {
+ float useContentHeight = -40; // button height + spacing
+ float useMenuWidth = 180; // make dynamic?
+
+ if (ImGui.BeginChild($"ScrollingCategorySelectors", ImGuiHelpers.ScaledVector2(useMenuWidth, useContentHeight), false, ImGuiWindowFlags.HorizontalScrollbar | ImGuiWindowFlags.NoBackground))
+ {
+ this.DrawPluginCategorySelectors();
+ ImGui.EndChild();
+ }
+
+ ImGui.SameLine((useMenuWidth + 20) * ImGuiHelpers.GlobalScale);
+
+ if (ImGui.BeginChild($"ScrollingCategoryContent", new Vector2(ImGui.GetContentRegionAvail().X, useContentHeight * ImGuiHelpers.GlobalScale), false, ImGuiWindowFlags.HorizontalScrollbar | ImGuiWindowFlags.NoBackground))
+ {
+ this.DrawPluginCategoryContent();
+ ImGui.EndChild();
+ }
+ }
+
+ private void DrawPluginCategorySelectors()
+ {
+ Vector4 colorSearchHighlight = Vector4.One;
+ unsafe
+ {
+ var colorPtr = ImGui.GetStyleColorVec4(ImGuiCol.NavHighlight);
+ if (colorPtr != null)
+ {
+ colorSearchHighlight = *colorPtr;
+ }
+ }
+
+ for (int groupIdx = 0; groupIdx < this.categoryManager.GroupList.Length; groupIdx++)
+ {
+ var groupInfo = this.categoryManager.GroupList[groupIdx];
+ var canShowGroup = (groupInfo.GroupKind != PluginCategoryManager.GroupKind.DevTools) || this.hasDevPlugins;
+ if (!canShowGroup)
+ {
+ continue;
+ }
+
+ ImGui.SetNextItemOpen(groupIdx == this.categoryManager.CurrentGroupIdx);
+ if (ImGui.CollapsingHeader(groupInfo.Name))
+ {
+ if (this.categoryManager.CurrentGroupIdx != groupIdx)
+ {
+ this.categoryManager.CurrentGroupIdx = groupIdx;
+ }
+
+ ImGui.Indent();
+ for (int categoryIdx = 0; categoryIdx < groupInfo.Categories.Count; categoryIdx++)
+ {
+ var categoryInfo = Array.Find(this.categoryManager.CategoryList, x => x.CategoryId == groupInfo.Categories[categoryIdx]);
+
+ bool hasSearchHighlight = this.categoryManager.IsCategoryHighlighted(categoryInfo.CategoryId);
+ if (hasSearchHighlight)
+ {
+ ImGui.PushStyleColor(ImGuiCol.Text, colorSearchHighlight);
+ }
+
+ if (ImGui.Selectable(categoryInfo.Name, this.categoryManager.CurrentCategoryIdx == categoryIdx))
+ {
+ this.categoryManager.CurrentCategoryIdx = categoryIdx;
+ }
+
+ if (hasSearchHighlight)
+ {
+ ImGui.PopStyleColor();
+ }
+ }
+
+ ImGui.Unindent();
+ }
+ }
+ }
+
+ private void DrawPluginCategoryContent()
+ {
+ if (!this.categoryManager.IsSelectionValid)
+ {
+ return;
+ }
+
+ var groupInfo = this.categoryManager.GroupList[this.categoryManager.CurrentGroupIdx];
+ if (groupInfo.GroupKind == PluginCategoryManager.GroupKind.DevTools)
+ {
+ // this one is never sorted and remains in hardcoded order from group ctor
+ switch (this.categoryManager.CurrentCategoryIdx)
+ {
+ case 0:
+ this.DrawInstalledDevPluginList();
+ break;
+
+ case 1:
+ this.DrawImageTester();
+ break;
+
+ default:
+ // umm, there's nothing else, keep handled set and just skip drawing...
+ break;
+ }
+ }
+ else if (groupInfo.GroupKind == PluginCategoryManager.GroupKind.Installed)
+ {
+ this.DrawInstalledPluginList();
+ }
+ else
+ {
+ var pluginList = this.pluginListAvailable;
+ if (pluginList.Count > 0)
+ {
+ // reset opened list of collapsibles when switching between categories
+ if (this.categoryManager.IsContentDirty)
+ {
+ this.openPluginCollapsibles.Clear();
+ }
+
+ var filteredManifests = pluginList.Where(rm => !this.IsManifestFiltered(rm));
+ var categoryManifestsList = this.categoryManager.GetCurrentCategoryContent(filteredManifests);
+
+ if (categoryManifestsList.Count > 0)
+ {
+ var i = 0;
+ foreach (var manifest in categoryManifestsList)
+ {
+ var rmManifest = manifest as RemotePluginManifest;
+ if (rmManifest != null)
+ {
+ ImGui.PushID($"{rmManifest.InternalName}{rmManifest.AssemblyVersion}");
+ this.DrawAvailablePlugin(rmManifest, i++);
+ ImGui.PopID();
+ }
+ }
+ }
+ else
+ {
+ ImGui.Text(Locs.TabBody_SearchNoMatching);
+ }
+ }
+ else
+ {
+ ImGui.Text(Locs.TabBody_SearchNoCompatible);
+ }
+ }
+ }
+
private void DrawImageTester()
{
var sectionSize = ImGuiHelpers.GlobalScale * 66;
@@ -1763,6 +1914,8 @@ namespace Dalamud.Interface.Internal.Windows
.ToList();
this.pluginListUpdatable = pluginManager.UpdatablePlugins.ToList();
this.ResortPlugins();
+
+ this.UpdateCategoriesOnPluginsChange();
}
private void OnInstalledPluginsChanged()
@@ -1773,6 +1926,8 @@ namespace Dalamud.Interface.Internal.Windows
this.pluginListUpdatable = pluginManager.UpdatablePlugins.ToList();
this.hasDevPlugins = this.pluginListInstalled.Any(plugin => plugin.IsDev);
this.ResortPlugins();
+
+ this.UpdateCategoriesOnPluginsChange();
}
private void ResortPlugins()
@@ -2111,6 +2266,25 @@ namespace Dalamud.Interface.Internal.Windows
return output;
}
+ private void UpdateCategoriesOnSearchChange()
+ {
+ if (string.IsNullOrEmpty(this.searchText))
+ {
+ this.categoryManager.SetCategoryHighlightsForPlugins(null);
+ }
+ else
+ {
+ var pluginsMatchingSearch = this.pluginListAvailable.Where(rm => !this.IsManifestFiltered(rm));
+ this.categoryManager.SetCategoryHighlightsForPlugins(pluginsMatchingSearch);
+ }
+ }
+
+ private void UpdateCategoriesOnPluginsChange()
+ {
+ this.categoryManager.BuildCategories(this.pluginListAvailable);
+ this.UpdateCategoriesOnSearchChange();
+ }
+
[SuppressMessage("StyleCop.CSharp.OrderingRules", "SA1201:Elements should appear in the correct order", Justification = "Disregard here")]
private static class Locs
{
diff --git a/Dalamud/Plugin/Internal/Types/PluginManifest.cs b/Dalamud/Plugin/Internal/Types/PluginManifest.cs
index 0076ddfc7..ddacb66de 100644
--- a/Dalamud/Plugin/Internal/Types/PluginManifest.cs
+++ b/Dalamud/Plugin/Internal/Types/PluginManifest.cs
@@ -47,6 +47,12 @@ namespace Dalamud.Plugin.Internal.Types
[JsonProperty]
public List? Tags { get; init; }
+ ///
+ /// Gets a list of category tags defined on the plugin.
+ ///
+ [JsonProperty]
+ public List? CategoryTags { get; init; }
+
///
/// Gets a value indicating whether or not the plugin is hidden in the plugin installer.
/// This value comes from the plugin master and is in addition to the list of hidden names kept by Dalamud.
From 6eb0953e3868222ecc289ed9a27df9973aa8d178 Mon Sep 17 00:00:00 2001
From: MgAl2O4 <51430403+MgAl2O4@users.noreply.github.com>
Date: Wed, 29 Sep 2021 17:43:53 -0400
Subject: [PATCH 2/5] added override and fallback tags for plugins
---
.../Internal/PluginCategoryManager.cs | 67 +++++++++++++------
.../Internal/Windows/PluginInstallerWindow.cs | 37 +++++++---
2 files changed, 73 insertions(+), 31 deletions(-)
diff --git a/Dalamud/Interface/Internal/PluginCategoryManager.cs b/Dalamud/Interface/Internal/PluginCategoryManager.cs
index 152e2e7f3..b1c8204b2 100644
--- a/Dalamud/Interface/Internal/PluginCategoryManager.cs
+++ b/Dalamud/Interface/Internal/PluginCategoryManager.cs
@@ -48,9 +48,23 @@ namespace Dalamud.Interface.Internal
private Dictionary mapPluginCategories = new();
private List highlightedCategoryIds = new();
+ ///
+ /// Forces plugin category tags, overrides settings from manifest.
+ /// key: PluginManifest.InternalName (lowercase, no spaces),
+ /// value: list of category tags, .
+ ///
+ private Dictionary mapPluginCategoryTagOverrides = new();
+
+ ///
+ /// Fallback plugin category tags, used only when manifest doesn't contain any.
+ /// key: PluginManifest.InternalName (lowercase, no spaces),
+ /// value: list of category tags, .
+ ///
+ private Dictionary mapPluginCategoryTagFallbacks = new();
+
#if DEBUG
// temp - hardcode some tag values for testing, idk what most of them does so it's probably not very accurate :D
- private Dictionary mapPluginTagTesting = new()
+ private Dictionary mapPluginCategoryTagFallbacksHACK = new()
{
["accuratecountdown"] = new string[] { "UI" },
["adventurerinneed"] = new string[] { "UI" },
@@ -136,7 +150,7 @@ namespace Dalamud.Interface.Internal
["xivchat"] = new string[] { "social" },
["xivcombo"] = new string[] { "jobs" },
};
- #endif // DEBUG
+#endif // DEBUG
///
/// Type of category group.
@@ -231,25 +245,10 @@ namespace Dalamud.Interface.Internal
foreach (var plugin in availablePlugins)
{
categoryList.Clear();
- if (plugin.Tags != null)
+
+ var pluginCategoryTags = this.GetCategoryTagsForManifest(plugin);
+ if (pluginCategoryTags != null)
{
- IEnumerable pluginCategoryTags = plugin.CategoryTags;
-#if DEBUG
- if (pluginCategoryTags == null)
- {
- var nameKey = plugin.InternalName.ToLowerInvariant().Replace(" ", string.Empty);
-
- if (this.mapPluginTagTesting.TryGetValue(nameKey, out var dummyTags))
- {
- pluginCategoryTags = dummyTags;
- }
- }
-#endif // DEBUG
- if (pluginCategoryTags == null)
- {
- continue;
- }
-
foreach (var tag in pluginCategoryTags)
{
// only tags from whitelist can be accepted
@@ -366,6 +365,34 @@ namespace Dalamud.Interface.Internal
/// true if highlight is needed.
public bool IsCategoryHighlighted(int categoryId) => this.highlightedCategoryIds.Contains(categoryId);
+ private IEnumerable GetCategoryTagsForManifest(PluginManifest pluginManifest)
+ {
+ var nameKey = pluginManifest.InternalName.ToLowerInvariant().Replace(" ", string.Empty);
+ if (this.mapPluginCategoryTagOverrides.TryGetValue(nameKey, out var overrideTags))
+ {
+ return overrideTags;
+ }
+
+ if (pluginManifest.CategoryTags != null)
+ {
+ return pluginManifest.CategoryTags;
+ }
+
+ if (this.mapPluginCategoryTagFallbacks.TryGetValue(nameKey, out var fallbackTags))
+ {
+ return fallbackTags;
+ }
+
+#if DEBUG
+ if (this.mapPluginCategoryTagFallbacksHACK.TryGetValue(nameKey, out var dummyTags))
+ {
+ return dummyTags;
+ }
+#endif // DEBUG
+
+ return null;
+ }
+
///
/// Plugin installer category info.
///
diff --git a/Dalamud/Interface/Internal/Windows/PluginInstallerWindow.cs b/Dalamud/Interface/Internal/Windows/PluginInstallerWindow.cs
index 2849cd891..cc6f9d323 100644
--- a/Dalamud/Interface/Internal/Windows/PluginInstallerWindow.cs
+++ b/Dalamud/Interface/Internal/Windows/PluginInstallerWindow.cs
@@ -611,26 +611,40 @@ namespace Dalamud.Interface.Internal.Windows
private void DrawPluginCategories()
{
float useContentHeight = -40; // button height + spacing
- float useMenuWidth = 180; // make dynamic?
+ float useMenuWidth = 180; // works fine as static value, table can be resized by user
- if (ImGui.BeginChild($"ScrollingCategorySelectors", ImGuiHelpers.ScaledVector2(useMenuWidth, useContentHeight), false, ImGuiWindowFlags.HorizontalScrollbar | ImGuiWindowFlags.NoBackground))
+ float useContentWidth = ImGui.GetContentRegionAvail().X;
+
+ if (ImGui.BeginChild("InstallerCategories", new Vector2(useContentWidth, useContentHeight * ImGuiHelpers.GlobalScale)))
{
- this.DrawPluginCategorySelectors();
- ImGui.EndChild();
- }
+ ImGui.PushStyleVar(ImGuiStyleVar.CellPadding, ImGuiHelpers.ScaledVector2(5, 0));
+ if (ImGui.BeginTable("##InstallerCategoriesCont", 2, ImGuiTableFlags.SizingFixedFit | ImGuiTableFlags.Resizable | ImGuiTableFlags.BordersInnerV))
+ {
+ ImGui.TableSetupColumn("##InstallerCategoriesSelector", ImGuiTableColumnFlags.WidthFixed, useMenuWidth * ImGuiHelpers.GlobalScale);
+ ImGui.TableSetupColumn("##InstallerCategoriesBody", ImGuiTableColumnFlags.WidthStretch);
+ ImGui.TableNextRow();
- ImGui.SameLine((useMenuWidth + 20) * ImGuiHelpers.GlobalScale);
+ ImGui.TableNextColumn();
+ this.DrawPluginCategorySelectors();
- if (ImGui.BeginChild($"ScrollingCategoryContent", new Vector2(ImGui.GetContentRegionAvail().X, useContentHeight * ImGuiHelpers.GlobalScale), false, ImGuiWindowFlags.HorizontalScrollbar | ImGuiWindowFlags.NoBackground))
- {
- this.DrawPluginCategoryContent();
+ ImGui.TableNextColumn();
+ if (ImGui.BeginChild($"ScrollingPlugins", new Vector2(useContentWidth, 0), false, ImGuiWindowFlags.HorizontalScrollbar | ImGuiWindowFlags.NoBackground))
+ {
+ this.DrawPluginCategoryContent();
+ ImGui.EndChild();
+ }
+
+ ImGui.EndTable();
+ }
+
+ ImGui.PopStyleVar();
ImGui.EndChild();
}
}
private void DrawPluginCategorySelectors()
{
- Vector4 colorSearchHighlight = Vector4.One;
+ var colorSearchHighlight = Vector4.One;
unsafe
{
var colorPtr = ImGui.GetStyleColorVec4(ImGuiCol.NavHighlight);
@@ -658,6 +672,7 @@ namespace Dalamud.Interface.Internal.Windows
}
ImGui.Indent();
+ var categoryItemSize = new Vector2(ImGui.GetContentRegionAvail().X - (5 * ImGuiHelpers.GlobalScale), ImGui.GetTextLineHeight());
for (int categoryIdx = 0; categoryIdx < groupInfo.Categories.Count; categoryIdx++)
{
var categoryInfo = Array.Find(this.categoryManager.CategoryList, x => x.CategoryId == groupInfo.Categories[categoryIdx]);
@@ -668,7 +683,7 @@ namespace Dalamud.Interface.Internal.Windows
ImGui.PushStyleColor(ImGuiCol.Text, colorSearchHighlight);
}
- if (ImGui.Selectable(categoryInfo.Name, this.categoryManager.CurrentCategoryIdx == categoryIdx))
+ if (ImGui.Selectable(categoryInfo.Name, this.categoryManager.CurrentCategoryIdx == categoryIdx, ImGuiSelectableFlags.None, categoryItemSize))
{
this.categoryManager.CurrentCategoryIdx = categoryIdx;
}
From 694aa113d637f147de4a7c9f6eaa5bdd516508d4 Mon Sep 17 00:00:00 2001
From: MgAl2O4 <51430403+MgAl2O4@users.noreply.github.com>
Date: Wed, 29 Sep 2021 17:47:28 -0400
Subject: [PATCH 3/5] initial values of plugin category fallbacks for testing
---
.../Interface/Internal/PluginCategoryManager.cs | 15 ++-------------
1 file changed, 2 insertions(+), 13 deletions(-)
diff --git a/Dalamud/Interface/Internal/PluginCategoryManager.cs b/Dalamud/Interface/Internal/PluginCategoryManager.cs
index b1c8204b2..818c2d333 100644
--- a/Dalamud/Interface/Internal/PluginCategoryManager.cs
+++ b/Dalamud/Interface/Internal/PluginCategoryManager.cs
@@ -60,12 +60,9 @@ namespace Dalamud.Interface.Internal
/// key: PluginManifest.InternalName (lowercase, no spaces),
/// value: list of category tags, .
///
- private Dictionary mapPluginCategoryTagFallbacks = new();
-
-#if DEBUG
- // temp - hardcode some tag values for testing, idk what most of them does so it's probably not very accurate :D
- private Dictionary mapPluginCategoryTagFallbacksHACK = new()
+ private Dictionary mapPluginCategoryTagFallbacks = new()
{
+ // temporary for testing, should be removed when manifests are updated
["accuratecountdown"] = new string[] { "UI" },
["adventurerinneed"] = new string[] { "UI" },
["aethersense"] = new string[] { "Other" },
@@ -150,7 +147,6 @@ namespace Dalamud.Interface.Internal
["xivchat"] = new string[] { "social" },
["xivcombo"] = new string[] { "jobs" },
};
-#endif // DEBUG
///
/// Type of category group.
@@ -383,13 +379,6 @@ namespace Dalamud.Interface.Internal
return fallbackTags;
}
-#if DEBUG
- if (this.mapPluginCategoryTagFallbacksHACK.TryGetValue(nameKey, out var dummyTags))
- {
- return dummyTags;
- }
-#endif // DEBUG
-
return null;
}
From 6e2ca6a133c99389098df4d94c451a87a2a9c54d Mon Sep 17 00:00:00 2001
From: MgAl2O4 <51430403+MgAl2O4@users.noreply.github.com>
Date: Wed, 29 Sep 2021 22:45:23 -0400
Subject: [PATCH 4/5] removed hardcoded fallback maps for plugin categories
---
.../Internal/PluginCategoryManager.cs | 111 ------------------
1 file changed, 111 deletions(-)
diff --git a/Dalamud/Interface/Internal/PluginCategoryManager.cs b/Dalamud/Interface/Internal/PluginCategoryManager.cs
index 818c2d333..ff8a583c9 100644
--- a/Dalamud/Interface/Internal/PluginCategoryManager.cs
+++ b/Dalamud/Interface/Internal/PluginCategoryManager.cs
@@ -48,106 +48,6 @@ namespace Dalamud.Interface.Internal
private Dictionary mapPluginCategories = new();
private List highlightedCategoryIds = new();
- ///
- /// Forces plugin category tags, overrides settings from manifest.
- /// key: PluginManifest.InternalName (lowercase, no spaces),
- /// value: list of category tags, .
- ///
- private Dictionary mapPluginCategoryTagOverrides = new();
-
- ///
- /// Fallback plugin category tags, used only when manifest doesn't contain any.
- /// key: PluginManifest.InternalName (lowercase, no spaces),
- /// value: list of category tags, .
- ///
- private Dictionary mapPluginCategoryTagFallbacks = new()
- {
- // temporary for testing, should be removed when manifests are updated
- ["accuratecountdown"] = new string[] { "UI" },
- ["adventurerinneed"] = new string[] { "UI" },
- ["aethersense"] = new string[] { "Other" },
- ["autovisor"] = new string[] { "UI" },
- ["betterpartyfinder"] = new string[] { "UI" },
- ["browserhost.plugin"] = new string[] { "UI" },
- ["burnttoast"] = new string[] { "UI" },
- ["chatalerts"] = new string[] { "social" },
- ["chatbubbles"] = new string[] { "social" },
- ["chatcoordinates"] = new string[] { "social" },
- ["chatextender"] = new string[] { "social" },
- ["chattranslator"] = new string[] { "social" },
- ["compass"] = new string[] { "UI" },
- ["dalamud.charactersync"] = new string[] { "other" },
- ["dalamud.discordbridge"] = new string[] { "other" },
- ["dalamud.loadingimage"] = new string[] { "other" },
- ["dalamud.richpresence"] = new string[] { "other" },
- ["dalamudvox"] = new string[] { "Other" },
- ["damageinfoplugin"] = new string[] { "UI" },
- ["deepdungeondex"] = new string[] { "UI" },
- ["easyeyes"] = new string[] { "UI" },
- ["engagetimer"] = new string[] { "jobs" },
- ["expandedsearchinfo"] = new string[] { "UI" },
- ["fantasyplayer.dalamud"] = new string[] { "UI" },
- ["fauxhollowssolver"] = new string[] { "minigames" },
- ["fcnamecolor"] = new string[] { "UI", "social" },
- ["fpsplugin"] = new string[] { "UI" },
- ["gatherbuddy"] = new string[] { "jobs" },
- ["gentletouch"] = new string[] { "UI" },
- ["globetrotter"] = new string[] { "UI" },
- ["goodmemory"] = new string[] { "UI" },
- ["harphero"] = new string[] { "jobs" },
- ["housemate"] = new string[] { "UI" },
- ["itemsearchplugin"] = new string[] { "inventory" },
- ["jobbars"] = new string[] { "jobs" },
- ["jobicons"] = new string[] { "jobs" },
- ["kapture"] = new string[] { "UI" },
- ["kingdomheartsplugin"] = new string[] { "UI" },
- ["macrochain"] = new string[] { "jobs" },
- ["maplinker"] = new string[] { "UI" },
- ["marketboardplugin"] = new string[] { "UI" },
- ["minicactpotsolver"] = new string[] { "minigames" },
- ["moaction"] = new string[] { "jobs" },
- ["namingway"] = new string[] { "jobs", "UI" },
- ["neatnoter"] = new string[] { "UI" },
- ["nosoliciting"] = new string[] { "UI", "social" },
- ["oopsalllalafells"] = new string[] { "other" },
- ["orchestrion"] = new string[] { "sound" },
- ["owofy"] = new string[] { "social" },
- ["peepingtom"] = new string[] { "UI" },
- ["pennypincher"] = new string[] { "UI", "inventory" },
- ["pingplugin"] = new string[] { "UI" },
- ["pixelperfect"] = new string[] { "UI" },
- ["playertrack"] = new string[] { "UI", "social" },
- ["prefpro"] = new string[] { "UI" },
- ["pricecheck"] = new string[] { "UI", "inventory" },
- ["qolbar"] = new string[] { "UI" },
- ["quest map"] = new string[] { "UI" },
- ["remindme"] = new string[] { "UI" },
- ["rezpls"] = new string[] { "UI" },
- ["sillychat"] = new string[] { "other" },
- ["simpletweaksplugin"] = new string[] { "UI" },
- ["skillswap"] = new string[] { "jobs" },
- ["slidecast"] = new string[] { "jobs" },
- ["sonarplugin"] = new string[] { "UI" },
- ["soundfilter"] = new string[] { "sound" },
- ["soundsetter"] = new string[] { "sound" },
- ["teleporterplugin"] = new string[] { "UI" },
- ["textboxstyler"] = new string[] { "UI" },
- ["texttotalk"] = new string[] { "UI" },
- ["thegreatseparator"] = new string[] { "UI" },
- ["titleedit"] = new string[] { "UI" },
- ["tourist"] = new string[] { "UI" },
- ["triadbuddy"] = new string[] { "minigames" },
- ["vfxeditor"] = new string[] { "UI" },
- ["visibility"] = new string[] { "UI" },
- ["voidlist"] = new string[] { "UI" },
- ["waymarkpresetplugin"] = new string[] { "UI" },
- ["wintitle"] = new string[] { "UI" },
- ["woldo"] = new string[] { "UI" },
- ["wondroustailssolver"] = new string[] { "minigames" },
- ["xivchat"] = new string[] { "social" },
- ["xivcombo"] = new string[] { "jobs" },
- };
-
///
/// Type of category group.
///
@@ -363,22 +263,11 @@ namespace Dalamud.Interface.Internal
private IEnumerable GetCategoryTagsForManifest(PluginManifest pluginManifest)
{
- var nameKey = pluginManifest.InternalName.ToLowerInvariant().Replace(" ", string.Empty);
- if (this.mapPluginCategoryTagOverrides.TryGetValue(nameKey, out var overrideTags))
- {
- return overrideTags;
- }
-
if (pluginManifest.CategoryTags != null)
{
return pluginManifest.CategoryTags;
}
- if (this.mapPluginCategoryTagFallbacks.TryGetValue(nameKey, out var fallbackTags))
- {
- return fallbackTags;
- }
-
return null;
}
From 70852791eb8f0d66bc96c97648ffd368b5c6c802 Mon Sep 17 00:00:00 2001
From: MgAl2O4 <51430403+MgAl2O4@users.noreply.github.com>
Date: Wed, 29 Sep 2021 22:45:54 -0400
Subject: [PATCH 5/5] fixed missing "plugins are loading" state in category
view
---
Dalamud/Interface/Internal/Windows/PluginInstallerWindow.cs | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/Dalamud/Interface/Internal/Windows/PluginInstallerWindow.cs b/Dalamud/Interface/Internal/Windows/PluginInstallerWindow.cs
index cc6f9d323..ba95fe623 100644
--- a/Dalamud/Interface/Internal/Windows/PluginInstallerWindow.cs
+++ b/Dalamud/Interface/Internal/Windows/PluginInstallerWindow.cs
@@ -701,7 +701,8 @@ namespace Dalamud.Interface.Internal.Windows
private void DrawPluginCategoryContent()
{
- if (!this.categoryManager.IsSelectionValid)
+ var ready = this.DrawPluginListLoading();
+ if (!this.categoryManager.IsSelectionValid || !ready)
{
return;
}