use egui::{
CentralPanel, Color32, Context, CursorIcon, EventFilter, Frame, Key, LayerId, Order, Pos2,
Rect, Rounding, Sense, Ui, Vec2,
};
use duplicate::duplicate;
use paste::paste;
use crate::{
utils::{expand_to_pixel, fade_dock_style, map_to_pixel},
AllowedSplits, DockArea, Node, NodeIndex, OverlayType, Style, SurfaceIndex, TabDestination,
TabViewer,
};
use super::{drag_and_drop::TreeComponent, state::State, tab_removal::TabRemoval};
mod leaf;
mod main_surface;
mod window_surface;
impl<'tree, Tab> DockArea<'tree, Tab> {
#[inline]
pub fn show(self, ctx: &Context, tab_viewer: &mut impl TabViewer<Tab = Tab>) {
CentralPanel::default()
.frame(
Frame::central_panel(&ctx.style())
.inner_margin(0.)
.fill(Color32::TRANSPARENT),
)
.show(ctx, |ui| {
self.show_inside(ui, tab_viewer);
});
}
pub fn show_inside(mut self, ui: &mut Ui, tab_viewer: &mut impl TabViewer<Tab = Tab>) {
self.style
.get_or_insert(Style::from_egui(ui.style().as_ref()));
self.window_bounds.get_or_insert(ui.ctx().screen_rect());
let mut state = State::load(ui.ctx(), self.id);
if !ui.input(|i| i.pointer.any_released()) {
state.last_hover_pos = ui.input(|i| i.pointer.hover_pos());
}
let style = self.style.as_ref().unwrap();
let fade_surface =
self.hovered_window_surface(&mut state, style.overlay.feel.fade_hold_time, ui.ctx());
let fade_style = {
fade_surface.is_some().then(|| {
let mut fade_style = style.clone();
fade_dock_style(&mut fade_style, style.overlay.surface_fade_opacity);
(fade_style, style.overlay.surface_fade_opacity)
})
};
for &surface_index in self.dock_state.valid_surface_indices().iter() {
self.show_surface_inside(
surface_index,
ui,
tab_viewer,
&mut state,
fade_style.as_ref().map(|(style, factor)| {
(style, *factor, fade_surface.unwrap_or(SurfaceIndex::main()))
}),
);
}
for index in self.to_remove.drain(..).rev() {
match index {
TabRemoval::Node(surface, node, tab) => {
self.dock_state[surface].remove_tab((node, tab));
if self.dock_state[surface].is_empty() && !surface.is_main() {
self.dock_state.remove_surface(surface);
}
}
TabRemoval::Window(index) => {
self.dock_state.remove_surface(index);
}
}
}
for (surface_index, node_index, tab_index) in self.to_detach.drain(..).rev() {
let mouse_pos = state.last_hover_pos;
self.dock_state.detach_tab(
(surface_index, node_index, tab_index),
Rect::from_min_size(
mouse_pos.unwrap_or(Pos2::ZERO),
self.dock_state[surface_index][node_index]
.rect()
.map_or(Vec2::new(100., 150.), |rect| rect.size()),
),
);
}
if let Some(focused) = self.new_focused {
self.dock_state.set_focused_node_and_surface(focused);
}
if let (Some(source), Some(hover)) = (self.drag_data.take(), self.hover_data.take()) {
let style = self.style.as_ref().unwrap();
state.set_drag_and_drop(source, hover, ui.ctx(), style);
let tab_dst = {
let layer_id = LayerId::new(Order::Foreground, "foreground".into());
ui.with_layer_id(layer_id, |ui| {
self.show_drag_drop_overlay(ui, &mut state, tab_viewer)
})
.inner
};
if ui.input(|i| i.pointer.primary_released()) {
let source = {
match state.dnd.as_ref().unwrap().drag.src {
TreeComponent::Tab(src_surf, src_node, src_tab) => {
(src_surf, src_node, src_tab)
}
_ => todo!(
"collections of tabs, like nodes and surfaces can't be docked (yet)"
),
}
};
if let Some(destination) = tab_dst {
self.dock_state.move_tab(source, destination);
}
state.reset_drag();
}
}
state.store(ui.ctx(), self.id);
}
#[inline(always)]
fn hovered_window_surface(
&self,
state: &mut State,
hold_time: f32,
ctx: &Context,
) -> Option<SurfaceIndex> {
if let Some(dnd_state) = &state.dnd {
if dnd_state.is_locked(self.style.as_ref().unwrap(), ctx) {
state.window_fade =
Some((ctx.input(|i| i.time), dnd_state.hover.dst.surface_address()));
}
}
state.window_fade.and_then(|(time, surface)| {
ctx.request_repaint();
(hold_time > (ctx.input(|i| i.time) - time) as f32).then_some(surface)
})
}
fn show_drag_drop_overlay(
&mut self,
ui: &Ui,
state: &mut State,
tab_viewer: &impl TabViewer<Tab = Tab>,
) -> Option<TabDestination> {
let drag_state = state.dnd.as_mut().unwrap();
let style = self.style.as_ref().unwrap();
let deserted_node = {
match (
drag_state.drag.src.node_address(),
drag_state.hover.dst.node_address(),
) {
((src_surf, Some(src_node)), (dst_surf, Some(dst_node))) => {
src_surf == dst_surf
&& src_node == dst_node
&& self.dock_state[src_surf][src_node].tabs_count() == 1
}
_ => false,
}
};
let restricted_splits = if drag_state.hover.dst.is_surface() || deserted_node {
AllowedSplits::None
} else {
AllowedSplits::All
};
let allowed_splits = self.allowed_splits & restricted_splits;
let allowed_in_window = match drag_state.drag.src {
TreeComponent::Tab(surface, node, tab) => {
let Node::Leaf { tabs, .. } = &mut self.dock_state[surface][node] else {
unreachable!("tab drags can only come from leaf nodes")
};
tab_viewer.allowed_in_windows(&mut tabs[tab.0])
}
_ => todo!("collections of tabs, like nodes or surfaces, can't be dragged! (yet)"),
};
if let Some(pointer) = state.last_hover_pos {
drag_state.pointer = pointer;
}
let window_bounds = self.window_bounds.unwrap();
if drag_state.is_on_title_bar()
|| style.overlay.overlay_type == OverlayType::HighlightedAreas
{
drag_state.resolve_traditional(
ui,
style,
allowed_splits,
allowed_in_window,
window_bounds,
)
} else {
drag_state.resolve_icon_based(
ui,
style,
allowed_splits,
allowed_in_window,
window_bounds,
)
}
}
fn show_surface_inside(
&mut self,
surf_index: SurfaceIndex,
ui: &mut Ui,
tab_viewer: &mut impl TabViewer<Tab = Tab>,
state: &mut State,
fade_style: Option<(&Style, f32, SurfaceIndex)>,
) {
if surf_index.is_main() {
self.show_root_surface_inside(ui, tab_viewer, state);
} else {
self.show_window_surface(ui, surf_index, tab_viewer, state, fade_style);
}
}
fn render_nodes(
&mut self,
ui: &mut Ui,
tab_viewer: &mut impl TabViewer<Tab = Tab>,
state: &mut State,
surf_index: SurfaceIndex,
fade_style: Option<(&Style, f32)>,
) {
let max_rect = self.allocate_area_for_root_node(ui, surf_index);
for node_index in self.dock_state[surf_index].breadth_first_index_iter() {
if self.dock_state[surf_index][node_index].is_parent() {
self.compute_rect_sizes(ui, (surf_index, node_index), max_rect);
}
}
for node_index in self.dock_state[surf_index].breadth_first_index_iter() {
if self.dock_state[surf_index][node_index].is_leaf() {
self.show_leaf(ui, state, (surf_index, node_index), tab_viewer, fade_style);
}
}
let fade_style = fade_style.map(|(style, _)| style);
for node_index in self.dock_state[surf_index].breadth_first_index_iter() {
if self.dock_state[surf_index][node_index].is_parent() {
self.show_separator(ui, (surf_index, node_index), fade_style);
}
}
}
fn allocate_area_for_root_node(&mut self, ui: &mut Ui, surface: SurfaceIndex) -> Rect {
let style = self.style.as_ref().unwrap();
let mut rect = ui.available_rect_before_wrap();
if let Some(margin) = style.dock_area_padding {
rect.min += margin.left_top();
rect.max -= margin.right_bottom();
}
ui.painter().rect_stroke(
rect,
style.main_surface_border_rounding,
style.main_surface_border_stroke,
);
if surface == SurfaceIndex::main() {
rect = rect.expand(-style.main_surface_border_stroke.width / 2.0);
}
ui.allocate_rect(rect, Sense::hover());
if self.dock_state[surface].is_empty() {
return rect;
}
self.dock_state[surface][NodeIndex::root()].set_rect(rect);
rect
}
fn compute_rect_sizes(
&mut self,
ui: &Ui,
(surface_index, node_index): (SurfaceIndex, NodeIndex),
max_rect: Rect,
) {
assert!(self.dock_state[surface_index][node_index].is_parent());
let style = self.style.as_ref().unwrap();
let pixels_per_point = ui.ctx().pixels_per_point();
duplicate! {
[
orientation dim_point dim_size left_of right_of;
[Horizontal] [x] [width] [left_of] [right_of];
[Vertical] [y] [height] [above] [below];
]
if let Node::orientation { fraction, rect } = &mut self.dock_state[surface_index][node_index] {
debug_assert!(!rect.any_nan() && rect.is_finite());
let rect = expand_to_pixel(*rect, pixels_per_point);
let midpoint = rect.min.dim_point + rect.dim_size() * *fraction;
let left_separator_border = map_to_pixel(
midpoint - style.separator.width * 0.5,
pixels_per_point,
f32::round
);
let right_separator_border = map_to_pixel(
midpoint + style.separator.width * 0.5,
pixels_per_point,
f32::round
);
paste! {
let left = rect.intersect(Rect::[<everything_ left_of>](left_separator_border)).intersect(max_rect);
let right = rect.intersect(Rect::[<everything_ right_of>](right_separator_border)).intersect(max_rect);
}
self.dock_state[surface_index][node_index.left()].set_rect(left);
self.dock_state[surface_index][node_index.right()].set_rect(right);
}
}
}
fn show_separator(
&mut self,
ui: &mut Ui,
(surface_index, node_index): (SurfaceIndex, NodeIndex),
fade_style: Option<&Style>,
) {
assert!(self.dock_state[surface_index][node_index].is_parent());
let style = fade_style.unwrap_or_else(|| self.style.as_ref().unwrap());
let pixels_per_point = ui.ctx().pixels_per_point();
duplicate! {
[
orientation dim_point dim_size;
[Horizontal] [x] [width];
[Vertical] [y] [height];
]
if let Node::orientation { fraction, ref rect } = &mut self.dock_state[surface_index][node_index] {
let mut separator = *rect;
let midpoint = rect.min.dim_point + rect.dim_size() * *fraction;
separator.min.dim_point = midpoint - style.separator.width * 0.5;
separator.max.dim_point = midpoint + style.separator.width * 0.5;
let mut expand = Vec2::ZERO;
expand.dim_point += style.separator.extra_interact_width / 2.0;
let interact_rect = separator.expand2(expand);
let response = ui.allocate_rect(interact_rect, Sense::click_and_drag())
.on_hover_and_drag_cursor(paste!{ CursorIcon::[<Resize orientation>]});
let should_respond_to_arrow_keys = ui.input(|i| i.modifiers.command || i.modifiers.shift);
if response.has_focus() {
ui.memory_mut(|m| m.set_focus_lock_filter(response.id, EventFilter {
horizontal_arrows: should_respond_to_arrow_keys,
vertical_arrows: should_respond_to_arrow_keys,
tab: false,
escape: false
}));
}
let arrow_key_offset = if response.has_focus() && should_respond_to_arrow_keys {
if ui.input(|i| i.key_pressed(Key::ArrowUp)) {
Some(egui::vec2(0., -16.))
} else if ui.input(|i| i.key_pressed(Key::ArrowDown)) {
Some(egui::vec2(0., 16.))
} else if ui.input(|i| i.key_pressed(Key::ArrowLeft)) {
Some(egui::vec2(-16., 0.))
} else if ui.input(|i| i.key_pressed(Key::ArrowRight)) {
Some(egui::vec2(16., 0.))
} else {
None
}
} else {
None
};
let midpoint = rect.min.dim_point + rect.dim_size() * *fraction;
separator.min.dim_point = map_to_pixel(
midpoint - style.separator.width * 0.5,
pixels_per_point,
f32::round,
);
separator.max.dim_point = map_to_pixel(
midpoint + style.separator.width * 0.5,
pixels_per_point,
f32::round,
);
let color = if response.dragged() {
style.separator.color_dragged
} else if response.hovered() || response.has_focus() {
style.separator.color_hovered
} else {
style.separator.color_idle
};
ui.painter().rect_filled(separator, Rounding::ZERO, color);
if let Some(pos) = response.interact_pointer_pos().or(arrow_key_offset.map(|v| separator.center() + v)) {
let dim_point = pos.dim_point;
let delta = arrow_key_offset.unwrap_or(response.drag_delta()).dim_point;
if (delta > 0. && dim_point > midpoint && dim_point < rect.max.dim_point)
|| (delta < 0. && dim_point < midpoint && dim_point > rect.min.dim_point)
{
let range = rect.max.dim_point - rect.min.dim_point;
let min = (style.separator.extra / range).min(1.0);
let max = 1.0 - min;
let (min, max) = (min.min(max), max.max(min));
*fraction = (*fraction + delta / range).clamp(min, max);
}
}
if response.double_clicked() {
*fraction = 0.5;
}
}
}
}
}