machine-check-gui 0.7.1

Utility crate for the formal verification tool machine-check
Documentation
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
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
pub mod camera;

use std::collections::BTreeMap;

use bimap::BiBTreeMap;
use camera::{Camera, Scheme};
use machine_check_common::NodeId;
use rstar::{
    primitives::{GeomWithData, Rectangle},
    RTree, AABB,
};

use crate::{
    frontend::tiling::TileType,
    shared::{
        snapshot::{PropertyIndex, Snapshot},
        BackendInfo,
    },
};

use super::{
    tiling::{NodeTileInfo, Tile, Tiling},
    util::{web_idl::main_canvas_with_context, PixelPoint, PixelRect},
};

#[derive(Debug)]
struct AxisSizing {
    normal_size: u64,
    sizes: BTreeMap<i64, u64>,
    starts: BiBTreeMap<i64, i64>,
}

impl AxisSizing {
    fn new(normal_size: u64, sizes: BTreeMap<i64, u64>) -> Self {
        let mut starts = BiBTreeMap::new();

        if let (Some((smallest_size_index, _)), Some((largest_size_index, _))) =
            (sizes.first_key_value(), sizes.last_key_value())
        {
            let (smallest_size_index, largest_size_index) =
                (*smallest_size_index, *largest_size_index);

            // the offset at the index 0 will be 0
            // first, work down from it to the smallest size index
            // do not process 0 in this manner and always pre-subtract the size at index one higher
            {
                let mut offset: i64 = 0;
                for index in (smallest_size_index..0).rev() {
                    offset -= sizes.get(&(index + 1)).copied().unwrap_or(normal_size) as i64;
                    starts.insert(index, offset);
                }
            }

            // next, work up from index 0 to the largest size index
            // process 0 and always post-add the current element size
            {
                let mut offset: i64 = 0;
                for index in 0..=largest_size_index + 1 {
                    starts.insert(index, offset);
                    offset += sizes.get(&index).copied().unwrap_or(normal_size) as i64;
                }
            }

            // this should make sure that the offsets from min(smallest_size_index, 0)
            // to max(0, largest_size_index) inclusive are present in the starts map
        }

        Self {
            normal_size,
            sizes,
            starts,
        }
    }

    fn index_size(&self, index: i64) -> u64 {
        self.sizes.get(&index).copied().unwrap_or(self.normal_size)
    }

    fn index_lower_offset(&self, index: i64) -> i64 {
        if let Some(start) = self.starts.get_by_left(&index) {
            return *start;
        }

        let (boundary_index, boundary_start) = if index >= 0 {
            // select last
            self.starts.iter().next_back()
        } else {
            // select first
            self.starts.iter().next()
        }
        .map(|(k, v)| (*k, *v))
        .unwrap_or((0, 0));

        let boundary_offset = index - boundary_index;
        boundary_start + boundary_offset * self.normal_size as i64
    }

    fn index_higher_offset(&self, index: i64) -> i64 {
        let lower_offset = self.index_lower_offset(index);
        let width = self.index_size(index);
        if width >= 1 {
            lower_offset + width as i64 - 1
        } else {
            lower_offset
        }
    }

    fn index_of(&self, offset: i64) -> i64 {
        // search by offset: should be this or lower
        let this_or_lower = self.starts.right_range(..=offset).next_back();

        let index = if let Some((lower_index, lower_offset)) = this_or_lower {
            // we have to make sure it is high enough
            let lower_size = self.index_size(*lower_index);
            if lower_offset + (lower_size as i64) < offset {
                // we can assume the lower size is normal size
                lower_index + (offset - lower_offset).div_euclid(self.normal_size as i64)
            } else {
                *lower_index
            }
        } else {
            // no lower offset, get the lowest offset and compute from there
            if let Some((lowest_index, lowest_offset)) = self.starts.iter().next() {
                lowest_index + (offset - lowest_offset).div_euclid(self.normal_size as i64)
            } else {
                // no lowest offset, just compute from normal size
                offset.div_euclid(self.normal_size as i64)
            }
        };
        index
    }
}

/// Determines what is currently selected in the frontend.
#[derive(Debug)]
struct Selection {
    selected_node_id: Option<NodeId>,
    selected_subproperty: Option<(PropertyIndex, usize)>,
}

