diff --git a/CringePlugins/Loader/PluginsLifetime.cs b/CringePlugins/Loader/PluginsLifetime.cs index 579932d..9a932d4 100644 --- a/CringePlugins/Loader/PluginsLifetime.cs +++ b/CringePlugins/Loader/PluginsLifetime.cs @@ -40,7 +40,9 @@ public class PluginsLifetime(string gameFolder) : ILoadingStage progress.Report("Loading config"); PackagesConfig? packagesConfig = null; - var configPath = Path.Join(_dir.FullName, "packages.json"); + + var configDir = _dir.CreateSubdirectory("config"); + var configPath = Path.Join(configDir.FullName, "packages.json"); if (File.Exists(configPath)) await using (var stream = File.OpenRead(configPath)) packagesConfig = await JsonSerializer.DeserializeAsync(stream, NuGetClient.SerializerOptions)!; diff --git a/CringePlugins/Ui/NotificationsComponent.cs b/CringePlugins/Ui/NotificationsComponent.cs index 86d08db..daa9b26 100644 --- a/CringePlugins/Ui/NotificationsComponent.cs +++ b/CringePlugins/Ui/NotificationsComponent.cs @@ -1,12 +1,11 @@ using CringePlugins.Abstractions; -using HarmonyLib; using ImGuiNET; using System.Numerics; using VRage; using VRageRender; namespace CringePlugins.Ui; -internal class NotificationsComponent : IRenderComponent +public sealed class NotificationsComponent : IRenderComponent { private static int _globalNotificationId; private const float TransitionTime = .5f; diff --git a/CringePlugins/Ui/PluginListComponent.cs b/CringePlugins/Ui/PluginListComponent.cs index fdef3fe..afa5763 100644 --- a/CringePlugins/Ui/PluginListComponent.cs +++ b/CringePlugins/Ui/PluginListComponent.cs @@ -26,6 +26,7 @@ internal class PluginListComponent : IRenderComponent private ImmutableDictionary _packages; private ImmutableDictionary? _searchResults; + private bool _searchResultsDirty; private string _searchQuery = ""; private Task? _searchTask; @@ -37,7 +38,7 @@ internal class PluginListComponent : IRenderComponent private ImmutableHashSet? _selectedSources; private readonly string _configPath; private readonly string _gameFolder; - private readonly ImmutableArray _plugins; + private ImmutableArray _plugins; private (SearchResultEntry entry, NuGetClient client)? _selected; private (PackageSource source, int index)? _selectedSource; @@ -88,13 +89,21 @@ internal class PluginListComponent : IRenderComponent { if (BeginTabItem("Installed Plugins")) { - if (BeginTable("InstalledTable", 3, ImGuiTableFlags.ScrollY | ImGuiTableFlags.Resizable)) + if (BeginTable("InstalledTable", 3, ImGuiTableFlags.ScrollY | ImGuiTableFlags.Resizable | ImGuiTableFlags.Sortable)) { - TableSetupColumn("Id", ImGuiTableColumnFlags.None, .5f); - TableSetupColumn("Version", ImGuiTableColumnFlags.None, .25f); - TableSetupColumn("Source", ImGuiTableColumnFlags.None, .25f); + TableSetupColumn("Id", ImGuiTableColumnFlags.None, .5f, (uint)Columns.Id); + TableSetupColumn("Version", ImGuiTableColumnFlags.None, .25f, (uint)Columns.Version); + TableSetupColumn("Source", ImGuiTableColumnFlags.None, .25f, (uint)Columns.Source); TableHeadersRow(); + var sortSpecs = TableGetSortSpecs(); + + if (sortSpecs.SpecsDirty) + { + _plugins = _plugins.Sort((x, y) => ComparePlugins(x, y, sortSpecs)); + sortSpecs.SpecsDirty = false; + } + foreach (var plugin in _plugins) { TableNextRow(); @@ -386,13 +395,34 @@ internal class PluginListComponent : IRenderComponent BeginChild("List", new(400, 0), ImGuiChildFlags.Border | ImGuiChildFlags.ResizeX); { if (BeginTable("AvailableTable", 3, - ImGuiTableFlags.ScrollY | ImGuiTableFlags.Resizable | ImGuiTableFlags.SizingStretchProp)) + ImGuiTableFlags.ScrollY | ImGuiTableFlags.Resizable | ImGuiTableFlags.SizingStretchProp | ImGuiTableFlags.Sortable)) { - TableSetupColumn("Id", ImGuiTableColumnFlags.None, .5f); - TableSetupColumn("Version", ImGuiTableColumnFlags.None, .25f); - TableSetupColumn("Installed", ImGuiTableColumnFlags.None, .25f); + TableSetupColumn("Id", ImGuiTableColumnFlags.None, .5f, (uint)Columns.Id); + TableSetupColumn("Version", ImGuiTableColumnFlags.None, .25f, (uint)Columns.Version); + TableSetupColumn("Installed", ImGuiTableColumnFlags.None, .25f, (uint)Columns.Installed); TableHeadersRow(); + var sortSpecs = TableGetSortSpecs(); + + if (_searchResultsDirty || sortSpecs.SpecsDirty) + { + var builder = searchResults.ToBuilder(); + foreach (var kvp in builder.ToArray()) + { + builder[kvp.Key] = kvp.Value with + { + Entries = kvp.Value.Entries.Sort( + (x, y) => SortSearchResults(x, y, _packages.ContainsKey(x.Id), _packages.ContainsKey(y.Id), sortSpecs)) + }; + } + + _searchResults = builder.ToImmutable(); + searchResults = _searchResults; + + _searchResultsDirty = false; + sortSpecs.SpecsDirty = false; + } + foreach (var (client, result) in searchResults) { foreach (var package in result.Entries.Take(100)) @@ -507,6 +537,7 @@ internal class PluginListComponent : IRenderComponent private async Task RefreshAsync() { _searchResults = null; + _searchResultsDirty = true; var builder = ImmutableDictionary.CreateBuilder(); @@ -541,4 +572,77 @@ internal class PluginListComponent : IRenderComponent Packages = [.._packages.Select(b => new PackageReference(b.Key, b.Value))] } : _packagesConfig, NuGetClient.SerializerOptions); } + + private static unsafe int ComparePlugins(PluginInstance x, PluginInstance y, ImGuiTableSortSpecsPtr specs) + { + ImGuiTableColumnSortSpecs* ptr = specs.Specs; + for (var i = 0; i < specs.SpecsCount; i++) + { + var spec = ptr[i]; + var delta = 0; + + switch ((Columns)spec.ColumnUserID) + { + case Columns.Id: + delta = string.Compare(x.Metadata.Name, y.Metadata.Name, StringComparison.OrdinalIgnoreCase); + break; + case Columns.Version: + delta = x.Metadata.Version.CompareTo(y.Metadata.Version); + break; + case Columns.Source: + delta = string.Compare(x.Metadata.Source, y.Metadata.Source, StringComparison.OrdinalIgnoreCase); + break; + } + + if (delta > 0) + return spec.SortDirection == ImGuiSortDirection.Descending ? -1 : 1; + + if (delta < 0) + return spec.SortDirection == ImGuiSortDirection.Descending ? 1 : -1; + } + + //default + return string.Compare(x.Metadata.Name, y.Metadata.Name, StringComparison.OrdinalIgnoreCase); + } + + private static unsafe int SortSearchResults(SearchResultEntry x, SearchResultEntry y, bool xInstall, bool yInstall, + ImGuiTableSortSpecsPtr specs) + { + ImGuiTableColumnSortSpecs* ptr = specs.Specs; + for (var i = 0; i < specs.SpecsCount; i++) + { + var spec = ptr[i]; + var delta = 0; + + switch ((Columns)spec.ColumnUserID) + { + case Columns.Id: + delta = string.Compare(x.Title, y.Title, StringComparison.OrdinalIgnoreCase); + break; + case Columns.Version: + delta = x.Version.CompareTo(y.Version); + break; + case Columns.Installed: + delta = xInstall.CompareTo(yInstall); + break; + } + + if (delta > 0) + return spec.SortDirection == ImGuiSortDirection.Descending ? -1 : 1; + + if (delta < 0) + return spec.SortDirection == ImGuiSortDirection.Descending ? 1 : -1; + } + + //default + return string.Compare(x.Title, y.Title, StringComparison.OrdinalIgnoreCase); + } + + private enum Columns : uint + { + Id, + Version, + Source, + Installed + } } \ No newline at end of file