Structural Widgets
Overview
WIP. These widgets render today, but several have known gaps — ListView does not yet wire its column or row callbacks into the DOM, so treat their APIs as provisional. TabHeader and TreeView are usable as documented.
The structural widgets are containers. They take other widgets or DOMs as content and lay them out in tabbed, hierarchical, or tabular forms.
Tabs
TabHeader is the clickable bar of labels. Compose it with your own content
DOM in a column flexbox so the header stays a fixed height and the content
fills the rest.
use azul::prelude::*;
extern "C"
fn on_tab(
_d: RefAny,
_i: CallbackInfo,
_s: TabHeaderState,
) -> Update {
Update::RefreshDom
}
let labels = StringVec::from_vec(vec![
"General".into(),
"Advanced".into()
]);
let dom = Dom::create_div()
.with_child(
TabHeader::create(labels)
.with_active_tab(0)
.with_on_click(RefAny::new(()), on_tab)
.dom()
)
.with_child(Dom::create_text("Settings go here."));
TabHeader keeps active_tab: usize and emits TabHeaderState { active_tab }
on click. The active state is driven externally; the widget does not toggle
itself. Your callback rebuilds the DOM with a new active_tab.
TreeView
TreeView renders a hierarchy where each node has a label, an optional list
of children, and per-node expanded/selected flags.
pub struct TreeViewNode {
pub label: AzString,
pub children: TreeViewNodeVec,
pub is_expanded: bool,
pub is_selected: bool,
}
Build the data with the chained with_* builders and convert in one call.
use azul::prelude::*;
extern "C"
fn on_click(_d: RefAny, _i: CallbackInfo, _idx: usize) -> Update {
Update::RefreshDom
}
let root = TreeViewNode::new("Project".into())
.with_expanded(true)
.with_child(TreeViewNode::new("src".into()).with_expanded(true)
.with_child(TreeViewNode::new("main.rs".into()))
.with_child(TreeViewNode::new("lib.rs".into())))
.with_child(TreeViewNode::new("Cargo.toml".into()));
let dom = TreeView::new(root)
.with_on_node_click(RefAny::new(()), on_click)
.dom();
The usize argument to the click callback is the depth-first preorder index
of the clicked node, not a node ID. Map it back to your data by walking the
tree in the same order.
is_expanded and is_selected are read-only outputs of the widget. To toggle,
store the tree in your RefAny, mutate it inside the callback, and return
Update::RefreshDom.
ListView
ListView renders a tabular list with column headers and rows. The data
shape:
pub struct ListView {
pub columns: StringVec,
pub rows: ListViewRowVec,
pub sorted_by: OptionUsize,
pub scroll_offset: PixelValueNoPercent,
pub content_height: OptionPixelValueNoPercent,
pub column_context_menu: OptionMenu,
pub on_lazy_load_scroll: OptionListViewOnLazyLoadScroll,
pub on_column_click: OptionListViewOnColumnClick,
pub on_row_click: OptionListViewOnRowClick,
}
The widget supports lazy-loading. Pass an explicit content_height to size the
scroll region for unloaded rows, and use on_lazy_load_scroll to fetch more
data as the user scrolls.
Known limitation. The current
dom()does not yet attachon_column_click,on_row_click,on_lazy_load_scroll, orcolumn_context_menuto event filters in the produced DOM. Configure them now to keep your call sites stable, but plan to handle clicks via external event filters until the widget wires them through.
Ribbon
Ribbon is an Office-style ribbon: a tab bar where each tab contains one or
more titled RibbonSections. Sections separate themselves with vertical
dividers and stack their title below the content.
use azul::prelude::*;
let home = RibbonTab::new("Home".into())
.with_section(RibbonSection::new("Clipboard".into(), Dom::create_div()))
.with_section(RibbonSection::new("Font".into(), Dom::create_div()));
let view = RibbonTab::new("View".into())
.with_section(RibbonSection::new("Zoom".into(), Dom::create_div()));
let dom = Ribbon::new(RibbonTabVec::from_vec(vec![home, view]))
.dom();
set_active_tab(idx) clamps to the last valid index. The
with_on_tab_click(data, fn) callback receives the clicked tab index. Like
tabs, the widget is externally driven; rebuild your DOM with the new active
tab to switch.
Picking the right container
- For flat tabs of independent panels, use
TabHeaderplus a content div. - For ribbon-style tabs with named subsections, use
Ribbon. - For a hierarchical sidebar, use
TreeView. - For a tabular data grid (columns + rows), use
ListView.
For ad-hoc layouts, plain Dom::create_div with flex CSS is almost always
shorter than wiring a widget. Reach for these widgets when you want the
platform-native look or the widget's specific event surface.
Coming Up Next
- Input Widgets — Text fields, checkboxes, radios, sliders, dropdowns
- Layout — Overview of the layout solver
- Events — Callbacks, event filters, and how state triggers relayout