1use egui::{
2 Context, Id, Pos2, Rect, Ui, Vec2,
3 ahash::HashSet,
4 emath::{GuiRounding, TSTransform},
5 style::Spacing,
6};
7use smallvec::{SmallVec, ToSmallVec, smallvec};
8
9use crate::{InPinId, NodeId, OutPinId, Treeize};
10
11use super::{TreeizeWidget, transform_matching_points};
12
13pub type RowHeights = SmallVec<[f32; 8]>;
14
15#[derive(Debug)]
17pub struct NodeState {
18 size: Vec2,
21 header_height: f32,
22 input_heights: RowHeights,
23 output_heights: RowHeights,
24
25 id: Id,
26 dirty: bool,
27}
28
29#[derive(Clone, PartialEq)]
30pub(crate) struct NodeData {
31 pub(crate) size: Vec2,
32 header_height: f32,
33 input_heights: RowHeights,
34 output_heights: RowHeights,
35}
36
37impl NodeState {
38 pub fn load(cx: &Context, id: Id, spacing: &Spacing) -> Self {
39 cx.data(|d| d.get_temp::<NodeData>(id)).map_or_else(
40 || {
41 cx.request_discard("NodeState initialization");
42 Self::initial(id, spacing)
43 },
44 |data| NodeState {
45 size: data.size,
46 header_height: data.header_height,
47 input_heights: data.input_heights,
48 output_heights: data.output_heights,
49 id,
50 dirty: false,
51 },
52 )
53 }
54
55 pub fn pick_data(cx: &Context, id: Id) -> Option<NodeData> {
56 cx.data(|d| d.get_temp::<NodeData>(id))
57 }
58
59 pub fn clear(self, cx: &Context) {
60 cx.data_mut(|d| d.remove::<Self>(self.id));
61 }
62
63 pub fn store(self, cx: &Context) {
64 if self.dirty {
65 cx.data_mut(|d| {
66 d.insert_temp(
67 self.id,
68 NodeData {
69 size: self.size,
70 header_height: self.header_height,
71 input_heights: self.input_heights,
72 output_heights: self.output_heights,
73 },
74 );
75 });
76 cx.request_repaint();
77 }
78 }
79
80 pub fn node_rect(&self, pos: Pos2, openness: f32) -> Rect {
82 Rect::from_min_size(
83 pos,
84 egui::vec2(self.size.x, f32::max(self.header_height, self.size.y * openness)),
85 )
86 .round_ui()
87 }
88
89 pub fn payload_offset(&self, openness: f32) -> f32 {
90 ((self.size.y) * (1.0 - openness)).round_ui()
91 }
92
93 pub fn set_size(&mut self, size: Vec2) {
94 if self.size != size {
95 self.size = size;
96 self.dirty = true;
97 }
98 }
99
100 pub fn header_height(&self) -> f32 {
101 self.header_height.round_ui()
102 }
103
104 pub fn set_header_height(&mut self, height: f32) {
105 #[allow(clippy::float_cmp)]
106 if self.header_height != height {
107 self.header_height = height;
108 self.dirty = true;
109 }
110 }
111
112 pub const fn input_heights(&self) -> &RowHeights {
113 &self.input_heights
114 }
115
116 pub const fn output_heights(&self) -> &RowHeights {
117 &self.output_heights
118 }
119
120 const fn initial(id: Id, spacing: &Spacing) -> Self {
121 NodeState {
122 size: spacing.interact_size,
123 header_height: spacing.interact_size.y,
124 input_heights: SmallVec::new_const(),
125 output_heights: SmallVec::new_const(),
126 id,
127 dirty: true,
128 }
129 }
130}
131
132#[derive(Clone)]
133pub enum NewWires {
134 In(SmallVec<[InPinId; 4]>),
135 Out(SmallVec<[OutPinId; 4]>),
136}
137
138#[derive(Clone, Copy)]
139struct RectSelect {
140 origin: Pos2,
141 current: Pos2,
142}
143
144pub struct TreeizeState {
145 to_global: TSTransform,
147
148 new_wires: Option<NewWires>,
149
150 new_wires_menu: bool,
152
153 id: Id,
154
155 dirty: bool,
157
158 rect_selection: Option<RectSelect>,
160
161 draw_order: Vec<NodeId>,
163
164 selected_nodes: SmallVec<[NodeId; 8]>,
166}
167
168#[derive(Clone, Default)]
169struct DrawOrder(Vec<NodeId>);
170
171impl DrawOrder {
172 fn save(self, cx: &Context, id: Id) {
173 cx.data_mut(|d| {
174 if self.0.is_empty() {
175 d.remove_temp::<Self>(id);
176 } else {
177 d.insert_temp::<Self>(id, self);
178 }
179 });
180 }
181
182 fn load(cx: &Context, id: Id) -> Self {
183 cx.data(|d| d.get_temp::<Self>(id)).unwrap_or_default()
184 }
185}
186
187#[derive(Clone, Default)]
188struct SelectedNodes(SmallVec<[NodeId; 8]>);
189
190impl SelectedNodes {
191 fn save(self, cx: &Context, id: Id) {
192 cx.data_mut(|d| {
193 if self.0.is_empty() {
194 d.remove_temp::<Self>(id);
195 } else {
196 d.get_temp_mut_or_default::<Self>(id).clone_from(&self);
197 d.insert_temp::<Self>(id, self);
198 }
199 });
200 }
201
202 fn load(cx: &Context, id: Id) -> Self {
203 cx.data(|d| d.get_temp::<Self>(id)).unwrap_or_default()
204 }
205}
206
207#[derive(Clone)]
208struct TreeizeStateData {
209 to_global: TSTransform,
210 new_wires: Option<NewWires>,
211 new_wires_menu: bool,
212 rect_selection: Option<RectSelect>,
213}
214
215impl TreeizeStateData {
216 fn save(self, cx: &Context, id: Id) {
217 cx.data_mut(|d| {
218 d.insert_temp(id, self);
219 });
220 }
221
222 fn load(cx: &Context, id: Id) -> Option<Self> {
223 cx.data(|d| d.get_temp(id))
224 }
225}
226
227fn prune_selected_nodes<T>(
228 selected_nodes: &mut SmallVec<[NodeId; 8]>,
229 treeize: &Treeize<T>,
230) -> bool {
231 let old_size = selected_nodes.len();
232 selected_nodes.retain(|node| treeize.nodes.contains(node.0));
233 old_size != selected_nodes.len()
234}
235
236impl TreeizeState {
237 pub fn load<T>(
238 cx: &Context,
239 id: Id,
240 treeize: &Treeize<T>,
241 ui_rect: Rect,
242 min_scale: f32,
243 max_scale: f32,
244 ) -> Self {
245 let Some(data) = TreeizeStateData::load(cx, id) else {
246 cx.request_discard("Initial placing");
247 return Self::initial(id, treeize, ui_rect, min_scale, max_scale);
248 };
249
250 let mut selected_nodes = SelectedNodes::load(cx, id).0;
251 let dirty = prune_selected_nodes(&mut selected_nodes, treeize);
252
253 let draw_order = DrawOrder::load(cx, id).0;
254
255 TreeizeState {
256 to_global: data.to_global,
257 new_wires: data.new_wires,
258 new_wires_menu: data.new_wires_menu,
259 id,
260 dirty,
261 rect_selection: data.rect_selection,
262 draw_order,
263 selected_nodes,
264 }
265 }
266
267 fn initial<T>(
268 id: Id,
269 treeize: &Treeize<T>,
270 ui_rect: Rect,
271 min_scale: f32,
272 max_scale: f32,
273 ) -> Self {
274 let mut bb = Rect::NOTHING;
275
276 for (_, node) in &treeize.nodes {
277 bb.extend_with(node.pos);
278 }
279
280 if bb.is_finite() {
281 bb = bb.expand(100.0);
282 } else if ui_rect.is_finite() {
283 bb = ui_rect;
284 } else {
285 bb = Rect::from_min_max(Pos2::new(-100.0, -100.0), Pos2::new(100.0, 100.0));
286 }
287
288 let scaling2 = ui_rect.size() / bb.size();
289 let scaling = scaling2.min_elem().clamp(min_scale, max_scale);
290
291 let to_global = transform_matching_points(bb.center(), ui_rect.center(), scaling);
292
293 TreeizeState {
294 to_global,
295 new_wires: None,
296 new_wires_menu: false,
297 id,
298 dirty: true,
299 draw_order: Vec::new(),
300 rect_selection: None,
301 selected_nodes: SmallVec::new(),
302 }
303 }
304
305 #[inline(always)]
306 pub fn store<T>(mut self, treeize: &Treeize<T>, cx: &Context) {
307 self.dirty |= prune_selected_nodes(&mut self.selected_nodes, treeize);
308
309 if self.dirty {
310 let data = TreeizeStateData {
311 to_global: self.to_global,
312 new_wires: self.new_wires,
313 new_wires_menu: self.new_wires_menu,
314 rect_selection: self.rect_selection,
315 };
316 data.save(cx, self.id);
317
318 DrawOrder(self.draw_order).save(cx, self.id);
319 SelectedNodes(self.selected_nodes).save(cx, self.id);
320
321 cx.request_repaint();
322 }
323 }
324
325 pub const fn to_global(&self) -> TSTransform {
326 self.to_global
327 }
328
329 pub fn set_to_global(&mut self, to_global: TSTransform) {
330 if self.to_global != to_global {
331 self.to_global = to_global;
332 self.dirty = true;
333 }
334 }
335
336 pub fn look_at(&mut self, view: Rect, ui_rect: Rect, min_scale: f32, max_scale: f32) {
337 let scaling2 = ui_rect.size() / view.size();
338 let scaling = scaling2.min_elem().clamp(min_scale, max_scale);
339
340 let to_global = transform_matching_points(view.center(), ui_rect.center(), scaling);
341
342 if self.to_global != to_global {
343 self.to_global = to_global;
344 self.dirty = true;
345 }
346 }
347
348 pub fn start_new_wire_in(&mut self, pin: InPinId) {
349 self.new_wires = Some(NewWires::In(smallvec![pin]));
350 self.new_wires_menu = false;
351 self.dirty = true;
352 }
353
354 pub fn start_new_wire_out(&mut self, pin: OutPinId) {
355 self.new_wires = Some(NewWires::Out(smallvec![pin]));
356 self.new_wires_menu = false;
357 self.dirty = true;
358 }
359
360 pub fn start_new_wires_in(&mut self, pins: &[InPinId]) {
361 self.new_wires = Some(NewWires::In(pins.to_smallvec()));
362 self.new_wires_menu = false;
363 self.dirty = true;
364 }
365
366 pub fn start_new_wires_out(&mut self, pins: &[OutPinId]) {
367 self.new_wires = Some(NewWires::Out(pins.to_smallvec()));
368 self.new_wires_menu = false;
369 self.dirty = true;
370 }
371
372 pub fn add_new_wire_in(&mut self, pin: InPinId) {
373 debug_assert!(!self.new_wires_menu);
374 let Some(NewWires::In(pins)) = &mut self.new_wires else {
375 unreachable!();
376 };
377
378 if !pins.contains(&pin) {
379 pins.push(pin);
380 self.dirty = true;
381 }
382 }
383
384 pub fn add_new_wire_out(&mut self, pin: OutPinId) {
385 debug_assert!(!self.new_wires_menu);
386 let Some(NewWires::Out(pins)) = &mut self.new_wires else {
387 unreachable!();
388 };
389
390 if !pins.contains(&pin) {
391 pins.push(pin);
392 self.dirty = true;
393 }
394 }
395
396 pub fn remove_new_wire_in(&mut self, pin: InPinId) {
397 debug_assert!(!self.new_wires_menu);
398 let Some(NewWires::In(pins)) = &mut self.new_wires else {
399 unreachable!();
400 };
401
402 if let Some(idx) = pins.iter().position(|p| *p == pin) {
403 pins.swap_remove(idx);
404 self.dirty = true;
405 }
406 }
407
408 pub fn remove_new_wire_out(&mut self, pin: OutPinId) {
409 debug_assert!(!self.new_wires_menu);
410 let Some(NewWires::Out(pins)) = &mut self.new_wires else {
411 unreachable!();
412 };
413
414 if let Some(idx) = pins.iter().position(|p| *p == pin) {
415 pins.swap_remove(idx);
416 self.dirty = true;
417 }
418 }
419
420 pub const fn has_new_wires(&self) -> bool {
421 matches!((self.new_wires.as_ref(), self.new_wires_menu), (Some(_), false))
422 }
423
424 pub const fn has_new_wires_in(&self) -> bool {
425 matches!((&self.new_wires, self.new_wires_menu), (Some(NewWires::In(_)), false))
426 }
427
428 pub const fn has_new_wires_out(&self) -> bool {
429 matches!((&self.new_wires, self.new_wires_menu), (Some(NewWires::Out(_)), false))
430 }
431
432 pub const fn new_wires(&self) -> Option<&NewWires> {
433 match (&self.new_wires, self.new_wires_menu) {
434 (Some(new_wires), false) => Some(new_wires),
435 _ => None,
436 }
437 }
438
439 pub const fn take_new_wires(&mut self) -> Option<NewWires> {
440 match (&self.new_wires, self.new_wires_menu) {
441 (Some(_), false) => {
442 self.dirty = true;
443 self.new_wires.take()
444 }
445 _ => None,
446 }
447 }
448
449 pub(crate) const fn take_new_wires_menu(&mut self) -> Option<NewWires> {
450 match (&self.new_wires, self.new_wires_menu) {
451 (Some(_), true) => {
452 self.dirty = true;
453 self.new_wires.take()
454 }
455 _ => None,
456 }
457 }
458
459 pub(crate) fn set_new_wires_menu(&mut self, wires: NewWires) {
460 debug_assert!(self.new_wires.is_none());
461 self.new_wires = Some(wires);
462 self.new_wires_menu = true;
463 }
464
465 pub(crate) fn update_draw_order<T>(&mut self, treeize: &Treeize<T>) -> Vec<NodeId> {
466 let mut node_ids = treeize.nodes.iter().map(|(id, _)| NodeId(id)).collect::<HashSet<_>>();
467
468 self.draw_order.retain(|id| {
469 let has = node_ids.remove(id);
470 self.dirty |= !has;
471 has
472 });
473
474 self.dirty |= !node_ids.is_empty();
475
476 for new_id in node_ids {
477 self.draw_order.push(new_id);
478 }
479
480 self.draw_order.clone()
481 }
482
483 pub(crate) fn node_to_top(&mut self, node: NodeId) {
484 if let Some(order) = self.draw_order.iter().position(|idx| *idx == node) {
485 self.draw_order.remove(order);
486 self.draw_order.push(node);
487 }
488 self.dirty = true;
489 }
490
491 pub fn selected_nodes(&self) -> &[NodeId] {
492 &self.selected_nodes
493 }
494
495 pub fn select_one_node(&mut self, reset: bool, node: NodeId) {
496 if reset {
497 if self.selected_nodes[..] == [node] {
498 return;
499 }
500
501 self.deselect_all_nodes();
502 } else if let Some(pos) = self.selected_nodes.iter().position(|n| *n == node) {
503 if pos == self.selected_nodes.len() - 1 {
504 return;
505 }
506 self.selected_nodes.remove(pos);
507 }
508 self.selected_nodes.push(node);
509 self.dirty = true;
510 }
511
512 pub fn select_many_nodes(&mut self, reset: bool, nodes: impl Iterator<Item = NodeId>) {
513 if reset {
514 self.deselect_all_nodes();
515 self.selected_nodes.extend(nodes);
516 self.dirty = true;
517 } else {
518 nodes.for_each(|node| self.select_one_node(false, node));
519 }
520 }
521
522 pub fn deselect_one_node(&mut self, node: NodeId) {
523 if let Some(pos) = self.selected_nodes.iter().position(|n| *n == node) {
524 self.selected_nodes.remove(pos);
525 self.dirty = true;
526 }
527 }
528
529 pub fn deselect_many_nodes(&mut self, nodes: impl Iterator<Item = NodeId>) {
530 for node in nodes {
531 if let Some(pos) = self.selected_nodes.iter().position(|n| *n == node) {
532 self.selected_nodes.remove(pos);
533 self.dirty = true;
534 }
535 }
536 }
537
538 pub fn deselect_all_nodes(&mut self) {
539 self.dirty |= !self.selected_nodes.is_empty();
540 self.selected_nodes.clear();
541 }
542
543 pub const fn start_rect_selection(&mut self, pos: Pos2) {
544 self.dirty |= self.rect_selection.is_none();
545 self.rect_selection = Some(RectSelect { origin: pos, current: pos });
546 }
547
548 pub const fn stop_rect_selection(&mut self) {
549 self.dirty |= self.rect_selection.is_some();
550 self.rect_selection = None;
551 }
552
553 pub const fn is_rect_selection(&self) -> bool {
554 self.rect_selection.is_some()
555 }
556
557 pub const fn update_rect_selection(&mut self, pos: Pos2) {
558 if let Some(rect_selection) = &mut self.rect_selection {
559 rect_selection.current = pos;
560 self.dirty = true;
561 }
562 }
563
564 pub fn rect_selection(&self) -> Option<Rect> {
565 let rect = self.rect_selection?;
566 Some(Rect::from_two_pos(rect.origin, rect.current))
567 }
568}
569
570impl TreeizeWidget {
571 #[must_use]
575 #[inline]
576 pub fn get_selected_nodes(self, ui: &Ui) -> Vec<NodeId> {
577 self.get_selected_nodes_at(ui.id(), ui.ctx())
578 }
579
580 #[must_use]
584 #[inline]
585 pub fn get_selected_nodes_at(self, ui_id: Id, ctx: &Context) -> Vec<NodeId> {
586 let treeize_id = self.get_id(ui_id);
587
588 ctx.data(|d| d.get_temp::<SelectedNodes>(treeize_id).unwrap_or_default().0).into_vec()
589 }
590}
591
592#[must_use]
597#[inline]
598pub fn get_selected_nodes(id: Id, ctx: &Context) -> Vec<NodeId> {
599 ctx.data(|d| d.get_temp::<SelectedNodes>(id).unwrap_or_default().0).into_vec()
600}