Skip to main content

dioxus_flow/
minimap.rs

1//! A minimap overview of the graph with the current viewport indicated.
2
3use dioxus::prelude::*;
4
5use crate::state::{use_overlay_inset, FlowCore, Interaction};
6use crate::types::{Point, Rect, Side};
7
8/// A minimap showing all nodes and the visible viewport. Click to jump.
9/// Render as a child of [`crate::Flow`].
10#[component]
11pub fn MiniMap(
12    #[props(default = 200.0)] width: f64,
13    #[props(default = 140.0)] height: f64,
14    class: Option<String>,
15) -> Element {
16    let core = use_context::<FlowCore>();
17    // Panel height + 14px offset + breathing room: fit-view keeps nodes
18    // from landing underneath the minimap.
19    use_overlay_inset(Side::Bottom, height + 26.0);
20    let geoms = core.geoms.read();
21    let vp = *core.viewport.read();
22    let container = *core.container.read();
23
24    // An empty graph has nothing to map; a blank card is just noise.
25    if geoms.is_empty() {
26        return rsx! {};
27    }
28
29    // Visible region of the canvas, in flow coordinates.
30    let visible = Rect::from_points(
31        vp.screen_to_flow(Point::ZERO),
32        (container.width / vp.zoom, container.height / vp.zoom).into(),
33    );
34    let mut world = visible;
35    for geom in geoms.iter() {
36        world = world.union(&geom.rect);
37    }
38    // Pad the world a little so rects don't touch the minimap border.
39    let pad = (world.width.max(world.height) * 0.05).max(10.0);
40    let world = Rect::new(
41        world.x - pad,
42        world.y - pad,
43        world.width + 2.0 * pad,
44        world.height + 2.0 * pad,
45    );
46
47    let class = format!(
48        "df-minimap{}",
49        class
50            .as_deref()
51            .map(|c| format!(" {c}"))
52            .unwrap_or_default()
53    );
54
55    let on_pointer_down = move |evt: Event<PointerData>| {
56        evt.stop_propagation();
57        core.interaction.clone().set(Interaction::Pressed);
58        // Map the click (svg element coords, uniform "meet" scaling) back to
59        // flow coordinates and center there.
60        let p = evt.element_coordinates();
61        let scale = (width / world.width).min(height / world.height);
62        let dx = (width - world.width * scale) / 2.0;
63        let dy = (height - world.height * scale) / 2.0;
64        let flow = Point::new(world.x + (p.x - dx) / scale, world.y + (p.y - dy) / scale);
65        core.center_on(flow, 250);
66    };
67    let on_pointer_up = move |_| {
68        let mut interaction = core.interaction;
69        if *interaction.peek() == Interaction::Pressed {
70            interaction.set(Interaction::None);
71        }
72    };
73
74    rsx! {
75        svg {
76            class,
77            width,
78            height,
79            view_box: "{world.x} {world.y} {world.width} {world.height}",
80            preserve_aspect_ratio: "xMidYMid meet",
81            "role": "img",
82            "aria-label": "Graph overview; click to move the view",
83            onpointerdown: on_pointer_down,
84            onpointerup: on_pointer_up,
85            MiniMapNodes {}
86            rect {
87                class: "df-minimap-viewport",
88                x: visible.x,
89                y: visible.y,
90                width: visible.width,
91                height: visible.height,
92            }
93        }
94    }
95}
96
97/// The node rects, isolated so that panning/zooming — which re-renders the
98/// parent every frame for the viewBox and viewport indicator — only diffs a
99/// handful of attributes instead of rebuilding one rect per node per frame.
100#[component]
101fn MiniMapNodes() -> Element {
102    let core = use_context::<FlowCore>();
103    let geoms = core.geoms.read();
104    rsx! {
105        for geom in geoms.iter() {
106            rect {
107                class: if geom.selected { "df-minimap-node df-selected" } else { "df-minimap-node" },
108                x: geom.rect.x,
109                y: geom.rect.y,
110                width: geom.rect.width,
111                height: geom.rect.height,
112                rx: 2.0,
113            }
114        }
115    }
116}