Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: allow disabling vertical/horizontal splits #145

Merged
merged 6 commits into from
Jul 23, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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;
Loading