Browse Source

Merge pull request #3138 from ltrzesniewski/dark-theme-highlighting

Adjust colors of AvalonEdit built-in highlightings for dark themes
natural-type-lambdas-methods
Siegfried Pammer 1 year ago committed by GitHub
parent
commit
b629cdb603
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 13
      ILSpy/TextView/DecompilerTextEditor.cs
  2. 55
      ILSpy/TextView/DecompilerTextView.xaml
  3. 105
      ILSpy/TextView/ThemeAwareHighlightingColorizer.cs
  4. 15
      ILSpy/Themes/ThemeManager.cs

13
ILSpy/TextView/DecompilerTextEditor.cs

@ -0,0 +1,13 @@ @@ -0,0 +1,13 @@
using ICSharpCode.AvalonEdit;
using ICSharpCode.AvalonEdit.Highlighting;
using ICSharpCode.AvalonEdit.Rendering;
namespace ICSharpCode.ILSpy.TextView;
public class DecompilerTextEditor : TextEditor
{
protected override IVisualLineTransformer CreateColorizer(IHighlightingDefinition highlightingDefinition)
{
return new ThemeAwareHighlightingColorizer(highlightingDefinition);
}
}

55
ILSpy/TextView/DecompilerTextView.xaml

@ -1,17 +1,16 @@ @@ -1,17 +1,16 @@
<UserControl x:Class="ICSharpCode.ILSpy.TextView.DecompilerTextView" x:ClassModifier="public" x:Name="self"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:properties="clr-namespace:ICSharpCode.ILSpy.Properties"
xmlns:controls="clr-namespace:ICSharpCode.ILSpy.Controls"
xmlns:local="clr-namespace:ICSharpCode.ILSpy.TextView"
xmlns:ae="clr-namespace:ICSharpCode.AvalonEdit;assembly=ICSharpCode.AvalonEdit"
xmlns:editing="clr-namespace:ICSharpCode.AvalonEdit.Editing;assembly=ICSharpCode.AvalonEdit"
xmlns:folding="clr-namespace:ICSharpCode.AvalonEdit.Folding;assembly=ICSharpCode.AvalonEdit"
xmlns:styles="urn:TomsToolbox.Wpf.Styles"
xmlns:themes="clr-namespace:ICSharpCode.ILSpy.Themes">
xmlns:properties="clr-namespace:ICSharpCode.ILSpy.Properties"
xmlns:controls="clr-namespace:ICSharpCode.ILSpy.Controls"
xmlns:local="clr-namespace:ICSharpCode.ILSpy.TextView"
xmlns:editing="clr-namespace:ICSharpCode.AvalonEdit.Editing;assembly=ICSharpCode.AvalonEdit"
xmlns:folding="clr-namespace:ICSharpCode.AvalonEdit.Folding;assembly=ICSharpCode.AvalonEdit"
xmlns:styles="urn:TomsToolbox.Wpf.Styles"
xmlns:themes="clr-namespace:ICSharpCode.ILSpy.Themes">
<UserControl.Resources>
<BooleanToVisibilityConverter x:Key="boolToVisibility" />
<SolidColorBrush x:Key="waitAdornerBackgoundBrush" Color="{DynamicResource {x:Static SystemColors.WindowColorKey}}" Opacity=".75"/>
<SolidColorBrush x:Key="waitAdornerBackgoundBrush" Color="{DynamicResource {x:Static SystemColors.WindowColorKey}}" Opacity=".75" />
<Style TargetType="{x:Type editing:TextArea}">
<Setter Property="FocusVisualStyle" Value="{x:Null}" />
<Setter Property="SelectionForeground" Value="{x:Null}" />
@ -31,15 +30,15 @@ @@ -31,15 +30,15 @@
<Grid>
<Border BorderThickness="1,1,0,1" BorderBrush="{DynamicResource {x:Static SystemColors.ControlLightBrushKey}}">
<Grid>
<ae:TextEditor Name="textEditor" AutomationProperties.Name="Decompilation" FontFamily="Consolas" FontSize="10pt" IsReadOnly="True"
Background="{DynamicResource {x:Static themes:ResourceKeys.TextBackgroundBrush}}"
Foreground="{DynamicResource {x:Static themes:ResourceKeys.TextForegroundBrush}}"
LineNumbersForeground="{DynamicResource {x:Static themes:ResourceKeys.LineNumbersForegroundBrush}}"
folding:FoldingMargin.FoldingMarkerBackgroundBrush="{DynamicResource {x:Static SystemColors.WindowBrushKey}}"
folding:FoldingMargin.SelectedFoldingMarkerBackgroundBrush="{DynamicResource {x:Static SystemColors.WindowBrushKey}}"
folding:FoldingMargin.FoldingMarkerBrush="{DynamicResource {x:Static SystemColors.ControlDarkBrushKey}}"
folding:FoldingMargin.SelectedFoldingMarkerBrush="{DynamicResource {x:Static SystemColors.ControlTextBrushKey}}">
<ae:TextEditor.Resources>
<local:DecompilerTextEditor x:Name="textEditor" AutomationProperties.Name="Decompilation" FontFamily="Consolas" FontSize="10pt" IsReadOnly="True"
Background="{DynamicResource {x:Static themes:ResourceKeys.TextBackgroundBrush}}"
Foreground="{DynamicResource {x:Static themes:ResourceKeys.TextForegroundBrush}}"
LineNumbersForeground="{DynamicResource {x:Static themes:ResourceKeys.LineNumbersForegroundBrush}}"
folding:FoldingMargin.FoldingMarkerBackgroundBrush="{DynamicResource {x:Static SystemColors.WindowBrushKey}}"
folding:FoldingMargin.SelectedFoldingMarkerBackgroundBrush="{DynamicResource {x:Static SystemColors.WindowBrushKey}}"
folding:FoldingMargin.FoldingMarkerBrush="{DynamicResource {x:Static SystemColors.ControlDarkBrushKey}}"
folding:FoldingMargin.SelectedFoldingMarkerBrush="{DynamicResource {x:Static SystemColors.ControlTextBrushKey}}">
<local:DecompilerTextEditor.Resources>
<!-- prevent App-wide button style from applying to the buttons in the search box -->
<Style TargetType="{x:Type Button}">
<Setter Property="Background" Value="{DynamicResource {x:Static SystemColors.ControlBrushKey}}" />
@ -65,9 +64,9 @@ @@ -65,9 +64,9 @@
</Trigger>
</Style.Triggers>
</Style>
</ae:TextEditor.Resources>
<ae:TextEditor.Template>
<ControlTemplate TargetType="{x:Type ae:TextEditor}">
</local:DecompilerTextEditor.Resources>
<local:DecompilerTextEditor.Template>
<ControlTemplate TargetType="{x:Type local:DecompilerTextEditor}">
<controls:ZoomScrollViewer
Focusable="False"
x:Name="PART_ScrollViewer"
@ -85,15 +84,15 @@ @@ -85,15 +84,15 @@
TextOptions.TextFormattingMode="{Binding CurrentZoom, ElementName=PART_ScrollViewer, Converter={x:Static local:ZoomLevelToTextFormattingModeConverter.Instance}}" />
<ControlTemplate.Triggers>
<Trigger Property="WordWrap"
Value="True">
Value="True">
<Setter TargetName="PART_ScrollViewer"
Property="HorizontalScrollBarVisibility"
Value="Disabled" />
Property="HorizontalScrollBarVisibility"
Value="Disabled" />
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</ae:TextEditor.Template>
</ae:TextEditor>
</local:DecompilerTextEditor.Template>
</local:DecompilerTextEditor>
<Border Name="waitAdorner" Background="{StaticResource waitAdornerBackgoundBrush}" Visibility="Collapsed">
<Grid>
<Grid.ColumnDefinitions>
@ -102,10 +101,10 @@ @@ -102,10 +101,10 @@
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<StackPanel Grid.Column="1" VerticalAlignment="Center">
<TextBlock Name="progressTitle" FontSize="14pt" Text="{x:Static properties:Resources.Decompiling}" Margin="3"/>
<TextBlock Name="progressTitle" FontSize="14pt" Text="{x:Static properties:Resources.Decompiling}" Margin="3" />
<ProgressBar Name="progressBar" Height="16" />
<TextBlock Name="progressText" Visibility="Collapsed" Margin="3" />
<Button Click="CancelButton_Click" HorizontalAlignment="Center" Margin="3" Content="{x:Static properties:Resources.Cancel}"/>
<Button Click="CancelButton_Click" HorizontalAlignment="Center" Margin="3" Content="{x:Static properties:Resources.Cancel}" />
</StackPanel>
</Grid>
</Border>

