From 98f67ad7f240e3cac409f299e4aa62096b6d1dd3 Mon Sep 17 00:00:00 2001 From: Benjamin Sutas Date: Thu, 25 Sep 2025 12:39:32 +1000 Subject: [PATCH 01/17] rework how filtering works on foldertree --- Gui/ViewModels/FolderTreeViewModel.cs | 250 +++++++++++++++----------- 1 file changed, 148 insertions(+), 102 deletions(-) diff --git a/Gui/ViewModels/FolderTreeViewModel.cs b/Gui/ViewModels/FolderTreeViewModel.cs index a145d8c5..68fc67d6 100644 --- a/Gui/ViewModels/FolderTreeViewModel.cs +++ b/Gui/ViewModels/FolderTreeViewModel.cs @@ -4,6 +4,7 @@ using Avalonia.Threading; using Dat.Data; using Definitions.ObjectModels.Types; +using DynamicData; using Gui.Models; using Index; using ReactiveUI; @@ -15,6 +16,7 @@ using System.Linq; using System.Reactive; using System.Reactive.Linq; +using System.Reactive.Subjects; using System.Threading.Tasks; namespace Gui.ViewModels; @@ -25,8 +27,8 @@ public DesignerFolderTreeViewModel() { SelectedTabIndex = 0; CurrentLocalDirectory = "test/directory"; - LocalDirectoryItems = [new("local-displayname1", "local-filename1", null)]; - OnlineDirectoryItems = [new("online-displayname1", null, 123)]; + //LocalDirectoryItems = [new("local-displayname1", "local-filename1", null)]; + //OnlineDirectoryItems = [new("online-displayname1", null, 123)]; UpdateDirectoryItemsView(); } @@ -34,21 +36,20 @@ public DesignerFolderTreeViewModel() public class FolderTreeViewModel : ReactiveObject { - public HierarchicalTreeDataGridSource TreeDataGridSource { get; set; } - ObservableCollection treeDataGridSource; + [Reactive] + protected SourceList CurrentDirectoryItems { get; set; } = new(); - ObjectEditorModel Model { get; init; } + //[Reactive] + //protected SourceList OnlineDirectoryItems { get; set; } = new(); - [Reactive] - public string CurrentLocalDirectory { get; set; } = string.Empty; - public string CurrentDirectory => SelectedTabIndex == 0 - ? CurrentLocalDirectory - : Model.Settings.UseHttps - ? Model.Settings.ServerAddressHttps - : Model.Settings.ServerAddressHttp; + //SourceList _sourceCache = new(); + public HierarchicalTreeDataGridSource TreeDataGridSource { get; set; } + ReadOnlyObservableCollection treeDataGridSource; - [Reactive] - public FileSystemItem? CurrentlySelectedObject { get; set; } + //SourceList CurrentDirectoryItems + // => IsLocal + // ? LocalDirectoryItems + // : OnlineDirectoryItems; [Reactive] public string FilenameFilter { get; set; } = string.Empty; @@ -62,11 +63,20 @@ public class FolderTreeViewModel : ReactiveObject [Reactive] public ObjectDisplayMode DisplayMode { get; set; } = ObjectDisplayMode.All; + private readonly BehaviorSubject> _filterSubject; + + ObjectEditorModel Model { get; init; } + [Reactive] - protected List LocalDirectoryItems { get; set; } = []; + public string CurrentLocalDirectory { get; set; } = string.Empty; + public string CurrentDirectory => SelectedTabIndex == 0 + ? CurrentLocalDirectory + : Model.Settings.UseHttps + ? Model.Settings.ServerAddressHttps + : Model.Settings.ServerAddressHttp; [Reactive] - protected List OnlineDirectoryItems { get; set; } = []; + public FileSystemItem? CurrentlySelectedObject { get; set; } [Reactive] public float IndexOrDownloadProgress { get; set; } @@ -90,7 +100,7 @@ public string RecreateText => IsLocal ? "Recreate index" : "Download object list"; public string DirectoryFileCount - => $"Objects: {CurrentDirectoryItems.Sum(CountNodes)}"; + => $"Objects: {CurrentDirectoryItems.Count /*Sum(CountNodes)*/}"; // used for design-time view public FolderTreeViewModel() @@ -114,10 +124,27 @@ public FolderTreeViewModel(ObjectEditorModel model) { var dir = Directory.GetParent(clickedOnObject.FileName)?.FullName; PlatformSpecific.FolderOpenInDesktop(dir, Model.Logger, Path.GetFileName(clickedOnObject.FileName)); - } }); + _filterSubject = new BehaviorSubject>(t => true); + + _ = CurrentDirectoryItems.Connect() + .Filter(_filterSubject) // Use the subject to filter + .Bind(out treeDataGridSource) + .Subscribe(_ => UpdateDirectoryItemsView()); // Rebuild the tree when the filtered list changes + + // When any filter property changes, create a new filter and push it to the subject + var filterChanged = this.WhenAnyValue( + x => x.FilenameFilter, + x => x.AuthorFilter, + x => x.ModpackFilter, + x => x.DisplayMode) + .Throttle(TimeSpan.FromMilliseconds(300)) + .Select(_ => CreateFilter()); + + _ = filterChanged.Subscribe(_filterSubject); + _ = this.WhenAnyValue(o => o.CurrentLocalDirectory) .Skip(1) .Subscribe(async _ => await ReloadDirectoryAsync(true)); @@ -126,25 +153,27 @@ public FolderTreeViewModel(ObjectEditorModel model) .Skip(1) .Subscribe(_ => this.RaisePropertyChanged(nameof(CurrentDirectory))); - _ = this.WhenAnyValue(o => o.DisplayMode) - .Throttle(TimeSpan.FromMilliseconds(1000)) - .Skip(1) - .Subscribe(async _ => await ReloadDirectoryAsync(true)); + //_ = this.WhenAnyValue(o => o.DisplayMode) + // .Throttle(TimeSpan.FromMilliseconds(1000)) + // .Skip(1) + // .Subscribe(async _ => await ReloadDirectoryAsync(true)); - _ = this.WhenAnyValue(o => o.FilenameFilter) - .Throttle(TimeSpan.FromMilliseconds(500)) - .Skip(1) - .Subscribe(async _ => await ReloadDirectoryAsync(true)); + //_ = this.WhenAnyValue(o => o.FilenameFilter) + // .Throttle(TimeSpan.FromMilliseconds(500)) + // .Skip(1) + // //.Select(_filterSubject => new Func(t => MatchesFilter(t, FilenameFilter, AuthorFilter, ModpackFilter, DisplayMode))) + // .Select(_filterSubject => new Func(t => t.DisplayName.StartsWith(FilenameFilter))) + // .Subscribe(_filterSubject); - _ = this.WhenAnyValue(o => o.AuthorFilter) - .Throttle(TimeSpan.FromMilliseconds(500)) - .Skip(1) - .Subscribe(async _ => await ReloadDirectoryAsync(true)); + //_ = this.WhenAnyValue(o => o.AuthorFilter) + // .Throttle(TimeSpan.FromMilliseconds(500)) + // .Skip(1) + // .Subscribe(async _ => await ReloadDirectoryAsync(true)); - _ = this.WhenAnyValue(o => o.ModpackFilter) - .Throttle(TimeSpan.FromMilliseconds(500)) - .Skip(1) - .Subscribe(async _ => await ReloadDirectoryAsync(true)); + //_ = this.WhenAnyValue(o => o.ModpackFilter) + // .Throttle(TimeSpan.FromMilliseconds(500)) + // .Skip(1) + // .Subscribe(async _ => await ReloadDirectoryAsync(true)); _ = this.WhenAnyValue(o => o.TreeDataGridSource) .Skip(1) @@ -166,14 +195,14 @@ public FolderTreeViewModel(ObjectEditorModel model) .Skip(1) .Subscribe(_ => this.RaisePropertyChanged(nameof(CurrentDirectory))); - _ = this.WhenAnyValue(o => o.LocalDirectoryItems) - //.Skip(1) - .Subscribe(_ => UpdateDirectoryItemsView()); - - _ = this.WhenAnyValue(o => o.OnlineDirectoryItems) + _ = this.WhenAnyValue(o => o.CurrentDirectoryItems) .Skip(1) .Subscribe(_ => UpdateDirectoryItemsView()); + //_ = this.WhenAnyValue(o => o.OnlineDirectoryItems) + // .Skip(1) + // .Subscribe(_ => UpdateDirectoryItemsView()); + // loads the last-viewed folder CurrentLocalDirectory = Model.Settings.ObjDataDirectory; } @@ -195,11 +224,6 @@ public static int CountNodes(FileSystemItem fib) return count; } - List CurrentDirectoryItems => IsLocal ? LocalDirectoryItems : OnlineDirectoryItems; - - protected void UpdateDirectoryItemsView() - => UpdateGrid(CurrentDirectoryItems); - async Task ReloadDirectoryAsync(bool useExistingIndex) { if (SelectedTabIndex == 0) @@ -219,27 +243,65 @@ async Task LoadObjDirectoryAsync(string directory, bool useExistingIndex) { if (string.IsNullOrEmpty(directory) || !Directory.Exists(directory)) { - LocalDirectoryItems = []; + CurrentDirectoryItems.Clear(); return; } await Model.LoadObjDirectoryAsync(directory, Progress, useExistingIndex); - LocalDirectoryItems = ConstructTreeView( - Model.ObjectIndex.Objects.Where(x => (int)x.ObjectType < Limits.kMaxObjectTypes), - Model.Settings.ObjDataDirectory, - FilenameFilter, - AuthorFilter, - ModpackFilter, - DisplayMode, - FileLocation.Local); - UpdateGrid(LocalDirectoryItems); + if (Model.ObjectIndex != null) + { + var items = Model.ObjectIndex.Objects.Where(x => (int)x.ObjectType < Limits.kMaxObjectTypes); + CurrentDirectoryItems.Clear(); + CurrentDirectoryItems.AddRange(items); + } + + UpdateDirectoryItemsView(); + } + + async Task LoadOnlineDirectoryAsync(bool useExistingIndex) + { + if (Design.IsDesignMode) + { + // DO NOT WEB QUERY AT DESIGN TIME + return; + } + + if ((!useExistingIndex || Model.ObjectIndexOnline == null) && Model.ObjectServiceClient != null) + { + Model.ObjectIndexOnline = new ObjectIndex((await Model.ObjectServiceClient.GetObjectListAsync()) + .Select(x => new ObjectIndexEntry( + x.DisplayName, + null, + x.Id, + x.DatChecksum, + null, + x.ObjectType, + x.ObjectSource, + x.CreatedDate, + x.ModifiedDate, + x.VehicleType))); + } + + if (Model.ObjectIndexOnline != null) + { + var items = Model.ObjectIndexOnline.Objects.Where(x => (int)x.ObjectType < Limits.kMaxObjectTypes); + CurrentDirectoryItems.Clear(); + CurrentDirectoryItems.AddRange(items); + + UpdateDirectoryItemsView(); + } } - void UpdateGrid(List items) + protected void UpdateDirectoryItemsView() { - treeDataGridSource = [.. items]; - TreeDataGridSource = new HierarchicalTreeDataGridSource(treeDataGridSource) + var _treeGridDataSource = ConstructTreeView( + treeDataGridSource, + Model.Settings.ObjDataDirectory, + IsLocal ? FileLocation.Local : FileLocation.Online); + + //treeDataGridSource = [.. items]; + TreeDataGridSource = new HierarchicalTreeDataGridSource(_treeGridDataSource) { Columns = { @@ -292,55 +354,41 @@ void SelectionChanged(object? sender, TreeSelectionModelSelectionChangedEventArg } } - async Task LoadOnlineDirectoryAsync(bool useExistingIndex) + private Func CreateFilter() { - if (Design.IsDesignMode) + return entry => { - // DO NOT WEB QUERY AT DESIGN TIME - return; - } + // Display Mode Filter + var displayable = DisplayMode == ObjectDisplayMode.All + || (DisplayMode == ObjectDisplayMode.Vanilla && (entry.ObjectSource is ObjectSource.LocomotionSteam or ObjectSource.LocomotionGoG)) + || (DisplayMode == ObjectDisplayMode.Custom && entry.ObjectSource == ObjectSource.Custom) + || (DisplayMode == ObjectDisplayMode.OpenLoco && entry.ObjectSource == ObjectSource.OpenLoco); - if ((!useExistingIndex || Model.ObjectIndexOnline == null) && Model.ObjectServiceClient != null) - { - Model.ObjectIndexOnline = new ObjectIndex((await Model.ObjectServiceClient.GetObjectListAsync()) - .Select(x => new ObjectIndexEntry( - x.DisplayName, - null, - x.Id, - x.DatChecksum, - null, - x.ObjectType, - x.ObjectSource, - x.CreatedDate, - x.ModifiedDate, - x.VehicleType))); - } + if (!displayable) + { + return false; + } - if (Model.ObjectIndexOnline != null) - { - OnlineDirectoryItems = ConstructTreeView( - Model.ObjectIndexOnline.Objects.Where(x => (int)x.ObjectType < Limits.kMaxObjectTypes), - Model.Settings.DownloadFolder, - FilenameFilter, - AuthorFilter, - ModpackFilter, - DisplayMode, - FileLocation.Online); - - UpdateGrid(OnlineDirectoryItems); - } - } + // Filename Filter + if (!string.IsNullOrEmpty(FilenameFilter) && !entry.DisplayName.Contains(FilenameFilter, StringComparison.CurrentCultureIgnoreCase)) + { + return false; + } - static bool MatchesFilter(ObjectIndexEntry o, string filenameFilter, string authorFilter, string modpackFilter, ObjectDisplayMode displayMode) - { - var displayable = displayMode == ObjectDisplayMode.All || (displayMode == ObjectDisplayMode.Vanilla == (o.ObjectSource is ObjectSource.LocomotionSteam or ObjectSource.LocomotionGoG)); + // Author Filter (example) + // if (!string.IsNullOrEmpty(AuthorFilter) && !entry.Author.Contains(AuthorFilter, StringComparison.CurrentCultureIgnoreCase)) + // { + // return false; + // } - var filters = - string.IsNullOrEmpty(filenameFilter) || o.DisplayName.Contains(filenameFilter, StringComparison.CurrentCultureIgnoreCase); - //&& (string.IsNullOrEmpty(authorFilter) || o.Author.Contains(authorFilter, StringComparison.CurrentCultureIgnoreCase)) - //&& (string.IsNullOrEmpty(modpackFilter) || o.DatName.Contains(modpackFilter, StringComparison.CurrentCultureIgnoreCase)); + // Modpack Filter (example) + // if (!string.IsNullOrEmpty(ModpackFilter) && !entry.DatName.Contains(ModpackFilter, StringComparison.CurrentCultureIgnoreCase)) + // { + // return false; + // } - return displayable && filters; + return true; + }; } public static FileSystemItem IndexEntryToFileSystemItem(ObjectIndexEntry x, string baseDirectory, FileLocation fileLocation) @@ -352,12 +400,10 @@ public static FileSystemItem IndexEntryToFileSystemItem(ObjectIndexEntry x, stri return new FileSystemItem(x.DisplayName, Path.Combine(baseDirectory, computedFileName), x.Id, x.CreatedDate, x.ModifiedDate, fileLocation, x.ObjectSource); } - static List ConstructTreeView(IEnumerable index, string baseDirectory, string filenameFilter, string authorFilter, string modpackFilter, ObjectDisplayMode displayMode, FileLocation fileLocation) + static List ConstructTreeView(IEnumerable index, string baseDirectory, FileLocation fileLocation) { var result = new List(); var groupedObjects = index - .OfType() // this won't show errored files - should we?? - .Where(x => MatchesFilter(x, filenameFilter, authorFilter, modpackFilter, displayMode)) .GroupBy(x => x.ObjectType) .OrderBy(fsg => fsg.Key.ToString()); From 55ec61e966e39a2807be913de7eca4a6e25f3047 Mon Sep 17 00:00:00 2001 From: Benjamin Sutas Date: Thu, 25 Sep 2025 14:09:43 +1000 Subject: [PATCH 02/17] add filter list --- Gui/App.axaml | 20 ++ Gui/ViewModels/Filters/FilterField.cs | 9 + Gui/ViewModels/Filters/FilterOperator.cs | 8 + Gui/ViewModels/Filters/FilterViewModel.cs | 92 +++++++++ Gui/ViewModels/FolderTreeViewModel.cs | 241 ++++++++-------------- Gui/Views/FolderTreeView.axaml | 20 +- Gui/Views/MainWindow.axaml | 2 +- 7 files changed, 239 insertions(+), 153 deletions(-) create mode 100644 Gui/ViewModels/Filters/FilterField.cs create mode 100644 Gui/ViewModels/Filters/FilterOperator.cs create mode 100644 Gui/ViewModels/Filters/FilterViewModel.cs diff --git a/Gui/App.axaml b/Gui/App.axaml index f30ed4aa..7790faf0 100644 --- a/Gui/App.axaml +++ b/Gui/App.axaml @@ -58,6 +58,22 @@ + + + + + Crimson + White + + + Crimson + White + + + + + + @@ -78,6 +94,10 @@ + + + + --> + + + + + + + + + From 927c18876ba0748a6c86831a2ff45655f3e6e40e Mon Sep 17 00:00:00 2001 From: Benjamin Sutas Date: Thu, 25 Sep 2025 22:56:25 +1000 Subject: [PATCH 07/17] type-specific operators --- Gui/ViewModels/Filters/FilterOperator.cs | 11 +- Gui/ViewModels/Filters/FilterViewModel.cs | 175 +++++++++++++++++++--- Gui/ViewModels/FolderTreeViewModel.cs | 36 +---- Gui/Views/FolderTreeView.axaml | 31 +--- 4 files changed, 174 insertions(+), 79 deletions(-) diff --git a/Gui/ViewModels/Filters/FilterOperator.cs b/Gui/ViewModels/Filters/FilterOperator.cs index fa538ef9..aba210fe 100644 --- a/Gui/ViewModels/Filters/FilterOperator.cs +++ b/Gui/ViewModels/Filters/FilterOperator.cs @@ -5,9 +5,10 @@ public enum FilterOperator Contains, Equals, NotEquals, - /* LessThan - * GreaterThan, - * LessThanOrEqual, - * GreaterThanOrEqual - */ + GreaterThan, + GreaterThanOrEqual, + LessThan, + LessThanOrEqual, + StartsWith, + EndsWith, } diff --git a/Gui/ViewModels/Filters/FilterViewModel.cs b/Gui/ViewModels/Filters/FilterViewModel.cs index 9e2d5734..0256f881 100644 --- a/Gui/ViewModels/Filters/FilterViewModel.cs +++ b/Gui/ViewModels/Filters/FilterViewModel.cs @@ -27,11 +27,22 @@ public class FilterViewModel : ReactiveObject { [Reactive] public FilterTypeViewModel? SelectedObjectType { get; set; } [Reactive] public PropertyInfo? SelectedField { get; set; } - [Reactive] public FilterOperator SelectedOperator { get; set; } = FilterOperator.Equals; - [Reactive] public object? FilterValue { get; set; } + [Reactive] public FilterOperator SelectedOperator { get; set; } + [Reactive] + public object? FilterValue + { + get; + set; + } public ObservableCollection AvailableFields { get; set; } = []; public ObservableCollection AvailableOperators { get; set; } = []; + public ObservableCollection AvailableEnumValues { get; } = []; + + [Reactive] public bool IsTextValue { get; private set; } + [Reactive] public bool IsDateValue { get; private set; } + [Reactive] public bool IsEnumValue { get; private set; } + [Reactive] public bool IsBoolValue { get; private set; } public ReactiveCommand RemoveFilterCommand { get; } @@ -45,20 +56,101 @@ public FilterViewModel(List availableFilters, Action x != null) .Subscribe(_ => { - FilterValue = null; - AvailableFields.Clear(); AvailableFields.AddRange(SelectedObjectType!.Type.GetProperties(BindingFlags.Public | BindingFlags.Instance).OrderBy(p => p.Name)); SelectedField = AvailableFields.FirstOrDefault(); + }); - SelectedOperator = FilterOperator.Equals; - AvailableOperators.Clear(); - AvailableOperators.AddRange(Enum.GetValues()); + _ = this.WhenAnyValue(x => x.SelectedField) + .Where(field => field is not null) + .Subscribe(field => + { + UpdateAvailableOperators(field!.PropertyType); + UpdateValueInputType(field!.PropertyType); }); RemoveFilterCommand = ReactiveCommand.Create(() => onRemove(this)); } + private void UpdateValueInputType(Type propertyType) + { + var underlyingType = Nullable.GetUnderlyingType(propertyType) ?? propertyType; + + IsTextValue = false; + IsDateValue = false; + IsEnumValue = false; + IsBoolValue = false; + AvailableEnumValues.Clear(); + + if (underlyingType.IsEnum) + { + IsEnumValue = true; + var enumValues = Enum.GetValues(underlyingType); + AvailableEnumValues.AddRange(enumValues.Cast()); + FilterValue = enumValues.GetValue(0); + } + else if (underlyingType == typeof(bool)) + { + IsBoolValue = true; + FilterValue = false; + } + else if (underlyingType == typeof(string) || IsNumericType(underlyingType)) + { + IsTextValue = true; + FilterValue = string.Empty; + } + else if (underlyingType == typeof(DateOnly)) + { + IsDateValue = true; + // Set a default value for the date picker + FilterValue = DateOnly.FromDateTime(DateTime.Now); + } + } + + private void UpdateAvailableOperators(Type propertyType) + { + AvailableOperators.Clear(); + var underlyingType = Nullable.GetUnderlyingType(propertyType) ?? propertyType; + + if (underlyingType == typeof(string)) + { + AvailableOperators.AddRange([ + FilterOperator.Contains, + FilterOperator.Equals, + FilterOperator.NotEquals, + FilterOperator.StartsWith, + FilterOperator.EndsWith + ]); + } + else if (underlyingType.IsEnum || underlyingType == typeof(bool)) + { + AvailableOperators.AddRange([FilterOperator.Equals, FilterOperator.NotEquals]); + } + else if (IsNumericType(underlyingType) || underlyingType == typeof(DateOnly)) + { + AvailableOperators.AddRange([ + FilterOperator.Equals, + FilterOperator.NotEquals, + FilterOperator.GreaterThan, + FilterOperator.GreaterThanOrEqual, + FilterOperator.LessThan, + FilterOperator.LessThanOrEqual + ]); + } + else + { + AvailableOperators.AddRange([FilterOperator.Equals, FilterOperator.NotEquals]); + } + + SelectedOperator = AvailableOperators.FirstOrDefault(); + } + + private static bool IsNumericType(Type type) => Type.GetTypeCode(type) switch + { + TypeCode.Byte or TypeCode.SByte or TypeCode.UInt16 or TypeCode.UInt32 or TypeCode.UInt64 or TypeCode.Int16 or TypeCode.Int32 or TypeCode.Int64 or TypeCode.Decimal or TypeCode.Double or TypeCode.Single => true, + _ => false, + }; + public Expression>? BuildExpression() { if (FilterValue == null || SelectedField == null) @@ -66,39 +158,84 @@ public FilterViewModel(List availableFilters, Action Expression.Call(member, method!, constant, comparisonConstant), - FilterOperator.Equals => Expression.Equal(Expression.Call(member, "ToLower", null), Expression.Call(constant, "ToLower", null)), - FilterOperator.NotEquals => Expression.NotEqual(Expression.Call(member, "ToLower", null), Expression.Call(constant, "ToLower", null)), + FilterOperator.StartsWith => Expression.Call(member, startsWithMethod!, constant, comparisonConstant), + FilterOperator.EndsWith => Expression.Call(member, endsWithMethod!, constant, comparisonConstant), + FilterOperator.Equals => Expression.Equal(memberLower, constantLower), + FilterOperator.NotEquals => Expression.NotEqual(memberLower, constantLower), + _ => null + }; + } + else if (underlyingType.IsEnum) + { + var enumConstant = Expression.Constant(FilterValue, member.Type); + body = SelectedOperator switch + { + FilterOperator.Equals => Expression.Equal(member, enumConstant), + FilterOperator.NotEquals => Expression.NotEqual(member, enumConstant), _ => null }; } - else if (member.Type.IsEnum || (Nullable.GetUnderlyingType(member.Type)?.IsEnum ?? false)) + else if (IsNumericType(underlyingType) || underlyingType == typeof(DateOnly)) { - var enumType = Nullable.GetUnderlyingType(member.Type) ?? member.Type; - if (Enum.TryParse(enumType, FilterValue as string, true, out var enumValue)) + try { - var enumConstant = Expression.Constant(enumValue, member.Type); + var convertedValue = Convert.ChangeType(FilterValue, underlyingType); + var constant = Expression.Constant(convertedValue, member.Type); + body = SelectedOperator switch { - FilterOperator.Equals => Expression.Equal(member, enumConstant), - FilterOperator.NotEquals => Expression.NotEqual(member, enumConstant), + FilterOperator.Equals => Expression.Equal(member, constant), + FilterOperator.NotEquals => Expression.NotEqual(member, constant), + FilterOperator.GreaterThan => Expression.GreaterThan(member, constant), + FilterOperator.GreaterThanOrEqual => Expression.GreaterThanOrEqual(member, constant), + FilterOperator.LessThan => Expression.LessThan(member, constant), + FilterOperator.LessThanOrEqual => Expression.LessThanOrEqual(member, constant), _ => null }; } + catch (Exception) + { + return null; // Conversion failed + } + } + else if (underlyingType == typeof(bool)) + { + var constant = Expression.Constant(FilterValue); + body = SelectedOperator switch + { + FilterOperator.Equals => Expression.Equal(member, constant), + FilterOperator.NotEquals => Expression.NotEqual(member, constant), + _ => null + }; + } + + if (body == null) + { + return null; } - return body != null ? Expression.Lambda>(body, parameter) : null; + return Expression.Lambda>(body, parameter); } } diff --git a/Gui/ViewModels/FolderTreeViewModel.cs b/Gui/ViewModels/FolderTreeViewModel.cs index c276ec20..a9d3f24a 100644 --- a/Gui/ViewModels/FolderTreeViewModel.cs +++ b/Gui/ViewModels/FolderTreeViewModel.cs @@ -133,10 +133,9 @@ public FolderTreeViewModel(ObjectEditorModel model) var rootFiltersChanged = this.WhenAnyValue(x => x.DisplayMode, x => x.SelectedObjectType).Select(_ => Unit.Default); var combinedTrigger = Observable.Merge(filtersChanged, rootFiltersChanged) - .Throttle(TimeSpan.FromMilliseconds(300)) - .Select(_ => CreateFilterPredicate()); - - _ = combinedTrigger.Subscribe(_filterSubject); + .Throttle(TimeSpan.FromMilliseconds(200)) + .Select(_ => CreateFilterPredicate()) + .Subscribe(_filterSubject); _ = this.WhenAnyValue(x => x.SelectedObjectType) .Skip(1) @@ -166,30 +165,6 @@ private Func CreateFilterPredicate() var parameter = Expression.Parameter(typeof(ObjectIndexEntry), "entry"); Expression combinedExpression = Expression.Constant(true); - // Master ObjectType Filter - if (SelectedObjectType.HasValue) - { - var objectTypeExpression = Expression.Equal( - Expression.Property(parameter, nameof(ObjectIndexEntry.ObjectType)), - Expression.Constant(SelectedObjectType.Value) - ); - combinedExpression = Expression.AndAlso(combinedExpression, objectTypeExpression); - } - - // Display Mode Filter - Expression displayModeExpression = DisplayMode switch - { - ObjectDisplayMode.Vanilla => Expression.OrElse( - Expression.Equal(Expression.Property(parameter, nameof(ObjectIndexEntry.ObjectSource)), Expression.Constant(ObjectSource.LocomotionSteam)), - Expression.Equal(Expression.Property(parameter, nameof(ObjectIndexEntry.ObjectSource)), Expression.Constant(ObjectSource.LocomotionGoG)) - ), - ObjectDisplayMode.Custom => Expression.Equal(Expression.Property(parameter, nameof(ObjectIndexEntry.ObjectSource)), Expression.Constant(ObjectSource.Custom)), - ObjectDisplayMode.OpenLoco => Expression.Equal(Expression.Property(parameter, nameof(ObjectIndexEntry.ObjectSource)), Expression.Constant(ObjectSource.OpenLoco)), - _ => Expression.Constant(true) - }; - combinedExpression = Expression.AndAlso(combinedExpression, displayModeExpression); - - // Dynamic Filters foreach (var filter in Filters) { var filterExpression = filter.BuildExpression(); @@ -200,8 +175,9 @@ private Func CreateFilterPredicate() } } - var lambda = Expression.Lambda>(combinedExpression, parameter); - return lambda.Compile(); + return Expression + .Lambda>(combinedExpression, parameter) + .Compile(); } public static int CountNodes(FileSystemItem fib) diff --git a/Gui/Views/FolderTreeView.axaml b/Gui/Views/FolderTreeView.axaml index 8e236174..6a01dd3e 100644 --- a/Gui/Views/FolderTreeView.axaml +++ b/Gui/Views/FolderTreeView.axaml @@ -65,36 +65,12 @@ - - @@ -106,7 +82,12 @@ - + + + + + +