mirror of https://github.com/icsharpcode/ILSpy.git
Browse Source
The dark palette is hand-authored for C# only; every other highlighting definition -- XML, IL, Asm, and all AvaloniaEdit built-ins -- is derived by inverting HSL lightness. HSL lightness is not perceptual luminance, so the result depended entirely on hue: blue carries a 0.0722 luminance weight, so plain Blue landed at 4.08:1 against the editor canvas, and an already-light source such as Asm's #8080FF inverted downwards to 1.29:1 -- invisible. Reported against XML resources in #3986. Enforcing a 5.5:1 WCAG floor on the converted foreground fixes every affected definition in the one place they all route through, which a per-language palette would not: the AvaloniaEdit built-ins (JSON, Markdown, JS, HTML, CSS, Python) have no palette to author. 5.5 is where the existing CSharpDark values already sit; the 4.5 AA threshold was measured and only moves the reported blue to 4.51. The floor is deliberately foreground-only -- forcing a span background to contrast with the canvas would repaint Asm's #EEEEEE Registers background as a bright block and bury the text on top of it -- and it is measured against the surface the foreground lands on, which is that span background when the colour declares one, so a light-on-dark span cannot be pulled apart into two colours that no longer contrast with each other. The same function's desaturation guard only fired when the inverted lightness stayed below 0.75, so a dark fully saturated source (DarkMagenta) came back light and still fully saturated -- exactly the neon the softening exists to prevent. Only the softening becomes unconditional; the lightness lift paired with it stays scoped to over-saturated colours, because it is not monotone across its own 0.75 boundary and would reorder neighbouring greys. Hyperlinks were a second, unrelated path: nothing ever set TextView.LinkTextForegroundBrush, so the About page and every decompiler-view link used AvaloniaEdit's registered default of pure blue, 1.94:1 on dark. They now share a themed ILSpy.LinkForeground with the metadata table's token cells, which take it from a style rather than a local Foreground so the selected row's white override still wins over the accent fill. Assisted-by: Claude:claude-opus-5[1m]:Claude Codepull/4013/head
6 changed files with 404 additions and 14 deletions
@ -0,0 +1,93 @@
@@ -0,0 +1,93 @@
|
||||
// Copyright (c) 2026 Christoph Wille
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy of this
|
||||
// software and associated documentation files (the "Software"), to deal in the Software
|
||||
// without restriction, including without limitation the rights to use, copy, modify, merge,
|
||||
// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons
|
||||
// to whom the Software is furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in all copies or
|
||||
// substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
|
||||
// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
|
||||
// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
|
||||
// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
|
||||
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
// DEALINGS IN THE SOFTWARE.
|
||||
|
||||
using System; |
||||
|
||||
using Avalonia; |
||||
using Avalonia.Controls; |
||||
using Avalonia.Headless.NUnit; |
||||
using Avalonia.Media; |
||||
using Avalonia.Styling; |
||||
using Avalonia.Threading; |
||||
|
||||
using AwesomeAssertions; |
||||
|
||||
using ICSharpCode.ILSpy.TextView; |
||||
|
||||
using NUnit.Framework; |
||||
|
||||
namespace ICSharpCode.ILSpy.Tests.Themes; |
||||
|
||||
/// <summary>
|
||||
/// The About page and every other hyperlink in a decompiler view are AvaloniaEdit
|
||||
/// <c>VisualLineLinkText</c> runs, which take their color from
|
||||
/// <c>TextView.LinkTextForegroundBrush</c>. AvaloniaEdit's registered default is pure blue,
|
||||
/// 1.9:1 against the dark editor canvas, so App.axaml restyles the property through
|
||||
/// <c>ILSpy.LinkForeground</c>. Verified here because a typo in the selector or the xmlns
|
||||
/// would silently fall back to that unreadable default.
|
||||
/// </summary>
|
||||
[TestFixture] |
||||
public class LinkColorTests |
||||
{ |
||||
[AvaloniaTest] |
||||
public void Editor_TextView_Takes_Its_Link_Color_From_The_Theme() |
||||
{ |
||||
var textView = HostedTextView(); |
||||
|
||||
BrushColor(textView.LinkTextForegroundBrush) |
||||
.Should().NotBe(Colors.Blue, "the App.axaml style must replace AvaloniaEdit's default link brush"); |
||||
} |
||||
|
||||
[AvaloniaTest] |
||||
public void Link_Color_Follows_The_Theme_Variant() |
||||
{ |
||||
var app = Application.Current ?? throw new InvalidOperationException("no Application"); |
||||
var previous = app.RequestedThemeVariant; |
||||
try |
||||
{ |
||||
var textView = HostedTextView(); |
||||
|
||||
app.RequestedThemeVariant = ThemeVariant.Light; |
||||
Dispatcher.UIThread.RunJobs(); |
||||
var light = BrushColor(textView.LinkTextForegroundBrush); |
||||
|
||||
app.RequestedThemeVariant = ThemeVariant.Dark; |
||||
Dispatcher.UIThread.RunJobs(); |
||||
var dark = BrushColor(textView.LinkTextForegroundBrush); |
||||
|
||||
dark.Should().NotBe(light, "each theme dictionary defines its own ILSpy.LinkForeground"); |
||||
} |
||||
finally |
||||
{ |
||||
app.RequestedThemeVariant = previous; |
||||
} |
||||
} |
||||
|
||||
// Application-level styles only apply once the control is attached to a TopLevel.
|
||||
static AvaloniaEdit.Rendering.TextView HostedTextView() |
||||
{ |
||||
var editor = new DecompilerTextEditor(); |
||||
var window = new Window { Content = editor, Width = 400, Height = 300 }; |
||||
window.Show(); |
||||
Dispatcher.UIThread.RunJobs(); |
||||
return editor.TextArea.TextView; |
||||
} |
||||
|
||||
static Color BrushColor(IBrush? brush) |
||||
=> (brush as ISolidColorBrush)?.Color ?? throw new InvalidOperationException("not a solid color brush"); |
||||
} |
||||
Loading…
Reference in new issue