1
//! Publishing layout's scroll containers into the [`ScrollManager`].
2
//!
3
//! This ran in the dll, AFTER `layout_and_generate_display_list` returned, and
4
//! the e2e runner carried a hand-maintained port of it. So there were three
5
//! answers to "which nodes are scrollable and how big are they": the dll's,
6
//! the runner's, and whatever each `layout/tests` test hand-seeded — and a
7
//! test could seed a scroll state production would never produce, which is
8
//! exactly the divergence that let scroll bugs hide from the suite.
9
//!
10
//! It lives here now and all three call it.
11

            
12
use azul_core::{
13
    dom::{DomId, NodeId},
14
    task::Instant,
15
};
16

            
17
use crate::{solver3::layout_tree::LayoutNodeId, window::LayoutWindow};
18

            
19
138
pub fn register_scroll_nodes(layout_window: &mut LayoutWindow, now: &Instant) {
20
276
    for (dom_id, layout_result) in &mut layout_window.layout_results {
21
        // What the VirtualView callbacks published for this DOM, read the way
22
        // `display_list::paint_scrollbars` reads it — same producer, same
23
        // `children_rect.size`, so the two cannot disagree about the extent.
24
        // Snapshotted before the registration loop mutates the manager;
25
        // registration never touches `virtual_scroll_size`.
26
138
        let scroll_states = layout_window.scroll_manager.get_scroll_states_for_dom(*dom_id);
27

            
28
2032
        for node_idx in 0..layout_result.layout_tree.nodes.len() {
29
2032
            let node = &layout_result.layout_tree.nodes[node_idx];
30
2032
            let Some(dom_node_id) = node.dom_node_id else {
31
2
                continue;
32
            };
33

            
34
            // CSS spec: scrolling occurs within the padding box, so the
35
            // viewport for scroll clamp must be padding-box, not content-box.
36
            // This must match compute_scrollbar_geometry() which also uses
37
            // padding-box (inner_rect = paint_rect - borders).
38
2030
            let border_box_size = node.used_size.unwrap_or_default();
39
2030
            let resolved = node.box_props.unpack();
40
2030
            let border = &resolved.border;
41
2030
            let container_size = azul_core::geom::LogicalSize {
42
2030
                width: (border_box_size.width
43
2030
                        - border.left - border.right).max(0.0),
44
2030
                height: (border_box_size.height
45
2030
                         - border.top - border.bottom).max(0.0),
46
2030
            };
47

            
48
            // STATIC (unscrolled) layout coordinates, NOT window coordinates —
49
            // the comment here used to claim otherwise. `scroll_selection_into_view`
50
            // depends on static, because it compares against a caret rect measured
51
            // the same way. A consumer that needs where the container APPEARS must
52
            // subtract the scroll of its ancestors itself.
53
2030
            let container_origin = layout_result
54
2030
                .calculated_positions
55
2030
                .get(node_idx)
56
2030
                .copied()
57
2030
                .unwrap_or_else(azul_core::geom::LogicalPosition::zero);
58

            
59
2030
            let Some(mut scrollbar_info) = layout_result
60
2030
                .layout_tree
61
2030
                .warm(LayoutNodeId::new(node_idx))
62
2030
                .and_then(|w| w.scrollbar_info)
63
            else {
64
677
                continue;
65
            };
66

            
67
            // A VirtualView's scrollable extent does not exist during layout —
68
            // it is whatever its callback just published. Amend the flags here,
69
            // where both the tree and the ScrollManager are in reach, and store
70
            // the answer back so every later consumer (the GPU thumb updater
71
            // `update_scrollbar_transforms`, the registration below, the
72
            // hit-test scrollbar states) reads one decision rather than
73
            // re-deriving it. `paint_scrollbars` calls the same function while
74
            // building the display list, which is why the numbers agree.
75
1353
            if let Some(pos) = scroll_states.get(&dom_node_id) {
76
15
                let raised = crate::solver3::cache::apply_virtual_scroll_necessity(
77
15
                    &layout_result.styled_dom,
78
15
                    dom_node_id,
79
15
                    pos.children_rect.size,
80
15
                    container_size,
81
15
                    &mut scrollbar_info,
82
                );
83
15
                if raised {
84
                    if let Some(warm) = layout_result.layout_tree.warm_mut(LayoutNodeId::new(node_idx)) {
85
                        warm.scrollbar_info = Some(scrollbar_info);
86
                    }
87
15
                }
88
1338
            }
89

            
90
1353
            if !(scrollbar_info.needs_vertical || scrollbar_info.needs_horizontal) {
91
1308
                continue;
92
45
            }
93

            
94
45
            let container_rect = azul_core::geom::LogicalRect {
95
45
                origin: container_origin,
96
45
                size: container_size,
97
45
            };
98

            
99
45
            let content_size = layout_result.layout_tree.get_content_size(LayoutNodeId::new(node_idx));
100

            
101
            // Use the layout-computed scrollbar width, not the
102
            // hardcoded default. On macOS with overlay scrollbars,
103
            // scrollbar_width is 0.0 (no layout space reserved).
104
            // The scroll_manager falls back to DEFAULT_SCROLLBAR_WIDTH_PX
105
            // when thickness is 0 for hit-test geometry.
106
45
            let scrollbar_thickness = scrollbar_info.scrollbar_width
107
45
                .max(scrollbar_info.scrollbar_height);
108

            
109
45
            layout_window.scroll_manager.register_or_update_scroll_node(
110
45
                *dom_id,
111
45
                dom_node_id,
112
45
                container_rect,
113
45
                content_size,
114
45
                now.clone(),
115
45
                scrollbar_thickness,
116
45
                scrollbar_info.visual_width_px,
117
45
                scrollbar_info.needs_horizontal,
118
45
                scrollbar_info.needs_vertical,
119
            );
120

            
121
        }
122
    }
123
138
    layout_window.scroll_manager.calculate_scrollbar_states();
124
138
}