rust_widgets 2.9.2

Pure Rust cross-platform native GUI library with hardware-adaptive rendering, 180 widgets, touch/gesture support, i18n, and SVG-pipeline-accurate output
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
// SPDX-FileCopyrightText: Copyright (c) 2026 Mike Li/Mikewolfli/Wei Li(mikewolfli@163.com)
// SPDX-License-Identifier: MIT

//! The read-only map from a mounted tree's **stable identity** to its live `ObjectId`
//! (BLUE26 B26.04).
//!
//! # What problem this solves
//!
//! A designer's runtime inspector draws the tree beside the model that produced it and must answer
//! "which live control is this model node?". [`ViewEngine::id_at`](crate::view::ViewEngine::id_at)
//! answers that for a **path**, but a path is not an identity: inserting one sibling moves every
//! later node to a new path, so a path captured before the update addresses a different node after
//! it. The engine already re-derives its map by identity internally (see
//! [`ViewEngine::reindex_paths`](crate::view::ViewEngine)); what was missing is a *public, read-only*
//! contract for a caller to query — with the rules stated, so a host does not have to reverse
//! engineer them.
//!
//! # The two identities, and their rules
//!
//! * **key** — the explicit identity a caller declared with [`Node::key`](crate::view::Node::key).
//!   It is **scoped to its siblings**: two lists may each hold a row keyed `"total"`. Within one
//!   parent it must be **unique**; a duplicate makes both claimants unmappable
//!   ([`TreeIndex::id_of_key`] answers `None`, and [`TreeIndex::duplicate_keys`] names the colliding
//!   key so a caller can say *why*; the diff reports the same condition as
//!   `duplicate_keys`). A keyed node is matched across a rebuild **only** by its key, never
//!   positionally.
//! * **path** — a sequence of child indices from the root. It is the addressing the diff uses, and
//!   it is **not stable across insertion**: a path is a snapshot, valid for the tree it was taken
//!   from.
//!
//! A caller resolving a model node should therefore hold the **key**, not the path, and ask for the
//! key. Ask for a path only when the tree has not changed since the path was taken.
//!
//! # Invalidation
//!
//! An index is a **snapshot** of the engine at the moment it was built. It holds for exactly as
//! long as the tree does not change. To re-query after an update, rebuild the index with
//! [`ViewEngine::tree_index`](crate::view::ViewEngine::tree_index). A stale index does not silently
//! answer wrongly: it answers about the tree it was built from, and
//! [`TreeIndex::generation`](TreeIndex::generation) lets a caller detect that the engine has moved
//! on.
//!
//! # `ObjectId` is not a `NodeId`
//!
//! An [`ObjectId`] is a **runtime handle**: it is valid only while the control is alive, and the
//! engine may reuse the value. It must never be persisted, written into a project file, or compared
//! across sessions. A designer that needs a durable identity uses the **key** (or the path in the
//! document), and resolves it to an `ObjectId` at run time through this index.

use crate::compat::{Arc, HashMap, String, Vec};
use crate::core::ObjectId;

