The combo's trigger retires — each picker is one rectangle again, taller, and Style becomes Appearance in the sidebar
Both the symbol picker and the background color control give up the two-zone combo chrome from the 2026-08-09 rework: no more face/trigger split, no more trailing chevron square. Each is now a single bordered rectangle with one hit zone, 2.1em tall and 1.5x that wide (a 4:6 ratio, 50% taller than the retired chrome's 18pt). A click anywhere opens the same curated popover the trigger used to gate. The background rectangle's popover is new: a None-plus-sixteen palette grid mirroring the Style… popover's own wells, with an Other… row onto the shared, pre-debounced Colors panel session — replacing the old NSMenu dropdown outright. The symbol rectangle keeps its existing popover (search, curated grid, tint colors, More Symbols…) verbatim; only its entry point collapsed to one zone. ComboFieldControl/ComboFieldMetrics (ComboField.swift) are replaced by PickerRectControl/PickerRectMetrics (PickerRect.swift). ColorComboView and its NSMenu-building pure model are retired wholesale in favor of ColorSwatchPicker. SymbolComboControl becomes SymbolGlyphControl. PaletteSwatch.rectImage, the last caller of which was the retired dropdown's menu rows, goes with it. In the card window sidebar, the "Style" section header becomes "Appearance" (sidebar only — the board context menu's Style… item and StyleEditorView's own naming are untouched), and the symbol and background controls move from stacked rows to side-by-side columns, each captioned above rather than leading. CardStyleSection no longer carries its own debounce Task for background panel picks — the shared Colors-panel session now delivers an already-settled value. Claude-Session: https://claude.ai/code/session_014PtZdPwqZuqEDLc6wZMtEy
This commit is contained in:
@@ -3,29 +3,29 @@ import AppKit
|
||||
/// **The shared Colors panel, as one takeover every surface can borrow** — `NSColorPanel.shared`
|
||||
/// seeded, targeted, and translated back into the app's stored-value vocabulary.
|
||||
///
|
||||
/// This is `ColorComboView.Coordinator`'s panel handling, lifted out of it. That coordinator was the
|
||||
/// only thing in the app that opened the system picker, so the logic could live inside it; now three
|
||||
/// surfaces do — the colour combo's face and its **Other…** row, the Style… popover's Background
|
||||
/// section, and the symbol popover's tint section (2026-08-09) — and the parts that must not be
|
||||
/// re-derived are the awkward ones: `NSColorPanel` is a **singleton with a settable target and no
|
||||
/// getter for it**, so "is the panel still mine to detach from" has nowhere to live but in a static,
|
||||
/// and a second surface taking the panel over has to leave the first one's teardown harmless.
|
||||
/// This used to be `ColorComboView.Coordinator`'s panel handling, lifted out of it when a second
|
||||
/// surface needed the identical takeover: `NSColorPanel` is a **singleton with a settable target and
|
||||
/// no getter for it**, so "is the panel still mine to detach from" has nowhere to live but in a
|
||||
/// static, and a second surface taking the panel over has to leave the first one's teardown harmless.
|
||||
/// `ColorComboView` is retired now (2026-08-10, `ColorSwatchPicker.swift`'s own header), and every
|
||||
/// caller left — the Style… popover's Background section, the symbol popover's tint section, and the
|
||||
/// card sidebar's own colour rectangle — reaches this class through the one shared instance
|
||||
/// `SharedColorPanelSession` holds, below.
|
||||
///
|
||||
/// ### What it hands back
|
||||
///
|
||||
/// Not an `NSColor` — the **stored string**: the palette name when the picked colour lands exactly
|
||||
/// on one of the caller's own palette entries, `#RRGGBB[AA]` otherwise. That rule (name wins over
|
||||
/// hex) is the same one `ColorComboModel.match` applies to a value already on disk, which is what
|
||||
/// makes a colour survive a round trip through the panel without drifting from `light-cayenne` into
|
||||
/// an anonymous `#B6071E`.
|
||||
/// on one of the caller's own palette entries, `#RRGGBB[AA]` otherwise (`changeColor(_:)` below). That
|
||||
/// is what makes a colour survive a round trip through the panel without drifting from
|
||||
/// `light-cayenne` into an anonymous `#B6071E`.
|
||||
///
|
||||
/// ### It fires continuously
|
||||
///
|
||||
/// `NSColorPanel`'s action runs on every tick of a drag, not on release, and that is deliberate: a
|
||||
/// live preview is the point of opening the panel at all. **Every caller therefore owes a debounce**
|
||||
/// before the value reaches disk — `CardStyleSection.debounceBackground` is the pattern, ~400ms
|
||||
/// trailing, and a drag that passes through a palette-exact colour mid-gesture must not be recorded
|
||||
/// in `StyleRecents` the way a deliberate pick is.
|
||||
/// before the value reaches disk — `SharedColorPanelSession.present`'s own ~400ms trailing debounce is
|
||||
/// that owed debounce for every caller today, and a drag that passes through a palette-exact colour
|
||||
/// mid-gesture must not be recorded in `StyleRecents` the way a deliberate pick is.
|
||||
@MainActor
|
||||
final class SystemColorPanel: NSObject {
|
||||
|
||||
@@ -84,23 +84,27 @@ final class SystemColorPanel: NSObject {
|
||||
|
||||
/// **The Colors panel for surfaces that do not outlive it, debounce included.**
|
||||
///
|
||||
/// The obvious shape is a `SystemColorPanel` in the opening view's own state, and that is exactly
|
||||
/// what `CardStyleSection` does — correctly, because a card window's sidebar outlives any panel
|
||||
/// opened from it. It does **not** work for the two surfaces that gained an **Other…** on
|
||||
/// 2026-08-09: the Style… popover's Background section and the symbol popover's tint section are
|
||||
/// both inside *transient popovers*. Opening the Colors panel takes key status away, the popover
|
||||
/// dismisses, the view's state goes with it — and the panel the user is now dragging in has nothing
|
||||
/// left to talk to. The gesture would break itself.
|
||||
/// The obvious shape is a `SystemColorPanel` in the opening view's own state — and it does **not**
|
||||
/// work for a surface whose door onto the panel sits *inside a transient popover*: the Style…
|
||||
/// popover's Background section, the symbol popover's tint section, and, since 2026-08-10, the card
|
||||
/// sidebar's own colour rectangle's popover (`ColorSwatchPicker`'s **Other…** row) are all like this.
|
||||
/// Opening the Colors panel takes key status away, the popover dismisses, the view's state goes with
|
||||
/// it — and the panel the user is now dragging in has nothing left to talk to. The gesture would
|
||||
/// break itself. (`CardStyleSection` used to be the one exception — a card window's sidebar outlives
|
||||
/// any panel opened from it, so it held its own `SystemColorPanel` and its own debounce `Task`
|
||||
/// directly. It reaches this shared session too now that its own colour control opens the panel from
|
||||
/// *inside* a popover rather than from a permanently-mounted face.)
|
||||
///
|
||||
/// So the session lives above every view: one panel, one pending write, replaced by whichever
|
||||
/// surface opened it last. Callers capture their store **weakly**, so a board closed while its
|
||||
/// Colors panel is still up drops the write instead of resurrecting a dead store.
|
||||
///
|
||||
/// The debounce is `CardStyleSection.debounceBackground`'s, restated because the state it needs now
|
||||
/// has to live somewhere a dismissed popover cannot take with it: the panel's action is continuous,
|
||||
/// and only the value the user is still on ~400ms after the last tick reaches disk. A drag is
|
||||
/// therefore one write, and never feeds `StyleRecents` — that row remembers deliberate palette
|
||||
/// picks, not colours a drag swept through.
|
||||
/// The debounce lives here because the state it needs has to live somewhere a dismissed popover
|
||||
/// cannot take with it: the panel's action is continuous, and only the value the user is still on
|
||||
/// ~400ms after the last tick reaches disk. A drag is therefore one write, and every caller keeps
|
||||
/// that one settled value **out of** `StyleRecents` — that row remembers deliberate palette picks,
|
||||
/// not colours a drag swept through, so every `write` closure above writes raw rather than through
|
||||
/// `StyleCommand.apply`.
|
||||
@MainActor
|
||||
enum SharedColorPanelSession {
|
||||
|
||||
|
||||
Reference in New Issue
Block a user