impl Selection {
    fn new(snapshot: &Snapshot) -> Self {
        // select the last property if it is available
        let selected_subproperty = snapshot
            .last_property_index()
            .map(|property_index| (property_index, 0));

        Self {
            selected_node_id: None,
            selected_subproperty,
        }
    }

    fn apply_snapshot(&mut self, snapshot: &Snapshot) {
        // make sure the selected things are still available
        if let Some(selected_node_id) = self.selected_node_id {
            if !snapshot.state_space.nodes.contains_key(&selected_node_id) {
                self.selected_node_id = None;
            }
        }

        if let Some((property_index, subproperty_index)) = self.selected_subproperty {
            if !snapshot.contains_subproperty(property_index, subproperty_index) {
                self.selected_subproperty = None;
            }
        }

        // if no property is selected, select the last one if it is available
        if self.selected_subproperty.is_none() {
            if let Some(last_property_index) = snapshot.last_property_index() {
                self.selected_subproperty = Some((last_property_index, 0));
            }
        }
    }
}

/// Presentation of a backend snapshot.
#[derive(Debug)]
struct Presentation {
    tiling: Tiling,
    column_sizing: AxisSizing,
    row_sizing: AxisSizing,
    visibility_tree: RTree<GeomWithData<Rectangle<(i64, i64)>, Tile>>,
}

impl Presentation {
    fn new(snapshot: Snapshot, scheme: &Scheme) -> Self {
        let tiling = Tiling::new(snapshot);

        let (column_sizes, row_sizes) = Self::compute_column_row_sizes(&tiling, scheme);
        let column_sizing = AxisSizing::new(scheme.tile_size, column_sizes);
        let row_sizing = AxisSizing::new(scheme.tile_size, row_sizes);

        let tiles_vec: Vec<_> = tiling
            .tile_types()
            .iter()
            .map(|(tile, tile_type)| {
                let rect = if let TileType::Node(node_id) = tile_type {
                    let (top_left, bottom_right) = tiling
                        .node_tile_info()
                        .get(node_id)
                        .unwrap()
                        .rendering_bounds();
                    let (top_left, _) = tile_rect(&column_sizing, &row_sizing, top_left);
                    let (_, bottom_right) = tile_rect(&column_sizing, &row_sizing, bottom_right);
                    (top_left, bottom_right)
                } else {
                    tile_rect(&column_sizing, &row_sizing, *tile)
                };

                GeomWithData::new(
                    Rectangle::from_corners((rect.0.x, rect.0.y), (rect.1.x, rect.1.y)),
                    *tile,
                )
            })
            .collect();

        let visibility_tree = RTree::bulk_load(tiles_vec);
        Self {
            tiling,
            column_sizing,
            row_sizing,
            visibility_tree,
        }
    }

    fn compute_column_row_sizes(
        tiling: &Tiling,
        scheme: &Scheme,
    ) -> (BTreeMap<i64, u64>, BTreeMap<i64, u64>) {
        let default_tile_size = scheme.tile_size;
        let mut column_sizes = BTreeMap::new();
        let mut row_sizes = BTreeMap::new();

        let context = main_canvas_with_context().1;
        let context_scheme = scheme.context_scheme(&context);
        for (tile, tile_type) in tiling.tile_types() {
            let (required_column_size, required_row_size) =
                context_scheme.required_tile_size(tile_type);
            let column_size = column_sizes.entry(tile.x).or_insert(default_tile_size);
            let row_size = row_sizes.entry(tile.y).or_insert(default_tile_size);
            *column_size = (*column_size).max(required_column_size);
            *row_size = (*row_size).max(required_row_size);
        }
        (column_sizes, row_sizes)
    }

    fn tiles_in_pixel_rect(&self, rect: PixelRect) -> impl Iterator<Item = Tile> + use<'_> {
        let aabb = AABB::from_corners(rect.top_left().to_tuple(), rect.bottom_right().to_tuple());
        let located = self.visibility_tree.locate_in_envelope_intersecting(&aabb);
        located.map(|geom_with_data| geom_with_data.data)
    }

    fn tile_rect(&self, tile: Tile) -> PixelRect {
        let top_left = PixelPoint::new(
            self.column_sizing.index_lower_offset(tile.x),
            self.row_sizing.index_lower_offset(tile.y),
        );
        let bottom_right = PixelPoint::new(
            self.column_sizing.index_higher_offset(tile.x),
            self.row_sizing.index_higher_offset(tile.y),
        );
        PixelRect::new(top_left, bottom_right)
    }
}

