refactor(ui): Icon-Faerbung zentral im ICON-PAINTING-Block

Inline Foreground/Stroke an Icons entfernt — ein lokaler Wert schlaegt jeden
Style, dadurch erreichten Hover/Checked/Disabled das Icon nicht. Farbe wird
jetzt per Klasse gewaehlt (.danger, .star-btn.on, .active), alle Zustaende
liegen in einem geordneten Block in IslandStyles.axaml.

- Path.plan-icon -> Path.icon-stroke (gilt jetzt fuer Button und ToggleButton)
- Icon.Activity als gefuellte Kontur (PathIcon fuellt, Stroke-Polyline gab Blobs)
- BloodBrightBrush als Hover-Ton fuer destruktive Icons
- icon-btn:disabled bleibt transparent statt Fluent-Grau
This commit is contained in:
mika kuns
2026-08-27 11:29:44 +02:00
parent d474fd13ea
commit 1ef677609f
9 changed files with 109 additions and 59 deletions
+88 -28
View File
@@ -5,7 +5,8 @@
How to use each style:
<Border Classes="island"> — floating island container
<Border Classes="chip running"> — status chip
<Button Classes="icon-btn"> — 24×24 icon button
<Button Classes="icon-btn"> — 24×24 icon button (icon colour: never inline,
see "ICON PAINTING" below)
<Button Classes="btn primary"> — rounded-rect button
<TextBlock Classes="eyebrow"> — uppercase mono label
<Border Classes="terminal"> — terminal/log window
@@ -35,8 +36,8 @@
<!-- Icon.Sun -->
<StreamGeometry x:Key="Icon.Sun">M12 8a4 4 0 1 0 0 8 4 4 0 0 0 0-8z M12 3v2M12 19v2M3 12h2M19 12h2M5.5 5.5l1.4 1.4M17.1 17.1l1.4 1.4M5.5 18.5l1.4-1.4M17.1 6.9l1.4-1.4</StreamGeometry>
<!-- Icon.Activity (pulse waveform) -->
<StreamGeometry x:Key="Icon.Activity">M3 12h4l2-6 4 12 2-8 2 2h4</StreamGeometry>
<!-- Icon.Activity (pulse waveform) — filled outline of the polyline; PathIcon fills, a stroke-only pulse renders as blobs -->
<StreamGeometry x:Key="Icon.Activity">M3,11 L6.28,11 L9,2.84 L12.86,14.43 L14.46,8.04 L17.41,11 L21,11 L21,13 L16.59,13 L15.54,11.96 L13.14,21.57 L9,9.16 L7.72,13 L3,13 Z</StreamGeometry>
<!-- Icon.Star -->
<StreamGeometry x:Key="Icon.Star">M12 3.5l2.6 5.3 5.8.8-4.2 4.1 1 5.8-5.2-2.7-5.2 2.7 1-5.8-4.2-4.1 5.8-.8z</StreamGeometry>
@@ -74,10 +75,10 @@
<!-- Icon.Broom (filled: handle + binding band + flared bristles) -->
<StreamGeometry x:Key="Icon.Broom">M11 3 H13 V10 H11 Z M8.5 10 H15.5 V12 H8.5 Z M9 12 H15 L17 21 H7 Z</StreamGeometry>
<!-- Icon.PlanDay (stroke-rendered via Path.plan-icon — sun over horizon) -->
<!-- Icon.PlanDay (stroke-rendered via Path.icon-stroke — sun over horizon) -->
<StreamGeometry x:Key="Icon.PlanDay">M3,20 L21,20 M8.4,11 a3.6,3.6 0 1,0 7.2,0 a3.6,3.6 0 1,0 -7.2,0 M12,4.5 L12,3 M6,11 L4.5,11 M18,11 L19.5,11 M7.5,6.5 L6.4,5.4 M16.5,6.5 L17.6,5.4</StreamGeometry>
<!-- Icon.Refine (stroke-rendered via Path.plan-icon — list lines + two sparkles) -->
<!-- Icon.Refine (stroke-rendered via Path.icon-stroke — list lines + two sparkles) -->
<StreamGeometry x:Key="Icon.Refine">M3,6 L13,6 M3,11 L11,11 M3,16 L9,16 M18.5,3 L19.28,5.22 L21.5,6 L19.28,6.78 L18.5,9 L17.72,6.78 L15.5,6 L17.72,5.22 Z M19.5,14.9 L19.85,16.15 L21.1,16.5 L19.85,16.85 L19.5,18.1 L19.15,16.85 L17.9,16.5 L19.15,16.15 Z</StreamGeometry>
<!-- Icon.X — filled X outline (PathIcon fills, so a stroke-only X renders invisible) -->
@@ -339,6 +340,12 @@
<Setter Property="HorizontalContentAlignment" Value="Center" />
<Setter Property="VerticalContentAlignment" Value="Center" />
</Style>
<!-- Fluent paints disabled buttons with its own gray background — keep icon buttons transparent.
Declared *before* :pointerover so a disabled button still gets the hover surface: it's the
only feedback that the button exists at all, and its tooltip explains why it won't act. -->
<Style Selector="Button.icon-btn:disabled /template/ ContentPresenter, ToggleButton.icon-btn:disabled /template/ ContentPresenter">
<Setter Property="Background" Value="Transparent" />
</Style>
<Style Selector="Button.icon-btn:pointerover /template/ ContentPresenter, ToggleButton.icon-btn:pointerover /template/ ContentPresenter">
<Setter Property="Background" Value="{StaticResource Surface2Brush}" />
<Setter Property="TextElement.Foreground" Value="{StaticResource TextBrush}" />
@@ -347,17 +354,85 @@
<Setter Property="Background" Value="{StaticResource Surface3Brush}" />
<Setter Property="TextElement.Foreground" Value="{StaticResource AccentBrush}" />
</Style>
<!-- PathIcon fills with its own Foreground and does not pick up TextElement.Foreground,
so the icon's three states are set on the icon itself. -->
<Style Selector="ToggleButton.icon-btn PathIcon">
<!-- Line-art icon geometry (PathIcon would fill the lines away). Declared before the
icon-painting block below because Avalonia resolves ties by declaration order,
not specificity — a later rule always wins. -->
<Style Selector="Path.icon-stroke">
<Setter Property="Stroke" Value="{StaticResource TextDimBrush}" />
<Setter Property="StrokeThickness" Value="1.7" />
<Setter Property="StrokeLineCap" Value="Round" />
<Setter Property="StrokeJoin" Value="Round" />
</Style>
<!-- ══ ICON PAINTING — the one place icon colour is decided ══════════════════
NEVER set Foreground/Stroke on an icon inline. A local value outranks every
style, so the button's hover/checked/disabled states can't reach the icon and
it stays dead on hover. Pick the colour with a class instead:
<PathIcon Data="{StaticResource Icon.X}"/> neutral (mute → text)
<PathIcon Classes="danger" .../> destructive (blood → bright)
<Viewbox><Path Classes="icon-stroke" .../></Viewbox> line-art geometry
PathIcon *fills* its geometry — line-art icons must use Path.icon-stroke or
they render as blobs/invisible. Both forms take the same classes and states.
Selectors are class-only (".icon-btn"), so Button and ToggleButton share them.
Order is the whole mechanism (Avalonia has no specificity — the last matching
rule wins), so the sequence below is: rest → hover → lit (.on/.active/:checked
stay lit under the pointer) → :disabled → .danger (red in every state). -->
<!-- rest -->
<Style Selector=".icon-btn PathIcon, .new-list-btn PathIcon">
<Setter Property="Foreground" Value="{StaticResource TextMuteBrush}" />
</Style>
<Style Selector="ToggleButton.icon-btn:pointerover PathIcon">
<Style Selector=".icon-btn Path.icon-stroke">
<Setter Property="Stroke" Value="{StaticResource TextMuteBrush}" />
</Style>
<!-- an unset star is barely there until you hover the row -->
<Style Selector=".icon-btn.star-btn PathIcon">
<Setter Property="Foreground" Value="{StaticResource TextFaintBrush}" />
</Style>
<!-- hover -->
<Style Selector=".icon-btn:pointerover PathIcon, .new-list-btn:pointerover PathIcon">
<Setter Property="Foreground" Value="{StaticResource TextBrush}" />
</Style>
<Style Selector="ToggleButton.icon-btn:checked PathIcon">
<Style Selector=".icon-btn:pointerover Path.icon-stroke">
<Setter Property="Stroke" Value="{StaticResource TextBrush}" />
</Style>
<!-- lit: on/active/checked keep their colour under the pointer -->
<Style Selector=".icon-btn.star-btn.on PathIcon">
<Setter Property="Foreground" Value="{StaticResource PeatBrush}" />
</Style>
<Style Selector=".icon-btn.active PathIcon, .icon-btn:checked PathIcon">
<Setter Property="Foreground" Value="{StaticResource AccentBrush}" />
</Style>
<Style Selector=".icon-btn.active Path.icon-stroke, .icon-btn:checked Path.icon-stroke">
<Setter Property="Stroke" Value="{StaticResource AccentBrush}" />
</Style>
<!-- disabled greys out every neutral variant -->
<Style Selector=".icon-btn:disabled PathIcon">
<Setter Property="Foreground" Value="{StaticResource TextFaintBrush}" />
</Style>
<Style Selector=".icon-btn:disabled Path.icon-stroke">
<Setter Property="Stroke" Value="{StaticResource TextFaintBrush}" />
</Style>
<!-- …except danger, which is last on purpose: a destructive action stays red in every state,
including disabled. Greying it out makes the button read as broken rather than blocked. -->
<Style Selector=".icon-btn PathIcon.danger">
<Setter Property="Foreground" Value="{StaticResource BloodBrush}" />
</Style>
<Style Selector=".icon-btn Path.icon-stroke.danger">
<Setter Property="Stroke" Value="{StaticResource BloodBrush}" />
</Style>
<Style Selector=".icon-btn:pointerover PathIcon.danger">
<Setter Property="Foreground" Value="{StaticResource BloodBrightBrush}" />
</Style>
<Style Selector=".icon-btn:pointerover Path.icon-stroke.danger">
<Setter Property="Stroke" Value="{StaticResource BloodBrightBrush}" />
</Style>
<!-- Segmented switch: one control, mutually exclusive segments, active one highlighted.
Use for picking a mode (not for independent on/off flags — those are icon toggles). -->
@@ -387,17 +462,6 @@
<Setter Property="TextElement.Foreground" Value="{StaticResource AccentBrush}" />
</Style>
<!-- Stroke-rendered icon (for line-art geometries that PathIcon would fill away) -->
<Style Selector="Button.icon-btn Path.plan-icon">
<Setter Property="Stroke" Value="{StaticResource TextBrush}" />
<Setter Property="StrokeThickness" Value="1.7" />
<Setter Property="StrokeLineCap" Value="Round" />
<Setter Property="StrokeJoin" Value="Round" />
</Style>
<Style Selector="Button.icon-btn:pointerover Path.plan-icon">
<Setter Property="Stroke" Value="{StaticResource AccentBrush}" />
</Style>
<!-- ============================================================ -->
<!-- INPUTS -->
<!-- ============================================================ -->
@@ -946,16 +1010,12 @@
<!-- ============================================================ -->
<!-- STAR BUTTON -->
<!-- ============================================================ -->
<!-- Icon colour comes from the icon-painting block; only the opacity differs here. -->
<Style Selector="Button.star-btn">
<Setter Property="Foreground" Value="{StaticResource TextFaintBrush}" />
<Setter Property="Opacity" Value="0.6" />
<Setter Property="Opacity" Value="0.6" />
</Style>
<Style Selector="Button.star-btn.on">
<Setter Property="Foreground" Value="{StaticResource PeatBrush}" />
<Setter Property="Opacity" Value="1.0" />
</Style>
<Style Selector="Button.star-btn.on /template/ ContentPresenter">
<Setter Property="TextElement.Foreground" Value="{StaticResource PeatBrush}" />
<Setter Property="Opacity" Value="1.0" />
</Style>
<!-- ============================================================ -->
+2
View File
@@ -33,6 +33,7 @@
<Color x:Key="PeatColor">#FFD4A574</Color>
<Color x:Key="PeatSoftColor">#FFB88D5E</Color>
<Color x:Key="BloodColor">#FFC87060</Color>
<Color x:Key="BloodBrightColor">#FFE08D7C</Color> <!-- hover state for destructive icons -->
<!-- Primary accent — equivalent to oklch(58% 0.08 88) -->
<Color x:Key="AccentColor">#FF7C9166</Color>
@@ -72,6 +73,7 @@
<SolidColorBrush x:Key="PeatBrush" Color="{StaticResource PeatColor}" />
<SolidColorBrush x:Key="PeatSoftBrush" Color="{StaticResource PeatSoftColor}" />
<SolidColorBrush x:Key="BloodBrush" Color="{StaticResource BloodColor}" />
<SolidColorBrush x:Key="BloodBrightBrush" Color="{StaticResource BloodBrightColor}" />
<SolidColorBrush x:Key="AccentBrush" Color="{StaticResource AccentColor}" />
<SolidColorBrush x:Key="AccentDimBrush" Color="{StaticResource AccentDimColor}" />