Files
Tushar Rohilla e5295f36aa Add Afterglow 1.0.6, a PS5 internet radio and music visualizer
222 stations in 17 genres, 56 visualizer scenes, Ogg and Opus playback, station logos, crossfade, My Music from USB, a command box and live BPM sync.
2026-10-16 00:59:10 -04:00

31 KiB

The component library

The kit has three levels, and you can enter at any of them:

Level What you get Where
Shapes Rectangles, arcs, text, images, glass: you draw everything KIT.md
Widgets ui::Painter: stateless themed controls (a button, a switch); you keep their values and animate them THEMES.md
Components Whole pieces of interface that own their focus, values, motion and sound: a list, a grid, a dialog, a form this page

A component is what you reach for when you want a working settings screen or a library grid this afternoon. You place it, forward input, and react to what it reports. Everything about how it looks and feels is a field you can change.

In the app, open Component Library (L1 / R1). L2 / R2 turn its pages, Square cycles each page's variants, Options restyles everything in the next theme.

The Component Library design in motion

The five rules

Every component follows them, so knowing one is knowing all of them (component.hpp):

  1. style is a public struct of plain values. It holds a ui::Theme (palette, how surfaces are built, corners, type, speed, bounce, sound set) and the component's own knobs. Change any of it at any time; state is kept.
  2. set_bounds(rect) says where it is, in the 1920 x 1080 virtual canvas.
  3. handle(input, feedback) takes one frame of input while the component has the focus and returns a ui::Event (moved, changed, activated, cancelled, refused or none). It plays its own cues and rumble.
  4. update(dt) advances its animation. Call it every frame, focused or not.
  5. draw(canvas) is const. It may run more than once per frame.
#include "ui/components.hpp"   // everything, or one header per component

class Library final : public app::Concept
{
    ui::GridView grid_;
    ui::Dialog confirm_;

    void update(const InputFrame &input, float dt, app::Feedback &feedback) override
    {
        clock_ += dt;
        if (confirm_.is_open())
        {
            if (confirm_.handle(input, feedback) == ui::Event::activated && confirm_.choice() == 1)
                remove(grid_.focus());
        }
        else if (grid_.handle(input, feedback) == ui::Event::activated)
        {
            confirm_.open(feedback);                   // overlays play a cue as they appear
        }
        grid_.update(dt);
        confirm_.update(dt);
    }

    void draw(app::Frame &frame) const override
    {
        ui::Canvas scene{frame.scene, context_.fonts, frame.glass_texture, clock_};
        grid_.draw(scene);
        frame.glass = confirm_.visible();              // blur the screen behind the dialog
        ui::Canvas overlay{frame.overlay, context_.fonts, frame.glass_texture, clock_};
        confirm_.draw(overlay);
    }
};

Only one component should receive handle() in a frame: the one that has the focus. Moving the focus between components is the screen's job, and there are three ways to do it, from simplest to most general:

  • By hand: a column index and set_active(), as lists_page.cpp does with left and right.
  • Edge exits: collections, groups and pickers have style.exits (ui::EdgeExits). With an exit open, pushing past that edge returns Event::none and exit() names the edge, so the screen hands the focus to the neighbour on that side (data_page.cpp).
  • ui::FocusGroup: register any rectangles (your own drawings, buttons, whole components) and it picks the neighbour in a direction, remembers where you were in each region, and scopes the focus to a modal layer (layout_page.cpp).

Rectangles come from the layout helpers (ui::Row, ui::Column, ui::GridLayout, ui::Wrap, ui::SplitView), or from your own arithmetic.

Customising, from broad to fine

  1. Pick a theme. component.style.theme = ui::themes()[n]; restyles the whole component: surfaces are built the theme's way (frosted glass, hard offset shadows, bevels, pen strokes), corners are round, cut or pixel notched, text uses the theme's faces, motion its speed and bounce. All thirty built-in themes work with every component. A theme is plain data, so making your own is filling in a struct (THEMES.md).
  2. Edit the theme's tokens for this component only. The style holds its own copy: list.style.theme.radius = 0; list.style.theme.accent = lime;
  3. Turn the component's knobs. Sizes, gaps, text sizes, variants, behaviour: grid.style.columns = 6; tabs.style.kind = ui::TabKind::underline; Each component's page below lists every knob with its default.
  4. Fill a slot. Slots are std::function members that replace part of the drawing with your own code: a list row's leading image, a grid cell, a sheet's content, a ring's centre.
  5. Change its voice. style.sounds maps each thing a component does to a cue. Assign another cue, name a sound set, scale gain and rumble, or set a cue to audio::Cue::count to silence it.
  6. Respect the player. Mirror the reduced-motion setting into style.reduced_motion: travel and bounce become plain fades.

The catalogue

