iced-cube

Navigation

Document tabs

A strip of open documents above an editor, with unsaved changes, pinning, reordering and a menu for tabs that do not fit.

use iced_cube::navigation::document_tabs::{self, Event, MenuItem, Output, State, document, document_tabs};
API reference
Editor: Open files with icons, a pinned README, unsaved changes shown as dots and a new tab button. Drag a tab to move it, middle-click to close it, or right-click for more, including an app item that copies the path.Editor: Open files with icons, a pinned README, unsaved changes shown as dots and a new tab button. Drag a tab to move it, middle-click to close it, or right-click for more, including an app item that copies the path.
Static preview
Editor. Open files with icons, a pinned README, unsaved changes shown as dots and a new tab button. Drag a tab to move it, middle-click to close it, or right-click for more, including an app item that copies the path.

Usage

Document tabs are the strip of open files above an editor. The app owns a document_tabs::State with the documents in order and routes the strip’s events into it. The strip only switches between documents: the app renders the active one below it, and the active tab takes the page background so it joins that content.

Closing never happens by itself. The close button, a middle click, Ctrl+W and the context menu all return Output::CloseRequested with the ids to close, so the app can save, or ask with an alert dialog when a document has unsaved changes, before it calls state.close(&ids). The confirmation example shows the pattern.

Each tab reserves a slot after its label. The active tab shows its close button there, other tabs show it on hover, and a document with unsaved changes shows a dot that turns into the close button under the pointer, so the label never moves. Pinned tabs sit before the others, show only their icon (or a short label when they have none), have no close button, and are left alone by Close others, Close to the right, Close saved and Close all. Dragging a tab moves it, with a line marking where it will land, and a pinned tab only moves among pinned tabs.

A tab’s context menu only lists what would do something on it, and never two close items that would close exactly the same tabs; Close and Close all are kept over the others. So Close saved, which closes the tabs without unsaved changes, appears only while some of those tabs have unsaved changes and some do not. A finger activates a tab when it lifts without having moved, so swiping the strip or holding a tab for its menu never switches tabs. For a document the app always keeps open, such as a list the other tabs are opened from, use .pinned(true).closable(false).locked(true): it then has no close button, no Close, Pin or Unpin item, and no close gesture or shortcut asks to close it.

When the tabs do not fit, the pinned tabs stay at the start of the strip and only the others scroll sideways, with the wheel or a finger, in the room after them. A button at the end of the strip lists every tab. When pinned tabs would take more than half that room, they scroll with the others instead, so a narrow window always has room to show the active tab. The strip keeps the active tab in view when it first appears, when the active tab changes and when the window is resized. The list opens below its button, marks the active tab and scrolls to it. Labels stay on one line, in the strip and in the list, and end in an ellipsis when they do not fit. A tab’s context menu opens below the tab, whether from a right-click, a long press or the keyboard, and scrolls when the window is too short for it.

Ctrl+Tab walks the documents in the order they were last used, as editors do: each press goes one further back until Ctrl is released, which the app learns from document_tabs::cycle_end(). Ctrl+PageDown and Ctrl+PageUp move through the strip in order. The segmented tabs also bind Ctrl+Tab and Ctrl+Shift+Tab, so an app showing both should unbind them from one of them, usually the segmented tabs. Browsers keep these chords for their own tabs, which is why the keyboard example adds chords such as Ctrl+] that reach the page.

Action::OpenMenu, which opens the active tab’s context menu, has no default chord. The strip never has keyboard focus, so an app-wide Shift+F10 would also open any other context menu on screen. Bind it with default_keymap().bind(Chord::named(Named::F10).shift(), Action::OpenMenu) when the tab strip is the only context menu, or resolve it only while the editor has focus. See Keyboard shortcuts for how components share keys.

Examples

Many tabs

Tabs that do not fit scroll sideways, keeping the active one in view, and a button at the end lists them all.

Many tabs: Tabs that do not fit scroll sideways, keeping the active one in view, and a button at the end lists them all.Many tabs: Tabs that do not fit scroll sideways, keeping the active one in view, and a button at the end lists them all.
Static preview

Confirm before closing

Closing only asks. The app checks for unsaved changes, confirms with an alert dialog, then closes the tabs itself.

