diff --git a/crates/egui_extras/src/dock/mod.rs b/crates/egui_extras/src/dock/mod.rs index 3b617fdab..0e0b1e7b4 100644 --- a/crates/egui_extras/src/dock/mod.rs +++ b/crates/egui_extras/src/dock/mod.rs @@ -37,6 +37,10 @@ impl std::fmt::Debug for NodeId { pub struct Dock { pub root: NodeId, pub nodes: Nodes, + + /// Smoothed avaerage of preview + #[serde(skip)] + pub smoothed_preview_rect: Option, } impl Default for Dock { @@ -44,6 +48,7 @@ impl Default for Dock { Self { root: Default::default(), nodes: Default::default(), + smoothed_preview_rect: None, } } } @@ -257,7 +262,11 @@ pub trait Behavior { impl Dock { pub fn new(root: NodeId, nodes: Nodes) -> Self { - Self { root, nodes } + Self { + root, + nodes, + smoothed_preview_rect: None, + } } } @@ -412,6 +421,22 @@ impl Dock { self.root } + fn smooth_preview_rect(&mut self, ctx: &egui::Context, new_rect: Rect) -> Rect { + let dt = ctx.input(|input| input.stable_dt).at_most(0.1); + let t = egui::emath::exponential_smooth_factor(0.9, 0.05, dt); + + let smoothed = self.smoothed_preview_rect.get_or_insert(new_rect); + *smoothed = smoothed.lerp_towards(&new_rect, t); + + let diff = smoothed.min.distance(new_rect.min) + smoothed.max.distance(new_rect.max); + if diff < 0.5 { + *smoothed = new_rect; + } else { + ctx.request_repaint(); + } + *smoothed + } + pub fn ui(&mut self, behavior: &mut dyn Behavior, ui: &mut Ui) { let options = behavior.simplification_options(); self.simplify(&options); @@ -462,11 +487,23 @@ impl Dock { }); if let Some(preview_rect) = drop_context.preview_rect { + let preview_rect = self.smooth_preview_rect(ui.ctx(), preview_rect); + + let preview_stroke = ui.visuals().selection.stroke; + let preview_color = preview_stroke.color; + + if let Some(insertion_point) = &drop_context.best_insertion { + if let Some(node) = self.nodes.nodes.get(&insertion_point.parent_id) { + // Show which parent we will be dropped into + ui.painter().rect_stroke(node.rect, 1.0, preview_stroke); + } + } + ui.painter().rect( preview_rect, 1.0, - Color32::LIGHT_BLUE.gamma_multiply(0.5), - (1.0, Color32::LIGHT_BLUE), + preview_color.gamma_multiply(0.5), + preview_stroke, ); let preview_child = false; @@ -489,7 +526,10 @@ impl Dock { if let Some(insertion_point) = drop_context.best_insertion { self.move_node(dragged_node_id, insertion_point); } + self.smoothed_preview_rect = None; } + } else { + self.smoothed_preview_rect = None; } } @@ -721,12 +761,14 @@ impl Nodes { // ---------------------------------------------------------------------------- // ui +#[derive(Clone, Copy, Debug)] enum LayoutType { Tabs, Horizontal, Vertical, } +#[derive(Clone, Copy, Debug)] struct InsertionPoint { parent_id: NodeId, @@ -951,7 +993,7 @@ impl Nodes { let rect = self.nodes[&child].rect; if is_being_dragged(ui.ctx(), child) { - // suggest self as drop-target: + // Leave a hole, and suggest that hole as drop-target: drop_context.suggest_rect( InsertionPoint::new(parent_id, LayoutType::Horizontal, i), rect, diff --git a/crates/emath/src/rect.rs b/crates/emath/src/rect.rs index c93e681df..464aded69 100644 --- a/crates/emath/src/rect.rs +++ b/crates/emath/src/rect.rs @@ -361,13 +361,28 @@ impl Rect { /// Linearly interpolate so that `[0, 0]` is [`Self::min`] and /// `[1, 1]` is [`Self::max`]. + #[deprecated = "Use `lerp_inside` instead"] pub fn lerp(&self, t: Vec2) -> Pos2 { + self.lerp_inside(t) + } + + /// Linearly interpolate so that `[0, 0]` is [`Self::min`] and + /// `[1, 1]` is [`Self::max`]. + pub fn lerp_inside(&self, t: Vec2) -> Pos2 { Pos2 { x: lerp(self.min.x..=self.max.x, t.x), y: lerp(self.min.y..=self.max.y, t.y), } } + /// Linearly self towards other rect. + pub fn lerp_towards(&self, other: &Rect, t: f32) -> Self { + Self { + min: self.min.lerp(other.min, t), + max: self.max.lerp(other.max, t), + } + } + #[inline(always)] pub fn x_range(&self) -> RangeInclusive { self.min.x..=self.max.x