diff --git a/ILSpy.Tests/Metadata/MetadataRowDetailsTests.cs b/ILSpy.Tests/Metadata/MetadataRowDetailsTests.cs index 6228bf626..2f0ec9282 100644 --- a/ILSpy.Tests/Metadata/MetadataRowDetailsTests.cs +++ b/ILSpy.Tests/Metadata/MetadataRowDetailsTests.cs @@ -28,6 +28,7 @@ using Avalonia.VisualTree; using AwesomeAssertions; +using ICSharpCode.ILSpy.AppEnv; using ICSharpCode.ILSpy.Metadata; using ICSharpCode.ILSpy.TextView; using ICSharpCode.ILSpy.ViewModels; @@ -198,6 +199,53 @@ public class MetadataRowDetailsTests unknown.SyntaxHighlighting.Should().BeNull("an unrecognized extension degrades to plain text"); } + [AvaloniaTest] + public void Text_Blob_Editor_Uses_The_Decompiler_View_Styling() + { + // The details editor is a second surface showing code, so it must look like the main + // decompiler view: the user-selected editor font (applied live, the same way the text + // view reacts to the Options page), the flat square-cornered selection highlight, and + // the themed editor background. + var settings = AppComposition.Current.GetExport().DisplaySettings; + var originalFont = settings.SelectedFont; + var originalSize = settings.SelectedFontSize; + var editor = (DecompilerTextEditor)MetadataRowDetails.BuildTextBlob("class C { }", ".cs"); + var window = new Window { Content = editor }; + try + { + settings.SelectedFont = "Liberation Mono"; + settings.SelectedFontSize = 17; + window.Show(); + + editor.FontFamily.Name.Should().Be("Liberation Mono", + "the details editor renders in the user-selected editor font"); + editor.FontSize.Should().Be(17); + + settings.SelectedFontSize = 21; + editor.FontSize.Should().Be(21, "font settings apply live while the details row is open"); + + editor.TextArea.SelectionCornerRadius.Should().Be(0, + "selection styling matches the decompiler view (flat, square corners)"); + window.TryFindResource("ILSpy.EditorSelectionBrush", window.ActualThemeVariant, out var selectionBrush) + .Should().BeTrue(); + editor.TextArea.SelectionBrush.Should().Be(selectionBrush); + window.TryFindResource("ILSpy.EditorBackground", window.ActualThemeVariant, out var background) + .Should().BeTrue(); + editor.Background.Should().Be(background); + + window.Close(); + settings.SelectedFontSize = 13; + editor.FontSize.Should().Be(21, + "an editor detached by row recycling must stop tracking the settings instance"); + } + finally + { + window.Close(); + settings.SelectedFont = originalFont; + settings.SelectedFontSize = originalSize; + } + } + [AvaloniaTest] public async Task Double_Tap_Inside_The_Details_Area_Does_Not_Resolve_To_An_Activatable_Row() { diff --git a/ILSpy/Metadata/MetadataRowDetails.cs b/ILSpy/Metadata/MetadataRowDetails.cs index 2d50d8e11..a0bc9607a 100644 --- a/ILSpy/Metadata/MetadataRowDetails.cs +++ b/ILSpy/Metadata/MetadataRowDetails.cs @@ -22,10 +22,8 @@ using System.Collections.Generic; using Avalonia; using Avalonia.Controls; -using Avalonia.Controls.Primitives; using Avalonia.Controls.Templates; using Avalonia.Data; -using Avalonia.Media; using ICSharpCode.ILSpy.TextView; using ICSharpCode.ILSpy.ViewModels; @@ -120,11 +118,11 @@ namespace ICSharpCode.ILSpy.Metadata /// /// Read-only, word-wrapped view of a decoded text blob (source, JSON, hex), rendered - /// in the theme-aware AvaloniaEdit editor: text payloads are mostly code, so they get - /// syntax colours (selected by , e.g. ".cs" or - /// ".json"; stays plain) and line virtualization keeps large - /// embedded-source documents cheap. Height is bounded so the host row cannot grow - /// unbounded; the editor scrolls internally. + /// in the decompiler-view-styled AvaloniaEdit editor: text payloads are mostly code, + /// so they get syntax colours (selected by , + /// e.g. ".cs" or ".json"; stays plain) and line virtualization + /// keeps large embedded-source documents cheap. Height is bounded so the host row + /// cannot grow unbounded; the editor scrolls internally. /// public static Control BuildTextBlob(string text, string? highlightExtension = null) { @@ -134,12 +132,9 @@ namespace ICSharpCode.ILSpy.Metadata IsReadOnly = true, WordWrap = true, MaxHeight = 400, - FontFamily = new FontFamily("Consolas, Menlo, Monospace"), - FontSize = 13, }; if (highlightExtension != null) editor.SyntaxHighlighting = HighlightingService.GetByExtension(highlightExtension); - editor.Bind(TemplatedControl.BackgroundProperty, editor.GetResourceObservable("ILSpy.EditorBackground")); return editor; } diff --git a/ILSpy/TextView/DecompilerTextEditor.cs b/ILSpy/TextView/DecompilerTextEditor.cs index b0e5a89f8..5004c0fe7 100644 --- a/ILSpy/TextView/DecompilerTextEditor.cs +++ b/ILSpy/TextView/DecompilerTextEditor.cs @@ -17,22 +17,34 @@ // DEALINGS IN THE SOFTWARE. using System; +using System.ComponentModel; + +using Avalonia; +using Avalonia.Controls; +using Avalonia.Media; using AvaloniaEdit; +using AvaloniaEdit.Editing; using AvaloniaEdit.Highlighting; using AvaloniaEdit.Rendering; +using ICSharpCode.ILSpy.AppEnv; +using ICSharpCode.ILSpy.Options; using ICSharpCode.ILSpy.Themes; namespace ICSharpCode.ILSpy.TextView { /// - /// subclass that hooks two theme concerns: + /// subclass carrying the decompiler-view look, so every surface + /// showing code (the main text view, metadata row details) renders identically: /// (a) overrides so syntax highlighting goes /// through and adapts to the active theme; /// (b) listens for and forces a TextView redraw /// so an already-rendered editor picks up the new palette without needing the user - /// to scroll or reselect. + /// to scroll or reselect; + /// (c) follows the user-selected editor font ( / + /// ) live while attached; + /// (d) uses the themed editor background and selection highlight. /// public class DecompilerTextEditor : TextEditor { @@ -41,7 +53,23 @@ namespace ICSharpCode.ILSpy.TextView // pointed at the base. Without this override AvaloniaEdit's template doesn't // apply to us — meaning no ScrollViewer is installed, scroll offsets stay 0, // and Copy can't reach the editor's TextArea via the template lookup chain. - protected override System.Type StyleKeyOverride => typeof(TextEditor); + protected override Type StyleKeyOverride => typeof(TextEditor); + + DisplaySettings? displaySettings; + + public DecompilerTextEditor() + { + // Fallback font for hosts without display settings (e.g. bare test compositions); + // overwritten from DisplaySettings on attach. + FontFamily = new FontFamily("Consolas, Menlo, Monospace"); + FontSize = 13; + // Selected text keeps its syntax colours (ports icsharpcode/ILSpy#2938): + // SelectionForeground stays unset, and the selection is a flat, translucent + // highlight (square corners, no border) instead of a recoloured run. + TextArea.SelectionCornerRadius = 0; + TextArea.Bind(TextArea.SelectionBrushProperty, this.GetResourceObservable("ILSpy.EditorSelectionBrush")); + this.Bind(BackgroundProperty, this.GetResourceObservable("ILSpy.EditorBackground")); + } protected override IVisualLineTransformer CreateColorizer(IHighlightingDefinition highlightingDefinition) { @@ -56,15 +84,52 @@ namespace ICSharpCode.ILSpy.TextView TextArea?.TextView?.Redraw(); } - protected override void OnAttachedToVisualTree(global::Avalonia.VisualTreeAttachmentEventArgs e) + void OnDisplaySettingsChanged(object? sender, PropertyChangedEventArgs e) + { + if (e.PropertyName is nameof(DisplaySettings.SelectedFont) or nameof(DisplaySettings.SelectedFontSize)) + ApplyFontSettings(); + } + + void ApplyFontSettings() + { + if (displaySettings == null) + return; + if (!string.IsNullOrEmpty(displaySettings.SelectedFont)) + FontFamily = new FontFamily(displaySettings.SelectedFont); + if (displaySettings.SelectedFontSize > 0) + FontSize = displaySettings.SelectedFontSize; + } + + static DisplaySettings? TryGetDisplaySettings() + { + try + { return AppComposition.Current.GetExport().DisplaySettings; } + catch { return null; } + } + + protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e) { base.OnAttachedToVisualTree(e); ThemeManager.Current.ThemeChanged += OnThemeChanged; + // (Re-)apply the font on every attach: editors inside recycled containers + // (metadata row details) detach and re-attach, and settings may have changed + // while the editor was off the tree. + displaySettings = TryGetDisplaySettings(); + if (displaySettings != null) + { + ApplyFontSettings(); + displaySettings.PropertyChanged += OnDisplaySettingsChanged; + } TextArea?.TextView?.Redraw(); } - protected override void OnDetachedFromVisualTree(global::Avalonia.VisualTreeAttachmentEventArgs e) + protected override void OnDetachedFromVisualTree(VisualTreeAttachmentEventArgs e) { + if (displaySettings != null) + { + displaySettings.PropertyChanged -= OnDisplaySettingsChanged; + displaySettings = null; + } ThemeManager.Current.ThemeChanged -= OnThemeChanged; base.OnDetachedFromVisualTree(e); } diff --git a/ILSpy/TextView/DecompilerTextView.axaml b/ILSpy/TextView/DecompilerTextView.axaml index 71c2462f0..e3d119117 100644 --- a/ILSpy/TextView/DecompilerTextView.axaml +++ b/ILSpy/TextView/DecompilerTextView.axaml @@ -3,7 +3,6 @@ xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:ae="using:AvaloniaEdit" - xmlns:aeediting="using:AvaloniaEdit.Editing" xmlns:textView="using:ICSharpCode.ILSpy.TextView" xmlns:omnibar="using:ICSharpCode.ILSpy.Controls.Omnibar" mc:Ignorable="d" d:DesignWidth="600" d:DesignHeight="400" @@ -11,16 +10,6 @@ x:Class="ICSharpCode.ILSpy.TextView.DecompilerTextView" x:DataType="textView:DecompilerTabPageModel"> - - - - - @@ -30,13 +19,11 @@ progress bar + cancel button while a decompilation is in flight. --> + and carries the shared decompiler-view styling: themed background and selection + highlight, user-selected editor font, redraw on theme-variant changes. --> + ShowLineNumbers="True" /> diff --git a/ILSpy/TextView/DecompilerTextView.axaml.cs b/ILSpy/TextView/DecompilerTextView.axaml.cs index c0b770085..4bac40bff 100644 --- a/ILSpy/TextView/DecompilerTextView.axaml.cs +++ b/ILSpy/TextView/DecompilerTextView.axaml.cs @@ -617,8 +617,9 @@ namespace ICSharpCode.ILSpy.TextView void ApplyAllDisplaySettings(DisplaySettings s) { - ApplyDisplaySetting(s, nameof(DisplaySettings.SelectedFont)); - ApplyDisplaySetting(s, nameof(DisplaySettings.SelectedFontSize)); + // Font family/size and the themed background/selection are not handled here: + // DecompilerTextEditor itself follows those settings, shared with every other + // surface hosting the editor (metadata row details). ApplyDisplaySetting(s, nameof(DisplaySettings.ShowLineNumbers)); ApplyDisplaySetting(s, nameof(DisplaySettings.EnableWordWrap)); ApplyDisplaySetting(s, nameof(DisplaySettings.HighlightCurrentLine)); @@ -631,14 +632,6 @@ namespace ICSharpCode.ILSpy.TextView { switch (propertyName) { - case nameof(DisplaySettings.SelectedFont): - if (!string.IsNullOrEmpty(s.SelectedFont)) - Editor.FontFamily = new FontFamily(s.SelectedFont); - break; - case nameof(DisplaySettings.SelectedFontSize): - if (s.SelectedFontSize > 0) - Editor.FontSize = s.SelectedFontSize; - break; case nameof(DisplaySettings.ShowLineNumbers): Editor.ShowLineNumbers = s.ShowLineNumbers; break;