fn tile_rect(
    column_sizing: &AxisSizing,
    row_sizing: &AxisSizing,
    tile: Tile,
) -> (PixelPoint, PixelPoint) {
    let top_left_x = column_sizing.index_lower_offset(tile.x);
    let top_left_y = row_sizing.index_lower_offset(tile.y);
    let bottom_right_x = column_sizing.index_higher_offset(tile.x);
    let bottom_right_y = row_sizing.index_higher_offset(tile.y);
    (
        PixelPoint {
            x: top_left_x,
            y: top_left_y,
        },
        PixelPoint {
            x: bottom_right_x,
            y: bottom_right_y,
        },
    )
}

/// The frontend view of everything.
///
/// Does not render itself, leaving that to the client.
#[derive(Debug)]
pub struct View {
    backend_info: BackendInfo,
    presentation: Presentation,
    selection: Selection,
    pub camera: Camera,
}

pub enum NavigationTarget {
    Root,
    Up,
    Left,
    Right,
    Down,
}

impl View {
    pub fn backend_info(&self) -> &BackendInfo {
        &self.backend_info
    }

    pub fn update_backend_info(&mut self, backend_info: BackendInfo) {
        self.backend_info = backend_info;
    }

    pub fn new(snapshot: Snapshot, backend_info: BackendInfo) -> View {
        let selection = Selection::new(&snapshot);
        let camera = Camera::new();
        let presentation = Presentation::new(snapshot, &camera.scheme);

        Self {
            backend_info,
            presentation,
            selection,
            camera,
        }
    }

    pub fn apply_snapshot(&mut self, snapshot: Snapshot) {
        self.selection.apply_snapshot(&snapshot);
        self.presentation = Presentation::new(snapshot, &self.camera.scheme);
    }

    pub fn snapshot(&self) -> &Snapshot {
        self.presentation.tiling.snapshot()
    }

    pub fn navigate(&mut self, target: NavigationTarget) {
        if let Some(selected_node_id) = self.selected_node_id() {
            let selected_node_aux = self
                .node_tile_info(selected_node_id)
                .expect("Selected node id should point to a valid node");

            match target {
                NavigationTarget::Root => {
                    // go to the root node
                    self.set_selected_node_id(Some(NodeId::ROOT));
                }
                NavigationTarget::Up => {
                    // go to the previous child of the tiling parent
                    if let Some(tiling_parent_id) = selected_node_aux.tiling_parent {
                        let tiling_parent_aux = self
                            .node_tile_info(tiling_parent_id)
                            .expect("Tiling parent id should point to a valid node");
                        let selected_index = tiling_parent_aux
                            .tiling_children
                            .iter()
                            .position(|e| *e == selected_node_id)
                            .unwrap();
                        let new_selected_index = selected_index.saturating_sub(1);
                        self.set_selected_node_id(Some(
                            tiling_parent_aux.tiling_children[new_selected_index],
                        ));
                    }
                }
                NavigationTarget::Left => {
                    // go to the tiling parent
                    if let Some(tiling_parent_id) = selected_node_aux.tiling_parent {
                        self.set_selected_node_id(Some(tiling_parent_id));
                    }
                }
                NavigationTarget::Right => {
                    // go to first tiling child
                    if let Some(first_child_id) = selected_node_aux.tiling_children.first() {
                        self.set_selected_node_id(Some(*first_child_id));
                    }
                }
                NavigationTarget::Down => {
                    // go to the next child of the tiling parent
                    if let Some(tiling_parent_id) = selected_node_aux.tiling_parent {
                        let tiling_parent_aux = self
                            .node_tile_info(tiling_parent_id)
                            .expect("Tiling parent id should point to a valid node");
                        let selected_index = tiling_parent_aux
                            .tiling_children
                            .iter()
                            .position(|e| *e == selected_node_id)
                            .unwrap();
                        if let Some(new_selected_node_id) =
                            tiling_parent_aux.tiling_children.get(selected_index + 1)
                        {
                            self.set_selected_node_id(Some(*new_selected_node_id));
                        }
                    }
                }
            }
        } else {
            self.set_selected_node_id(Some(NodeId::ROOT));
        };

        // make sure the newly selected node is in view

        if let Some(selected_node_id) = self.selected_node_id() {
            let selected_node_aux = self
                .node_tile_info(selected_node_id)
                .expect("Selected node id should point to a valid node");

            // make sure the selected node is in view
            self.show_tile(selected_node_aux.tile);
        }
    }

