indicatrix-cut 0.7.2

Desktop faceting-design editor: library browsing, spectral 3D rendering, material retargeting, and a solid inspection view.
// The colour palette and design tokens.
//
// HIGH CONTRAST. `high-contrast` switches every colour token between the normal dark
// palette (the second value of each `high-contrast ? <hc> : <normal>` pair, unchanged
// from before the switch existed) and a high-contrast one. Rust sets the flag on each
// window's own `Theme` instance (a global exists once per window): `gui::preferences`
// does it at start-up, on the Preferences toggle, and when the compare and rough-planner
// windows are created.
//
// The high-contrast palette is near-black surfaces, white primary text, light (never
// dim) secondary text, clearly visible borders and bright accents. Ratios targeted,
// WCAG relative luminance, against the surface the token sits on:
//   - text-primary / text-secondary / text-muted on any bg-* or surface-* step:
//     white 15:1 or more, #e6e6e6 12:1, #c4c4c4 8.6:1 (bg-card-hover is the lightest
//     surface, so these are the worst cases);
//   - text-disabled #9a9a9a: 5.4:1 on bg-card-hover, 4.8:1 on surface-disabled, so even
//     disabled text stays readable and only differs from muted by being dimmer;
//   - borders and icons 3:1 or more: border-subtle #8c8c8c 4.5:1 on bg-card-hover and
//     5.6:1 on bg-card, border-medium / border-dialog / border-control #b0b0b0 7:1,
//     border-accent (the focus colour) yellow 14:1 on black;
//   - accents used as text on dark surfaces 5:1 or more (accent-cyan 9.4:1 on
//     bg-card-hover, accent-amber 8.4:1, accent-purple 6.4:1, accent-ruby 5.1:1,
//     danger 5.4:1);
//   - ink on a bright fill (text-on-accent, text-on-amber) is black, 11:1 or more;
//   - white text on `primary` (#1a66ff) 4.8:1 and on `primary-hover` 7.2:1. `primary` is a
//     FILL colour: as a stroke or icon it is 3.9:1 on bg-card and 4.4:1 on black. A
//     colour cannot be both text-bright on black and carry white text at 4.5:1, so text
//     that must be bright uses accent-cyan instead;
//   - selected / info / warning surfaces keep white 9:1 and muted text 5:1 or more.
//
// STANDARD WIDGETS (Button, LineEdit, ComboBox, Slider, SpinBox, CheckBox, ScrollView)
// draw themselves from the Slint style palette, not from these tokens. The app is dark only,
// in normal mode and in high contrast, so the widgets are dark whatever the system theme is.
// Two things make that so: `build.rs` compiles the UI with the `fluent-dark` style (the
// compiler then fixes the colour scheme to dark for every window from the first frame), and
// the `changed high-contrast` handler below sets `Palette.color-scheme` to dark again each
// time the switch flips, as a second line of defence. The style's own control borders stay
// subtle in high contrast, which cannot be changed from here; the custom controls draw their
// own strong borders from the tokens.
//
// COLOURS THAT CARRY MEANING (`chart-extinction`, `compare-removed`, `compare-added`, the
// gem colours that Rust pushes into swatches) keep their own value in both palettes; only
// the surfaces and labels around them change.
//
// New code uses a token from this file, never a hex literal, so this switch reaches it.
import { Palette } from "std-widgets.slint";

