etyOS

Nirvana: writing applets

Components

Everything a scene can hold, by what it is for, with the setters each one has.

All components live in nirvana_protocol::scene and come in with use nirvana_protocol::prelude::*. Each has a constructor and chainable setters, and becomes a Node with .into() (containers take impl Into<Node>, so inside a .child(…) you rarely write it).

The shape of a window

Three shells, for three kinds of applet:

Page

A centred column of content with a large title. For an applet that is one screen of settings or one list.

Page::new()
    .title(tr("Sound"))
    .subtitle(tr("Output, input and what plays where."))
    .child(group)

.wide() gives the column more room. .key(…) names the page, so that replacing it with another plays the entrance of a new page.

SplitView

A glass sidebar of destinations on the left, one page on the right. For an applet with several screens (Settings).

SplitView::new()
    .sidebar_item(SidebarItem::new("nav.wifi", tr("Wi-Fi")).icon("wifi").selected(true).value(tr("Home")))
    .sidebar_item(SidebarItem::new("nav.sound", tr("Sound")).icon("speaker"))
    .child(self.page())

SidebarItem also takes .detail(…) (a second line), .busy(true), .action(icon, label) (a small button at the edge, which sends click to <id>.action), .context() (right click), .reorder(group) and .drop_target().

Pane

A working surface instead of a column of settings: a toolbar, a scrolling body, an optional bar of actions and a status bar. For tools (Calculator, Files, the Terminal). Used alone or as the content of a SplitView.

Pane::new()
    .tool(Text::new(tr("Calculator")).style(TextStyle::Headline))
    .tool(Button::icon_only("history", "clock", tr("History")).plain())
    .child(body)
    .status(Text::new(tr("12 items")).style(TextStyle::Footnote))

Grouping and layout

Group and Row

The workhorse. A Group is an inset rounded list; a Row is one line of it: an icon, a title and subtitle on the left; a value, controls and an accessory on the right.

Group::new()
    .header(tr("Network"))
    .footer(tr("Known networks are joined automatically."))
    .child(
        Row::new(tr("Wi-Fi"))
            .id("open.wifi")                 // the whole row is clickable
            .icon("wifi")
            .value(tr("Home"))
            .accessory(Accessory::Chevron),
    )
    .child(Row::new(tr("Airplane Mode")).trailing(Switch::new("airplane", self.airplane).label(tr("Airplane Mode"))))
Row setterWhat it does
.id(…)Makes the whole row clickable (click).
.key(…)Its identity in a changing list.
.subtitle(…), .value(…)The quiet line under the title; the text on the right.
.icon(…), .image(Some(name_or_path))A glyph; or an application’s own icon.
.trailing(node)A control on the right. Can be called several times.
.accessory(…)Chevron, Check, Spinner.
.tone(…)Tone::Destructive for a row that deletes, Tone::Accent for one that opens something.
.context()Also send context on a right click.
.disabled(true)Shown, not pressable.

Group has .loading(true) (a spinner by the header) and .key(…).

Stack

Free layout where rows do not fit: children in a line, with a gap from the tokens.

Stack::horizontal().gap(Gap::Sm).wrap().child(a).child(b)
Stack::vertical().gap(Gap::Lg).align(Align::Center)

Gap: Xs, Sm, Md, Lg, Xl. Align: Start, Center, End, Stretch. Justify: Start, Center, End, Between.

Text and marks

ComponentUse
Text::new(…)Text. .style(TextStyle::…): LargeTitle, Title, Headline, Body, Callout, Footnote. .tone(Tone::…): Primary, Secondary, Tertiary, Destructive, Success. .centered().
IconView::new(name)A glyph from the icon set. .size(Size::…), .tile(tint) for a tile.
Badge::new(…)A short label. .tone(BadgeTone::…). Not for passing states: those go in a subtitle.
Spinner::new(), Spinner::small()Work under way.
Banner::new(BannerTone::…, title)A message about the page: Info, Success, Warning, Error. .body(…), .action(button).
EmptyState::new(title)Nothing to show, and why. .icon(…), .body(…), .action(button).
Progress::new()A bar. .level(0.0..=1.0), .label(…).

Icons are named (wifi, trash, search, folder, settings, …): about 150 glyphs on a 24 px grid. A tint passed for an icon tile is accepted and ignored: tiles are quiet by design.

Controls

Button

Button::new("join", tr("Join")).primary()
Button::new("forget", tr("Forget This Network")).destructive()
Button::new("more", tr("More")).plain().size(Size::Small)
Button::icon_only("refresh", "refresh", tr("Refresh"))   // the label is for assistive technology

Variants: .primary() (one per view), secondary (the default), .plain(), .destructive(). Also .icon(name), .size(Size::Small | Regular | Large), .fill() (as wide as its place), .disabled(true), and .opens_menu(): the click then carries the point to open a Menu at.