    fn show_tile(&mut self, tile: Tile) {
        let tile_rect = self.tile_rect(tile);
        console_log!("Ensuring in view: {:?}", tile_rect);
        let mut top_left = tile_rect.top_left();
        let mut bottom_right = tile_rect.bottom_right();
        // adjust by half of default tile size
        let node_size = self.camera.scheme.tile_size / 2;
        let node_size_point = PixelPoint {
            x: node_size as i64,
            y: node_size as i64,
        };
        top_left -= node_size_point;
        bottom_right += node_size_point;

        self.camera.ensure_in_view(top_left);
        self.camera.ensure_in_view(bottom_right);
    }

    pub fn selected_property_index(&self) -> Option<PropertyIndex> {
        self.selection
            .selected_subproperty
            .map(|(property_index, _)| property_index)
    }

    pub fn selected_subproperty_index(&self) -> Option<(PropertyIndex, usize)> {
        self.selection.selected_subproperty
    }

    pub fn select_subproperty_index(
        &mut self,
        property_index: PropertyIndex,
        subproperty_index: usize,
    ) {
        assert!(self
            .snapshot()
            .contains_subproperty(property_index, subproperty_index));
        self.selection.selected_subproperty = Some((property_index, subproperty_index));
    }

    pub fn selected_node_id(&self) -> Option<NodeId> {
        self.selection.selected_node_id
    }

    pub fn tile_at_global_point(&self, point: PixelPoint) -> Tile {
        let x = self.presentation.column_sizing.index_of(point.x);
        let y = self.presentation.row_sizing.index_of(point.y);
        Tile { x, y }
    }

    pub fn tile_at_viewport_point(&self, point: PixelPoint) -> Tile {
        let global_point = point + self.camera.view_offset();
        self.tile_at_global_point(global_point)
    }

    pub fn scheme(&self) -> &Scheme {
        &self.camera.scheme
    }

    pub fn tile_rect(&self, tile: Tile) -> PixelRect {
        self.presentation.tile_rect(tile)
    }

    pub fn tiles_in_rect(&self, rect: PixelRect) -> impl Iterator<Item = Tile> + use<'_> {
        self.presentation.tiles_in_pixel_rect(rect)
    }

    pub fn node_rect(&self, tile: Tile) -> PixelRect {
        let node_half_margin = self.scheme().node_half_margin();
        self.tile_rect(tile)
            .without_half_margin(node_half_margin, node_half_margin)
    }

    pub fn view_offset(&self) -> PixelPoint {
        self.camera.view_offset()
    }

    pub fn tile_type(&self, tile: Tile) -> Option<&TileType> {
        self.presentation.tiling.at_tile(tile)
    }

    pub fn node_tile_info(&self, node_id: NodeId) -> Option<&NodeTileInfo> {
        self.presentation.tiling.node_tile_info().get(&node_id)
    }

    pub fn set_selected_node_id(&mut self, node_id: Option<NodeId>) {
        self.selection.selected_node_id = node_id;
    }

    pub fn mouse_drag_start(&mut self, mouse_coords: PixelPoint) -> bool {
        self.camera.mouse_down_coords = Some(mouse_coords);
        self.camera.mouse_current_coords = Some(mouse_coords);
        true
    }

    pub fn mouse_drag_update(&mut self, mouse_coords: PixelPoint) -> bool {
        self.camera.mouse_current_coords = Some(mouse_coords);
        self.camera.mouse_down_coords.is_some()
    }

    pub fn mouse_drag_end(&mut self, mouse_coords: PixelPoint) -> bool {
        let Some(mouse_down_coords) = self.camera.mouse_down_coords.take() else {
            return false;
        };
        let offset = mouse_coords - mouse_down_coords;
        self.camera.view_offset -= offset;
        true
    }

    pub fn set_view_size(&mut self, width: u32, height: u32) {
        self.camera.view_size = PixelPoint {
            x: width as i64,
            y: height as i64,
        }
    }
}