Skip to content

Commit

Permalink
feat: allow disabling vertical/horizontal splits (#145)
Browse files Browse the repository at this point in the history
* feat: allow disabling vertical/horizontal splits

in my use case vertical splits are useless, maybe other people
have some use for this. /shrug

* fix: rename vertical/horizontal to leftright/topbottom

* Move `allowed_splits` from `Style` to `DockArea`

* Add split directions to the style editor. Fix panic while dragging tabs with `None` split directions.

* Delete redundant module path

---------

Co-authored-by: Adam Gąsior <adanos020@gmail.com>
  • Loading branch information
zkldi and Adanos020 committed Aug 28, 2023
1 parent 73c8967 commit 850c339
Show file tree
Hide file tree
Showing 5 changed files with 139 additions and 39 deletions.
25 changes: 23 additions & 2 deletions examples/hello.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,9 @@ use egui::{
CentralPanel, ComboBox, Frame, Slider, TopBottomPanel, Ui, WidgetText,
};

use egui_dock::{DockArea, Node, NodeIndex, Style, TabInteractionStyle, TabViewer, Tree};
use egui_dock::{
DockArea, Node, NodeIndex, SplitTypes, Style, TabInteractionStyle, TabViewer, Tree,
};

fn main() -> eframe::Result<()> {
let options = NativeOptions {
Expand All @@ -32,6 +34,7 @@ struct MyContext {
show_add_buttons: bool,
draggable_tabs: bool,
show_tab_name_on_hover: bool,
allowed_splits: SplitTypes,
}

struct MyApp {
Expand Down Expand Up @@ -102,6 +105,22 @@ impl MyContext {
ui.checkbox(&mut self.show_add_buttons, "Show add buttons");
ui.checkbox(&mut self.draggable_tabs, "Draggable tabs");
ui.checkbox(&mut self.show_tab_name_on_hover, "Show tab name on hover");
ComboBox::new("cbox:allowed_splits", "Split direction(s)")
.selected_text(format!("{:?}", self.allowed_splits))
.show_ui(ui, |ui| {
ui.selectable_value(&mut self.allowed_splits, SplitTypes::All, "All");
ui.selectable_value(
&mut self.allowed_splits,
SplitTypes::LeftRightOnly,
"LeftRightOnly",
);
ui.selectable_value(
&mut self.allowed_splits,
SplitTypes::TopBottomOnly,
"TopBottomOnly",
);
ui.selectable_value(&mut self.allowed_splits, SplitTypes::None, "None");
});
});

let style = self.style.as_mut().unwrap();
Expand Down Expand Up @@ -188,7 +207,7 @@ impl MyContext {

ui.separator();

fn tab_style_editor_ui(ui: &mut egui::Ui, tab_style: &mut TabInteractionStyle) {
fn tab_style_editor_ui(ui: &mut Ui, tab_style: &mut TabInteractionStyle) {
ui.separator();

ui.label("Rounding");
Expand Down Expand Up @@ -345,6 +364,7 @@ impl Default for MyApp {
show_add_buttons: false,
draggable_tabs: true,
show_tab_name_on_hover: false,
allowed_splits: SplitTypes::default(),
};

Self { context, tree }
Expand Down Expand Up @@ -393,6 +413,7 @@ impl eframe::App for MyApp {
.show_add_buttons(self.context.show_add_buttons)
.draggable_tabs(self.context.draggable_tabs)
.show_tab_name_on_hover(self.context.show_tab_name_on_hover)
.allowed_splits(self.context.allowed_splits)
.show_inside(ui, &mut self.context);
});
}
Expand Down
2 changes: 2 additions & 0 deletions src/style.rs
Original file line number Diff line number Diff line change
Expand Up @@ -420,6 +420,7 @@ impl TabInteractionStyle {
..TabInteractionStyle::from_egui_active(style)
}
}

/// Derives relevant fields from `egui::Style` for a focused tab and sets the remaining fields to their default values.
///
/// Fields overwritten by [`egui::Style`] are:
Expand All @@ -433,6 +434,7 @@ impl TabInteractionStyle {
..TabInteractionStyle::from_egui_active(style)
}
}

/// Derives relevant fields from `egui::Style` for a hovered tab and sets the remaining fields to their default values.
///
/// Fields overwritten by [`egui::Style`] are:
Expand Down
89 changes: 63 additions & 26 deletions src/widgets/dock_area/hover_data.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use crate::{NodeIndex, Split, TabDestination, TabIndex};
use crate::{NodeIndex, Split, SplitTypes, TabDestination, TabIndex};
use egui::{Pos2, Rect};

#[derive(Debug)]
Expand All @@ -11,7 +11,7 @@ pub(super) struct HoverData {
}

impl HoverData {
pub(super) fn resolve(&self) -> (Rect, TabDestination) {
pub(super) fn resolve(&self, allowed_splits: &SplitTypes) -> (Rect, TabDestination) {
if let Some(tab) = self.tab {
return (tab.0, TabDestination::Insert(tab.1));
}
Expand All @@ -22,33 +22,70 @@ impl HoverData {
let (rect, pointer) = (self.rect, self.pointer);

let center = rect.center();
let pts = [
(

let pts = match allowed_splits {
SplitTypes::All => vec![
(
center.distance(pointer),
TabDestination::Append,
Rect::EVERYTHING,
),
(
rect.left_center().distance(pointer),
TabDestination::Split(Split::Left),
Rect::everything_left_of(center.x),
),
(
rect.right_center().distance(pointer),
TabDestination::Split(Split::Right),
Rect::everything_right_of(center.x),
),
(
rect.center_top().distance(pointer),
TabDestination::Split(Split::Above),
Rect::everything_above(center.y),
),
(
rect.center_bottom().distance(pointer),
TabDestination::Split(Split::Below),
Rect::everything_below(center.y),
),
],
SplitTypes::LeftRightOnly => vec![
(
center.distance(pointer),
TabDestination::Append,
Rect::EVERYTHING,
),
(
rect.left_center().distance(pointer),
TabDestination::Split(Split::Left),
Rect::everything_left_of(center.x),
),
(
rect.right_center().distance(pointer),
TabDestination::Split(Split::Right),
Rect::everything_right_of(center.x),
),
],
SplitTypes::TopBottomOnly => vec![
(
rect.center_top().distance(pointer),
TabDestination::Split(Split::Above),
Rect::everything_above(center.y),
),
(
rect.center_bottom().distance(pointer),
TabDestination::Split(Split::Below),
Rect::everything_below(center.y),
),
],
SplitTypes::None => vec![(
center.distance(pointer),
TabDestination::Append,
Rect::EVERYTHING,
),
(
rect.left_center().distance(pointer),
TabDestination::Split(Split::Left),
Rect::everything_left_of(center.x),
),
(
rect.right_center().distance(pointer),
TabDestination::Split(Split::Right),
Rect::everything_right_of(center.x),
),
(
rect.center_top().distance(pointer),
TabDestination::Split(Split::Above),
Rect::everything_above(center.y),
),
(
rect.center_bottom().distance(pointer),
TabDestination::Split(Split::Below),
Rect::everything_below(center.y),
),
];
)],
};

let (_, tab_dst, overlay) = pts
.into_iter()
Expand Down
60 changes: 50 additions & 10 deletions src/widgets/dock_area/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,20 @@ use hover_data::HoverData;
use paste::paste;
use state::State;

/// What directions can this dock split in?
#[derive(Copy, Clone, Debug, Default, Eq, PartialEq)]
pub enum SplitTypes {
#[default]
/// Allow splits in any direction (horizontal and vertical).
All,
/// Only allow split in a horizontal direction.
LeftRightOnly,
/// Only allow splits in a vertical direction.
TopBottomOnly,
/// Don't allow splits at all.
None,
}

/// Displays a [`Tree`] in `egui`.
pub struct DockArea<'tree, Tab> {
id: Id,
Expand All @@ -30,6 +44,7 @@ pub struct DockArea<'tree, Tab> {
draggable_tabs: bool,
show_tab_name_on_hover: bool,
scroll_area_in_tabs: bool,
allowed_splits: SplitTypes,

drag_data: Option<(NodeIndex, TabIndex)>,
hover_data: Option<HoverData>,
Expand All @@ -54,6 +69,7 @@ impl<'tree, Tab> DockArea<'tree, Tab> {
draggable_tabs: true,
show_tab_name_on_hover: false,
scroll_area_in_tabs: true,
allowed_splits: SplitTypes::default(),
drag_data: None,
hover_data: None,
to_remove: Vec::new(),
Expand Down Expand Up @@ -124,6 +140,13 @@ impl<'tree, Tab> DockArea<'tree, Tab> {
self.scroll_area_in_tabs = scroll_area_in_tabs;
self
}

/// What directions can a node be split in: left-right, top-bottom, all, or none.
/// By default it's all.
pub fn allowed_splits(mut self, allowed_splits: SplitTypes) -> Self {
self.allowed_splits = allowed_splits;
self
}
}

// UI
Expand Down Expand Up @@ -214,7 +237,7 @@ impl<'tree, Tab> DockArea<'tree, Tab> {
&& self.tree[dst].is_leaf()
&& (src != dst || self.tree[dst].tabs_count() > 1)
{
let (overlay, tab_dst) = hover.resolve();
let (overlay, tab_dst) = hover.resolve(&self.allowed_splits);
let id = Id::new("overlay");
let layer_id = LayerId::new(Order::Foreground, id);
let painter = ui.ctx().layer_painter(layer_id);
Expand Down Expand Up @@ -368,7 +391,9 @@ impl<'tree, Tab> DockArea<'tree, Tab> {
assert!(self.tree[node_index].is_leaf());

let rect = {
let Node::Leaf { rect, .. } = &mut self.tree[node_index] else { unreachable!() };
let Node::Leaf { rect, .. } = &mut self.tree[node_index] else {
unreachable!()
};
*rect
};
let ui = &mut ui.child_ui_with_id_source(
Expand All @@ -383,7 +408,9 @@ impl<'tree, Tab> DockArea<'tree, Tab> {
let tabbar_response = self.tab_bar(ui, state, node_index, tab_viewer);
self.tab_body(ui, state, node_index, tab_viewer, spacing, tabbar_response);

let Node::Leaf { tabs, .. } = &mut self.tree[node_index] else { unreachable!() };
let Node::Leaf { tabs, .. } = &mut self.tree[node_index] else {
unreachable!()
};
for (tab_index, tab) in tabs.iter_mut().enumerate() {
if tab_viewer.force_close(tab) {
self.to_remove.push((node_index, TabIndex(tab_index)));
Expand Down Expand Up @@ -419,7 +446,9 @@ impl<'tree, Tab> DockArea<'tree, Tab> {
}

let actual_width = {
let Node::Leaf { tabs, scroll, .. } = &mut self.tree[node_index] else { unreachable!() };
let Node::Leaf { tabs, scroll, .. } = &mut self.tree[node_index] else {
unreachable!()
};

let tabbar_inner_rect = Rect::from_min_size(
(tabbar_outer_rect.min - pos2(-*scroll, 0.0)).to_pos2(),
Expand Down Expand Up @@ -499,7 +528,9 @@ impl<'tree, Tab> DockArea<'tree, Tab> {

let focused = self.tree.focused_leaf();
let tabs_len = {
let Node::Leaf { tabs, .. } = &mut self.tree[node_index] else { unreachable!() };
let Node::Leaf { tabs, .. } = &mut self.tree[node_index] else {
unreachable!()
};
tabs.len()
};

Expand All @@ -514,7 +545,9 @@ impl<'tree, Tab> DockArea<'tree, Tab> {
}

let (is_active, label, tab_style) = {
let Node::Leaf { tabs, active, .. } = &mut self.tree[node_index] else { unreachable!() };
let Node::Leaf { tabs, active, .. } = &mut self.tree[node_index] else {
unreachable!()
};
let style = self.style.as_ref().unwrap();
let tab_style = tab_viewer.tab_style_override(&tabs[tab_index.0], &style.tab);
(
Expand Down Expand Up @@ -579,7 +612,9 @@ impl<'tree, Tab> DockArea<'tree, Tab> {
Sense::click_and_drag()
};

let Node::Leaf { tabs, active, .. } = &mut self.tree[node_index] else { unreachable!() };
let Node::Leaf { tabs, active, .. } = &mut self.tree[node_index] else {
unreachable!()
};
let tab = &mut tabs[tab_index.0];
if self.show_tab_name_on_hover {
response = response.on_hover_ui(|ui| {
Expand Down Expand Up @@ -627,7 +662,9 @@ impl<'tree, Tab> DockArea<'tree, Tab> {
};

// Paint hline below each tab unless its active (or option says otherwise)
let Node::Leaf { tabs, active, .. } = &mut self.tree[node_index] else { unreachable!() };
let Node::Leaf { tabs, active, .. } = &mut self.tree[node_index] else {
unreachable!()
};
let tab = &mut tabs[tab_index.0];
let style = self.style.as_ref().unwrap();
let tab_style = tab_viewer.tab_style_override(tab, &style.tab);
Expand Down Expand Up @@ -872,7 +909,9 @@ impl<'tree, Tab> DockArea<'tree, Tab> {
available_width: f32,
tabbar_response: &Response,
) {
let Node::Leaf { scroll, .. } = &mut self.tree[node_index] else { unreachable!() };
let Node::Leaf { scroll, .. } = &mut self.tree[node_index] else {
unreachable!()
};
let overflow = (actual_width - available_width).at_least(0.0);
let style = self.style.as_ref().unwrap();

Expand Down Expand Up @@ -962,7 +1001,8 @@ impl<'tree, Tab> DockArea<'tree, Tab> {
active,
viewport,
..
} = &mut self.tree[node_index] else {
} = &mut self.tree[node_index]
else {
unreachable!();
};

Expand Down
2 changes: 1 addition & 1 deletion src/widgets/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,5 @@ pub(crate) mod popup;
/// Trait for tab-viewing types.
pub mod tab_viewer;

pub use dock_area::DockArea;
pub use dock_area::{DockArea, SplitTypes};
pub use tab_viewer::TabViewer;

0 comments on commit 850c339

Please sign in to comment.