/// A read-only snapshot of a mounted tree's identity map.
///
/// Built by [`ViewEngine::tree_index`](crate::view::ViewEngine::tree_index); see the module doc for
/// the identity rules and the invalidation contract.
#[derive(Debug, Clone, Default)]
pub struct TreeIndex {
    /// Every node's `ObjectId`, by its path from the root.
    ///
    /// The key is a shared `Arc` slice rather than an owned `Vec` so the reverse map below
    /// can point at the **same** allocation: a snapshot therefore costs one path allocation
    /// per node instead of one per map (G-167).
    path_to_id: HashMap<Arc<[usize]>, ObjectId>,
    /// Every **keyed** node's `ObjectId`, by `(parent id, key)`.
    ///
    /// Keyed by the parent's id rather than the parent's path because a key is scoped to its
    /// siblings, and the parent's id survives an insertion above it while its path does not.
    key_to_id: HashMap<(Option<ObjectId>, String), ObjectId>,
    /// The reverse of `path_to_id`, for the "what is this control in the model?" direction.
    ///
    /// Shares the path allocation with `path_to_id` (see the field comment above).
    id_to_path: HashMap<ObjectId, Arc<[usize]>>,
    /// Keys claimed more than once under one parent, sorted for a stable report.
    ///
    /// A duplicated key identifies **neither** claimant, so neither is entered in `key_to_id`: this
    /// list is what keeps the collision from being resolved arbitrarily, and what lets a caller
    /// explain the missing mapping instead of guessing.
    duplicated_keys: Vec<(Option<ObjectId>, String)>,
    /// The widget name of each node, so a query can report the node *type* without a second walk.
    widget_of: HashMap<ObjectId, String>,
    /// The parent of each node, or absent for the root.
    parent_of: HashMap<ObjectId, ObjectId>,
    /// The children of each node, in order, so a query can walk without the engine.
    children_of: HashMap<ObjectId, Vec<ObjectId>>,
    /// The engine instance generation this snapshot was taken at.
    generation: u64,
    /// The engine's root id, if a tree is mounted.
    root: Option<ObjectId>,
}

impl TreeIndex {
    /// The engine generation this snapshot describes.
    ///
    /// Compare it against [`ViewEngine::generation`](crate::view::ViewEngine::generation) to tell
    /// whether the engine has changed since the snapshot — a stale index answers about the tree it
    /// was built from, which is exactly what this lets a caller detect.
    pub fn generation(&self) -> u64 {
        self.generation
    }

    /// The root control's id, or `None` when no tree is mounted.
    pub fn root(&self) -> Option<ObjectId> {
        self.root
    }

    /// How many nodes the snapshot holds.
    pub fn len(&self) -> usize {
        self.path_to_id.len()
    }

    /// Whether the snapshot holds no nodes.
    pub fn is_empty(&self) -> bool {
        self.path_to_id.is_empty()
    }

    /// The `ObjectId` at `path`, or `None` when the path addresses nothing in this snapshot.
    pub fn id_at(&self, path: &[usize]) -> Option<ObjectId> {
        self.path_to_id.get(path).copied()
    }

    /// The `ObjectId` of the keyed node `key` under `parent`, or `None`.
    ///
    /// `parent` is `None` for the root's own children. Because a key is scoped to its siblings, the
    /// caller must supply the parent (which it can get from [`Self::id_at`] or from a previous
    /// resolution).
    ///
    /// A key claimed by more than one sibling under `parent` answers `None`, because it identifies
    /// neither of them; [`Self::duplicate_keys`] names the collision. Answering with one of the two
    /// would send the caller to a control at random, which is worse than saying "not mappable".
    pub fn id_of_key(&self, parent: Option<ObjectId>, key: &str) -> Option<ObjectId> {
        self.key_to_id.get(&(parent, String::from(key))).copied()
    }

    /// Every key that more than one sibling claims, sorted, with its parent.
    ///
    /// A caller that asked for a key and got `None` from [`Self::id_of_key`] asks this to tell
    /// "there is no such node" from "the document claims that key twice" — two conditions with two
    /// different fixes, and the second is the one a designer must show.
    pub fn duplicate_keys(&self) -> &[(Option<ObjectId>, String)] {
        &self.duplicated_keys
    }

    /// The path of `id` in this snapshot, or `None` when the id is not part of it.
    pub fn path_of(&self, id: ObjectId) -> Option<&[usize]> {
        self.id_to_path.get(&id).map(|path| path.as_ref())
    }

    /// The widget type name of `id`, or `None` when the id is not part of this snapshot.
    pub fn widget_of(&self, id: ObjectId) -> Option<&str> {
        self.widget_of.get(&id).map(String::as_str)
    }

    /// The key of `id`, if it declared one and is part of this snapshot.
    pub fn key_of(&self, id: ObjectId) -> Option<&str> {
        self.key_to_id.iter().find(|(_, v)| **v == id).map(|((_, k), _)| k.as_str())
    }