Confirm before closing: Closing only asks. The app checks for unsaved changes, confirms with an alert dialog, then closes the tabs itself.Confirm before closing: Closing only asks. The app checks for unsaved changes, confirms with an alert dialog, then closes the tabs itself.
Static preview

Keyboard shortcuts

The default keymap with extra chords that reach a web page, since browsers keep Ctrl+Tab, Ctrl+W and Ctrl+PageDown for themselves. Closing a modified tab asks first. Releasing every modifier ends a walk through recent tabs.

Keyboard shortcuts: The default keymap with extra chords that reach a web page, since browsers keep Ctrl+Tab, Ctrl+W and Ctrl+PageDown for themselves. Closing a modified tab asks first. Releasing every modifier ends a walk through recent tabs.Keyboard shortcuts: The default keymap with extra chords that reach a web page, since browsers keep Ctrl+Tab, Ctrl+W and Ctrl+PageDown for themselves. Closing a modified tab asks first. Releasing every modifier ends a walk through recent tabs.
Static preview

API

ItemDescription
State::new(documents)Opens the documents, pinned ones first, with the first one active. Ids must be unique.
document(id, label)A saved, unpinned document. Add .icon(glyph), .modified(true) or .pinned(true).
.closable(false)A document the user cannot close: no close button, no Close item, left out of Close others, Close to the right, Close saved and Close all, and Ctrl+W does nothing on it.
.locked(true)Keeps a document pinned or unpinned as it is: its menu has no Pin or Unpin item and Event::Pin leaves it alone. It still moves within its group.
.with_active(id) / .with_menu_items(entries)The document to start on, and app items added to every tab's context menu, built with dropdown_menu::item(MenuItem::Custom("name"), label).
state.update(Event)Activates, walks recent documents, moves, pins and drives both menus. Returns Output::Activated(id), CloseRequested(ids), Moved { id, from, to }, Pinned(id, bool), NewTab or Custom(name, id).
state.close(&ids)Closes documents. When the active one closes, the most recently used one left becomes active and is returned.
state.open(document)Activates a document, adding it at the end of its group when it is not open yet.
state.set_modified(id, bool) / state.set_label(id, label)Marks unsaved changes, or renames a tab.
state.documents() / state.document(id) / state.active() / state.recent()The documents in strip order, one document, the active id, and the ids most recently used first.
document_tabs(&state)Renders the strip. Tabs render disabled until .on_event is set.
.new_tab(bool)Shows a new tab button after the last tab, and lets a double click on the empty strip ask for a new document.
.max_label_width(f32)The widest a label grows before it ends in an ellipsis. Defaults to 200 pixels.
.context_menu_keymap(keymap) / .overflow_keymap(keymap)Replaces the default keymap of the open tab context menu or of the list of every tab.
document_tabs::cycle_end()A subscription that reports when every modifier is released, which ends a walk through recent documents. Map it with Event::from.
document_tabs::default_keymap() / action.event(&state)The default shortcuts, and the Event a resolved Action sends.
document_tabs::tab_style(tokens, active, hovered) / strip_style(tokens)The colours of one tab and of the strip behind it.

Keyboard

Default shortcuts. Every one can be changed or removed with a keymap; see Keyboard shortcuts.

KeysAction
Ctrl+TabRecentNext Activates the previously used document. Pressed again while the modifier is held, goes further back.
Ctrl+Shift+TabRecentPrevious Walks the recently used documents the other way.
Ctrl+PageDownNext Activates the tab to the right, wrapping at the end.
Ctrl+PageUpPrevious Activates the tab to the left, wrapping at the start.
Ctrl+Shift+PageUpMoveLeft Moves the active tab one place left, within its group.
Ctrl+Shift+PageDownMoveRight Moves the active tab one place right, within its group.
Ctrl+WClose Asks to close the active document.
Ctrl+NNew Asks for a new document.
Ctrl+1Tab1 Activates the first tab.
Ctrl+2Tab2 Activates the second tab.
Ctrl+3Tab3 Activates the third tab.
Ctrl+4Tab4 Activates the fourth tab.
Ctrl+5Tab5 Activates the fifth tab.
Ctrl+6Tab6 Activates the sixth tab.
Ctrl+7Tab7 Activates the seventh tab.
Ctrl+8Tab8 Activates the eighth tab.
Ctrl+9LastTab Activates the last tab.
Esc