Crate egui_dock

source ·
Expand description

§egui_dock: docking support for egui

Originally created by @lain-dono, this library provides docking support for egui. It lets you open and close tabs, freely move them around, resize them, and undock them into new egui windows which can also have other tabs docked in them.

§Basic usage

The library is centered around the DockState. It contains a series of Surfaces which all have their own Tree. Each Tree stores a hierarchy of Nodes which contain the splits and tabs.

DockState is generic (DockState<Tab>) so you can use any data to represent a tab. You show the tabs using DockArea and specify how they are shown by implementing TabViewer.

use egui_dock::{DockArea, DockState, NodeIndex, Style, TabViewer};
use egui::{Ui, WidgetText};

// First, let's pick a type that we'll use to attach some data to each tab.
// It can be any type.
type Tab = String;

// To define the contents and properties of individual tabs, we implement the `TabViewer`
// trait. Only three things are mandatory: the `Tab` associated type, and the `ui` and
// `title` methods. There are more methods in `TabViewer` which you can also override.
struct MyTabViewer;

impl TabViewer for MyTabViewer {
    // This associated type is used to attach some data to each tab.
    type Tab = Tab;

    // Returns the current `tab`'s title.
    fn title(&mut self, tab: &mut Self::Tab) -> WidgetText {
        tab.as_str().into()
    }

    // Defines the contents of a given `tab`.
    fn ui(&mut self, ui: &mut Ui, tab: &mut Self::Tab) {
        ui.label(format!("Content of {tab}"));
    }
}

// Here is a simple example of how you can manage a `DockState` of your application.
struct MyTabs {
    dock_state: DockState<Tab>
}

impl MyTabs {
    pub fn new() -> Self {
        // Create a `DockState` with an initial tab "tab1" in the main `Surface`'s root node.
        let tabs = ["tab1", "tab2", "tab3"].map(str::to_string).into_iter().collect();
        let dock_state = DockState::new(tabs);
        Self { dock_state }
    }

    fn ui(&mut self, ui: &mut Ui) {
        // Here we just display the `DockState` using a `DockArea`.
        // This is where egui handles rendering and all the integrations.
        //
        // We can specify a custom `Style` for the `DockArea`, or just inherit
        // all of it from egui.
        DockArea::new(&mut self.dock_state)
            .style(Style::from_egui(ui.style().as_ref()))
            .show_inside(ui, &mut MyTabViewer);
    }
}

§Look and feel customization

egui_dock exposes the Style struct that lets you change how tabs and the DockArea should look and feel. Style is divided into several, more specialized structs that handle individual elements of the UI.

Your Style can inherit all its properties from an egui::Style through the Style::from_egui function.

Example:

// Inherit the look and feel from egui.
let mut style = Style::from_egui(ui.style());

// Modify a few fields.
style.overlay.overlay_type = OverlayType::HighlightedAreas;
style.buttons.add_tab_align = TabAddAlign::Left;

// Use the style with the `DockArea`.
DockArea::new(&mut dock_state)
    .style(style)
    .show_inside(ui, &mut MyTabViewer);

§Surfaces

A Surface is an abstraction for any tab hierarchy. There are two kinds of non-empty surfaces: Main and Window.

There can only be one Main surface. It’s the one surface that is rendered inside the Ui you’ve passed to DockArea::show_inside, or inside the egui::CentralPanel created by DockArea::show.

On the other hand, there can be multiple Window surfaces. Those represent surfaces that were created by undocking tabs from the Main surface, and each of them is rendered inside a egui::Window - hence their name.

While most of surface management will be done by the user of your application, you can also do it programatically using the DockState API.

Example:

// Create a new window `Surface` with one tab inside it.
let mut surface_index = dock_state.add_window(vec!["Window Tab".to_string()]);

// Access the window state by its surface index and then move and resize it.
let window_state = dock_state.get_window_state_mut(surface_index).unwrap();
window_state.set_position(Pos2::ZERO);
window_state.set_size(Vec2::splat(100.0));

For more details, see: DockState.

§Trees

In each Surface there is a Tree which actually stores the tabs. As the name suggests, tabs and splits are represented with a binary tree.

The Tree API allows you to programatically manipulate the dock layout.

Example:

// Create a `DockState` with an initial tab "tab1" in the main `Surface`'s root node.
let mut dock_state = DockState::new(vec!["tab1".to_string()]);

// Currently, the `DockState` only has one `Surface`: the main one.
// Let's get mutable access to add more nodes in it.
let surface = dock_state.main_surface_mut();

// Insert "tab2" to the left of "tab1", where the width of "tab2"
// is 20% of root node's width.
let [_old_node, new_node] =
    surface.split_left(NodeIndex::root(), 0.20, vec!["tab2".to_string()]);

// Insert "tab3" below "tab2" with both tabs having equal size.
surface.split_below(new_node, 0.5, vec!["tab3".to_string()]);

// The layout will look similar to this:
// +--------+--------------------------------+
// |        |                                |
// |  tab2  |                                |
// |        |                                |
// +--------+              tab1              |
// |        |                                |
// |  tab3  |                                |
// |        |                                |
// +--------+--------------------------------+

§Translations

Some parts of the DockArea contain text that has nothing to do with tab content (currently it’s just the tab context menus, but that might change in the future). The translations module provides an API for defining an alternative for each text element. This is especially useful when your application’s interface is in any language other than English, but can also be used in any other way, e.g. to add icons.

Example usage:

let translations_pl = Translations {
    tab_context_menu: TabContextMenuTranslations {
        close_button: "Zamknij zakładkę".to_string(),
        eject_button: "Przenieś zakładkę do nowego okna".to_string(),
    },
    window: WindowTranslations {
        close_button_tooltip: "To okno zawiera zakładki, których nie można zamknąć.".to_string(),
    }
};
let dock_state = DockState::<Tab>::new(vec![]).with_translations(translations_pl);

// Alternatively:
let mut dock_state = DockState::<Tab>::new(vec![]);
dock_state.translations.tab_context_menu.close_button = "タブを閉じる".to_string();
dock_state.translations.tab_context_menu.eject_button = "タブを新しいウィンドウへ移動".to_string();
dock_state.translations.window.close_button_tooltip = "このウィンドウは閉じられないタブがある。".to_string();

Re-exports§

Modules§