ResolvedStyle Struct

#include <vstyle.h>

Public Members

std::string fill
bool fillIsGradient = false
std::string borderFill
bool borderIsGradient = false
double borderThickness = 0
double radiusTL = 0
double radiusTR = 0
double radiusBL = 0
double radiusBR = 0
double marginL = 0
double marginT = 0
double marginR = 0
double marginB = 0
double paddingL = 0
double paddingT = 0
double paddingR = 0
double paddingB = 0
std::string textColor
std::string codeBg
double fontSize = 0
double fontWeight = 0
std::string fontFamily
bool visible = true
bool collapsed = false
std::string direction
double spacing = 0
std::string crossAlign
std::string justify
std::string scroll
double grow = 0
double shrink = -1
double width = 0
double height = 0
double heightVp = 0
double maxWidth = 0
double aspectRatio = 0
double translateY = 0
double translateX = 0
double rotation = 0
double opacity = 1.0
std::string text
std::string placeholder
bool secret = false
bool multiline = false
bool readOnly = false
bool wrap = true
std::string iconSource
std::string mediaSource
std::string fit
bool autoplay = false
bool loop = false
bool muted = false
bool controls = false
std::string qrData
std::string intent
std::string overlay
std::string href
std::string tag
std::string anchor
std::string bind

Detailed Description

Fully-resolved styling for a single box/text node. Colors are hex strings ("#rrggbb" / "#aarrggbb"). Gradients are stored as comma-joined stops ("0:#3a3c42,1:#42454d") with the matching *IsGradient flag set.

Member Variable Documentation

double marginL = 0

Box model. margin = EXTERNAL space around the node, contributed by the parent's layout; padding = INTERNAL space between the node's border and its content. Content inset = borderThickness + padding.

double marginT = 0

Box model. margin = EXTERNAL space around the node, contributed by the parent's layout; padding = INTERNAL space between the node's border and its content. Content inset = borderThickness + padding.

double marginR = 0

Box model. margin = EXTERNAL space around the node, contributed by the parent's layout; padding = INTERNAL space between the node's border and its content. Content inset = borderThickness + padding.

double marginB = 0

Box model. margin = EXTERNAL space around the node, contributed by the parent's layout; padding = INTERNAL space between the node's border and its content. Content inset = borderThickness + padding.

std::string codeBg

Markdown nodes only: the resolved Theme/Secondary hex used as the surface color behind inline + fenced code (so code reads as a tinted pill/block over the body). Resolved in core from the active theme — not an author-facing attribute — so all three reconcilers theme code identically. Empty for every non-Markdown node.

double fontSize = 0

text point/px size (0 == reconciler default)

double fontWeight = 0

CSS-style numeric weight (100–900; 400 normal, 700 bold). 0 == unset, which each reconciler renders as its default/normal weight.

std::string fontFamily

Font family name ("" == reconciler default). The generic "monospace" maps to the platform's fixed-width face (code/raw-text views; web: CSS family).

bool visible = true

