Build a richer workspace
Combine advanced layouts, data views, overlays, charts, an editor, files, and terminal layers without losing sight of their real feature boundaries.
Outcome: you can design a compact desktop workspace and select optional Cargo features with confidence.
01
Give every panel a job
Start with one scrollable rail and one flexible work area. SplitPane owns the resize handle, while each ScrollView keeps overflow inside its panel.
Scroll on purpose
Choose vertical, horizontal, or both axes. Scrollbars are enabled by default and follow_end is handy for feeds that should stay near the newest item.
Resize without guesswork
SplitPane::columns and SplitPane::rows accept two views. Minimum sizes protect useful content, and bind_position lets the application retain the divider position.
Mini app 3: a resizable workspace
SplitPane places two views around a draggable divider and enforces a minimum size for each side.
let workspace = SplitPane::columns(navigation, content)
.initial_position(230.0)
.min_start(180.0)
.min_end(420.0);02
Turn rows into decisions
TableView keeps its header fixed while the body scrolls. Typed row IDs make selection useful to the rest of the application.
Typed selection
Build columns first, then rows. bind_selected writes the activated row ID into a State value, while on_select can dispatch an application action.
Rich cells
A cell can show normal text, muted text, a semantic badge, or progress. Column and cell alignment remain explicit.
Keyboard included
Enabled rows participate in focus and keyboard navigation. Disabled rows stay visible but are skipped by selection.
Mini app 1: a selectable table
Columns define sizing, rows keep stable keys, and bind_selected writes the selected row key into State.
fn task_table(selected: State<u8>) -> TableView<u8> {
TableView::new()
.column(TableColumn::new("Task").flex(2.0))
.column(TableColumn::new("Status"))
.column(TableColumn::new("Progress").width(120.0))
.row(
TableRow::new(1)
.leading_icon(IconId::Check)
.cell(TableCell::text("Polish the workspace"))
.cell(TableCell::badge("Active", BadgeTone::Success))
.cell(TableCell::progress(0.75)),
)
.row(
TableRow::new(2)
.cell(TableCell::text("Invite a teammate"))
.cell(TableCell::muted("Next"))
.cell(TableCell::progress(0.25)),
)
.bind_selected(selected)
.max_body_height(180.0)
}Compiled with Rust 1.88 against beta.2. The four snippets come from one fixture using the default Ailloli UI feature set.
03
Add help without stealing the whole screen
Use each overlay for one clear purpose. Visibility can be internal, controlled, or bound to a writable signal depending on who owns the state.
Dialog
Dialog can paint a confirmation surface over its child. Bound mode closes by writing false after confirm, cancel, Escape, or backdrop dismissal.
Context menu
ContextMenu opens on right click and supports icons, shortcuts, separators, disabled entries, and one interactive submenu level.
Toast host
ToastHost stacks lightweight notifications over one child. Bound lists can remove matching IDs when the close control is activated.
Mini app 4: overlays around content
ToastHost owns notifications and Dialog wraps the content it can cover. Opening still needs an explicit trigger or bound state.
ToastHost::<()>::new()
.position(ToastPosition::BottomRight)
.toast(
Toast::new("ready", "Workspace ready")
.description("Right-click the navigation for more options.")
.tone(ToastTone::Success)
.leading_icon(IconId::Check),
)
.child(
Dialog::<()>::new()
.title("Ready for a focused session?")
.body("Your panels, table, and charts are prepared.")
.confirm_label("Let's go")
.cancel_label("Not yet")
.default_open(false)
.child(
Container::new()
.fill()
.background(palette.background)
.child(workspace),
),
)04
Make the data visible first
The chart controls are intentionally non-interactive. They turn finite values into a quick visual answer while your application keeps ownership of the data.
BarChart
Use labels for a short comparison. Multiple series share each group and cycle through the theme palette.
LineChart
Supply explicit x and y points for a trend. Markers are optional, and all enabled points should contain finite coordinates.
RadialGauge
Map one value through a range, then add an optional percentage and label for a compact status card.
Mini app 2: three compact charts
BarChart compares categories, LineChart follows a numeric trend, and RadialGauge shows one progress value.
Row::new()
.gap(16.0)
.child(
BarChart::new()
.series("Completed", [3.0, 5.0, 4.0, 7.0])
.labels(["Mon", "Tue", "Wed", "Thu"]),
)
.child(
LineChart::new()
.series(
"Focus",
[(0.0, 42.0), (1.0, 61.0), (2.0, 54.0)],
)
.range(0.0, 100.0)
.show_points(true)
.tone(ChartTone::Info),
),
)
.child(
RadialGauge::new()
.value(75.0)
.range(0.0, 100.0)
.show_value(true)
.label("Weekly goal"),
),05
A few more tools worth knowing
You do not need all of these today. Remember the family name, then return when your application needs that job.
Find and navigate
Sidebar and ListView suit small familiar menus and lay out every item. TreeView handles nested data and can use a virtualized TreeModelHandle for large trees. CommandPalette filters keyboard-friendly commands.
Choose a value
Select and ComboBox handle lists. DatePicker, TimePicker, and ColorPicker bind focused values and present their choice in an overlay.
Show progress and status
ProgressBar, CircularProgress, and StatusIndicator make ongoing work visible. Toast and Dialog communicate short results or decisions.
Build richer workflows
UploadDropzone emits file-drop intent but does not perform file I/O. ChatWidget supplies chat presentation and typed handlers while your application owns the service.
06
Enable only the capability you need
The editor and terminal presentation widgets are part of the default widget set. Filesystem access, parser enrichment, and PTY process I/O are separate opt-in capabilities.
Editor by default
Editor and CodeEditor are exported without an extra feature. The tree_sitter feature enriches Rust parsing, symbols, folds, and syntax work.
Provider-neutral files
The files feature exposes FileExplorer and snapshot models. It emits intents and does not mutate a filesystem by itself.
Local filesystem
files_local adds LocalFileExplorer and native local providers. It can start background workers and read the process-visible filesystem.
Current Rust 1.88 resolver caveat
A fresh tree_sitter resolution currently selects tree-sitter-language 0.1.8, which requires Rust 1.90. Pin 0.1.7 in the application lockfile until the dependency range is tightened.
Choose the smallest feature
Six configurations were verified: five compiled directly, while tree_sitter compiled on Rust 1.88 after pinning tree-sitter-language to 0.1.7 in the consumer lockfile.
Rich widgets
Default featuresLayouts, tables, overlays, charts, editors, Terminal, and TerminalView
CompiledRust parser enrichment
tree_sitterTree-sitter syntax, symbols, call edges, and fold regions for Rust
Compiled with lock pinFile snapshots
filesProvider-neutral FileExplorer and file models
CompiledLocal files
files_localLocalFileExplorer, local providers, and background loading
CompiledPTY contracts
terminal_ptyBackend trait, session handle, events, batching, and mock backend
CompiledNative PTY backend
terminal_pty_portablePortablePtyBackend and native child process I/O
Compiled on Linux
| Layer | Cargo feature | What it adds | Verification |
|---|---|---|---|
| Rich widgets | Default features | Layouts, tables, overlays, charts, editors, Terminal, and TerminalView | Compiled |
| Rust parser enrichment | tree_sitter | Tree-sitter syntax, symbols, call edges, and fold regions for Rust | Compiled with lock pin |
| File snapshots | files | Provider-neutral FileExplorer and file models | Compiled |
| Local files | files_local | LocalFileExplorer, local providers, and background loading | Compiled |
| PTY contracts | terminal_pty | Backend trait, session handle, events, batching, and mock backend | Compiled |
| Native PTY backend | terminal_pty_portable | PortablePtyBackend and native child process I/O | Compiled on Linux |
Default does not mean connected
Terminal and TerminalView can render without PTY support. Enabling terminal_pty exposes process I/O contracts but does not wire them into your application automatically.
Write the feature contract in Cargo.toml
Keep beta.2 pinned, then add only the feature names required by the next layer of your application.
[package]
name = "ailloli_ui_book_feature_matrix"
version = "0.1.0"
edition = "2021"
rust-version = "1.88"
publish = false
[features]
tree-sitter = ["ailloli_ui/tree_sitter"]
files = ["ailloli_ui/files"]
files-local = ["files", "ailloli_ui/files_local"]
terminal-pty = ["ailloli_ui/terminal_pty"]
terminal-pty-portable = ["terminal-pty", "ailloli_ui/terminal_pty_portable"]
[dependencies]
ailloli_ui = "=0.1.0-beta.2"use ailloli_ui::prelude::*;
#[cfg(feature = "terminal-pty-portable")]
use ailloli_ui::PortablePtyBackend;
#[cfg(feature = "terminal-pty")]
use ailloli_ui::{MockPtyBackend, PtyBackend, PtySpawnConfig};
fn main() {
let _editor = Editor::new(State::new(TextBuffer::from_string(
"A friendly note".to_owned(),
)));
let _code_editor: CodeEditor<()> = CodeEditor::new(State::new(Document::new(
DocumentId(1),
TextBuffer::from_string("fn main() {}".to_owned()),
)))
.language(EditorLanguage::Rust);
let _log = TerminalView::new()
.line(TerminalLine::prompt("$ cargo check"))
.line(TerminalLine::success("Finished"));
#[cfg(feature = "files")]
{
let root = FileUri::parse("file:///project").expect("valid URI");
let node =
FileExplorerNode::directory(root.clone(), "project").child(FileExplorerNode::file(
root.join_child("main.rs").expect("valid child URI"),
"main.rs",
));
let _files = FileExplorer::<()>::new([node]).default_expanded(root);
}
#[cfg(feature = "files-local")]
let _local_files = LocalFileExplorer::<()>::new(".").lazy_cached();
#[cfg(feature = "terminal-pty")]
{
let backend = MockPtyBackend::default();
let handle = backend
.spawn(PtySpawnConfig::default())
.expect("mock PTY spawn");
handle.shutdown().expect("mock PTY shutdown");
}
#[cfg(feature = "terminal-pty-portable")]
let _portable_backend = PortablePtyBackend::new();
}Temporary Rust 1.88 lockfile pin
Run this only when enabling tree_sitter on Rust 1.88, then commit the resulting application Cargo.lock and recheck it when beta.2 dependencies change.
cargo update -p tree-sitter-language --precise 0.1.707
A terminal is three different layers
Separating presentation, emulation, and process I/O prevents a log viewer from quietly becoming a shell launcher.
TerminalView
A scrollable log viewer for static lines or streamed events. It is not a PTY and it does not parse ANSI sequences.
Terminal
An interactive view over TerminalState. It can emit protocol bytes, but your application still owns transport and synchronization.
terminal_pty
Adds backend contracts, handles, batching, and a safe in-memory mock. terminal_pty_portable adds the native portable-pty backend.
Treat process launch as a security boundary
The portable backend does not sandbox programs, validate paths or environment values, redact output, or bound its queues. Validate every input before spawning.
Try it yourself
Exercise: observe a resizable split
Turn a rich layout behaviour into one small, visible experiment.
- 1Bind a SplitPane position to a State initialized to 280.
- 2Display the current position in a Text while the divider moves.
Keep the two pane contents simple. One label in each pane is enough for this experiment.
Dragging the divider changes both the pane sizes and the number shown in the text.