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.
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 five rules
Every component follows them, so knowing one is knowing all of them
(component.hpp):
styleis a public struct of plain values. It holds aui::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.set_bounds(rect)says where it is, in the 1920 x 1080 virtual canvas.handle(input, feedback)takes one frame of input while the component has the focus and returns aui::Event(moved,changed,activated,cancelled,refusedornone). It plays its own cues and rumble.update(dt)advances its animation. Call it every frame, focused or not.draw(canvas)isconst. 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(), aslists_page.cppdoes with left and right. - Edge exits: collections, groups and pickers have
style.exits(ui::EdgeExits). With an exit open, pushing past that edge returnsEvent::noneandexit()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
- 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). - 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; - 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. - Fill a slot. Slots are
std::functionmembers 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. - Change its voice.
style.soundsmaps each thing a component does to a cue. Assign another cue, name a sound set, scale gain and rumble, or set a cue toaudio::Cue::countto silence it. - 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
![]() |
![]() |
![]() |
![]() |
Collections
ui::Card, ui::GridView, ui::Carousel · knobs, slots, events and cues
![]() |
![]() |
![]() |
![]() |
![]() |
Navigation
ui::TabBar, ui::SideNav, ui::Breadcrumb, ui::PageDots, ui::Menu · knobs, slots, events and cues
![]() |
![]() |
![]() |
![]() |
![]() |
Structure
ui::RadialMenu, ui::Wizard, ui::Accordion, ui::TreeView, ui::JumpBar · knobs, slots, events and cues
![]() |
![]() |
![]() |
![]() |
![]() |
Overlays
ui::Dialog, ui::Sheet, ui::ToastStack, ui::Tooltip · knobs, slots, events and cues
![]() |
![]() |
![]() |
![]() |
![]() |
Notifications
ui::NotificationStack, ui::NotificationCenter, ui::NotificationBell · knobs, slots, events and cues
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
Actions
ui::PushButton, ui::IconButton, ui::ButtonGroup, ui::SplitButton, ui::HoldButton, ui::QuickAction, ui::QuickActionBar, ui::Banner, ui::CoachMark · knobs, slots, events and cues
![]() |
![]() |
![]() |
![]() |
![]() |
Forms
ui::Form, ui::Stepper, ui::ChoicePicker, ui::TextField · knobs, slots, events and cues
![]() |
![]() |
![]() |
![]() |
![]() |
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
![]() |
![]() |
![]() |
![]() |
Entry
ui::Keyboard, ui::PinEntry, ui::SearchField, ui::KeyBinder, ui::InputPrompt · knobs, slots, events and cues
![]() |
![]() |
![]() |
![]() |
![]() |
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
![]() |
![]() |
![]() |
![]() |
![]() |
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
![]() |
![]() |
![]() |
![]() |
Media
ui::TextView, ui::ImageViewer, ui::MediaControls, ui::LoadingScreen · knobs, slots, events and cues
![]() |
![]() |
![]() |
![]() |
![]() |
Game
ui::PauseMenu, ui::UnlockPopup, ui::ProfilePicker, ui::InventoryGrid, ui::HealthBar, ui::AmmoCounter, ui::ObjectiveTracker, ui::MinimapFrame, ui::NodeMap · knobs, slots, events and cues
![]() |
![]() |
![]() |
![]() |
![]() |
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
![]() |
![]() |
![]() |
![]() |
![]() |
The same components in other themes
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
|
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
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_cueandui::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 theiropen()andclose()take theFeedback;ToastStack::updatedoes too. - "Has the screen's focus" is
set_active(bool)on lists and ratings andset_focused(bool)on the navigation components (whereset_active(index)selects a tab). ui::Feedback: what a component asks the platform to play; the same struct a design receives inupdate().
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::Painterwithstyle.theme. No colour literals. - Motion uses
style.omega()andstyle.damping();reduced_motionremoves travel. - Sounds go through
ui::play_cue(feedback, style, style.sounds.x, x). - Edges refuse softly and stay silent on
input.nav_repeat. - Changing
stylenever 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).











































































