The rest

ComponentConstructorNotes
SwitchSwitch::new(id, checked).label(…) is required when it stands without a row title.
SegmentedSegmented::new(id, &["Day", "Week"], active)One of 2–5 views or modes.
SliderSlider::new(id, value, min, max).step(…), .label(…). Acts as it moves.
TextFieldTextField::new(id, value).placeholder(…), .label(…), .icon(…), .secure(), .multiline(), .autofocus(), .revision(n), .disabled(true).
ShortcutShortcut::new(id, "Super+Space")Records a key combination, shown as keycaps.
Keypad, KeyKeypad::new(key, columns).key(Key::new(id, "7"))A grid of keys; KeyRole: Digit, Operator, Action, Primary.
PictureChoicePictureChoice::new(id, active)A choice drawn as pictures instead of words.
QuickGridQuickGrid::new().action(id, icon, label, on, detail)Tiles of quick actions.

Over the window

Sheet

A modal question or a detail view. Add it as a child of the page while it is open.

Sheet::new(tr("Forget “Home”?"))
    .id("forget")                       // Escape and a click outside send `dismiss` here
    .message(tr("This computer will no longer join it automatically."))
    .action(Button::new("forget.cancel", tr("Cancel")))
    .action(Button::new("forget.confirm", tr("Forget")).destructive())

A sheet can hold children (.child(…)): a field for a password, a few rows. The actions share the width, the primary one on the right.

A popup at a point: the answer to a right click, or to a button with .opens_menu().

// in event:   "row" if event.is("context") => self.menu_at = event.point(),
// in render:
if let Some(point) = self.menu_at {
    pane = pane.child(
        Menu::new("menu", point)
            .item(MenuItem::new("menu.open", tr("Open")))
            .item(MenuItem::new("menu.rename", tr("Rename…")).shortcut("F2"))
            .separator()
            .item(MenuItem::new("menu.delete", tr("Move to Trash")).destructive()),
    );
}

A click elsewhere, Escape, or the window losing focus sends dismiss to the menu’s id: set menu_at = None then, and after any item is chosen. MenuItem also has .icon(…), .checked(true), .disabled(true).

Data

ComponentFor
DataTableRecords with sortable columns and one selected row. TableColumn::new(id, label).numeric(), TableRow::new(id, cells).
Chart, ChartSeriesA history over time. A second series is a dashed line, never another color.
StatGrid, StatTiles of numbers.
BarGridMany small levels side by side (per-core load).
MeterOne labelled level.
TabBar, TabTabs in a toolbar, each with a close button.
Gallery, GalleryItemPictures to choose one of.
CalendarGrid, CalendarDay, TaskList, TaskA month, and things to do.

Files

ComponentFor
FileViewFiles as icons or as a table: selection, drag and drop, inline rename. FileLayout: Grid, List, Desktop. The applet owns the selection; the view reports what was done.
PathBarBreadcrumbs that become a text field when clicked.
PreviewA look at a file without opening it.

Live components

These are created once and kept across renders; their content does not come from the scene.

ComponentWhat lives in itNeeds
TerminalA terminal screen (xterm.js). Output goes in with Handle::stream; typing comes back as data.
MediaViewA picture, a video or a sound file, with zoom or playback."media": true in the registry
EditorA plain-text editor.
RegionSelectA rectangle dragged out over a picture (screenshots of an area).
VideoEditorMontage’s workspace: library, preview, tracks."media": true
BrowserNetbird’s window: pages are <webview> elements kept by the runtime."webview": true

Guided steps

For something done in a few steps, one question at a time: Wizard (a rail of steps on glass, a page, back and next), ChoiceCard (a large choice that says what choosing it means), DiskMap (a disk drawn to scale), ProgressRing (long work, its parts ticked off), Persona (a person as they type themselves in), BrandMark (the system’s mark). The installer and the welcome are made of these.

The shell’s own

Dock, DockItem, Start, AppGrid, AppTile, AppRow, Hud, Toast, Desktop, LockScreen, InfoCard, LayoutSwitch, WeatherView: the pieces the dock, the launcher, notifications, the desktop and the lock screen are built from. An ordinary applet has no use for them, but they are components like the rest, and the shell applets are worth reading for how they are used.

Tokens instead of numbers

TypeValues
SizeSmall, Regular, Large
GapXs, Sm, Md, Lg, Xl
TonePrimary, Secondary, Tertiary, Accent, Destructive, Success (Accent renders as primary: there is no accent hue)
TextStyleLargeTitle, Title, Headline, Body, Callout, Footnote
TintBlue, Green, Orange, Red, Purple, Indigo, Teal, Pink, Yellow, Gray, Accent

Something wrong or missing on this page? Say so in the tracker (choose “Site and docs”).

Esc