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
use std::cell::{Ref, RefCell};
use std::rc::Rc;
use platform_core::Event;
use reactive_core::batch;
use renderer_core::DrawCommand;
use crate::component::{Component, EventResult};
use crate::segment::{self, Segment, SegmentRoot};
pub struct ComponentList {
// Shared with the root segment: the segment borrows it immutably to render; on_event borrows it mutably. They never overlap because event dispatch is batched (flush happens after on_event).
root: Rc<RefCell<dyn Component>>,
segment_root: SegmentRoot,
}
impl ComponentList {
pub fn new<C: Component + 'static>(component: C) -> Self {
let root: Rc<RefCell<dyn Component>> = Rc::new(RefCell::new(component));
let seg = Segment::mount_dyn(Rc::clone(&root));
Self {
root,
segment_root: SegmentRoot::from_segment(seg),
}
}
/// Current content generation. Increments whenever the composed draw commands are rebuilt. Two reads returning the same value guarantee identical `commands()` output.
pub fn generation(&self) -> u64 {
self.segment_root.generation()
}
pub fn is_dirty(&self) -> bool {
self.segment_root.is_dirty()
}
pub fn commands(&self) -> Ref<'_, Vec<DrawCommand>> {
self.segment_root.commands()
}
/// Emits the component tree in pre-order for the devtools inspector. See [`SegmentRoot::walk`].
pub fn walk_tree(&self, out: &mut Vec<segment::SegmentNodeInfo>) {
self.segment_root.walk(out);
}
pub fn on_event(&mut self, event: &Event) -> EventResult {
// Batch so any signals mutated by handlers flush their effects AFTER on_event returns (and releases the borrow_mut), never re-entering a segment effect mid-borrow.
// Overlay priority routing (blocking a modal's background) is NOT done here: it must run on the
// side that owns the overlay registry, which under hot reload is the app dylib, not the host that
// holds this `ComponentList`. The runner consults it via `App::dispatch_overlays` (bridged across
// the dylib boundary like `relayout`) before calling this, and skips this call when an overlay
// consumed the event. See `overlay_dispatch` and `crate::app::App::dispatch_overlays`.
batch(|| self.root.borrow_mut().on_event(event))
}
// In hot-reload mode the dylib's reactive signals are not tracked by the binary's effects, so state changes from on_event (e.g. WindowResized updating layout) would never trigger a re-render. Call this after on_event to force every segment's view effect to re-run so it reads fresh layout and state.
pub fn bump_force_ticks(&self) {
batch(segment::bump_force_ticks);
}
}
#[cfg(test)]
mod tests {
use geometry_core::Rect;
use reactive_core::signal;
use renderer_core::{Color, RectStyle, ShapeStyle};
use std::sync::Arc;
use super::*;
use crate::render_node::RenderNode;
fn sample_rect(x: f32) -> DrawCommand {
DrawCommand::Rect {
rect: Rect::new(x, 0.0, 10.0, 10.0),
style: Arc::new(RectStyle::default().with_fill(Color::BLACK)),
}
}
struct Fixed;
impl Component for Fixed {
fn view(&self) -> RenderNode {
RenderNode::group([
RenderNode::Primitive(sample_rect(0.0)),
RenderNode::Primitive(sample_rect(20.0)),
])
}
}
#[test]
fn tree_initial_render() {
let tree = ComponentList::new(Fixed);
let cmds = tree.commands();
assert_eq!(cmds.len(), 2);
}
struct Counter {
value: reactive_core::RwSignal<i32>,
}
impl Component for Counter {
fn view(&self) -> RenderNode {
let n = self.value.get();
RenderNode::group((0..n).map(|i| RenderNode::Primitive(sample_rect(i as f32 * 10.0))))
}
}
#[test]
fn tree_reactive_update() {
let signal = signal(2i32);
let tree = ComponentList::new(Counter {
value: signal.clone(),
});
assert_eq!(tree.commands().len(), 2);
signal.set(5);
assert_eq!(tree.commands().len(), 5);
signal.set(0);
assert_eq!(tree.commands().len(), 0);
}
}