Layout & Dock
Side panes that fold and resize, a status bar, and a Dock of panels the user rearranges.
| Component | What it does |
|---|---|
FoldablePane(open, width, edge, splitter)(content*) | A side pane that folds to zero width; its content never reflows |
Splitter(size, range, edge, label) | A resize grip. Drag it, or press the arrow keys (Shift for bigger steps); double-click or Enter resets |
StatusBar(content*) | A bar along the bottom; StatusBar.spacer pushes the rest to the end |
A resizable sidebar:
scala
import wvlet.uni.dom.all.*
import wvlet.uni.dom.all.given
import wvlet.uni.rx.Rx
import wvlet.uni.ui.*
import scala.language.implicitConversions
val open = Rx.variable(true)
val width = Rx.variable(272)
val range = SizeRange(min = 208, default = 272, max = 480)
FoldablePane(open, width, Edge.Right, Some(Splitter(width, range, Edge.Right, "Resize sidebar")))(
ChatList()
)Edge names the pane's inner side, the one that faces the main content: Edge.Right for a left sidebar, Edge.Left for a right-hand dock.
Dock
A Dock shows panels in tab groups and splits that the user rearranges, like the side panels of an editor. Its arrangement is a DockLayout: plain data in an RxVar that you own. Save it with Storage.local, which stores it as JSON:
scala
val layout = Storage.local(
"app.dock",
DockLayout(DockNode.column(DockNode.group("details", "activity"), DockNode.group("terminal")))
)
Dock(layout, "Dock")(
DockPanel("details", "Details")(DetailsView()),
DockPanel("activity", "Activity")(ActivityLog()).withTab(Icons.activity, "Activity", Badge(count)),
DockPanel("terminal", "Terminal")(ShellView()).withClosable(false)
).withActions(IconButton("Close dock", Icons.panelRight).onClick(() => dockOpen := false))- Layout. A layout is a tree of
DockNode.Split(direction, children, sizes)andDockNode.Group(panels, active).DockNode.row,DockNode.column, andDockNode.groupbuild one, andsizesare shares of the space that sum to 1. The layout names panels by id only. - Rearranging. Drag a tab onto another group's tabs to move it there, onto the middle of a group to add it, or onto one of a group's edges to split the group. A preview shows where it will land, and Escape cancels. A group that loses its last tab disappears.
- Keyboard. In a tab bar, the arrow keys, Home, and End switch tabs. With Alt, the same keys move the focused tab within its group, as
Reorderdoes in a list. Delete closes a closable tab. A tab's context menu (right-click, Shift+F10, or the context-menu key) moves it left, right, or to the next group, splits it right or down, and closes it. The border between two children is a separator: the arrow keys move it, Shift moves it farther, Home and End go to the limits, and Enter shares the space equally. No child gets smaller than.withMinPanelSize(px)(120 by default). - Panels. A panel's content is created the first time it shows and is kept, so moving a panel keeps its state.
dock.open(id)shows a panel, adding it back if the user closed it, anddock.hiddenlists the closed ones. A panel with.withClosable(false)can't be closed and is added back when a saved layout is missing it. - Focus from code.
dock.focusGroup(i)moves keyboard focus to the active tab of groupi, counted from 0 in reading order (the tab list named "group 1" is index 0), anddock.groupCounttells how many there are.dock.focusPanel(id)shows a panel and focuses its tab. Both return false when there is nothing to focus. The Dock binds no keys itself, so pick your own shortcuts:(1 to 9).foreach(n => Keyboard.bind(s"ctrl+${n}", () => dock.focusGroup(n - 1))). - Old saves. The Dock drops ids it doesn't know, so removing a panel from the app never breaks a saved layout. The JSON carries a format
version; a save that is unreadable or from a newer version falls back to your default. - Changing it from code.
DockModelinwvlet.uni.ui.baseholds the operations the Dock runs (move,split,close,open,resize) as pure functions on aDockLayout, so you can set a layout from a command or test one without a DOM.layout.open(id)andlayout.close(id)are shortcuts.