@ -17,22 +17,34 @@
// DEALINGS IN THE SOFTWARE.
// DEALINGS IN THE SOFTWARE.
using System ;
using System ;
using System.ComponentModel ;
using Avalonia ;
using Avalonia.Controls ;
using Avalonia.Media ;
using AvaloniaEdit ;
using AvaloniaEdit ;
using AvaloniaEdit.Editing ;
using AvaloniaEdit.Highlighting ;
using AvaloniaEdit.Highlighting ;
using AvaloniaEdit.Rendering ;
using AvaloniaEdit.Rendering ;
using ICSharpCode.ILSpy.AppEnv ;
using ICSharpCode.ILSpy.Options ;
using ICSharpCode.ILSpy.Themes ;
using ICSharpCode.ILSpy.Themes ;
namespace ICSharpCode.ILSpy.TextView
namespace ICSharpCode.ILSpy.TextView
{
{
/// <summary>
/// <summary>
/// <see cref="TextEditor"/> subclass that hooks two theme concerns:
/// <see cref="TextEditor"/> subclass carrying the decompiler-view look, so every surface
/// showing code (the main text view, metadata row details) renders identically:
/// (a) overrides <see cref="TextEditor.CreateColorizer"/> so syntax highlighting goes
/// (a) overrides <see cref="TextEditor.CreateColorizer"/> so syntax highlighting goes
/// through <see cref="ThemeAwareHighlightingColorizer"/> and adapts to the active theme;
/// through <see cref="ThemeAwareHighlightingColorizer"/> and adapts to the active theme;
/// (b) listens for <see cref="ThemeManager.ThemeChanged"/> and forces a TextView redraw
/// (b) listens for <see cref="ThemeManager.ThemeChanged"/> and forces a TextView redraw
/// so an already-rendered editor picks up the new palette without needing the user
/// 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 (<see cref="DisplaySettings.SelectedFont"/> /
/// <see cref="DisplaySettings.SelectedFontSize"/>) live while attached;
/// (d) uses the themed editor background and selection highlight.
/// </summary>
/// </summary>
public class DecompilerTextEditor : TextEditor
public class DecompilerTextEditor : TextEditor
{
{
@ -41,7 +53,23 @@ namespace ICSharpCode.ILSpy.TextView
// pointed at the base. Without this override AvaloniaEdit's template doesn't
// pointed at the base. Without this override AvaloniaEdit's template doesn't
// apply to us — meaning no ScrollViewer is installed, scroll offsets stay 0,
// 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.
// 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 = 1 3 ;
// 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 )
protected override IVisualLineTransformer CreateColorizer ( IHighlightingDefinition highlightingDefinition )
{
{
@ -56,15 +84,52 @@ namespace ICSharpCode.ILSpy.TextView
TextArea ? . TextView ? . Redraw ( ) ;
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 < SettingsService > ( ) . DisplaySettings ; }
catch { return null ; }
}
protected override void OnAttachedToVisualTree ( VisualTreeAttachmentEventArgs e )
{
{
base . OnAttachedToVisualTree ( e ) ;
base . OnAttachedToVisualTree ( e ) ;
ThemeManager . Current . ThemeChanged + = OnThemeChanged ;
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 ( ) ;
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 ;
ThemeManager . Current . ThemeChanged - = OnThemeChanged ;
base . OnDetachedFromVisualTree ( e ) ;
base . OnDetachedFromVisualTree ( e ) ;
}
}