Visibility. False drops the node (and its subtree) from the render tree entirely — driven by a "Visible" attribute that can cascade on state (e.g. hide a tab's label in @Mobile). Defaults true.

bool collapsed = false

Collapsed. Unlike visible (which removes the node), a collapsed node STAYS in the tree but the reconciler animates its height to 0 — and back to its natural content height when un-collapsed. Driven by a "Collapsed" attribute that cascades on state (the dashboard drawers collapse their body unless the drawer carries @Expanded). Defaults false. The node persists across the toggle so the height change can tween (a Visible toggle can't animate — the node is gone). Reconcilers that don't animate may treat it as hide/show.

std::string direction

Layout — container properties (how this node arranges its children). direction: "Row" | "Column" (empty == "Column", the default). crossAlign: "Start" | "Center" | "End" | "Stretch" cross-axis placement of children (empty == "Center", the default). justify: "Start" | "Center" | "End" main-axis placement of the child block when nothing grows (empty == "Center", the default). scroll: "Vertical" | "Horizontal" | "Both" — overflow scrolls instead of clipping (empty == no scroll).

double spacing = 0

gap between children, px

double grow = 0

main-axis flex factor (0 == don't grow)

double shrink = -1

Main-axis shrink factor ("Layout.Shrink"). -1 == unset (the platform default, CSS flex-shrink 1). 0 pins the node at its content size inside a SCROLLABLE ancestor — the static-web idiom for "this section must never be squashed to fit; overflow scrolls instead" (a page row that grows to push the footer down, the footer itself). Mirrors the legacy sites' hand-written flex: 1 0 auto / flex: none rules. Runtime reconcilers currently ignore it (their scroll regions size children natively).

double width = 0

fixed width, px (0 == intrinsic)

double height = 0

fixed height, px (0 == intrinsic)

double heightVp = 0

Height as a FRACTION of the VIEWPORT height ("Size.Height Viewport", 0 == unset) — the viewport cousin of translateY's self-fraction. Folded into height at build time against the platform-reported viewport (VApp::set_viewport_height), so reconcilers only ever see the concrete px value; an authored Size.Height doubles as the fallback when no viewport height has been reported. A fraction (not a px length), so the mobile metric scale leaves it alone. Sizes the scan drawer's camera to the legacy "70% of the screen" drawer proportions.

double maxWidth = 0

Max width, px (0 == none). A constraint, NOT a fixed size: the node EXPANDS to fill its parent's cross axis up to this cap, then SHRINKS below it when the parent is narrower. Combined with the parent's Layout.Align (default Center) this yields the classic centered, max-width content column that stays comfortably wide on big windows yet reflows on small ones. Reconcilers implement the fill-then-cap behavior natively (flex width:100%+max-width on web; a centering container on Qt/Android).

double aspectRatio = 0

Image / Video nodes: width÷height ASPECT RATIO ("Aspect Ratio", e.g. 1.7778 for 16:9; 0 == unset). The node keeps its normal width behavior (fill the parent's cross axis / a fixed Size.Width) and its HEIGHT derives from the LAID-OUT width (height = width / ratio) — so a feed card's media holds a proportional shape at any card width, instead of a fixed pixel height cropping tall media. When set, Size.Height is ignored. The mismatch between this ratio and the artwork's own resolves via Fit as usual ("Cover" crops, "Contain" letterboxes). Overridable per-row via the "Aspect:" bind (a post's own, clamped ratio). A ratio (unitless), so the mobile metric scale leaves it alone.

double translateY = 0

Transform — a render-time offset along the main axis, expressed as a FRACTION of the node's own measured height (1.0 == fully shifted off by its own height; 0 == in place). The reconciler multiplies by the measured size, so "fully hidden" stays correct regardless of the node's intrinsic height, and animates the change. Drives the titlebar's hide-away-on-scroll slide (a @Narrow&ControlsHidden variant pushes the bottom-pinned bar to 1.0).

double translateX = 0

Transform — the horizontal twin of translateY: a render-time offset expressed as a FRACTION of the node's own measured WIDTH (1.0 == fully shifted off by its own width; 0 == in place). Drives a "SideSheet" overlay's slide-in from the right edge (a @Open variant pulls it from 1.0 to 0).

double rotation = 0

Transform — a render-time rotation in DEGREES, clockwise, about the node's own center. Purely visual: it does not affect layout/measurement (the slot the node occupies is its un-rotated box). Used to reorient an icon shared by two controls — e.g. the same downward "arrow" glyph rotated 180 for an upvote button. 0 == no rotation. Reconcilers apply it on icon nodes (CSS rotate / QPainter::rotate / View.rotation), all clockwise-positive.

double opacity = 1.0

Render-time opacity in [0,1], purely visual: it fades the node (and its subtree) WITHOUT reflowing — the node keeps its full layout slot, unlike visible=false which drops it. Mirrors CSS opacity / Android alpha / QGraphicsOpacityEffect. 1 == fully opaque (the default). Drives the space-preserving hide of a gated affordance — e.g. a feed post's vote arrows when the viewer isn't verified, so the vote box keeps its size.

std::string text

Text nodes: static, design-owned literal copy authored directly in the style (mirrors iconSource for icons). The base value for node.text; a runtime bind_text() value or a dynamic "Bind" override takes precedence. Empty when the style supplies no literal (text then comes from code/bind). NOTE: literal copy here is not localizable — when i18n lands this should become a string-table key (e.g. "@app.title") resolved against app data.

std::string placeholder

Editor nodes: the floating placeholder copy — shown filling the field while it is empty and unfocused, animating up + smaller to a label once the user focuses or types (the legacy desktop line-editor behavior, now portable to every reconciler). The editor's INITIAL/default value rides the normal text field (the style's literal Text, overridable via bind_text); the user's typed value is owned by the reconciler and read back via VApp::editor_text(). Empty for non-editor nodes.

bool secret = false

Editor nodes: mask the typed value (password field) when true. Defaults false (a normal visible field).

bool multiline = false

Editor nodes: a multi-line text box (wrapping, top-aligned) instead of the single-line field. The floating placeholder is a single-line affordance, so multi-line fields show a STATIC placeholder that fills the box while empty. Height comes from the style's Size.Height (no auto-grow). Defaults false.

bool readOnly = false

Editor nodes: a display surface the user can select/copy from but not type into (Docuriser's streamed build log). The value still moves via app rebinds (bind_text); a read-only multi-line editor additionally pins its scroll to the bottom on each rebind — log semantics. Defaults false.

bool wrap = true

Text nodes wrap long lines to the available width by default (like web + Android); "Wrap": 0 keeps them single-line. Editor nodes (multi-line): wrap long lines to the box width (the default). "Wrap": 0 disables wrapping and scrolls horizontally instead — the log/code look (long paths stay on one line). Single-line fields ignore it.

std::string iconSource

Icon nodes: the .svg asset name (no extension) the reconciler loads and recolors (to textColor). Empty for non-icon nodes.

std::string mediaSource

Image / Video nodes: the media file path or URL the reconciler loads and displays (a photo, an mp4, an SVG logo). Raster/video render verbatim; an SVG Image renders as a live vector and — unlike a photo, but like an icon — recolors to textColor when the style sets Text Color (else full color is preserved). Resolves the same "Source" attribute icons use; the reconciler reads whichever of iconSource/mediaSource matches the node type. Empty otherwise.

std::string fit

Image / Video nodes: how the media fills its box when its aspect ratio differs from the box's — "Cover" (fill + crop) or "Contain" (fit whole, letterbox). Maps to CSS object-fit. Empty == the reconciler's default (Cover). Ignored by non-media nodes.

bool autoplay = false

Video nodes only: playback flags. autoplay starts on load (browsers require muted to honor it), loop restarts at the end, muted starts silent, controls shows the native scrubber/volume UI. All default false. Ignored elsewhere.

std::string qrData

QR nodes: the payload string encoded into the QR matrix (a node URL, a key blob, …). Resolves the "Data" attribute (a separate attribute from Source, so a QR and an icon/image can't be confused). The matrix is derived from this on demand — by the core when serializing for web/Android, by the desktop reconciler directly — so only the payload rides the resolved style. Empty for non-QR nodes (an empty payload paints just the box fill).

std::string intent

Optional semantic action a reconciler may bind to platform behavior, e.g. "window:minimize" / "window:maximize" / "window:close" on a button, or "window:drag" / "window:resize" on a box. Desktop-only today; other platforms ignore intents they don't handle. Empty for most nodes.

std::string overlay

Overlay mode. "" == this node lays out normally in its parent's flow. "Modal" == the reconciler lifts this node out of the flow and presents it as a dialog centered over a darkening backdrop covering the rest of the UI (desktop = a movable, modal frameless window; web/Android = a fixed, centered element). The node is shown/hidden via the normal visibility overlay (bind_visible), so opening/closing the dialog rides the diff as a single Insert / Remove. "PinnedBottom" == the reconciler floats this node over the content, anchored to the bottom edge (rather than occupying a slot in the flow), so it can slide off-screen via translateY without reflowing content. Used by the titlebar in @Narrow (where the bar sits at the bottom) to drive hide-away- on-scroll. Empty for most nodes. "BottomSheet" == like PinnedBottom (floated at the anchor's bottom edge, centered up to Size.Max Width), but reserves NO bottom space: the node OVERLAYS the content instead of pushing it. Used by the dashboard drawers, which peek as just a header (their body carries a Visible "@Expanded" variant) and grow upward over the list when expanded; the list reserves the collapsed peek with its own bottom padding. "SideSheet" == a modal detail panel floated at the anchor's RIGHT edge, full anchor height, right-aligned and capped at Size.Max Width (0 == fill, i.e. edge-to-edge in @Narrow). The reconciler draws a darkening scrim over the rest of the anchor (click-to-dismiss; dispatches a "click" to THIS node's path) and slides the panel in/out horizontally via translateX (rest at 1 off-screen; an @Open variant pulls it to 0), the scrim's opacity tracking the slide. Stays in the tree across open/close (state-driven, like the titlebar's translateY hide-away) so the slide animates both ways. Used by the profile post-detail.

std::string href

Link nodes: the target URL (the "Href" attribute; overridable at runtime via bind_href and per-row via a "Href:" bind). The static-web emitter writes it as the ; runtime reconcilers ignore it.

std::string tag

Document tag hint (the "Tag" attribute): the semantic HTML element the static-web emitter uses instead of div/span — "h1".."h6", "nav", "main", "header", "footer", "section", "article", "ul", "li", … Authored per style for crawlable, reader-mode-friendly generated pages. Runtime reconcilers ignore it. Empty = the emitter's default element.

std::string anchor

Fragment anchor (the "Anchor" attribute): the emitted element's id, the target of "#anchor" deep links in generated documentation. Runtime reconcilers ignore it. Empty = no id.

std::string bind

Data binding handled in the core during tree-build (consumed there, never sent to reconcilers): "ThemeList" makes a container generate one button per loaded theme (from its first child as a template); "ActiveThemeName" / "ActiveThemePublisher" set a text node to the active theme's name/publisher; "ThemeName" / "ThemePublisher" do the same for the theme of the enclosing generated theme button (its primary label + publisher subtext), so each button reads its own theme rather than the active one. Row-data forms (inside a "List:" container's row template): "List:" generates one row per app-supplied entry (first child as the template; an optional SECOND child is a separator template, stamped between consecutive rows — list chrome built without row context, keyed to the row it follows so mid-list edits diff minimally; each row's subtree also resolves with a "@First"/"@Last" state folded in at the ends, so a template can restyle end rows — round only the outer corners, hide an embedded divider on the last row, etc.); "Field:" fills a node from the row's field — text for most nodes, the media Source for an Image/Video (an empty value collapses it); "Placeholder:" sets an Editor's placeholder from a row field — the row's identity rides the editor's floating field label, reading exactly like a static form field's authored Placeholder (absent/empty leaves the authored one, so a template can carry a static default); "Opacity:" fades a node from a boolean-ish row field (empty/"0"/"false" → transparent) WITHOUT collapsing its slot, to gate a per-row affordance while preserving layout (e.g. the feed vote arrows); "MaxWidth:" caps Size.Max Width from a numeric row field (non-positive → uncapped, fills the cross axis), e.g. an inline embed's pixel cap; "Aspect:" sets a media node's Aspect Ratio from a numeric row field (non-positive/absent → the authored ratio), e.g. a feed post's media at its own clamped shape; "Indent:" sets the node's LEFT MARGIN from a numeric row field (non-positive → flush), insetting the whole node (fill included) — used to step threaded comment replies in by depth. Row-data binds compose: a "Bind" value may carry SEVERAL of them separated by ';' (e.g. "Field:image; Aspect:aspect" — one media node bound to both its per-row source and its per-row shape). The structural binds (List:, ThemeList — they generate children) are whole-value only.

Appearance
Theme
—