1
0
mirror of https://github.com/emilk/egui.git synced 2026-06-27 23:13:13 -04:00

Preview drag destinations

This commit is contained in:
Emil Ernerfeldt
2023-04-23 20:28:23 +02:00
parent 92d93cb24a
commit 9b154c6d1d
3 changed files with 211 additions and 43 deletions

View File

@@ -1,8 +1,8 @@
use std::collections::{HashMap, HashSet};
use egui::{
pos2, vec2, CursorIcon, Id, Key, NumExt, Rect, Response, Sense, Style, TextStyle, Ui,
WidgetText,
pos2, vec2, Color32, CursorIcon, Id, Key, NumExt, Pos2, Rect, Response, Sense, Style,
TextStyle, Ui, WidgetText,
};
// ----------------------------------------------------------------------------
@@ -277,40 +277,80 @@ impl<Leaf> Dock<Leaf> {
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;
}
// Check if anything is being dragged:
let mouse_pos = ui.input(|i| i.pointer.hover_pos());
let dragged_id = self.dragged_id(ui.ctx());
if let (Some(mouse_pos), Some(dragged_node_id)) = (mouse_pos, dragged_id) {
// Preview what is being dragged:
egui::Area::new(Id::new((dragged_node_id, "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, dragged_node_id);
ui.label(text);
});
});
// 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);
});
});
let mut drop_context = DropContext {
dragged_node_id,
mouse_pos,
best_dist_sq: f32::INFINITY,
target_node_id: None,
preview_rect: None,
};
self.nodes.find_drop_target(&mut drop_context, self.root);
if let Some(preview_rect) = drop_context.preview_rect {
ui.painter().rect_filled(
preview_rect,
1.0,
Color32::LIGHT_BLUE.gamma_multiply(0.5),
);
}
// if ui.input(|i| i.pointer.any_released()) {
// ui.memory_mut(|mem| mem.stop_dragging());
// if let Some(target_node_id) = drop_context.target_node_id {
// self.remove_node_id_from_parent(dragged_node_id);
// // self.drop_node(behavior, target_node_id, dragged_node_id); // TODO
// }
// }
}
}
/// Find the currently dragged node, if any.
fn dragged_id(&self, ctx: &egui::Context) -> Option<NodeId> {
if ctx.input(|i| i.pointer.could_any_button_be_click()) {
// We're not sure we're dragging _at all_ yet.
return None;
}
for &node_id in self.nodes.nodes.keys() {
if node_id == self.root {
continue; // now allowed to drag root
}
let id = node_id.id();
let is_node_being_dragged = ctx.memory(|mem| mem.is_being_dragged(id));
if is_node_being_dragged {
// Abort drags on escape:
if ctx.input(|i| i.key_pressed(Key::Escape)) {
ctx.memory_mut(|mem| mem.stop_dragging());
return None;
}
return Some(node_id);
}
}
None
}
fn remove_node_id_from_parent(&mut self, dragged_node_id: NodeId) {
self.nodes
.remove_node_id_from_parent(self.root, dragged_node_id);
}
}
@@ -349,14 +389,9 @@ impl<Leaf> Nodes<Leaf> {
return GcAction::Remove;
}
}
NodeLayout::Tabs(layout) => {
layout
.children
.retain(|&child| self.gc_node_id(behavior, visited, child) == GcAction::Keep);
}
NodeLayout::Horizontal(layout) => {
layout
.children
NodeLayout::Tabs(Tabs { children, .. })
| NodeLayout::Horizontal(Horizontal { children, .. }) => {
children
.retain(|&child| self.gc_node_id(behavior, visited, child) == GcAction::Keep);
}
}
@@ -480,9 +515,16 @@ impl<Leaf> Nodes<Leaf> {
for &child_id in &tabs.children {
let selected = child_id == tabs.active;
let id = child_id.id();
let is_node_being_dragged = ui.memory(|mem| mem.is_being_dragged(id))
&& !ui.input(|i| i.pointer.could_any_button_be_click());
if is_node_being_dragged {
continue; // leave a gap!
}
let response = behavior.tab_ui(self, ui, id, child_id, selected);
let response = response.on_hover_cursor(CursorIcon::Grab);
if response.clicked() {
if response.clicked() || response.drag_started() {
tabs.active = child_id;
}
}
@@ -503,3 +545,105 @@ impl<Leaf> Nodes<Leaf> {
}
}
}
// ----------------------------------------------------------------------------
// Dropping
struct DropContext {
dragged_node_id: NodeId,
mouse_pos: Pos2,
target_node_id: Option<NodeId>,
best_dist_sq: f32,
preview_rect: Option<Rect>,
}
impl DropContext {
fn suggest_point(&mut self, node_id: NodeId, target_point: Pos2, preview_rect: Rect) {
let dist_sq = self.mouse_pos.distance_sq(target_point);
if dist_sq < self.best_dist_sq {
self.best_dist_sq = dist_sq;
self.target_node_id = Some(node_id);
self.preview_rect = Some(preview_rect);
}
}
}
impl<Leaf> Nodes<Leaf> {
fn find_drop_target(&self, drop_context: &mut DropContext, node_id: NodeId) {
if drop_context.dragged_node_id == node_id {
// Can't drag a node onto self or any children
return;
}
let Some(node) = self.nodes.get(&node_id) else { return; };
drop_context.suggest_point(
node_id,
node.rect.left_center(),
node.rect.split_left_right_at_fraction(0.5).0,
);
drop_context.suggest_point(
node_id,
node.rect.right_center(),
node.rect.split_left_right_at_fraction(0.5).1,
);
drop_context.suggest_point(
node_id,
node.rect.center_top(),
node.rect.split_top_bottom_at_fraction(0.5).0,
);
drop_context.suggest_point(
node_id,
node.rect.center_bottom(),
node.rect.split_top_bottom_at_fraction(0.5).1,
);
drop_context.suggest_point(node_id, node.rect.center(), node.rect);
match &node.layout {
NodeLayout::Leaf(_) => {}
NodeLayout::Tabs(Tabs { active, .. }) => {
if let Some(active_node) = self.nodes.get(active) {
// Suggest dropping into tab bar
let tabs_rect = active_node
.rect
.split_top_bottom_at_y(active_node.rect.top())
.0;
if tabs_rect.contains(drop_context.mouse_pos) {
drop_context.suggest_point(node_id, drop_context.mouse_pos, tabs_rect)
}
}
self.find_drop_target(drop_context, *active);
}
NodeLayout::Horizontal(Horizontal { children, .. }) => {
for &child in children {
self.find_drop_target(drop_context, child);
}
}
}
}
// fn drop_node(
// behavior: &mut dyn Behavior<Leaf>,
// dropped_node_id: NodeId,
// target_node_id: NodeId,
// mouse_pos: Pos2,
// ) {
// // TODO
// }
fn remove_node_id_from_parent(&mut self, it: NodeId, remove: NodeId) {
let Some(mut node) = self.nodes.remove(&it) else { return; };
match &mut node.layout {
NodeLayout::Leaf(_) => {}
NodeLayout::Tabs(Tabs { children, .. })
| NodeLayout::Horizontal(Horizontal { children, .. }) => {
children.retain(|&child| {
self.remove_node_id_from_parent(child, remove);
child != remove
});
}
}
self.nodes.insert(it, node);
}
}

