diff --git a/ILSpy.Tests/Options/OptionsTabTests.cs b/ILSpy.Tests/Options/OptionsTabTests.cs index ebe9fbc9e..c4ad0eedb 100644 --- a/ILSpy.Tests/Options/OptionsTabTests.cs +++ b/ILSpy.Tests/Options/OptionsTabTests.cs @@ -128,12 +128,13 @@ public class OptionsTabTests } [AvaloniaTest] - public async Task Apply_Writes_Snapshot_Through_To_Live_DecompilerSettings() + public void Panel_Edits_Take_Effect_Immediately_On_Live_DecompilerSettings() { - // Snapshot pattern: changes in the panel's settings instance don't reach the live - // service until Apply. After Apply, SettingsService.DecompilerSettings reflects the - // new value. Toggling a property on the snapshot copy alone shouldn't affect the - // live instance until Apply is called. + // Live-binding architecture (no snapshot): toggling a checkbox in the Decompiler + // panel mutates the live SettingsService.DecompilerSettings instance directly. No + // Apply step. Subscribers to DecompilerSettings.PropertyChanged see the change + // immediately; the next decompile pulls the new value via Clone() in + // DecompilerTabPageModel. var window = AppComposition.Current.GetExport(); window.Show(); var settings = AppComposition.Current.GetExport(); @@ -149,26 +150,17 @@ public class OptionsTabTests .OfType().First(t => t.Content is OptionsPageModel).Content!; var decompilerPage = (DecompilerSettingsViewModel)model.Pages[0]; - // Find the UsingDeclarations item in the reflection-built tree and flip it. + // Flip UsingDeclarations through the panel viewmodel. var usingDecl = decompilerPage.Settings .SelectMany(g => g.Settings) .First(s => s.Property.Name == nameof(global::ICSharpCode.Decompiler.DecompilerSettings.UsingDeclarations)); usingDecl.IsEnabled = !liveBefore; - // Live service unaffected so far — only the snapshot copy changed. - // Snapshot edits must not leak into the live service before Apply. - settings.DecompilerSettings.UsingDeclarations.Should().Be(liveBefore); - - // Apply — flushes snapshot to XML and reloads sections so live values match. - model.ApplyCommand.Execute(null); - await Task.Yield(); - - // After Apply, the live service must see the panel's toggled value. + // Live service must see the change immediately — no Apply needed. settings.DecompilerSettings.UsingDeclarations.Should().Be(!liveBefore); - // Clean-up: restore the original so the persisted XML doesn't pollute later tests. + // Clean-up: restore the original so the next test sees a clean slate. usingDecl.IsEnabled = liveBefore; - model.ApplyCommand.Execute(null); } [AvaloniaTest] diff --git a/ILSpy/Options/DecompilerSettingsViewModel.cs b/ILSpy/Options/DecompilerSettingsViewModel.cs index 47fa9bf37..ab18e6980 100644 --- a/ILSpy/Options/DecompilerSettingsViewModel.cs +++ b/ILSpy/Options/DecompilerSettingsViewModel.cs @@ -58,9 +58,9 @@ namespace ILSpy.Options DecompilerSettings decompilerSettings = null!; - public void Load(SettingsSnapshot snapshot) + public void Load(SettingsService settings) { - decompilerSettings = snapshot.GetSettings(); + decompilerSettings = settings.DecompilerSettings; LoadSettings(); } diff --git a/ILSpy/Options/DisplaySettingsPanel.axaml b/ILSpy/Options/DisplaySettingsPanel.axaml index 15dea07e4..027d4f671 100644 --- a/ILSpy/Options/DisplaySettingsPanel.axaml +++ b/ILSpy/Options/DisplaySettingsPanel.axaml @@ -20,8 +20,13 @@ Value="{Binding Settings.SelectedFontSize, Mode=TwoWay}" /> + diff --git a/ILSpy/Options/DisplaySettingsViewModel.cs b/ILSpy/Options/DisplaySettingsViewModel.cs index db1de586b..ba6080b21 100644 --- a/ILSpy/Options/DisplaySettingsViewModel.cs +++ b/ILSpy/Options/DisplaySettingsViewModel.cs @@ -17,6 +17,7 @@ // DEALINGS IN THE SOFTWARE. using System.Collections.Generic; +using System.ComponentModel; using System.Linq; using System.Xml.Linq; @@ -47,9 +48,31 @@ namespace ILSpy.Options .OrderBy(n => n, System.StringComparer.OrdinalIgnoreCase) .ToArray(); - public void Load(SettingsSnapshot snapshot) + /// + /// Derived FontFamily for the preview TextBlock. Avalonia's runtime binding pipeline + /// doesn't auto-coerce a string source to (the implicit + /// conversion fires at XAML parse time, not on binding evaluation), so the preview + /// goes through this property — recomputed on every + /// change via the PropertyChanged subscription in . + /// + public FontFamily CurrentFontFamily => + Settings != null && !string.IsNullOrEmpty(Settings.SelectedFont) + ? new FontFamily(Settings.SelectedFont) + : FontFamily.Default; + + public void Load(SettingsService service) + { + if (Settings != null) + Settings.PropertyChanged -= OnSettingsPropertyChanged; + Settings = service.DisplaySettings; + Settings.PropertyChanged += OnSettingsPropertyChanged; + OnPropertyChanged(nameof(CurrentFontFamily)); + } + + void OnSettingsPropertyChanged(object? sender, PropertyChangedEventArgs e) { - Settings = snapshot.GetSettings(); + if (e.PropertyName == nameof(DisplaySettings.SelectedFont)) + OnPropertyChanged(nameof(CurrentFontFamily)); } public void LoadDefaults() diff --git a/ILSpy/Options/IOptionPage.cs b/ILSpy/Options/IOptionPage.cs index 9e1a0d29d..18bb71544 100644 --- a/ILSpy/Options/IOptionPage.cs +++ b/ILSpy/Options/IOptionPage.cs @@ -21,14 +21,17 @@ namespace ILSpy.Options /// /// Plugin contract for individual panels in the Options tab. Each implementation is /// MEF-discovered via ; the host calls - /// with a snapshot of the current settings when the tab opens, and - /// when the user clicks the per-panel reset button. + /// with the live settings service when the tab opens, and + /// when the user clicks the per-panel reset button. Panels + /// bind directly to the live section instances — every toggle is immediately visible + /// to other consumers (the decompiler view, the tree, etc.). XML persistence rides on + /// at app exit. /// public interface IOptionPage { string Title { get; } - void Load(SettingsSnapshot settings); + void Load(SettingsService settings); void LoadDefaults(); } diff --git a/ILSpy/Options/MiscSettingsViewModel.cs b/ILSpy/Options/MiscSettingsViewModel.cs index b7b5f0603..57587011e 100644 --- a/ILSpy/Options/MiscSettingsViewModel.cs +++ b/ILSpy/Options/MiscSettingsViewModel.cs @@ -35,9 +35,9 @@ namespace ILSpy.Options [ObservableProperty] MiscSettings settings = null!; - public void Load(SettingsSnapshot snapshot) + public void Load(SettingsService service) { - Settings = snapshot.GetSettings(); + Settings = service.MiscSettings; } public void LoadDefaults() diff --git a/ILSpy/Options/OptionsPageModel.cs b/ILSpy/Options/OptionsPageModel.cs index 0dec80ffb..064b7605e 100644 --- a/ILSpy/Options/OptionsPageModel.cs +++ b/ILSpy/Options/OptionsPageModel.cs @@ -28,30 +28,31 @@ using ICSharpCode.ILSpy.Properties; namespace ILSpy.Options { /// - /// Content viewmodel for the Options document tab. Wraps a , - /// MEF-discovered panels (ordered by ), - /// and the page-level Apply / Reset commands the panel views bind to. + /// Content viewmodel for the Options document tab. Wraps the live + /// , MEF-discovered panels + /// (ordered by ), and a Reset command that + /// rolls the currently-selected panel back to its built-in defaults. + /// + /// Panels bind two-way to the live section instances, so every toggle takes effect + /// immediately — there's no Apply step. XML persistence rides on + /// at app exit. + /// /// public sealed partial class OptionsPageModel : ObservableObject { - readonly SettingsSnapshot snapshot; - public OptionsPageModel(SettingsService settingsService, IEnumerable> pageFactories) { - snapshot = settingsService.CreateSnapshot(); - Pages = pageFactories .OrderBy(f => f.Metadata.Order) .Select(f => f.CreateExport().Value) .ToArray(); foreach (var page in Pages) - page.Load(snapshot); + page.Load(settingsService); selectedPage = Pages.FirstOrDefault(); Title = Resources._Options; - ApplyCommand = new RelayCommand(Apply); ResetCurrentPageCommand = new RelayCommand(ResetCurrentPage); } @@ -67,12 +68,8 @@ namespace ILSpy.Options [ObservableProperty] IOptionPage? selectedPage; - public IRelayCommand ApplyCommand { get; } - public IRelayCommand ResetCurrentPageCommand { get; } - void Apply() => snapshot.Save(); - void ResetCurrentPage() => SelectedPage?.LoadDefaults(); } } diff --git a/ILSpy/Options/OptionsPageView.axaml b/ILSpy/Options/OptionsPageView.axaml index 5efd66bf1..928f3af9e 100644 --- a/ILSpy/Options/OptionsPageView.axaml +++ b/ILSpy/Options/OptionsPageView.axaml @@ -9,19 +9,17 @@ x:Class="ILSpy.Options.OptionsPageView" x:DataType="vm:OptionsPageModel"> - +