use crate::Theme;
use egui::Ui;
use egui_cha::ViewCtx;
pub use egui_dock::{NodeIndex, SurfaceIndex};
#[derive(Debug, Clone)]
pub enum DockEvent<Tab> {
TabClosed(Tab),
AddClicked {
surface: SurfaceIndex,
node: NodeIndex,
},
FocusChanged,
}
pub trait TabInfo {
fn title(&self) -> String;
fn icon(&self) -> Option<&str> {
None
}
fn is_dirty(&self) -> bool {
false
}
fn closeable(&self) -> bool {
true
}
}
pub struct DockTree<Tab> {
inner: egui_dock::DockState<Tab>,
}
impl<Tab: Default> Default for DockTree<Tab> {
fn default() -> Self {
Self::new_single(Tab::default())
}
}
impl<Tab> DockTree<Tab> {
pub fn new_single(tab: Tab) -> Self {
Self {
inner: egui_dock::DockState::new(vec![tab]),
}
}
pub fn new_tabs(tabs: Vec<Tab>) -> Self {
Self {
inner: egui_dock::DockState::new(tabs),
}
}
pub fn from_state(state: egui_dock::DockState<Tab>) -> Self {
Self { inner: state }
}
pub fn inner(&self) -> &egui_dock::DockState<Tab> {
&self.inner
}
pub fn inner_mut(&mut self) -> &mut egui_dock::DockState<Tab> {
&mut self.inner
}
pub fn push(&mut self, tab: Tab) {
self.inner.push_to_focused_leaf(tab);
}
pub fn tab_count(&self) -> usize {
self.inner.iter_all_tabs().count()
}
}
impl<Tab: PartialEq> DockTree<Tab> {
pub fn close(&mut self, tab: &Tab) -> Option<Tab> {
self.inner.find_tab(tab).map(|(surface, node, tab_idx)| {
self.inner.remove_tab((surface, node, tab_idx)).unwrap()
})
}
}
#[derive(Clone)]
pub struct DockStyle {
pub show_close_buttons: bool,
pub show_add_buttons: bool,
pub tabs_are_draggable: bool,
pub allowed_splits: bool,
pub tab_bar_height: Option<f32>,
}
impl Default for DockStyle {
fn default() -> Self {
Self {
show_close_buttons: true,
show_add_buttons: false,
tabs_are_draggable: true,
allowed_splits: true,
tab_bar_height: None,
}
}
}
pub struct DockArea<'a, Tab> {
tree: &'a mut DockTree<Tab>,
style: DockStyle,
}
impl<'a, Tab> DockArea<'a, Tab> {
pub fn new(tree: &'a mut DockTree<Tab>) -> Self {
Self {
tree,
style: DockStyle::default(),
}
}
pub fn show_close_buttons(mut self, show: bool) -> Self {
self.style.show_close_buttons = show;
self
}
pub fn show_add_buttons(mut self, show: bool) -> Self {
self.style.show_add_buttons = show;
self
}
pub fn tabs_are_draggable(mut self, draggable: bool) -> Self {
self.style.tabs_are_draggable = draggable;
self
}
pub fn allowed_splits(mut self, allowed: bool) -> Self {
self.style.allowed_splits = allowed;
self
}
pub fn tab_bar_height(mut self, height: f32) -> Self {
self.style.tab_bar_height = Some(height);
self
}
pub fn show<F>(self, ui: &mut Ui, mut tab_ui: F)
where
Tab: std::fmt::Debug,
F: FnMut(&mut Ui, &mut Tab),
{
let theme = Theme::current(ui.ctx());
let dock_style = build_dock_style(&theme, &self.style, ui.style());
let mut viewer = SimpleTabViewer {
tab_ui: &mut tab_ui,
style: &self.style,
_events: Vec::new(),
};
egui_dock::DockArea::new(&mut self.tree.inner)
.style(dock_style)
.show_close_buttons(self.style.show_close_buttons)
.show_add_buttons(self.style.show_add_buttons)
.draggable_tabs(self.style.tabs_are_draggable)
.show_inside(ui, &mut viewer);
}
pub fn show_with<Msg, F>(
self,
ctx: &mut ViewCtx<'_, Msg>,
mut tab_ui: F,
on_event: impl Fn(DockEvent<Tab>) -> Msg,
) where
Tab: std::fmt::Debug + Clone,
F: FnMut(&mut Ui, &mut Tab),
{
let theme = Theme::current(ctx.ui.ctx());
let dock_style = build_dock_style(&theme, &self.style, ctx.ui.style());
let mut events = Vec::new();
let mut viewer = EventTabViewer {
tab_ui: &mut tab_ui,
style: &self.style,
events: &mut events,
};
egui_dock::DockArea::new(&mut self.tree.inner)
.style(dock_style)
.show_close_buttons(self.style.show_close_buttons)
.show_add_buttons(self.style.show_add_buttons)
.draggable_tabs(self.style.tabs_are_draggable)
.show_inside(ctx.ui, &mut viewer);
for event in events {
ctx.emit(on_event(event));
}
}
}
fn build_dock_style(
theme: &Theme,
style: &DockStyle,
egui_style: &egui::Style,
) -> egui_dock::Style {
let mut dock_style = egui_dock::Style::from_egui(egui_style);
dock_style.tab_bar.height = style
.tab_bar_height
.unwrap_or(theme.spacing_lg + theme.spacing_sm);
dock_style.tab_bar.fill_tab_bar = true;
dock_style.tab_bar.bg_fill = theme.bg_primary;
dock_style.tab.tab_body.bg_fill = theme.bg_secondary;
dock_style.tab.active.bg_fill = theme.bg_secondary;
dock_style.tab.inactive.bg_fill = theme.bg_primary;
dock_style.tab.focused.bg_fill = theme.bg_secondary;
dock_style.tab.hovered.bg_fill = theme.bg_tertiary;
dock_style.separator.width = theme.spacing_xs;
dock_style.separator.color_idle = theme.border;
dock_style.separator.color_hovered = theme.primary;
dock_style.separator.color_dragged = theme.primary;
dock_style.main_surface_border_stroke = egui::Stroke::new(theme.border_width, theme.border);
dock_style
}
struct SimpleTabViewer<'a, Tab, F>
where
F: FnMut(&mut Ui, &mut Tab),
{
tab_ui: &'a mut F,
style: &'a DockStyle,
_events: Vec<DockEvent<Tab>>,
}
impl<'a, Tab, F> egui_dock::TabViewer for SimpleTabViewer<'a, Tab, F>
where
Tab: std::fmt::Debug,
F: FnMut(&mut Ui, &mut Tab),
{
type Tab = Tab;
fn title(&mut self, tab: &mut Self::Tab) -> egui::WidgetText {
format!("{:?}", tab).into()
}
fn ui(&mut self, ui: &mut Ui, tab: &mut Self::Tab) {
(self.tab_ui)(ui, tab);
}
fn closeable(&mut self, _tab: &mut Self::Tab) -> bool {
self.style.show_close_buttons
}
fn allowed_in_windows(&self, _tab: &mut Self::Tab) -> bool {
false }
}
struct EventTabViewer<'a, Tab, F>
where
F: FnMut(&mut Ui, &mut Tab),
{
tab_ui: &'a mut F,
style: &'a DockStyle,
events: &'a mut Vec<DockEvent<Tab>>,
}
impl<'a, Tab, F> egui_dock::TabViewer for EventTabViewer<'a, Tab, F>
where
Tab: std::fmt::Debug + Clone,
F: FnMut(&mut Ui, &mut Tab),
{
type Tab = Tab;
fn title(&mut self, tab: &mut Self::Tab) -> egui::WidgetText {
format!("{:?}", tab).into()
}
fn ui(&mut self, ui: &mut Ui, tab: &mut Self::Tab) {
(self.tab_ui)(ui, tab);
}
fn closeable(&mut self, _tab: &mut Self::Tab) -> bool {
self.style.show_close_buttons
}
fn on_close(&mut self, tab: &mut Self::Tab) -> egui_dock::widgets::tab_viewer::OnCloseResponse {
self.events.push(DockEvent::TabClosed(tab.clone()));
egui_dock::widgets::tab_viewer::OnCloseResponse::Close
}
fn on_add(&mut self, surface: SurfaceIndex, node: NodeIndex) {
self.events.push(DockEvent::AddClicked { surface, node });
}
fn allowed_in_windows(&self, _tab: &mut Self::Tab) -> bool {
false }
}
pub mod layout {
use super::*;
pub fn left_right<Tab>(left: Tab, right: Tab, left_fraction: f32) -> DockTree<Tab> {
let mut state = egui_dock::DockState::new(vec![left]);
let surface = state.main_surface_mut();
let [_left_node, _right_node] =
surface.split_right(NodeIndex::root(), 1.0 - left_fraction, vec![right]);
DockTree::from_state(state)
}
pub fn top_bottom<Tab>(top: Tab, bottom: Tab, top_fraction: f32) -> DockTree<Tab> {
let mut state = egui_dock::DockState::new(vec![top]);
let surface = state.main_surface_mut();
let [_top_node, _bottom_node] =
surface.split_below(NodeIndex::root(), 1.0 - top_fraction, vec![bottom]);
DockTree::from_state(state)
}
pub fn three_column<Tab>(
left: Tab,
center: Tab,
right: Tab,
left_frac: f32,
right_frac: f32,
) -> DockTree<Tab> {
let mut state = egui_dock::DockState::new(vec![center]);
let surface = state.main_surface_mut();
let [_left_node, center_node] =
surface.split_left(NodeIndex::root(), left_frac, vec![left]);
let [_center_node, _right_node] =
surface.split_right(center_node, right_frac / (1.0 - left_frac), vec![right]);
DockTree::from_state(state)
}
pub fn daw<Tab>(browser: Tab, main: Tab, inspector: Tab, timeline: Tab) -> DockTree<Tab> {
let mut state = egui_dock::DockState::new(vec![main]);
let surface = state.main_surface_mut();
let [_browser_node, center_node] =
surface.split_left(NodeIndex::root(), 0.2, vec![browser]);
let [_center_node, _inspector_node] =
surface.split_right(center_node, 0.25, vec![inspector]);
let [_top_node, _timeline_node] =
surface.split_below(NodeIndex::root(), 0.25, vec![timeline]);
DockTree::from_state(state)
}
pub fn vscode<Tab>(sidebar: Tab, editors: Vec<Tab>, terminals: Vec<Tab>) -> DockTree<Tab> {
let editor_tabs = if editors.is_empty() {
return DockTree::new_single(sidebar);
} else {
editors
};
let mut state = egui_dock::DockState::new(editor_tabs);
let surface = state.main_surface_mut();
let [_sidebar_node, editor_node] =
surface.split_left(NodeIndex::root(), 0.2, vec![sidebar]);
if !terminals.is_empty() {
let [_editor_node, _terminal_node] = surface.split_below(editor_node, 0.3, terminals);
}
DockTree::from_state(state)
}
}
pub mod raw {
#[allow(unused_imports)]
pub use egui_dock::*;
}