Skip to main content

ui/
diagram.rs

1// diagram -- a node / edge canvas: zoom and pan, grid and snapping, a spatial index, hit
2// testing, selection (click, marquee, keyboard), moving, connecting, waypoints, a minimap and
3// one semantic record per visible node.  The app owns the model and paints it; the widget
4// keeps the view, the selection and the gestures in DiagramState and reports what the user
5// did as DEv events.
6//
7//   ui.diagram(id, &mut st, &m, tool, paint, ver) -> Vec<DEv>
8//                                   the canvas in the next layout slot (size it with
9//                                   next_size); tool DG_SELECT / DG_PAN / DG_CONNECT /
10//                                   DG_PLACE; paint draws the content at render time
11//                                   (DgPaint); ver stands for everything it shows besides the
12//                                   geometry (labels, states, colours)
13//   ui.diagram_minimap(id, &mut st, &m) -> bool    an overview in the bottom right corner of
14//                                   the canvas; pressing / dragging there moves the view
15//   ui.diagram_sems(id, &mut st, &m, &label)   one K_LISTITEM record per visible node
16//                                   (scripts: click listitem:"R1"; screen readers), no hit box
17//   ui.diagram_edge_sems(id, &mut st, &m, &label)   the same for the visible edges (a box
18//                                   around the middle of the route); st.tab_edges lets Tab
19//                                   walk them after the nodes, st.name names the canvas
20//   Viewport::{scale, to_screen, to_world, world_rect, rect_to_screen, zoom_at, step, fit}
21//   dg_paint_grid(g, vp, step, minor, major)   grid lines inside the clip (render time)
22//   dg_edge_points(m, i, &mut out)  the route of edge i (world polyline) without a state
23//   dg_stroke_edge(g, vp, pts, width, argb, cap0, cap1, dash)   stroke a route (gfx::dash
24//                                   caps LCAP_* and patterns DASH_*), culled by the clip
25//   dg_point_at(pts, f) -> point    the point at fraction f of a route's length (packets)
26//   dg_hairline(g, vp, pts, argb)   a route as 1 px anti-aliased lines: far cheaper than
27//                                   dg_stroke_edge for many thin edges (DgFrame::moving)
28//   DgFrame::find(margin, edges)    the nodes or edges near the clip (the spatial buckets)
29//   dg_toggle_rect(node, bar)       the world box of a DN_TOGGLE node's collapse button
30//   DgPaint::paint_under(g, f)      (optional) what lies under the grid: a backdrop image
31//
32// Frames and groups (additive flags, G1 of JFNetworker): a DN_FRAME node is a labelled frame
33// around other nodes (the app computes its box from theirs and puts it before them, so they lie
34// on top).  It is hit on its title bar -- the top DiagramState::frame_bar world units -- and on
35// its border (4 DIP) only: its inside stays the background (marquees start there, members are
36// clicked through it); its overview box is an outline, its record (diagram_sems) covers the
37// title bar.  DN_TOGGLE gives a node a collapse button at the right end of its title bar (a
38// frame) or in its top right corner: a click there reports DEv::Toggle (no selection change).
39// DN_STEP nodes move by whole grid steps instead of snapping their corner, so the members a
40// frame or a cluster stands for stay on the grid.  A selected DN_RESIZE node shows a handle at
41// its bottom right corner; dragging it reports DEv::Resize with the new box (Esc: back).
42// DN_OUTLINE draws a node as an outline in the overview (a backdrop, an area).  A DN_HITMIN node
43// (a line of text) is hit in a box of at least 16 x 16 DIP around it, so it stays selectable at
44// low zoom.
45//
46// Model (DModel, borrowed every frame): nodes with a world rectangle and a range of ports,
47// ports at a position relative to their node's top left corner, edges from key a to key b
48// (a port, or a node: then the route starts on its border) with a route DR_STRAIGHT /
49// DR_BEZIER / DR_ORTHO through a range of waypoints.  Keys are non-zero and unique over
50// nodes, ports and edges; later nodes lie on top.  ver changes whenever the geometry
51// changes: the key maps, the routes and the spatial buckets (256 world units) are rebuilt
52// then.  World units are DIP at zoom 1.
53//
54// Input: the hit test prefers the resize handles of selected DN_RESIZE nodes, the handles of
55// selected edges (waypoints, then the middles of their stretches), then the ports of the node
56// under the pointer, the topmost node (a DN_TOGGLE node's button first), the nearest edge
57// (max(5 DIP, half the edge width + 3 DIP)), the background.  A press + drag
58// (4 DIP) on a selected node moves the selection (snapped to the grid, Alt: free), from a
59// port connects, on a handle moves or inserts a waypoint, on the background draws a marquee
60// (Shift adds, Ctrl toggles); middle drag, Space + drag and DG_PAN pan; the wheel scrolls
61// (Shift: sideways), Ctrl + wheel zooms at the pointer.  A double click on the background with
62// DG_SELECT (no Shift, no Ctrl) reports DEv::DoubleClick at the second press; a press that became
63// a drag (a marquee) or a key between the clicks makes none.  Keys while the canvas has the focus:
64// arrows nudge the selection (grid steps, Shift x10, Alt 1) or pan, Del, Ctrl+A, Esc cancels
65// a gesture (else clears the selection), Tab / Shift+Tab walk the nodes (with tab_edges then
66// the edges, a focused edge drawn with the focus ring along its route; the focus leaves after
67// the last), Enter activates, Ctrl + plus / minus zoom, Ctrl+0 fits, Shift+F10 / the menu key
68// ask for the context menu.  DiagramState::test_keys stands in for held keys in scripts.
69// Drawing: the paint runs as one custom command whose version covers ver, the model version,
70// the view, the theme and the gesture flag; selection, hover, ports, handles, marquee and rubber
71// band are recorded commands on top, so hovering redraws only their cells.  The paint draws the
72// whole canvas into a backing store (an XRGB surface of the DiagramState) once per version;
73// every dirty rectangle copies its part out of it.  Skip what lies outside DgFrame::clip
74// (DgFrame::find asks the spatial buckets).  While a gesture moves the view or nodes (a pan,
75// wheel turns until MOVING_MS after the last one, a drag of nodes or a waypoint)
76// DgFrame::moving asks for less detail; a full paint follows the gesture.  Content that moves
77// every frame (packets) is best drawn after diagram() with ui.draw_* inside
78// ui.clip_push(st.vp.rect).  With a GPU a pan or a wheel zoom comes from tiles of the full paint
79// (canvas.rs, DiagramState::gpu, "diagram"; the first ones are copied from the backing store), no
80// less detail then; the CPU paints what lies under overlays and, a moment after it ends, the view.
81
82use alloc::collections::{BTreeMap, BTreeSet};
83use alloc::rc::Rc;
84use alloc::string::String;
85use alloc::vec::Vec;
86use core::cell::RefCell;
87use core::ops::Range;
88
89use crate::canvas::{CanvasContent, CanvasDesc, CanvasGpu, CanvasView};
90use crate::cmd::{Draw, DrawRef, Gfx};
91use crate::consts::*;
92use crate::keys::*;
93use crate::layout::dp_at;
94use crate::theme::{color_mix, THEME_N};
95use crate::Ui;
96use gfx::dash::{dash_pattern, dash_stroke, DStyle, LCAP_FLAT};
97use gfx::{Surface, FMT_XRGB};
98use rt::fx::{floor_i, sqrt};
99
100/// tools of ui.diagram
101pub const DG_SELECT: u32 = 0; // select, move, connect from ports, waypoints
102pub const DG_PAN: u32 = 1; // a left drag pans
103pub const DG_CONNECT: u32 = 2; // a drag (or two clicks) from a node / port to another connects
104pub const DG_PLACE: u32 = 3; // a click reports DEv::Click (snapped) to place something
105/// edge routes (DEdge::route)
106pub const DR_STRAIGHT: u8 = 0;
107pub const DR_BEZIER: u8 = 1;
108pub const DR_ORTHO: u8 = 2; // horizontal and vertical stretches (L / Z shapes)
109/// node flags (DNode::flags)
110pub const DN_LOCKED: u32 = 1; // never moved by the widget
111pub const DN_NOSELECT: u32 = 2; // decoration: never hit nor selected
112pub const DN_FRAME: u32 = 4; // a frame around other nodes: hit on its title bar and border only
113pub const DN_TOGGLE: u32 = 8; // a collapse button (DEv::Toggle), see dg_toggle_rect
114pub const DN_STEP: u32 = 16; // moves by whole grid steps (its corner keeps its offset to the grid)
115pub const DN_RESIZE: u32 = 32; // selected: a handle at the bottom right corner resizes it
116pub const DN_OUTLINE: u32 = 64; // an outline in the overview (DN_FRAME nodes are always)
117pub const DN_HITMIN: u32 = 128; // hit in a box of at least 16 x 16 DIP around it (a small text)
118/// port flags (DPort::flags)
119pub const DPT_HIDDEN: u32 = 1; // neither shown nor hit
120pub const DPT_USED: u32 = 2; // shown filled (connected)
121
122const CELL: f32 = 256.0; // bucket size (world units)
123const CELLS_MAX: i64 = 4096; // a query over more buckets looks at everything
124const SPAN_MAX: i64 = 64; // an item over more buckets goes to the always-checked list
125const DRAG_DIP: i32 = 4;
126const HITMIN_DIP: i32 = 16;
127const STUB: f32 = 16.0; // orthogonal routes leave a port straight for this many units
128const EPS: f32 = 0.01;
129/// a view gesture of wheel turns (or the minimap) lasts this long after the last one (ms)
130const MOVING_MS: u32 = 160;
131const ZOOMS: [f32; 16] = [0.1, 0.125, 0.167, 0.25, 0.333, 0.5, 0.667, 0.75, 1.0, 1.25, 1.5, 2.0, 3.0, 4.0, 6.0, 8.0];
132// sides of a port / anchor
133const S_NONE: u8 = 0;
134const S_L: u8 = 1;
135const S_R: u8 = 2;
136const S_T: u8 = 3;
137const S_B: u8 = 4;
138
139// ------------------------------------------------------------------ view
140
141/// what part of the world the canvas shows: zoom (DIP per world unit, 1 = 100 %), the world
142/// point at the centre of rect; rect (absolute px) and dpi are kept by ui.diagram
143#[derive(Clone, Copy, Debug, PartialEq)]
144pub struct Viewport {
145    pub zoom: f32,
146    pub cx: f64,
147    pub cy: f64,
148    pub rect: [i32; 4],
149    pub dpi: i32,
150}
151
152impl Default for Viewport {
153    fn default() -> Viewport {
154        Viewport { zoom: 1.0, cx: 0.0, cy: 0.0, rect: [0; 4], dpi: 96 }
155    }
156}
157
158impl Viewport {
159    pub const ZOOM_MIN: f32 = 0.1;
160    pub const ZOOM_MAX: f32 = 8.0;
161
162    /// px per world unit
163    pub fn scale(&self) -> f64 {
164        clampz(self.zoom) as f64 * self.dpi.max(1) as f64 / 96.0
165    }
166
167    fn mid(&self) -> (f64, f64) {
168        (self.rect[0] as f64 + self.rect[2] as f64 * 0.5, self.rect[1] as f64 + self.rect[3] as f64 * 0.5)
169    }
170
171    fn world64(&self, x: f64, y: f64) -> (f64, f64) {
172        let s = self.scale();
173        let (mx, my) = self.mid();
174        ((x - mx) / s + self.cx, (y - my) / s + self.cy)
175    }
176
177    /// a world point in absolute px
178    pub fn to_screen(&self, p: [f32; 2]) -> [f32; 2] {
179        let s = self.scale();
180        let (mx, my) = self.mid();
181        [(mx + (p[0] as f64 - self.cx) * s) as f32, (my + (p[1] as f64 - self.cy) * s) as f32]
182    }
183
184    /// an absolute px position in world units
185    pub fn to_world(&self, x: f32, y: f32) -> [f32; 2] {
186        let (wx, wy) = self.world64(x as f64, y as f64);
187        [wx as f32, wy as f32]
188    }
189
190    /// the world rectangle x0, y0, x1, y1 the canvas shows
191    pub fn world_rect(&self) -> [f32; 4] {
192        let r = self.rect;
193        let a = self.to_world(r[0] as f32, r[1] as f32);
194        let b = self.to_world((r[0] + r[2]) as f32, (r[1] + r[3]) as f32);
195        [a[0], a[1], b[0], b[1]]
196    }
197
198    /// a world rectangle x, y, w, h in px (x, y, w, h, fractional)
199    pub fn rect_to_screen(&self, r: [f32; 4]) -> [f32; 4] {
200        let a = self.to_screen([r[0], r[1]]);
201        let s = self.scale() as f32;
202        [a[0], a[1], r[2] * s, r[3] * s]
203    }
204
205    /// zoom (clamped) keeping the world point under the absolute px (sx, sy) in place
206    pub fn zoom_at(&mut self, zoom: f32, sx: f32, sy: f32) {
207        let (wx, wy) = self.world64(sx as f64, sy as f64);
208        self.zoom = clampz(zoom);
209        let s = self.scale();
210        let (mx, my) = self.mid();
211        self.cx = wx - (sx as f64 - mx) / s;
212        self.cy = wy - (sy as f64 - my) / s;
213    }
214
215    /// the next zoom step above (dir > 0) or below around the absolute px (sx, sy)
216    pub fn step(&mut self, dir: i32, sx: f32, sy: f32) {
217        let z = self.zoom;
218        let n = if dir > 0 {
219            ZOOMS.iter().copied().find(|&p| p > z * 1.001)
220        } else {
221            ZOOMS.iter().rev().copied().find(|&p| p < z * 0.999)
222        };
223        if let Some(n) = n {
224            self.zoom_at(n, sx, sy);
225        }
226    }
227
228    /// show the world rectangle b (x0, y0, x1, y1) with `margin` DIP around it, at most 100 %
229    pub fn fit(&mut self, b: [f32; 4], margin: i32) {
230        let m = dp_at(margin, self.dpi) as f64 * 2.0;
231        let (w, h) = ((b[2] - b[0]) as f64, (b[3] - b[1]) as f64);
232        let (vw, vh) = (self.rect[2] as f64 - m, self.rect[3] as f64 - m);
233        let d = self.dpi.max(1) as f64 / 96.0;
234        let mut z = 1.0f64;
235        if w > 0.0 && vw > 0.0 {
236            z = z.min(vw / (w * d));
237        }
238        if h > 0.0 && vh > 0.0 {
239            z = z.min(vh / (h * d));
240        }
241        self.zoom = clampz(z as f32);
242        self.cx = (b[0] as f64 + b[2] as f64) * 0.5;
243        self.cy = (b[1] as f64 + b[3] as f64) * 0.5;
244    }
245}
246
247fn clampz(z: f32) -> f32 {
248    if z > 0.0 { z.clamp(Viewport::ZOOM_MIN, Viewport::ZOOM_MAX) } else { 1.0 }
249}
250
251// ------------------------------------------------------------------ model and events
252
253/// a node: world rectangle x, y, w, h, its ports (a range of DModel::ports), DN_* flags
254#[derive(Clone, Debug, Default)]
255pub struct DNode {
256    pub key: u64,
257    pub rect: [f32; 4],
258    pub ports: Range<u32>,
259    pub flags: u32,
260}
261
262/// a port: its position relative to the node's top left corner, DPT_* flags
263#[derive(Clone, Copy, Debug, Default)]
264pub struct DPort {
265    pub key: u64,
266    pub at: [f32; 2],
267    pub flags: u32,
268}
269
270/// an edge from key a to key b (ports or nodes) along route DR_* through a range of
271/// DModel::wps
272#[derive(Clone, Debug, Default)]
273pub struct DEdge {
274    pub key: u64,
275    pub a: u64,
276    pub b: u64,
277    pub route: u8,
278    pub wps: Range<u32>,
279}
280
281/// the app's diagram, borrowed for one call; ver changes with every geometry change
282#[derive(Clone, Copy)]
283pub struct DModel<'a> {
284    pub nodes: &'a [DNode],
285    pub ports: &'a [DPort],
286    pub edges: &'a [DEdge],
287    pub wps: &'a [[f32; 2]],
288    pub ver: u32,
289}
290
291/// what a point of the canvas shows
292#[derive(Clone, Copy, Debug, PartialEq, Default)]
293#[non_exhaustive]
294pub enum DHit {
295    #[default]
296    None,
297    Node(u64),
298    Port(u64),
299    /// an edge: seg = the stretch between its defining points seg and seg + 1 (0 = the
300    /// start, then the waypoints), t = 0..1 along it
301    Edge { key: u64, seg: u32, t: f32 },
302    /// the handle of waypoint idx of a selected edge
303    Waypoint(u64, u32),
304    /// the handle in the middle of stretch idx of a selected edge (a drag inserts waypoint idx)
305    Mid(u64, u32),
306    /// the collapse button of a DN_TOGGLE node
307    Toggle(u64),
308    /// the resize handle of a selected DN_RESIZE node
309    Resize(u64),
310}
311
312impl DHit {
313    /// the node, port or edge key (0 = the background)
314    pub fn key(&self) -> u64 {
315        match *self {
316            DHit::None => 0,
317            DHit::Node(k) | DHit::Port(k) | DHit::Waypoint(k, _) | DHit::Mid(k, _) | DHit::Toggle(k) | DHit::Resize(k) => k,
318            DHit::Edge { key, .. } => key,
319        }
320    }
321}
322
323// the same thing (a double click needs both presses on it)
324fn same(a: DHit, b: DHit) -> bool {
325    core::mem::discriminant(&a) == core::mem::discriminant(&b) && a.key() == b.key()
326}
327
328/// what the user did (ui.diagram's result, in order)
329#[derive(Clone, Debug, PartialEq)]
330#[non_exhaustive]
331pub enum DEv {
332    /// st.sel changed
333    Select,
334    /// move nodes `keys` by d (world units); the d of one gesture add up to total; done = the
335    /// gesture ended (released, a key nudge; Esc sends d = minus everything so far)
336    Move { keys: Vec<u64>, d: [f32; 2], total: [f32; 2], done: bool },
337    /// connect from -> to (port or node keys); quick = dragged out of a port with DG_SELECT
338    Connect { from: u64, to: u64, quick: bool },
339    /// a connection is being drawn: the pointer at `at`, over port / node `over` (0 = none)
340    ConnectDrag { from: u64, at: [f32; 2], over: u64 },
341    /// the connection ended without a target (released elsewhere, Esc)
342    ConnectCancel,
343    /// waypoint idx of edge goes to at; insert = add it at idx first (the first event of a
344    /// drag from a middle handle); done = released
345    Waypoint { edge: u64, idx: u32, at: [f32; 2], insert: bool, done: bool },
346    /// remove waypoint idx of edge (double click on its handle, Esc after an insert)
347    WaypointDelete { edge: u64, idx: u32 },
348    /// double click on a node / port / edge, Enter on the focused node
349    Activate(DHit),
350    /// right click, Shift+F10 or the menu key: at = window px (logical, for popups)
351    Context(DHit, [i32; 2]),
352    /// Del with a selection: delete st.sel
353    Delete,
354    /// a click on the background, or anywhere with DG_PLACE (snapped then); world units
355    Click { at: [f32; 2] },
356    /// a double click on the background with DG_SELECT (no Shift, no Ctrl): the world point of
357    /// the second press, not snapped; neither a Click nor a marquee follows it
358    DoubleClick { at: [f32; 2] },
359    /// zoom or centre changed
360    ViewChanged,
361    /// a click on the collapse button of a DN_TOGGLE node
362    Toggle(u64),
363    /// the resize handle of a DN_RESIZE node was dragged: its new box x, y, w, h (world, the
364    /// top left corner stays; at least 8 units); done = released (Esc: the box it had)
365    Resize { key: u64, rect: [f32; 4], done: bool },
366}
367
368// ------------------------------------------------------------------ geometry for painting
369
370/// an edge's route in world units: the polyline, the indices of its defining points (a, the
371/// waypoints, b) in it, its bounding box x0, y0, x1, y1 (empty pts: an end is unknown)
372#[derive(Clone, Debug, Default)]
373pub struct DgRoute {
374    pub key: u64,
375    pub pts: Vec<[f32; 2]>,
376    pub marks: Vec<u32>,
377    pub bb: [f32; 4],
378}
379
380/// the routes of every edge in model order and the spatial buckets of nodes and edges, built
381/// once per model version (DgFrame::find asks the buckets)
382#[derive(Default)]
383pub struct DgGeo {
384    pub routes: Vec<DgRoute>,
385    // (row, column) -> (nodes, edges); what spans too many buckets; the numbers of nodes and
386    // edges
387    cells: BTreeMap<(i32, i32), (Vec<u32>, Vec<u32>)>,
388    big: (Vec<u32>, Vec<u32>),
389    counts: (usize, usize),
390}
391
392/// what a DgPaint gets at render time
393pub struct DgFrame<'a> {
394    pub vp: Viewport,
395    /// the world rectangle x0, y0, x1, y1 that needs pixels now: skip what lies outside
396    pub clip: [f32; 4],
397    pub geo: &'a DgGeo,
398    /// the theme's colours (color(T_*)), the canvas background (opaque)
399    pub theme: &'a [u32],
400    pub bg: u32,
401    /// font and size (24.8 px at zoom 1) of every TS_* text style
402    pub fonts: [(u32, i32); 7],
403    pub dark: bool,
404    /// a gesture moves the view or nodes (a pan, wheel turns, a drag of nodes or a waypoint):
405    /// leave out what costs and nobody reads in passing (small labels; plain shapes,
406    /// dg_hairline); the canvas is painted in full after it
407    pub moving: bool,
408}
409
410impl DgFrame<'_> {
411    /// 0xAARRGGBB of a theme token
412    pub fn color(&self, tok: u32) -> u32 {
413        self.theme.get(((tok & 252) / 4) as usize).copied().unwrap_or(0xFF00_0000)
414    }
415
416    /// the world rectangle x, y, w, h (with 2 px for anti-aliasing) touches the clip
417    pub fn visible(&self, r: [f32; 4]) -> bool {
418        let g = 2.0 / self.vp.scale() as f32;
419        overlaps([r[0] - g, r[1] - g, r[0] + r[2] + g, r[1] + r[3] + g], self.clip)
420    }
421
422    /// a route stroked `width` world units wide (dg_stroke_edge, arrow heads included)
423    /// touches the clip
424    pub fn route_visible(&self, rt: &DgRoute, width: f32) -> bool {
425        let s = self.vp.scale() as f32;
426        let g = ((width * s).max(1.0) * 4.0 + 6.0) / s;
427        rt.pts.len() >= 2 && overlaps([rt.bb[0] - g, rt.bb[1] - g, rt.bb[2] + g, rt.bb[3] + g], self.clip)
428    }
429
430    /// the nodes (edges = false: DModel::nodes) or the edges whose spatial buckets touch the
431    /// clip grown by `margin` px: ascending indices, a superset of what can need paint (cheaper
432    /// than looking at all of them)
433    pub fn find(&self, margin: f32, edges: bool) -> Vec<u32> {
434        let g = margin / (self.vp.scale() as f32).max(1e-6);
435        let c = self.clip;
436        let mut v = Vec::new();
437        self.geo.find([c[0] - g, c[1] - g, c[2] + g, c[3] + g], edges, &mut v);
438        v
439    }
440
441    /// font and size (24.8 px) of text style ts at the current zoom
442    pub fn font(&self, ts: u32) -> (u32, i32) {
443        let (f, s) = self.fonts[((ts & 7) as usize).min(6)];
444        (f, (s as f32 * clampz(self.vp.zoom)) as i32)
445    }
446}
447
448/// the app's painting of a diagram (render time): nodes, edges (DgFrame::geo), labels; it
449/// runs once per dirty rectangle, so skip what DgFrame::visible / route_visible reject
450pub trait DgPaint {
451    fn paint(&self, g: &mut Gfx, f: &DgFrame);
452    /// what lies under the grid (a backdrop image), painted before it; nothing by default
453    fn paint_under(&self, _g: &mut Gfx, _f: &DgFrame) {}
454}
455
456// ------------------------------------------------------------------ state
457
458#[derive(Clone, Default)]
459enum Gesture {
460    #[default]
461    Idle,
462    /// a left press below the drag threshold: what it hit, where (px, world), the modifiers,
463    /// whether the hit was selected before
464    Press { hit: DHit, px: [i32; 2], at: [f32; 2], mods: u32, was: bool },
465    /// ignore the pointer until the left button is up
466    Hold,
467    Pan { btn: u32, px: [i32; 2], cx: f64, cy: f64 },
468    /// step: the anchor is a DN_STEP node (the move snaps to whole grid steps)
469    Move { keys: Vec<u64>, start: [f32; 2], anchor: [f32; 2], sent: [f32; 2], step: bool },
470    /// the resize handle of node key: its box at the start, where the drag started, sent at all
471    Resize { key: u64, orig: [f32; 4], start: [f32; 2], at: [f32; 4], started: bool },
472    Marquee { start: [f32; 2], cur: [f32; 2], mods: u32, base: BTreeSet<u64> },
473    /// armed: started by a click, a second click ends it
474    Connect { from: u64, quick: bool, armed: bool, at: [f32; 2], over: u64 },
475    Waypoint { edge: u64, idx: u32, orig: [f32; 2], insert: bool, started: bool, at: [f32; 2] },
476}
477
478// the key maps, the routes and the spatial buckets of one model version
479#[derive(Default)]
480struct Index {
481    built: bool,
482    ver: u32,
483    counts: [usize; 4],
484    nodes: BTreeMap<u64, u32>,
485    ports: BTreeMap<u64, (u32, u32)>,
486    edges: BTreeMap<u64, u32>,
487    geo: Rc<DgGeo>,
488    bounds: Option<[f32; 4]>,
489}
490
491/// the widget's state, kept by the app: the view, the selection (node and edge keys), the
492/// focused node, grid and snapping, the hover, the gesture in progress and the index
493pub struct DiagramState {
494    pub vp: Viewport,
495    pub sel: BTreeSet<u64>,
496    /// the node with the keyboard focus (0 = none): Tab walks, Enter activates it
497    pub focus: u64,
498    /// grid step in world units (0 = no grid)
499    pub grid: f32,
500    /// moves, waypoints and DG_PLACE clicks snap to the grid (Alt held: free)
501    pub snap: bool,
502    /// what the pointer is over (after the last diagram call)
503    pub hover: DHit,
504    /// the stroke width of the edges in world units (hit testing, the highlight)
505    pub edge_width: f32,
506    /// scripts cannot hold keys: 1 = as if Space were held, 2 = Ctrl
507    pub test_keys: u32,
508    /// Tab / Shift+Tab also walk the edges (after the nodes, in model order): the focused
509    /// edge is selected, Enter activates it, the menu key asks for its context menu
510    pub tab_edges: bool,
511    /// the canvas' name for screen readers (its K_CANVAS record; empty: none)
512    pub name: String,
513    /// the height of a DN_FRAME node's title bar in world units (its hit area, its record, the
514    /// side of its DN_TOGGLE button)
515    pub frame_bar: f32,
516    fit_req: bool,
517    pending_sel: bool,
518    g: Gesture,
519    ix: Index,
520    // what the last press that can start a double click hit (None: no such press; DHit::None
521    // alone is the background)
522    last_hit: Option<DHit>,
523    last_ms: u32,
524    last_px: [i32; 2],
525    visible: Vec<u32>,
526    paint_obj: Option<(u64, Rc<Wrap>)>,
527    mini_obj: Option<(u64, DrawRef)>,
528    mini_ext: Option<[f32; 4]>,
529    /// the canvas as painted last (Wrap)
530    store: Rc<RefCell<Backing>>,
531    /// the view moves until this ms (wheel turns, the minimap)
532    moving_until: u32,
533    /// how the view's pan and zoom use the GPU (canvas.rs)
534    pub gpu: CanvasGpu,
535    /// the widget's id (the tiles' canvas)
536    id: u32,
537}
538
539impl Default for DiagramState {
540    fn default() -> DiagramState {
541        DiagramState::new()
542    }
543}
544
545impl DiagramState {
546    /// grid 16, snapping on, edges 2 units wide; the first diagram call fits the content
547    pub fn new() -> DiagramState {
548        DiagramState {
549            vp: Viewport::default(),
550            sel: BTreeSet::new(),
551            focus: 0,
552            grid: 16.0,
553            snap: true,
554            hover: DHit::None,
555            edge_width: 2.0,
556            test_keys: 0,
557            tab_edges: false,
558            name: String::new(),
559            frame_bar: 24.0,
560            fit_req: true,
561            pending_sel: false,
562            g: Gesture::Idle,
563            ix: Index::default(),
564            last_hit: None,
565            last_ms: 0,
566            last_px: [0; 2],
567            visible: Vec::new(),
568            paint_obj: None,
569            mini_obj: None,
570            mini_ext: None,
571            store: Rc::new(RefCell::new(Backing::default())),
572            moving_until: 0,
573            gpu: CanvasGpu::Zoom,
574            id: 0,
575        }
576    }
577
578    /// fit the whole content into the view with the next diagram call
579    pub fn request_fit(&mut self) {
580        self.fit_req = true;
581    }
582
583    /// a gesture (drag, marquee, connection, pan) is in progress
584    pub fn busy(&self) -> bool {
585        !matches!(self.g, Gesture::Idle | Gesture::Hold)
586    }
587
588    /// the route of edge `key` (as of the last diagram call)
589    pub fn route(&self, key: u64) -> Option<&DgRoute> {
590        let i = *self.ix.edges.get(&key)?;
591        self.ix.geo.routes.get(i as usize)
592    }
593
594    /// every route (model order), shared with the paint
595    pub fn geo(&self) -> &Rc<DgGeo> {
596        &self.ix.geo
597    }
598
599    /// the bounding box x0, y0, x1, y1 of nodes and routes (None: empty)
600    pub fn bounds(&self) -> Option<[f32; 4]> {
601        self.ix.bounds
602    }
603
604    /// the indices of the nodes the canvas showed in the last diagram call
605    pub fn visible(&self) -> &[u32] {
606        &self.visible
607    }
608
609    fn snaps(&self, mods: u32) -> bool {
610        self.snap && self.grid > 0.0 && mods & MOD_ALT == 0
611    }
612
613    fn snap_pt(&self, p: [f32; 2], mods: u32) -> [f32; 2] {
614        if self.snaps(mods) { [snapv(p[0], self.grid), snapv(p[1], self.grid)] } else { p }
615    }
616
617    // the node index of a node or port key
618    fn node_index(&self, key: u64) -> Option<u32> {
619        self.ix.ports.get(&key).map(|p| p.0).or_else(|| self.ix.nodes.get(&key).copied())
620    }
621
622    fn node<'m>(&self, m: &DModel<'m>, key: u64) -> Option<&'m DNode> {
623        let i = *self.ix.nodes.get(&key)?;
624        m.nodes.get(i as usize)
625    }
626
627    // forget selected / focused keys the model no longer has
628    fn prune(&mut self) -> bool {
629        let ix = &self.ix;
630        let n = self.sel.len();
631        self.sel.retain(|k| ix.nodes.contains_key(k) || ix.edges.contains_key(k));
632        if self.focus != 0 && !ix.nodes.contains_key(&self.focus) && !ix.edges.contains_key(&self.focus) {
633            self.focus = 0;
634        }
635        n != self.sel.len()
636    }
637
638    // ---- hit testing
639
640    fn hit(&self, m: &DModel, p: [f32; 2], tool: u32, dpi: i32) -> DHit {
641        let s = self.vp.scale() as f32;
642        if tool == DG_PAN || !(s > 0.0) {
643            return DHit::None;
644        }
645        let w = |dip: i32| dp_at(dip, dpi) as f32 / s; // DIP -> world units
646        // the resize handles of selected DN_RESIZE nodes
647        if tool == DG_SELECT {
648            let hr = w(6);
649            for &k in &self.sel {
650                let Some(n) = self.node(m, k).filter(|n| n.flags & DN_RESIZE != 0) else { continue };
651                let c = [n.rect[0] + n.rect[2], n.rect[1] + n.rect[3]];
652                if fabs(p[0] - c[0]) <= hr && fabs(p[1] - c[1]) <= hr {
653                    return DHit::Resize(k);
654                }
655            }
656        }
657        // the handles of selected edges: waypoints first, then the middles
658        if tool == DG_SELECT {
659            let hr = w(6);
660            let mut best: Option<(f32, DHit)> = None;
661            for pass in 0..2 {
662                for &k in &self.sel {
663                    let Some(&ei) = self.ix.edges.get(&k) else { continue };
664                    let (Some(e), Some(rt)) = (m.edges.get(ei as usize), self.ix.geo.routes.get(ei as usize)) else { continue };
665                    if pass == 0 {
666                        for (j, q) in m.wps[range_of(&e.wps, m.wps.len())].iter().enumerate() {
667                            let d = dist(p, *q);
668                            if d <= hr && best.is_none_or(|b| d < b.0) {
669                                best = Some((d, DHit::Waypoint(k, j as u32)));
670                            }
671                        }
672                    } else {
673                        for j in 0..rt.marks.len().saturating_sub(1) {
674                            let Some(q) = mid_of(rt, j) else { continue };
675                            let d = dist(p, q);
676                            if d <= hr && best.is_none_or(|b| d < b.0) {
677                                best = Some((d, DHit::Mid(k, j as u32)));
678                            }
679                        }
680                    }
681                }
682                if let Some((_, h)) = best {
683                    return h;
684                }
685            }
686        }
687        // the ports of the node under the pointer, the topmost node
688        let pr = w(6);
689        let mut cand = Vec::new();
690        self.ix.query([p[0] - pr, p[1] - pr, p[0] + pr, p[1] + pr], false, m.nodes.len(), &mut cand);
691        for &i in cand.iter().rev() {
692            let Some(n) = m.nodes.get(i as usize) else { continue };
693            if n.flags & DN_NOSELECT != 0 {
694                continue;
695            }
696            let r = n.rect;
697            let within = |g: f32| p[0] >= r[0] - g && p[1] >= r[1] - g && p[0] < r[0] + r[2] + g && p[1] < r[1] + r[3] + g;
698            if !within(pr) {
699                continue;
700            }
701            if tool != DG_PLACE {
702                let mut best: Option<(f32, u64)> = None;
703                for q in &m.ports[range_of(&n.ports, m.ports.len())] {
704                    if q.flags & DPT_HIDDEN != 0 {
705                        continue;
706                    }
707                    let d = dist(p, [r[0] + q.at[0], r[1] + q.at[1]]);
708                    if d <= pr && best.is_none_or(|b| d < b.0) {
709                        best = Some((d, q.key));
710                    }
711                }
712                if let Some((_, k)) = best {
713                    return DHit::Port(k);
714                }
715            }
716            if n.flags & DN_FRAME != 0 {
717                // a frame: its title bar and its border; the inside belongs to what lies there
718                let (b, bar) = (w(4), self.frame_bar.min(r[3]));
719                let on_bar = within(0.0) && p[1] < r[1] + bar;
720                let inner = p[0] >= r[0] + b && p[1] >= r[1] + b && p[0] < r[0] + r[2] - b && p[1] < r[1] + r[3] - b;
721                if !on_bar && !(within(b) && !inner) {
722                    continue;
723                }
724            } else if n.flags & DN_HITMIN != 0 {
725                // up to the reach of the query above
726                let min = w(HITMIN_DIP);
727                let (gx, gy) = (((min - r[2]) * 0.5).clamp(0.0, pr), ((min - r[3]) * 0.5).clamp(0.0, pr));
728                if !(p[0] >= r[0] - gx && p[1] >= r[1] - gy && p[0] < r[0] + r[2] + gx && p[1] < r[1] + r[3] + gy) {
729                    continue;
730                }
731            } else if !within(0.0) {
732                continue;
733            }
734            if n.flags & DN_TOGGLE != 0 && tool == DG_SELECT {
735                let t = dg_toggle_rect(n, self.frame_bar);
736                if p[0] >= t[0] && p[1] >= t[1] && p[0] < t[0] + t[2] && p[1] < t[1] + t[3] {
737                    return DHit::Toggle(n.key);
738                }
739            }
740            return DHit::Node(n.key);
741        }
742        // the nearest edge
743        if tool == DG_SELECT {
744            let tol = w(5).max((self.edge_width * s * 0.5 + dp_at(3, dpi) as f32) / s);
745            self.ix.query([p[0] - tol, p[1] - tol, p[0] + tol, p[1] + tol], true, m.edges.len(), &mut cand);
746            let mut best: Option<(f32, usize, usize, f32)> = None;
747            for &i in &cand {
748                let Some(rt) = self.ix.geo.routes.get(i as usize) else { continue };
749                if rt.pts.len() < 2 || !overlaps(rt.bb, [p[0] - tol, p[1] - tol, p[0] + tol, p[1] + tol]) {
750                    continue;
751                }
752                let (d2, j, u) = near_route(rt, p);
753                if d2 <= tol * tol && best.is_none_or(|b| d2 <= b.0) {
754                    best = Some((d2, i as usize, j, u));
755                }
756            }
757            if let Some((_, i, j, u)) = best {
758                let rt = &self.ix.geo.routes[i];
759                let (seg, t) = seg_t(rt, j, u);
760                return DHit::Edge { key: rt.key, seg, t };
761            }
762        }
763        DHit::None
764    }
765
766    fn cursor(&self, m: &DModel, tool: u32, space: bool) -> u32 {
767        match self.g {
768            Gesture::Pan { .. } => return CUR_HAND,
769            Gesture::Move { .. } | Gesture::Waypoint { .. } => return CUR_MOVE,
770            Gesture::Connect { .. } => return CUR_CROSS,
771            Gesture::Resize { .. } => return CUR_SIZE_NWSE,
772            _ => {}
773        }
774        if space || tool == DG_PAN {
775            return CUR_HAND;
776        }
777        match (tool, self.hover) {
778            (DG_PLACE, _) => CUR_CROSS,
779            (_, DHit::Toggle(_)) => CUR_ARROW,
780            (_, DHit::Resize(_)) => CUR_SIZE_NWSE,
781            (_, DHit::Port(_)) => CUR_CROSS,
782            (DG_CONNECT, DHit::Node(_)) => CUR_CROSS,
783            (_, DHit::Waypoint(..) | DHit::Mid(..)) => CUR_MOVE,
784            (_, DHit::Node(k)) if self.node(m, k).is_some_and(|n| n.flags & DN_LOCKED == 0) => CUR_MOVE,
785            _ => CUR_ARROW,
786        }
787    }
788
789    // ---- gestures
790
791    fn wheel(&mut self, wx: i32, wy: i32, mods: u32, at: [i32; 2], notch: i32) {
792        if wy != 0 && mods & MOD_CTRL != 0 {
793            let n = wy.unsigned_abs().div_ceil(120).min(16);
794            for _ in 0..n {
795                self.vp.step(if wy > 0 { 1 } else { -1 }, at[0] as f32 + 0.5, at[1] as f32 + 0.5);
796            }
797            return;
798        }
799        // wheel up shows the upper part (Shift: the left part)
800        let k = notch as f64 / 120.0 / self.vp.scale();
801        let (dx, dy) = if mods & MOD_SHIFT != 0 { (wx - wy, 0) } else { (wx, -wy) };
802        self.vp.cx += dx as f64 * k;
803        self.vp.cy += dy as f64 * k;
804    }
805
806    fn step(&mut self, m: &DModel, i: &In, out: &mut Vec<DEv>) {
807        let g = core::mem::take(&mut self.g);
808        self.g = match g {
809            Gesture::Idle => self.idle(i, out),
810            Gesture::Press { hit, px, at, mods, was } => {
811                let far = (i.px[0] - px[0]).abs() > i.lim || (i.px[1] - px[1]).abs() > i.lim;
812                if i.btn & (1 << BTN_LEFT) == 0 {
813                    self.click(m, i.tool, hit, at, mods, was, out)
814                } else if far {
815                    self.last_hit = None; // a drag is no first click
816                    self.drag(m, i.tool, hit, at, mods)
817                } else {
818                    Gesture::Press { hit, px, at, mods, was }
819                }
820            }
821            g => g,
822        };
823        let g = core::mem::take(&mut self.g);
824        self.g = self.follow(m, i, g, out);
825    }
826
827    // a press with nothing in progress
828    fn idle(&mut self, i: &In, out: &mut Vec<DEv>) -> Gesture {
829        let hov = i.ev & EV_HOVER != 0;
830        let lpress = hov && i.pressed & (1 << BTN_LEFT) != 0 && i.mine;
831        let mpress = hov && i.pressed & (1 << BTN_MIDDLE) != 0;
832        if mpress || (lpress && (i.space || i.tool == DG_PAN)) {
833            let btn = if mpress { BTN_MIDDLE } else { BTN_LEFT };
834            return Gesture::Pan { btn, px: i.px, cx: self.vp.cx, cy: self.vp.cy };
835        }
836        if !lpress {
837            return Gesture::Idle;
838        }
839        // a double click: the second press on the same thing, in time and near the first
840        // (behave's EV_DOUBLE also counts a press after a drag)
841        let hit = self.hover;
842        let near = (i.px[0] - self.last_px[0]).abs() <= i.lim * 2 && (i.px[1] - self.last_px[1]).abs() <= i.lim * 2;
843        if self.last_hit.is_some_and(|l| same(hit, l)) && near && i.now.wrapping_sub(self.last_ms) <= i.dbl {
844            self.last_hit = None;
845            match hit {
846                DHit::Waypoint(e, k) => {
847                    out.push(DEv::WaypointDelete { edge: e, idx: k });
848                    return Gesture::Hold;
849                }
850                DHit::None if i.tool == DG_SELECT && i.mods & (MOD_SHIFT | MOD_CTRL) == 0 => {
851                    out.push(DEv::DoubleClick { at: i.p });
852                    return Gesture::Hold;
853                }
854                // buttons and handles take every click as one of their own
855                DHit::None | DHit::Mid(..) | DHit::Toggle(_) | DHit::Resize(_) => {}
856                h => {
857                    out.push(DEv::Activate(h));
858                    return Gesture::Hold;
859                }
860            }
861        } else {
862            self.last_hit = Some(hit);
863            self.last_ms = i.now;
864            self.last_px = i.px;
865        }
866        let mut was = false;
867        if i.tool == DG_SELECT {
868            let k = match hit {
869                DHit::Node(k) => k,
870                DHit::Edge { key, .. } => key,
871                _ => 0,
872            };
873            if k != 0 {
874                was = self.sel.contains(&k);
875                let changed = if i.mods & MOD_CTRL != 0 {
876                    if was {
877                        self.sel.remove(&k);
878                    } else {
879                        self.sel.insert(k);
880                    }
881                    true
882                } else if i.mods & MOD_SHIFT != 0 {
883                    self.sel.insert(k)
884                } else if !was {
885                    self.sel.clear();
886                    self.sel.insert(k);
887                    true
888                } else {
889                    false
890                };
891                if changed {
892                    out.push(DEv::Select);
893                }
894                if matches!(hit, DHit::Node(_)) {
895                    self.focus = k;
896                }
897            }
898        }
899        Gesture::Press { hit, px: i.px, at: i.p, mods: i.mods, was }
900    }
901
902    // released below the drag threshold
903    #[allow(clippy::too_many_arguments)]
904    fn click(&mut self, m: &DModel, tool: u32, hit: DHit, at: [f32; 2], mods: u32, was: bool, out: &mut Vec<DEv>) -> Gesture {
905        match tool {
906            DG_PLACE => {
907                out.push(DEv::Click { at: self.snap_pt(at, mods) });
908                Gesture::Idle
909            }
910            DG_CONNECT => match hit {
911                DHit::Node(k) | DHit::Port(k) => Gesture::Connect { from: k, quick: false, armed: true, at, over: 0 },
912                _ => {
913                    out.push(DEv::Click { at });
914                    Gesture::Idle
915                }
916            },
917            _ => {
918                match hit {
919                    // a click on one of several selected things selects it alone
920                    DHit::Node(k) | DHit::Edge { key: k, .. } => {
921                        if mods & (MOD_SHIFT | MOD_CTRL) == 0 && was && self.sel.len() > 1 {
922                            self.sel.clear();
923                            self.sel.insert(k);
924                            out.push(DEv::Select);
925                        }
926                    }
927                    // a click on a port selects its node
928                    DHit::Port(k) => {
929                        let nk = self.node_index(k).and_then(|n| m.nodes.get(n as usize)).map_or(0, |n| n.key);
930                        if nk != 0 {
931                            let before = self.sel.clone();
932                            if mods & MOD_CTRL != 0 {
933                                if !self.sel.remove(&nk) {
934                                    self.sel.insert(nk);
935                                }
936                            } else {
937                                if mods & MOD_SHIFT == 0 {
938                                    self.sel.clear();
939                                }
940                                self.sel.insert(nk);
941                            }
942                            if before != self.sel {
943                                out.push(DEv::Select);
944                            }
945                            self.focus = nk;
946                        }
947                    }
948                    DHit::None => {
949                        if mods & (MOD_SHIFT | MOD_CTRL) == 0 && !self.sel.is_empty() {
950                            self.sel.clear();
951                            out.push(DEv::Select);
952                        }
953                        out.push(DEv::Click { at });
954                    }
955                    DHit::Toggle(k) => out.push(DEv::Toggle(k)),
956                    _ => {}
957                }
958                Gesture::Idle
959            }
960        }
961    }
962
963    // moved past the drag threshold: the gesture the press starts
964    fn drag(&mut self, m: &DModel, tool: u32, hit: DHit, at: [f32; 2], mods: u32) -> Gesture {
965        match tool {
966            DG_PLACE => Gesture::Hold,
967            DG_CONNECT => match hit {
968                DHit::Node(k) | DHit::Port(k) => Gesture::Connect { from: k, quick: false, armed: false, at, over: 0 },
969                _ => Gesture::Hold,
970            },
971            _ => match hit {
972                DHit::Node(k) if self.sel.contains(&k) => {
973                    let movable = |key: u64| self.node(m, key).is_some_and(|n| n.flags & DN_LOCKED == 0);
974                    let keys: Vec<u64> = self.sel.iter().copied().filter(|&s| movable(s)).collect();
975                    let Some(&first) = keys.first() else { return Gesture::Hold };
976                    let a = if movable(k) { k } else { first };
977                    let anchor = self.node(m, a).map_or([0.0; 2], |n| [n.rect[0], n.rect[1]]);
978                    let step = self.node(m, a).is_some_and(|n| n.flags & DN_STEP != 0);
979                    Gesture::Move { keys, start: at, anchor, sent: [0.0; 2], step }
980                }
981                DHit::Resize(k) => match self.node(m, k) {
982                    Some(n) => Gesture::Resize { key: k, orig: n.rect, start: at, at: n.rect, started: false },
983                    None => Gesture::Hold,
984                },
985                DHit::Port(k) => Gesture::Connect { from: k, quick: true, armed: false, at, over: 0 },
986                DHit::Waypoint(e, idx) => {
987                    let orig = self.waypoint(m, e, idx).unwrap_or(at);
988                    Gesture::Waypoint { edge: e, idx, orig, insert: false, started: false, at: orig }
989                }
990                DHit::Mid(e, idx) => Gesture::Waypoint { edge: e, idx, orig: at, insert: true, started: false, at },
991                DHit::None => {
992                    let base = if mods & (MOD_SHIFT | MOD_CTRL) != 0 { self.sel.clone() } else { BTreeSet::new() };
993                    Gesture::Marquee { start: at, cur: at, mods, base }
994                }
995                _ => Gesture::Hold,
996            },
997        }
998    }
999
1000    fn waypoint(&self, m: &DModel, edge: u64, idx: u32) -> Option<[f32; 2]> {
1001        let e = m.edges.get(*self.ix.edges.get(&edge)? as usize)?;
1002        m.wps[range_of(&e.wps, m.wps.len())].get(idx as usize).copied()
1003    }
1004
1005    // the connection target under the pointer: a port of another node, another node
1006    fn target(&self, from: u64) -> u64 {
1007        let src = self.node_index(from);
1008        match self.hover {
1009            DHit::Port(k) if k != from && self.node_index(k) != src => k,
1010            DHit::Node(k) if self.node_index(k) != src => k,
1011            _ => 0,
1012        }
1013    }
1014
1015    // a running gesture follows the pointer
1016    fn follow(&mut self, m: &DModel, i: &In, g: Gesture, out: &mut Vec<DEv>) -> Gesture {
1017        let held = |b: u32| i.btn & (1 << b) != 0;
1018        match g {
1019            Gesture::Pan { btn, px, cx, cy } => {
1020                if !held(btn) {
1021                    return Gesture::Idle;
1022                }
1023                let s = self.vp.scale();
1024                self.vp.cx = cx - (i.px[0] - px[0]) as f64 / s;
1025                self.vp.cy = cy - (i.px[1] - px[1]) as f64 / s;
1026                Gesture::Pan { btn, px, cx, cy }
1027            }
1028            Gesture::Hold => {
1029                if held(BTN_LEFT) {
1030                    Gesture::Hold
1031                } else {
1032                    Gesture::Idle
1033                }
1034            }
1035            Gesture::Move { keys, start, anchor, sent, step } => {
1036                let raw = [i.p[0] - start[0], i.p[1] - start[1]];
1037                let tot = if self.snaps(i.mods) && step {
1038                    [snapv(raw[0], self.grid), snapv(raw[1], self.grid)]
1039                } else if self.snaps(i.mods) {
1040                    [snapv(anchor[0] + raw[0], self.grid) - anchor[0], snapv(anchor[1] + raw[1], self.grid) - anchor[1]]
1041                } else {
1042                    raw
1043                };
1044                let done = !held(BTN_LEFT);
1045                let d = [tot[0] - sent[0], tot[1] - sent[1]];
1046                if d != [0.0, 0.0] || done {
1047                    out.push(DEv::Move { keys: keys.clone(), d, total: tot, done });
1048                }
1049                if done { Gesture::Idle } else { Gesture::Move { keys, start, anchor, sent: tot, step } }
1050            }
1051            Gesture::Resize { key, orig, start, at, started } => {
1052                let rect = [orig[0], orig[1], (orig[2] + i.p[0] - start[0]).max(8.0), (orig[3] + i.p[1] - start[1]).max(8.0)];
1053                let done = !held(BTN_LEFT);
1054                if rect != at || done {
1055                    out.push(DEv::Resize { key, rect, done });
1056                }
1057                if done { Gesture::Idle } else { Gesture::Resize { key, orig, start, at: rect, started: started || rect != orig } }
1058            }
1059            Gesture::Marquee { start, mods, base, .. } => {
1060                let cur = i.p;
1061                let rr = [start[0].min(cur[0]), start[1].min(cur[1]), start[0].max(cur[0]), start[1].max(cur[1])];
1062                let toggle = mods & MOD_CTRL != 0;
1063                let mut sel = base.clone();
1064                let mut flip = |k: u64| {
1065                    if toggle && sel.contains(&k) {
1066                        sel.remove(&k);
1067                    } else {
1068                        sel.insert(k);
1069                    }
1070                };
1071                let mut v = Vec::new();
1072                self.ix.query(rr, false, m.nodes.len(), &mut v);
1073                for &n in &v {
1074                    if let Some(n) = m.nodes.get(n as usize) {
1075                        if n.flags & DN_NOSELECT == 0 && inside(xyxy(n.rect), rr) {
1076                            flip(n.key);
1077                        }
1078                    }
1079                }
1080                self.ix.query(rr, true, m.edges.len(), &mut v);
1081                for &e in &v {
1082                    if let Some(rt) = self.ix.geo.routes.get(e as usize) {
1083                        if rt.pts.len() >= 2 && inside(rt.bb, rr) {
1084                            flip(rt.key);
1085                        }
1086                    }
1087                }
1088                if sel != self.sel {
1089                    self.sel = sel;
1090                    out.push(DEv::Select);
1091                }
1092                if held(BTN_LEFT) { Gesture::Marquee { start, cur, mods, base } } else { Gesture::Idle }
1093            }
1094            Gesture::Connect { from, quick, armed, at, over } => {
1095                let o = self.target(from);
1096                let a = i.p;
1097                if a != at || o != over {
1098                    out.push(DEv::ConnectDrag { from, at: a, over: o });
1099                }
1100                let lpress = i.pressed & (1 << BTN_LEFT) != 0;
1101                let end = if armed { lpress } else { !held(BTN_LEFT) };
1102                if end {
1103                    // armed: a press outside the canvas only cancels
1104                    let ours = !armed || (i.mine && i.ev & EV_HOVER != 0);
1105                    if o != 0 && ours {
1106                        out.push(DEv::Connect { from, to: o, quick });
1107                    } else {
1108                        out.push(DEv::ConnectCancel);
1109                    }
1110                    return if armed { Gesture::Hold } else { Gesture::Idle };
1111                }
1112                Gesture::Connect { from, quick, armed, at: a, over: o }
1113            }
1114            Gesture::Waypoint { edge, idx, orig, insert, started, at } => {
1115                let a = self.snap_pt(i.p, i.mods);
1116                let done = !held(BTN_LEFT);
1117                if a != at || !started || done {
1118                    if started || done || insert || a != orig {
1119                        out.push(DEv::Waypoint { edge, idx, at: a, insert: insert && !started, done });
1120                    } else {
1121                        return Gesture::Waypoint { edge, idx, orig, insert, started, at };
1122                    }
1123                }
1124                if done { Gesture::Idle } else { Gesture::Waypoint { edge, idx, orig, insert, started: true, at: a } }
1125            }
1126            g => g,
1127        }
1128    }
1129
1130    // Esc: take the gesture back -> handled
1131    fn cancel(&mut self, out: &mut Vec<DEv>) -> bool {
1132        let g = core::mem::take(&mut self.g);
1133        self.g = match g {
1134            Gesture::Idle => return false,
1135            Gesture::Press { .. } | Gesture::Hold => Gesture::Hold,
1136            Gesture::Pan { cx, cy, .. } => {
1137                self.vp.cx = cx;
1138                self.vp.cy = cy;
1139                Gesture::Idle
1140            }
1141            Gesture::Move { keys, sent, .. } => {
1142                if sent != [0.0, 0.0] {
1143                    out.push(DEv::Move { keys, d: [-sent[0], -sent[1]], total: [0.0; 2], done: true });
1144                }
1145                Gesture::Hold
1146            }
1147            Gesture::Resize { key, orig, started, .. } => {
1148                if started {
1149                    out.push(DEv::Resize { key, rect: orig, done: true });
1150                }
1151                Gesture::Hold
1152            }
1153            Gesture::Marquee { base, .. } => {
1154                if self.sel != base {
1155                    self.sel = base;
1156                    out.push(DEv::Select);
1157                }
1158                Gesture::Hold
1159            }
1160            Gesture::Connect { armed, .. } => {
1161                out.push(DEv::ConnectCancel);
1162                if armed { Gesture::Idle } else { Gesture::Hold }
1163            }
1164            Gesture::Waypoint { edge, idx, orig, insert, started, .. } => {
1165                if started && insert {
1166                    out.push(DEv::WaypointDelete { edge, idx });
1167                } else if started {
1168                    out.push(DEv::Waypoint { edge, idx, at: orig, insert: false, done: true });
1169                }
1170                Gesture::Hold
1171            }
1172        };
1173        true
1174    }
1175
1176    // a right click / the context key on `hit` (selects an unselected node or edge first; a
1177    // collapse button counts as its node)
1178    fn context(&mut self, hit: DHit, at: [i32; 2], out: &mut Vec<DEv>) {
1179        let hit = if let DHit::Toggle(k) = hit { DHit::Node(k) } else { hit };
1180        if let DHit::Node(k) | DHit::Edge { key: k, .. } = hit {
1181            if !self.sel.contains(&k) {
1182                self.sel.clear();
1183                self.sel.insert(k);
1184                out.push(DEv::Select);
1185            }
1186            if matches!(hit, DHit::Node(_)) {
1187                self.focus = k;
1188            }
1189        }
1190        out.push(DEv::Context(hit, at));
1191    }
1192
1193    // the selectable node keys in model order
1194    fn walk_keys(&self, m: &DModel) -> Vec<u64> {
1195        m.nodes.iter().filter(|n| n.flags & DN_NOSELECT == 0).map(|n| n.key).collect()
1196    }
1197
1198    // what Tab walks: the nodes, then (tab_edges) the edges that have a route
1199    fn tab_keys(&self, m: &DModel) -> Vec<u64> {
1200        let mut keys = self.walk_keys(m);
1201        if self.tab_edges {
1202            keys.extend(self.ix.geo.routes.iter().filter(|rt| rt.pts.len() >= 2).map(|rt| rt.key));
1203        }
1204        keys
1205    }
1206
1207    // centre the view on a node (or the middle of an edge) that is not fully shown
1208    fn reveal(&mut self, m: &DModel, key: u64) {
1209        let r = match self.node(m, key) {
1210            Some(n) => xyxy(n.rect),
1211            None => match self.route(key) {
1212                Some(rt) if rt.pts.len() >= 2 => {
1213                    let c = dg_point_at(&rt.pts, 0.5);
1214                    [c[0] - 8.0, c[1] - 8.0, c[0] + 8.0, c[1] + 8.0]
1215                }
1216                _ => return,
1217            },
1218        };
1219        if !inside(r, self.vp.world_rect()) {
1220            self.vp.cx = (r[0] as f64 + r[2] as f64) * 0.5;
1221            self.vp.cy = (r[1] as f64 + r[3] as f64) * 0.5;
1222        }
1223    }
1224
1225    fn movable_sel(&self, m: &DModel) -> Vec<u64> {
1226        self.sel.iter().copied().filter(|&k| self.node(m, k).is_some_and(|n| n.flags & DN_LOCKED == 0)).collect()
1227    }
1228}
1229
1230// the input of one diagram call
1231struct In {
1232    tool: u32,
1233    mods: u32,
1234    space: bool,
1235    ev: u32,
1236    px: [i32; 2],
1237    p: [f32; 2],
1238    btn: u32,
1239    pressed: u32,
1240    mine: bool,
1241    lim: i32,
1242    now: u32,
1243    dbl: u32,
1244}
1245
1246// ------------------------------------------------------------------ index and routes
1247
1248fn cells_of(r: [f32; 4]) -> (i32, i32, i32, i32) {
1249    (fl(r[0] / CELL), fl(r[1] / CELL), fl(r[2] / CELL), fl(r[3] / CELL))
1250}
1251
1252impl Index {
1253    // rebuild for another model version -> rebuilt
1254    fn update(&mut self, m: &DModel) -> bool {
1255        let counts = [m.nodes.len(), m.ports.len(), m.edges.len(), m.wps.len()];
1256        if self.built && self.ver == m.ver && self.counts == counts {
1257            return false;
1258        }
1259        self.built = true;
1260        self.ver = m.ver;
1261        self.counts = counts;
1262        self.nodes.clear();
1263        self.ports.clear();
1264        self.edges.clear();
1265        for (i, n) in m.nodes.iter().enumerate() {
1266            self.nodes.insert(n.key, i as u32);
1267            for p in range_of(&n.ports, m.ports.len()) {
1268                self.ports.insert(m.ports[p].key, (i as u32, p as u32));
1269            }
1270        }
1271        for (i, e) in m.edges.iter().enumerate() {
1272            self.edges.insert(e.key, i as u32);
1273        }
1274        let mut geo = DgGeo { routes: Vec::with_capacity(m.edges.len()), counts: (m.nodes.len(), m.edges.len()), ..DgGeo::default() };
1275        for e in m.edges {
1276            let mut r = DgRoute::default();
1277            route(m, Some(self), e, &mut r);
1278            geo.routes.push(r);
1279        }
1280        let mut b = None;
1281        for (i, n) in m.nodes.iter().enumerate() {
1282            let r = xyxy(n.rect);
1283            geo.put(r, i as u32, false);
1284            b = union(b, r);
1285        }
1286        for i in 0..geo.routes.len() {
1287            let rt = &geo.routes[i];
1288            if rt.pts.len() >= 2 {
1289                let bb = rt.bb;
1290                geo.put(bb, i as u32, true);
1291                b = union(b, bb);
1292            }
1293        }
1294        self.bounds = b;
1295        self.geo = Rc::new(geo);
1296        true
1297    }
1298
1299    // the node (or edge) indices in the buckets touching r (x0, y0, x1, y1), ascending; all
1300    // of them when r spans too many buckets
1301    fn query(&self, r: [f32; 4], edge: bool, all: usize, out: &mut Vec<u32>) {
1302        self.geo.find_in(r, edge, all, out);
1303    }
1304}
1305
1306impl DgGeo {
1307    fn put(&mut self, r: [f32; 4], i: u32, edge: bool) {
1308        let (c0, r0, c1, r1) = cells_of(r);
1309        let n = (c1 as i64 - c0 as i64 + 1) * (r1 as i64 - r0 as i64 + 1);
1310        if !(1..=SPAN_MAX).contains(&n) {
1311            if edge { self.big.1.push(i) } else { self.big.0.push(i) }
1312            return;
1313        }
1314        for y in r0..=r1 {
1315            for x in c0..=c1 {
1316                let c = self.cells.entry((y, x)).or_default();
1317                if edge { c.1.push(i) } else { c.0.push(i) }
1318            }
1319        }
1320    }
1321
1322    /// the indices of the nodes (edges = false) or edges whose buckets touch r (world x0, y0,
1323    /// x1, y1), ascending: a superset of what overlaps it (all when r spans too many buckets)
1324    pub fn find(&self, r: [f32; 4], edges: bool, out: &mut Vec<u32>) {
1325        let all = if edges { self.counts.1 } else { self.counts.0 };
1326        self.find_in(r, edges, all, out);
1327    }
1328
1329    fn find_in(&self, r: [f32; 4], edge: bool, all: usize, out: &mut Vec<u32>) {
1330        out.clear();
1331        let (c0, r0, c1, r1) = cells_of(r);
1332        let n = (c1 as i64 - c0 as i64 + 1) * (r1 as i64 - r0 as i64 + 1);
1333        if !(1..=CELLS_MAX).contains(&n) {
1334            out.extend(0..all as u32);
1335            return;
1336        }
1337        for y in r0..=r1 {
1338            for (_, c) in self.cells.range((y, c0)..=(y, c1)) {
1339                out.extend_from_slice(if edge { &c.1 } else { &c.0 });
1340            }
1341        }
1342        out.extend_from_slice(if edge { &self.big.1 } else { &self.big.0 });
1343        out.sort_unstable();
1344        out.dedup();
1345    }
1346}
1347
1348// an end of an edge: a port (point, side, node) or a whole node (rect, node)
1349#[derive(Clone, Copy)]
1350enum Ep {
1351    Port([f32; 2], u8),
1352    Node([f32; 4]),
1353}
1354
1355fn ep_of(m: &DModel, ix: Option<&Index>, key: u64) -> Option<Ep> {
1356    let (ni, pi) = match ix {
1357        Some(ix) => match ix.ports.get(&key) {
1358            Some(&(n, p)) => (n as usize, Some(p as usize)),
1359            None => (*ix.nodes.get(&key)? as usize, None),
1360        },
1361        None => {
1362            let mut f = None;
1363            for (i, n) in m.nodes.iter().enumerate() {
1364                if n.key == key {
1365                    f = Some((i, None));
1366                    break;
1367                }
1368                if let Some(p) = range_of(&n.ports, m.ports.len()).find(|&p| m.ports[p].key == key) {
1369                    f = Some((i, Some(p)));
1370                    break;
1371                }
1372            }
1373            f?
1374        }
1375    };
1376    let n = m.nodes.get(ni)?;
1377    match pi {
1378        Some(p) => {
1379            let q = m.ports.get(p)?;
1380            Some(Ep::Port([n.rect[0] + q.at[0], n.rect[1] + q.at[1]], port_side(n.rect, q.at)))
1381        }
1382        None => Some(Ep::Node(n.rect)),
1383    }
1384}
1385
1386// the side of the node a port sits on (the nearest edge of the rectangle)
1387fn port_side(r: [f32; 4], at: [f32; 2]) -> u8 {
1388    let d = [fabs(at[0]), fabs(r[2] - at[0]), fabs(at[1]), fabs(r[3] - at[1])];
1389    let mut s = 0;
1390    for k in 1..4 {
1391        if d[k] < d[s] {
1392            s = k;
1393        }
1394    }
1395    [S_L, S_R, S_T, S_B][s]
1396}
1397
1398fn side_vec(s: u8) -> [f32; 2] {
1399    match s {
1400        S_L => [-1.0, 0.0],
1401        S_R => [1.0, 0.0],
1402        S_T => [0.0, -1.0],
1403        S_B => [0.0, 1.0],
1404        _ => [0.0, 0.0],
1405    }
1406}
1407
1408fn ep_point(e: Ep) -> [f32; 2] {
1409    match e {
1410        Ep::Port(p, _) => p,
1411        Ep::Node(r) => [r[0] + r[2] * 0.5, r[1] + r[3] * 0.5],
1412    }
1413}
1414
1415// where a route leaves an end towards `toward`: a port's point and side; a node's border
1416// (orthogonal: the middle of the side facing it)
1417fn anchor(e: Ep, toward: [f32; 2], ortho: bool) -> ([f32; 2], u8) {
1418    match e {
1419        Ep::Port(p, s) => (p, s),
1420        Ep::Node(r) => {
1421            let c = [r[0] + r[2] * 0.5, r[1] + r[3] * 0.5];
1422            let (hw, hh) = (r[2] * 0.5, r[3] * 0.5);
1423            let v = [toward[0] - c[0], toward[1] - c[1]];
1424            if fabs(v[0]) < EPS && fabs(v[1]) < EPS {
1425                return (c, S_NONE);
1426            }
1427            let horiz = fabs(v[0]) * hh >= fabs(v[1]) * hw;
1428            let s = if horiz {
1429                if v[0] < 0.0 { S_L } else { S_R }
1430            } else if v[1] < 0.0 {
1431                S_T
1432            } else {
1433                S_B
1434            };
1435            if ortho {
1436                let d = side_vec(s);
1437                return ([c[0] + d[0] * hw, c[1] + d[1] * hh], s);
1438            }
1439            let kx = if fabs(v[0]) > EPS { hw / fabs(v[0]) } else { f32::MAX };
1440            let ky = if fabs(v[1]) > EPS { hh / fabs(v[1]) } else { f32::MAX };
1441            let k = kx.min(ky);
1442            ([c[0] + v[0] * k, c[1] + v[1] * k], s)
1443        }
1444    }
1445}
1446
1447// the route of edge e into out (empty when an end is unknown)
1448fn route(m: &DModel, ix: Option<&Index>, e: &DEdge, out: &mut DgRoute) {
1449    out.key = e.key;
1450    out.pts.clear();
1451    out.marks.clear();
1452    out.bb = [0.0; 4];
1453    let (Some(ea), Some(eb)) = (ep_of(m, ix, e.a), ep_of(m, ix, e.b)) else { return };
1454    let wps = &m.wps[range_of(&e.wps, m.wps.len())];
1455    let ta = wps.first().copied().unwrap_or(ep_point(eb));
1456    let tb = wps.last().copied().unwrap_or(ep_point(ea));
1457    let ortho = e.route == DR_ORTHO;
1458    let (pa, sa) = anchor(ea, ta, ortho);
1459    let (pb, sb) = anchor(eb, tb, ortho);
1460    match e.route {
1461        DR_BEZIER => bezier(pa, sa, wps, pb, sb, out),
1462        DR_ORTHO => ortho_route(pa, sa, wps, pb, sb, out),
1463        _ => {
1464            out.pts.push(pa);
1465            out.pts.extend_from_slice(wps);
1466            out.pts.push(pb);
1467            out.marks.extend(0..out.pts.len() as u32);
1468        }
1469    }
1470    let mut bb = [f32::MAX, f32::MAX, f32::MIN, f32::MIN];
1471    for p in &out.pts {
1472        bb = [bb[0].min(p[0]), bb[1].min(p[1]), bb[2].max(p[0]), bb[3].max(p[1])];
1473    }
1474    out.bb = bb;
1475}
1476
1477// a smooth curve through the ends and the waypoints (Catmull-Rom; the ends leave along the
1478// ports' sides), flattened
1479fn bezier(pa: [f32; 2], sa: u8, wps: &[[f32; 2]], pb: [f32; 2], sb: u8, out: &mut DgRoute) {
1480    let mut p = Vec::with_capacity(wps.len() + 2);
1481    p.push(pa);
1482    p.extend_from_slice(wps);
1483    p.push(pb);
1484    let n = p.len();
1485    // the control points of every stretch
1486    let tan = |i: usize| -> [f32; 2] {
1487        let (a, b) = (p[i.saturating_sub(1)], p[(i + 1).min(n - 1)]);
1488        let k = if i == 0 || i == n - 1 { 1.0 / 3.0 } else { 1.0 / 6.0 };
1489        [(b[0] - a[0]) * k, (b[1] - a[1]) * k]
1490    };
1491    out.pts.push(pa);
1492    out.marks.push(0);
1493    for i in 0..n - 1 {
1494        let (p0, p3) = (p[i], p[i + 1]);
1495        let reach = (dist(p0, p3) * 0.5).clamp(12.0, 200.0);
1496        let c1 = if i == 0 && sa != S_NONE {
1497            let v = side_vec(sa);
1498            [p0[0] + v[0] * reach, p0[1] + v[1] * reach]
1499        } else {
1500            let t = tan(i);
1501            [p0[0] + t[0], p0[1] + t[1]]
1502        };
1503        let c2 = if i == n - 2 && sb != S_NONE {
1504            let v = side_vec(sb);
1505            [p3[0] + v[0] * reach, p3[1] + v[1] * reach]
1506        } else {
1507            let t = tan(i + 1);
1508            [p3[0] - t[0], p3[1] - t[1]]
1509        };
1510        let len = dist(p0, c1) + dist(c1, c2) + dist(c2, p3);
1511        let steps = ((len / 6.0) as i32).clamp(4, 64);
1512        for s in 1..=steps {
1513            let t = s as f32 / steps as f32;
1514            let u = 1.0 - t;
1515            let (a, b, c, d) = (u * u * u, 3.0 * u * u * t, 3.0 * u * t * t, t * t * t);
1516            out.pts.push([
1517                a * p0[0] + b * c1[0] + c * c2[0] + d * p3[0],
1518                a * p0[1] + b * c1[1] + c * c2[1] + d * p3[1],
1519            ]);
1520        }
1521        out.marks.push(out.pts.len() as u32 - 1);
1522    }
1523}
1524
1525const AX_H: u8 = 0;
1526const AX_V: u8 = 1;
1527
1528fn axis_of(s: u8) -> u8 {
1529    if s == S_T || s == S_B { AX_V } else { AX_H }
1530}
1531
1532// horizontal and vertical stretches: out of the ports (STUB units), L shapes through the
1533// waypoints (each one a corner), an L or Z shape into the far port
1534fn ortho_route(pa: [f32; 2], sa: u8, wps: &[[f32; 2]], pb: [f32; 2], sb: u8, out: &mut DgRoute) {
1535    let va = side_vec(sa);
1536    let vb = side_vec(sb);
1537    let a1 = [pa[0] + va[0] * STUB, pa[1] + va[1] * STUB];
1538    let b1 = [pb[0] + vb[0] * STUB, pb[1] + vb[1] * STUB];
1539    let mut def = Vec::with_capacity(wps.len() + 2); // the points the route passes, a1 .. b1
1540    def.push(a1);
1541    def.extend_from_slice(wps);
1542    def.push(b1);
1543    // (point, defining?) -- simplified at the end
1544    let mut v: Vec<([f32; 2], bool)> = Vec::with_capacity(def.len() * 3 + 2);
1545    v.push((pa, true));
1546    v.push((a1, false));
1547    // the first stretch leaves along the port's side unless that turns back over the stub
1548    let q = def[1];
1549    let mut ax = if sa != S_NONE {
1550        let back = (q[0] - a1[0]) * va[0] + (q[1] - a1[1]) * va[1] < 0.0;
1551        if back { axis_of(sa) ^ 1 } else { axis_of(sa) }
1552    } else if fabs(q[0] - a1[0]) >= fabs(q[1] - a1[1]) {
1553        AX_H
1554    } else {
1555        AX_V
1556    };
1557    for i in 0..def.len() - 1 {
1558        let (p, q) = (def[i], def[i + 1]);
1559        let last = i + 2 == def.len();
1560        let (dx, dy) = (q[0] - p[0], q[1] - p[1]);
1561        if fabs(dx) < EPS || fabs(dy) < EPS {
1562            ax = if fabs(dx) < EPS { AX_H } else { AX_V };
1563        } else if last && sb != S_NONE {
1564            // arrive along the far port's side, coming from outside its stub
1565            let mut e = axis_of(sb);
1566            if (p[0] - b1[0]) * vb[0] + (p[1] - b1[1]) * vb[1] < 0.0 {
1567                e ^= 1;
1568            }
1569            if ax != e {
1570                v.push((if ax == AX_H { [q[0], p[1]] } else { [p[0], q[1]] }, false));
1571            } else if ax == AX_H {
1572                let mx = (p[0] + q[0]) * 0.5;
1573                v.push(([mx, p[1]], false));
1574                v.push(([mx, q[1]], false));
1575            } else {
1576                let my = (p[1] + q[1]) * 0.5;
1577                v.push(([p[0], my], false));
1578                v.push(([q[0], my], false));
1579            }
1580        } else {
1581            v.push((if ax == AX_H { [q[0], p[1]] } else { [p[0], q[1]] }, false));
1582        }
1583        v.push((q, !last));
1584    }
1585    v.push((pb, true));
1586    // drop repeated points and corners on a straight line (defining points stay)
1587    let mut s: Vec<([f32; 2], bool)> = Vec::with_capacity(v.len());
1588    for (p, d) in v {
1589        if let Some(l) = s.last_mut() {
1590            if fabs(l.0[0] - p[0]) < EPS && fabs(l.0[1] - p[1]) < EPS {
1591                l.1 |= d;
1592                continue;
1593            }
1594        }
1595        if s.len() >= 2 {
1596            let (a, b) = (s[s.len() - 2].0, s[s.len() - 1]);
1597            let straight = (fabs(a[0] - b.0[0]) < EPS && fabs(b.0[0] - p[0]) < EPS) || (fabs(a[1] - b.0[1]) < EPS && fabs(b.0[1] - p[1]) < EPS);
1598            if straight && !b.1 {
1599                s.pop();
1600            }
1601        }
1602        s.push((p, d));
1603    }
1604    for (i, (p, d)) in s.iter().enumerate() {
1605        out.pts.push(*p);
1606        if *d || i == 0 || i + 1 == s.len() {
1607            out.marks.push(i as u32);
1608        }
1609    }
1610    out.marks.dedup();
1611}
1612
1613// the nearest point of a route: (distance^2, segment, 0..1 along it)
1614fn near_route(r: &DgRoute, p: [f32; 2]) -> (f32, usize, f32) {
1615    let mut best = (f32::MAX, 0, 0.0);
1616    for j in 0..r.pts.len().saturating_sub(1) {
1617        let (d2, u) = seg_dist2(p, r.pts[j], r.pts[j + 1]);
1618        if d2 < best.0 {
1619            best = (d2, j, u);
1620        }
1621    }
1622    best
1623}
1624
1625fn seg_dist2(p: [f32; 2], a: [f32; 2], b: [f32; 2]) -> (f32, f32) {
1626    let (vx, vy) = (b[0] - a[0], b[1] - a[1]);
1627    let l2 = vx * vx + vy * vy;
1628    let u = if l2 > 0.0 { (((p[0] - a[0]) * vx + (p[1] - a[1]) * vy) / l2).clamp(0.0, 1.0) } else { 0.0 };
1629    let (dx, dy) = (a[0] + vx * u - p[0], a[1] + vy * u - p[1]);
1630    (dx * dx + dy * dy, u)
1631}
1632
1633// segment j at u -> (stretch between defining points, 0..1 along it)
1634fn seg_t(r: &DgRoute, j: usize, u: f32) -> (u32, f32) {
1635    let mut seg = 0;
1636    while seg + 2 < r.marks.len() && r.marks[seg + 1] as usize <= j {
1637        seg += 1;
1638    }
1639    let (a, b) = (r.marks.get(seg).copied().unwrap_or(0) as usize, r.marks.get(seg + 1).copied().unwrap_or(0) as usize);
1640    if b <= a || b >= r.pts.len() {
1641        return (seg as u32, 0.0);
1642    }
1643    let total = dg_length(&r.pts[a..=b]);
1644    let mut at = 0.0;
1645    for k in a..j.min(b) {
1646        at += dist(r.pts[k], r.pts[k + 1]);
1647    }
1648    if j < b {
1649        at += dist(r.pts[j], r.pts[j + 1]) * u;
1650    }
1651    (seg as u32, if total > 0.0 { (at / total).clamp(0.0, 1.0) } else { 0.0 })
1652}
1653
1654// the middle of stretch i of a route
1655fn mid_of(r: &DgRoute, i: usize) -> Option<[f32; 2]> {
1656    let (a, b) = (*r.marks.get(i)? as usize, *r.marks.get(i + 1)? as usize);
1657    if b <= a || b >= r.pts.len() {
1658        return None;
1659    }
1660    Some(dg_point_at(&r.pts[a..=b], 0.5))
1661}
1662
1663/// the length of a polyline
1664pub fn dg_length(pts: &[[f32; 2]]) -> f32 {
1665    pts.windows(2).map(|w| dist(w[0], w[1])).sum()
1666}
1667
1668/// the point at fraction f (0..1) of a polyline's length (a packet travelling along an edge)
1669pub fn dg_point_at(pts: &[[f32; 2]], f: f32) -> [f32; 2] {
1670    let Some(&first) = pts.first() else { return [0.0; 2] };
1671    let total = dg_length(pts);
1672    if !(total > 0.0) {
1673        return first;
1674    }
1675    let mut left = total * f.clamp(0.0, 1.0);
1676    for w in pts.windows(2) {
1677        let l = dist(w[0], w[1]);
1678        if left <= l && l > 0.0 {
1679            let u = left / l;
1680            return [w[0][0] + (w[1][0] - w[0][0]) * u, w[0][1] + (w[1][1] - w[0][1]) * u];
1681        }
1682        left -= l;
1683    }
1684    pts[pts.len() - 1]
1685}
1686
1687/// the world box x, y, w, h of a DN_TOGGLE node's collapse button: the square of the title bar's
1688/// height `bar` at its right end (a DN_FRAME node), else a square of that side (at most the
1689/// node's) in the top right corner; apps paint the button there
1690pub fn dg_toggle_rect(n: &DNode, bar: f32) -> [f32; 4] {
1691    let r = n.rect;
1692    let s = if n.flags & DN_FRAME != 0 { bar.min(r[3]) } else { bar.min(r[2]).min(r[3]) };
1693    [r[0] + r[2] - s, r[1], s, s]
1694}
1695
1696/// the route of edge i of m (world polyline) into out, without a DiagramState (ports and
1697/// nodes are looked up linearly; DiagramState::route has them indexed)
1698pub fn dg_edge_points(m: &DModel, i: usize, out: &mut Vec<[f32; 2]>) {
1699    out.clear();
1700    let Some(e) = m.edges.get(i) else { return };
1701    let mut r = DgRoute::default();
1702    route(m, None, e, &mut r);
1703    out.extend_from_slice(&r.pts);
1704}
1705
1706// ------------------------------------------------------------------ painting helpers
1707
1708/// grid lines every `step` world units (every fifth in `major`) inside the clip and the
1709/// canvas; steps closer than 8 px are widened five times
1710pub fn dg_paint_grid(g: &mut Gfx, vp: &Viewport, step: f32, minor: u32, major: u32) {
1711    if !(step > 0.0) {
1712        return;
1713    }
1714    let s = vp.scale() as f32;
1715    let mut st = step;
1716    let mut k = 0;
1717    while st * s < 8.0 && k < 12 {
1718        st *= 5.0;
1719        k += 1;
1720    }
1721    let r = vp.rect;
1722    let c = g.dc.clip;
1723    let (x0, y0) = (c[0].max(r[0]), c[1].max(r[1]));
1724    let (x1, y1) = (c[2].min(r[0] + r[2]), c[3].min(r[1] + r[3]));
1725    if x1 <= x0 || y1 <= y0 {
1726        return;
1727    }
1728    let a = vp.to_world(x0 as f32, y0 as f32);
1729    let b = vp.to_world(x1 as f32, y1 as f32);
1730    let hair = ((vp.dpi + 24) / 96).max(1);
1731    let (i0, i1) = (fl(a[0] / st), fl(b[0] / st) + 1);
1732    let (j0, j1) = (fl(a[1] / st), fl(b[1] / st) + 1);
1733    if i1 as i64 - i0 as i64 > 4000 || j1 as i64 - j0 as i64 > 4000 {
1734        return;
1735    }
1736    for pass in 0..2 {
1737        // the minor lines first, the major ones over them
1738        let want = pass == 1;
1739        g.dc.set_color(if want { major } else { minor });
1740        for i in i0..=i1 {
1741            if (i.rem_euclid(5) == 0) != want {
1742                continue;
1743            }
1744            let x = fl(vp.to_screen([i as f32 * st, 0.0])[0]);
1745            g.dc.fill(g.sf, x, y0, hair, y1 - y0);
1746        }
1747        for j in j0..=j1 {
1748            if (j.rem_euclid(5) == 0) != want {
1749                continue;
1750            }
1751            let y = fl(vp.to_screen([0.0, j as f32 * st])[1]);
1752            g.dc.fill(g.sf, x0, y, x1 - x0, hair);
1753        }
1754    }
1755}
1756
1757/// stroke a route (world points) `width` world units wide (at least 1 px) in argb, with
1758/// gfx::dash caps (LCAP_*: arrows at an end) and pattern DASH_*; nothing when it misses the
1759/// clip
1760#[allow(clippy::too_many_arguments)]
1761pub fn dg_stroke_edge(g: &mut Gfx, vp: &Viewport, pts: &[[f32; 2]], width: f32, argb: u32, cap0: u32, cap1: u32, dash: u32) {
1762    if pts.len() < 2 {
1763        return;
1764    }
1765    let w = (width * vp.scale() as f32).max(1.0);
1766    let mut bb = [f32::MAX, f32::MAX, f32::MIN, f32::MIN];
1767    let path = &mut g.text.path;
1768    path.reset();
1769    path.set_origin(0, 0);
1770    for (i, p) in pts.iter().enumerate() {
1771        let q = vp.to_screen(*p);
1772        bb = [bb[0].min(q[0]), bb[1].min(q[1]), bb[2].max(q[0]), bb[3].max(q[1])];
1773        if i == 0 {
1774            path.move_f(q[0], q[1]);
1775        } else {
1776            path.line_f(q[0], q[1]);
1777        }
1778    }
1779    let m = w * 4.0 + 6.0; // half the width and an arrow head
1780    let c = g.dc.clip;
1781    if bb[2] + m < c[0] as f32 || bb[0] - m > c[2] as f32 || bb[3] + m < c[1] as f32 || bb[1] - m > c[3] as f32 {
1782        path.reset();
1783        return;
1784    }
1785    let st = DStyle { width: w as f64, pat: dash_pattern(dash), join: gfx::JOIN_ROUND, cap0, cap1, dcap: LCAP_FLAT, offset: 0.0, miter: 0.0 };
1786    dash_stroke(path, &st);
1787    g.dc.set_color(argb);
1788    path.fill(&mut g.text.ras, g.dc, g.sf, gfx::RULE_NONZERO);
1789}
1790
1791// the canvas as painted last: Wrap paints the whole canvas into it once per paint hash and
1792// copies every dirty rectangle out of it (absolute px: the surface reaches from the window's
1793// corner to the canvas' far corner)
1794#[derive(Default)]
1795struct Backing {
1796    hash: u64,
1797    sf: Surface,
1798    // what it holds, in full detail or not, through which view, in which rectangle
1799    content: u64,
1800    full: bool,
1801    view: CanvasView,
1802    rect: [i32; 4],
1803}
1804
1805/// a route (world points) as 1 px anti-aliased lines in argb (Xiaolin Wu): a fraction of
1806/// dg_stroke_edge's cost for many thin edges (the level of detail of DgFrame::moving); nothing
1807/// outside the clip
1808pub fn dg_hairline(g: &mut Gfx, vp: &Viewport, pts: &[[f32; 2]], argb: u32) {
1809    if pts.len() < 2 || g.sf.fmt == gfx::FMT_A8 {
1810        return;
1811    }
1812    g.dc.set_color(argb);
1813    let mut a = vp.to_screen(pts[0]);
1814    for &q in &pts[1..] {
1815        let b = vp.to_screen(q);
1816        wu_line(g, a, b);
1817        a = b;
1818    }
1819}
1820
1821// one anti-aliased line between two px points in the Dc's colour, inside its clip: per column
1822// (row when steep) the two pixels next to the line share its coverage (16.16 fixed point)
1823fn wu_line(g: &mut Gfx, a: [f32; 2], b: [f32; 2]) {
1824    let c = g.dc.clip;
1825    let (ox, oy) = (g.dc.ox as f32, g.dc.oy as f32);
1826    let (mut x0, mut y0, mut x1, mut y1) = (a[0] + ox, a[1] + oy, b[0] + ox, b[1] + oy);
1827    let lim = 1.0e6;
1828    if !(fabs(x0) < lim && fabs(y0) < lim && fabs(x1) < lim && fabs(y1) < lim) {
1829        return;
1830    }
1831    let steep = fabs(y1 - y0) > fabs(x1 - x0);
1832    if steep {
1833        core::mem::swap(&mut x0, &mut y0);
1834        core::mem::swap(&mut x1, &mut y1);
1835    }
1836    if x0 > x1 {
1837        core::mem::swap(&mut x0, &mut x1);
1838        core::mem::swap(&mut y0, &mut y1);
1839    }
1840    // the clip along the line's main axis and across it
1841    let (lo, hi, mlo, mhi) = if steep { (c[1], c[3], c[0], c[2]) } else { (c[0], c[2], c[1], c[3]) };
1842    let dx = x1 - x0;
1843    let grad = if dx > 1e-6 { (y1 - y0) / dx } else { 0.0 };
1844    // the columns whose centres lie on the line
1845    let i0 = fl(x0 + 0.499).max(lo);
1846    let i1 = fl(x1 - 0.5).min(hi - 1);
1847    if i1 < i0 {
1848        return;
1849    }
1850    // the line at column i0's centre, measured from the pixel centres across it
1851    let mut y = ((y0 + grad * (i0 as f32 + 0.5 - x0) - 0.5) * 65536.0) as i64;
1852    let step = (grad * 65536.0) as i64;
1853    let (pcol, stride, w) = (g.dc.pcol, g.sf.stride as i64, g.sf.w);
1854    let px = &mut g.sf.px;
1855    for i in i0..=i1 {
1856        let j = (y >> 16) as i32;
1857        let f = ((y >> 8) & 0xFF) as u32;
1858        for (jj, cov) in [(j, 255 - f), (j + 1, f)] {
1859            if jj < mlo || jj >= mhi || cov == 0 {
1860                continue;
1861            }
1862            let (x, yy) = if steep { (jj, i) } else { (i, jj) };
1863            if x < 0 || x >= w || yy < 0 {
1864                continue;
1865            }
1866            let k = (yy as i64 * stride + x as i64 * 4) as usize;
1867            let Some(p) = px.get_mut(k..k + 4) else { continue };
1868            let ia = 255 - gfx::div255(pcol[3] * cov);
1869            for ch in 0..4 {
1870                p[ch] = (gfx::div255(pcol[ch] * cov) + gfx::div255(p[ch] as u32 * ia)).min(255) as u8;
1871            }
1872        }
1873        y += step;
1874    }
1875}
1876
1877// the render-time object of the canvas: background, grid, the app's paint
1878struct Wrap {
1879    paint: Option<Rc<dyn DgPaint>>,
1880    vp: Viewport,
1881    geo: Rc<DgGeo>,
1882    theme: [u32; THEME_N],
1883    fonts: [(u32, i32); 7],
1884    grid: f32,
1885    dark: bool,
1886    moving: bool,
1887    // the view or nodes move now: a small part is painted directly, the store stays
1888    direct: bool,
1889    hash: u64,
1890    content: u64,
1891    store: Rc<RefCell<Backing>>,
1892}
1893
1894impl Draw for Wrap {
1895    fn draw(&self, g: &mut Gfx, r: [i32; 4]) {
1896        let k = g.dc.clip;
1897        if k[2] <= k[0] || k[3] <= k[1] {
1898            return;
1899        }
1900        let v = self.vp.rect;
1901        let (w, h) = (v[0].max(0) + v[2], v[1].max(0) + v[3]);
1902        let Ok(mut b) = self.store.try_borrow_mut() else {
1903            self.paint_all(g, r);
1904            return;
1905        };
1906        let small = ((k[2] - k[0]) as i64 * (k[3] - k[1]) as i64) * 2 < v[2] as i64 * v[3] as i64;
1907        if self.direct && small && b.hash != self.hash {
1908            self.paint_all(g, r);
1909            return;
1910        }
1911        if b.hash != self.hash || b.sf.w != w || b.sf.h != h || b.sf.px.is_empty() {
1912            if b.sf.w != w || b.sf.h != h || b.sf.px.is_empty() {
1913                b.sf = Surface::new(w, h, FMT_XRGB);
1914            }
1915            if b.sf.px.is_empty() {
1916                // no memory for it: paint the dirty rectangle straight away
1917                b.hash = 0;
1918                drop(b);
1919                self.paint_all(g, r);
1920                return;
1921            }
1922            let Backing { hash, sf, content, full, view, rect } = &mut *b;
1923            g.dc.begin(sf);
1924            g.dc.clip_push(v[0], v[1], v[2], v[3]);
1925            self.paint_all(&mut Gfx { dc: &mut *g.dc, sf, text: &mut *g.text }, v);
1926            g.dc.end();
1927            *hash = self.hash;
1928            *content = self.content;
1929            *full = !self.moving;
1930            *view = view_of(&self.vp);
1931            *rect = v;
1932        }
1933        // a canvas the window moved (a mirrored window's island) shows the same pixels there
1934        g.dc.blit(g.sf, &b.sf, r[0] - v[0], r[1] - v[1]);
1935    }
1936}
1937
1938// the canvas's content through a viewport: world point (u, v) at (ox + u * scale, oy + v * scale)
1939fn view_of(vp: &Viewport) -> CanvasView {
1940    let s = vp.scale();
1941    let (mx, my) = vp.mid();
1942    CanvasView { scale: s, ox: mx - vp.cx * s, oy: my - vp.cy * s }
1943}
1944
1945impl CanvasContent for Wrap {
1946    // a tile: as far as the backing store holds it in full detail at this scale, whole pixels away
1947    // (a gesture's first tiles: no paint), its pixels; the rest painted
1948    fn draw_view(&self, g: &mut Gfx, t: CanvasView, rect: [i32; 4]) {
1949        let dpi = self.vp.dpi.max(1) as f64;
1950        let mut vp = self.vp;
1951        vp.rect = rect;
1952        vp.zoom = (t.scale * 96.0 / dpi) as f32;
1953        let (mx, my) = vp.mid();
1954        vp.cx = (mx - t.ox) / t.scale;
1955        vp.cy = (my - t.oy) / t.scale;
1956        let mut cut = None;
1957        if let Ok(b) = self.store.try_borrow() {
1958            let (dx, dy) = (b.view.ox - t.ox, b.view.oy - t.oy);
1959            let (rx, ry) = (fl((dx + 0.5) as f32) as f64, fl((dy + 0.5) as f32) as f64);
1960            let usable = b.full && b.content == self.content && !b.sf.px.is_empty() && b.view.scale == t.scale;
1961            if usable && (dx - rx).abs() < 1.0 / 64.0 && (dy - ry).abs() < 1.0 / 64.0 {
1962                let (rx, ry) = (rx as i32, ry as i32);
1963                let s = b.rect;
1964                let (x0, y0) = ((s[0] - rx).max(rect[0]), (s[1] - ry).max(rect[1]));
1965                let (x1, y1) = ((s[0] + s[2] - rx).min(rect[0] + rect[2]), (s[1] + s[3] - ry).min(rect[1] + rect[3]));
1966                if x0 < x1 && y0 < y1 {
1967                    g.dc.clip_push(x0, y0, x1 - x0, y1 - y0);
1968                    g.dc.blit(g.sf, &b.sf, -rx, -ry);
1969                    g.dc.clip_pop();
1970                    cut = Some([x0, y0, x1, y1]);
1971                }
1972            }
1973        }
1974        let Some([x0, y0, x1, y1]) = cut else {
1975            self.paint_view(g, &vp, rect, false);
1976            return;
1977        };
1978        let (rx1, ry1) = (rect[0] + rect[2], rect[1] + rect[3]);
1979        for s in [[rect[0], rect[1], rect[2], y0 - rect[1]], [rect[0], y1, rect[2], ry1 - y1], [rect[0], y0, x0 - rect[0], y1 - y0], [x1, y0, rx1 - x1, y1 - y0]] {
1980            if s[2] > 0 && s[3] > 0 {
1981                g.dc.clip_push(s[0], s[1], s[2], s[3]);
1982                self.paint_view(g, &vp, rect, false);
1983                g.dc.clip_pop();
1984            }
1985        }
1986    }
1987}
1988
1989impl Wrap {
1990    // the canvas in r (the clip says what needs pixels)
1991    fn paint_all(&self, g: &mut Gfx, r: [i32; 4]) {
1992        self.paint_view(g, &self.vp, r, self.moving);
1993    }
1994
1995    // background, the app's paint under the grid, grid and the app's paint in r through vp
1996    fn paint_view(&self, g: &mut Gfx, vp: &Viewport, r: [i32; 4], moving: bool) {
1997        let k = g.dc.clip;
1998        let bg = canvas_bg(&self.theme);
1999        g.dc.set_color(bg);
2000        g.dc.fill(g.sf, r[0], r[1], r[2], r[3]);
2001        let a = vp.to_world(k[0] as f32, k[1] as f32);
2002        let b = vp.to_world(k[2] as f32, k[3] as f32);
2003        let f = DgFrame { vp: *vp, clip: [a[0], a[1], b[0], b[1]], geo: &self.geo, theme: &self.theme, bg, fonts: self.fonts, dark: self.dark, moving };
2004        // what lies under the grid (a backdrop image)
2005        if let Some(p) = &self.paint {
2006            p.paint_under(g, &f);
2007        }
2008        if self.grid > 0.0 {
2009            let div = self.theme[(T_DIVIDER / 4) as usize];
2010            let minor = over(bg, (div & 0x00FF_FFFF) | ((div >> 25) << 24));
2011            dg_paint_grid(g, vp, self.grid, minor, over(bg, div));
2012        }
2013        if let Some(p) = &self.paint {
2014            p.paint(g, &f);
2015        }
2016    }
2017}
2018
2019// the overview: node rectangles (absolute px) in two colours, filled or (frames, outlines) as
2020// a 1 px frame
2021struct Mini {
2022    rects: Vec<([i32; 4], bool, bool)>,
2023    node: u32,
2024    sel: u32,
2025}
2026
2027impl Draw for Mini {
2028    fn draw(&self, g: &mut Gfx, _r: [i32; 4]) {
2029        for (r, s, outline) in &self.rects {
2030            g.dc.set_color(if *s { self.sel } else { self.node });
2031            if *outline {
2032                g.dc.frame(g.sf, r[0], r[1], r[2], r[3]);
2033            } else {
2034                g.dc.fill(g.sf, r[0], r[1], r[2], r[3]);
2035            }
2036        }
2037    }
2038}
2039
2040fn canvas_bg(theme: &[u32]) -> u32 {
2041    theme[(T_BG / 4) as usize] | 0xFF00_0000
2042}
2043
2044// c (straight alpha) over the opaque bg -> opaque
2045fn over(bg: u32, c: u32) -> u32 {
2046    color_mix(bg | 0xFF00_0000, c | 0xFF00_0000, ((c >> 24) * 256 / 255) as i32)
2047}
2048
2049struct Fnv(u64);
2050
2051impl Fnv {
2052    fn new() -> Fnv {
2053        Fnv(0xCBF2_9CE4_8422_2325)
2054    }
2055    fn u64(&mut self, v: u64) {
2056        for b in v.to_le_bytes() {
2057            self.0 ^= b as u64;
2058            self.0 = self.0.wrapping_mul(0x0000_0100_0000_01B3);
2059        }
2060    }
2061}
2062
2063// ------------------------------------------------------------------ small maths
2064
2065fn fabs(x: f32) -> f32 {
2066    if x < 0.0 { -x } else { x }
2067}
2068
2069// floor for any float (non-finite: 0)
2070fn fl(x: f32) -> i32 {
2071    if x.is_finite() { floor_i(x.clamp(-1.0e9, 1.0e9)) } else { 0 }
2072}
2073
2074fn snapv(v: f32, g: f32) -> f32 {
2075    fl(v / g + 0.5) as f32 * g
2076}
2077
2078fn dist(a: [f32; 2], b: [f32; 2]) -> f32 {
2079    let (dx, dy) = (a[0] - b[0], a[1] - b[1]);
2080    sqrt(dx * dx + dy * dy)
2081}
2082
2083// x, y, w, h -> x0, y0, x1, y1
2084fn xyxy(r: [f32; 4]) -> [f32; 4] {
2085    [r[0], r[1], r[0] + r[2], r[1] + r[3]]
2086}
2087
2088fn overlaps(a: [f32; 4], b: [f32; 4]) -> bool {
2089    a[0] <= b[2] && b[0] <= a[2] && a[1] <= b[3] && b[1] <= a[3]
2090}
2091
2092// a inside b (both x0, y0, x1, y1)
2093fn inside(a: [f32; 4], b: [f32; 4]) -> bool {
2094    a[0] >= b[0] && a[1] >= b[1] && a[2] <= b[2] && a[3] <= b[3]
2095}
2096
2097fn union(a: Option<[f32; 4]>, b: [f32; 4]) -> Option<[f32; 4]> {
2098    Some(match a {
2099        None => b,
2100        Some(a) => [a[0].min(b[0]), a[1].min(b[1]), a[2].max(b[2]), a[3].max(b[3])],
2101    })
2102}
2103
2104fn range_of(r: &Range<u32>, len: usize) -> Range<usize> {
2105    let a = (r.start as usize).min(len);
2106    let b = (r.end as usize).min(len).max(a);
2107    a..b
2108}
2109
2110// a px rectangle x, y, w, h (fractional) rounded to whole px
2111fn ipx(r: [f32; 4]) -> [i32; 4] {
2112    let (x0, y0) = (fl(r[0] + 0.5), fl(r[1] + 0.5));
2113    let (x1, y1) = (fl(r[0] + r[2] + 0.5), fl(r[1] + r[3] + 0.5));
2114    [x0, y0, (x1 - x0).max(1), (y1 - y0).max(1)]
2115}
2116
2117// px -> 24.8
2118fn fx(v: f32) -> i32 {
2119    (v.clamp(-4.0e6, 4.0e6) * 256.0) as i32
2120}
2121
2122// the semantic id of a node of diagram id
2123fn node_sem_id(id: u32, key: u64) -> u32 {
2124    let mut h = Fnv::new();
2125    h.u64(id as u64);
2126    h.u64(key);
2127    0x5000_0000 | (h.0 as u32 ^ (h.0 >> 32) as u32) & 0x0FFF_FFFF
2128}
2129
2130// ------------------------------------------------------------------ the widget
2131
2132impl Ui {
2133    /// ui_diagram(id, st, m, tool, paint, ver) -> what the user did: the node / edge canvas
2134    /// in the next layout slot (see the file header)
2135    #[cfg_attr(jfgui_dev, track_caller)]
2136    pub fn diagram(&mut self, id: u32, st: &mut DiagramState, m: &DModel, tool: u32, paint: Option<Rc<dyn DgPaint>>, ver: u32) -> Vec<DEv> {
2137        let _src = self.src_scope();
2138        let mut out = Vec::new();
2139        let _ = self.take_flags();
2140        let r = self.place(0, 0);
2141        let dpi = self.wp.rt.dpi;
2142        st.id = id;
2143        st.vp.rect = r;
2144        st.vp.dpi = dpi;
2145        st.vp.zoom = clampz(st.vp.zoom);
2146        if st.ix.update(m) && st.prune() {
2147            out.push(DEv::Select);
2148        }
2149        if st.fit_req && r[2] > 0 && r[3] > 0 {
2150            st.fit_req = false;
2151            match st.ix.bounds {
2152                Some(b) => st.vp.fit(b, 24),
2153                None => {
2154                    st.vp.cx = 0.0;
2155                    st.vp.cy = 0.0;
2156                }
2157            }
2158        }
2159        if st.pending_sel {
2160            st.pending_sel = false;
2161            out.push(DEv::Select);
2162        }
2163        let view0 = (st.vp.zoom, st.vp.cx, st.vp.cy);
2164        // the pointer (a canvas keeps its LTR content in a mirrored window)
2165        let c = &self.wp.ctx;
2166        let (hot, mine0) = (c.hot == id, c.active == id);
2167        let (mx, my) = (self.rtl_map(c.mx, r[0], r[2]), c.my);
2168        let inside_r = c.mx != -1 && mx >= r[0] && my >= r[1] && mx < r[0] + r[2] && my < r[1] + r[3];
2169        let mut mods = self.mods;
2170        if st.test_keys & 2 != 0 {
2171            mods |= MOD_CTRL;
2172        }
2173        let space = st.test_keys & 1 != 0 || self.key_down(VK_SPACE);
2174        let p = st.vp.to_world(mx as f32 + 0.5, my as f32 + 0.5);
2175        let track = (hot || mine0) && inside_r && !space && !matches!(st.g, Gesture::Pan { .. });
2176        st.hover = if track { st.hit(m, p, tool, dpi) } else { DHit::None };
2177        let cur = st.cursor(m, tool, space);
2178        // Enter activates; behave would take it as a click
2179        let enter = self.wp.ctx.focus == id && self.key_peek(VK_RETURN);
2180        let lim = self.dp(DRAG_DIP);
2181        let notch = self.dp(48);
2182        let ev = self.behave(id, HB_FOCUS | HB_MOVES | HB_GRAB_H | HB_GRAB_V | cur << HB_CURSOR_SHIFT);
2183        let state = self.wp.ctx.last_state;
2184        let c = &self.wp.ctx;
2185        let (now, dbl) = (self.now_ms, self.dbl_ms);
2186        let inp = In { tool, mods, space, ev, px: [mx, my], p, btn: c.mbtn, pressed: c.pressed, mine: c.active == id, lim, now, dbl };
2187        if ev & EV_HOVER != 0 && (c.wheel_x != 0 || c.wheel_y != 0) {
2188            let wx = if self.rtl { -c.wheel_x } else { c.wheel_x };
2189            st.wheel(wx, c.wheel_y, mods, [mx, my], notch);
2190            st.moving_until = now.wrapping_add(MOVING_MS);
2191        }
2192        st.step(m, &inp, &mut out);
2193        if ev & EV_RCLICK != 0 {
2194            let at = [self.wp.ctx.mx, my];
2195            st.context(st.hover, at, &mut out);
2196        }
2197        if self.wp.ctx.focus == id {
2198            self.dg_keys(st, m, enter, &mut out);
2199        }
2200        if (st.vp.zoom, st.vp.cx, st.vp.cy) != view0 {
2201            out.push(DEv::ViewChanged);
2202        }
2203        if !out.is_empty() {
2204            // the view or the selection moved: what the pointer is over now
2205            let p = st.vp.to_world(mx as f32 + 0.5, my as f32 + 0.5);
2206            st.hover = if track { st.hit(m, p, tool, dpi) } else { DHit::None };
2207            self.repass();
2208        }
2209        // drawing: the paint, then the overlays
2210        self.ltr_begin(r[0], r[2]);
2211        self.clip_push(r[0], r[1], r[2], r[3]);
2212        // a gesture that moves the view or nodes (a pan, a drag of nodes or a waypoint, wheel
2213        // turns until MOVING_MS after the last one) paints with less detail; a frame after it
2214        // paints everything again
2215        let wheel = (st.moving_until.wrapping_sub(self.now_ms) as i32) > 0;
2216        if wheel {
2217            self.animating = true;
2218        }
2219        let view_moves = wheel || matches!(st.g, Gesture::Pan { .. });
2220        let moving = view_moves || matches!(st.g, Gesture::Move { .. } | Gesture::Waypoint { .. } | Gesture::Resize { .. });
2221        let (h, obj, content) = self.dg_paint_obj(st, m, paint, ver, moving, view_moves);
2222        self.cmd_custom(r[0], r[1], r[2], r[3], Some(obj.clone()), 0, h as u32);
2223        let view = view_of(&st.vp);
2224        let gpu = st.gpu;
2225        self.canvas_gpu(CanvasDesc { id, name: "diagram", rect: r, view, ver: (content ^ content >> 32) as u32, content: obj, gpu, exact_pan: false });
2226        let ring = self.wp.ctx.focus == id && self.wp.ctx.focus_visible;
2227        self.dg_overlay(st, m, tool, ring);
2228        self.clip_pop();
2229        self.ltr_end();
2230        self.wp.ctx.last_rect = r;
2231        self.wp.ctx.last_state = state;
2232        let name = if st.name.is_empty() { None } else { Some(st.name.clone()) };
2233        self.sem(K_CANVAS, id, name.as_deref(), st.sel.len() as i32);
2234        out
2235    }
2236
2237    // an unconsumed key vk whose modifiers & mask == need -> its queue index and modifiers
2238    // (Left / Right as drawn: the canvas is not mirrored)
2239    fn dg_key_find(&self, vk: u32, need: u32, mask: u32) -> Option<(usize, u32)> {
2240        for (i, &k) in self.wp.ctx.keyq.iter().enumerate() {
2241            if (k as i32) < 0 {
2242                continue;
2243            }
2244            let mods = (k >> 16) & 0x7FFF;
2245            let mut v = k & 0xFFFF;
2246            if self.rtl && mods & (MOD_CTRL | MOD_ALT) == 0 {
2247                v = match v {
2248                    VK_LEFT => VK_RIGHT,
2249                    VK_RIGHT => VK_LEFT,
2250                    v => v,
2251                };
2252            }
2253            if v == vk && mods & mask == need {
2254                return Some((i, mods));
2255            }
2256        }
2257        None
2258    }
2259
2260    fn dg_key_take(&mut self, i: usize) {
2261        if let Some(k) = self.wp.ctx.keyq.get_mut(i) {
2262            *k |= 0x8000_0000;
2263        }
2264    }
2265
2266    // the keys of a focused diagram
2267    fn dg_keys(&mut self, st: &mut DiagramState, m: &DModel, enter: bool, out: &mut Vec<DEv>) {
2268        // a key after a click on the background: the next click is a click of its own
2269        if st.last_hit == Some(DHit::None) && !self.wp.ctx.keyq.is_empty() {
2270            st.last_hit = None;
2271        }
2272        let r = st.vp.rect;
2273        let idle = matches!(st.g, Gesture::Idle);
2274        // Esc: the gesture, else the selection
2275        if let Some((i, _)) = self.dg_key_find(VK_ESCAPE, 0, 0) {
2276            let done = st.cancel(out) || {
2277                let any = !st.sel.is_empty();
2278                if any {
2279                    st.sel.clear();
2280                    out.push(DEv::Select);
2281                }
2282                any
2283            };
2284            if done {
2285                self.dg_key_take(i);
2286            }
2287        }
2288        if !idle {
2289            return;
2290        }
2291        if let Some((i, _)) = self.dg_key_find(VK_DELETE, 0, MOD_CTRL | MOD_ALT) {
2292            if !st.sel.is_empty() {
2293                self.dg_key_take(i);
2294                out.push(DEv::Delete);
2295            }
2296        }
2297        if let Some((i, _)) = self.dg_key_find(VK_A, MOD_CTRL, MOD_CTRL | MOD_ALT) {
2298            self.dg_key_take(i);
2299            let mut all: BTreeSet<u64> = st.walk_keys(m).into_iter().collect();
2300            all.extend(st.ix.geo.routes.iter().filter(|rt| rt.pts.len() >= 2).map(|rt| rt.key));
2301            if all != st.sel {
2302                st.sel = all;
2303                out.push(DEv::Select);
2304            }
2305        }
2306        // arrows: nudge the selection, or pan
2307        for (vk, dx, dy) in [(VK_LEFT, -1.0f32, 0.0f32), (VK_RIGHT, 1.0, 0.0), (VK_UP, 0.0, -1.0), (VK_DOWN, 0.0, 1.0)] {
2308            let Some((i, km)) = self.dg_key_find(vk, 0, MOD_CTRL) else { continue };
2309            self.dg_key_take(i);
2310            let keys = st.movable_sel(m);
2311            if keys.is_empty() {
2312                let s = st.vp.scale();
2313                st.vp.cx += dx as f64 * r[2] as f64 / 8.0 / s;
2314                st.vp.cy += dy as f64 * r[3] as f64 / 8.0 / s;
2315                continue;
2316            }
2317            let base = if st.grid > 0.0 { st.grid } else { 8.0 };
2318            let k = if km & MOD_ALT != 0 {
2319                1.0
2320            } else if km & MOD_SHIFT != 0 {
2321                base * 10.0
2322            } else {
2323                base
2324            };
2325            let d = [dx * k, dy * k];
2326            out.push(DEv::Move { keys, d, total: d, done: true });
2327        }
2328        // Tab / Shift+Tab: the next node, then the edges with tab_edges (after the last one the
2329        // focus leaves the canvas)
2330        if let Some((i, km)) = self.dg_key_find(VK_TAB, 0, MOD_CTRL | MOD_ALT) {
2331            let keys = st.tab_keys(m);
2332            let pos = keys.iter().position(|&k| k == st.focus);
2333            let next = match (pos, km & MOD_SHIFT != 0) {
2334                (None, false) => keys.first().copied(),
2335                (None, true) => keys.last().copied(),
2336                (Some(p), false) => keys.get(p + 1).copied(),
2337                (Some(p), true) => p.checked_sub(1).and_then(|p| keys.get(p).copied()),
2338            };
2339            if let Some(k) = next {
2340                self.dg_key_take(i);
2341                self.wp.ctx.focus_visible = true;
2342                st.focus = k;
2343                if st.sel.len() != 1 || !st.sel.contains(&k) {
2344                    st.sel.clear();
2345                    st.sel.insert(k);
2346                    out.push(DEv::Select);
2347                }
2348                st.reveal(m, k);
2349            }
2350        }
2351        // the thing Enter and the context key act on: the focused node, else a single selection
2352        let subject = if st.focus != 0 && st.ix.nodes.contains_key(&st.focus) {
2353            DHit::Node(st.focus)
2354        } else if st.focus != 0 && st.ix.edges.contains_key(&st.focus) && st.sel.contains(&st.focus) {
2355            DHit::Edge { key: st.focus, seg: 0, t: 0.5 }
2356        } else if st.sel.len() == 1 {
2357            let k = *st.sel.iter().next().unwrap_or(&0);
2358            if st.ix.nodes.contains_key(&k) { DHit::Node(k) } else { DHit::Edge { key: k, seg: 0, t: 0.5 } }
2359        } else {
2360            DHit::None
2361        };
2362        if enter && subject != DHit::None {
2363            out.push(DEv::Activate(subject));
2364        }
2365        let ctx = self.dg_key_find(VK_APPS, 0, 0).or_else(|| self.dg_key_find(VK_F10, MOD_SHIFT, MOD_SHIFT | MOD_CTRL | MOD_ALT));
2366        if let Some((i, _)) = ctx {
2367            self.dg_key_take(i);
2368            let at = match subject {
2369                DHit::Node(k) => st.node(m, k).map(|n| st.vp.to_screen([n.rect[0] + n.rect[2] * 0.5, n.rect[1] + n.rect[3] * 0.5])),
2370                DHit::Edge { key, .. } => st.route(key).map(|rt| st.vp.to_screen(dg_point_at(&rt.pts, 0.5))),
2371                _ => None,
2372            };
2373            let at = at.map_or([r[0] + r[2] / 2, r[1] + r[3] / 2], |q| [fl(q[0]), fl(q[1])]);
2374            let at = [self.rtl_map(at[0], r[0], r[2]), at[1]];
2375            st.context(subject, at, out);
2376        }
2377        // Ctrl + plus / minus / 0: zoom around the centre, fit
2378        let (cx, cy) = (r[0] as f32 + r[2] as f32 * 0.5, r[1] as f32 + r[3] as f32 * 0.5);
2379        for (vk, dir) in [(VK_OEM_PLUS, 1), (VK_ADD, 1), (VK_OEM_MINUS, -1), (VK_SUBTRACT, -1)] {
2380            if let Some((i, _)) = self.dg_key_find(vk, MOD_CTRL, MOD_CTRL | MOD_ALT) {
2381                self.dg_key_take(i);
2382                st.vp.step(dir, cx, cy);
2383            }
2384        }
2385        for vk in [VK_0, VK_NUMPAD0] {
2386            if let Some((i, _)) = self.dg_key_find(vk, MOD_CTRL, MOD_CTRL | MOD_ALT) {
2387                self.dg_key_take(i);
2388                if let Some(b) = st.ix.bounds {
2389                    st.vp.fit(b, 24);
2390                }
2391            }
2392        }
2393    }
2394
2395    // the cached render-time object of the canvas (a new one when anything it shows changed)
2396    fn dg_paint_obj(&mut self, st: &mut DiagramState, m: &DModel, paint: Option<Rc<dyn DgPaint>>, ver: u32, direct: bool, view_moves: bool) -> (u64, Rc<Wrap>, u64) {
2397        let mut fonts = [(0u32, 0i32); 7];
2398        for (ts, f) in fonts.iter_mut().enumerate() {
2399            self.ts_font(ts as u32);
2400            *f = self.text.get_font();
2401        }
2402        let vp = st.vp;
2403        // what the canvas shows, wherever it lies
2404        let mut h = Fnv::new();
2405        h.u64(ver as u64 | (m.ver as u64) << 32);
2406        h.u64(Rc::as_ptr(&st.ix.geo) as usize as u64);
2407        h.u64(paint.as_ref().map_or(0, |p| Rc::as_ptr(p) as *const () as usize as u64));
2408        h.u64(vp.dpi as u64);
2409        h.u64(st.grid.to_bits() as u64 | (self.theme_dark as u64) << 32);
2410        for &c in self.theme.iter() {
2411            h.u64(c as u64);
2412        }
2413        for &(f, s) in &fonts {
2414            h.u64(f as u64 | (s as u32 as u64) << 32);
2415        }
2416        let content = h.0;
2417        // a move of the view that comes from tiles shows the full detail wherever the CPU has to
2418        // paint a part; else the moving view has less of it
2419        let moving = direct && !(view_moves && st.gpu != CanvasGpu::Cpu && self.gp_canvas_steady(st.id, (content ^ content >> 32) as u32));
2420        h.u64(vp.zoom.to_bits() as u64);
2421        h.u64(vp.cx.to_bits());
2422        h.u64(vp.cy.to_bits());
2423        h.u64(vp.rect[0] as u32 as u64 | (vp.rect[1] as u32 as u64) << 32);
2424        h.u64(vp.rect[2] as u32 as u64 | (vp.rect[3] as u32 as u64) << 32);
2425        h.u64(moving as u64);
2426        let hash = h.0;
2427        if let Some((k, d)) = &st.paint_obj {
2428            if *k == hash {
2429                return (hash, d.clone(), content);
2430            }
2431        }
2432        let d = Rc::new(Wrap { paint, vp, geo: st.ix.geo.clone(), theme: self.theme, fonts, grid: st.grid, dark: self.theme_dark, moving, direct, hash, content, store: st.store.clone() });
2433        st.paint_obj = Some((hash, d.clone()));
2434        (hash, d, content)
2435    }
2436
2437    // a polyline (world) as recorded lines w px wide
2438    fn dg_poly(&mut self, vp: &Viewport, pts: &[[f32; 2]], w: f32, c: u32) {
2439        if pts.len() < 2 {
2440            return;
2441        }
2442        self.draw_color(c);
2443        let wfx = fx(w);
2444        let r = vp.rect;
2445        let g = w + 2.0;
2446        let (x0, y0, x1, y1) = (r[0] as f32 - g, r[1] as f32 - g, (r[0] + r[2]) as f32 + g, (r[1] + r[3]) as f32 + g);
2447        let mut a = vp.to_screen(pts[0]);
2448        for &q in &pts[1..] {
2449            let b = vp.to_screen(q);
2450            let off = (a[0] < x0 && b[0] < x0) || (a[0] > x1 && b[0] > x1) || (a[1] < y0 && b[1] < y0) || (a[1] > y1 && b[1] > y1);
2451            if !off {
2452                self.draw_line(fx(a[0]), fx(a[1]), fx(b[0]), fx(b[1]), wfx);
2453            }
2454            a = b;
2455        }
2456    }
2457
2458    // a dashed line between two px points
2459    fn dg_dashed(&mut self, a: [f32; 2], b: [f32; 2], w: f32, c: u32) {
2460        let len = dist(a, b);
2461        if !(len > 0.5) {
2462            return;
2463        }
2464        self.draw_color(c);
2465        let (on, off) = (self.dp(6) as f32, self.dp(4) as f32);
2466        let n = ((len / (on + off)) as i32 + 1).min(400);
2467        for k in 0..n {
2468            let t0 = k as f32 * (on + off) / len;
2469            if t0 >= 1.0 {
2470                break;
2471            }
2472            let t1 = ((k as f32 * (on + off) + on) / len).min(1.0);
2473            let p = [a[0] + (b[0] - a[0]) * t0, a[1] + (b[1] - a[1]) * t0];
2474            let q = [a[0] + (b[0] - a[0]) * t1, a[1] + (b[1] - a[1]) * t1];
2475            self.draw_line(fx(p[0]), fx(p[1]), fx(q[0]), fx(q[1]), fx(w));
2476        }
2477    }
2478
2479    // a round handle / port dot at px (x, y): radius r, fill, ring t px in stroke
2480    fn dg_dot(&mut self, p: [f32; 2], r: i32, t: i32, fill: u32, stroke: u32) {
2481        let (x, y) = (fl(p[0] + 0.5) - r, fl(p[1] + 0.5) - r);
2482        self.cmd_rrect(x, y, r * 2, r * 2, r, t, fill, stroke, stroke);
2483    }
2484
2485    // selection, hover, focus, handles, ports, marquee and rubber band over the paint
2486    fn dg_overlay(&mut self, st: &mut DiagramState, m: &DModel, tool: u32, ring: bool) {
2487        let vp = st.vp;
2488        let s = vp.scale() as f32;
2489        let wr = vp.world_rect();
2490        let accent = self.color(T_ACCENT);
2491        let bg = self.color(T_BG) | 0xFF00_0000;
2492        let hair = self.hair();
2493        // the visible nodes (also for diagram_sems)
2494        let mut vis = core::mem::take(&mut st.visible);
2495        st.ix.query(wr, false, m.nodes.len(), &mut vis);
2496        vis.retain(|&i| m.nodes.get(i as usize).is_some_and(|n| overlaps(xyxy(n.rect), wr)));
2497        // selected and hovered edges
2498        let ew = (st.edge_width * s).max(1.0);
2499        let hov_edge = if let DHit::Edge { key, .. } = st.hover { key } else { 0 };
2500        let mut sel_edges = Vec::new();
2501        for &k in &st.sel {
2502            if let Some(&i) = st.ix.edges.get(&k) {
2503                sel_edges.push(i as usize);
2504            }
2505        }
2506        let geo = st.ix.geo.clone();
2507        // the focus ring of a focused edge (tab_edges): the focus colours along its route,
2508        // under its selection highlight
2509        let mut edge_ring = false;
2510        if ring && st.focus != 0 {
2511            if let Some(rt) = st.ix.edges.get(&st.focus).and_then(|&i| geo.routes.get(i as usize)) {
2512                if overlaps(rt.bb, wr) {
2513                    let (fo, fi) = (self.color(T_FOCUS_OUT), self.color(T_FOCUS_IN));
2514                    self.dg_poly(&vp, &rt.pts, ew + self.dp(8) as f32, fo);
2515                    self.dg_poly(&vp, &rt.pts, ew + self.dp(5) as f32, fi);
2516                    edge_ring = true;
2517                }
2518            }
2519        }
2520        for &i in &sel_edges {
2521            if let Some(rt) = geo.routes.get(i) {
2522                if overlaps(rt.bb, wr) {
2523                    self.dg_poly(&vp, &rt.pts, ew + self.dp(2) as f32, accent);
2524                }
2525            }
2526        }
2527        if hov_edge != 0 && !st.sel.contains(&hov_edge) && matches!(st.g, Gesture::Idle) {
2528            if let Some(rt) = st.route(hov_edge) {
2529                let c = self.color(T_ACCENT_HOVER);
2530                let pts = rt.pts.clone();
2531                self.dg_poly(&vp, &pts, ew, c);
2532            }
2533        }
2534        // nodes: selection frames, the hovered node, the focus ring
2535        let hov_node = match st.hover {
2536            DHit::Node(k) | DHit::Toggle(k) => k,
2537            DHit::Port(k) => st.node_index(k).and_then(|i| m.nodes.get(i as usize)).map_or(0, |n| n.key),
2538            _ => 0,
2539        };
2540        let (pad, t2, rad) = (self.dp(3), self.dp(2), self.dp(4));
2541        let (fo, fi) = (self.color(T_FOCUS_OUT), self.color(T_FOCUS_IN));
2542        for &i in &vis {
2543            let n = &m.nodes[i as usize];
2544            let q = ipx(vp.rect_to_screen(n.rect));
2545            if st.sel.contains(&n.key) {
2546                self.cmd_rrect(q[0] - pad, q[1] - pad, q[2] + pad * 2, q[3] + pad * 2, rad, t2, 0, accent, accent);
2547            } else if n.key == hov_node && matches!(st.g, Gesture::Idle | Gesture::Press { .. }) {
2548                let c = (accent & 0x00FF_FFFF) | 0x9000_0000;
2549                self.cmd_rrect(q[0] - pad, q[1] - pad, q[2] + pad * 2, q[3] + pad * 2, rad, hair, 0, c, c);
2550            }
2551            if ring && st.focus == n.key {
2552                let g = pad + t2 + self.dp(1);
2553                self.cmd_rrect(q[0] - g, q[1] - g, q[2] + g * 2, q[3] + g * 2, rad + t2, t2, 0, fo, fo);
2554                self.cmd_rrect(q[0] - g + t2, q[1] - g + t2, q[2] + (g - t2) * 2, q[3] + (g - t2) * 2, rad, hair, 0, fi, fi);
2555            }
2556            // the hovered collapse button (the app paints the button itself)
2557            if st.hover == DHit::Toggle(n.key) && matches!(st.g, Gesture::Idle | Gesture::Press { .. }) {
2558                let b = ipx(vp.rect_to_screen(dg_toggle_rect(n, st.frame_bar)));
2559                let c = self.color(T_SUBTLE_HOVER);
2560                self.cmd_rrect(b[0] + hair, b[1] + hair, b[2] - hair * 2, b[3] - hair * 2, self.dp(3), hair, c, accent, accent);
2561            }
2562            // the resize handle of a selected DN_RESIZE node
2563            if n.flags & DN_RESIZE != 0 && tool == DG_SELECT && st.sel.contains(&n.key) {
2564                let h = if st.hover == DHit::Resize(n.key) { self.dp(5) + hair } else { self.dp(5) };
2565                let (x, y) = (q[0] + q[2] - h, q[1] + q[3] - h);
2566                self.cmd_rrect(x, y, h * 2, h * 2, self.dp(2), hair, accent, bg, bg);
2567            }
2568        }
2569        // the canvas' own focus ring while no node (or edge) has the focus
2570        if ring && !edge_ring && !vis.iter().any(|&i| m.nodes[i as usize].key == st.focus) {
2571            let r = vp.rect;
2572            self.cmd_rrect(r[0], r[1], r[2], r[3], 0, t2, 0, fo, fo);
2573        }
2574        // the handles of selected edges
2575        if tool == DG_SELECT {
2576            let (hr, mr) = (self.dp(5), self.dp(4));
2577            for &i in &sel_edges {
2578                let (Some(e), Some(rt)) = (m.edges.get(i), geo.routes.get(i)) else { continue };
2579                if !overlaps(rt.bb, wr) {
2580                    continue;
2581                }
2582                for j in 0..rt.marks.len().saturating_sub(1) {
2583                    if let Some(q) = mid_of(rt, j) {
2584                        let hot = st.hover == DHit::Mid(e.key, j as u32);
2585                        self.dg_dot(vp.to_screen(q), if hot { mr + hair } else { mr }, hair.max(self.dp(1)), bg, accent);
2586                    }
2587                }
2588                for (j, q) in m.wps[range_of(&e.wps, m.wps.len())].iter().enumerate() {
2589                    let hot = st.hover == DHit::Waypoint(e.key, j as u32);
2590                    let p = vp.to_screen(*q);
2591                    let h = if hot { hr + hair } else { hr };
2592                    let (x, y) = (fl(p[0] + 0.5) - h, fl(p[1] + 0.5) - h);
2593                    self.cmd_rrect(x, y, h * 2, h * 2, self.dp(2), hair, accent, bg, bg);
2594                }
2595            }
2596        }
2597        // ports: of the hovered node; while connecting of the source and the target
2598        let mut port_nodes = Vec::new();
2599        let (mut hot_port, mut src_port) = (match st.hover {
2600            DHit::Port(k) => k,
2601            _ => 0,
2602        }, 0);
2603        if tool != DG_PAN && tool != DG_PLACE && hov_node != 0 {
2604            port_nodes.push(hov_node);
2605        }
2606        if let Gesture::Connect { from, over, .. } = st.g {
2607            src_port = from;
2608            hot_port = over;
2609            for k in [from, over] {
2610                if let Some(n) = st.node_index(k).and_then(|i| m.nodes.get(i as usize)) {
2611                    port_nodes.push(n.key);
2612                }
2613            }
2614        }
2615        port_nodes.sort_unstable();
2616        port_nodes.dedup();
2617        let (pr, pr2) = (self.dp(4), self.dp(6));
2618        let strong = self.color(T_STRONG_STROKE);
2619        for k in port_nodes {
2620            let Some(n) = st.node(m, k) else { continue };
2621            for q in &m.ports[range_of(&n.ports, m.ports.len())] {
2622                if q.flags & DPT_HIDDEN != 0 {
2623                    continue;
2624                }
2625                let p = vp.to_screen([n.rect[0] + q.at[0], n.rect[1] + q.at[1]]);
2626                if q.key == hot_port || q.key == src_port {
2627                    self.dg_dot(p, pr2, t2, if q.flags & DPT_USED != 0 { accent } else { bg }, accent);
2628                } else {
2629                    self.dg_dot(p, pr, hair.max(self.dp(1)), if q.flags & DPT_USED != 0 { accent } else { bg }, strong);
2630                }
2631            }
2632        }
2633        // the marquee
2634        if let Gesture::Marquee { start, cur, .. } = &st.g {
2635            let a = vp.to_screen(*start);
2636            let b = vp.to_screen(*cur);
2637            let q = ipx([a[0].min(b[0]), a[1].min(b[1]), fabs(b[0] - a[0]), fabs(b[1] - a[1])]);
2638            let fill = (accent & 0x00FF_FFFF) | 0x2400_0000;
2639            self.cmd_rrect(q[0], q[1], q[2], q[3], 0, hair, fill, accent, accent);
2640        }
2641        // the rubber band of a connection
2642        if let Gesture::Connect { from, at, over, .. } = st.g {
2643            let end = if over != 0 { ep_of(m, Some(&st.ix), over).map(|e| anchor(e, at, false).0).unwrap_or(at) } else { at };
2644            if let Some(e) = ep_of(m, Some(&st.ix), from) {
2645                let a = vp.to_screen(anchor(e, end, false).0);
2646                let b = vp.to_screen(end);
2647                self.dg_dashed(a, b, self.dp(2) as f32, accent);
2648                self.dg_dot(b, self.dp(3), 0, accent, 0);
2649            }
2650        }
2651        st.visible = vis;
2652    }
2653
2654    /// ui_diagram_minimap(id, st, m) -> the view moved: an overview of the nodes and the
2655    /// shown part in the bottom right corner of the last diagram (nothing when the canvas is
2656    /// small); pressing or dragging there centres the view on that point
2657    #[cfg_attr(jfgui_dev, track_caller)]
2658    pub fn diagram_minimap(&mut self, id: u32, st: &mut DiagramState, m: &DModel) -> bool {
2659        let _src = self.src_scope();
2660        let r = st.vp.rect;
2661        let (w, h, mg) = (self.dp(176), self.dp(112), self.dp(12));
2662        if r[2] < w + mg * 2 + self.dp(160) || r[3] < h + mg * 2 + self.dp(120) {
2663            return false;
2664        }
2665        let mr = [r[0] + r[2] - w - mg, r[1] + r[3] - h - mg, w, h];
2666        let pad = self.dp(6);
2667        let inner = [mr[0] + pad, mr[1] + pad, mr[2] - pad * 2, mr[3] - pad * 2];
2668        // the world it shows: the content and the view (kept while the pointer holds it)
2669        let held = self.wp.ctx.active == id;
2670        let ext = match (held, st.mini_ext) {
2671            (true, Some(e)) => e,
2672            _ => {
2673                let v = st.vp.world_rect();
2674                let b = st.ix.bounds.map_or(v, |b| [b[0].min(v[0]), b[1].min(v[1]), b[2].max(v[2]), b[3].max(v[3])]);
2675                let (gx, gy) = ((b[2] - b[0]) * 0.04 + 1.0, (b[3] - b[1]) * 0.04 + 1.0);
2676                let e = [b[0] - gx, b[1] - gy, b[2] + gx, b[3] + gy];
2677                st.mini_ext = Some(e);
2678                e
2679            }
2680        };
2681        let (ew, eh) = (ext[2] - ext[0], ext[3] - ext[1]);
2682        let k = (inner[2] as f32 / ew).min(inner[3] as f32 / eh);
2683        let ox = inner[0] as f32 + (inner[2] as f32 - ew * k) * 0.5;
2684        let oy = inner[1] as f32 + (inner[3] as f32 - eh * k) * 0.5;
2685        let map = |p: [f32; 2]| [ox + (p[0] - ext[0]) * k, oy + (p[1] - ext[1]) * k];
2686        // input (the hit box where the mirrored window shows it)
2687        let hx = if self.rtl { self.rtl_map(mr[0] + mr[2] - 1, r[0], r[2]) } else { mr[0] };
2688        let ev = self.hit_rect(id, hx, mr[1], mr[2], mr[3], HB_MOVES | CUR_HAND << HB_CURSOR_SHIFT);
2689        let state = self.wp.ctx.last_state;
2690        let mut moved = false;
2691        if ev & EV_HELD != 0 && k > 0.0 {
2692            let c = &self.wp.ctx;
2693            let (mx, my) = (self.rtl_map(c.mx, r[0], r[2]), c.my);
2694            let wx = ext[0] as f64 + (mx as f64 + 0.5 - ox as f64) / k as f64;
2695            let wy = ext[1] as f64 + (my as f64 + 0.5 - oy as f64) / k as f64;
2696            if wx != st.vp.cx || wy != st.vp.cy {
2697                st.vp.cx = wx;
2698                st.vp.cy = wy;
2699                moved = true;
2700                st.moving_until = self.now_ms.wrapping_add(MOVING_MS);
2701                self.repass();
2702            }
2703        }
2704        // drawing
2705        let fill = over(self.color(T_BG), self.color(T_FLYOUT));
2706        let stroke = self.color(T_FLYOUT_STROKE);
2707        let (node_c, sel_c) = (over(fill, self.color(T_TEXT3)), self.color(T_ACCENT));
2708        self.ltr_begin(r[0], r[2]);
2709        self.cmd_rrect(mr[0], mr[1], mr[2], mr[3], self.dp(6), self.hair(), fill, stroke, stroke);
2710        let mut hh = Fnv::new();
2711        hh.u64(m.ver as u64 | (node_c as u64) << 32);
2712        hh.u64(sel_c as u64 | (mr[0] as u32 as u64) << 32);
2713        hh.u64(mr[1] as u32 as u64 | (mr[2] as u32 as u64) << 32);
2714        for v in ext {
2715            hh.u64(v.to_bits() as u64);
2716        }
2717        for &s in &st.sel {
2718            hh.u64(s);
2719        }
2720        let hash = hh.0;
2721        let obj = match &st.mini_obj {
2722            Some((h, d)) if *h == hash => d.clone(),
2723            _ => {
2724                let mut rects = Vec::with_capacity(m.nodes.len());
2725                for n in m.nodes {
2726                    let a = map([n.rect[0], n.rect[1]]);
2727                    let q = [fl(a[0]), fl(a[1]), fl(n.rect[2] * k).max(1), fl(n.rect[3] * k).max(1)];
2728                    rects.push((q, st.sel.contains(&n.key), n.flags & (DN_FRAME | DN_OUTLINE) != 0));
2729                }
2730                let d: DrawRef = Rc::new(Mini { rects, node: node_c, sel: sel_c });
2731                st.mini_obj = Some((hash, d.clone()));
2732                d
2733            }
2734        };
2735        self.clip_push(inner[0], inner[1], inner[2], inner[3]);
2736        self.cmd_custom(inner[0], inner[1], inner[2], inner[3], Some(obj), 0, hash as u32);
2737        let v = st.vp.world_rect();
2738        let (a, b) = (map([v[0], v[1]]), map([v[2], v[3]]));
2739        let q = ipx([a[0], a[1], b[0] - a[0], b[1] - a[1]]);
2740        self.draw_color(sel_c);
2741        self.draw_frame(q[0], q[1], q[2], q[3], 0, self.dp(1).max(1));
2742        self.clip_pop();
2743        self.ltr_end();
2744        self.wp.ctx.last_rect = [hx, mr[1], mr[2], mr[3]];
2745        self.wp.ctx.last_state = state;
2746        self.sem(K_CANVAS, id, None, 0);
2747        moved
2748    }
2749
2750    /// ui_diagram_sems(id, st, m, label): one K_LISTITEM record per node the last diagram
2751    /// call showed (label(key), value = the node's index, selected / focused / hover states),
2752    /// cut to the canvas; a screen reader's click on one selects it
2753    #[cfg_attr(jfgui_dev, track_caller)]
2754    pub fn diagram_sems(&mut self, id: u32, st: &mut DiagramState, m: &DModel, label: &dyn Fn(u64) -> String) {
2755        let _src = self.src_scope();
2756        let r = st.vp.rect;
2757        let focused = self.wp.ctx.focus == id;
2758        let hov = match st.hover {
2759            DHit::Node(k) | DHit::Toggle(k) => k,
2760            _ => 0,
2761        };
2762        let vis = core::mem::take(&mut st.visible);
2763        for &i in &vis {
2764            let Some(n) = m.nodes.get(i as usize) else { continue };
2765            // a frame's record is its title bar (where it is hit)
2766            let nr = if n.flags & DN_FRAME != 0 { [n.rect[0], n.rect[1], n.rect[2], st.frame_bar.min(n.rect[3])] } else { n.rect };
2767            let q = ipx(st.vp.rect_to_screen(nr));
2768            let (x0, y0) = (q[0].max(r[0]), q[1].max(r[1]));
2769            let (x1, y1) = ((q[0] + q[2]).min(r[0] + r[2]), (q[1] + q[3]).min(r[1] + r[3]));
2770            if x1 <= x0 || y1 <= y0 {
2771                continue;
2772            }
2773            let mut rect = [x0, y0, x1 - x0, y1 - y0];
2774            if self.rtl {
2775                rect[0] = self.rtl_map(rect[0] + rect[2] - 1, r[0], r[2]);
2776            }
2777            let mut state = 0;
2778            if st.sel.contains(&n.key) {
2779                state |= SS_SELECTED;
2780            }
2781            if focused && st.focus == n.key {
2782                state |= SS_FOCUSED;
2783            }
2784            if hov == n.key {
2785                state |= SS_HOVER;
2786            }
2787            let sid = node_sem_id(id, n.key);
2788            if self.m.a11y.pending() && self.a11y_click(sid) {
2789                st.sel.clear();
2790                st.sel.insert(n.key);
2791                st.focus = n.key;
2792                st.pending_sel = true;
2793                self.repass();
2794            }
2795            self.last_set(rect, state as u32);
2796            let l = label(n.key);
2797            self.sem(K_LISTITEM, sid, Some(&l), i as i32);
2798        }
2799        st.visible = vis;
2800    }
2801
2802    /// ui_diagram_edge_sems(id, st, m, label): one K_LISTITEM record per edge the last diagram
2803    /// call showed (label(key); value = the edge's index; selected / focused / hover states),
2804    /// a small box around the middle of its route cut to the canvas, after the nodes' records;
2805    /// a screen reader's click on one selects the edge (and focuses it with tab_edges)
2806    #[cfg_attr(jfgui_dev, track_caller)]
2807    pub fn diagram_edge_sems(&mut self, id: u32, st: &mut DiagramState, m: &DModel, label: &dyn Fn(u64) -> String) {
2808        let _src = self.src_scope();
2809        let r = st.vp.rect;
2810        if r[2] <= 0 || r[3] <= 0 {
2811            return;
2812        }
2813        let focused = self.wp.ctx.focus == id;
2814        let hov = match st.hover {
2815            DHit::Edge { key, .. } | DHit::Mid(key, _) | DHit::Waypoint(key, _) => key,
2816            _ => 0,
2817        };
2818        let wr = st.vp.world_rect();
2819        let half = self.dp(6);
2820        let geo = st.ix.geo.clone();
2821        for (i, rt) in geo.routes.iter().enumerate() {
2822            if rt.pts.len() < 2 || !overlaps(rt.bb, wr) || m.edges.get(i).is_none_or(|e| e.key != rt.key) {
2823                continue;
2824            }
2825            let c = st.vp.to_screen(dg_point_at(&rt.pts, 0.5));
2826            let (cx, cy) = (fl(c[0] + 0.5), fl(c[1] + 0.5));
2827            let (x0, y0) = ((cx - half).max(r[0]), (cy - half).max(r[1]));
2828            let (x1, y1) = ((cx + half).min(r[0] + r[2]), (cy + half).min(r[1] + r[3]));
2829            if x1 <= x0 || y1 <= y0 {
2830                continue;
2831            }
2832            let mut rect = [x0, y0, x1 - x0, y1 - y0];
2833            if self.rtl {
2834                rect[0] = self.rtl_map(rect[0] + rect[2] - 1, r[0], r[2]);
2835            }
2836            let mut state = 0;
2837            if st.sel.contains(&rt.key) {
2838                state |= SS_SELECTED;
2839            }
2840            if focused && st.focus == rt.key {
2841                state |= SS_FOCUSED;
2842            }
2843            if hov == rt.key {
2844                state |= SS_HOVER;
2845            }
2846            let sid = node_sem_id(id, rt.key);
2847            if self.m.a11y.pending() && self.a11y_click(sid) {
2848                st.sel.clear();
2849                st.sel.insert(rt.key);
2850                if st.tab_edges {
2851                    st.focus = rt.key;
2852                }
2853                st.pending_sel = true;
2854                self.repass();
2855            }
2856            self.last_set(rect, state as u32);
2857            let l = label(rt.key);
2858            self.sem(K_LISTITEM, sid, Some(&l), i as i32);
2859        }
2860    }
2861}