105
ILSpy/TextView/ThemeAwareHighlightingColorizer.cs

@ -0,0 +1,105 @@ @@ -0,0 +1,105 @@
using System;
using System.Collections.Generic;
using System.Windows.Media;
using ICSharpCode.AvalonEdit.Highlighting;
using ICSharpCode.AvalonEdit.Rendering;
using ICSharpCode.ILSpy.Themes;
namespace ICSharpCode.ILSpy.TextView;
#nullable enable
public class ThemeAwareHighlightingColorizer : HighlightingColorizer
{
private readonly Dictionary<HighlightingColor, HighlightingColor> _darkColors = new();
private readonly bool _isHighlightingThemeAware;
public ThemeAwareHighlightingColorizer(IHighlightingDefinition highlightingDefinition)
: base(highlightingDefinition)
{
_isHighlightingThemeAware = ThemeManager.Current.IsThemeAware(highlightingDefinition);
}
protected override void ApplyColorToElement(VisualLineElement element, HighlightingColor color)
{
if (!_isHighlightingThemeAware && ThemeManager.Current.IsDarkTheme)
{
color = GetColorForDarkTheme(color);
}
base.ApplyColorToElement(element, color);
}
private HighlightingColor GetColorForDarkTheme(HighlightingColor lightColor)
{
if (lightColor.Foreground is null && lightColor.Background is null)
{
return lightColor;
}
if (!_darkColors.TryGetValue(lightColor, out var darkColor))
{
darkColor = lightColor.Clone();
darkColor.Foreground = AdjustForDarkTheme(darkColor.Foreground);
darkColor.Background = AdjustForDarkTheme(darkColor.Background);
_darkColors[lightColor] = darkColor;
}
return darkColor;
}
private static HighlightingBrush? AdjustForDarkTheme(HighlightingBrush? lightBrush)
{
if (lightBrush is SimpleHighlightingBrush simpleBrush && simpleBrush.GetBrush(null) is SolidColorBrush brush)
{
return new SimpleHighlightingBrush(AdjustForDarkTheme(brush.Color));
}
return lightBrush;
}
private static Color AdjustForDarkTheme(Color color)
{
var c = System.Drawing.Color.FromArgb(color.R, color.G, color.B);
var (h, s, l) = (c.GetHue(), c.GetSaturation(), c.GetBrightness());
// Invert the lightness, but also increase it a bit
l = 1f - MathF.Pow(l, 1.2f);
// Desaturate the colors, as they'd be too intense otherwise
if (s > 0.75f && l < 0.75f)
{
s *= 0.75f;
l *= 1.2f;
}
var (r, g, b) = HslToRgb(h, s, l);
return Color.FromArgb(color.A, r, g, b);
}
private static (byte r, byte g, byte b) HslToRgb(float h, float s, float l)
{
// https://en.wikipedia.org/wiki/HSL_and_HSV#HSL_to_RGB
var c = (1f - Math.Abs(2f * l - 1f)) * s;
h = h % 360f / 60f;
var x = c * (1f - Math.Abs(h % 2f - 1f));
var (r1, g1, b1) = (int)Math.Floor(h) switch {
0 => (c, x, 0f),
1 => (x, c, 0f),
2 => (0f, c, x),
3 => (0f, x, c),
4 => (x, 0f, c),
_ => (c, 0f, x)
};
var m = l - c / 2f;
var r = (byte)((r1 + m) * 255f);
var g = (byte)((g1 + m) * 255f);
var b = (byte)((b1 + m) * 255f);
return (r, g, b);
}
}

