For setup and runnable programs, start with basic usage and examples.
UIRuntime is the main-thread owner for one native UI surface. It owns a
renderer-neutral stable-slot arena, a unique surface ID, and revisioned
SurfaceMetrics. Retained coordinates are logical units local to the surface,
with (0, 0) at the top left. The host supplies both logical and framebuffer
sizes plus DPI scale; Core does not include platform or Vulkan types.
UIView is a move-only ownership scope. Objects adopted by a view receive typed
non-owning WidgetHandle<T> identities. Checked lookup validates surface, slot,
generation, and concrete type in constant time. Destroying a parent view or its
runtime destroys every descendant once and makes old handles stale.
struct PanelState {
int selectedRow{0};
};
Nikerva::UIRuntime ui;
(void)ui.updateSurfaceMetrics({1280.0f, 720.0f}, {2560U, 1440U}, 2.0f);
Nikerva::UIView panel = ui.createView();
auto state = panel.emplace<PanelState>();
ui.resolve(state)->selectedRow = 3;Signal<...> creates move-only Connection owners. A connection observes its
source weakly, while ConnectionGroup lets a view or application object own
several subscriptions. Destruction is safe in either order and callbacks may
disconnect themselves during dispatch. Connecting may allocate; notification
dispatch does not allocate inside the signal.
UIView::createRootPanel() and container-handle factories add persistent nodes
to that same arena. UINodeRef performs checked parent/child/sibling traversal,
insertion, moves, and reparenting. A presentation node may move inside its own
UIView or attach below an ancestor view; it cannot move into an unrelated or
shorter-lived view. This keeps visible topology separate from lifetime ownership.
Nikerva::UIRuntime ui;
Nikerva::UIView view = ui.createView();
auto panel = view.createRootPanel("statistics-panel");
auto fpsLabel = panel.createLabel({}, "fps");
auto reset = panel.createButton("Reset", "reset");
Nikerva::Property<double> fps{60.0};
Nikerva::Property<bool> canReset{true};
fpsLabel.bindText(fps.readOnly(), [](double value) {
return std::to_string(value) + " FPS";
});
reset.bindEnabled(canReset.readOnly());Property<T> is move-only but keeps a move-stable shared source state. It
compares in set(), emits synchronously only for real changes, and exposes a
weak ReadOnlyProperty<T>. Observation delivers the current value once and then
future real changes. Widget bindings are stored by the target node, so source or
target may be destroyed first. Text, enabled, checked, selected, visible,
progress, and renderer-neutral image values support one-way transformation.
Slider values and checkbox states additionally support normalized two-way bindings. Change-origin suppression prevents an edit from echoing recursively. Explicit begin/update/commit/cancel events keep validation, commands, history, and rollback decisions in the application rather than the UI library.
Nikerva::Property<float> volume{0.5F};
auto slider = panel.createSlider(0.0F, 0.0F, 1.0F, "volume");
slider.bindValue(volume, [](float value) {
return std::round(value * 10.0F) / 10.0F;
});beginMutationTransaction() coalesces repeated node/property records while
keeping setters and events synchronous. mutationDiagnostics() distinguishes
application setter calls, comparisons, no-ops, node creation/destruction/moves,
events, bindings, node work, and transaction coalescing. The standalone sample
reports zero application setters, comparisons, events, node work, and
allocations across 1,000 unchanged frames after warm-up.
Every real retained mutation now queues exact UIDirtyCategory work at mutation
time. processDirty() drains coalesced epochs in style, measure, arrange,
transform/clip, and dependent-result order. Measure bubbles to the nearest
container whose containment flag is true; if that container's desired size
changes, its parent-facing dependency is remeasured before the same call
publishes. Ancestor roots suppress queued descendant roots, and mutations
created while processing converge through bounded later epochs. A clean call
returns immediately without walking the tree.
Nikerva::UIRetainedLayout layout;
layout.mode = Nikerva::UIRetainedLayoutMode::Vertical;
layout.gap = 6.0F;
layout.clipChildren = true;
panel.setLayout(layout);
Nikerva::UIRetainedLayoutItem row;
row.preferredSize = {120.0F, 24.0F};
fpsLabel.setLayoutItem(row);
ui.processDirty();Child bounds and inherited clips are computed in surface-local logical units.
Linear layout iteratively freezes children at minimum/maximum constraints and
redistributes remaining space. Impossible minimums are preserved in presentation
order and clipped by the container. Programmatic padding/gap override stylesheet
values; omitting them inherits the style, and the two sources never add.
UIRetainedContentSizing explicitly selects whether measured visible content
replaces a child's preferred width, height, or both. Measured text, icons, gaps,
and padding still clamp to the child's minimum/maximum and the parent's available
allocation; ordinary labels do not force content sizing on their parents.
A host resize changes only SurfaceMetrics; the runtime updates every affected
descendant automatically. DPI-only changes preserve logical geometry while
invalidating text/resource/submission dependencies for later retained rendering.
UITheme owns immutable style data plus an explicit non-zero revision. Nodes
retain type/class/id selectors, visual state, resolved style, and property
dependencies. Replacing a theme with the same revision creates no work even if
the C++ object has different pointer identity. Metric differences queue layout;
color, opacity, border, and hover-only differences queue paint/submission only.
dirtyDiagnostics() reports queued/coalesced/processed categories, minimal
layout roots and reasons, selector/measure/arrange visits, bounds/clip changes,
and future hit/text/paint/resource/submission hooks.
Pointer and keyboard input now enter the retained runtime as validated
UIPlatformEvent values. processInput() performs no work when its queue is
empty. Pointer events query a private per-surface spatial grid maintained from
the same structure/bounds/clip/visibility/interaction mutations used by
NUI-04; the public API does not expose the grid algorithm.
Handlers receive UIRoutedEvent in capture, target, and bubble phases. They can
stop later phases, suppress built-in activation, inspect typed drag payloads,
or accept a drag operation. Focus, pointer capture, hover paths, tab order,
default/cancel actions, popup/modal stacks, cursor/tooltip state, and drag
sessions all store generation-checked handles. Hide, disable, destruction,
surface focus loss, popup dismissal, and expired drag data clean them up
deterministically. Forced pointer-capture release routes pointer-cancel and
capture-lost while the old owner is still valid. Non-modal popup dismissal does
not consume the new target's pointer event unless explicit policy requests it.
processInput() converges dirty mutations caused by its final queued event
before returning; an empty input queue still performs zero dirty or input work.
auto save = panel.createButton("Save", "save");
Nikerva::UIInteractionOptions interaction = *save.interaction();
interaction.defaultAction = true;
interaction.tooltip = "Save project";
save.setInteraction(interaction);
save.onEvent(Nikerva::UIEventType::Click,
[](Nikerva::UIRoutedEvent& event) {
// Application commands remain outside Nikerva Core.
});
Nikerva::UIPlatformEvent move;
move.type = Nikerva::UIPlatformEventType::PointerMove;
move.position = {32.0F, 18.0F};
ui.queueInput(move);
ui.processInput();Consecutive queued pointer moves coalesce to the latest absolute position and
sum relative delta. Absolute placement controls such as UISplitter therefore
track the newest pointer even when intermediate moves are skipped. A current
capture owner may call requestPointerPosition(); the host consumes the latest
logical request after processInput() and applies the platform warp. This keeps
platform policy outside Core while supporting hidden anchored number scrubbing.
inputDiagnostics() reports coalesced moves, pointer-position requests, index
updates, candidates, path depths, phase deliveries, focus/capture/popup/drag
changes, event cost, and explicit fallback walks. NUI-05 has no full-tree fallback.
Retained paint is renderer-neutral. processDirty() caches text layout and
per-node shape, image, text, and custom fragments, then composes immutable
ordered packets with logical translation, inherited clip, layer, and resource
revision data. A position-only change reuses local geometry and changes only
the packet. Clean calls retain the same snapshot revision and shared data.
ui.processDirty();
Nikerva::UIPaintSnapshot snapshot = ui.paintSnapshot();
// Copies keep their immutable packets and fragments alive independently.
auto packets = snapshot.packets();UIFontCatalog supplies revisioned renderer-neutral glyph metrics and an
optional opaque atlas resource reference. UITextLayoutOptions chooses wrapping
or a single line, clip or measured ... overflow, and an optional full-text
tooltip that is active only when geometry is actually truncated. Every text
packet intersects its inherited clip with its own resolved bounds. Text geometry
is cached by content, font, size, logical constraint, DPI, layout policy, and
catalog revision. Image and atlas
references use non-zero logical revisions, so an older snapshot remains safe
while a backend consumes a newer one. UICustomPaint similarly owns immutable
primitive data with an explicit revision and never stores renderer handles.
UITextWrapping provides four policies through setTextLayout():
Wrappreserves the existing behavior: break between Unicode codepoints.NoWrapkeeps one line, withCliporEllipsisoverflow.WordWrapprefers spaces/tabs and breaks after existing hyphens.IdentifierWrapadditionally prefers ASCII camelCase/acronym boundaries (Flight|Control,XML|Parser) and breaks after_,.,/, and\.
The two word-based policies share one line-break calculation between measurement
and paint. They preserve explicit newlines (including CRLF and empty lines),
display tabs as single spaces, and omit boundary whitespace at soft line breaks.
Words too wide for an empty line fall back to codepoint wrapping. These are
deterministic UI rules, not language-specific hyphenation or full Unicode word
segmentation. They never insert a hyphen or modify the stored text. Wrapped text
retains the existing clipping behavior; Ellipsis applies to NoWrap. A fixed
row still needs sufficient height for its wrapped lines.
(void)actionLabel.setTextLayout({
.wrapping = Nikerva::UITextWrapping::IdentifierWrap,
.overflow = Nikerva::UITextOverflow::Clip,
});UIRuntime::tooltip() is the resolved hover state. Applications that want
visible tooltip paint create one UITooltipPresenter in a surface-filling
retained overlay root. The presenter is non-interactive, follows the current
logical pointer position, wraps at its configured maximum width, clamps to the
surface, and disappears as soon as Core clears the hover tooltip.
paintDiagnostics() reports rebuilt or reused text layouts, glyphs, fragments,
vertices, packets, snapshot publications, and referenced resources. Virtual
collections publish through this same path. Cutover-required composite controls
and NikreonEditor integration remain later stages; current UIFrame
applications are unchanged.
VulkanRetainedRenderer is the Vulkan-side consumer for one native surface.
The host owns frame fences and calls beginFrame(slot) only after that slot's
fence signals. Each slot has independent mapped shape and textured-instance
storage, so updating one slot cannot overwrite data used by another frame in
flight. Unchanged ranges, descriptors, compatible batches, clips, and packet
metadata are reused. The renderer caches a maximal compatible batch plan for each
immutable snapshot. It may cross only non-overlapping packets inside the same
popup/layer/clip domain; resource changes, custom ranges, and painter-order overlap
remain explicit boundaries. Shape and glyph/image quads use instanced draws.
Revisioned image/font resources retire only after no
submitted slot and no current snapshot references them.
Core paint packets remain in logical surface coordinates. The Vulkan consumer
converts positions, extents, scissor rectangles, border widths, and corner radii
with the actual per-axis framebufferSize / logicalSize ratio. dpiScale is
independent metadata for text/resource/style decisions; it is not a substitute
for framebuffer conversion. The offscreen regression intentionally uses DPI
1.25 with a 1:1 framebuffer ratio and verifies the resulting pixel and clip.
#include "Nikerva/Renderer/Vulkan/VulkanRetainedRenderer.hpp"
Nikerva::VulkanRetainedRenderer backend{
device, physicalDevice, graphicsQueue, graphicsQueueFamily,
uploadCommandPool, renderPass};
// The host has already waited for frameFences[frameSlot].
backend.beginFrame(frameSlot);
const auto snapshotResult = backend.consume(ui.paintSnapshot());
if (snapshotResult != Nikerva::VulkanRetainedSnapshotResult::Updated &&
snapshotResult != Nikerva::VulkanRetainedSnapshotResult::Reused) {
backend.finishFrame(Nikerva::VulkanRetainedFrameDisposition::Abandoned);
return;
}
backend.recordTimestampBegin(commandBuffer); // before the render pass
// begin render pass
backend.record(commandBuffer);
// end render pass
backend.recordTimestampEnd(commandBuffer);
// submit commandBuffer with frameFences[frameSlot]
backend.finishFrame(Nikerva::VulkanRetainedFrameDisposition::Submitted);Resource uploads use immutable UIResourceReference revisions. Upload submits
to the supplied graphics queue and waits on one upload fence, so the owning
thread must externally synchronize queue access. Ordinary frames never wait
inside the backend. Swapchain/render-pass replacement is host-owned: after all
referencing frame fences complete, call recreatePipelines(newRenderPass).
VulkanRetainedRendererDiagnostics separates preparation, geometry-write,
resource-upload, command-generation, batch/draw, fragmentation/compaction, and
GPU timestamp costs.
VirtualizationController is the renderer-neutral collection core. The
application still owns every domain object and supplies only an ordered count
and opaque 128-bit stable keys through UIVirtualItemSource. After an accepted
application mutation, call the matching notifyInsert, notifyErase,
notifyMove, or notifyUpdate; ordinary scrolling never reads the application
collection. Filtering and sorting remain application-owned and use an explicit
synchronize() of the new presentation order.
The controller keeps an incremental order/extent index. Fixed item extents use
constant-time visible-range arithmetic. Variable extents use logarithmic
cumulative lookup and setMeasuredExtent(). Both preserve the key at the top
of the viewport when items or measurements change before it. Item extents are
logical units and include any row gap the future collection control wants.
UIVirtualCellHost maps logical cells to consumer-owned widget templates. It
must outlive the controller and must not call back into it. The controller calls
resetCell() immediately before every new binding, so hover, handlers, edit
buffers, and custom template state cannot leak from one item to another. Cell
handles carry a generation that changes on recycle. Selection, expansion,
focus, edit, capture, and accessibility identity live under the stable item key
instead, so they survive recycling and application-owned filter/sort resets.
Nikerva::UIVirtualizationConfig config;
config.estimatedItemExtent = 24.0F;
config.overscanItems = 2;
config.recycleReserve = 4;
// cellHost creates/resets/binds future concrete widget templates.
Nikerva::VirtualizationController virtualItems{cellHost, config};
virtualItems.synchronize(itemSource);
virtualItems.setViewport({scrollOffset, viewportHeight});
const Nikerva::UIVirtualRealizeResult result = virtualItems.realize();
// The domain collection commits first, then only the changed keys are read.
domainItems.insert(domainItems.begin() + index, newItem);
virtualItems.notifyInsert(itemSource, index, 1);The controller exposes live-cell, key-read, node-allocation, index-visit, extent-update, recycling, and per-realize timing diagnostics.
UIListView, responsive UIGridView, UITreeView, and UITableView turn that
model into persistent retained controls. Each control owns its root, clipped
scrolling content, bounded template pool, interaction state, and realized
accessibility projection. The application still owns item data, filtering,
sorting, commands, and drag payload lifetime. The source and every object
captured by a cell factory must outlive the control.
#include "Nikerva/UI/Retained/Collections.hpp"
Nikerva::UICollectionCellFactory rows =
[&names](const Nikerva::UIContainerHandle& parent) {
auto root = parent.createContainer("project-row");
auto label = root.createLabel({}, "project-row-name");
Nikerva::UICollectionCellInstance cell;
cell.root = root;
cell.reset = [label] { (void)label.setText({}); };
cell.bind = [&names, label](
const Nikerva::UICollectionCellBinding& binding) {
(void)label.setText(names.at(binding.virtualization.item));
};
cell.update = cell.bind;
return cell;
};
Nikerva::UICollectionViewOptions listOptions;
listOptions.virtualization.fixedItemExtent = 24.0F;
Nikerva::UIListView list{ui, panel, filteredKeys, rows, listOptions};
list.setViewportSize({320.0F, 240.0F});
ui.processDirty();
Nikerva::ConnectionGroup subscriptions;
subscriptions.add(list.onSelectionChanged(
[](const Nikerva::UICollectionSelectionChange& change) {
// Translate stable keys into an application command here.
}));
// Mutate application data first, then describe only the affected range.
filteredKeys.insert(index, addedKeys);
list.notifyInsert(index, addedKeys.size());setViewportSize() requests fixed root layout size; the effective viewport is
always derived from final resolved root bounds. Ordinary fill/grow composition
does not call it. UIGridView derives its lane count from that viewport and
minimum cell width.
UITreeView consumes an application-owned parent/child source and incrementally
inserts or erases only visible flattened descendants during expand/collapse and
structural mutations. UITableView virtualizes rows while retaining headers;
column sort callbacks request an application reorder rather than sorting domain
objects inside Core. Selection, tree expansion, row focus, and table cell focus
use stable keys, so explicit filter/sort synchronize() calls do not attach
state to recycled templates. Tree rows reserve a real retained disclosure button;
its visible bounds are its exact pointer target, while row click selection,
double-click expansion, Enter activation, and Left/Right navigation remain
separate stable-key operations across recycling.
Every concrete collection owns a generic retained vertical UIScrollbar driven
by its resolved virtual viewport and content extent. UIScrollbarOptions selects
Auto, Always, or Hidden, orientation, line step, thickness, and minimum
thumb size, plus ReserveSpace or OverlayContent placement. Collections default
to editor-style Auto + ReserveSpace: overflow reserves a gutter, a short Auto
collection keeps its full width, Always reserves, and Hidden never reserves.
The reserved extent is the interactive scrollbar thickness plus reserveGap,
so content paint, clips, and hit regions end before the visible track instead of
touching it. Overlay placement ignores that separation and keeps the full viewport.
The standalone control supports vertical or horizontal wheel, keyboard,
track-page, and captured absolute-position thumb input. Collection wheel offsets
clamp before entering virtualization, so overshoot reaches exact zero/maximum,
and a handled nested wheel event does not also scroll an ancestor. Collection
metric synchronization is internal output and never writes resolved viewport
dimensions back into parent-facing layout inputs.
NUI-10 adds the reusable form, shell, popup, navigation, and composition controls required by the current NikreonEditor inventory. These remain isolated library controls: NikreonEditor does not use them until the later hard cutover.
| Retained control | Current named consumer |
|---|---|
UITextField, UISearchFilterBar |
ContentBrowserPanel asset search/type filters and Project Browser form fields |
UINumberField, UIVectorNumberField, UISliderNumberField |
Inspector scalar/vector channels, camera values, opacity, roughness, and other bounded numeric properties |
UICheckboxHandle, UIToggleSwitch |
Inspector boolean rows and editor settings/tool-state presentation |
UIPropertyGrid |
Inspector, Project Settings, Material Editor, Input Action, and Mapping Context property forms |
UISplitter, UITabView, UIDockModel |
EditorUI hierarchy/viewport/inspector splits and bottom-dock tabs |
UIMenuBar, UIContextMenu |
EditorUI command menus and ContentBrowserPanel item/filter menus |
UIDialog |
EditorUI close guard plus project/content confirmation flows |
UISearchableComboBox |
ToolbarPanel mode choices and ContentBrowserPanel type selection |
UIBreadcrumb, UICollapsibleSection |
ContentBrowserPanel path navigation/folder tree and responsive editor sections |
UIToolbar, UIStatusBar, UIProgressControl |
ToolbarPanel and ContentBrowserPanel action/status/import presentation |
UIPickerView |
Inspector asset-valued fields and Content Browser selection flows |
UICustomWidget |
editor viewport/custom preview composition and style-state extensions |
UINumberFieldMode selects plain text entry, horizontal scrubbing, spinner
buttons, or scrubbing plus spinner buttons without duplicating parsing, focus,
range, or edit-lifecycle logic. UISliderNumberField composes the same exact
entry with a retained slider and keeps both values synchronized by targeted
mutations. Axis strips are optional presentation metadata for X/Y/Z/W channel
compositions. UIVectorNumberField composes two to four persistent number
fields, distributes the resolved width equally, and emits the active component
plus the complete vector value in one typed transaction. It has preferred and
minimum content measurements but no Inspector-specific fixed width, so a parent
value column remains authoritative. Scrubbing uses accumulated relative motion, applies Shift/Ctrl
fine/coarse multipliers per movement segment, hides the cursor after its threshold,
returns it to the captured press anchor, and preserves the field's arranged/text
origin while only its value changes. Optional units are a separate retained suffix
label: they never enter the parse buffer, caret, or selection, hide while text
editing is active, and return on commit/cancel. Genuinely truncated units use
measured ellipsis plus a conditional full-value tooltip.
UIToggleSwitch reuses checkbox focus, keyboard, checked-state,
and begin/update/commit/cancel semantics while publishing a switch track/thumb
visual through replacement custom paint, so no default checkbox rectangle leaks
behind it. A standard retained checkbox remains the compact square-mark variant.
Composite controls own their retained nodes, local presentation state, and event connections. They destroy their complete subtree when the C++ wrapper is destroyed. The runtime, parent view, application-owned virtual source, and any objects captured by factories must outlive the corresponding control.
UIPropertyGridSource supplies stable keys and typed presentation metadata.
The grid selects the exact editor for std::string, bool, float,
std::array<float, 3>, or choice-key values and emits
begin/update/commit/cancel events. Vector rows use one responsive X/Y/Z group
and publish the complete array when one component changes. It never converts or
validates values, mutates a Scene or asset, or records history. The application
does that work and then calls notifyUpdate() for the accepted row. Only
realized or explicitly changed realized rows read metadata. Because the default
property text flow is SingleLineEllipsis, compact rows stay one line and a
read-only value exposes its complete text through a tooltip only while it is
genuinely truncated. A field may explicitly select UIPropertyTextFlow::Wrap;
the grid then measures the resolved value-column width with Core's glyph metrics
and updates that stable key's variable virtual extent in the viewport-dependency
stage so following rows move below every rendered line.
#include "Nikerva/UI/Retained/PropertyGrid.hpp"
Nikerva::UIPropertyGrid properties{ui, inspectorHost, propertySource};
properties.setViewportSize({360.0F, 480.0F});
Nikerva::Connection edit = properties.onEdit(
[&](const Nikerva::UIPropertyGridEdit& event) {
// Validate and apply through the application's command/history layer.
if (event.phase == Nikerva::UIEditPhase::Commit &&
applyProperty(event.field, event.value)) {
properties.notifyUpdate(indexOf(event.field), 1U);
}
});Closed menu, combo, picker, and dialog popup roots stay retained but are
automatically excluded from hit testing and paint snapshot composition by
UIRuntime. Open roots retain a point, below-opener, or centered placement policy
and Core recomputes/clamps placement from current surface-local bounds after
resize and DPI changes. UIDockModel stores only a checked stable-key split/leaf/tab tree;
serialization, native-window decisions, and editor workspace policy remain
application-owned. UICustomWidget supplies a retained content host, optional
local state, renderer-neutral custom paint, routed-event hooks, and explicit
style-state classes without acquiring renderer or editor dependencies.
UIControlIconSet lets an application map renderer-neutral image resources to
generic roles such as combo chevrons, TreeView disclosure, check, search/clear,
spinner arrows, collapsible sections, submenu, close, and reset. Controls copy
the relevant image values into persistent nodes when composed. Core never loads
an atlas or depends on engine resource types. The host supplies any icon atlas;
the small examples use text and shapes and require no icon atlas.
Combo boxes use one static downward chevron. Searchable combinations focus their
retained text field when opened, while small enums may disable search explicitly.
Menu/combo popups opt into consuming a repeated press on their own opener without
closing or duplicating the popup; ordinary popups retain outside-dismiss behavior.
NikervaRetainedControlsTests supplies broader control construction fixtures
beyond the small preferences example. It covers focus, UTF-8
editing, capture, modal blocking, popup paint exclusion, styles, DPI, lifetime,
typed PropertyGrid edits, changed/realized row work, dock validation, and clean-
frame diagnostics.
UISurface gives each UI tree its own local coordinate space. UIFrame
carries the surface, resolved style, shape renderer, text renderer, input
context, and synchronized clipping.
UIContext input;
UIStyle style;
UISurface hud{{24.0f, 24.0f}, {320.0f, 180.0f}};
UIFrame frame{input, shapes, text, style, hud};
Label title{"hud.title", "Mission"};
title.setBounds({12.0f, 12.0f}, {180.0f, 24.0f});
title.setStyleClass("heading");
title.render(frame);The core target provides:
Button,Checkbox,Slider,NumberInput, andTextInputLabel,Panel, texturedImage/Icon,NineSlicePanel, andProgressBarScrollContainerwith clipping, child ownership, content offsets, wheel scrolling, draggable thumbs, and theme-owned scrollbar styling- Linear, dock, stack, anchor, padding, gap, and alignment layouts
Composite controls own their editing details. Render Slider, NumberInput,
and TextInput with render(frame) to draw shape, value text, selection, and
caret without reaching into nested controls.
Slider volume{"hud.volume", 0.8f};
volume.setBounds({12.0f, 52.0f}, {180.0f, 20.0f});
volume.update(input, text, style);
volume.render(frame);UIBuilder exposes type-specific builders over one private element record.
Unsupported combinations are rejected by C++ constraints instead of being
stored and silently ignored.
| Builder family | Supported content and behavior |
|---|---|
PanelBuilder / surface() |
Normal children, layout, scrolling, ordered custom paint |
LabelBuilder / text() |
Text and tooltip only; never acquires action state |
ImageBuilder / icon() |
Renderer-neutral texture ID, UVs, tint, intrinsic pixel size, fit, snapping |
SeparatorBuilder / SpacerBuilder |
Layout-only visual separation or empty extent |
ButtonBuilder |
Text, secondary text, icon, selected/enabled state, click callback |
| Choice/menu builders | Popup children plus their typed choice/menu properties |
| Value builders | Only the value, range, label, edit, and callback facets relevant to that control |
Linear layouts support explicit minimum/maximum size, margin, grow, and shrink. Overlay children share the parent content rectangle: declaration order is painter order and input walks the same children front-to-back. Scroll clips propagate to input, images, text, and custom paint automatically.
UIImageFit::Contain, Cover, and Stretch operate on the supplied intrinsic
pixel size and UV rectangle. UI coordinates are physical framebuffer pixels;
pixel snapping rounds the image origin or both image edges in that coordinate
space and never modifies atlas UVs.
Configure NIKERVA_ENABLE_DIAGNOSTICS=ON to compile legacy frame validation
and per-frame statistics in Debug or RelWithDebInfo. Release and MinSizeRel
explicitly compile NIKERVA_DIAGNOSTICS=0, even when the option is enabled.
With that instrumentation disabled, legacy statistics snapshots remain
available and return zero values. Retained UIPaintSnapshot data and
paintDiagnostics() are part of Core behavior and remain available in every
configuration.
The builder reports stable, deduplicated layout findings. The Vulkan backend
counts actual batches, descriptor/scissor changes, uploads, texture-slot splits,
glyphs, and draw commands where they occur. statistics() returns the previous
completed frame so a statistics UI does not recursively include its own partial
submission. Instrumentation does not add flushes or render-order boundaries.
UIStyleParser supports a deliberately small CSS-like format with
type, type.class, type#id, and type.class#id selectors. ID rules override
class rules. Shared theme data is application-neutral; editor panel sizing,
viewport colors, and toolbar configuration belong in the editor.
slider.hud {
fill: 0.12, 0.14, 0.18, 1;
accent: 0.36, 0.58, 0.82, 1;
}
text.heading {
color: 0.92, 0.98, 1, 1;
font-scale: 1.1;
}Text inputs support focus, UTF-8 insertion, caret movement, selection, clipboard shortcuts, and horizontal overflow scrolling. Stylesheets can set their text, placeholder, selection, caret, and scrollbar colors.