Files
craft-libs/docs/ui-panes.md
T
2026-10-20 03:24:45 +00:00

6.7 KiB
Raw Blame History

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.
  • states returns the app's saved PaneState (place, size, float rect; serde with the serde feature). Return None to let the host keep the state in egui memory for the session, as egui panels keep widths today.
  • Apply Resize and FloatRect as they come: they arrive every frame of a gesture, and an Escape sends the start rect back.
  • panes::float_rect brings a saved rect back inside the window, whatever the file says.

Adoption

WordCraft (Columns)

  • Add a Pane enum for the nine panes and implement PaneContent by calling the existing bodies; drop their header() calls, since the host draws the title and ×.
  • Build open each frame from the ViewState flags, in today's order: SmartArt Text Pane (only while SmartArt is selected), Navigation and Clipboard at Start; Styles, Style Inspector, Accessibility, Alt Text, Restrict Editing and Comments at End. Default widths as today.
  • Close(p) runs the pane's existing command (view.navigationPane {value: false}, …). Command ids and nav_pane in ui.json stay as they are.
  • Styles takes #2584's behavior through the host: map StylesPaneDock::{Left, Right, Floating} to Place (Left is Docked(Start) in left-to-right interfaces), run view.stylesPaneDock on Place, keep the float rect in UI prefs. #2584's own drag, snap and float_rect code goes.
  • Other panes return None from states; 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)

  • open is ui.pane (one entry) at End; states returns PaneState::docked(Edge::End, pane_width); Resize writes pane_width; Close clears ui.pane as the × does today. size_range is 240..=520. The ui.json fields stay.
  • Format, Animation and Selection return movable = true; the rest false. Saving their place needs new optional ui.json fields (#[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))

  • open is the task slot pane (grid.pane, or "comments" while session.comments_pane) plus PivotTable Fields while the active cell is in a pivot. PivotTable Fields returns closable = false and movable = false, so it stays beside the slot.
  • Close("comments") runs review.showComments {on: false}; agents still drive it through the engine.
  • No pane prefs today: return None from states.
  • Code Editor (#1193) and Queries & Connections (#1203) become slot panes. The formula bar, sheet tabs and status bar stay plain panels.