appcui/ui/graphview/graphview.rs
1use std::any::TypeId;
2
3use super::events::*;
4use super::graph::Graph;
5use super::initialization_flags::*;
6use super::RenderingOptions;
7use crate::{prelude::*, ui::graphview::GraphNode};
8
9struct NodeInfo {
10 id: usize,
11 top_left: Point,
12 origin: Point,
13}
14enum Drag {
15 None,
16 View(Point),
17 Node(NodeInfo),
18}
19
20#[CustomControl(overwrite=OnPaint+OnKeyPressed+OnMouseEvent+OnResize+OnFocus, internal=true)]
21pub struct GraphView<T>
22where
23 T: GraphNode + 'static,
24{
25 graph: Graph<T>,
26 origin_point: Point,
27 background: Option<Character>,
28 flags: Flags,
29 drag: Drag,
30 comp: ListScrollBars,
31 arrange_method: ArrangeMethod,
32 rendering_options: RenderingOptions,
33}
34impl<T> GraphView<T>
35where
36 T: GraphNode,
37{
38 /// Creates a new GraphView control with the specified layout and flags.
39 ///
40 /// # Parameters
41 /// - `layout`: The layout configuration for positioning and sizing the GraphView
42 /// - `flags`: Combination of initialization flags that control the GraphView behavior:
43 /// - `Flags::ScrollBars`: Enables scroll bars for navigating large graphs
44 /// - `Flags::SearchBar`: Enables a search bar for finding nodes
45 ///
46 /// # Example
47 /// ```rust, no_run
48 /// use appcui::prelude::*;
49 ///
50 /// type MyNode = &'static str; // or any other type that implements the GraphNode trait
51 ///
52 /// let mut graph_view: GraphView<MyNode> = GraphView::new(
53 /// layout!("x:1,y:1,w:50,h:30"),
54 /// graphview::Flags::ScrollBars | graphview::Flags::SearchBar
55 /// );
56 /// ```
57 pub fn new(layout: Layout, flags: Flags) -> Self {
58 Self {
59 base: ControlBase::with_status_flags(
60 layout,
61 (StatusFlags::Visible | StatusFlags::Enabled | StatusFlags::AcceptInput)
62 | if flags.contains_one(Flags::ScrollBars | Flags::SearchBar) {
63 StatusFlags::IncreaseBottomMarginOnFocus | StatusFlags::IncreaseRightMarginOnFocus
64 } else {
65 StatusFlags::None
66 },
67 ),
68 flags,
69 origin_point: Point::ORIGIN,
70 background: None,
71 drag: Drag::None,
72 graph: Graph::default(),
73 arrange_method: ArrangeMethod::GridPacked,
74 rendering_options: RenderingOptions::new(),
75 comp: ListScrollBars::new(flags.contains(Flags::ScrollBars), flags.contains(Flags::SearchBar)),
76 }
77 }
78
79 /// Sets the background of the GraphView to the specified character.
80 ///
81 /// This method allows you to customize the background appearance of the GraphView.
82 /// The background character will be used to fill the entire control area before
83 /// drawing the graph nodes and edges.
84 ///
85 /// # Parameters
86 /// - `backgroud_char`: The character to use as background, including its color and formatting
87 ///
88 /// # Example
89 /// ```rust, no_run
90 /// use appcui::prelude::*;
91 ///
92 /// type MyNode = &'static str; // or any other type that implements the GraphNode trait
93 ///
94 /// let mut graph_view: GraphView<MyNode> = GraphView::new(
95 /// layout!("x:1,y:1,w:30,h:10"),
96 /// graphview::Flags::ScrollBars
97 /// );
98 /// graph_view.set_background(Character::new('ยท', Color::Gray, Color::Black, CharFlags::None));
99 /// ```
100 pub fn set_background(&mut self, backgroud_char: Character) {
101 self.background = Some(backgroud_char);
102 }
103
104 /// Clears the background character of the GraphView.
105 ///
106 /// This method removes any previously set background character and resets the GraphView
107 /// to use transparent foreground and background colors, allowing the default theme
108 /// background to show through.
109 ///
110 /// # Example
111 /// ```rust, no_run
112 /// use appcui::prelude::*;
113 ///
114 /// type MyNode = &'static str; // or any other type that implements the GraphNode trait
115 ///
116 /// let mut graph_view: GraphView<MyNode> = GraphView::new(
117 /// layout!("x:1,y:1,w:30,h:10"),
118 /// graphview::Flags::None
119 /// );
120 /// graph_view.set_background(Character::new('*', Color::White, Color::Black, CharFlags::None));
121 /// graph_view.clear_background(); // Removes the background character
122 /// ```
123 pub fn clear_background(&mut self) {
124 self.background = None;
125 }
126
127 /// Sets the graph data to be displayed in the GraphView.
128 ///
129 /// This method replaces the current graph with a new one, updates the rendering options,
130 /// and automatically arranges the nodes using the current arrangement method.
131 ///
132 /// # Parameters
133 /// - `graph`: The graph containing nodes and edges to be displayed
134 ///
135 /// # Example
136 /// ```rust, no_run
137 /// use appcui::prelude::*;
138 ///
139 /// type MyNode = &'static str; // or any other type that implements the GraphNode trait
140 ///
141 /// let mut graph_view: GraphView<MyNode> = GraphView::new(
142 /// layout!("x:1,y:1,w:50,h:30"),
143 /// graphview::Flags::ScrollBars
144 /// );
145 ///
146 /// let nodes = &["A", "B", "C", "D", "E"];
147 /// let edges = &[(0, 1), (0, 2), (1, 3), (2, 4)];
148 /// let graph = graphview::Graph::with_slices(nodes, edges, true);
149 /// graph_view.set_graph(graph);
150 /// ```
151 pub fn set_graph(&mut self, graph: Graph<T>) {
152 self.graph = graph;
153 self.graph.update_rendering_options(&self.rendering_options, &self.base);
154 self.arrange_nodes(self.arrange_method);
155 }
156
157 /// Sets the edge routing method for drawing connections between nodes.
158 ///
159 /// This method determines how edges are drawn between nodes in the graph.
160 ///
161 /// # Parameters
162 /// - `routing`: The routing method to use:
163 /// - `EdgeRouting::Direct`: Draw edges as straight lines between nodes
164 /// - `EdgeRouting::Orthogonal`: Draw edges as orthogonal (right-angled) lines
165 ///
166 /// # Example
167 /// ```rust, no_run
168 /// use appcui::prelude::*;
169 ///
170 /// type MyNode = &'static str; // or any other type that implements the GraphNode trait
171 ///
172 /// let mut graph_view: GraphView<MyNode> = GraphView::new(
173 /// layout!("x:1,y:1,w:50,h:30"),
174 /// graphview::Flags::ScrollBars
175 /// );
176 /// graph_view.set_edge_routing(graphview::EdgeRouting::Orthogonal);
177 /// ```
178 pub fn set_edge_routing(&mut self, routing: EdgeRouting) {
179 self.rendering_options.edge_routing = routing;
180 self.graph.update_rendering_options(&self.rendering_options, &self.base);
181 }
182
183 /// Sets the line type used for drawing edges between nodes.
184 ///
185 /// This method controls the visual style of the lines used to draw edges.
186 /// Different line types provide different visual appearances.
187 ///
188 /// # Parameters
189 /// - `line_type`: The line style to use:
190 /// - `LineType::Single`: Single lines with Unicode box-drawing characters
191 /// - `LineType::Double`: Double lines with Unicode box-drawing characters
192 /// - `LineType::SingleThick`: Thick single lines
193 /// - `LineType::Border`: Border-style thick lines
194 /// - `LineType::Ascii`: ASCII characters ('+', '-', '|')
195 /// - `LineType::AsciiRound`: ASCII with rounded corners
196 /// - `LineType::SingleRound`: Unicode single lines with rounded corners
197 /// - `LineType::Braille`: Double lines using Braille characters
198 ///
199 /// # Example
200 /// ```rust, no_run
201 /// use appcui::prelude::*;
202 ///
203 /// type MyNode = &'static str; // or any other type that implements the GraphNode trait
204 ///
205 /// let mut graph_view: GraphView<MyNode> = GraphView::new(
206 /// layout!("x:1,y:1,w:50,h:30"),
207 /// graphview::Flags::ScrollBars
208 /// );
209 /// graph_view.set_edge_line_type(LineType::Double);
210 /// ```
211 pub fn set_edge_line_type(&mut self, line_type: LineType) {
212 self.rendering_options.edge_line_type = line_type;
213 self.graph.update_rendering_options(&self.rendering_options, &self.base);
214 }
215
216 /// Enables or disables edge highlighting for the currently selected node.
217 ///
218 /// When edge highlighting is enabled, edges connected to the currently selected
219 /// node will be visually highlighted to make the connections more apparent.
220 ///
221 /// # Parameters
222 /// - `incoming`: Whether to highlight edges pointing to the current node
223 /// - `outgoing`: Whether to highlight edges originating from the current node
224 ///
225 /// # Example
226 /// ```rust, no_run
227 /// use appcui::prelude::*;
228 ///
229 /// type MyNode = &'static str; // or any other type that implements the GraphNode trait
230 ///
231 /// let mut graph_view: GraphView<MyNode> = GraphView::new(
232 /// layout!("x:1,y:1,w:50,h:30"),
233 /// graphview::Flags::ScrollBars
234 /// );
235 /// // Highlight both incoming and outgoing edges
236 /// graph_view.enable_edge_highlighting(true, true);
237 ///
238 /// // Only highlight outgoing edges
239 /// graph_view.enable_edge_highlighting(false, true);
240 /// ```
241 pub fn enable_edge_highlighting(&mut self, incoming: bool, outgoing: bool) {
242 self.rendering_options.highlight_edges_in = incoming;
243 self.rendering_options.highlight_edges_out = outgoing;
244 self.graph.update_rendering_options(&self.rendering_options, &self.base);
245 }
246
247 /// Enables or disables arrow heads on directed edges.
248 ///
249 /// When enabled, directed edges will display arrow heads to indicate
250 /// the direction of the connection between nodes.
251 ///
252 /// # Parameters
253 /// - `enabled`: Whether to show arrow heads on directed edges
254 ///
255 /// # Example
256 /// ```rust, no_run
257 /// use appcui::prelude::*;
258 ///
259 /// type MyNode = &'static str; // or any other type that implements the GraphNode trait
260 ///
261 /// let mut graph_view: GraphView<MyNode> = GraphView::new(
262 /// layout!("x:1,y:1,w:50,h:30"),
263 /// graphview::Flags::ScrollBars
264 /// );
265 /// graph_view.enable_arrow_heads(true);
266 /// ```
267 pub fn enable_arrow_heads(&mut self, enabled: bool) {
268 self.rendering_options.show_arrow_heads = enabled;
269 self.graph.update_rendering_options(&self.rendering_options, &self.base);
270 }
271
272 /// Arranges the nodes in the graph using the specified layout algorithm.
273 ///
274 /// This method automatically positions all nodes in the graph according to
275 /// the chosen arrangement algorithm. The graph will be resized and repainted
276 /// after the arrangement is complete.
277 ///
278 /// # Parameters
279 /// - `method`: The arrangement algorithm to use:
280 /// - `ArrangeMethod::None`: No automatic arrangement (manual positioning)
281 /// - `ArrangeMethod::Grid`: Arrange nodes in a regular grid with spacing
282 /// - `ArrangeMethod::GridPacked`: Arrange nodes in a compact grid
283 /// - `ArrangeMethod::Circular`: Arrange nodes in a circular pattern
284 /// - `ArrangeMethod::Hierarchical`: Arrange nodes in hierarchical layers with spacing
285 /// - `ArrangeMethod::HierarchicalPacked`: Arrange nodes in compact hierarchical layers
286 /// - `ArrangeMethod::ForceDirected`: Use force-directed algorithm for natural positioning
287 ///
288 /// # Example
289 /// ```rust, no_run
290 /// use appcui::prelude::*;
291 ///
292 /// type MyNode = &'static str; // or any other type that implements the GraphNode trait
293 ///
294 /// let mut graph_view: GraphView<MyNode> = GraphView::new(
295 /// layout!("x:1,y:1,w:50,h:30"),
296 /// graphview::Flags::ScrollBars
297 /// );
298 ///
299 /// // Arrange nodes in a hierarchical layout
300 /// graph_view.arrange_nodes(graphview::ArrangeMethod::Hierarchical);
301 ///
302 /// // Use force-directed layout for organic appearance
303 /// graph_view.arrange_nodes(graphview::ArrangeMethod::ForceDirected);
304 /// ```
305 pub fn arrange_nodes(&mut self, method: ArrangeMethod) {
306 match method {
307 ArrangeMethod::None => { /* do nothing */ }
308 ArrangeMethod::Grid => super::node_layout::grid::rearange(&mut self.graph, 2),
309 ArrangeMethod::GridPacked => super::node_layout::grid::rearange(&mut self.graph, 1),
310 ArrangeMethod::Circular => super::node_layout::circular::rearange(&mut self.graph),
311 ArrangeMethod::Hierarchical => super::node_layout::hierarchical::rearange(&mut self.graph, 2),
312 ArrangeMethod::HierarchicalPacked => super::node_layout::hierarchical::rearange(&mut self.graph, 1),
313 ArrangeMethod::ForceDirected => super::node_layout::force_directed::rearange(&mut self.graph),
314 }
315 self.arrange_method = method;
316 self.graph.resize_graph(true);
317 self.graph.repaint(&self.base);
318 }
319
320 /// Returns an immutable reference to the underlying graph data.
321 ///
322 /// This method provides read-only access to the graph structure,
323 /// allowing you to query nodes, edges, and other graph properties.
324 ///
325 /// # Returns
326 /// A reference to the `Graph<T>` containing all nodes and edges
327 ///
328 /// # Example
329 /// ```rust, no_run
330 /// use appcui::prelude::*;
331 ///
332 /// type MyNode = &'static str; // or any other type that implements the GraphNode trait
333 ///
334 /// let graph_view: GraphView<MyNode> = GraphView::new(
335 /// layout!("x:1,y:1,w:50,h:30"),
336 /// graphview::Flags::ScrollBars
337 /// );
338 ///
339 /// let graph = graph_view.graph();
340 /// if let Some(node) = graph.current_node() {
341 /// // do something with the current node
342 /// }
343 /// ```
344 pub fn graph(&self) -> &Graph<T> {
345 &self.graph
346 }
347
348 fn move_scroll_to(&mut self, x: i32, y: i32) {
349 let sz = self.size();
350 let surface_size = self.graph.size();
351 self.origin_point.x = if surface_size.width <= sz.width {
352 0
353 } else {
354 x.max((sz.width as i32) - (surface_size.width as i32))
355 };
356 self.origin_point.y = if surface_size.height <= sz.height {
357 0
358 } else {
359 y.max((sz.height as i32) - (surface_size.height as i32))
360 };
361 self.origin_point.x = self.origin_point.x.min(0);
362 self.origin_point.y = self.origin_point.y.min(0);
363 self.comp.set_indexes((-self.origin_point.x) as u64, (-self.origin_point.y) as u64);
364 }
365 fn update_scroll_pos_from_scrollbars(&mut self) {
366 let h = -(self.comp.horizontal_index() as i32);
367 let v = -(self.comp.vertical_index() as i32);
368 self.move_scroll_to(h, v);
369 }
370 fn update_scroll_bars(&mut self) {
371 let paint_sz = self.graph.size();
372 let sz = self.size();
373 self.comp.resize(paint_sz.width as u64, paint_sz.height as u64, &self.base, sz);
374 self.move_scroll_to(self.origin_point.x, self.origin_point.y);
375 }
376 fn ensure_node_is_visible(&mut self, node_id: usize) {
377 if let Some(node) = self.graph.nodes.get(node_id) {
378 let node_rect = node.rect;
379 let sz = self.size();
380 let view_rect = Rect::with_point_and_size(Point::new(-self.origin_point.x, -self.origin_point.y), sz);
381
382 if !view_rect.contains_rect(node_rect) {
383 let mut adx = 0;
384 let mut ady = 0;
385
386 if node_rect.right() > view_rect.right() {
387 adx = node_rect.right() - view_rect.right();
388 }
389 if node_rect.left() < view_rect.left() {
390 adx = node_rect.left() - view_rect.left();
391 }
392 if node_rect.bottom() > view_rect.bottom() {
393 ady = node_rect.bottom() - view_rect.bottom();
394 }
395 if node_rect.top() < view_rect.top() {
396 ady = node_rect.top() - view_rect.top();
397 }
398 self.move_scroll_to(self.origin_point.x - adx, self.origin_point.y - ady);
399 }
400 }
401 }
402 fn ensure_current_node_is_visible(&mut self) {
403 if let Some(id) = self.graph.current_node_id() {
404 self.ensure_node_is_visible(id);
405 }
406 }
407 fn search_text(&mut self) {
408 let txt = self.comp.search_text();
409 let count = self.graph.filter(txt, &self.base);
410 self.ensure_current_node_is_visible();
411 if count == self.graph.nodes.len() {
412 self.comp.clear_match_count();
413 } else {
414 self.comp.set_match_count(count);
415 }
416 }
417 fn goto_next_match(&mut self) {
418 self.graph.goto_next_match(&self.base);
419 self.ensure_current_node_is_visible();
420 }
421 fn goto_previous_match(&mut self) {
422 self.graph.goto_previous_match(&self.base);
423 self.ensure_current_node_is_visible();
424 }
425 fn raise_current_node_changed(&mut self, old_id: Option<usize>) {
426 let new_id = self.graph.current_node_id();
427 if (old_id != new_id) && new_id.is_some() {
428 self.raise_event(ControlEvent {
429 emitter: self.handle,
430 receiver: self.event_processor,
431 data: ControlEventData::GraphView(EventData {
432 event_type: GraphViewEventTypes::CurrentNodeChanged,
433 type_id: TypeId::of::<T>(),
434 }),
435 });
436 }
437 }
438 fn raise_action_on_node(&mut self, id: usize) {
439 self.raise_event(ControlEvent {
440 emitter: self.handle,
441 receiver: self.event_processor,
442 data: ControlEventData::GraphView(EventData {
443 event_type: GraphViewEventTypes::NodeAction(id),
444 type_id: TypeId::of::<T>(),
445 }),
446 });
447 }
448}
449impl<T> OnResize for GraphView<T>
450where
451 T: GraphNode,
452{
453 fn on_resize(&mut self, _: Size, _: Size) {
454 self.update_scroll_bars();
455 }
456}
457impl<T> OnPaint for GraphView<T>
458where
459 T: GraphNode,
460{
461 fn on_paint(&self, surface: &mut Surface, theme: &Theme) {
462 if (self.has_focus()) && (self.flags.contains_one(Flags::ScrollBars | Flags::SearchBar)) {
463 self.comp.paint(surface, theme, self);
464 surface.reduce_clip_by(0, 0, 1, 1);
465 }
466 if let Some(back) = self.background {
467 surface.clear(back);
468 }
469 surface.draw_surface(self.origin_point.x, self.origin_point.y, self.graph.surface());
470 // let sz = format!("Size {:?}", self.size());
471 // surface.write_string(0, 0, &sz, charattr!("w,black"), false);
472 }
473}
474impl<T> OnKeyPressed for GraphView<T>
475where
476 T: GraphNode,
477{
478 fn on_key_pressed(&mut self, key: Key, character: char) -> EventProcessStatus {
479 let nid = self.graph.current_node_id();
480 if self.comp.process_key_pressed(key, character) {
481 self.search_text();
482 self.raise_current_node_changed(nid);
483 return EventProcessStatus::Processed;
484 }
485 if self.graph.process_key_events(key, &self.base) {
486 self.ensure_current_node_is_visible();
487 self.comp.exit_edit_mode();
488 self.raise_current_node_changed(nid);
489 return EventProcessStatus::Processed;
490 }
491 let result = match key.value() {
492 key!("Alt+Left") => {
493 self.move_scroll_to(self.origin_point.x + 1, self.origin_point.y);
494 EventProcessStatus::Processed
495 }
496 key!("Alt+Right") => {
497 self.move_scroll_to(self.origin_point.x - 1, self.origin_point.y);
498 EventProcessStatus::Processed
499 }
500 key!("Alt+Up") => {
501 self.move_scroll_to(self.origin_point.x, self.origin_point.y + 1);
502 EventProcessStatus::Processed
503 }
504 key!("Alt+Down") => {
505 self.move_scroll_to(self.origin_point.x, self.origin_point.y - 1);
506 EventProcessStatus::Processed
507 }
508 key!("PageUp") => {
509 self.move_scroll_to(self.origin_point.x, self.origin_point.y + self.size().height as i32);
510 EventProcessStatus::Processed
511 }
512 key!("PageDown") => {
513 self.move_scroll_to(self.origin_point.x, self.origin_point.y - self.size().height as i32);
514 EventProcessStatus::Processed
515 }
516 key!("Home") => {
517 self.move_scroll_to(0, 0);
518 EventProcessStatus::Processed
519 }
520 key!("End") => {
521 self.move_scroll_to(i32::MIN, i32::MIN);
522 EventProcessStatus::Processed
523 }
524 key!("Enter") => {
525 if self.comp.is_in_edit_mode() {
526 self.goto_next_match();
527 self.raise_current_node_changed(nid);
528 // exist directly so that we don't exit the edit mode
529 return EventProcessStatus::Processed;
530 } else {
531 if let Some(id) = self.graph.current_node_id() {
532 self.raise_action_on_node(id);
533 return EventProcessStatus::Processed;
534 }
535 EventProcessStatus::Ignored
536 }
537 }
538 key!("Ctrl+Enter") => {
539 if self.comp.is_in_edit_mode() {
540 self.goto_previous_match();
541 self.raise_current_node_changed(nid);
542 // exist directly so that we don't exit the edit mode
543 return EventProcessStatus::Processed;
544 } else {
545 EventProcessStatus::Ignored
546 }
547 }
548 _ => EventProcessStatus::Ignored,
549 };
550 if result == EventProcessStatus::Processed {
551 self.comp.exit_edit_mode();
552 }
553 if self.comp.should_repaint() {
554 EventProcessStatus::Processed
555 } else {
556 result
557 }
558 }
559}
560
561impl<T> OnFocus for GraphView<T>
562where
563 T: GraphNode,
564{
565 fn on_focus(&mut self) {
566 self.graph.repaint(&self.base);
567 }
568
569 fn on_lose_focus(&mut self) {
570 self.graph.repaint(&self.base);
571 }
572}
573
574impl<T> OnMouseEvent for GraphView<T>
575where
576 T: GraphNode,
577{
578 fn on_mouse_event(&mut self, event: &MouseEvent) -> EventProcessStatus {
579 if self.comp.process_mouse_event(event) {
580 self.update_scroll_pos_from_scrollbars();
581 return EventProcessStatus::Processed;
582 }
583 match event {
584 MouseEvent::Enter | MouseEvent::Leave => {
585 self.graph.reset_hover(&self.base);
586 self.hide_tooltip();
587 EventProcessStatus::Processed
588 }
589 MouseEvent::Over(point) => {
590 let p = Point::new(point.x - self.origin_point.x, point.y - self.origin_point.y);
591 if !self.graph.process_mouse_over(&self.base, p) {
592 return EventProcessStatus::Ignored;
593 }
594 if let Some(id) = self.graph.hovered_node_id() {
595 let r = self.graph.nodes[id].rect + (self.origin_point.x, self.origin_point.y);
596 if let Some(desc) = self.graph.node_description(id) {
597 self.base.show_tooltip_on_rect(desc, &r);
598 } else {
599 self.hide_tooltip();
600 }
601 } else {
602 self.hide_tooltip();
603 }
604 EventProcessStatus::Processed
605 }
606 MouseEvent::Pressed(mouse_data) => {
607 let data = Point::new(mouse_data.x - self.origin_point.x, mouse_data.y - self.origin_point.y);
608 if let Some(id) = self.graph.mouse_pos_to_index(data.x, data.y) {
609 // click on a node
610 let nid = self.graph.current_node_id();
611 self.graph.set_current_node(id, &self.base);
612 let tl = self.graph.nodes[id].rect.top_left();
613 self.drag = Drag::Node(NodeInfo {
614 id,
615 top_left: tl,
616 origin: Point::new(data.x, data.y),
617 });
618 self.raise_current_node_changed(nid);
619 return EventProcessStatus::Processed;
620 }
621 if self.flags.contains_one(Flags::ScrollBars) && (self.has_focus()) {
622 let sz = self.size();
623 if (data.x == sz.width as i32) || (data.y == sz.height as i32) {
624 return EventProcessStatus::Ignored;
625 }
626 }
627 self.drag = Drag::View(Point::new(data.x, data.y));
628 EventProcessStatus::Processed
629 }
630 MouseEvent::Released(mouse_data) => match &self.drag {
631 Drag::None => {
632 EventProcessStatus::Ignored
633 }
634 Drag::View(p) => {
635 self.move_scroll_to(self.origin_point.x + mouse_data.x - p.x, self.origin_point.y + mouse_data.y - p.y);
636 self.drag = Drag::None;
637 EventProcessStatus::Processed
638 }
639 Drag::Node(node_info) => {
640 let data = Point::new(mouse_data.x - self.origin_point.x, mouse_data.y - self.origin_point.y);
641 if self.graph.move_node_to(
642 node_info.id,
643 node_info.top_left.x + data.x - node_info.origin.x,
644 node_info.top_left.y + data.y - node_info.origin.y,
645 &self.base,
646 ) {
647 self.update_scroll_bars();
648 }
649 self.drag = Drag::None;
650 self.ensure_current_node_is_visible();
651 EventProcessStatus::Processed
652 }
653 },
654 MouseEvent::DoubleClick(mouse_data) => {
655 let data = Point::new(mouse_data.x - self.origin_point.x, mouse_data.y - self.origin_point.y);
656 if let Some(id) = self.graph.mouse_pos_to_index(data.x, data.y) {
657 self.raise_action_on_node(id);
658 EventProcessStatus::Processed
659 } else {
660 EventProcessStatus::Ignored
661 }
662 }
663 MouseEvent::Drag(mouse_data) => match &self.drag {
664 Drag::None => {
665 EventProcessStatus::Ignored
666 }
667 Drag::View(p) => {
668 self.move_scroll_to(self.origin_point.x + mouse_data.x - p.x, self.origin_point.y + mouse_data.y - p.y);
669 self.drag = Drag::View(Point::new(mouse_data.x, mouse_data.y));
670 EventProcessStatus::Processed
671 }
672 Drag::Node(node_info) => {
673 let data = Point::new(mouse_data.x - self.origin_point.x, mouse_data.y - self.origin_point.y);
674 if self.graph.move_node_to(
675 node_info.id,
676 node_info.top_left.x + data.x - node_info.origin.x,
677 node_info.top_left.y + data.y - node_info.origin.y,
678 &self.base,
679 ) {
680 self.update_scroll_bars();
681 }
682 self.ensure_current_node_is_visible();
683 EventProcessStatus::Processed
684 }
685 },
686 MouseEvent::Wheel(dir) => {
687 match dir {
688 MouseWheelDirection::Left => self.move_scroll_to(self.origin_point.x + 1, self.origin_point.y),
689 MouseWheelDirection::Right => self.move_scroll_to(self.origin_point.x - 1, self.origin_point.y),
690 MouseWheelDirection::Up => self.move_scroll_to(self.origin_point.x, self.origin_point.y + 1),
691 MouseWheelDirection::Down => self.move_scroll_to(self.origin_point.x, self.origin_point.y - 1),
692 };
693 EventProcessStatus::Processed
694 }
695 }
696 }
697}