Group Components Guide
Lists ListView components/lists.md
Collections Card, GridView, Carousel components/collections.md
Navigation TabBar, SideNav, Breadcrumb, PageDots, Menu components/navigation.md
Structure RadialMenu, Wizard, Accordion, TreeView, JumpBar components/structure.md
Overlays Dialog, Sheet, ToastStack, Tooltip components/overlays.md
Notifications NotificationStack, NotificationCenter, NotificationBell components/notifications.md
Actions PushButton, IconButton, ButtonGroup, SplitButton, HoldButton, QuickAction, QuickActionBar, Banner, CoachMark components/actions.md
Forms Form, Stepper, ChoicePicker, TextField components/forms.md
Pickers Select, CheckGroup, RadioGroup, TagSelect, ColorPicker, WheelPicker, DatePicker, TimePicker, Slider, RangeSlider components/pickers.md
Entry Keyboard, PinEntry, SearchField, KeyBinder, InputPrompt components/entry.md
Indicators ProgressBar, ProgressRing, Spinner, Meter, Badge, Chip, Avatar, AvatarStack, Rating, Counter, Skeleton, StatTile, EmptyState components/indicators.md
Data Table, DetailList, Timeline, BarChart, LineChart, DonutChart, Legend, Calendar, Countdown, StorageBar, StatusBar components/data.md
Media TextView, ImageViewer, MediaControls, LoadingScreen components/media.md
Game PauseMenu, UnlockPopup, ProfilePicker, InventoryGrid, HealthBar, AmmoCounter, ObjectiveTracker, MinimapFrame, NodeMap components/game.md
Layout FocusGroup, Row, Column, GridLayout, Wrap, SpringLayout, SplitView, ScrollArea, Panel, Divider, SectionHeader, Spacer, Transition components/layout.md

Lists

ui::ListView · knobs, slots, events and cues

Lists in motion
Lists: components-lists-bar Lists: components-lists-fill
Lists: components-lists-glow Lists: components-lists

Collections

ui::Card, ui::GridView, ui::Carousel · knobs, slots, events and cues

Collections in motion
Collections: components-collections-hero Collections: components-collections-paged
Collections: components-collections-posters Collections: components-collections-wheel
Collections: components-collections

Navigation

ui::TabBar, ui::SideNav, ui::Breadcrumb, ui::PageDots, ui::Menu · knobs, slots, events and cues

Navigation in motion
Navigation: components-navigation-boxed Navigation: components-navigation-menu
Navigation: components-navigation-segmented Navigation: components-navigation-underline
Navigation: components-navigation

Structure

ui::RadialMenu, ui::Wizard, ui::Accordion, ui::TreeView, ui::JumpBar · knobs, slots, events and cues

Structure in motion
Structure: components-structure-compact Structure: components-structure-quick
Structure: components-structure-radial Structure: components-structure-vertical
Structure: components-structure

Overlays

ui::Dialog, ui::Sheet, ui::ToastStack, ui::Tooltip · knobs, slots, events and cues

Overlays in motion
Overlays: components-overlays-bottom Overlays: components-overlays-compact
Overlays: components-overlays-sheet Overlays: components-overlays-toasts
Overlays: components-overlays

Notifications

ui::NotificationStack, ui::NotificationCenter, ui::NotificationBell · knobs, slots, events and cues

Notifications in motion
Notifications: components-notifications-accent Notifications: components-notifications-center
Notifications: components-notifications-compact Notifications: components-notifications-progress
Notifications: components-notifications-queue Notifications: components-notifications

Actions

ui::PushButton, ui::IconButton, ui::ButtonGroup, ui::SplitButton, ui::HoldButton, ui::QuickAction, ui::QuickActionBar, ui::Banner, ui::CoachMark · knobs, slots, events and cues

Actions in motion
Actions: components-actions-accent Actions: components-actions-compact
Actions: components-actions-menu Actions: components-actions-tour
Actions: components-actions

Forms

ui::Form, ui::Stepper, ui::ChoicePicker, ui::TextField · knobs, slots, events and cues

Forms in motion
Forms: components-forms-compact Forms: components-forms-help
Forms: components-forms-panel Forms: components-forms-wide
Forms: components-forms

Pickers

ui::Select, ui::CheckGroup, ui::RadioGroup, ui::TagSelect, ui::ColorPicker, ui::WheelPicker, ui::DatePicker, ui::TimePicker, ui::Slider, ui::RangeSlider · knobs, slots, events and cues

Pickers in motion
Pickers: components-pickers-compact Pickers: components-pickers-hsv
Pickers: components-pickers-select Pickers: components-pickers

Entry

ui::Keyboard, ui::PinEntry, ui::SearchField, ui::KeyBinder, ui::InputPrompt · knobs, slots, events and cues

Entry in motion
Entry: components-entry-flat Entry: components-entry-numeric
Entry: components-entry-pin Entry: components-entry-prompt
Entry: components-entry

Indicators

