Add the library, collections, and trash views; theme from style.yaml
The UI gains the views the catalog work was building toward: a windowed
library grid with ratings and flags, the collection tree with drag-to-add,
and trash with restore. derived_sync pushes thumbnail shards and the catalog
snapshot to the server's derived folder.
Tokens now have one source of truth. build.rs reads style.yaml and generates
theme.slint into OUT_DIR, which answers every existing
`import { Theme } from "theme.slint"` unchanged, because Slint resolves
imports against the importing file's directory first and the include paths
after. Generating into OUT_DIR rather than beside the hand-written Slint is
the point: a generated file sitting in ui/ looks exactly like the files
around it that are meant to be edited, and an edit to it would survive until
the next touch of style.yaml — a bug that hides for weeks. build.rs fails
loudly if a stale ui/theme.slint exists, which would otherwise shadow the
generated one silently and make every palette change vanish with no error.
The palette moves to near-neutral dark with achromatic signalling, so the
accent means "modified" or "active" rather than "heading". Shared components
land in widgets.slint: a token that binds several values into one concept is
a component, not a row in a YAML file.
Adds an optional live-style feature that makes the tokens in-out so they can
be written at startup — a feature rather than the default because it stops
the properties being constant-folded.
serde_norway is the YAML crate: serde_yaml and serde_yml are both deprecated,
and its mappings preserve insertion order, which is what lets the generated
Slint keep the token ordering the author chose.
Assisted-by: LLM
This commit is contained in:
@@ -0,0 +1,163 @@
|
||||
# The source of truth for the UI's colour and length tokens.
|
||||
#
|
||||
# `build.rs` reads this file and generates `theme.slint` into OUT_DIR at
|
||||
# compile time; nothing here is read at runtime unless the `live-style`
|
||||
# feature is on. Edit this file, never the generated one.
|
||||
#
|
||||
# Leaf values only. A token that binds several values into one concept — a
|
||||
# panel heading's colour *and* size *and* weight — is a Slint component, not
|
||||
# a row in a YAML file (see widgets.slint).
|
||||
#
|
||||
# Structure:
|
||||
# preamble — prose emitted at the top of the generated file
|
||||
# colors: — name -> "#RRGGBB", or `alias:` naming another colour token
|
||||
# lengths: — name -> pixels
|
||||
# Any token may carry `note:` (a `//` comment above it) or `doc:` (a `///`
|
||||
# doc comment, which Slint surfaces to editors).
|
||||
#
|
||||
# Two entries are dividers rather than tokens, because YAML discards the blank
|
||||
# lines between map entries and the generated file's grouping has to survive:
|
||||
# `section: <title>` — a headed run, optionally with its own `note:`
|
||||
# `break: true` — a bare blank line between related tokens
|
||||
|
||||
preamble: |
|
||||
Near-neutral dark palette, achromatic signalling.
|
||||
|
||||
Two commitments, both about not lying to the photographer.
|
||||
|
||||
**Dark ground.** A light UI surrounding an image biases how that image is
|
||||
judged — the eye adapts to the brightest thing in view, and a white panel
|
||||
makes a correctly-exposed photograph look dark.
|
||||
|
||||
**Near-neutral greys.** The earlier palette was a warm "darkroom safelight"
|
||||
brown (ground #14120F, R twelve points above B). That is worse than it
|
||||
sounds: simultaneous contrast pushes perception of the image *away* from the
|
||||
surround, so a warm chrome makes a neutral photograph read cool, and the
|
||||
photographer corrects toward warm to compensate. Every export drifts yellow.
|
||||
It is the same reason a print viewing booth is neutral grey rather than
|
||||
whatever colour the room happens to be.
|
||||
|
||||
These greys carry a 2–3 point blue lift rather than being flatly achromatic.
|
||||
Pure R=G=B reads as dead to most eyes; a trace of cool reads as instrument
|
||||
rather than absence, and biases far less than warmth because the eye is more
|
||||
tolerant of a cool surround. The lift is small enough not to matter
|
||||
perceptually and deliberate enough not to be mistaken for drift.
|
||||
|
||||
**No hue in the chrome.** Active and modified states are signalled by
|
||||
brightness alone. An accent sitting beside the image competes with it for
|
||||
attention and shifts the perception of nearby colours; a photo editor cannot
|
||||
afford either. The one exception is `warn-ink` — a caution is genuinely a
|
||||
different kind of thing from an active state, and hue is the fastest way to
|
||||
say so.
|
||||
|
||||
colors:
|
||||
ground: "#121314"
|
||||
surface: "#1B1C1E"
|
||||
surface-raised: "#252629"
|
||||
rule: "#323438"
|
||||
|
||||
_inks: { break: true }
|
||||
ink: "#EDEEF0"
|
||||
ink-dim: "#9EA1A6"
|
||||
ink-faint: "#71747A"
|
||||
|
||||
hover:
|
||||
value: "#2E3034"
|
||||
note: |
|
||||
Interactive states for surfaces. Named rather than written inline so a
|
||||
button, a section header and a list row cannot drift apart: hover lifts
|
||||
toward the light, press sinks back past the resting surface so the
|
||||
control reads as depressed rather than merely lit.
|
||||
pressed: "#17181A"
|
||||
|
||||
_signalling:
|
||||
section: signalling
|
||||
note: |
|
||||
Achromatic, so the separation has to come from luminance. These are
|
||||
spaced further apart than a coloured palette would need: with hue
|
||||
unavailable, a two-step brightness difference is invisible, and a
|
||||
modified marker that cannot be spotted at a glance is not a marker.
|
||||
|
||||
active:
|
||||
value: "#FFFFFF"
|
||||
doc: |
|
||||
An active or engaged control: a slider fill, a curve line, a checked
|
||||
box. Brighter than `ink` so it reads as lit rather than merely present.
|
||||
active-dim:
|
||||
value: "#C6C9CE"
|
||||
doc: Active, at rest — a filled control that is not under the pointer.
|
||||
active-pressed:
|
||||
value: "#8E9298"
|
||||
doc: Active, pressed. Sinks rather than lifts, matching `pressed`.
|
||||
|
||||
modified:
|
||||
alias: active
|
||||
note: |
|
||||
"This differs from its default." Deliberately the brightest thing in the
|
||||
chrome: it is the one piece of state the photographer scans for, and
|
||||
with no hue to carry it, brightness is all there is.
|
||||
|
||||
selected:
|
||||
value: "#383B40"
|
||||
doc: |
|
||||
A selected grid cell. A lifted neutral rather than a tint — distinct
|
||||
from `hover` because a multi-selection must stay legible after the
|
||||
pointer has moved on, which is the whole point of selecting several
|
||||
before dragging them.
|
||||
selected-ring:
|
||||
value: "#D5D8DD"
|
||||
doc: |
|
||||
The ring around a selected cell. Brighter than the fill so selection
|
||||
survives against a pale thumbnail, where the fill alone would vanish.
|
||||
|
||||
warn-ink:
|
||||
value: "#C9A05A"
|
||||
note: |
|
||||
Semantic, and the only hue in the palette. A caution is not an active
|
||||
state, and it is worth the one exception to say that instantly. Muted
|
||||
rather than saturated so it does not shift perception of a nearby image.
|
||||
|
||||
lengths:
|
||||
_gaps: { break: true }
|
||||
gap-sm: 6
|
||||
gap: 12
|
||||
gap-lg: 20
|
||||
|
||||
_text: { break: true }
|
||||
text-sm: 11
|
||||
text: 13
|
||||
text-lg: 17
|
||||
text-xl: 24
|
||||
|
||||
radius-sm:
|
||||
value: 3
|
||||
note: |
|
||||
Corner radii. Two steps only: `radius-sm` for things that sit inside
|
||||
other things, `radius` for the controls themselves.
|
||||
radius: 4
|
||||
|
||||
touch-target:
|
||||
value: 44
|
||||
note: "FR-UI-3: minimum 44pt hit target under touch."
|
||||
|
||||
row-height:
|
||||
value: 26
|
||||
note: |
|
||||
One row of the collections tree, and one level of nesting. Both are
|
||||
tokens because a tree's indentation has to stay proportional to its row
|
||||
height; hard-coding either makes the hierarchy read wrong when the other
|
||||
changes.
|
||||
indent: 14
|
||||
|
||||
control-height:
|
||||
value: 28
|
||||
note: |
|
||||
The drawn height of a button or section header. Deliberately shorter
|
||||
than `touch-target` — chrome this tall in every row would crowd the
|
||||
photograph — so controls grow their TouchArea past their own bounds to
|
||||
meet FR-UI-3 rather than growing their ink.
|
||||
control-min-width:
|
||||
value: 88
|
||||
doc: |
|
||||
Floor on button width, so a one-word label is still a comfortable
|
||||
target and a row of buttons has an even rhythm.
|
||||
Reference in New Issue
Block a user