View File

@@ -521,6 +521,30 @@ impl Rect {
pub fn right_bottom(&self) -> Pos2 {
pos2(self.right(), self.bottom())
}
/// Split rectangle in left and right halfs. `t` is expected to be in the (0,1) range.
pub fn split_left_right_at_fraction(&self, t: f32) -> (Rect, Rect) {
self.split_left_right_at_x(lerp(self.min.x..=self.max.x, t))
}
/// Split rectangle in left and right halfs at the given `x` coordinate.
pub fn split_left_right_at_x(&self, split_x: f32) -> (Rect, Rect) {
let left = Rect::from_min_max(self.min, Pos2::new(split_x, self.max.y));
let right = Rect::from_min_max(Pos2::new(split_x, self.min.y), self.max);
(left, right)
}
/// Split rectangle in top and bottom halfs. `t` is expected to be in the (0,1) range.
pub fn split_top_bottom_at_fraction(&self, t: f32) -> (Rect, Rect) {
self.split_top_bottom_at_y(lerp(self.min.y..=self.max.y, t))
}
/// Split rectangle in top and bottom halfs at the given `y` coordinate.
pub fn split_top_bottom_at_y(&self, split_y: f32) -> (Rect, Rect) {
let top = Rect::from_min_max(self.min, Pos2::new(self.max.x, split_y));
let bottom = Rect::from_min_max(Pos2::new(self.min.x, split_y), self.max);
(top, bottom)
}
}
impl std::fmt::Debug for Rect {

View File

@@ -8,7 +8,7 @@ use egui_extras::dock;
fn main() -> Result<(), eframe::Error> {
env_logger::init(); // Log to stderr (if you run with `RUST_LOG=debug`).
let options = eframe::NativeOptions {
initial_window_size: Some(egui::vec2(320.0, 240.0)),
initial_window_size: Some(egui::vec2(800.0, 600.0)),
..Default::default()
};
eframe::run_native("Dock", options, Box::new(|_cc| Box::<MyApp>::default()))
@@ -22,7 +22,7 @@ pub struct View {
impl View {
pub fn with_nr(i: usize) -> Self {
Self {
title: format!("Node {}", i),
title: format!("View {i}"),
color: egui::epaint::Hsva::new(0.1 * i as f32, 0.5, 0.5, 1.0).into(),
}
}