ui::ProgressBar, ui::ProgressRing, ui::Spinner, ui::Meter, ui::Badge, ui::Chip, ui::Avatar, ui::AvatarStack, ui::Rating, ui::Counter, ui::Skeleton, ui::StatTile, ui::EmptyState · knobs, slots, events and cues

Indicators in motion
Indicators: components-indicators-chunky Indicators: components-indicators-loaded
Indicators: components-indicators-slim Indicators: components-indicators-status
Indicators: components-indicators

Data

ui::Table, ui::DetailList, ui::Timeline, ui::BarChart, ui::LineChart, ui::DonutChart, ui::Legend, ui::Calendar, ui::Countdown, ui::StorageBar, ui::StatusBar · knobs, slots, events and cues

Data in motion
Data: components-data-charts Data: components-data-plain
Data: components-data-range Data: components-data

Media

ui::TextView, ui::ImageViewer, ui::MediaControls, ui::LoadingScreen · knobs, slots, events and cues

Media in motion
Media: components-media-compact Media: components-media-loading
Media: components-media-reader Media: components-media-zoom
Media: components-media

Game

ui::PauseMenu, ui::UnlockPopup, ui::ProfilePicker, ui::InventoryGrid, ui::HealthBar, ui::AmmoCounter, ui::ObjectiveTracker, ui::MinimapFrame, ui::NodeMap · knobs, slots, events and cues

Game in motion
Game: components-game-inventory Game: components-game-nodes
Game: components-game-pause Game: components-game-profiles
Game: components-game

Layout

ui::FocusGroup, ui::Row, ui::Column, ui::GridLayout, ui::Wrap, ui::SpringLayout, ui::SplitView, ui::ScrollArea, ui::Panel, ui::Divider, ui::SectionHeader, ui::Spacer, ui::Transition · knobs, slots, events and cues

Layout in motion
Layout: components-layout-guides Layout: components-layout-modal
Layout: components-layout-panes Layout: components-layout-stacked
Layout: components-layout

The same components in other themes

components-brutal-actions components-brutal-collections
components-brutal-data components-brutal-entry
components-brutal-forms components-brutal-game
components-brutal-indicators components-brutal-layout
components-brutal-lists components-brutal-media
components-brutal-navigation components-brutal-notifications
components-brutal-overlays components-brutal-pickers
components-brutal-structure components-classic
components-clay components-hazard
components-paper components-pico
components-pixel-actions components-pixel-collections
components-pixel-data components-pixel-entry
components-pixel-forms components-pixel-game
components-pixel-indicators components-pixel-layout
components-pixel-lists components-pixel-media
components-pixel-navigation components-pixel-notifications
components-pixel-overlays components-pixel-pickers
components-pixel-structure components-sketch-actions
components-sketch-collections components-sketch-data
components-sketch-entry components-sketch-forms
components-sketch-game components-sketch-indicators
components-sketch-layout components-sketch-lists
components-sketch-media components-sketch-navigation
components-sketch-notifications components-sketch-overlays
components-sketch-pickers components-sketch-structure

Shared pieces

  • ui::Highlight: the gliding focus highlight every list-like component uses, in seven kinds (ring, fill, tint, bar, underline, glow, none). Use it in your own components for the same feel.
  • ui::play_cue and ui::refuse: cues placed in the stereo field by screen position, and the standard soft refusal (quiet error, short rumble, shake, silent on a held direction).
  • ui::fit_label / ui::fit_body: text cut to a width in the theme's faces.
  • ui::StatusKind (overlay.hpp) says what a message is (info, success, warning, danger, question) and has an icon; ui::Status (progress.hpp) is a colour role for indicators (neutral, primary, accent, success, warning, danger). Both resolve to the theme's status colours.
  • ui::EdgeExits (card.hpp): grids and carousels can let the focus leave through an edge instead of refusing, so a screen can chain them.
  • Overlays (Dialog, Sheet, Menu) play a cue as they appear, so their open() and close() take the Feedback; ToastStack::update does too.
  • "Has the screen's focus" is set_active(bool) on lists and ratings and set_focused(bool) on the navigation components (where set_active(index) selects a tab).
  • ui::Feedback: what a component asks the platform to play; the same struct a design receives in update().

Writing a component

Start from list.hpp and list.cpp, the reference.

  • A style struct deriving from ui::ComponentStyle; every number a user might want to change is a commented field with a sensible default.
  • Surfaces, wells, focus rings and text go through ui::Painter with style.theme. No colour literals.
  • Motion uses style.omega() and style.damping(); reduced_motion removes travel.
  • Sounds go through ui::play_cue(feedback, style, style.sounds.x, x).
  • Edges refuse softly and stay silent on input.nav_repeat.
  • Changing style never resets state.
  • A page in the gallery with at least three variants, tests that include "draws in every theme", and a page under docs/components/.
  • You looked at it in at least six themes, light and dark, round and square.

The kit's components do not include anything from app/, concepts/ or demo/: copy src/ui, src/gfx, src/core and src/audio into another project and they come along (ADOPTING.md).