    /// The parent of `id`, or `None` for the root or an unknown id.
    pub fn parent_of(&self, id: ObjectId) -> Option<ObjectId> {
        self.parent_of.get(&id).copied()
    }

    /// The children of `id`, in order, or an empty slice for a leaf or unknown id.
    pub fn children_of(&self, id: ObjectId) -> &[ObjectId] {
        self.children_of.get(&id).map(Vec::as_slice).unwrap_or(&[])
    }

    /// Every keyed node as `(parent, key, id)`, sorted by `(parent, key)` for a stable report.
    ///
    /// A duplicated key is absent: it maps to no single node. Ask [`Self::duplicate_keys`] for
    /// those.
    pub fn keys(&self) -> Vec<(Option<ObjectId>, &str, ObjectId)> {
        let mut out: Vec<(Option<ObjectId>, &str, ObjectId)> =
            self.key_to_id.iter().map(|((parent, key), id)| (*parent, key.as_str(), *id)).collect();
        out.sort_by(|a, b| (a.0, a.1).cmp(&(b.0, b.1)));
        out
    }

    /// Build a snapshot from the parts the engine owns.
    ///
    /// Crate-internal: the engine is the only thing that knows all of these at once, and exposing a
    /// public constructor would let a caller build an index that disagrees with the tree it claims
    /// to describe.
    pub(crate) fn build(
        path_to_id: HashMap<Arc<[usize]>, ObjectId>,
        id_to_path: HashMap<ObjectId, Arc<[usize]>>,
        widget_of: HashMap<ObjectId, String>,
        key_of: HashMap<ObjectId, String>,
        parent_of: HashMap<ObjectId, ObjectId>,
        children_of: HashMap<ObjectId, Vec<ObjectId>>,
        root: Option<ObjectId>,
        generation: u64,
    ) -> Self {
        // The key map is derived here rather than maintained by the engine, so it cannot drift from
        // `path_to_id`: a node is in it exactly when it is in the snapshot and declared a key —
        // unless the key is claimed twice under one parent, in which case it identifies neither
        // claimant and belongs in `duplicated_keys` instead.
        //
        // The ambiguity is decided by *removing* the first claimant's entry when a second arrives
        // and then ignoring every later claimant, rather than by testing whether the map happens to
        // hold the key: `path_to_id` is a `HashMap`, so this walk's order is unspecified, and a
        // test on the map's contents would resolve a key claimed three times back to whichever
        // claimant the walk visited last.
        let mut key_to_id: HashMap<(Option<ObjectId>, String), ObjectId> = HashMap::new();
        let mut duplicated_keys: Vec<(Option<ObjectId>, String)> = Vec::new();
        for id in path_to_id.values() {
            if let Some(key) = key_of.get(id) {
                let claimed = (parent_of.get(id).copied(), key.clone());
                if duplicated_keys.contains(&claimed) {
                    // Already known to be ambiguous: a third claimant changes nothing.
                    continue;
                }
                if key_to_id.remove(&claimed).is_some() {
                    // A second claimant: keeping either id would answer with one of two
                    // equally-valid candidates, so the key becomes unmappable.
                    duplicated_keys.push(claimed);
                } else {
                    key_to_id.insert(claimed, *id);
                }
            }
        }
        duplicated_keys.sort();
        Self {
            path_to_id,
            key_to_id,
            duplicated_keys,
            id_to_path,
            widget_of,
            parent_of,
            children_of,
            generation,
            root,
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    /// Assemble both path maps from a forward-only fixture.
    ///
    /// The production builder takes the two maps from one walk (G-167); a fixture states the
    /// paths once and derives the reverse table here so each test stays a declaration of the
    /// tree it exercises.
    fn build_index(
        path_to_id: HashMap<Vec<usize>, ObjectId>,
        widget_of: HashMap<ObjectId, String>,
        key_of: HashMap<ObjectId, String>,
        parent_of: HashMap<ObjectId, ObjectId>,
        children_of: HashMap<ObjectId, Vec<ObjectId>>,
        root: Option<ObjectId>,
        generation: u64,
    ) -> TreeIndex {
        let mut forward: HashMap<Arc<[usize]>, ObjectId> = HashMap::new();
        let mut reverse: HashMap<ObjectId, Arc<[usize]>> = HashMap::new();
        for (path, id) in &path_to_id {
            let shared: Arc<[usize]> = Arc::from(path.as_slice());
            forward.insert(Arc::clone(&shared), *id);
            reverse.insert(*id, shared);
        }
        TreeIndex::build(
            forward,
            reverse,
            widget_of,
            key_of,
            parent_of,
            children_of,
            root,
            generation,
        )
    }

    fn sample_index() -> TreeIndex {
        // A tiny two-level tree: root(1) -> panel(2, key "sidebar"), panel(2) -> label(3, key "title").
        let mut path_to_id = HashMap::new();
        path_to_id.insert(Vec::new(), 1u64);
        path_to_id.insert(vec![0usize], 2u64);
        path_to_id.insert(vec![0usize, 0usize], 3u64);
        let mut widget_of = HashMap::new();
        widget_of.insert(1u64, String::from("window"));
        widget_of.insert(2u64, String::from("panel"));
        widget_of.insert(3u64, String::from("label"));
        let mut key_of = HashMap::new();
        key_of.insert(2u64, String::from("sidebar"));
        key_of.insert(3u64, String::from("title"));
        let mut parent_of = HashMap::new();
        parent_of.insert(2u64, 1u64);
        parent_of.insert(3u64, 2u64);
        let mut children_of: HashMap<ObjectId, Vec<ObjectId>> = HashMap::new();
        children_of.insert(1u64, vec![2u64]);
        children_of.insert(2u64, vec![3u64]);
        build_index(path_to_id, widget_of, key_of, parent_of, children_of, Some(1), 7)
    }

    /// Both directions must resolve: path → id and id → path.
    #[test]
    fn path_and_id_resolve_both_ways() {
        let index = sample_index();
        assert_eq!(index.root(), Some(1));
        assert_eq!(index.id_at(&[0]), Some(2));
        assert_eq!(index.path_of(3), Some(&[0usize, 0usize][..]));
        assert_eq!(index.id_at(&[9]), None);
        assert_eq!(index.path_of(999), None);
    }

    /// A key must resolve within its own parent, and not leak across parents.
    #[test]
    fn a_key_resolves_within_its_parent_only() {
        let index = sample_index();
        assert_eq!(index.id_of_key(Some(1), "sidebar"), Some(2));
        assert_eq!(index.id_of_key(Some(2), "title"), Some(3));
        // The root's children never contain `title` — a key is scoped, not global.
        assert_eq!(index.id_of_key(Some(1), "title"), None);
        assert_eq!(index.id_of_key(Some(2), "sidebar"), None);
    }

    /// The node type and parent must be answerable from the snapshot alone.
    #[test]
    fn node_type_and_parent_are_queryable() {
        let index = sample_index();
        assert_eq!(index.widget_of(2), Some("panel"));
        assert_eq!(index.widget_of(3), Some("label"));
        assert_eq!(index.parent_of(3), Some(2));
        assert_eq!(index.parent_of(1), None);
        assert_eq!(index.children_of(2), &[3u64]);
        assert_eq!(index.children_of(3), &[] as &[ObjectId]);
    }

    /// `keys` must be sorted and complete, so a report is stable.
    #[test]
    fn keys_are_sorted_and_complete() {
        let index = sample_index();
        let keys = index.keys();
        assert_eq!(keys.len(), 2);
        assert_eq!(keys[0], (Some(1), "sidebar", 2));
        assert_eq!(keys[1], (Some(2), "title", 3));
    }

    /// The generation must be reported, so a stale snapshot is detectable.
    #[test]
    fn the_generation_is_reported() {
        let index = sample_index();
        assert_eq!(index.generation(), 7);
        assert_eq!(index.len(), 3);
        assert!(!index.is_empty());
    }

    /// A key claimed by two siblings must map to **neither** of them, and must be nameable.
    ///
    /// The earlier behaviour kept whichever id the map happened to write last, so a lookup for an
    /// ambiguous key silently sent the inspector to one of two equally-valid controls — and said
    /// nothing. `id_of_key` answering `None` plus `duplicate_keys` is the honest pair.
    #[test]
    fn a_duplicated_key_is_mappable_to_neither_claimant() {
        let mut path_to_id = HashMap::new();
        path_to_id.insert(Vec::new(), 1u64);
        path_to_id.insert(vec![0usize], 2u64);
        path_to_id.insert(vec![1usize], 3u64);
        let mut widget_of = HashMap::new();
        widget_of.insert(1u64, String::from("window"));
        widget_of.insert(2u64, String::from("label"));
        widget_of.insert(3u64, String::from("label"));
        let mut key_of = HashMap::new();
        key_of.insert(2u64, String::from("dup"));
        key_of.insert(3u64, String::from("dup"));
        let mut parent_of = HashMap::new();
        parent_of.insert(2u64, 1u64);
        parent_of.insert(3u64, 1u64);
        let mut children_of: HashMap<ObjectId, Vec<ObjectId>> = HashMap::new();
        children_of.insert(1u64, vec![2u64, 3u64]);
        let index = build_index(path_to_id, widget_of, key_of, parent_of, children_of, Some(1), 1);

        assert_eq!(index.id_of_key(Some(1), "dup"), None, "the key identifies neither claimant");
        assert_eq!(index.duplicate_keys(), &[(Some(1), String::from("dup"))]);
        // Neither claimant is reachable *by that key*, so `keys()` must not list a winner either.
        assert!(index.keys().is_empty(), "{:?}", index.keys());
        assert_eq!(index.key_of(2), None);
        assert_eq!(index.key_of(3), None);
        // Both are still addressable by path — the collision is about the key, not the tree.
        assert_eq!(index.id_at(&[0]), Some(2));
        assert_eq!(index.id_at(&[1]), Some(3));
    }

    /// A key claimed by **three** siblings must stay unmappable.
    ///
    /// The snapshot is built by walking a `HashMap`, so a build that decided ambiguity by testing
    /// whether the map happened to hold the key would resolve a triple collision back to whichever
    /// claimant the walk reached last. This pins the order-independent answer.
    #[test]
    fn a_key_claimed_three_times_is_still_unmappable() {
        let mut path_to_id = HashMap::new();
        path_to_id.insert(Vec::new(), 1u64);
        path_to_id.insert(vec![0usize], 2u64);
        path_to_id.insert(vec![1usize], 3u64);
        path_to_id.insert(vec![2usize], 4u64);
        let mut widget_of = HashMap::new();
        for id in [1u64, 2, 3, 4] {
            widget_of.insert(id, String::from("label"));
        }
        let mut key_of = HashMap::new();
        for id in [2u64, 3, 4] {
            key_of.insert(id, String::from("dup"));
        }
        let mut parent_of = HashMap::new();
        for id in [2u64, 3, 4] {
            parent_of.insert(id, 1u64);
        }
        let mut children_of: HashMap<ObjectId, Vec<ObjectId>> = HashMap::new();
        children_of.insert(1u64, vec![2u64, 3u64, 4u64]);
        let index = build_index(path_to_id, widget_of, key_of, parent_of, children_of, Some(1), 2);

        assert_eq!(index.id_of_key(Some(1), "dup"), None, "three claimants map to none of them");
        assert_eq!(index.duplicate_keys(), &[(Some(1), String::from("dup"))]);
        assert_eq!(index.keys().len(), 0, "{:?}", index.keys());
    }
}