From 92d93cb24aa220d9bd2d4c266b77c1cc1b822362 Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Sun, 23 Apr 2023 17:24:06 +0200 Subject: [PATCH] Drag tab names --- crates/egui/src/input_state.rs | 2 +- crates/egui/src/memory.rs | 5 ++ crates/egui_extras/src/dock/mod.rs | 80 +++++++++++++++++++++++------- 3 files changed, 69 insertions(+), 18 deletions(-) diff --git a/crates/egui/src/input_state.rs b/crates/egui/src/input_state.rs index 67cc06318..eccda3f5c 100644 --- a/crates/egui/src/input_state.rs +++ b/crates/egui/src/input_state.rs @@ -895,7 +895,7 @@ impl PointerState { /// If the pointer button is down, will it register as a click when released? #[inline(always)] - pub(crate) fn could_any_button_be_click(&self) -> bool { + pub fn could_any_button_be_click(&self) -> bool { if !self.any_down() { return false; } diff --git a/crates/egui/src/memory.rs b/crates/egui/src/memory.rs index 893db0fbf..db76f7e7c 100644 --- a/crates/egui/src/memory.rs +++ b/crates/egui/src/memory.rs @@ -477,6 +477,11 @@ impl Memory { self.interaction.drag_id = Some(id); } + #[inline(always)] + pub fn stop_dragging(&mut self) { + self.interaction.drag_id = None; + } + /// Forget window positions, sizes etc. /// Can be used to auto-layout windows. pub fn reset_areas(&mut self) { diff --git a/crates/egui_extras/src/dock/mod.rs b/crates/egui_extras/src/dock/mod.rs index 0e3552416..0d11d190b 100644 --- a/crates/egui_extras/src/dock/mod.rs +++ b/crates/egui_extras/src/dock/mod.rs @@ -1,6 +1,9 @@ use std::collections::{HashMap, HashSet}; -use egui::{pos2, vec2, CursorIcon, NumExt, Rect, WidgetText}; +use egui::{ + pos2, vec2, CursorIcon, Id, Key, NumExt, Rect, Response, Sense, Style, TextStyle, Ui, + WidgetText, +}; // ---------------------------------------------------------------------------- // Types required for state @@ -18,6 +21,11 @@ impl NodeId { use rand::Rng as _; Self(rand::thread_rng().gen()) } + + /// Corresponding [`egui::Id`], used for dragging. + pub fn id(&self) -> Id { + Id::new(self) + } } /// The top level type. Contains all peristent state, including layouts and sizes. @@ -157,9 +165,9 @@ pub trait Behavior { /// Show this leaf node in the given [`egui::Ui`]. /// /// If this is an unknown node, return [`NodeAction::Remove`] and the node will be removed. - fn leaf_ui(&mut self, _ui: &mut egui::Ui, _node_id: NodeId, _leaf: &mut Leaf); + fn leaf_ui(&mut self, _ui: &mut Ui, _node_id: NodeId, _leaf: &mut Leaf); - fn tab_text_for_leaf(&mut self, leaf: &Leaf) -> egui::WidgetText; + fn tab_text_for_leaf(&mut self, leaf: &Leaf) -> WidgetText; fn tab_text_for_node(&mut self, nodes: &Nodes, node_id: NodeId) -> WidgetText { match &nodes.nodes[&node_id].layout { @@ -171,14 +179,16 @@ pub trait Behavior { fn tab_ui( &mut self, nodes: &Nodes, - ui: &mut egui::Ui, + ui: &mut Ui, + id: Id, node_id: NodeId, selected: bool, - ) -> egui::Response { + ) -> Response { let text = self.tab_text_for_node(nodes, node_id); - let font_id = egui::TextStyle::Button.resolve(ui.style()); + let font_id = TextStyle::Button.resolve(ui.style()); let galley = text.into_galley(ui, Some(false), f32::INFINITY, font_id); - let (rect, response) = ui.allocate_exact_size(galley.size(), egui::Sense::click_and_drag()); + let (_, rect) = ui.allocate_space(galley.size()); + let response = ui.interact(rect, id, Sense::click_and_drag()); let widget_style = ui.style().interact_selectable(&response, selected); ui.painter() .galley_with_color(rect.min, galley.galley, widget_style.text_color()); @@ -194,12 +204,12 @@ pub trait Behavior { // Settings: /// The height of the bar holding tab names. - fn tab_bar_height(&self, _style: &egui::Style) -> f32 { + fn tab_bar_height(&self, _style: &Style) -> f32 { 20.0 } /// Width of the gap between nodes in a horizontal or vertical layout - fn gap_width(&self, _style: &egui::Style) -> f32 { + fn gap_width(&self, _style: &Style) -> f32 { 1.0 } } @@ -255,7 +265,7 @@ impl Dock { self.root } - pub fn ui(&mut self, behavior: &mut dyn Behavior, ui: &mut egui::Ui) { + pub fn ui(&mut self, behavior: &mut dyn Behavior, ui: &mut Ui) { self.nodes.gc_root(behavior, self.root); self.nodes.layout_node( @@ -266,6 +276,41 @@ impl Dock { ); self.nodes.node_ui(behavior, ui, self.root); + + if let Some(mouse_pos) = ui.input(|i| { + if i.pointer.could_any_button_be_click() { + // Wait until the mouse has move a bit or been down long enough + // before registerint a drag + None + } else { + i.pointer.hover_pos() + } + }) { + // Check if anything is being dragged: + for (node_id, node) in &self.nodes.nodes { + let id = node_id.id(); + if ui.memory(|mem| mem.is_being_dragged(id)) { + // Abort on escape: + if ui.input(|i| i.key_pressed(Key::Escape)) { + ui.memory_mut(|mem| mem.stop_dragging()); + continue; + } + + // This node is being dragged! + egui::Area::new(id.with("preview")) + .pivot(egui::Align2::CENTER_CENTER) + .current_pos(mouse_pos) + .interactable(false) + .show(ui.ctx(), |ui| { + egui::Frame::popup(ui.style()).show(ui, |ui| { + // TODO: preview contents + let text = behavior.tab_text_for_node(&self.nodes, *node_id); + ui.label(text); + }); + }); + } + } + } } } @@ -326,7 +371,7 @@ impl Nodes { impl Nodes { fn layout_node( &mut self, - style: &egui::Style, + style: &Style, behavior: &mut dyn Behavior, rect: Rect, node_id: NodeId, @@ -349,7 +394,7 @@ impl Nodes { fn layout_tabs( &mut self, - style: &egui::Style, + style: &Style, behavior: &mut dyn Behavior, rect: Rect, tabs: &Tabs, @@ -369,7 +414,7 @@ impl Nodes { fn layout_horizontal( &mut self, - style: &egui::Style, + style: &Style, behavior: &mut dyn Behavior, rect: Rect, horizontal: &Horizontal, @@ -399,7 +444,7 @@ impl Nodes { // ui impl Nodes { - fn node_ui(&mut self, behavior: &mut dyn Behavior, ui: &mut egui::Ui, node_id: NodeId) { + fn node_ui(&mut self, behavior: &mut dyn Behavior, ui: &mut Ui, node_id: NodeId) { let Some(mut node) = self.nodes.remove(&node_id) else { return }; match &mut node.layout { @@ -418,7 +463,7 @@ impl Nodes { fn tabs_ui( &mut self, behavior: &mut dyn Behavior, - ui: &mut egui::Ui, + ui: &mut Ui, rect: Rect, tabs: &mut Tabs, ) { @@ -434,7 +479,8 @@ impl Nodes { tab_bar_ui.horizontal(|ui| { for &child_id in &tabs.children { let selected = child_id == tabs.active; - let response = behavior.tab_ui(self, ui, child_id, selected); + let id = child_id.id(); + let response = behavior.tab_ui(self, ui, id, child_id, selected); let response = response.on_hover_cursor(CursorIcon::Grab); if response.clicked() { tabs.active = child_id; @@ -448,7 +494,7 @@ impl Nodes { fn horizontal_ui( &mut self, behavior: &mut dyn Behavior, - ui: &mut egui::Ui, + ui: &mut Ui, _rect: Rect, horizontal: &mut Horizontal, ) {