API — ICoreEssentials/Theme
The public contract of 20 header(s) under ICoreEssentials/Theme — 41 class/struct definition(s), 160 declaration(s). Each section shows the header's banner and its public (and protected-virtual) surface exactly as the file writes it.
ICoreQtLaneStyle.h#
ICoreEssentials/Theme/ICoreQtLaneStyle.h
SURFACES THE QT LANE STYLES AND THE PAINTED BACKENDS DO NOT.
⚠⚠ THIS IS A DECLARED DIVERGENCE, NOT A STUB, AND THE DIFFERENCE IS THE WHOLE REASON THIS HEADER EXISTS RATHER THAN AN
#ifdefAT A CALL SITE.A6.4 refused, on an owner ruling, to make the
getStyle_*shims portable: they build stylesheet TEXT,setStyleSheetis deliberately empty on AppKit, and porting them would close link symbols while leaving surfaces silently unstyled -- "the stub in disguise". Everything behind THIS header is the opposite case: an owner has looked at a specific surface, decided its sheet is the Qt lane's business, and accepted what the other backends show instead. The AppKit half does nothing and says so; nobody is told a surface was styled when it was not.
Declares no class of its own — see the file.
ICoreScrollBarStyles.h#
ICoreEssentials/Theme/ICoreScrollBarStyles.h
Stylesheet text for the surfaces the TOOLKIT owns: scroll bars, and the chrome of the text panes they live in. Rows A6.4 / A8.1 (the board name is in the .cpp -- a header banner is PUBLISHED, see there).
⚠ THESE PRODUCE QSS, AND THAT IS NOT A LEAK -- IT IS WHAT THEY ARE FOR.
Everything else in the A6.4 migration replaces stylesheet text with a backend-neutral RECORD, because the surface belongs to this tree and both backends can paint it. These four do not, and the difference is real: a QScrollBar is Qt's, an NSScroller is AppKit's, and neither is ours to describe with an ICoreStyleSpec.
So the text stays text. What moved is WHERE IT LIVES: the bodies were in Theme/QtBinding/, which is dropped from an AppKit build, so twelve
Declares no class of its own — see the file.
ICoreStyleApply.h#
ICoreEssentials/Theme/ICoreStyleApply.h
THE NEUTRAL STYLE CALL -- one spelling a panel outside Theme/ can write.
⚠⚠ WHY THIS EXISTS, STATED AS THE MEASUREMENT RATHER THAN AS A GOAL.
A0.3 landed a backend-neutral style RECORD (ICoreStyleSpec, ICoreThemeStyleSpecs) and BOTH interpreters -- and no neutral CALL. The two interpreters have different names, different return types and different things the caller then does with them:
ICoreStyleSpecQss::declarationsFor(spec) -> ICoreString (CSS to paste) ICoreStyleSpecAppKit::resolveSelf(spec) -> numbers to paint with
Measured 2026-08-21:
icoreApplyStyle/applyStyleSpec/setStyleSpecreturned ZERO hits across src/ and include/, and ICoreThemeStyleSpecs had
File-scope declarations#
// How far the rule is allowed to reach.
//
// ⚠⚠ THIS EXISTS BECAUSE A STYLESHEET CASCADES AND A PAINTED GROUND DOES NOT,
// so "apply this spec to this widget" means two different things on the two
// backends unless the caller can say which it meant. Measured, not assumed --
// testingLabs/tests/qss_scope, seven asserted rows:
enum class ICoreStyleScope : int {
// The default, and what every call site written before this enum got: the
// rule is handed to the widget and the toolkit cascades it to descendants.
// Correct for a leaf -- a label, a badge -- where there is nothing below to
// reach.
//
// ⚠⚠ AND WRONG FOR ANYTHING THAT IS NOT A LEAF, WHICH IS THE HALF THIS
// COMMENT USED TO LEAVE TO THE READER. A bare declaration body reaches
// EVERY descendant -- measured in testingLabs/tests/qss_scope, not assumed
// -- and a scroll bar, a viewport and a child field are all descendants. So
// applying a surface's ground to a SCROLLING pane at this scope paints its
// bars with that ground, and it blocks a bar rule the pane inherits from
// above. On Qt only, with nothing printed, and the first render looks
// right.
//
// ⚠ Use SelfOnly or ThisWidgetOnly for anything with children. This value
// is kept as the default because every call site written before the enum
// existed already had it, and changing a default silently re-styles them
// all -- but a NEW call site on a non-leaf should say which it means.
Inherited = 0,
// THIS widget only. On Qt the rule is emitted under the widget's own runtime
// class, which is self-scoping precisely while no descendant is of that
// class or a subclass of it -- the condition to check per surface, and the
// ordinary case for panes whose children are viewports and plain containers.
//
// ⚠ On AppKit this is a NO-OP, and that is the point rather than a gap: the
// resolved style is stored on the view that was named and painted beneath
// its own content. Nothing cascades on that backend, so `SelfOnly` is what
// it already did.
SelfOnly,
// THIS INSTANCE, and nothing below it -- the narrow reach `SelfOnly` was
// assumed to give and does not.
//
// ⚠⚠ WHY IT EXISTS, AS THE MEASUREMENT RATHER THAN THE INTENT.
// `SelfOnly` emits `metaObject()->className()`, which is exactly right for the shims
// it was written for -- QDialog, QPlainTextEdit and QLineEdit all carry
// Q_OBJECT, so the name is theirs. A widget wrapping ICoreWidget::Impl has
// NO Q_OBJECT (ICoreWidget.cpp says so in its own words), so className()
// reports the nearest ancestor that does -- `QWidget` -- and a rule under a
// QWidget selector matches the widget AND EVERY QWidget BELOW IT. Six live
// call sites scope with an `#objectName` selector precisely to avoid that,
// and for them BOTH existing scopes are wider than what they ask for.
//
// ⚠ IT USES THE WIDGET'S OWN OBJECT NAME AND NEVER INVENTS ONE. A generated
// name would be a second key on a widget something else may already be
// keying on. A widget with no object name is a NO-OP with a log, which is
// the loud version of "this scope had nothing to scope to".
//
// ⚠ On AppKit this is what the backend already does, for the same reason
// `SelfOnly` is: the resolved style is stored on the view that was named
// and nothing cascades. The two scopes are distinguishable on Qt and
// identical here, and that asymmetry is the honest one -- a cascade is a
// toolkit behaviour and only one backend has it.
ThisWidgetOnly,
};
ICoreStyleSpec.h#
ICoreEssentials/Theme/ICoreStyleSpec.h
ICoreStyleSpec -- what a themed surface should LOOK like, said without naming a toolkit.
The currency between the theme tokens and whatever draws them: ICoreThemeQss's getStyle_* shims stop returning stylesheet TEXT and start returning one of these, and each backend interprets it -- Qt renders it back to QSS (byte for byte what it emits today), AppKit and WinUI apply it to painted controls and native view properties.
⚠ Colours here are ICoreRgba, NOT ICoreColor, and that is the module's first invariant rather than a preference: Theme/ proper takes no Qt even TRANSITIVELY, and ICoreColor carries 16 bytes of QColor storage and includes ICoreString.h, which forwards to QString. ICoreRgba includes <cstdint>/<string>/<string_view> and nothing else. The architecture census
ICoreStyleEdge#
ICoreStyleSpec.h:313 · struct · 0 declaration(s)
A border.
struct ICoreStyleEdge {
public:
double width = -1.0;
// Default-constructed ICoreRgba is INVALID, which is how "unset" is said.
ICoreRgba color;
};
};
ICoreStyleBox#
ICoreStyleSpec.h:321 · struct · 2 declaration(s)
Padding/margins in logical pixels.
struct ICoreStyleBox {
public:
double left = -1.0;
double top = -1.0;
double right = -1.0;
double bottom = -1.0;
bool isUnset() const;
// All four the same.
static ICoreStyleBox uniform(double v);
};
};
ICoreStyleRule#
ICoreStyleSpec.h:342 · struct · 0 declaration(s)
One part, in one state, and what it should look like.
struct ICoreStyleRule {
public:
ICoreStylePart part = ICoreStylePart::Self;
ICoreStyleState state = ICoreStyleState::Normal;
// Invalid = not mentioned. Valid with alpha 0 = explicitly transparent.
ICoreRgba background;
ICoreRgba foreground;
ICoreStyleEdge border;
// The BOTTOM edge on its own, for the "no frame, one hairline underneath"
// pattern: a tool-bar strip separating itself from the surface below, and
// a column header separating itself from the rows. Both write
// `border: none; border-bottom: 1px solid ...` today, which the single
// `border` above cannot say -- it can state one edge for all four.
//
// ⚠ ONLY THE BOTTOM. The other three are deliberately absent: two call
// sites want this one, none wants the others, and a spec that grows a
// field per edge on spec becomes an ad-hoc CSS parser. Add an edge when a
// surface needs it, never before.
ICoreStyleEdge borderBottom;
double borderRadius = -1.0;
// ---- restored by W0.12: the per-side edges, the stripe, the arrow and
// ---- the indent rule. All six are ADDITIVE -- no field of this struct
// ---- was renamed or removed -- and every one exists because a real
// ---- surface could not be expressed without it.
// Per-side edges, for the surfaces where a single `border` cannot say it:
// the navigator's 3px LEFT edge is the accent bar marking the picked row,
// a header section's BOTTOM edge separates it from the rows, and the
// divider between sections is a RIGHT edge. `borderTop` is here only
// because a per-side model missing one side is a trap for whoever needs it
// first.
//
// ⚠ ORDER MATTERS ON THE QT SIDE: a per-side declaration must FOLLOW the
// shorthand or the shorthand overwrites it. The interpreter owns that
// ordering; a caller sets whichever sides it means.
ICoreStyleEdge borderLeft;
ICoreStyleEdge borderTop;
ICoreStyleEdge borderRight;
// A second ground, for the alternating rows of an item view. Invalid =
// the view does not stripe. Qt: alternate-background-color.
ICoreRgba alternateBackground;
// A pre-tinted image per theme (":/SVGs/RightArrowOnDark.svg") instead of
// going through ICoreIcons::themed() like every other icon in the tree.
// That is a real limitation of the Qt lane, carried into the spec rather
// than hidden: a painted backend is free to tint one source instead, and
// can see from the two paths that the theme is what differs.
std::string image;
// Whether a selected row's wash reaches across the INDENT column as well
// as the row. Qt: show-decoration-selected. On a painted backend it is the
// Branch/Selected rule, because a second wash over the same strip stacks
// its alpha into a darker bar down the left edge. Setting both is the
// defect this flag makes visible.
bool selectionSpansRow = false;
ICoreStyleBox padding;
// Space OUTSIDE the surface, as padding is space inside; unset by the
// same rule. Needed because a context-menu separator insets its rule from
// the popup's edges with margins.
ICoreStyleBox margin;
// Suppress the toolkit's focus indicator.
//
// ⚠ Semantic, not a CSS passthrough. One pane says `outline: none` today;
// on AppKit the equivalent is refusing the focus ring and on WinUI it is a
// different property again. A backend that cannot honour it should say so
// rather than quietly emit nothing.
bool suppressFocusRing = false;
// A fixed width in logical pixels; < 0 is unset. Same protest as
// fixedHeight below -- a splitter states a 1px width in its sheet today.
double fixedWidth = -1.0;
// A fixed height in logical pixels; < 0 is unset.
//
// ⚠ This is a SIZE, not a colour, and it is here under protest: a divider
// line is one pixel tall and today says so in its stylesheet
// (`height: 1px`), so a spec that could not carry it would leave that
// surface unmigratable. Do not grow this into a layout system -- geometry
// belongs to the layout engine, and anything beyond a fixed line thickness
// should go there instead.
double fixedHeight = -1.0;
// Only meaningful on ICoreStylePart::Selection, but kept on the common
// rule so a field's selection does not need a second type.
// A FLOOR, not a size -- distinct from fixedWidth/fixedHeight above, which
// pin a splitter's 1px grab bar exactly. A prompt's buttons say
// `min-width: 84px` so a row of them stays even while still growing for a
// long label; rendering that as `width` would clip "Don't Save".
double minWidth = -1.0;
double minHeight = -1.0;
// ------------------------------------------------------------------
// TEXT (A6.4). `foreground` above is this rule's ink; these three are the
// rest of what a call site used to append as inline CSS.
//
// ⚠ MEASURED BEFORE BEING ADDED, not guessed: across the 19 src/ICoreSDK
// sites that wrote `getStyle_Label() + "; ..."` the appended fragments were
// only FOUR distinct properties -- font-size (7), color (7),
// font-weight:bold (2), font-family:monospace (3). `foreground` already
// covered the second; these cover the other three and nothing more. A
// fifth field should be added the same way -- by counting call sites, not
// by mirroring CSS.
//
// ⚠ PIXELS, NOT POINTS. Every one of those sites says `font-size:11px`,
// and px is not pt: on macOS at 72dpi they coincide and on a 96dpi Qt
// platform they do not. ICoreFont::setPixelSize exists on both backends,
// so the unit survives the crossing intact.
double fontPixelSize = -1.0; // <= 0 is unset
bool fontBold = false;
// ⚠ std::string, NOT ICoreString, AND THAT IS THE MODULE'S FIRST INVARIANT
// RATHER THAN A STYLE PREFERENCE -- the same rule that already forces
// ICoreRgba on every colour in this file. ICoreString forwards to QString,
// so a single member of that type here drags Qt into Theme/ proper
// TRANSITIVELY, which is precisely the trap theme_qt_free_selftest exists
// to catch (CMakeLists.txt, "ICoreString would satisfy the scan and still
// drag QString in behind it").
//
// ⚠ IT WAS ICoreString FOR EXACTLY ONE COMMIT AND THE TREE WENT ON
// BUILDING, which is the part worth remembering: the selftest is
// EXCLUDE_FROM_ALL, so no ordinary build compiles it, and the architecture
// census stayed 14/14 because it scans for Qt NAMES and ICoreString is not
// one. What noticed was testingLabs/tests/style_spec, whose banner says it
// would -- "if that command ever starts needing a Qt include path, the
// invariant has been broken and this file is the thing that noticed."
//
// The two interpreters convert at their own edge with
// ICoreString::fromStdString; both are already Qt-side or backend-side, so
// the crossing costs one call in a place that is allowed to make it.
std::string fontFamily; // empty is unset
ICoreRgba selectionBackground;
ICoreRgba selectionForeground;
};
};
ICoreStyleSpec#
ICoreStyleSpec.h:490 · struct · 17 declaration(s)
The whole instruction for one themed surface: its Self rule plus whatever toolkit-owned parts and states it also has an opinion about.
struct ICoreStyleSpec {
public:
std::vector<ICoreStyleRule> rules;
// The rule for a (part, state), or nullptr. Does NOT fall back to Normal
// -- a backend that wants inheritance composes it explicitly, because the
// one place that must not guess is the interpreter.
//
// ⚠ THE RETURNED POINTER BORROWS FROM THIS SPEC. Two ways to lose it, one
// of which the standalone suite caught being written for real:
// - `makeSpec().ruleFor(...)` points into a temporary that dies at the
// end of the full expression. Hold the spec in a named variable.
// - a later set() that ADDS a rule may reallocate `rules`, invalidating
// every pointer taken before it. Re-query after mutating; a set() that
// replaces an existing (part, state) is safe, but do not rely on
// telling the two apart at a call site.
const ICoreStyleRule* ruleFor(ICoreStylePart part, ICoreStyleState state) const &;
// ⚠ Deleted on an RVALUE, deliberately: `makeSpec().ruleFor(...)` borrows
// into a temporary that dies at the end of the full expression. This turns
// that dangling read into a COMPILE ERROR rather than a stale pointer.
//
// It is a deleted overload rather than a comment because the comment was
// tried first and did not work: the session that wrote the warning above
// then made the same mistake an hour later in its own test, and only the
// suite caught it. Hold the spec in a named local.
const ICoreStyleRule* ruleFor(ICoreStylePart part, ICoreStyleState state) const && = delete;
// Convenience for the overwhelmingly common case.
const ICoreStyleRule* selfRule() const &;
const ICoreStyleRule* selfRule() const && = delete;
// Adds, or replaces the existing rule for that (part, state).
void set(const ICoreStyleRule& rule);
// This spec's rules for ONE PART, re-tagged as Self.
//
// ⚠⚠ THE PROJECTION THE PART-SELECTING CALL IS BUILT ON, and it lives here
// rather than in either binding because it is pure record manipulation and
// BOTH bindings need the identical answer. A part overload that reduced the
// spec its own way per backend would be two interpreters again, which is
// the thing ICoreStyleApply.h exists to stop.
//
// The rules keep their STATES: Normal, Hover and Disabled for the part all
// survive, and only the part tag moves. So a caller handed the child that
// IS the part gets that child's whole story, which is what "apply this
// spec's Caption rules to this label" has to mean.
//
// Returns an empty spec when the spec carries nothing for that part, which
// the seam treats as "the theme says nothing here" -- a no-op, not a reset.
[[nodiscard]] ICoreStyleSpec partAsSelf(ICoreStylePart part) const;
// This spec with `other`'s rules folded in, `other` winning on any
// (part, state) both carry.
//
// ⚠⚠ IT EXISTS SO ONE SHARED FRAGMENT CAN REACH MANY SURFACES WITHOUT
// BEING COPIED INTO EACH, which is a property this tree paid for once
// already: task C8 folded two inline copies of the overlay scrollbar rules
// onto a single helper precisely so a token change could not make a code
// editor's bars drift from a log pane's. A per-surface spec that RESTATED
// those rules would undo that silently, and the drift would show up as two
// panes looking different months later rather than as a failure now.
//
// ⚠ `other` WINS, and that direction is the useful one: a surface takes the
// shared fragment and may then override one part of it. The reverse would
// make the fragment unable to say anything a surface had already mentioned.
[[nodiscard]] ICoreStyleSpec withPartsOf(const ICoreStyleSpec& other) const;
// Overlays `other` onto this spec: for each of its rules, a matching
// (part, state) has only its SET fields copied over, and a rule with no
// match is appended whole.
//
// This exists because the shims compose. `bareFieldSheet` wraps another
// shim's body in field chrome, so the spec peer has to be able to say
// "that surface, plus these overrides" without flattening one into the
// other by hand at every site.
//
// ⚠ "Set" means isValid() for a colour and >= 0 for a length -- the same
// unset/explicit distinction the rest of the type turns on. An overlay
// therefore cannot UNSET something the base said; it can only add or
// replace. That is deliberate: every composition in the shims today adds.
void overlay(const ICoreStyleSpec& other);
// Whether anything asks about a part the current backend may not be able
// to style natively. A backend uses this to assert rather than to silently
// drop -- see the ⚠ in the file banner.
bool touchesToolkitOwnedParts() const;
// ---------------------------------------------------------------------
// THE ONE-LINE OVERRIDE (A6.4). Each returns a COPY with one field of the
// Self/Normal rule changed, so a call site that used to append inline CSS
// stays one line after migrating.
//
// ⚠ THIS EXISTS BECAUSE OF A COUNT, NOT BECAUSE FLUENT APIS ARE NICE. The
// migration pattern without it is three statements -- take the spec, name
// a mutable rule, set it back -- at each of the 19 src/ICoreSDK sites that
// today write `getStyle_Label() + "; font-size:11px;"`. Three lines where
// there was one, 19 times, is how a mechanical migration turns into a
// diff nobody wants to read. With it:
//
// icoreApplyStyleSpec(label,
// ICoreThemeStyleSpecs::label(theme).withFontPixelSize(11));
//
// ⚠ THE FOUR ARE THE FOUR THAT WERE MEASURED, and the list does not grow
// on taste. Across those 19 sites the appended fragments were only four
// distinct properties -- font-size (7), color (7), font-weight:bold (2),
// font-family:monospace (3) -- which is exactly withFontPixelSize, withInk,
// withBold and withFontFamily. A fifth is added the way these were: by
// counting call sites that need it, never by mirroring CSS.
//
// ⚠ THEY ACT ON Self/Normal AND CREATE IT IF ABSENT, which is what makes
// them safe to chain onto any spec. A spec that says nothing about its own
// surface still has somewhere to put an ink.
//
// ⚠ AND THEY RETURN BY VALUE RATHER THAN `ICoreStyleSpec&`, deliberately.
// A reference-returning chain on a temporary hands back a dangling
// reference the moment somebody writes `const auto& s = spec().withBold();`
// -- the same class of mistake ruleFor() already deletes its rvalue
// overload to prevent. A copy costs one vector of small PODs and cannot
// dangle.
ICoreStyleSpec withInk(const ICoreRgba& ink) const;
ICoreStyleSpec withFontPixelSize(double px) const;
ICoreStyleSpec withBold(bool bold = true) const;
ICoreStyleSpec withFontFamily(const std::string& family) const;
// ---------------------------------------------------------------------
// THE GROUND TRIO (A8.1's fragment block), added by the same rule the four
// above were: a COUNT of call sites, never a mirror of CSS.
//
// The four above came out of the 19 sites that appended inline CSS to a
// NAMED style. These three come out of the 57 statements that never named a
// style at all -- they build the whole thing at the call site out of
// cssRgb() fragments, which is why they are a link blocker on AppKit rather
// than a spelling. Inventoried from HEAD blobs, over src/ICoreSDK and
// src/Main:
//
// a ground 30 statements withGround
// a border 14 withBorder
// a corner radius 7 withCornerRadius
// padding, some shape 6 NOT ADDED -- see below
//
// ⚠ PADDING IS DELIBERATELY ABSENT AT SIX. It is over the line where the
// other three sit, and it is the one property in the block whose sites do
// NOT agree on a shape: they write one-value, two-value and single-edge
// forms (`padding`, `padding-left`, `padding-bottom`). ICoreStyleBox can
// hold all of them, so the missing piece is not the field -- it is a
// decision about which spelling the call sites should converge on, and that
// is worth taking once, with the six in front of it, rather than inside a
// migration.
//
// ⚠ THEY CHAIN ONTO AN EMPTY SPEC, which is what makes them usable at a
// site that names no themed surface: like the four above they act on
// Self/Normal and create it if absent, so `ICoreStyleSpec().withGround(c)`
// is a complete instruction and needs no ICoreThemeStyleSpecs entry to
// hang off.
//
// ⚠ withBorder TAKES BOTH TERMS BECAUSE A COLOUR ALONE IS NOT A BORDER --
// the QSS interpreter emits nothing for an edge whose width is unset, and
// Qt would ignore it if it did. Width 0 is the explicit `border: none` that
// 15 of today's sites write, and it is distinct from leaving the border
// unmentioned; both reach the interpreter unchanged through this.
ICoreStyleSpec withGround(const ICoreRgba& fill) const;
ICoreStyleSpec withBorder(double width, const ICoreRgba& colour) const;
ICoreStyleSpec withCornerRadius(double px) const;
// ⚠ THE FIFTH, AND IT IS THE DECISION THE COMMENT ABOVE SAID TO TAKE ONCE.
// Padding was held back at four statements precisely because they disagree
// on spelling -- `padding: 5px` twice, `padding-left: 4px`, and
// `padding-left: 5px; padding-bottom: 4px`. Taken here with all four in
// front of it, the answer is ONE override that takes the box the record
// already holds, not four edge-named ones: `ICoreStyleBox::uniform(5)`
// covers the shorthand and a braced box covers the per-edge sites, so no
// CSS vocabulary is mirrored into this type to say what it can already say.
//
// ⚠ AND IT IS NOT A PURE TRANSLATION, WHICH IS WHY THE SITES THAT TAKE IT
// MATTER. ICoreStyleSpecQss renders padding as the FOUR-VALUE shorthand
// always, filling an unset edge with 0 -- its own comment says the
// shorthand has no way to say "leave this one alone". So a spec carrying
// only a left padding emits `padding: 0px 0px 0px 4px`, which SETS the
// other three rather than leaving them. That is the same instruction only
// where the other three were already 0. All four sites here are labels
// whose entire sheet is replaced by this call, so their other edges come
// from the toolkit default, which is 0. A surface that inherits a padding
// from somewhere else is NOT covered by that argument.
ICoreStyleSpec withPadding(const ICoreStyleBox& padding) const;
};
};
File-scope declarations#
// Which part of a themed surface a rule is about.
//
// Self is the widget the app made. Everything else is a sub-part the TOOLKIT
// made and handed back -- the group that cannot be styled by stylesheet on a
// native backend, and must be answered per-backend.
enum class ICoreStylePart : int {
Self = 0,
// The toolkit's own Cut/Copy/Paste popup on an editable field. Qt: QMenu
// rules appended to the field's sheet -- and NOT optional there, because
// setting any sheet hands the subtree to the stylesheet engine and a QMenu
// with no background rule is drawn with no ground at all. AppKit: the
// field editor's NSMenu, set natively.
ContextMenu,
// An item view's column header. Qt: QHeaderView::section. AppKit:
// NSTableHeaderView / NSTableColumn.
Header,
// Rows/items inside an item view, and the selected-row treatment.
Item,
// Text selection inside an editable field. Qt: selection-background-color
// and selection-color. AppKit: NSTextView's selectedTextAttributes.
Selection,
// The scrollbar of a scrolling surface -- the TROUGH the handle runs in.
// Qt: QScrollBar sub-rules. AppKit: a themed NSScroller subclass.
//
// ⚠ `fixedWidth` ON THIS PART MEANS THICKNESS, whichever way the bar runs.
// A bar's short axis is `width` when it is vertical and `height` when it is
// horizontal, and the tree's overlay bars use the SAME number for both --
// measured, not assumed. So one value covers both orientations and the
// interpreter transposes it. There is deliberately NO orientation axis on
// the record: an orientation is not a style, it is which of two shapes the
// same style is poured into.
ScrollBar,
// The draggable part of that scrollbar -- the knob.
//
// ⚠ A PART RATHER THAN A QT SPELLING, and that distinction is the whole
// reason this one is allowed where `add-page` and `sub-line` are not: an
// NSScroller HAS a knob, so the role survives the backend swap, while Qt's
// page and stepper sub-controls have no counterpart at all and would be a
// toolkit's vocabulary smuggled into a type whose purpose is to have none.
//
// ⚠ `minHeight` ON THIS PART MEANS MINIMUM LENGTH ALONG THE BAR -- the same
// transposition as `fixedWidth` above, for the same measured reason.
//
// ⚠ THE HOVER WASH IS A STATE, not a second part: ScrollBarHandle/Hover.
// No new state vocabulary was needed and none was added.
// The MOVING part of a scroll bar, as distinct from the trough.
//
// ⚠ RESTORED BY W0.12 AFTER THE 2026-08-22 MERGE DROPPED IT, and the
// finding it carries is W0.3's: `ScrollBar` was the TROUGH all along, so a
// surface that wanted a visible thumb on an invisible groove could not say
// so with one part. Qt spells them `QScrollBar` and `QScrollBar::handle`;
enum class ICoreStyleState : int {
Normal = 0,
Hover,
Pressed,
Disabled,
Checked,
Focused,
// An item view's SELECTED row. Distinct from Hover (the pointer is over
// it) and from Checked (a control is toggled on): a list can have a
// selected row the pointer is nowhere near, and both states can be true at
// once with different colours. Three shims need it -- the script
// explorer's list and the two trees.
//
// ⚠ NOT the same thing as ICoreStylePart::Selection, which is a text
// field's selected TEXT and folds onto the field's own selector as
// `selection-background-color`. Same English word, two unrelated
// mechanisms, and Qt spells them differently on purpose.
Selected,
// ---- restored by W0.12 ------------------------------------------------
// Selected AND hovered, as one state rather than two composed.
//
// ⚠ IT IS NOT Hover-OVER-Selected AND THAT IS THE POINT. The navigator's
// wash steps 1.7x at rest and 2.2x under the pointer, and the diagnosis
// tree deliberately makes the two IDENTICAL -- a spec with no way to say
// this could express neither, because a backend composing Hover over
// Selected has no idea which of the two it should have produced.
SelectedHover,
// Selected while the view does not have focus. Qt: `:selected:!active`.
SelectedInactive,
};
ICoreStyledObjectNames.h#
ICoreEssentials/Theme/ICoreStyledObjectNames.h
ICoreStyledObjectNames#
ICoreStyledObjectNames.h:62 · struct · 1 declaration(s)
THE OBJECT NAMES A PORTABLE CALLER SETS AND A PER-BACKEND INTERPRETER KEYS ON -- the one kind of styling string that has TWO owners.
struct ICoreStyledObjectNames {
public:
ICoreStyledObjectNames() = delete;
// The three surfaces of the code editor's chrome. The app constructs all
// three, sets these on them, and applies one spec per surface; the Qt
// binding renders ICoreStylePart::ToolBar and ::Badge as `#`-selectors on
// the last two, and ICoreStyleScope::ThisWidgetOnly reaches the first by
// reading the widget's own name back.
//
// ⚠ Constants rather than literals repeated at both ends: the sheet is
// built in one file and the setObjectName() calls are in another, so a
// typo in either would be a rule that silently matches nothing.
static constexpr const char* kCodeEditorRootName = "icoreCodeEditorRoot";
static constexpr const char* kCodeEditorToolBarName = "icoreCodeEditorToolBar";
static constexpr const char* kCodeEditorBadgeName = "icoreCodeEditorBadge";
// The two action buttons on the toolkit's own modal prompt, which
// ICoreStylePart::AffirmativeAction and ::DestructiveAction render as here.
//
// ⚠ THERE WERE THREE SPELLINGS OF THESE TWO STRINGS UNTIL 2026-08-26 --
// this pair, ICoreMessageBox.cpp's own file-local constants, and a bare
// literal in ICoreInputDialog.cpp. ICoreThemeQss.h recorded the second and
// left it, because "a shared home for them is a question for whoever holds
// both". This is that home; all three sites read it now.
static constexpr const char* kAffirmativeActionName = "icoreAffirmativeAction";
static constexpr const char* kDestructiveActionName = "icoreDestructiveAction";
};
};
ICoreTheme.h#
ICoreEssentials/Theme/ICoreTheme.h
ICoreSurfaceTokens#
ICoreTheme.h:25 · struct · 1 declaration(s)
Surfaces: what things sit on -----------------------------------------
struct ICoreSurfaceTokens {
public:
ICoreRgba window; // root window background
ICoreRgba titleBar; // title bar strip
ICoreRgba panel; // standard panel background
ICoreRgba panelRaised; // content areas raised above panels (tab pane, menus)
// The plate for a card whose CONTENT brings its own bright surface --
// the Terminal panel is the case: its grid paints the editor's white over
// most of the card, so the chrome around it (title, path, the action row)
// on a panelRaised white too is one undifferentiated slab. A step under
// panelRaised gives that chrome its own ground to sit on and leaves the
// grid reading as the brightest thing in the panel, which is what it is.
// The dark theme spells it exactly panelRaised -- a dark card has no glare
// to separate itself from -- so this only ever moves the light one.
ICoreRgba panelRecessed;
ICoreRgba canvas; // block-diagram canvas
ICoreRgba sidebar; // left tool bar container
ICoreRgba hairline; // hairline borders between surfaces
ICoreRgba hairlineAccent; // grayed-royal hairline for outlined containers & fields
ICoreRgba panelBorder; // outline around the window's fixed panels
ICoreRgba divider; // layout divider lines
ICoreRgba shadow; // default drop-shadow color
};
};
ICoreContentTokens#
ICoreTheme.h:49 · struct · 0 declaration(s)
Content: text & icons -------------------------------------------------
struct ICoreContentTokens {
public:
ICoreRgba textPrimary;
ICoreRgba textSecondary;
ICoreRgba textTertiary; // faint labels, inactive items
ICoreRgba textAccent; // brand-colored text
ICoreRgba textOnAccent; // text on DARK filled surfaces (royal, and the
// dark theme's deepened variant plates)
// ...and text on PALE filled surfaces. The pair exists because a solid
// variant plate is not one brightness: the light theme lifts its fills
// toward the panel, and a near-white caption that reads at 4.2:1 on that
// theme's royal reads at 3.1:1 on its red and 2.3:1 on its green. Which of
// the two a plate takes is not a per-theme decision and cannot be one token
// -- ICoreThemeBinding::inkOn measures the fill and picks.
ICoreRgba textOnLightFill;
ICoreRgba textDisabled;
// How much of its own background a disabled rich-text view washes back over
// itself. Character formats -- syntax colours, logger severities -- outrank
// a stylesheet's disabled colour, so those views fade rather than recolour,
// and this is what they fade by.
double disabledScrimOpacity;
};
};
ICoreInteractionTokens#
ICoreTheme.h:73 · struct · 5 declaration(s)
Interaction: hover / press / selection --------------------------------
struct ICoreInteractionTokens {
public:
ICoreRgba hoverFill; // generic hover wash (translucent)
ICoreRgba pressedFill; // generic pressed wash (translucent)
ICoreRgba controlFill; // rest fill of inline controls (combo boxes, toggles)
ICoreRgba controlFillHover; // their hover fill
ICoreRgba selectionFill; // canvas selection marquee fill (translucent)
ICoreRgba selectionBorder; // canvas selection border / selected object tint
ICoreRgba selectionShadow; // shadow color of selected objects
ICoreRgba accent; // the royal, as usable on this theme's surfaces
ICoreRgba accentDeep;
ICoreRgba accentContainer; // prominent brand container (run section, etc.)
};
};
ICoreButtonTokens#
ICoreTheme.h:95 · struct · 2 declaration(s)
⚠ ICoreGlassTokens — the tinted-GLASS finish the ICoreStyles variants used to wear (a fill lit from above inside a rim-lit border) — was deleted on 2026-08-15.
struct ICoreButtonTokens {
public:
// The FILL of the nav-row hover treatment — its wash only. Its hairline and
// its left edge stay on interaction.accent (ICoreNavItemStyle), so this is
// the one part of the treatment that differs per theme: the dark theme needs
// a lighter body behind the pointer without lightening the strokes that
// frame it, and without moving interaction.accent, which is also the
// selection border and the canvas selection.
ICoreRgba hoverWash;
// How much of that wash lands, 0-255, at full hover. Per theme for the same
// reason as the colour above: over a near-white panel the wash needs very
// little body before it reads, and over a g950 one it needs a good deal
// more. It was a shared constant in ICoreNavItemStyle.cpp, which meant the
// only way to lift the dark theme was to lift the light theme with it.
int hoverWashAlpha;
// The treatment's two STROKES — the hairline around it and the edge growing
// out of its left side. Separate from hoverWash above because the two halves
// move in opposite directions per theme: the dark theme wants a lighter body
// inside a darker frame, which is what gives the wash something to sit
// against. Both drew interaction.accent before this split.
ICoreRgba hoverEdge;
// How much of the hairline lands, 0-255, at full hover. The left edge is
// always full strength; this is the ring around the body. Per theme because
// a selection blue at part strength over a dark panel reads as a dull line
// rather than a lit one — the dark theme runs it at full to keep the colour
// as rich as it is in the swatch.
int hoverEdgeAlpha;
ICoreRgba hoverFill; // base color of the animated hover background
// The RESTING plate under a caption-only button — one that carries no icon.
// A button whose whole content is a word has nothing but that word to say it
// is a button, so it gets a surface at rest; a button carrying a glyph is
// already legible as one and stays transparent until the pointer reaches it.
// (ICoreButton::wearsRestPlate is where that rule is spelled out.)
//
// WELL OFF interaction.controlFill, in OPPOSITE directions per theme: darker
// than the inline control fill on light, lighter on dark. It began as that
// token exactly, which was the wrong read — a combo box and a text field are
// things you type INTO and sit level with the panel, while a button is a
// thing you press and has to sit proud of it. Both moves are the same move
// said in each theme's own vocabulary: away from the surface, which on a
// near-white panel means down the grey ramp and on a g950 one means up.
//
// The distance is NOT symmetric, and the asymmetry is the finding rather
// than an accident: dark went a clean two stops out to g700 and settled
// first time, while light came back through five passes to sit halfway
// between g100 and g200 — barely off its own panel. A dark panel gives a
// plate room to be a surface; a near-white one has almost none between
// invisible and loud, and this theme wants the quiet end of it. The .cpp
// carries both ladders. Retune each on its own theme; a move that looks like
// restoring symmetry is a move nobody looked at.
//
// ⚠ DARK sits one stop off content.textDisabled (g700 against g600), so a
// disabled caption there would be drawn in very nearly its own background.
// The plate is not drawn at all while a button is disabled, which is what
// makes that safe. Light has since retreated clear of the same collision —
// it was g300, that theme's textDisabled EXACTLY, three passes ago — but the
// clause is dark's and stays whatever light does. Do not restore the plate on
// disabled buttons, and do not push either value further from its panel
// without checking that clause is still in ICoreButton::wearsRestPlate.
ICoreRgba restFill;
double hoverOpacity; // target opacity on hover
double pressedOpacity; // target opacity while pressed
// ⚠ THE THREE OPACITIES BELOW NO LONGER REACH AN ALPHA CHANNEL. Since the
// tinted variants went solid (2026-08-15) their fill is opaque in every
// state; what these still do is carry the button's ONE hover animation, and
// the painters read the travel between them as a 0..1 progress and spend it
// lightening the plate by tintedHoverLighter. Retuning them therefore
// changes how far a hover lifts the colour, not how much of it lands.
double tintedRestOpacity; // rest end of a tinted variant's hover animation
double tintedHoverOpacity; // ... and its hover end. Separate from the plain
// button's hoverOpacity above: a tint has to
// carry its own body over the surface, and how
// much body that takes differs per theme, where
// the plain grey wash is the same job in both
double dangerHoverOpacity; // hover end for danger buttons — deliberately
// above hoverOpacity: a muted fill at the shared
// 0.5 barely moves against the rest state, and
// Cancel/Delete must clearly answer the pointer
// How far a solid variant's fill LIGHTENS at full hover, as an
// ICoreColor::lighter() factor (100 = unchanged). This is the whole of the
// pointer response now that the fill is opaque, so it is the knob to turn if
// a hovered Commit/Delete does not read — and it is per theme because the
// same lift over a near-white panel and over a g950 one are not the same
// move. Press pushes past it: the progress is clamped a little above 1, so a
// held button lifts further in the same direction it always did.
int tintedHoverLighter;
// The variant's HOVER STROKES — the nav treatment's hairline and its left
// accent edge — as ONE colour per theme, shared by all three plates.
//
// ⚠ IT IS NOT DERIVED FROM THE PLATE, and two attempts at deriving it were
// deleted on 2026-08-15 before this landed. Stepping the plate's own hue
// away from itself (the `tintedEdgeLighter` factor that stood here) makes
// the stroke a shade of the button, so it reads as the surface catching
// light rather than as a mark appearing on it. Measuring the caption's ink
// per plate (ICoreThemeBinding::inkOn) fixes that but splits the treatment:
// the light theme's royal plate then took a near-white frame while its red
// and green took a near-black one, so the three siblings no longer hovered
// alike. Owner chose one colour per theme over either.
//
// The two themes go OPPOSITE ways, which is the same finding
// ICoreNavItemStyle records for its own strokes: over pale plates on a
// near-white panel only a dark step defines an edge, and over deepened
// plates on a g950 one only a lit step is seen. Neutral on light so it is
// never any plate's own hue; the near-white on dark is already neutral
// enough at that ground.
//
// Measured against the three plates it lands on (light g800 / dark onRoyal):
//
// light royal 2.8:1 danger 3.7:1 success 5.0:1
// dark royal 9.6:1 danger 6.3:1 success 4.7:1
//
// ⚠ The light theme's royal is still the weak one, a shade under the 3:1
// that non-text contrast wants — it was 2.5:1 until that plate was lifted to
// royalBright 0.45, which is the lever this note named and the owner then
// pulled for other reasons. It is accepted because the frame is not the only
// thing that moves: the plate lightens by tintedHoverLighter underneath it.
// Lifting the plate further is still the only fix that does not split the
// treatment again — but it spends the caption, which is already past AA
// there. Do not spend it silently.
ICoreRgba tintedEdge;
ICoreRgba primaryFill; // brand variant (royal)
ICoreRgba primaryText;
ICoreRgba ghostBorder; // outline variant
ICoreRgba ghostText;
ICoreRgba dangerFill; // semantic variants
ICoreRgba successFill;
ICoreRgba warningFill;
ICoreRgba graphicsHoverFill; // hover pill of buttons floating on the canvas (translucent, royal-tinted)
int radius;
int fadeMs; // hover fade duration
};
};
ICoreToggleTokens#
ICoreTheme.h:226 · struct · 0 declaration(s)
struct ICoreToggleTokens {
public:
ICoreRgba track; // switch track at rest
ICoreRgba trackHover;
ICoreRgba trackOn; // the lit track, when checked
ICoreRgba trackOnBorder; // and its rim
ICoreRgba knobOn; // knob when checked
ICoreRgba knobOff;
};
};
ICoreSyntaxTokens#
ICoreTheme.h:235 · struct · 0 declaration(s)
struct ICoreSyntaxTokens {
public:
ICoreRgba background; // code editor background
ICoreRgba text; // default code text
ICoreRgba keyword;
ICoreRgba function;
ICoreRgba number;
ICoreRgba string;
ICoreRgba comment;
};
};
ICoreTabTokens#
ICoreTheme.h:245 · struct · 0 declaration(s)
struct ICoreTabTokens {
public:
ICoreRgba barBackground; // strip holding the tab headers
ICoreRgba activeBackground; // active tab pill
ICoreRgba activeText;
ICoreRgba inactiveText;
ICoreRgba headerSelected; // focused tab header widget
ICoreRgba headerUnselected;
int radius;
};
};
ICoreMenuTokens#
ICoreTheme.h:255 · struct · 0 declaration(s)
struct ICoreMenuTokens {
public:
ICoreRgba barBackground; // context menu bar
ICoreRgba background; // popup background
ICoreRgba text;
ICoreRgba hoverBackground; // highlighted item
ICoreRgba hoverText;
int radius;
int itemPaddingV;
int itemPaddingH;
};
};
ICoreFieldTokens#
ICoreTheme.h:266 · struct · 0 declaration(s)
struct ICoreFieldTokens {
public:
ICoreRgba background; // line edits, directory field
ICoreRgba border;
ICoreRgba text;
int radius;
// Fill for a box showing a value the user cannot type in, sitting beside
// ones they can. Both themes need it and neither can borrow another token:
// on dark, background is the near-black both kinds would wear; on light it
// is pure white, and so is panelRaised. Pair it with content.textSecondary.
ICoreRgba readOnlyBackground;
// Text selection INSIDE an editable field -- the wash behind the selected
// characters and the ink drawn on it. Without these a field falls back to
// the toolkit's own highlight, which is the platform blue and belongs to
// no theme here.
//
// Solid rather than translucent, unlike interaction.selectionFill: that one
// washes over a canvas whose ink is not underneath it, whereas this one has
// the field's text on top of it, so the pairing must be legible on its own
// terms. A translucent wash over `background` gives an ink contrast that
// depends on the field's fill, which call sites are free to override
// (setFieldBackground), so it cannot be checked once and trusted.
ICoreRgba selectionBackground;
ICoreRgba selectionText;
// Field metrics. A themed field draws its own border rather than using the
// Qt frame, so nothing else gives the text room -- these are what keep it
// off the border, and what a field falls back to when a layout does not
// pin its height.
int paddingH;
int paddingV;
int minHeight;
// Whether a field marks focus with the platform's heavier BOTTOM edge where
// the platform has one -- today the WinUI TextBox, whose focused visual state
// thickens its lower border into an accent underline. On by default, which
// is the stock Windows text box; an app that wants focus shown by the border
// colour alone turns it off through ICoreThemeManager::setThemeCustomizer().
// The AppKit and gtk4 seats draw no underline, so they have nothing to turn off.
bool focusUnderline = true;
};
};
ICoreBlockTokens#
ICoreTheme.h:309 · struct · 2 declaration(s)
struct ICoreBlockTokens {
public:
ICoreRgba frameFill; // canvas block body
ICoreRgba frameBorder;
double frameBorderWidth;
ICoreRgba selectionFill; // block selection overlay (translucent)
ICoreRgba selectionBorder;
int radius;
// ---- The live readout plate -----------------------------------------
//
// What a block wears when it shows a VALUE on its own face — today the
// Display block, which paints its input there instead of logging it.
//
// It is its own pair rather than a borrowed one because the plate has to
// separate from `frameFill` in BOTH themes, and nothing already in the
// theme does. field.readOnlyBackground is the closest idea and is the
// wrong value twice over: on light it resolves near white, a hair off the
// g50 block body, and on dark it is panelRaised, a few percent off
// royalTintDark. A readout the same colour as the block it sits on is not
// a readout.
//
// The plate INVERTS its block in each theme, and that is the whole idea:
// dark face with light digits under the light theme's near-white body,
// light face with dark digits under the dark theme's near-black one. Each
// is the furthest a plate can get from the block it is set into, which is
// what makes it read as a separate surface rather than a shaded corner.
//
// ⚠ Consequently readoutText is NOT light in both themes any more, and
// nothing may pin it: it is the ink that travels with readoutFill, and a
// reader that snapshots it in a constructor shows dark-on-dark after a live
// theme switch. Read it on paint, or re-read it from a theme subscription —
// ICoreBlockViewFaceLabel does the latter.
ICoreRgba readoutFill;
ICoreRgba readoutBorder;
ICoreRgba readoutText; // the ink the CURRENT theme's plate carries,
// dark or light — see the ⚠ above.
// ---- The configuration overlay --------------------------------------
//
// What a block wears while its config UI is open: a SCRIM directly over the
// block, and the two floating PANES (parameters, description) that sit
// beside it. Two roles rather than one wash, because they answer different
// questions — the scrim must let the block stay visible through it, and the
// panes carry text and must not.
//
// The scrim is the selection's own hue at a fraction of its weight, and
// that is deliberate. Right-clicking a block selects it, so the scrim and
// the selection ring are almost always drawn concentrically; giving the
// scrim a foreign hue made two rings that disagreed. Same family, separated
// by weight, reads as one state — "selected, and open for editing" — with
// the saturated ring still on top.
//
// The panes take the app's ordinary panel surface and its grayed-royal
// outline, because that is what they are: panels, floating over the canvas
// exactly as a menu does.
ICoreRgba configScrimFill; // wash over the block itself (translucent)
ICoreRgba configScrimBorder; // the scrim's outline
ICoreRgba configPaneFill; // surface of the parameter / description panes
ICoreRgba configPaneBorder; // their outline
// The port stubs on a block's edge. They are drawn as strokes ON the block
// body, so they read against frameFill rather than against the canvas.
ICoreRgba portStub;
ICoreRgba portStubHover;
// The ink of the block library's own art — the glyph inside the block body
// (a Gain's triangle, an Integrator's 1/s). That art is registered as
// literal SVG next to each block, drawn in a single flat ink, so this is
// the one colour a theme has to say about all of it.
ICoreRgba iconInk;
// A handful of glyphs carry one part in colour, because the colour IS the
// thing being drawn: the Scope's trace, the Subsystem's wiring. Those parts
// are the exception to the flat-ink rule above, so they get their own pair
// rather than being flooded with iconInk.
//
// Both are picked to clear frameFill on their own theme — the art sits on a
// near-white body on light and a near-black one on dark, so a single value
// could not have held on both.
ICoreRgba iconAccent; // the wiring/signal teal
ICoreRgba iconAccentAlt; // the live-trace green
ICoreRgba iconAccentWarm; // the second channel / output side
ICoreRgba iconAccentCool; // cursors, and the die inside a package
// Four rather than two because a glyph that draws more than one of a thing
// has to tell them apart: the Scope shows two traces and the Subsystem's
// pins run in and out, and one accent for both halves of either pair says
// nothing. They are the plot series' own hues, so a two-trace Scope icon
// and a two-trace chart agree on which colour is which channel.
};
};
ICoreLinkTokens#
ICoreTheme.h:405 · struct · 1 declaration(s)
Links: the wires between ports ---------------------------------------- A link is drawn in one of four states, and the pairs matter as much as the individual colours: hovering must clearly lift the...
struct ICoreLinkTokens {
public:
ICoreRgba wire; // connected, at rest
ICoreRgba wireHover;
ICoreRgba unconnected; // a dangling end — drawn dashed
ICoreRgba unconnectedHover;
ICoreRgba tailDot; // the dot at a branch's tail, at rest
ICoreRgba tailDotHover;
ICoreRgba selectionBand; // wash behind a selected segment (translucent)
};
};
ICoreSignalTypeTokens#
ICoreTheme.h:440 · struct · 0 declaration(s)
struct ICoreSignalTypeTokens {
public:
ICoreRgba port; // the port stub at rest
ICoreRgba portHover;
ICoreRgba wire; // a CONNECTED link carrying this type
ICoreRgba wireHover;
// The dot at a branch's tail. Its own pair rather than "just use wire",
// because on the light theme the dot has always been a STEP DARKER than the
// wire it terminates -- it is a mark, not a length of line -- and the
// ICoreDouble row keeps exactly the two greys link.tailDot / tailDotHover
// had, so a diagram of ordinary double ports is unchanged.
ICoreRgba dot;
ICoreRgba dotHover;
ICoreRgba badgeText; // the ink of the badge beside the port
ICoreRgba badgePlate; // the plate the badge sits on
ICoreWireStroke stroke = ICoreWireStroke::Solid;
};
};
ICoreSignalIncompatibleTokens#
ICoreTheme.h:463 · struct · 0 declaration(s)
The one pair in this family that is NOT per type: what a port and a wire look like while the drop the user is holding would be REFUSED.
struct ICoreSignalIncompatibleTokens {
public:
ICoreRgba port;
ICoreRgba wire;
};
};
ICoreCanvasAreaTokens#
ICoreTheme.h:469 · struct · 1 declaration(s)
Canvas areas: the grouping rectangles blocks are dropped into ----------
struct ICoreCanvasAreaTokens {
public:
ICoreRgba defaultFill; // wash a newly created area starts with (translucent)
ICoreRgba titleBarFill;
ICoreRgba titleBarBorder;
};
};
ICoreTimeLineTokens#
ICoreTheme.h:496 · struct · 1 declaration(s)
Timeline: the ticked ruler inside the floating simulation bar --------- The ruler is a plate INSIDE the timeline pill, not the pill itself, so it takes its own ground rather than surface.panel — on...
struct ICoreTimeLineTokens {
public:
ICoreRgba rulerFill; // the plate the dashes are drawn on
ICoreRgba rulerBorder; // its outline against the pill
ICoreRgba dashMajor; // whole- and half-unit ticks
ICoreRgba dashMinor; // the tenth ticks between them
ICoreRgba elapsedFill; // the plate from the left edge to the progress mark (opaque)
ICoreRgba pauseTarget; // the fast-forward mark a click ahead of the progress mark sets
};
};
ICoreChartTokens#
ICoreTheme.h:516 · struct · 0 declaration(s)
Charts: the plot itself, as opposed to the window around it ----------- Qt Charts paints its own background, grid, axis rules and tick labels from a built-in theme of its own, and that theme wins o...
struct ICoreChartTokens {
public:
ICoreRgba plotBackground; // the chart rectangle, behind everything
ICoreRgba plotAreaBackground; // the data region bounded by the axes
ICoreRgba gridLine; // major grid
ICoreRgba minorGridLine; // minor grid — only a log ruler draws one
ICoreRgba axisLine; // the axis rule and its tick marks
ICoreRgba tickLabel; // the numbers running along an axis
ICoreRgba legendText;
ICoreRgba pointLabel; // per-point value labels drawn onto the plot
// The colors successive line paths are handed. Theme-independent by
// design — see ICoreVisualIdentity::plotSeries for why.
std::vector<ICoreRgba> seriesPalette;
};
};
ICoreInfoTokens#
ICoreTheme.h:531 · struct · 0 declaration(s)
struct ICoreInfoTokens {
public:
ICoreRgba background; // info label container
ICoreRgba border;
int radius;
int padding;
};
};
ICoreTypographyTokens#
ICoreTheme.h:538 · struct · 0 declaration(s)
struct ICoreTypographyTokens {
public:
ICoreFontSpec display;
ICoreFontSpec title;
ICoreFontSpec body;
ICoreFontSpec control; // buttons, tabs, list items
ICoreFontSpec caption;
ICoreFontSpec label; // uppercase section labels, wide tracking
ICoreFontSpec mono; // numeric output, paths, technical labels
};
};
ICoreBrandTokens#
ICoreTheme.h:570 · struct · 0 declaration(s)
Brand: the tokens the NAMED gradients are made of ---------------------- ⚠ PANELS DO NOT BUILD BRAND GRADIENTS.
struct ICoreBrandTokens {
public:
// The backdrop's vertical base ramp, top and bottom.
ICoreRgba backdropTop;
ICoreRgba backdropBottom;
// Its two lights, alpha baked in: the brand colour blooming from the
// upper left, and an optional second light from the lower right. Under the
// navy royal the glint is fully transparent -- the backdrop is the single
// bloom it always was -- but the stop exists so a two-colour brand is a
// change to the derivation, not to every backdrop.
ICoreRgba backdropBloom;
ICoreRgba backdropGlint;
// The trace board the website sits on (ICorePublicFace site.css, "THE
// BOARD"), alpha baked in: the near and far layers of flat routed trace,
// and the brighter pool of it behind a title. The site's --trace-2,
// --trace-3 and --trace-hero, one for one.
ICoreRgba traceNear;
ICoreRgba traceFar;
ICoreRgba traceHero;
// A brand object's ground, its hover lift, and the ink that sits on it.
ICoreRgba objectGround;
ICoreRgba objectGroundLifted;
ICoreRgba objectInk;
ICoreRgba objectInkMuted;
// The object's own accent: the wash its buttons lift toward, and the
// colour an informational notice is marked in.
ICoreRgba objectAccent;
// The rim light's two colours: a near-white where the light lands, and the
// colour it cools to as it runs round the shape and fades out.
ICoreRgba rimLight;
ICoreRgba rimGlint;
// The severity a notice is marked in. Semantic rather than brand, but read
// by brand objects, so they sit here instead of being fished out of the
// identity by every notice that draws one.
ICoreRgba noticeError;
ICoreRgba noticeWarning;
};
};
ICoreTheme#
ICoreTheme.h:611 · struct · 3 declaration(s)
The resolved theme ------------------------------------------------------
struct ICoreTheme {
public:
std::string name;
bool isDark = false;
ICoreSurfaceTokens surface;
ICoreContentTokens content;
ICoreInteractionTokens interaction;
ICoreButtonTokens button;
ICoreToggleTokens toggle;
ICoreTabTokens tab;
ICoreMenuTokens menu;
ICoreFieldTokens field;
ICoreBlockTokens block;
ICoreLinkTokens link;
// One row per signal-type id, light and dark. Read it through
// signalTypeOf() rather than indexing: an id with no row must resolve to
// something drawable, not to a default-constructed (invalid, black) token
// set, and a stale project file is exactly how an unknown id arrives.
std::map<std::string, ICoreSignalTypeTokens> signalType;
// Not keyed by type: see the struct.
ICoreSignalIncompatibleTokens signalIncompatible;
ICoreCanvasAreaTokens canvasArea;
ICoreTimeLineTokens timeline;
ICoreChartTokens chart;
ICoreInfoTokens info;
ICoreSyntaxTokens syntax;
ICoreTypographyTokens type;
// The stops of the named gradients (ICoreThemeGradients). Last on purpose:
// appended rather than inserted, so no earlier member moves.
ICoreBrandTokens brand;
// The signal-type row for an id, or the ICoreDouble row when the id has
// none. Never returns an invalid token set: an unknown id is a stale file
// or a type this build does not carry, and neither is a reason to paint a
// port black.
const ICoreSignalTypeTokens& signalTypeOf(const std::string& id) const;
static ICoreTheme deriveLight(const ICoreVisualIdentity& id);
static ICoreTheme deriveDark(const ICoreVisualIdentity& id);
};
};
File-scope declarations#
// ---- Signal types: what a port's TYPE looks like ---------------------------
//
// A port carries a typed signal, and the type is visible: the port and every
// link that carries it take the type's colour, and the badge beside the port
// spells it. This is the styling half of that; the language-neutral facts of a
// type (its width, its family, its zero literal) live in the model layer, which
enum class ICoreWireStroke {
Solid, // every numeric kind
DashDot, // String
Double // Bus -- two parallel lines, as Simulink draws one
};
ICoreThemeBinding.h#
ICoreEssentials/Theme/ICoreThemeBinding.h
ICoreThemeBinding, on the path every backend can compile.
⚠⚠ WHAT THIS FIXES, AND IT WAS THE ROW'S ONE GENUINELY UNOWNED ARCHITECTURAL ITEM.
The CMake switch drops every Theme/<name>Binding/ from the build and adds back exactly one. But callers included the binding BY PATH, and the paths were hard-coded: 140 files named Theme/QtBinding/ICoreThemeBinding.h, 100 of them in src/ICoreSDK. So on an appkit build, a hundred SDK translation units took the QT header's declarations and linked against the APPKIT binding's definitions.
⚠ NOTHING FAILED TO COMPILE, WHICH IS WHY IT SURVIVED SO LONG. Both headers declare a class called ICoreThemeBinding with the same member names, so
Declares no class of its own — see the file.
ICoreThemeInk.h#
ICoreEssentials/Theme/ICoreThemeInk.h
The styling module's colour ARITHMETIC -- the half of the theme binding that never needed a toolkit.
Two functions, both pure: an alpha swap and a contrast measurement. Neither names a toolkit type, neither touches a widget, and neither has any reason to be compiled once per UI backend. They live here so that exactly one implementation answers for every backend.
WHY THIS FILE EXISTS AT ALL
Both of these were members of ICoreThemeBinding, and that class is toolkit-bound by definition: a subscription that severs when a native view dies has to name the native view. When a second backend needs the same two answers, a class like that gives you two bad choices -- duplicate the WCAG
Declares no class of its own — see the file.
ICoreThemeManager.h#
ICoreEssentials/Theme/ICoreThemeManager.h
ICoreThemeManager#
ICoreThemeManager.h:46 · class · pImpl · 7 declaration(s)
I-Core Theme Manager — Layer 2 of the styling module.
class ICoreThemeManager {
public:
static ICoreThemeManager& instance();
// The ACTIVE theme's name ("Light"/"Dark") -- this manager's own state,
// not the user's stored preference. Until D4 this read
// ICoreUserPreferences::getEffectiveTheme(); the two agree from startup on
// because Initialization.cpp applies the preference via setActiveTheme()
// and re-applies it on every preference/system-appearance change. Asking
// the styling module what theme is ACTIVE is an SDK question; asking what
// the user CHOSE is an app question, and now lives with the app
// (ESSENTIALS_INDEPENDENCE D4 -- initializeTheme() moved to
// Initialization.cpp whole for the same reason).
static std::string getThemeName();
// The active theme's tokens — the one access point for all styling.
static const ICoreTheme& theme();
static const ICoreVisualIdentity& identity();
// Runtime theme switch ("Light" / "Dark"). Fires onThemeChanged.
//
// std::string, not ICoreString: ICoreString forwards to QString, so taking
// one here would reach Qt through the back door. This was caught by
// theme_qt_free_selftest rather than by the boundary scan — the scan sees
// no Qt token, and the type resolved anyway in normal builds because pch.h
// delivers it. That is the whole reason the selftest target exists.
void setActiveTheme(const std::string& themeName);
// The theme-change notification. Fired from the single site in
// setActiveTheme() — there is exactly one, which is what made retiring the
// old Qt signal safe: a mirror wired at only some emit sites is the failure
// mode that leaves subscribers silently un-notified.
//
// Subscribers that are a widget or a scene item should go through
// ICoreThemeFollow (UI/System/ICoreThemeFollow.h) rather than calling
// connect() here: it ties the subscription to that object's destruction,
// which is what the old three-argument connect did.
// Subscribers that own an ICoreSignalScope can connect to this directly.
ICoreSignal<>& onThemeChanged();
// An app's own adjustments to the derived tokens, run on every derivation --
// each setActiveTheme() and the startup default -- after the Light/Dark
// theme is built from the identity and before anyone is told. This is how an
// app gives ITS theme a look the SDK's defaults do not have, without
// changing those defaults for every other consumer of the platform SDK.
// The theme passed in carries `name` and `isDark`, so one customizer can
// answer each theme differently.
//
// Installing one re-derives the active theme at once but does NOT fire
// onThemeChanged (the one emit site stays setActiveTheme()), so install it
// before the first setActiveTheme() the app makes. An empty function removes it.
using ThemeCustomizer = std::function<void(ICoreTheme&)>;
void setThemeCustomizer(ThemeCustomizer customizer);
// The "Customize UI Performance" forwards to ICoreUserPreferences that
// stood here are gone (ESSENTIALS_INDEPENDENCE D4). The rendering-budget
// flags are ICoreRenderingPolicy's (UI/System/), which the app's
// preferences feed; the two link-path flags were never styling and their
// single callers each read ICoreUserPreferences directly now.
private:
class Impl; // the two-line residue; state lives here
std::unique_ptr<Impl> impl;
};
ICoreThemeStyleSpecs.h#
ICoreEssentials/Theme/ICoreThemeStyleSpecs.h
ICoreThemeStyleSpecs#
ICoreThemeStyleSpecs.h:34 · class · 32 declaration(s)
ICoreThemeStyleSpecs -- the theme's surfaces, as specs rather than as text.
class ICoreThemeStyleSpecs {
public:
ICoreThemeStyleSpecs() = delete;
// Peer of ICoreThemeQss::getStyle_Label().
//
// Today's text is "background-color: transparent; color: rgb(...)", and
// the transparent half is load-bearing: a label that inherits a ground
// instead of having none shows as a coloured rectangle over a themed
// panel. It is spelled here as a VALID ICoreRgba with alpha 0, which is
// how a spec distinguishes "explicitly none" from "not mentioned".
static ICoreStyleSpec label(const ICoreTheme& theme);
// Peer of ICoreThemeQss::getStyle_PanelDialog().
//
// Today's text is a whole rule -- "QDialog { background-color: rgb(...); }"
// -- but that is a SCOPING device, not a toolkit-owned surface: the sheet
// is set on the dialog the app constructed, and `QDialog` stops the rule
// cascading to its children. So this is Self, and the caller passes the
// selector to rulesFor(). See the ⚠ under ICoreStylePart.
static ICoreStyleSpec panelDialog(const ICoreTheme& theme);
// ---- the plain-ground surfaces --------------------------------------
//
// Each is one Self rule whose only instruction is a ground, and in three
// cases a border or radius with it. They are grouped because they are
// genuinely the same shape, not to save typing: a backend that can draw
// one of these can draw all eleven, which is what makes them the cheap
// half of the 43.
static ICoreStyleSpec themePrimaryColor(const ICoreTheme& theme); // surface.panel
static ICoreStyleSpec themeSecondaryColor(const ICoreTheme& theme); // interaction.accentContainer
static ICoreStyleSpec windowRootWidget(const ICoreTheme& theme); // surface.window
static ICoreStyleSpec widget(const ICoreTheme& theme); // panelRaised + border none
static ICoreStyleSpec titleBar(const ICoreTheme& theme); // surface.titleBar
static ICoreStyleSpec leftSideToolBar(const ICoreTheme& theme); // sidebar + radius 10
static ICoreStyleSpec contextMenuBar(const ICoreTheme& theme); // menu.barBackground
static ICoreStyleSpec scrollPane(const ICoreTheme& theme); // panelRaised + border none
static ICoreStyleSpec runBackground(const ICoreTheme& theme); // accentContainer + radius 10
static ICoreStyleSpec studioSurface(const ICoreTheme& theme); // surface.panelRaised
static ICoreStyleSpec tabBar(const ICoreTheme& theme); // tab.barBackground
// ---- surfaces that say more than a ground ---------------------------
static ICoreStyleSpec dividerLine(const ICoreTheme& theme);
static ICoreStyleSpec blockSelection(const ICoreTheme& theme);
static ICoreStyleSpec codeEditorEyebrow(const ICoreTheme& theme);
// ⚠ Carries the field SELECTION pair as well as its own ground and text.
// Today that pair comes from a shared fieldSelectionCss() helper, and the
// reason it is shared is worth preserving into the spec world: a field
// whose selection does not match the field beside it is the exact fault
// that helper was introduced to retire.
static ICoreStyleSpec directoryTextField(const ICoreTheme& theme);
// ⚠ Today spells its border as LONGHAND (border-width / border-color /
// border-style) rather than the shorthand every other site uses. The spec
// has one border either way, so this is another spelling the renderer
// normalises -- see the byte-compatibility note on ICoreStyleSpecQss.
static ICoreStyleSpec blockFrame(const ICoreTheme& theme);
// ---- the toolkit's own right-click popup -----------------------------
//
// ⚠ The single most important spec on this list for a native backend.
// Qt renders it from five stylesheet rules (QMenu, ::item, ::item:selected,
// ::item:disabled, ::separator); AppKit has an NSMenu that takes no
// stylesheet at all, so this is the surface that MUST be answered with
// native calls rather than interpreted.
//
// It is not optional on any pane whose text is selectable: setting ANY
// sheet hands the subtree to Qt's stylesheet engine, and a QMenu the
// engine styles with no background rule is drawn with NO GROUND AT ALL,
// floating over whatever is behind the window.
static ICoreStyleSpec toolkitContextMenu(const ICoreTheme& theme);
// ---- read-only text panes -------------------------------------------
static ICoreStyleSpec gitReadOnlyPane(const ICoreTheme& theme);
static ICoreStyleSpec verificationReadOnlyPane(const ICoreTheme& theme);
static ICoreStyleSpec scriptOutputPane(const ICoreTheme& theme);
// ⚠ Carries the context menu too, for the reason above: its text is
// selectable, so Qt builds a Copy/Select All popup for it.
static ICoreStyleSpec codeSurface(const ICoreTheme& theme);
// The rich-text code surface, which is a SECOND function rather than a
// call to codeSurface() for the same reason its shim is: the script editor
// sits on the rich text widget, and its selection pair is the accent one
// rather than the field one. Folding them would be a visual change.
static ICoreStyleSpec scriptCodeSurface(const ICoreTheme& theme);
// A read-only cell inside an ICoreTable.
//
// ⚠ Its transparent background is LOAD-BEARING, not a default. The table
// sets a panelRaised fill on ITSELF, and a Qt type selector reaches every
// subclass in the child tree -- so without an explicit transparent here
// that fill lands on the cell and covers the rounded ground the field
// paints for itself. The shim documents the whole incident; the spec keeps
// the declaration because losing it is what caused it.
static ICoreStyleSpec readOnlyCellField(const ICoreTheme& theme);
// ⚠ Self ONLY, deliberately. The shim also carries a descendant `QLabel`
// rule, and that rule is a Qt CASCADE ARTIFACT rather than part of this
// surface: on a native backend the labels inside the container are
// separate widgets that get label() in their own right. Modelling it here
// would bake Qt's inheritance into a toolkit-neutral type.
static ICoreStyleSpec verificationReportContainer(const ICoreTheme& theme);
// A script explorer's list. The first spec in the tree to use
// ICoreStyleState::Selected, which exists because an item view's row
// highlight is not a checkbox tick -- see the enum's note.
static ICoreStyleSpec scriptExplorerList(const ICoreTheme& theme);
// ---- the shared field chrome, and the three fields built on it -------
//
// Today these go through a bareFieldSheet() helper that wraps a body in
// a QLineEdit rule, a :disabled rule and the toolkit popup. In spec terms
// that is exactly Self/Disabled + ContextMenu, so `fieldChrome` carries
// those and each field overlays its own Self/Normal onto it.
//
// ⚠ The disabled colour is not decoration. Without an explicit enabled
// colour these fields fall back to the application palette, which is
// forced to black at startup and does not follow the theme -- that is how
// typed text stayed black on the dark field, twice.
static ICoreStyleSpec fieldChrome(const ICoreTheme& theme);
static ICoreStyleSpec navBarField(const ICoreTheme& theme);
static ICoreStyleSpec searchBarField(const ICoreTheme& theme);
static ICoreStyleSpec commandBarField(const ICoreTheme& theme);
// ---- containers and the splitter -------------------------------------
static ICoreStyleSpec infoLabelContainer(const ICoreTheme& theme);
// ⚠ Two surfaces, and only the second is toolkit-owned: the QSplitter is
// the widget the app made, while `QSplitter::handle` is the grab bar Qt
// draws between panes, in three states. AppKit's NSSplitView draws its own
// divider and takes no styling, which is why A2.6 proposes a painted
// divider -- this spec is what that painted lane has to reproduce.
static ICoreStyleSpec splitPane(const ICoreTheme& theme);
// ---- the overlay scrollbar -------------------------------------------
//
// Peer of ICoreStyles::overlayScrollBarsQss(), which is NOT a getStyle_*
// shim but is exactly their kind: one central helper whose text every
// scrolling surface in the product pastes onto its own sheet.
//
// ⚠ TWO PARTS, and the split is the whole surface. The trough is
// EXPLICITLY transparent and the thumb carries the only ink -- that is
// what makes this an overlay bar rather than a channel with a slider in
// it. A spec that put the wash on ScrollBar would fill the groove end to
// end; see the ⚠ on ICoreStylePart::ScrollBar, which was written after
// this surface nearly shipped that way.
//
// ⚠ NOTHING HERE IS A SIZE. The 6px thickness, the 20px minimum thumb and
// the zeroed stepper arrows in today's text are all GEOMETRY, and they
// belong to ICoreScrollCore / ICoreScrollBarSpec rather than to a style
// spec -- the same split splitPane() makes for the handle's thickness.
// The arrows in particular are Qt being told not to draw a stepper it
// would otherwise draw: this tree's bar has THREE regions, not five
// (ICoreScrollCore.h), so a backend with no default stepper has nothing
// to suppress and must not model a suppression.
//
// ⚠ Axis-neutral, unlike the text. Today's sheet writes every rule twice,
// once for :vertical and once for :horizontal, and the ONLY difference
// between the two copies is the width/height -- which is the geometry
// this does not carry. One spec dresses both bars.
static ICoreStyleSpec scrollBar(const ICoreTheme& theme);
// The same bar in the wash a caller names, and WITHOUT a hover rule.
//
// ⚠ This is not a convenience overload, it is a second LOOK that three
// panes actually wear: overlayScrollBarsQss() has an overload taking the
// handle's colour, and its text has no :hover rule at all, so those bars
// do not lift under the pointer. Read a missing Hover as "this surface
// does not change on hover", which is what a backend composing Normal
// then state already does.
//
// The overload's `includeHorizontal` flag has no peer here on purpose: it
// selects which AXES get rules, and an axis-neutral spec has nothing to
// say about that. A caller that scrolls one way styles one bar because it
// only has one, not because the ink differs.
static ICoreStyleSpec scrollBar(const ICoreRgba& handleWash);
// ---- the two trees, and the vocabulary they needed -------------------
//
// These are the shims two holders of this row stopped in front of, and the
// owner decided the vocabulary on 2026-08-21: typography on the rule,
// three branch parts carrying an image, and SelectedHover /
// SelectedInactive as states. Read ICoreStylePart and ICoreStyleState --
// each new enumerator states which surface forced it.
//
// ⚠ THE DIAGNOSIS TREE IS THE ONE THAT SETS selectionSpansRow, AND IT
// DELIBERATELY HAS NO Branch/Selected RULE. Its own sheet says why: the
// flag already carries the wash across the indent column, so a second rule
// over the same strip stacks its alpha into a darker bar down the left
// edge. Setting both is a defect, and a spec that carried only one of the
// two facts would invite it.
static ICoreStyleSpec diagnosisTree(const ICoreTheme& theme);
// ⚠ THE NAVIGATOR ROW IS THE LEFT PANEL'S BUTTON, SPELLED A SECOND TIME.
// A navigator row and a menu button are both "a thing you can open", so
// they read the same -- an accent wash over the resting surface, an accent
// hairline around it, and an accent edge on the left, all on the SAME
// ICoreButtonTokens the button paints from. Do not put local constants
// here; a missing number belongs in ICoreButtonTokens.
//
// ⚠ THE BORDER IS DECLARED AT REST, TRANSPARENT, and that is not a
// formality: a border takes space, so one that only appears on hover moves
// the row's text sideways under the pointer.
//
// ⚠ What the Qt lane cannot carry, and neither can this: the button's fade
// (button.fadeMs) and the accent edge's rounded, vertically-inset pill --
// border-left is full height and square. A painted backend is free to draw
// the pill; this spec says the colour and the width, which is what both
// lanes agree on.
static ICoreStyleSpec navigatorTree(const ICoreTheme& theme);
// ---- the code editor's chrome: THREE surfaces, three specs -----------
//
// getStyle_CodeEditorChrome() emits one sheet with three `#id` rules in
// it, and the owner's decision (2026-08-21) is that this becomes three
// specs rather than a named-sub-surface concept in ICoreStyleSpec.
//
// ⚠ THE REASON IS THE HEADER'S OWN TEST FOR A PART: did the app construct
// this widget, or did the toolkit hand it back? The app constructs all
// three. An `#id` is a SCOPING device exactly as `QDialog` is in
// panelDialog() -- modelling it as ownership is the mistake the ⚠ under
// ICoreStylePart was written to prevent. Each of these is a Self rule and
// the caller passes its object name to rulesFor().
// ⚠ THESE THREE ARE RETIRED, NOT MISSING (W0.12). They were declared here
// and defined nowhere after the 2026-08-22 merge, which kept this header
// from the Windows board and the .cpp from the AppKit one. Origin's
// codeEditorChrome() below consolidates all three -- the ground, the strip
// and the badge in one spec -- so the right repair is to drop the
// declarations rather than resurrect bodies that would then disagree with
// it. An undefined declaration in a public header is inert until the first
// caller and then a link error with nothing pointing at the merge.
// The ground the tool bar sits on, and the line under it. Public because
// the Qt shim has published them as ICoreThemeQss::codeEditorBarFill/Edge
// since before the spec existed and other sheets read them; these are the
// Qt-free peers, and the asymmetry between the themes is documented at
// length on the Qt originals.
static ICoreRgba codeEditorBarFill(const ICoreTheme& theme);
static ICoreRgba codeEditorBarEdge(const ICoreTheme& theme);
// ---- the toolkit's own modal prompt ----------------------------------
//
// ⚠ THE SECOND MOST IMPORTANT SPEC ON THIS LIST FOR A NATIVE BACKEND,
// after toolkitContextMenu(). Qt renders it as ten rules scoped to
// QMessageBox/QInputDialog, reaching a label, a field and buttons the
// TOOLKIT built and never handed back. AppKit has NSAlert, which lays out
// its own buttons and takes no stylesheet; WinUI has ContentDialog, which
// does take Styles. Both must SEE that they were asked -- which is what
// the four Dialog* parts are for.
//
// ⚠ THE ACTION BUTTON IS PICKED OUT BY OBJECT NAME, NOT BY `:default`, and
// that was a real defect until 2026-08-15: on a destructive prompt the
// default is deliberately the SAFE button, so an accent hung on `:default`
// marks the button that does nothing. Never merge the two.
//
// ⚠ THE DESTRUCTIVE VARIANT IS NOT MODELLED HERE. The shim styles
// `#icoreDestructiveAction` as well, and that is a per-PROMPT choice
// rather than a property of the surface -- a spec function taking no
// argument cannot say "this prompt deletes something". It stays in the
// shim until a caller needs it as a spec, and this sentence is so that
// absence reads as a decision rather than an oversight.
// RENDER IT ONCE PER CLASS, NOT ONCE WITH A COMMA. The shim scopes every
// selector to `QMessageBox` AND `QInputDialog`, and the obvious way to
// reproduce that -- rulesFor(spec, "QMessageBox, QInputDialog") -- is
// silently wrong: a comma binds tighter than a descendant space, so the
// label rule comes out as `QMessageBox, QInputDialog QLabel`, which paints
// the label's treatment onto EVERY MESSAGE BOX and reaches only the input
// dialog's labels. Two calls, one selector each:
//
// rulesFor(modalPrompt(t), "QMessageBox")
// + rulesFor(modalPrompt(t), "QInputDialog")
//
// Measured on the rendered text, 2026-08-21, not reasoned from the grammar.
static ICoreStyleSpec modalPrompt(const ICoreTheme& theme);
// ⚠ NOT the same as toolkitContextMenu(). This is the lighter sheet the
// app sets on its OWN menus: ground, text and row padding, plus the
// selected row -- no border, no radius, no disabled rule, no separator.
// Keeping them separate matters because the toolkit's popup needs the full
// set or it renders with no ground at all.
static ICoreStyleSpec contextMenu(const ICoreTheme& theme);
// The code editor's chrome: the ground it sits on, the strip across its
// top, and the language badge on that strip.
static ICoreStyleSpec codeEditorChrome(const ICoreTheme& theme,
const ICoreRgba& barFill,
const ICoreRgba& barEdge);
// The overlay scroll bars, as a FRAGMENT rather than a surface.
//
// ⚠ IT DESCRIBES NO SURFACE OF ITS OWN and carries no Self rule: it is two
// parts -- a trough and a knob, the knob in two states -- meant to be
// folded into a scrolling surface's spec with ICoreStyleSpec::withPartsOf.
// That is what keeps ONE description of the tree's bars while many surfaces
// wear them (task C8's property, in the record instead of in a string).
//
// ⚠ THE VALUES ARE THE ONES THE STRING BUILDER ALREADY USED -- handle on
// text-tertiary at alpha 180, lifting to text-secondary at 200 under the
// pointer, 6 thick, 3 radius, 20 minimum length. They are not new design;
// they are the same numbers, said in the record rather than in CSS, which
// is why the emitted stylesheet is byte-identical.
static ICoreStyleSpec overlayScrollBars(const ICoreTheme& theme);
// The same bars with the knob's wash named by the caller, and NO hover
// rule -- which is a different shape rather than the same one with a field
// missing.
//
// ⚠⚠ THE TREE HAS EXACTLY TWO OVERLAY-BAR SHAPES AND THIS IS THE SECOND.
// Read at the builder rather than assumed from the name: the theme-driven
// form emits a hover rule AND a scroll-area corner rule; this one emits
// neither. They are not a general form and a special case, they are two
// texts, and the record distinguishes them by whether a ScrollBarHandle
// HOVER rule is present. A caller that wanted the corner without the hover,
// or the hover without the corner, could not say so -- no call site has
// ever wanted either, and when one does that is a field, counted.
static ICoreStyleSpec overlayScrollBars(const ICoreTheme& theme,
const ICoreRgba& handleWash);
};
};
ICoreThemeSurfaceInk.h#
ICoreEssentials/Theme/ICoreThemeSurfaceInk.h
Declares no class of its own — see the file.
ICoreVisualIdentity.h#
ICoreEssentials/Theme/ICoreVisualIdentity.h
ICoreVisualIdentity#
ICoreVisualIdentity.h:25 · class · 19 declaration(s)
I-Core Visual Identity — Layer 0 of the styling module.
class ICoreVisualIdentity {
public:
static const ICoreVisualIdentity& get();
// Rebuilds the shared identity from the shipped values with the user's
// appearance preferences folded in: the UI font family (empty = platform
// default), a density scale over the type/spacing primitives, and a motion
// scale over the durations below. Always starts from the pristine values,
// so calling it repeatedly cannot compound its own scaling.
//
// This only changes the primitives. ICoreThemeManager has to re-derive its
// tokens afterwards for anything on screen to move — ICoreUserPreferences
// does both together in applyAppearanceToStylingModule().
// sansFamily is UTF-8. std::string rather than ICoreString because
// ICoreString forwards to QString, and this header has to stay free of Qt
// — both callers already hold a std::string and were wrapping it.
static void applyUserOverrides(const std::string& sansFamily, int densityPercent, int motionPercent);
// ------------------------------------------------------------------
// Neutral ramp — "G" scale. Cool, navy-tinted grays.
// g50..g950 come straight from the identity sheet's color system row;
// the in-between stops (g200/g400/g600/g800) appear throughout the
// sheet's UI examples (borders, secondary text, faint text on dark).
// ------------------------------------------------------------------
ICoreRgba g50 = ICoreRgba("#f2f3f8");
ICoreRgba g100 = ICoreRgba("#d8dae8");
ICoreRgba g200 = ICoreRgba("#c8cad8");
ICoreRgba g300 = ICoreRgba("#a8aac0");
ICoreRgba g400 = ICoreRgba("#8a8ca8");
ICoreRgba g500 = ICoreRgba("#686a80");
ICoreRgba g600 = ICoreRgba("#4a4c68");
ICoreRgba g700 = ICoreRgba("#38394c");
ICoreRgba g800 = ICoreRgba("#2a2d42");
ICoreRgba g850 = ICoreRgba("#1e2030");
ICoreRgba g950 = ICoreRgba("#0d0f18");
// Brightest content color on dark surfaces (wordmark on dark).
ICoreRgba inkOnDark = ICoreRgba("#e8eaf4");
// ------------------------------------------------------------------
// Royal family — the brand accent ("Navy royal").
// ------------------------------------------------------------------
ICoreRgba royal = ICoreRgba("#2850d0"); // primary
ICoreRgba royalDeep = ICoreRgba("#1c38a8"); // deep
ICoreRgba royalBright = ICoreRgba("#6888e8"); // accent text on dark surfaces
ICoreRgba royalTintDark = ICoreRgba("#141828"); // royal-tinted dark surface
ICoreRgba royalTintLight = ICoreRgba("#eceeff"); // royal-tinted light surface
ICoreRgba royalContainer = ICoreRgba("#1c2848"); // filled container on dark
ICoreRgba onRoyal = ICoreRgba("#e8ecff"); // content on royal fills
ICoreRgba onRoyalMuted = ICoreRgba("#aabcf8"); // secondary content on royal
// ------------------------------------------------------------------
// Data / code accents (numeric output, strings in the editor).
// ------------------------------------------------------------------
ICoreRgba dataOrange = ICoreRgba("#e8a87c");
ICoreRgba dataPurple = ICoreRgba("#a898e8");
// ------------------------------------------------------------------
// Plot series palette — the colors a chart hands out to successive line
// paths. [PROPOSED] — not in the identity sheet.
//
// This is the one part of the plot that is deliberately NOT theme-aware.
// A path's color is saved with the model, so a palette that changed with
// the theme would reopen a plot in colors it was never saved in, and a
// figure exported yesterday would not match the one exported today.
//
// The cost of one shared list is that no entry can sit at either end of
// the value range: it has to carry against a white plot area and against
// a near-black one. That is why the royal is lifted here and the two data
// accents are deepened — the shipped tones are tuned for one background
// each, and these have to do both.
// ------------------------------------------------------------------
std::vector<ICoreRgba> plotSeries = {
ICoreRgba("#4a7ce8"), // blue — the royal, lifted to clear a dark plot
ICoreRgba("#d8905c"), // amber — dataOrange, deepened to clear a white one
ICoreRgba("#30a06a"), // green — success, mid-toned as shipped
ICoreRgba("#8878d8"), // violet — dataPurple, deepened likewise
ICoreRgba("#c24a56"), // crimson — danger, mid-toned as shipped
ICoreRgba("#2ba0b8"), // teal
ICoreRgba("#c89628"), // gold
ICoreRgba("#d868a8"), // pink
};
// ------------------------------------------------------------------
// Semantic accents. [PROPOSED] — not in the identity sheet.
// Chosen muted/cool to sit next to the royal family.
// ------------------------------------------------------------------
ICoreRgba success = ICoreRgba("#30a06a");
// Cool, muted crimson rather than a fiery vermillion: this fills Cancel and
// Delete buttons, which should read as "stop" without alarming — and its blue
// side sits with the royal family instead of fighting it. (The old #d04438
// had B < G, pulling it orange, away from the rest of the palette.)
ICoreRgba danger = ICoreRgba("#c24a56");
ICoreRgba warning = ICoreRgba("#e8a87c"); // reuses dataOrange
// ------------------------------------------------------------------
// Typography. Pixel sizes, matching the identity sheet.
// Empty family = platform default UI font; mono resolved by the theme.
// ------------------------------------------------------------------
// UTF-8, std::string for the same reason applyUserOverrides takes one.
std::string fontSansFamily = ""; // system UI font
std::string fontMonoFamily = ""; // system fixed font
int fontSizeDisplay = 36; // hero text, weight 500
int fontSizeHeadline = 26; // wordmark, weight 500
int fontSizeTitle = 22; // titles, weight 500
int fontSizeBody = 16; // body copy, weight 400
int fontSizeControl = 13; // buttons, weight 500
int fontSizeCaption = 12; // captions, weight 400
int fontSizeLabel = 11; // UPPERCASE section labels
int fontSizeMicro = 10; // smallest legible labels
// Letter spacing as a percent of character width, 100 = normal.
// Display/title track tight; labels track wide.
double trackingTight = 97.0; // -3% (display / titles)
double trackingWide = 112.0; // +12% (uppercase labels)
// ------------------------------------------------------------------
// Corner radii.
// ------------------------------------------------------------------
int radiusSmall = 4; // list items, tags
int radiusTab = 5; // tabs, canvas blocks
int radiusControl = 7; // buttons
int radiusField = 8; // inputs, swatches
int radiusPanel = 12; // cards, panels
int radiusLarge = 16; // hero containers, windows
int radiusPill = 9999; // pills / tags
// ------------------------------------------------------------------
// Spacing & strokes.
// ------------------------------------------------------------------
int spacingUnit = 4; // base grid
int controlPaddingV = 7; // button padding (7px 16px)
int controlPaddingH = 16;
int itemPaddingV = 3; // list/tab item padding
int itemPaddingH = 10;
int fieldPaddingV = 3; // input padding (3px 10px); kept
int fieldPaddingH = 10; // low enough not to clip the
// panels that pin a 24-25px field
int fieldMinHeight = 28; // inputs, when no layout pins one
double hairline = 0.5; // hairline borders/dividers
// ------------------------------------------------------------------
// Motion. [PROPOSED] standard = existing app behavior (200ms OutCubic).
// ------------------------------------------------------------------
int motionFastMs = 120;
int motionStandardMs = 200;
// Kept as the brand's stated motion curve even though nothing reads it yet
// — it is a value from the identity sheet, not dead plumbing. ICoreEasing
// is the wrapper layer's own enum (ICoreInputEnums.h) and names no Qt type.
ICoreEasing motionEasing = ICoreEasing::OutCubic;
// ------------------------------------------------------------------
// The trace board — the brand's background (since 2026-09-25).
//
// Routed signal traces with pads and junctions: the thing the product
// draws, used as the ground the brand stands on. The public website sits
// on it (ICorePublicFace Website/assets/site.css, "THE BOARD" and
// .page-head) and so does every brand window's rail in the app, through
// ICoreThemeGradients::paintTraceBoard(). These are the site's numbers,
// one for one, so the two surfaces cannot drift: change them here and in
// site.css together, or not at all.
//
// Appended LAST on purpose -- every field above keeps its offset.
// ------------------------------------------------------------------
// Tile sizes in points (the tile is 240 units square). Near and far are
// the two flat layers; 173 against 380 is deliberately not a round ratio,
// so their periods never come back into phase and the tiling never reads
// as wallpaper. The hero tile is the brighter pool behind a title.
double traceTileNear = 380.0;
double traceTileFar = 173.0;
double traceTileHero = 300.0;
double traceFarOffsetX = 29.0; // the far layer's mask-position
double traceFarOffsetY = 61.0;
double traceStroke = 1.5; // in tile units, scaled with the tile
// The pool's reach, as a multiple of the area's width, and where along it
// the pool has faded out (the site's "transparent 72%").
double traceHeroRadius = 1.5;
double traceHeroFade = 0.72;
// Ink alpha, 0..255, per layer and theme: the site's --trace-2 (near),
// --trace-3 (far) and --trace-hero, which are .030/.022/.26 on light and
// .038/.026/.32 on dark. The inks are royal/royalDeep/royal on light and
// royalBright/onRoyalMuted/royalBright on dark (ICoreTheme.cpp, deriveBrand).
int traceNearAlphaLight = 8;
int traceFarAlphaLight = 6;
int traceHeroAlphaLight = 66;
int traceNearAlphaDark = 10;
int traceFarAlphaDark = 7;
int traceHeroAlphaDark = 82;
};
};
ICoreStyleSpecAppKit.h#
ICoreEssentials/Theme/AppKitBinding/ICoreStyleSpecAppKit.h
ICoreStyleSpecAppKit -- the AppKit backend's answer to an ICoreStyleSpec.
The Qt binding renders a spec to QSS TEXT, because Qt's styling architecture is a stylesheet. AppKit has no such thing: there is no text to hand it, and
setStyleSheethas no counterpart. So this interpreter does not produce a sheet at all. It answers two questions instead:
- RESOLVED -- for one part in one state, what are the actual numbers to
paint with, after the Normal rule underneath has been inherited from? A painted control asks this in its drawing code and gets a plain struct.
- VERDICT -- for a whole spec, what can this backend actually honour, and
what can it not? Not a bool: three answers, because "cannot apply the sheet" and "loses nothing" are frequently the same surface (§5.1).
ICoreAppKitResolvedStyle#
ICoreStyleSpecAppKit.h:49 · struct · 0 declaration(s)
Everything a painted control needs to draw one part in one state.
struct ICoreAppKitResolvedStyle {
public:
ICoreRgba background;
ICoreRgba foreground;
ICoreRgba borderColor;
double borderWidth = -1.0;
ICoreRgba borderBottomColor;
double borderBottomWidth = -1.0;
double cornerRadius = -1.0;
// A6.4's text trio, carried so icoreApplyStyleSpec can hand it to a control
// that HAS text. `foreground` above is the ink; these are the face.
double fontPixelSize = -1.0;
bool fontBold = false;
// ⚠ std::string, NOT ICoreString, FOR THE REASON THIS WHOLE FILE IS
// BUILDABLE WITHOUT A TOOLKIT. ICoreString forwards to QString, so one
// member of that type here drags Qt in transitively and the standalone
// build this file's own lab documents -- two .cpp files and a C++17
// compiler, no toolkit, no app, no screen -- stops working.
//
// ⚠ IT WAS ICoreString FOR ONE COMMIT AND NOTHING ROUTINE NOTICED. The same
// change that pulled ICoreString into Theme/ proper (ICoreStyleSpec.h) put
// it here too, and the app went on building because a Qt build has Qt on
// the include path anyway. What breaks is only ever noticed by someone
// running the lab's own command:
//
// c++ -std=c++17 -I src src/ICoreEssentials/Theme/ICoreStyleSpec.cpp \
// src/ICoreEssentials/Theme/AppKitBinding/ICoreStyleSpecAppKit.cpp \
// testingLabs/tests/style_spec_appkit/main.cpp -o /tmp/lab
//
// The crossing to ICoreString belongs at the point of USE -- ICoreStyleApply.mm
// hands it to ICoreFont::setFamily -- which is a .mm inside the backend zone
// and allowed to name whatever it likes.
std::string fontFamily;
double paddingTop = -1.0;
double paddingRight = -1.0;
double paddingBottom = -1.0;
double paddingLeft = -1.0;
double minWidth = -1.0;
double minHeight = -1.0;
ICoreRgba selectionBackground;
ICoreRgba selectionForeground;
// False when the spec carries no rule for this part at all, in any state.
// Distinct from "a rule exists and sets nothing": the first means the
// theme has nothing to say about this surface, the second is a deliberate
// reset, and a control that conflates them paints the wrong thing when a
// rule is later added.
bool found = false;
};
};
ICoreAppKitPartVerdict#
ICoreStyleSpecAppKit.h:127 · struct · 0 declaration(s)
struct ICoreAppKitPartVerdict {
public:
ICoreStylePart part = ICoreStylePart::Self;
ICoreStyleState state = ICoreStyleState::Normal;
ICoreAppKitStyleFidelity fidelity = ICoreAppKitStyleFidelity::Applied;
// Why, in a sentence, for the Unrepresentable and Native cases. Empty for
// Applied, which needs no explanation.
const char* reason = "";
};
};
ICoreStyleSpecAppKit#
ICoreStyleSpecAppKit.h:152 · class · 3 declaration(s)
class ICoreStyleSpecAppKit {
public:
ICoreStyleSpecAppKit() = delete;
// The numbers for one part in one state, with Normal folded in underneath.
static ICoreAppKitResolvedStyle resolve(const ICoreStyleSpec& spec,
ICoreStylePart part,
ICoreStyleState state);
// Self/Normal, which is what most callers want.
static ICoreAppKitResolvedStyle resolveSelf(const ICoreStyleSpec& spec);
// One verdict per rule in the spec, in the spec's own order.
static std::vector<ICoreAppKitPartVerdict> verdictsFor(const ICoreStyleSpec& spec,
ICoreAppKitStyleSurface surface);
// True when any rule comes back Unrepresentable for this surface -- i.e.
// when applying this spec here LOSES something a developer should know
// about. ⚠ NOT the same as ICoreStyleSpec::touchesToolkitOwnedParts(),
// which asks about the spec alone and answers the same way for a painted
// menu and a system one.
static bool losesFidelity(const ICoreStyleSpec& spec, ICoreAppKitStyleSurface surface);
};
};
File-scope declarations#
// How faithfully this backend can honour one part.
//
// ⚠ THREE, NOT TWO, and the middle one is the reason. Found while seating the
// real system menu bar: `::item:selected` and
// `::item:disabled` are NSMenu's OWN highlight and disabled drawing, so the
// rule is not applied and nothing is lost -- reporting that as "dropped"
enum class ICoreAppKitStyleFidelity : int {
// The app built this surface and paints it: every value is honoured.
Applied = 0,
// The toolkit draws this itself, in a way that already matches what the
// rule asks for. The rule is not applied and the appearance is right.
Native,
// The toolkit owns this surface and does NOT reproduce what the rule asks
// for. This is the one that needs a decision from a human: either accept
// the platform's appearance, or replace the control with a painted one.
Unrepresentable,
};
// Where the spec is being applied. The SAME spec gets different verdicts
// depending on who is asking, which is `...-df`'s point and the reason this is
// not a property of the spec alone: this tree keeps a PAINTED `ICoreMenu` for
// context menus, which takes every rule happily, while the system menu bar is
// a real NSMenu that takes none of them. A per-spec query would have to give
// one answer to two call sites with opposite needs.
enum class ICoreAppKitStyleSurface : int {
// A view the app draws itself. Everything is Applied.
PaintedControl = 0,
// An object the toolkit created and draws: the system menu bar, an alert,
// a table's header, a scroller, a split view's divider.
ToolkitObject,
};
ICoreThemeBinding.h#
ICoreEssentials/Theme/AppKitBinding/ICoreThemeBinding.h
ICoreThemeBinding#
ICoreThemeBinding.h:72 · class · 12 declaration(s)
ICoreThemeBinding -- attaches native views to the active theme.
class ICoreThemeBinding {
public:
ICoreThemeBinding() = delete;
// Both forward to Theme/ICoreThemeInk.h -- the SAME implementation the Qt
// binding forwards to, not a copy of it. See that header for why the
// arithmetic does not live in either backend.
//
// ⚠ DECLARED HERE, DEFINED IN THE .cpp, even though each body is a single
// return. A one-line forward in a header is still a body, and R4.2 of the
// census counts it -- correctly: the header-surface rule is about what a
// header CONTAINS, not about how much work the body does. The Qt peer
// declares these the same way for the same reason.
static ICoreColor withAlpha(ICoreColor c, int alpha);
static ICoreColor inkOn(const ICoreColor& surface);
// Run `apply` now, and again on every theme change until `view` dies.
//
// The lifetime is the view's ICoreSignalScope and nothing else. That scope
// is severed by ~ICoreAppKitView as its FIRST statement, before any field
// is torn down, so a slot capturing the view cannot fire into a
// half-destroyed one.
//
// ⚠ CAPTURE THE VIEW, NOT A POINTER YOU OWN. The subscription outlives the
// call and is severed by the view's destructor; anything else the closure
// captures must outlive the view, because nothing here can sever on ITS
// death.
template <typename ApplyFn>
static void subscribe(const ICoreAppKitView& view, ApplyFn apply) {
apply();
ICoreThemeManager::instance().onThemeChanged().connect(
view.subscriptionScope(), std::move(apply));
}
// Repaint now, and on every theme change until `view` dies -- for a view
// that reads tokens inside its paint hook and needs nothing but an update.
static void repaintOnThemeChange(ICoreAppKitView& view);
// ------------------------------------------------------------------
// The wrapper-facing pair. A converted wrapper holds an ICoreNativeWidget,
// not a seat, so `subscribe(this, ...)` from inside one lands here -- the
// same spelling the Qt peer's ICoreNativeWidget* overloads serve.
//
// ⚠ A NULL WRAPPER IS A NO-OP, NOT A CRASH, AND apply IS STILL RUN ONCE.
// The unwrap answers null for a null wrapper, and the two shapes differ in
// what they can promise: the immediate apply() does not need a view and
// happens either way, while the SUBSCRIPTION needs something to die with
// and cannot be made. A caller that passes null therefore gets a widget
// styled once and never again -- which is why null is worth stating here
// rather than being absorbed silently.
template <typename ApplyFn>
static void subscribe(ICoreNativeWidget* owner, ApplyFn apply) {
if (const ICoreAppKitView* view = icoreAppKitView(owner)) {
subscribe(*view, std::move(apply));
return;
}
apply();
}
static void repaintOnThemeChange(ICoreNativeWidget* widget);
// ------------------------------------------------------------------
// ⚠⚠ THE ITEM PAIR, WHICH THIS HEADER RECORDED AS OWED AND NAMED THE
// CONDITION FOR: *"There is no scene tier on THIS backend yet, so what a
// native item resolves to is A4.x's decision and not a theme row's -- an
// overload written now would be a guess with a signature."* There is a
// scene tier now (A4.1/A4.2), and what a native item resolves to is an
// `ICoreAppKitSceneNode`. So this is that decision, and the guess the note
// refused is no longer a guess.
//
// ⚠ THE LIFETIME IS THE NODE'S, AND THE SCOPE LIVES BESIDE THE NODE RATHER
// THAN IN IT. `icoreAppKitSceneNodeScope()` hands out a scope severed when
// the item's Impl forgets its node -- a registry keyed on the node address,
// because `ICoreAppKitSceneNode` is a plain struct compiled into every TU
// of the item tier and a member added to it changes its LAYOUT. Every
// node-owning Impl calls `icoreAppKitSceneNodeForget()` from its
// destructor; that is the rule the scope rests on.
//
// ⚠ NULL, AND AN ITEM THAT IS NOT A SCENE ITEM, ARE NO-OPS THAT STILL RUN
// apply() ONCE -- the same shape and the same reason as the widget pair
// above. A caller passing one gets an item styled once and never again.
//
// ⚠ ICoreNativeWidget AND ICoreNativeItem ARE DISJOINT (no class in this
// tree implements both), so this overload is never ambiguous with the
// widget one -- the same check the Qt peer's note records making.
template <typename ApplyFn>
static void subscribe(ICoreNativeItem* owner, ApplyFn apply) {
apply();
if (const ICoreSignalScope* scope =
icoreAppKitSceneNodeScope(icoreAppKitSceneNode(owner))) {
ICoreThemeManager::instance().onThemeChanged().connect(*scope, std::move(apply));
}
}
// Mark the item dirty now and on every theme change until it dies -- for an
// item that reads tokens inside its paint hook and needs nothing but a
// repaint. The item twin of the view flavour above.
//
// ⚠ IT IS A REPAINT **AND** A DAMAGE, WHICH ON THIS BACKEND ARE TWO STEPS.
// The scene core records WHAT is dirty and cannot announce it -- it is
// data, and carries no callback of any kind -- so the pump has to be asked
// separately. An implementation that only marked would recolour in memory
// and leave the old paint on screen, which is the exact defect the rect
// seat shipped and its suite caught.
static void repaintOnThemeChange(ICoreNativeItem* item);
// ------------------------------------------------------------------
// ⚠⚠ THE SHADOW-PREFERENCE TRIO (A8.1). These three were the last members
// this header listed as owed, and the bullet at the top names the exact
// condition it was waiting on: the effects tier. A7.1 seated it on
// CALayer, so the wait is over -- and the three do NOT all become the same
// kind of member when it ends, which is the thing worth reading before
// copying one.
//
// ⚠ THE WIDGET OVERLOAD IS REAL WORK ON THIS BACKEND. Its one caller,
// ICoreBlockViewFrameRenderer, is a widget-tier object; a view HAS a
// layer, so a shadow really is installed, really is removed when the user
// turns canvas shadows off, and really does follow the preference for as
// long as the view lives.
//
// ⚠⚠ THE TWO ITEM OVERLOADS ARE STRUCTURALLY IDENTICAL AND CURRENTLY
// ACHIEVE NOTHING, AND THAT IS STATED HERE RATHER THAN HIDDEN IN THEM.
// A scene item on this backend has no layer, so nothing can carry a
// shadow: `ICoreDropShadow::installOn(ICoreNativeItem*)` returns null and
// says why in its own body, and `ICoreGraphicsObject::hasGraphicsEffect()`
// returns false and argues that false is the true answer. Both of those
// decisions predate this file and neither is a stub.
//
// So the bodies below are written as the SAME decision the Qt peer makes,
// expressed through the same wrapper calls, and they inherit their
// no-op-ness from the one seat that already declares it. What this
// deliberately is NOT is an empty body: the port board's §0.48 spends a
// whole section on the shape where a member links, does nothing, and has
// nothing anywhere to say so -- `becomeTopLevelWindow` shipped that way
// under a comment claiming the opposite. (⚠ The board is named in the
// .cpp, not here: gen_api.py publishes a header's doc-comments to
// docs/generated/, so a board FILENAME in a header trips D15 for whoever
// next rebuilds the docs, never for the author -- the same reason
// ICoreStyleSpecQss.cpp carries its board reference in the .cpp.) Here the "does nothing" lives in
// exactly one place, is greppable, and dissolves with no edit to this file
// the day an item can hold an effect.
//
// ⚠ AND THEY ARE NOT LEFT UNDEFINED, WHICH IS THE OTHER CANDIDATE. A link
// error is the honest failure for a seat nobody has decided about; these
// are decided. Both are reached from src/ICoreSDK, so leaving them out
// means the application never links at all -- and A8.1's gate is exactly
// that link.
static void followCanvasShadowPreference(ICoreNativeWidget* target);
static void followCanvasShadowPreference(ICoreNativeItem* target);
static void followFloatingPanelShadowPreference(ICoreNativeItem* target);
};
};
ICoreStyleSpecGtkCss.h#
ICoreEssentials/Theme/Gtk4Binding/ICoreStyleSpecGtkCss.h
ICoreStyleSpecGtkCss#
ICoreStyleSpecGtkCss.h:70 · class · 5 declaration(s)
ICoreStyleSpecGtkCss -- the GTK4 interpreter for ICoreStyleSpec.
class ICoreStyleSpecGtkCss {
public:
ICoreStyleSpecGtkCss() = delete;
// A bare rule BODY -- "background-color: ...; color: ..." -- built from the
// spec's Self/Normal rule only. No selector, no braces, no trailing
// semicolon, so it composes the way its Qt peer's declarationsFor() does.
//
// ⚠ A BARE BODY IS NOT LOADABLE CSS ON EITHER ENGINE, and on GTK it is not
// loadable at all: `gtk_css_provider_load_from_string` wants rules. Qt
// accepts a body because setStyleSheet() wraps it. Whoever calls this must
// wrap it in a selector -- which on this backend is the seat's job, and the
// seat has a per-widget class to wrap it in.
//
// Parts other than Self are IGNORED here, because a body has nowhere to put
// them. Use rulesFor() if the spec has any.
[[nodiscard]] static std::string declarationsFor(const ICoreStyleSpec& spec);
// WHOLE rules for every rule in the spec, each wrapped in the selector its
// part maps to, with `selfSelector` used for ICoreStylePart::Self (pass
// e.g. ".icore-s7"). States become GTK pseudo-classes.
//
// Emits nothing for a rule whose every field is unset, rather than an empty
// `Selector { }`.
//
// ⚠ THE EMISSION ORDER IS THIS FUNCTION'S, NOT THE FACTORY'S -- Self/Normal
// first, then Self's other states, then every other part in enum order.
// Same reason the Qt peer sorts: a composed spec builds inside out, so the
// surface's own rule comes out of the factory LAST.
[[nodiscard]] static std::string rulesFor(const ICoreStyleSpec& spec,
const std::string& selfSelector);
// The GTK selector a part maps to, or an empty string for Self (whose
// selector the caller supplies).
//
// ⚠⚠ TWO KINDS OF ANSWER LIVE IN ONE TABLE, AND THE DIFFERENCE IS WHO OWNS
// THE WIDGET. A part the TOOLKIT draws answers with a real GTK node name
// (`popover.menu`, `scrollbar slider`, `selection`) and really does reach
// something the moment this backend puts that control on screen. A part the
// APP draws answers with one of the style classes below, and reaches
// whatever seat has added that class to itself -- which today is NOTHING,
// because the painted control seats are L2.2's. That is recorded here and
// by unrepresentableIn(); it is not a claim that those parts are styled.
[[nodiscard]] static std::string selectorForPart(ICoreStylePart part);
// The GTK pseudo-class suffix for a state -- ":hover", ":disabled", … and
// an empty string for Normal.
//
// ⚠ RETURNS EMPTY FOR SelectedInactive AS WELL, and that collision is why
// unrepresentableIn() exists: GTK has no spelling for "selected while the
// view is not focused", so a caller cannot be allowed to read an empty
// suffix as "Normal". rulesFor() DROPS that rule rather than emitting it
// unsuffixed, which would silently restyle the Normal state.
[[nodiscard]] static std::string pseudoStateFor(ICoreStyleState state);
// ---------------------------------------------------------------------
// ⚠⚠ THE CENSUS -- what this spec asks for that GTK CSS cannot say.
//
// ICoreStyleSpec::touchesToolkitOwnedParts() answers the question
// Theme/ICoreStyleApply.h asks before REFUSING a part. This answers a
// different and larger one: of everything the spec sets, what does the
// rendering LOSE? Four properties have no GTK spelling, one state has none,
// a fixed size degrades to a minimum, and several parts have a selector
// that reaches no widget this backend has built yet.
//
// Each entry is one human-readable line -- "Self/Normal: alternateBackground
// has no GTK CSS property" -- so a caller can log them, a suite can count
// them, and L6.3's parity audit can diff them. Empty means nothing was lost.
//
// ⚠ THIS IS THE MEMBER THAT KEEPS THE BACKEND FROM DROPPING SILENTLY, which
// is §5.1's rule and the thing ICoreStyleSpecAppKit's Unrepresentable verdict
// exists for one toolkit over. A renderer that simply omitted what it could
// not say would produce CSS that loads clean and looks nearly right.
[[nodiscard]] static std::vector<std::string> unrepresentableIn(const ICoreStyleSpec& spec);
// ---------------------------------------------------------------------
// The style classes this binding renders the app-owned roles as.
//
// ⚠ THEY ARE THIS BINDING'S CONVENTION, exactly as the object names
// ICoreThemeQss::kAffirmativeActionName holds are the Qt binding's. A ROLE
// survives the backend swap; the spelling does not, and the spelling belongs
// wherever the toolkit does.
//
// ⚠ THE SEAT THAT DRAWS A ROLE ADDS ITS OWN CLASS, and that indirection is
// forced rather than chosen: every painted control in this tree is the one
// CSS node `icorewidget` (ICoreGtk4Widget's class_init names it), so a
// caption, a field and a button are indistinguishable by node name -- the
// GTK counterpart of the Qt trap ICoreNativeHandle.h documents.
//
// ⚠ FIVE OF THE SEVEN ARE WORN TODAY and two are not. ICoreButton,
// ICoreLabel and ICoreToolBar add the button, caption, toolbar and the two
// action classes in their own constructors and variant setters, against
// these constants, so a rule aimed at one of those reaches a real widget.
// Nothing draws a Field role or a Badge yet, so those two selectors are
// still legal CSS that matches nothing -- and unrepresentableIn() says so
// for any spec that uses one. Read its predicate before assuming either
// list is current.
static const char* const kCaptionClass;
static const char* const kFieldClass;
static const char* const kButtonClass;
static const char* const kAffirmativeActionClass;
static const char* const kDestructiveActionClass;
static const char* const kToolBarClass;
static const char* const kBadgeClass;
};
};
ICoreThemeBinding.h#
ICoreEssentials/Theme/Gtk4Binding/ICoreThemeBinding.h
ICoreThemeBinding#
ICoreThemeBinding.h:85 · class · 10 declaration(s)
ICoreThemeBinding -- attaches native widgets to the active theme.
class ICoreThemeBinding {
public:
ICoreThemeBinding() = delete;
// Both forward to Theme/ICoreThemeInk.h -- the SAME implementation the
// other three bindings forward to, not a copy of it. See that header for
// why the arithmetic does not live in any backend.
//
// ⚠ DECLARED HERE, DEFINED IN THE .cpp, even though each body is a single
// return. A one-line forward in a header is still a body and the header
// surface census counts it -- correctly: the rule is about what a header
// CONTAINS, not about how much work the body does. All three peers declare
// these the same way for the same reason.
static ICoreColor withAlpha(ICoreColor c, int alpha);
static ICoreColor inkOn(const ICoreColor& surface);
// Run `apply` now, and again on every theme change until `widget` dies.
//
// ⚠ A NULL OR UNSEATED WRAPPER IS A NO-OP, NOT A CRASH, AND apply IS STILL
// RUN ONCE. The two halves differ in what they can promise: the immediate
// apply() needs no toolkit object and happens either way, while the
// SUBSCRIPTION needs something to die with and cannot be made. A caller
// that passes one therefore gets a widget styled once and never again --
// worth stating here rather than being absorbed silently. Same shape, same
// sentence, as both native peers.
//
// ⚠ CAPTURE THE WIDGET, NOT A POINTER YOU OWN. The subscription outlives
// the call and is severed when the widget's toolkit object is finalized;
// anything else the closure captures must outlive the widget, because
// nothing here can sever on ITS death.
template <typename ApplyFn>
static void subscribe(ICoreNativeWidget* owner, ApplyFn apply) {
apply();
if (const ICoreSignalScope* const scope = icoreGtk4ThemeScope(owner)) {
ICoreThemeManager::instance().onThemeChanged().connect(*scope, std::move(apply));
}
}
// 📌 **THE ITEM OVERLOAD IS REAL SINCE L4.1**, and the note that stood here
// said what it was waiting for: *"there is no graphics VIEW on this backend,
// so no scene item is on screen to go stale. The day there is one, this
// overload is a two-line edit and the sentence to delete is this one."*
// There is one. The lifetime a subscription is severed by is the item's
// NODE -- ICoreGtk4SceneNode, which is the wrapper's own Impl in every seat
// in this zone -- and `icoreGtk4ThemeScope(ICoreNativeItem*)` hands it out.
//
// ⚠ WHAT THE GAP COST WAS MEASURED BEFORE IT WAS CLOSED: **33 item-tier
// classes** in this tree subscribe through this overload, so a Light/Dark
// switch recoloured every block face, port label, canvas frame and config
// dialog in memory and left the old colours on screen. Nothing failed; the
// window simply stayed the wrong colour.
//
// ⚠ A NULL OR UNSEATED ITEM IS STILL A NO-OP WITH apply() RUN ONCE, which is
// the widget overload's own contract and the honest answer for an item no
// backend has seated.
template <typename ApplyFn>
static void subscribe(ICoreNativeItem* owner, ApplyFn apply) {
apply();
if (const ICoreSignalScope* const scope = icoreGtk4ThemeScope(owner)) {
ICoreThemeManager::instance().onThemeChanged().connect(*scope, std::move(apply));
}
}
// Repaint now, and on every theme change until `widget` dies -- for a
// widget that reads tokens inside its paint hook and needs nothing but an
// update.
static void repaintOnThemeChange(ICoreNativeWidget* widget);
// 📌 **THE ITEM TWIN IS REAL SINCE L4.1 TOO**, and it is a DIRTY MARK
// rather than a repaint: a scene item has nothing of its own to invalidate,
// so it records damage in the scene core and the view showing that scene
// turns it into a toolkit invalidation. An item in no scene marks nothing,
// which is correct -- nothing is showing it.
static void repaintOnThemeChange(ICoreNativeItem* item);
// ------------------------------------------------------------------
// The shadow-preference trio.
//
// ⚠⚠ ALL THREE ARE INERT ON THIS BACKEND TODAY, INCLUDING THE WIDGET ONE --
// WHICH IS WHERE THIS PEER DIFFERS FROM BOTH NATIVE SIBLINGS. On AppKit a
// view has a layer and on WinUI an element takes a Composition shadow, so
// each of those really installs one. Here the effects tier does not exist:
// UI/Backends/Gtk4/ has no Effects/ directory, and
// ICoreDropShadow::installOn() has no definition in this zone at all.
// Calling it from these bodies would put an unresolved external into the
// tree, which is the one thing a static ARCHIVE will not tell you about
// until something links the product.
//
// ⚠ THE ROW THAT ENDS THIS IS L7.1, and it inherits a measurement rather
// than a blank: GTK's CSS engine accepts `box-shadow: 0 0 20px rgba(...)`
// and `opacity`, so a shadow on this backend can be either a CSS
// declaration or gtk_snapshot_append_outset_shadow. Deliberately NOT taken
// here -- an effect installed from the theme binding would be a SECOND
// place deciding what a shadow is, competing with the ICoreDropShadow
// wrapper that is supposed to own it, and this tree has paid for a second
// source of truth often enough to have a standing warning about it (SW5).
//
// ⚠ WHAT THEY ARE NOT IS EMPTY BODIES UNDER A COMMENT CLAIMING OTHERWISE.
// The "does nothing" is written down once, here, in the header a caller
// reads -- and it dissolves with no edit to this file the day the effects
// tier lands.
static void followCanvasShadowPreference(ICoreNativeWidget* target);
static void followCanvasShadowPreference(ICoreNativeItem* target);
static void followFloatingPanelShadowPreference(ICoreNativeItem* target);
};
};
ICoreThemeBinding.h#
ICoreEssentials/Theme/UIKitBinding/ICoreThemeBinding.h
ICoreThemeBinding#
ICoreThemeBinding.h:33 · class · 12 declaration(s)
The UIKit theme binding (TB2.16): ICoreThemeBinding for the iPadOS backend, with the AppKit binding's surface and guarantees, which Theme/AppKitBinding/ICoreThemeBinding.h states in full (one fact,...
class ICoreThemeBinding {
public:
ICoreThemeBinding() = delete;
static ICoreColor withAlpha(ICoreColor c, int alpha);
static ICoreColor inkOn(const ICoreColor& surface);
template <typename ApplyFn>
static void subscribe(const ICoreUIKitView& view, ApplyFn apply) {
apply();
ICoreThemeManager::instance().onThemeChanged().connect(
view.subscriptionScope(), std::move(apply));
}
static void repaintOnThemeChange(ICoreUIKitView& view);
template <typename ApplyFn>
static void subscribe(ICoreNativeWidget* owner, ApplyFn apply) {
if (const ICoreUIKitView* view = icoreUIKitView(owner)) {
subscribe(*view, std::move(apply));
return;
}
apply();
}
static void repaintOnThemeChange(ICoreNativeWidget* widget);
template <typename ApplyFn>
static void subscribe(ICoreNativeItem* owner, ApplyFn apply) {
(void)owner;
apply();
}
static void repaintOnThemeChange(ICoreNativeItem* item);
static void followCanvasShadowPreference(ICoreNativeWidget* target);
static void followCanvasShadowPreference(ICoreNativeItem* target);
static void followFloatingPanelShadowPreference(ICoreNativeItem* target);
};
ICoreStyleSpecWeb.h#
ICoreEssentials/Theme/WebBinding/ICoreStyleSpecWeb.h
The web binding's style resolver: an ICoreStyleSpec read down to the plain properties a painted surface can take -- fill, border, corner radius, text colour and font, padding, minimum size.
The FOLD is Theme/AppKitBinding/ICoreStyleSpecAppKit.h's: AppKit paints its controls the way a canvas does -- no style engine takes a sheet -- so its reading of a spec is the right model, and a change to it belongs in both copies. ⚠ THE VERDICTS ARE NOT AppKit's (WB6.3): there, a menu, a scroller, a split view's divider and a table's selected row are platform objects; on the web every one of them is painted by a seat of this tree, so each verdict names the seat that draws the part instead.
ICoreWebResolvedStyle#
ICoreStyleSpecWeb.h:21 · struct · 0 declaration(s)
struct ICoreWebResolvedStyle {
public:
ICoreRgba background;
ICoreRgba foreground;
ICoreRgba borderColor;
double borderWidth = -1.0;
ICoreRgba borderBottomColor;
double borderBottomWidth = -1.0;
double cornerRadius = -1.0;
double fontPixelSize = -1.0;
bool fontBold = false;
std::string fontFamily;
double paddingTop = -1.0;
double paddingRight = -1.0;
double paddingBottom = -1.0;
double paddingLeft = -1.0;
double minWidth = -1.0;
double minHeight = -1.0;
ICoreRgba selectionBackground;
ICoreRgba selectionForeground;
bool found = false;
};
};
ICoreWebFieldRoute#
ICoreStyleSpecWeb.h:63 · struct · 0 declaration(s)
A field a rule SETS that ICoreWebResolvedStyle does not carry, and where it goes instead.
struct ICoreWebFieldRoute {
public:
const char* field = "";
ICoreWebStyleFidelity fidelity = ICoreWebStyleFidelity::Applied;
const char* reason = "";
};
};
ICoreWebPartVerdict#
ICoreStyleSpecWeb.h:69 · struct · 0 declaration(s)
struct ICoreWebPartVerdict {
public:
ICoreStylePart part = ICoreStylePart::Self;
ICoreStyleState state = ICoreStyleState::Normal;
ICoreWebStyleFidelity fidelity = ICoreWebStyleFidelity::Applied;
const char* reason = "";
};
};
ICoreStyleSpecWeb#
ICoreStyleSpecWeb.h:83 · class · 4 declaration(s)
class ICoreStyleSpecWeb {
public:
ICoreStyleSpecWeb() = delete;
static ICoreWebResolvedStyle resolve(const ICoreStyleSpec& spec,
ICoreStylePart part,
ICoreStyleState state);
static ICoreWebResolvedStyle resolveSelf(const ICoreStyleSpec& spec);
static std::vector<ICoreWebPartVerdict> verdictsFor(const ICoreStyleSpec& spec,
ICoreWebStyleSurface surface);
static bool losesFidelity(const ICoreStyleSpec& spec, ICoreWebStyleSurface surface);
// One route per field this rule sets and resolve() does not carry; empty
// when resolve() carries the whole rule. Nothing a rule sets is dropped in
// silence: a field is either in the resolved style or in this list.
static std::vector<ICoreWebFieldRoute> uncarriedFields(const ICoreStyleRule& rule);
};
};
File-scope declarations#
enum class ICoreWebStyleFidelity : int {
Applied = 0,
Native,
Unrepresentable,
};
enum class ICoreWebStyleSurface : int {
PaintedControl = 0,
ToolkitObject,
};
ICoreThemeBinding.h#
ICoreEssentials/Theme/WebBinding/ICoreThemeBinding.h
The web backend's theme binding: how a widget follows a theme change.
⚠ THE SAME CLASS, THE SAME CONTRACT, AS Theme/Gtk4Binding/ICoreThemeBinding.h, whose comments carry the reasons:
subscribeapplies once now and again on every change, for exactly as long as the owner lives, because the connection is scoped to a signal scope that dies WITH the owner -- here the scope is the widget node's (icoreWebThemeScope), as it is the GtkWidget's there.⚠ THE ITEM OVERLOADS COMPILE AND DO NOT YET LINK: a graphics item's scope is the graphics tier's, which the web backend does not have yet. A caller reaches the linker, which names it.
ICoreThemeBinding#
ICoreThemeBinding.h:25 · class · 10 declaration(s)
class ICoreThemeBinding {
public:
ICoreThemeBinding() = delete;
static ICoreColor withAlpha(ICoreColor c, int alpha);
static ICoreColor inkOn(const ICoreColor& surface);
template <typename ApplyFn>
static void subscribe(ICoreNativeWidget* owner, ApplyFn apply) {
apply();
if (const ICoreSignalScope* const scope = icoreWebThemeScope(owner)) {
ICoreThemeManager::instance().onThemeChanged().connect(*scope, std::move(apply));
}
}
template <typename ApplyFn>
static void subscribe(ICoreNativeItem* owner, ApplyFn apply) {
apply();
if (const ICoreSignalScope* const scope = icoreWebThemeScope(owner)) {
ICoreThemeManager::instance().onThemeChanged().connect(*scope, std::move(apply));
}
}
static void repaintOnThemeChange(ICoreNativeWidget* widget);
static void repaintOnThemeChange(ICoreNativeItem* item);
static void followCanvasShadowPreference(ICoreNativeWidget* target);
static void followCanvasShadowPreference(ICoreNativeItem* target);
static void followFloatingPanelShadowPreference(ICoreNativeItem* target);
};
};
ICoreThemeBinding.h#
ICoreEssentials/Theme/WinUIBinding/ICoreThemeBinding.h
ICoreThemeBinding#
ICoreThemeBinding.h:70 · class · 10 declaration(s)
ICoreThemeBinding -- attaches native widgets to the active theme.
class ICoreThemeBinding {
public:
ICoreThemeBinding() = delete;
// Both forward to Theme/ICoreThemeInk.h -- the SAME implementation the Qt
// and AppKit bindings forward to, not a copy of it. See that header for
// why the arithmetic does not live in any backend.
//
// ⚠ DECLARED HERE, DEFINED IN THE .cpp, even though each body is a single
// return. A one-line forward in a header is still a body, and the header
// surface census counts it -- correctly: the rule is about what a header
// CONTAINS, not about how much work the body does. Both peers declare
// these the same way for the same reason.
static ICoreColor withAlpha(ICoreColor c, int alpha);
static ICoreColor inkOn(const ICoreColor& surface);
// Run `apply` now, and again on every theme change until `widget` dies.
//
// ⚠ A NULL OR UNSEATED WRAPPER IS A NO-OP, NOT A CRASH, AND apply IS STILL
// RUN ONCE. The two halves differ in what they can promise: the immediate
// apply() needs no element and happens either way, while the SUBSCRIPTION
// needs something to die with and cannot be made. A caller that passes one
// therefore gets a widget styled once and never again -- worth stating
// here rather than being absorbed silently. Same shape, same sentence, as
// the AppKit peer.
//
// ⚠ CAPTURE THE WIDGET, NOT A POINTER YOU OWN. The subscription outlives
// the call and is severed when the widget's element is destroyed; anything
// else the closure captures must outlive the widget, because nothing here
// can sever on ITS death.
template <typename ApplyFn>
static void subscribe(ICoreNativeWidget* owner, ApplyFn apply) {
apply();
if (const ICoreSignalScope* const scope = icoreWinUIThemeScope(owner)) {
ICoreThemeManager::instance().onThemeChanged().connect(*scope, std::move(apply));
}
}
// The item twin: apply() now, and on every theme change until the item
// dies. The lifetime is the item's scene node, the same one the AppKit
// seat hangs its item subscriptions on.
//
// ⚠ CAPTURE THE ITEM, NOT A POINTER YOU OWN -- the same rule as the widget
// overload above. A null item, or one that is not a scene item, is styled
// once and never again.
template <typename ApplyFn>
static void subscribe(ICoreNativeItem* owner, ApplyFn apply) {
apply();
if (const ICoreSignalScope* const scope = icoreWinUIThemeScope(owner)) {
ICoreThemeManager::instance().onThemeChanged().connect(*scope, std::move(apply));
}
}
// Repaint now, and on every theme change until `widget` dies -- for a
// widget that reads tokens inside its paint hook and needs nothing but an
// update.
static void repaintOnThemeChange(ICoreNativeWidget* widget);
// The item twin. Marks the item dirty now and on every theme change until
// the item dies.
static void repaintOnThemeChange(ICoreNativeItem* item);
// ------------------------------------------------------------------
// The shadow-preference trio.
//
// ⚠ THE WIDGET OVERLOAD IS REAL WORK ON THIS BACKEND. A XAML element can
// carry a Composition drop shadow, so a shadow really is installed, really
// is taken off when the user turns canvas shadows off, and really does
// follow the preference for as long as the widget lives.
//
// ⚠⚠ THE TWO ITEM OVERLOADS DO NOTHING, AND THE REASON CHANGED UNDER THEM
// ON 2026-08-28 WITHOUT THE BEHAVIOUR NEEDING TO.
//
// It read: "ICoreDropShadow::installOn(ICoreNativeItem*) is deliberately
// left UNDEFINED by this backend ... Calling it from here would put that
// unresolved external straight back into the tree." It is DEFINED now --
// Backends/WinUI/Effects/ICoreDropShadow.cpp seats the ICoreNativeItem*
// overload, landed by W1.12 -- so calling it would no
// longer break the link. It would still be wrong: that seat returns null
// and says in its own body why a scene item has no visual to hang a shadow
// under. So these stay no-ops on the seat's argument rather than on a
// linker's, which is a weaker reason honestly stated (W8.6, 2026-08-28).
//
// ⚠ AND THAT IS THIS ROW'S MEASURED WIN RATHER THAN AN EXCUSE: the seat's
// own note records, from `dumpbin /symbols`, that its ONE remaining
// unresolved external had exactly one caller compiled in a winui build --
// Theme/QtBinding/ICoreThemeBinding.cpp, the file this directory replaces.
// Writing these two as no-ops is what retires it.
//
// ⚠ WHAT THEY ARE NOT IS EMPTY BODIES UNDER A COMMENT CLAIMING OTHERWISE.
// The "does nothing" is written down once, here, in the header a caller
// reads -- and it dissolves with no edit to this file the day an item can
// hold an effect.
static void followCanvasShadowPreference(ICoreNativeWidget* target);
static void followCanvasShadowPreference(ICoreNativeItem* target);
static void followFloatingPanelShadowPreference(ICoreNativeItem* target);
};
};