export global Theme {
    /// Whether the high-contrast palette is on. Set from Rust, per window.
    in-out property <bool> high-contrast: false;

    // Pins the standard widgets' style palette to dark (see the header). It is dark in both
    // palettes; the handler never hands the choice back to the system. A `changed` handler on a
    // global does not run at start-up (the `fluent-dark` style in `build.rs` covers that) and
    // runs on the next event-loop pass, like the ones on the `*Model` globals.
    changed high-contrast => {
        Palette.color-scheme = ColorScheme.dark;
    }

    // Dark Theme Palette
    out property <color> bg-darkest: root.high-contrast ? #000000 : #0f1117;
    out property <color> bg-dark: root.high-contrast ? #0a0a0a : #161922;
    out property <color> bg-card: root.high-contrast ? #121212 : #1c202c;
    out property <color> bg-card-hover: root.high-contrast ? #262626 : #252b3b;
    out property <color> bg-input: root.high-contrast ? #000000 : #12141c;
    out property <color> bg-elevated: root.high-contrast ? #1e1e1e : #262c3d;

    // Borders & Dividers
    out property <color> border-subtle: root.high-contrast ? #8c8c8c : #252b3b;
    out property <color> border-medium: root.high-contrast ? #b0b0b0 : #333b50;
    out property <color> border-accent: root.high-contrast ? #ffd400 : #3b82f6;

    // Accent & Highlights
    out property <color> primary: root.high-contrast ? #1a66ff : #3b82f6;
    out property <color> primary-hover: root.high-contrast ? #0b4bd0 : #60a5fa;
    out property <color> primary-glow: root.high-contrast ? #1a66ff66 : #3b82f633;
    out property <color> accent-cyan: root.high-contrast ? #00e0ff : #38bdf8;
    /// `accent-cyan`'s hovered state, mirroring `primary-hover` above -- named
    /// so `material_editor_dialog.slint`'s "Save & Apply" button uses the same
    /// token as the other colors on its line, rather than a bare hex literal.
    out property <color> accent-cyan-hover: root.high-contrast ? #80efff : #7dd3fc;
    /// The dark surface that carries `accent-cyan` content -- named so
    /// `header.slint`'s remote-library pill (paired with an `accent-cyan`
    /// border) and its "local" branch (`bg-elevated`) share a token for the
    /// same role instead of mixing a hex code and a token on one line.
    out property <color> accent-cyan-surface: root.high-contrast ? #003844 : #164e63;
    out property <color> accent-emerald: root.high-contrast ? #00e676 : #10b981;
    out property <color> accent-amber: root.high-contrast ? #ffb300 : #f59e0b;
    out property <color> accent-ruby: root.high-contrast ? #ff5c7a : #f43f5e;
    out property <color> accent-purple: root.high-contrast ? #d28bff : #a855f7;
    /// `accent-amber` at low opacity, the same "-glow" convention `primary-glow`
    /// already established -- names the tier table's multi-selected-row
    /// highlight (`editor_tier_table.slint`) so it stays tied to `accent-amber`
    /// for a future palette change.
    out property <color> accent-amber-glow: root.high-contrast ? #ffb30066 : #f59e0b33;

    // Text & Typography
    out property <color> text-primary: root.high-contrast ? #ffffff : #f1f5f9;
    out property <color> text-secondary: root.high-contrast ? #e6e6e6 : #94a3b8;
    out property <color> text-muted: root.high-contrast ? #c4c4c4 : #64748b;
    // Already white, the highest contrast there is: the same in both palettes. The
    // high-contrast `primary` was chosen dark enough to carry it at 4.5:1.
    out property <color> text-on-primary: #ffffff;
    /// Text that is present but not currently actionable -- a disabled label, or a
    /// chart's own axis tick labels, which must recede behind the plotted data.
    /// One step dimmer than `text-muted`. Same value as `border-control`, named
    /// separately because the two roles would not move together in a re-palette.
    /// Shared by `performance_graph_dialog.slint` (7 axis labels) and
    /// `settings_dialog.slint` (a disabled override label).
    out property <color> text-disabled: root.high-contrast ? #9a9a9a : #475569;
    /// Dark text/icon color for content sitting directly on an `accent-amber`
    /// fill (a toggle button's own "on" state, e.g. `header.slint`'s Filters
    /// button) -- the amber-background counterpart to `text-on-primary`,
    /// naming the role so it stays tied together across every call site.
    out property <color> text-on-amber: root.high-contrast ? #000000 : #0f172a;

    /// Ink drawn on a LIGHT or saturated fill -- the counterpart to
    /// `text-on-primary` (white, for dark saturated fills) and the general form of
    /// `text-on-amber` (which stays as-is for its existing amber call sites).
    /// Names the role shared by `material_editor_dialog.slint`'s light "Clear"
    /// swatch label and the check icon + label on its accent-cyan "Save & Apply"
    /// button.
    out property <color> text-on-accent: root.high-contrast ? #000000 : #0f172a;

    // Control surfaces inside a dialog. Distinct from the
    // `bg-*` scale above, which describes the main window's panels: a dialog sits
    // on its own `surface-dialog` card and its controls need their own steps
    // against that, which is why these values never matched a `bg-*` token.
    /// A field, hairline divider, or hovered secondary button inside a dialog --
    /// one step ABOVE the dialog card. Shared by eight call sites in
    /// `material_editor_dialog.slint` alone.
    out property <color> surface-raised: root.high-contrast ? #262626 : #1e293b;
    /// A recessed secondary-button or chip-row background -- one step BELOW the
    /// dialog card. Same value as `text-on-accent` above, deliberately named
    /// separately because the roles are unrelated.
    out property <color> surface-sunken: root.high-contrast ? #000000 : #0f172a;
    /// A small square control (a dialog's own close button) at rest.
    out property <color> surface-control: root.high-contrast ? #1a1a1a : #1a202c;
    /// ...and hovered.
    out property <color> surface-control-hover: root.high-contrast ? #333333 : #2d3748;
    /// A disabled button's flat fill. Same value as `border-dialog`, named
    /// separately for the same reason as the pair above: a fill is not a border,
    /// and a future palette change would almost certainly move one without the
    /// other.
    out property <color> surface-disabled: root.high-contrast ? #2e2e2e : #334155;

    /// A selected list row's fill, matching `diagram_list.slint`'s ternary whose
    /// other two branches are already `bg-card-hover`/`bg-input`. Deliberately
    /// NOT shared with `header.slint`'s logo mark, which happens to use the
    /// same value for an unrelated, brand reason -- a palette change should
    /// move one without the other.
    out property <color> surface-selected: root.high-contrast ? #0b3fa8 : #1e3a8a;
    /// An informational banner's fill (the catalogue's "showing N just-imported
    /// designs" strip).
    out property <color> surface-info: root.high-contrast ? #0b3a63 : #1e3a5f;
    /// A warning banner's fill -- the dark amber counterpart to `accent-amber`
    /// content, as `surface-info` is to a blue one.
    out property <color> surface-warning: root.high-contrast ? #5c3000 : #422006;

    // Radii
    out property <length> radius-sm: 4px;
    out property <length> radius-md: 8px;
    out property <length> radius-lg: 12px;

    // Overlay & Modal Surfaces -- every dialog card in this app
    // (`new_design_dialog.slint`, `retarget_dialog.slint`, `material_editor_dialog.slint`,
    // `metadata_editor_dialog.slint`, `confirm_action_dialog.slint`) uses these
    // exact four literals for its backdrop/card/border/shadow -- named here so the
    // convention is explicit and a future dialog (or a future palette change) doesn't
    // have to rediscover it by copying another file's hex codes.
    /// The full-viewport semi-transparent backdrop shown behind every modal dialog.
    out property <color> overlay-backdrop: root.high-contrast ? #000000cc : #00000088;
    /// A modal dialog card's own background, distinct from `bg-elevated` (used for
    /// smaller in-panel popups like the Log popup rather than full dialogs).
    out property <color> surface-dialog: root.high-contrast ? #0d0d0d : #11151f;
    /// A modal dialog card's own border color.
    out property <color> border-dialog: root.high-contrast ? #b0b0b0 : #334155;
    /// A checkbox, toggle or similar small control's border at rest, i.e. while
    /// unchecked -- one step brighter than `border-dialog` so the control reads as
    /// interactive against the card it sits on. Shared by five dialogs
    /// (`retarget_dialog`, `export_dialog`, `material_editor_dialog`,
    /// `performance_graph_dialog`, `settings_dialog`), always opposite an
    /// `accent-cyan` checked state.
    out property <color> border-control: root.high-contrast ? #b0b0b0 : #475569;
    /// The drop-shadow color under a floating modal dialog card.
    out property <color> modal-shadow: root.high-contrast ? #000000 : #000000bb;
    /// A small floating popup's own translucent card background -- distinct from
    /// the opaque `surface-dialog` (a full modal dialog card): names the role
    /// for `editor_status_strip.slint`'s "Details" popup. `border-dialog`/
    /// `modal-shadow` above already fit that same popup's border/shadow exactly
    /// (it independently uses the identical literals), so only the background
    /// needed a new name rather than reusing one of the four above.
    out property <color> popup-surface: root.high-contrast ? #0d0d0df2 : #111827ee;

    // Semantic state, distinct from the decorative `accent-*` colors above (which
    // this codebase also reuses for non-status purposes, e.g. chart curves).
    /// Danger/error/destructive-action color -- already the de facto shade for this
    /// (toast error icon, Retarget's solve-error text, Material Editor's Delete
    /// button) before this token existed to name it.
    out property <color> danger: root.high-contrast ? #ff6b6b : #f87171;
    /// A destructive button's own flat fill at rest, and hovered -- the dark-red
    /// surfaces that carry `danger` text/icons. Shared by the Material Editor's
    /// Delete button and the Filters panel's remove-row button, tying the two
    /// files to the same role.
    out property <color> danger-surface: root.high-contrast ? #2b0000 : #1f1111;
    out property <color> danger-surface-hover: root.high-contrast ? #4d0000 : #450a0a;
    /// A destructive button's border.
    out property <color> danger-border: root.high-contrast ? #ff6b6b : #7f1d1d;
    /// Success/confirmation color -- the `danger` counterpart, and like `danger` a
    /// lighter shade than the `accent-emerald` it derives from, so it reads as ink
    /// or an icon on a dark surface rather than as a fill. Used by the toast's
    /// success icon, alongside `danger`/`accent-amber`/`accent-cyan` on the same
    /// line.
    out property <color> success: root.high-contrast ? #3ddc84 : #34d399;

    // Toast backgrounds. Translucent by design -- a toast floats over live content
    // and must not hide it -- which is why these are their own tokens rather than
    // the opaque `*-surface` colors above. Named for `toast.slint`, the last
    // component in `ui/` to get tokens for its own colors. The high-contrast fills are
    // nearly opaque (93 %), so text on them keeps its contrast over any content.
    /// The success toast's translucent card fill.
    out property <color> toast-success-surface: root.high-contrast ? #003d24ee : #064e3bcc;
    /// The error toast's translucent card fill.
    out property <color> toast-error-surface: root.high-contrast ? #4d0010ee : #7f1d1dcc;
    /// The warning toast's translucent card fill.
    out property <color> toast-warning-surface: root.high-contrast ? #4d3300ee : #78350fcc;
    /// The neutral/info toast's translucent card fill.
    out property <color> toast-info-surface: root.high-contrast ? #000000ee : #0f172acc;
    /// The drop-shadow under a toast -- lighter than `modal-shadow`, since a toast
    /// sits closer to the content than a modal dialog card does.
    out property <color> toast-shadow: root.high-contrast ? #000000 : #000000aa;
    /// A hover fill for a control that has no surface of its own and must tint
    /// whatever it floats over -- the toast's close button. Deliberately white at
    /// low alpha rather than a named surface, so it works on all four toast fills.
    out property <color> overlay-hover: root.high-contrast ? #ffffff44 : #ffffff22;

    // ---- Tokens for one feature each ----------------------------------------------
    // Added when the last hard-coded colours were moved into the theme. Every normal value
    // below is the literal the code used before, so the normal look is unchanged.

    // Chips, pills and fields inside the export and tilt-graph dialogs.
    /// The recessed row or field behind a group of choice chips (frame-rate, bounce and
    /// colour-space pills, the step field, the progress track). Text on it: 10:1 or more
    /// for `text-muted` in high contrast.
    out property <color> surface-chip: root.high-contrast ? #1a1a1a : #131b2e;
    /// An unselected pill that sits on a panel (the library's tag chips). A FILL, so it is
    /// not `border-dialog`, which turns light in high contrast and would swallow the label.
    out property <color> surface-chip-off: root.high-contrast ? #333333 : #334155;
    /// Fill of a selected channel or axis pill, one tint per series. Each is the series
    /// colour at about 20 % over the dark surface under it, so the series-coloured label
    /// keeps 4.5:1 or more on it.
    out property <color> tint-cyan: root.high-contrast ? #00e0ff33 : #0284c733;
    out property <color> tint-amber: root.high-contrast ? #ffb30033 : #d9770633;
    out property <color> tint-red: root.high-contrast ? #ef444433 : #b91c1c33;
    out property <color> tint-purple: root.high-contrast ? #d28bff33 : #a855f733;
    /// The same for a pill with no series colour of its own (the "Angle" value pill).
    out property <color> tint-neutral: root.high-contrast ? #ffffff40 : #33415577;

    // The tilt-performance graph dialog and its chart.
    /// The graph dialog's card (a hair bluer than `surface-dialog`).
    out property <color> surface-graph: root.high-contrast ? #0d0d0d : #0f1523;
    /// The chart's floating hover card, translucent over the lines.
    out property <color> surface-graph-popup: root.high-contrast ? #0d0d0df2 : #0f1523ee;
    /// The plot area and its frame.
    out property <color> chart-bg: root.high-contrast ? #000000 : #050811;
    out property <color> chart-border: root.high-contrast ? #8c8c8c : #1e293b;
    /// The horizontal reference lines at 25 / 75 % and at 50 %.
    out property <color> chart-grid: root.high-contrast ? #ffffff40 : #1e293b44;
    out property <color> chart-grid-mid: root.high-contrast ? #ffffff66 : #1e293b66;
    /// The faint vertical guide at 0 degrees, and the hover crosshair.
    out property <color> chart-guide: root.high-contrast ? #ffffff59 : #33415577;
    out property <color> chart-crosshair: root.high-contrast ? #ffffffcc : #94a3b877;
    /// The extinction series (red) -- data colour, the same in both palettes: 5.6:1 on black.
    /// Brilliance and windowing use `accent-cyan` and `accent-amber`.
    out property <color> chart-extinction: #ef4444;

    // A "this needs your attention" banner in orange (stale cached curve, long video run).
    // Not `surface-warning`, which is the dark-amber banner of another feature.
    /// The banner fill, translucent in the normal palette.
    out property <color> notice-surface: root.high-contrast ? #331a00 : #7c2d1233;
    /// Its border, and the fill of the action button inside it (ink on it: `text-on-accent`).
    out property <color> notice-accent: root.high-contrast ? #ff8a3d : #ea580c;
    out property <color> notice-accent-hover: root.high-contrast ? #ffa566 : #f97316;
    /// Its text: 12:1 on `notice-surface` in high contrast.
    out property <color> notice-text: root.high-contrast ? #ffd2a1 : #fdba74;

    // Small badges in the catalogue (fill; the label uses the matching `accent-*`).
    out property <color> badge-amber-bg: root.high-contrast ? #4d3300 : #78350f;
    out property <color> badge-emerald-bg: root.high-contrast ? #003d24 : #065f46;
    out property <color> badge-purple-bg: root.high-contrast ? #2e0a66 : #4c1d95;
    /// The "Competition Design" badge: fill and label (10:1 in high contrast).
    out property <color> badge-orange-bg: root.high-contrast ? #4d1f00 : #7c2d12;
    out property <color> badge-orange-text: root.high-contrast ? #ffd9b3 : #fed7aa;

    // Overlays drawn over the Live Render picture. In high contrast they are almost opaque
    // so the text stays readable over a bright render.
    /// What shows behind the render picture before the first frame arrives.
    out property <color> viewport-bg: root.high-contrast ? #000000 : #080a0f;
    /// The metrics strip over the picture.
    out property <color> hud-surface: root.high-contrast ? #000000f2 : #0f172aee;
    /// The mouse-hint pills along the bottom edge, and their text and icons.
    out property <color> hint-surface: root.high-contrast ? #000000eb : #1e293bdd;
    out property <color> hint-text: root.high-contrast ? #ffffff : #cbd5e1;
    /// The dimmer behind the render settings card.
    out property <color> scrim-light: root.high-contrast ? #000000b0 : #00000055;

    // Shadows under a floating card: the strong one under the performance-graph and remote
    // dialogs, the medium one under the import dialog. (`modal-shadow` and `toast-shadow`
    // are the other two.) Fully black in high contrast, like those.
    out property <color> shadow-heavy: root.high-contrast ? #000000 : #000000ee;
    out property <color> shadow-medium: root.high-contrast ? #000000 : #000000aa;

    /// The round knob of a switch (on a track that is `border-dialog` when off and an
    /// accent colour when on). White would vanish on the light high-contrast tracks, so it
    /// is black there: 9:1 on `border-dialog`, 10:1 or more on every accent track.
    out property <color> toggle-knob: root.high-contrast ? #000000 : #ffffff;

    /// The logo mark in the header (the same blue as `surface-selected`, for a brand reason
    /// rather than a role, so a re-palette can move one without the other).
    out property <color> brand-mark-bg: root.high-contrast ? #0b3fa8 : #1e3a8a;

    // The Compare window's overlay legend. Data colours: they must match the red and
    // green the overlay raster draws, so they are the same in both palettes.
    out property <color> compare-removed: #ef4444;
    out property <color> compare-added: #22c55e;

    // The "Clear" and "Keep" swatches of the material editor's body-colour row. (The coloured
    // swatches are data -- the colour they stand for -- and stay literals in that file.)
    /// "Clear" when it is the chosen swatch: the pale grey-white of an uncoloured stone. Its
    /// label is `text-on-accent`.
    out property <color> swatch-clear: root.high-contrast ? #ffffff : #e2e8f0;
    /// "Clear" when it is not chosen: a faint translucent grey over the dialog card.
    out property <color> swatch-clear-off: root.high-contrast ? #ffffff40 : #94a3b844;
    /// "Keep" (keep the saved colour, which matches no swatch): fainter still.
    out property <color> swatch-keep: root.high-contrast ? #ffffff26 : #94a3b822;
}