15
ILSpy/Themes/ThemeManager.cs

@ -19,11 +19,11 @@ @@ -19,11 +19,11 @@
#nullable enable
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using ICSharpCode.AvalonEdit.Highlighting;
@ -31,6 +31,8 @@ namespace ICSharpCode.ILSpy.Themes @@ -31,6 +31,8 @@ namespace ICSharpCode.ILSpy.Themes
{
public class ThemeManager
{
private const string _isThemeAwareKey = "ILSpy.IsThemeAware";
private string? _theme;
private readonly ResourceDictionary _themeDictionaryContainer = new();
private readonly Dictionary<string, SyntaxColor> _syntaxColors = new();
@ -44,6 +46,8 @@ namespace ICSharpCode.ILSpy.Themes @@ -44,6 +46,8 @@ namespace ICSharpCode.ILSpy.Themes
public string DefaultTheme => "Light";
public bool IsDarkTheme { get; private set; }
public static IReadOnlyCollection<string> AllThemes => new[] {
"Light",
"Dark",
@ -89,6 +93,13 @@ namespace ICSharpCode.ILSpy.Themes @@ -89,6 +93,13 @@ namespace ICSharpCode.ILSpy.Themes
if (color is not null)
syntaxColor.ApplyTo(color);
}
highlightingDefinition.Properties[_isThemeAwareKey] = bool.TrueString;
}
public bool IsThemeAware(IHighlightingDefinition highlightingDefinition)
{
return highlightingDefinition.Properties.TryGetValue(_isThemeAwareKey, out var value) && value == bool.TrueString;
}
private void UpdateTheme(string? themeName)
@ -109,6 +120,8 @@ namespace ICSharpCode.ILSpy.Themes @@ -109,6 +120,8 @@ namespace ICSharpCode.ILSpy.Themes
var resourceDictionary = new ResourceDictionary { Source = new Uri($"/themes/Theme.{themeFileName}.xaml", UriKind.Relative) };
_themeDictionaryContainer.MergedDictionaries.Add(resourceDictionary);
IsDarkTheme = resourceDictionary[ResourceKeys.TextBackgroundBrush] is SolidColorBrush { Color: { R: < 128, G: < 128, B: < 128 } };
// Iterate over keys first, because we don't want to instantiate all values eagerly, if we don't need them.
foreach (var item in resourceDictionary.Keys)
{

Loading…
Cancel
Save