-
Notifications
You must be signed in to change notification settings - Fork 5
Expand file tree
/
Copy pathuAnalyserTheme.pas
More file actions
562 lines (512 loc) · 22.8 KB
/
Copy pathuAnalyserTheme.pas
File metadata and controls
562 lines (512 loc) · 22.8 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
unit uAnalyserTheme;
// Theme-Helper fuer das Static Code Analysis Tool for Delphi.
// Aufgabe: Severity-bezogene Farben aus dem aktiven IDE-/VCL-Theme ableiten.
//
// Das Modul kennt KEINE Strings - es operiert ausschliesslich auf dem
// TFindingSeverity-Enum aus uAnalyserTypes. Konvertierung von/zum String
// findet nur an der UI-Grenze statt.
//
// Ergebnis-Farben werden ueber StyleServices.GetSystemColor zur Aufruf-
// Zeit bestimmt - der aktive VCL-Style/IDE-Theme bestimmt die Basis,
// auf die der Severity-Akzent gemischt wird. Damit gleicher Code in
// Light-, Dark- und Custom-Themes konsistent aussieht.
interface
uses
System.SysUtils,
Vcl.Graphics, Vcl.Themes,
uAnalyserTypes;
type
TStyleServicesProvider = reference to function: TCustomStyleServices;
// Liefert eine Farbe aus einer Quelle, die diese Unit nicht kennen darf.
TStyleColorProvider = reference to function: TColor;
var
// Global Hook fuer Color-Auflösung. IDE-Plugin setzt das auf eine
// Funktion die IOTAIDEThemingServices.StyleServices liefert.
// Standalone laesst's nil — ActiveStyleServices faellt dann auf die
// VCL-globale Vcl.Themes.StyleServices zurueck.
StyleServicesProvider: TStyleServicesProvider = nil;
// Global Hook fuer die ECHTE Hintergrundfarbe des Code-Editors. Das
// IDE-Plugin setzt das auf eine Funktion, die
// INTACodeEditorServices.Options.BackgroundColor[atWhiteSpace] liefert
// (TIDETheme.EditorBg); ausserhalb der IDE bleibt es nil.
//
// WOFUER: GCachedEditorBgDark steuert, ob EditorAccent die Hell- oder die
// Dunkel-Variante der Gray/Subtle-Paletten waehlt - also die Farbe der
// Markierungen IM EDITOR. Bis 2026-08-10 kam die Antwort aus
// IsActiveThemeDark, das GetSystemColor(clWindow) misst: die Farbe des
// IDE-RAHMENS. Bei hellem Editor in dunkler IDE (und umgekehrt) wurde
// damit die falsche Variante gewaehlt - und zwar genau in dem Fall, fuer
// den TIDETheme.EditorBg ueberhaupt existiert. Der Kommentar in
// uAnalyserPalette hatte die richtige Regel schon beschrieben, der Code
// folgte ihr nur nicht.
//
// Als Hook und nicht als direkter Aufruf, weil diese Unit in
// SCA.SharedUI liegt und ToolsAPI nicht kennen darf - dieselbe
// Schichtgrenze und dasselbe Muster wie bei StyleServicesProvider
// daneben. Der Setzer muss ihn beim Entladen wieder auf nil setzen,
// sonst zeigt die Closure in entladenen Plugin-Code.
EditorBgProvider: TStyleColorProvider = nil;
// Liefert die aktive TCustomStyleServices. Im IDE-Plugin-Kontext via
// StyleServicesProvider die IDE-spezifische (folgt IDE-Theme).
// Sonst die VCL-globale (folgt TStyleManager.ActiveStyle).
function ActiveStyleServices: TCustomStyleServices;
// Saturierte Akzentfarbe fuer eine Severity. Wird verwendet:
// * 3px-Indikatorleiste am linken Zellenrand
// * Akzent-Schriftfarben fuer "Vorher"/"Nachher"-Labels
// * Mix-Quelle in SeverityBg
function SeverityAccent(Severity: TFindingSeverity): TColor;
type
// Farbschema NUR fuer Editor-Marker (Stripe + Mini-Infobar + Overlay-
// Titlebar). Properties-Panel + Hauptfenster-Grid + Stat-Tiles ignorieren
// den Schema-Schalter und behalten ihre eigenen Severity-Farben aus
// SeverityAccent.
TEditorColorScheme = (
ecsDefault, // Original-ACCENT_* (kraftvoll, hohe Saturation)
ecsGray, // Reine Graustufen, kein Farbton
ecsSubtle // Gedaempfte Farben, niedrige Saturation
);
// Editor-spezifische Akzentfarbe. Nutzt das gewuenschte Schema und passt
// die Helligkeit automatisch an Light/Dark-Theme an (BgIsDark = True ->
// dunkler Theme-Hintergrund -> hellere Marker, sonst umgekehrt).
function EditorAccent(Severity: TFindingSeverity;
Scheme: TEditorColorScheme; BgIsDark: Boolean): TColor;
// Heuristisch: True wenn der aktive Theme einen dunklen Hintergrund hat.
// Schwellwert: Luminanz von StyleServices.GetSystemColor(clWindow) < 128.
// ACHTUNG: das ist der IDE-RAHMEN. Fuer alles, was IM EDITOR gezeichnet
// wird, ist IsEditorBgDark die richtige Frage.
function IsActiveThemeDark: Boolean;
/// <summary>
/// True, wenn der Hintergrund des CODE-EDITORS dunkel ist. Fuer die
/// Farbwahl der Editor-Markierungen die richtige Frage; ohne gesetzten
/// EditorBgProvider identisch zu IsActiveThemeDark.
/// </summary>
function IsEditorBgDark: Boolean;
/// <summary>
/// Frischt NUR GCachedEditorBgDark auf, ohne Einstellungen zu lesen.
/// </summary>
/// <remarks>
/// Fuer Theme- und Editor-Farbschema-Wechsel: dabei aendert sich die
/// Helligkeit des Untergrunds, nicht das eingestellte Marker-Schema.
/// RefreshEditorColorSchemeCache waere dafuer zu grob - es braucht den
/// Schema-String und damit einen INI-Zugriff, der hier nichts beitraegt.
/// Faengt jeden Fehler ab; im Zweifel bleibt der Cache auf "hell".
/// </remarks>
procedure RefreshEditorBgDarkCache;
// String <-> Enum Konvertierung fuer INI-Persistierung. Akzeptiert die
// Tokens aus EDITOR_COLOR_SCHEME_INFO, case-insensitiv. Unbekannt ->
// ecsDefault (mit Debug-Log, damit ein Tippfehler auffaellt).
function ParseEditorColorScheme(const S: string): TEditorColorScheme;
function EditorColorSchemeToStr(Scheme: TEditorColorScheme): string;
// Aktualisiert die globalen GCachedEditorScheme + GCachedEditorBgDark.
// Caller liefert den String aus seiner Settings-Quelle - so kommt der
// uAnalyserTheme-Layer ohne TRepoSettings-Abhaengigkeit aus (Layering:
// SCA.SharedUI darf SCA.Engine.uRepoSettings nicht direkt importieren).
// Komplett defensiv - Exceptions werden geschluckt, die Defaults bleiben
// erhalten.
procedure RefreshEditorColorSchemeCache(const ASchemeStr: string);
type
/// <summary>Alles, was je Farbschema variiert: der Token fuer die INI
/// und der Text fuer die Anzeige.</summary>
TEditorColorSchemeInfo = record
/// <summary>INI-Wert. STABIL und niemals uebersetzt - er steht in
/// Anwender-Dateien und muss ein Update ueberleben.</summary>
Token : string;
/// <summary>Anzeigetext. Englisch hinterlegt, die Uebersetzung holt
/// der Aufrufer mit _() - die msgid bleibt damit dieselbe wie zu der
/// Zeit, als der Text noch literal im Options-Dialog stand.</summary>
Caption : string;
end;
const
// EINE Tabelle ueber das GANZE Enum (Fremdbericht 2026-08-23, Befund 3).
//
// Vorher lagen dieselben drei Schemata an drei Stellen: ein
// array[0..2] fuer die Reihenfolge, drei literale Items.Add im
// Options-Dialog und zwei case-Anweisungen fuer die Persistenz. Ein
// viertes Schema waere an allen dreien still durchgefallen - im
// schlimmsten Fall haette der Anwender es gewaehlt, und die INI haette
// 'default' behalten, weil EditorColorSchemeToStr ein else hatte.
//
// Jetzt loest dasselbe Versaeumnis E2072 aus, und zwar hier.
EDITOR_COLOR_SCHEME_INFO: array[TEditorColorScheme] of TEditorColorSchemeInfo =
((Token: 'default'; Caption: 'Default (bright colors)'),
(Token: 'gray'; Caption: 'Gray (neutral)'),
(Token: 'subtle'; Caption: 'Subtle (muted colors)'));
// Display-Reihenfolge fuer ComboBoxen. Index hier == ComboBox.ItemIndex.
// Die Laenge haengt am Enum, nicht an einer Zahl - ein neues Schema
// ohne Eintrag hier gibt E2072 statt einer stillen Luecke im Combo.
EDITOR_COLOR_SCHEME_ORDER: array[0..Ord(High(TEditorColorScheme))] of TEditorColorScheme =
(ecsDefault, ecsGray, ecsSubtle);
/// <summary>ComboBox-Index des Eintrags "Text", der KEIN Farbschema
/// ist, sondern [UI] OverlayTextOnly schaltet. Er haengt hinten an den
/// Schemata; die Zahl stand vorher zweimal literal im Options-Dialog
/// (uIDESCAOptions, zwei verschiedene Methoden).</summary>
SCHEME_IDX_TEXTONLY = High(EDITOR_COLOR_SCHEME_ORDER) + 1;
// Konvertierung ComboBox.ItemIndex <-> TEditorColorScheme. Fuer Caller
// die das Combo-Mapping nicht jedes Mal selbst hinschreiben wollen.
function SchemeFromComboIndex(AIndex: Integer): TEditorColorScheme;
function ComboIndexFromScheme(Scheme: TEditorColorScheme): Integer;
var
// Globaler Cache fuer das Editor-Farbschema. Wird vom IDE-Plugin-Init
// gesetzt und nach jedem Settings-Save / Theme-Change refresht.
// BuildMarkEntries / HighlightAllFindingsInFile lesen nur diese Var -
// kein TRepoSettings.Create + StyleServices-Lookup pro Scan-Lauf.
// Defaults sind sicher (ecsDefault, False) bis das Plugin sie aktiv setzt.
GCachedEditorScheme : TEditorColorScheme = ecsDefault;
GCachedEditorBgDark : Boolean = False;
// Themed Severity-Hintergrundfarbe. Mischt einen kleinen Anteil
// (TINT_RATIO) der Akzentfarbe in die theme-aufgeloeste Basisfarbe.
// ABase = clWindow -> Default fuer Datentabellen
// ABase = clBtnFace -> fuer Chrome-Elemente (z. B. Help-Desc-Label)
function SeverityBg(Severity: TFindingSeverity;
ABase: TColor = clWindow): TColor; overload;
// Wie oben, aber mit explizitem StyleServices fuer die Color-Aufloesung.
// Wird vom IDE-Plugin-Renderer aufgerufen — dort muss die IDE-spezifische
// Theming.StyleServices verwendet werden (sonst gewinnt die VCL-globale
// und Severity-Hintergruende rendern im VCL-Style statt im IDE-Theme).
function SeverityBg(Severity: TFindingSeverity;
ABase: TColor; AStyleServices: TCustomStyleServices): TColor; overload;
// Lineare Farbmischung. Ratio=0 -> Base, Ratio=1 -> Accent.
// Loest System-Color-Indices vorher per ColorToRGB auf.
function BlendColor(Base, Accent: TColor; Ratio: Single): TColor;
/// <summary>
/// True, wenn AColor als "hell" wirkt und schwarze Schrift darauf besser
/// kontrastiert als weisse. Luminanz nach ITU-R BT.601, Schwelle 127.
/// </summary>
/// <remarks>
/// Hier und nicht je Aufrufer: die Funktion lag wortgleich zweimal im
/// Plugin (uIDELineHighlighter und uIDEAnnotationOverlay). Zwei Kopien
/// einer Kontrastregel koennen auseinanderlaufen, ohne dass es auffaellt -
/// das Ergebnis waere Schrift, die an einer Stelle lesbar ist und an der
/// anderen nicht.
///
/// Bewusst NICHT mit IsActiveThemeDark zusammengelegt: das misst das
/// THEME (eine Systemfarbe, arithmetisches Mittel), diese Funktion eine
/// BELIEBIGE uebergebene Farbe. Gleiche Frage, andere Quelle und andere
/// Formel - siehe Konzept_UiArchitektur_IdePlugin_2026-08-10.md.
///
/// System-Color-Indices werden per ColorToRGB aufgeloest, clNone liefert
/// damit den Wert der aktuellen Systemfarbe und nicht $1FFFFFFF.
/// </remarks>
function IsLightColor(AColor: TColor): Boolean;
/// <summary>
/// Sichert den Akzent des Nur-Text-Hints gegen den Editor-Hintergrund:
/// liegt seine BT.601-Luminanz zu nah an der typischen Hintergrund-
/// Luminanz, wird er Richtung Weiss (dunkler Hintergrund) bzw. Schwarz
/// (heller Hintergrund) aufgehellt/abgedunkelt, bis ein MINDESTABSTAND
/// erreicht ist. Bewusst Abstand statt Seitenvergleich - die an
/// ApplyStyleColors notierte Schwaeche (gleiche Seite reicht als
/// Kriterium nicht, ein dunkles Rot auf dunklem Grau ist unlesbar,
/// obwohl beide "dunkel" sind) wird hier nicht wiederholt.
/// </summary>
function EnsureHintContrast(AAccent: TColor; ABgIsDark: Boolean): TColor;
implementation
// noinspection-file EmptyArgumentList, GroupedDeclaration, TooLongLine, UnsortedUses, UnusedParameter
// Self-scan Stil-Cluster - im jeweiligen File idiomatisch oder Hot-Path-bedingt.
uses
Winapi.Windows,
uAnalyserPalette;
function ActiveStyleServices: TCustomStyleServices;
begin
Result := nil;
if Assigned(StyleServicesProvider) then
Result := StyleServicesProvider();
if not Assigned(Result) then
Result := Vcl.Themes.StyleServices;
end;
function SeverityAccent(Severity: TFindingSeverity): TColor;
begin
case Severity of
fsError: Result := ACCENT_ERROR;
fsWarning: Result := ACCENT_WARNING;
fsHint: Result := ACCENT_HINT;
fsFileError: Result := ACCENT_FILEERROR;
else
Result := clNone;
end;
end;
const
// Die Farbwahl IST eine Tabelle - also steht sie auch als eine da.
// Indizes: [Schema, Hintergrund dunkel, Severity].
//
// Vorher waren das ein aeusseres case ueber das Schema, zwei
// if BgIsDark und vier innere case ueber die Severity - 45 Zeilen
// Kontrollfluss fuer 16 Farbwerte. Der eigene Detektor hat das als
// CaseStatementSize gemeldet, zu Recht.
//
// Der eigentliche Gewinn ist aber nicht die Kuerze: der Compiler
// erzwingt jetzt VOLLSTAENDIGKEIT auf BEIDEN Achsen. Wer
// TFindingSeverity oder TEditorColorScheme erweitert, bekommt E2072 und
// muss eine Farbe waehlen. Die alte Fassung liess einen neuen Enum-Wert ueber ihr
// 'else' stillschweigend auf clNone laufen - unsichtbar, bis jemand
// den fehlenden Marker im Editor bemerkt.
//
// fsUnknown ist clNone - dieselbe Wahl wie im frueheren else-Zweig,
// hier nur sichtbar statt versteckt.
//
// WARUM ecsDefault MITGEFUEHRT WIRD, obwohl es nie gelesen wird
// (Fremdbericht 2026-08-23, Befund 2):
// Die Tabelle stand bis dahin ueber dem TEILBEREICH ecsGray..ecsSubtle,
// weil ecsDefault theme-unabhaengig ist und an SeverityAccent
// delegiert. Damit galt die oben behauptete Vollstaendigkeit aber nur
// fuer EINE der beiden Achsen: ein Teilbereich bleibt gueltig, wenn
// das Enum waechst. Ein vierter Wert haette die Tabelle KLAGLOS
// uebersetzt und danach EDITOR_ACCENT[Scheme, ...] an ihr vorbei
// gelesen - ohne Bereichspruefung ein stiller Fehlgriff, kein Fehler.
//
// Der Preis ist eine tote Zeile. Dafuer loest jetzt beides E2072 aus:
// eine neue Severity verkuerzt jede Zeile, ein neues Schema laesst
// eine ganze fehlen.
EDITOR_ACCENT : array[TEditorColorScheme, Boolean, TFindingSeverity] of TColor =
(
// ecsDefault - NIE gelesen: EditorAccent beantwortet es vorher ueber
// SeverityAccent. Steht nur da, damit das Enum vollstaendig indiziert
// ist; wer hier Farben eintraegt, aendert nichts.
( { hell } (clNone, clNone, clNone, clNone, clNone),
{ dunkel } (clNone, clNone, clNone, clNone, clNone) ),
// ecsGray
( { hell } (clNone, GRAY_LIGHT_ERROR, GRAY_LIGHT_WARNING,
GRAY_LIGHT_HINT, GRAY_LIGHT_FILEERROR),
{ dunkel } (clNone, GRAY_DARK_ERROR, GRAY_DARK_WARNING,
GRAY_DARK_HINT, GRAY_DARK_FILEERROR) ),
// ecsSubtle
( { hell } (clNone, SUBTLE_LIGHT_ERROR, SUBTLE_LIGHT_WARNING,
SUBTLE_LIGHT_HINT, SUBTLE_LIGHT_FILEERROR),
{ dunkel } (clNone, SUBTLE_DARK_ERROR, SUBTLE_DARK_WARNING,
SUBTLE_DARK_HINT, SUBTLE_DARK_FILEERROR) )
);
function EditorAccent(Severity: TFindingSeverity;
Scheme: TEditorColorScheme; BgIsDark: Boolean): TColor;
begin
if Scheme = ecsDefault then
Result := SeverityAccent(Severity) // theme-unabhaengig, s.o.
else
Result := EDITOR_ACCENT[Scheme, BgIsDark, Severity];
end;
const
// Mid-Point der 0..255-Luminanz-Skala. Dark-Themes haben typisch
// ~30 (#1E1E1E), Light-Themes ~240 (#F0F0F0) - 128 trennt sicher.
THEME_DARK_AVG_THRESHOLD = 128;
// Mitte derselben Skala fuer die gewichtete BT.601-Luminanz. Getrennte
// Konstante, weil die Frage eine andere ist: hier geht es um den
// Schriftkontrast auf einer beliebigen Flaeche, oben um die Einordnung
// des Themes anhand einer Systemfarbe.
CONTRAST_LUM_THRESHOLD = 127;
function ColorLuma(AColor: TColor): Integer;
// Gewichtete BT.601-Luminanz (0..255) einer aufgeloesten Farbe.
// ITU-R BT.601: Gruen wiegt am schwersten, Blau am leichtesten - das
// entspricht der Helligkeitswahrnehmung des Auges besser als ein
// arithmetisches Mittel. Gemeinsamer Kern von IsLightColor und
// EnsureHintContrast - zwei Kopien derselben Formel koennen
// auseinanderlaufen, ohne dass es auffaellt.
var
rgb : Cardinal;
begin
rgb := ColorToRGB(AColor);
Result := (Integer(GetRValue(rgb)) * 299
+ Integer(GetGValue(rgb)) * 587
+ Integer(GetBValue(rgb)) * 114) div 1000;
end;
function IsLightColor(AColor: TColor): Boolean;
// Vertrag und Begruendung stehen an der Deklaration.
begin
Result := ColorLuma(AColor) > CONTRAST_LUM_THRESHOLD;
end;
// noinspection BooleanParam
// Das Flag kommt woertlich aus IsEditorBgDark und folgt dem Nachbarn
// EditorAccent(BgIsDark) - ein Zwei-Wert-Enum waere Zeremonie fuer
// dieselbe Information.
function EnsureHintContrast(AAccent: TColor; ABgIsDark: Boolean): TColor;
// Vertrag steht an der Deklaration. Der Hintergrund kommt als Flag
// herein (IsEditorBgDark) - gerechnet wird gegen die TYPISCHE Luminanz
// der jeweiligen Seite, nicht gegen eine konkrete Farbe: der Zeichenpfad
// kennt nur das Flag, und die Schwelle soll fuer alle gaengigen Themes
// tragen (Dark ~30 #1E1E1E, Light ~240 #F0F0F0).
const
DARK_BG_LUM = 30;
LIGHT_BG_LUM = 240;
// Mindestabstand auf der 0..255-Skala. 90 liegt bewusst ueber der
// Wahrnehmungsschwelle - der Text hat keinen Hintergrundbalken mehr,
// der ihn traegt, die Farbe ist sein einziges Signal.
MIN_LUM_GAP = 90;
var
Cur, Target : Integer;
begin
Result := ColorToRGB(AAccent);
Cur := ColorLuma(Result);
if ABgIsDark then
begin
Target := DARK_BG_LUM + MIN_LUM_GAP;
if Cur >= Target then Exit;
// Linear Richtung Weiss mischen: die Luminanz waechst linear mit der
// Ratio, also trifft genau dieser Anteil den Zielabstand.
Result := BlendColor(Result, clWhite, (Target - Cur) / (255 - Cur));
end
else
begin
Target := LIGHT_BG_LUM - MIN_LUM_GAP;
if Cur <= Target then Exit;
Result := BlendColor(Result, clBlack, (Cur - Target) / Cur);
end;
end;
function IsEditorBgDark: Boolean;
// Ist der Hintergrund des CODE-EDITORS dunkel? Das ist die Frage, die fuer
// die Farbe der Editor-Markierungen zaehlt - nicht, ob das IDE-Theme
// dunkel ist. Beides faellt oft zusammen, aber eben nicht immer.
//
// Ohne gesetzten EditorBgProvider (Standalone-EXE, Tests) faellt die
// Funktion auf IsActiveThemeDark zurueck - dasselbe Verhalten wie vor dem
// 2026-08-10, nur als bewusster Rueckfall statt als einziger Weg.
//
// Gerechnet wird mit IsLightColor, also BT.601 wie ueberall sonst beim
// Kontrast; der Rueckfall behaelt seine eigene Formel, weil er eine
// Systemfarbe misst (Begruendung an IsActiveThemeDark).
var
Bg : TColor;
begin
if not Assigned(EditorBgProvider) then
Exit(IsActiveThemeDark);
Bg := clNone;
try
Bg := EditorBgProvider();
// Provider zeigt ins Leere (Plugin halb entladen) oder die ToolsAPI
// liefert nicht - Bg bleibt clNone und die Zeile darunter faellt auf
// IsActiveThemeDark zurueck. Eine Farbfrage darf nichts abbrechen.
// noinspection EmptyExcept
except
end;
if Bg = clNone then
Exit(IsActiveThemeDark);
Result := not IsLightColor(Bg);
end;
procedure RefreshEditorBgDarkCache;
// Vertrag steht an der Deklaration. IsEditorBgDark statt
// IsActiveThemeDark (2026-08-10): der Cache steuert die Farbe der Marker
// IM EDITOR, also muss er den Editor-Hintergrund messen und nicht den
// IDE-Rahmen.
begin
try
GCachedEditorBgDark := IsEditorBgDark;
except
GCachedEditorBgDark := False;
end;
end;
function IsActiveThemeDark: Boolean;
// Einfache RGB-Durchschnitts-Heuristik (statt gewichtetem Luminanz-
// Mittel Y = 0.299R + 0.587G + 0.114B) weil clWindow meist neutral-grau
// ist und der Unterschied zwischen Light/Dark eindeutig liegt.
var
Svc : TCustomStyleServices;
C : TColor;
rgb : Cardinal;
Avg : Integer;
begin
Result := False;
Svc := ActiveStyleServices;
if not Assigned(Svc) then Exit;
try
C := Svc.GetSystemColor(clWindow);
except
Exit;
end;
rgb := ColorToRGB(C);
Avg := (GetRValue(rgb) + GetGValue(rgb) + GetBValue(rgb)) div 3;
Result := Avg < THEME_DARK_AVG_THRESHOLD;
end;
function ParseEditorColorScheme(const S: string): TEditorColorScheme;
// Schleife ueber die Tokens der Tabelle - ein neues Schema wird damit
// gelesen, sobald es dort steht, ohne dass hier jemand nachzieht.
var
Lower : string;
Sch : TEditorColorScheme;
begin
Lower := LowerCase(Trim(S));
for Sch := Low(TEditorColorScheme) to High(TEditorColorScheme) do
if Lower = EDITOR_COLOR_SCHEME_INFO[Sch].Token then Exit(Sch);
// Unbekannter Wert: still auf Default zurueck, aber Debug-Log fuer den
// Fall, dass in der INI ein Tippfehler steht (z.B. 'grey' statt
// 'gray'). Leerer String und Erst-Init liefern auch Default, das ist
// richtig - nur 'echte' Tippfehler werden geloggt.
if Lower <> '' then
Winapi.Windows.OutputDebugString(PChar(
'SCA: unknown EditorColorScheme value ' + QuotedStr(S) +
' - falling back to default'));
Result := ecsDefault;
end;
function EditorColorSchemeToStr(Scheme: TEditorColorScheme): string;
// Nachschlagen statt case: das fruehere else hat ein neues Schema still
// als 'default' persistiert - der Anwender waehlt es, die INI behaelt
// etwas anderes, und beim naechsten Laden ist die Wahl weg.
begin
Result := EDITOR_COLOR_SCHEME_INFO[Scheme].Token;
end;
procedure RefreshEditorColorSchemeCache(const ASchemeStr: string);
begin
try
GCachedEditorScheme := ParseEditorColorScheme(ASchemeStr);
except
GCachedEditorScheme := ecsDefault;
end;
RefreshEditorBgDarkCache;
end;
function SchemeFromComboIndex(AIndex: Integer): TEditorColorScheme;
begin
if (AIndex >= Low(EDITOR_COLOR_SCHEME_ORDER)) and
(AIndex <= High(EDITOR_COLOR_SCHEME_ORDER)) then
Result := EDITOR_COLOR_SCHEME_ORDER[AIndex]
else
Result := ecsDefault;
end;
function ComboIndexFromScheme(Scheme: TEditorColorScheme): Integer;
var
i : Integer;
begin
for i := Low(EDITOR_COLOR_SCHEME_ORDER) to High(EDITOR_COLOR_SCHEME_ORDER) do
if EDITOR_COLOR_SCHEME_ORDER[i] = Scheme then Exit(i);
Result := 0; // Fallback: erster Eintrag (Default)
end;
function BlendColor(Base, Accent: TColor; Ratio: Single): TColor;
var
rgbB, rgbA : Cardinal;
rB, gB, bB : Integer;
rA, gA, bA : Integer;
Inv : Single;
begin
rgbB := ColorToRGB(Base);
rgbA := ColorToRGB(Accent);
rB := GetRValue(rgbB); gB := GetGValue(rgbB); bB := GetBValue(rgbB);
rA := GetRValue(rgbA); gA := GetGValue(rgbA); bA := GetBValue(rgbA);
Inv := 1 - Ratio;
Result := Winapi.Windows.RGB(
Round(rB * Inv + rA * Ratio),
Round(gB * Inv + gA * Ratio),
Round(bB * Inv + bA * Ratio));
end;
function SeverityBg(Severity: TFindingSeverity;
ABase: TColor): TColor;
begin
// Default-Pfad: via Vcl.Themes.StyleServices (global). Im Docked-Mode
// wird die globale durch Theming.ApplyTheme(Form) korrekt mitgezogen;
// Theming.StyleServices/ActiveStyleServices liefert dort nach Theme-
// Switch nicht zuverlaessig frische Farben fuer Custom-Paint. Wer
// explizit die IDE-Variante braucht (Grid-Renderer) kann das ueber
// die 3-Parameter-Overload tun.
Result := SeverityBg(Severity, ABase, StyleServices);
end;
function SeverityBg(Severity: TFindingSeverity;
ABase: TColor; AStyleServices: TCustomStyleServices): TColor;
const
TINT_RATIO = 0.22; // 22% Akzent eingemischt - sichtbar, nicht aggressiv
var
Base, Accent: TColor;
ResolvedBase: TColor;
begin
Accent := SeverityAccent(Severity);
if Accent = clNone then Exit(clNone);
if Assigned(AStyleServices) then
ResolvedBase := AStyleServices.GetSystemColor(ABase)
else
ResolvedBase := StyleServices.GetSystemColor(ABase);
Base := ResolvedBase;
Result := BlendColor(Base, Accent, TINT_RATIO);
end;
end.