mirror of
https://github.com/storytold/craft-libs.git
synced 2026-10-31 15:49:54 +00:00
6.7 KiB
6.7 KiB
Task panes
craft_ui::panes draws task panes around an app's own document: Navigation, Styles, Comments,
Format Shape and the like. Each pane is an egui side or bottom panel, or a floating frame. The
document stays the app's CentralPanel.
Panes or docking
| Use | When |
|---|---|
panes |
Office-style apps (WordCraft, DeckCraft, GridCraft). A few panes at the sides of a document the app draws itself; the app's commands own which panes are open. |
docking |
Creative apps whose whole window is panels: tab groups, splits, stacked groups, workspaces, a protected canvas panel. See ui-docking.md. |
| plain egui panels | Fixed view parts: slide thumbnails, notes, formula bar, sheet tabs, status bar. |
egui::Window |
Free tool windows that never dock (Read Aloud, Power Query). |
panes needs no docking types; it uses only the docking chrome (FloatingFrame, TitleBar,
live_move).
API
use craft_ui::panes::{Edge, PaneContent, PaneHost, PaneRequest, PaneState, Place, SidePolicy};
impl PaneContent<Pane> for MyPanes<'_> {
fn title(&self, pane: &Pane) -> String { tr(pane.title()) }
fn body(&mut self, ui: &mut egui::Ui, pane: &Pane) { /* the pane's contents */ }
fn default_state(&self, pane: &Pane) -> PaneState { PaneState::docked(Edge::End, 250.0) }
// Optional: size_range (200..=520), movable, closable, menu (app items in the ▾ menu).
}
// Every frame, after the top and bottom bars and before the CentralPanel:
let out = PaneHost::new("task-panes")
.rtl(ui_is_rtl)
.policy(SidePolicy::Columns)
.show(ui, &open_panes, |pane| prefs.pane_state(pane), &mut MyPanes { app });
for request in out.requests {
match request {
PaneRequest::Close(pane) => app.run(pane.close_command()),
PaneRequest::Place(pane, place) => prefs.set_place(pane, place),
PaneRequest::Resize(pane, size) => prefs.set_size(pane, size),
PaneRequest::FloatRect(pane, rect) => prefs.set_float_rect(pane, rect),
PaneRequest::Focus(_) => {}
}
}
egui::CentralPanel::default().show(ui, |ui| {
let response = document(ui);
if out.to_document {
// F6 or Shift+F6 left the panes: the document takes keyboard focus.
response.request_focus();
}
});
| Piece | Behavior |
|---|---|
Edge::{Start, End, Bottom} |
Logical edges. Start is left, or right with .rtl(true). |
SidePolicy::Columns |
Every docked pane shows; panes on one edge sit side by side in open order, the first outermost. |
SidePolicy::OneSlot |
One closable pane per edge: the last one in open. Non-closable context panes (PivotTable Fields) show beside the slot. |
.max_fraction(f) |
One docked pane takes at most f of the host's width (height for bottom panes). |
| Chrome | Title, ▾ menu (app items, Move to Start, Move to End, Move to Bottom for bottom panes, Float, Close), ×, a resize edge with pointer and arrow keys. The title bar mirrors with .rtl(true). Its accessible name is PaneLabels::move_pane with {title} replaced by the pane's title ("Move Styles pane"). |
| Size | A docked pane reserves exactly its size and clips its body, so a body wider than the pane never pushes into the document. |
| Drag out | A docked pane's title dragged more than DOCK_SNAP (32 pt) out of the pane floats it under the pointer when dropped; dropped within 32 pt of the host's left or right edge it docks there. |
| Floating | FloatingFrame with its resize handles; the title bar moves it. Escape or window focus loss during a move puts it back. A drop within 32 pt of the left or right edge docks it. Rects are clamped to the host area. |
| Focus | out.focused is the pane holding keyboard focus or last clicked; a click or focus in the document clears it. F6 and Shift+F6 cycle through the shown panes and then report out.to_document: give focus to the document. .f6(false) leaves F6 to the app, which calls panes::focus_next(ctx, id, reverse) instead. |
State:
- The host never stores which panes are open and never writes files.
statesreturns the app's savedPaneState(place, size, float rect; serde with theserdefeature). ReturnNoneto let the host keep the state in egui memory for the session, as egui panels keep widths today.- Apply
ResizeandFloatRectas they come: they arrive every frame of a gesture, and an Escape sends the start rect back. panes::float_rectbrings a saved rect back inside the window, whatever the file says.
Adoption
WordCraft (Columns)
- Add a
Paneenum for the nine panes and implementPaneContentby calling the existing bodies; drop theirheader()calls, since the host draws the title and ×. - Build
openeach frame from theViewStateflags, in today's order: SmartArt Text Pane (only while SmartArt is selected), Navigation and Clipboard atStart; Styles, Style Inspector, Accessibility, Alt Text, Restrict Editing and Comments atEnd. Default widths as today. Close(p)runs the pane's existing command (view.navigationPane {value: false}, …). Command ids andnav_paneinui.jsonstay as they are.- Styles takes #2584's behavior through the host: map
StylesPaneDock::{Left, Right, Floating}toPlace(LeftisDocked(Start)in left-to-right interfaces), runview.stylesPaneDockonPlace, keep the float rect in UI prefs. #2584's own drag, snap andfloat_rectcode goes. - Other panes return
Nonefromstates; their widths stay session-only, as now. - Pane title bars are focusable. Check that Tab in the canvas still stays in the document, so
panes stay off its keyboard focus. Use
.f6(false)if F6 is bound elsewhere.
DeckCraft (OneSlot)
openisui.pane(one entry) atEnd;statesreturnsPaneState::docked(Edge::End, pane_width);Resizewritespane_width;Closeclearsui.paneas the × does today.size_rangeis 240..=520. Theui.jsonfields stay.- Format, Animation and Selection return
movable = true; the restfalse. Saving their place needs new optionalui.jsonfields (#[serde(default)]). - Thumbnails, outline, notes and the #835 canvas shell stay plain panels.
- F6 from the host closes the ui-parity F6 gap.
GridCraft (OneSlot, .max_fraction(0.5))
openis the task slot pane (grid.pane, or "comments" whilesession.comments_pane) plus PivotTable Fields while the active cell is in a pivot. PivotTable Fields returnsclosable = falseandmovable = false, so it stays beside the slot.Close("comments")runsreview.showComments {on: false}; agents still drive it through the engine.- No pane prefs today: return
Nonefromstates. - Code Editor (#1193) and Queries & Connections (#1203) become slot panes. The formula bar, sheet tabs and status bar stay plain panels.