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; }