1use egui::{
2 self, emath::TSTransform, vec2, DragAndDrop, Id, LayerId, Order, Rect, Response, Sense, Ui,
3 UiBuilder,
4};
5
6pub fn drag_source_collapsing<P>(
7 ui: &mut Ui,
8 id: Id,
9 payload: P,
10 body: impl FnOnce(&mut Ui),
11) -> Response
12where
13 P: 'static + Send + Sync,
14{
15 let ctx = ui.ctx().clone();
16 let is_dragged = ctx.is_being_dragged(id);
17 if !is_dragged {
18 return ui.dnd_drag_source(id, payload, body).response;
19 }
20 DragAndDrop::set_payload(&ctx, payload);
21 let layer_id = LayerId::new(Order::Tooltip, id);
22 let mut child = ui.new_child(UiBuilder::new().layer_id(layer_id));
23 body(&mut child);
24 let body_rect = child.min_rect();
25 if let Some(pos) = ctx.pointer_interact_pos() {
26 let delta = pos - body_rect.center();
27 ctx.transform_layer_shapes(layer_id, TSTransform::from_translation(delta));
28 }
29 ui.interact(body_rect, id, Sense::hover())
30}
31
32pub fn make_room_gap(
34 ui: &mut Ui,
35 is_target: bool,
36 slot_id: Id,
37 height: f32,
38 open_width: f32,
39) -> bool {
40 let target_w = if is_target { open_width } else { 0.0 };
41 let smooth_w = ui.ctx().animate_value_with_time(slot_id, target_w, 0.12);
42 if smooth_w >= 0.5 {
43 let _ = ui.allocate_exact_size(vec2(smooth_w, height), Sense::hover());
44 }
45 let dropped = is_target && ui.ctx().input(|i| i.pointer.any_released());
46 if dropped {
47 let _ = ui.ctx().animate_value_with_time(slot_id, 0.0, 0.0);
48 }
49 dropped
50}
51
52pub fn drop_target_idx(
54 ctx: &egui::Context,
55 is_dragging: bool,
56 row_rect: Rect,
57 item_count: usize,
58) -> Option<usize> {
59 if !is_dragging {
60 return None;
61 }
62 let cursor = ctx.input(|i| i.pointer.hover_pos())?;
63 let band = row_rect.expand2(vec2(0.0, 40.0));
64 if !band.x_range().contains(cursor.x) || !band.y_range().contains(cursor.y) {
65 return None;
66 }
67 let n_slots = item_count + 1;
68 let slot_w = (row_rect.width() / n_slots as f32).max(1.0);
69 let rel = (cursor.x - row_rect.left()).max(0.0);
70 Some(((rel / slot_w) as usize).min(item_count))
71}
72
73pub fn force_opaque_active(ui: &mut Ui) {
75 let active = ui.visuals().widgets.active;
76 let v = ui.visuals_mut();
77 v.widgets.inactive.bg_fill = active.bg_fill;
78 v.widgets.inactive.weak_bg_fill = active.weak_bg_fill;
79 v.widgets.inactive.fg_stroke = active.fg_stroke;
80 v.widgets.inactive.bg_stroke = active.bg_stroke;
81 v.widgets.noninteractive.bg_fill = active.bg_fill;
82 v.widgets.noninteractive.weak_bg_fill = active.weak_bg_fill;
83}
84
85pub fn pickup_t(ctx: &egui::Context, drag_id: Id) -> f32 {
87 let target = if ctx.is_being_dragged(drag_id) {
88 1.0
89 } else {
90 0.0
91 };
92 ctx.animate_value_with_time(drag_id.with("pickup"), target, 0.12)
93}
94
95pub fn apply_drop_pre_pass<T, P>(
97 ui: &mut Ui,
98 vec: &mut Vec<T>,
99 drop_idx: Option<usize>,
100 filter: impl FnOnce(&P) -> Option<usize>,
101 gap_id_prefix: &'static str,
102 card_id_prefix: &'static str,
103 max_count: usize,
104) -> bool
105where
106 P: 'static + Send + Sync,
107{
108 if !ui.ctx().input(|i| i.pointer.any_released()) {
109 return false;
110 }
111 let Some(to) = drop_idx else {
112 return false;
113 };
114 let Some(arc) = DragAndDrop::payload::<P>(ui.ctx()) else {
115 return false;
116 };
117 let Some(from) = filter(&arc) else {
118 return false;
119 };
120 let _ = DragAndDrop::take_payload::<P>(ui.ctx());
121 if from == to || from >= vec.len() {
122 return false;
123 }
124 let item = vec.remove(from);
125 let dest = if to > from { to - 1 } else { to };
126 vec.insert(dest.min(vec.len()), item);
127 let ctx = ui.ctx();
128 for i in 0..=max_count {
129 let gap_id = ui.make_persistent_id((gap_id_prefix, i));
130 let _ = ctx.animate_value_with_time(gap_id, 0.0, 0.0);
131 let card_id = ui.make_persistent_id((card_id_prefix, i));
132 let _ = ctx.animate_value_with_time(card_id.with("pickup"), 0.0, 0.0);
133 }
134 true
135}