rate_ui/agents/graphics/
agent.rs1use super::reframer::Reframer;
2use derive_more::{From, Into};
3use std::collections::{HashMap, HashSet};
4use std::time::Duration;
5use typed_slab::TypedSlab;
6use web_sys::Element;
7use yew::services::interval::{IntervalService, IntervalTask};
8use yew::services::resize::{ResizeService, ResizeTask};
9use yew::worker::{Agent, AgentLink, Context, HandlerId};
10use yew::NodeRef;
11
12#[derive(Debug, Clone, Default, PartialEq, Eq)]
13pub struct RectSize {
14 pub width: usize,
15 pub height: usize,
16}
17
18#[derive(Debug, Clone, Copy, From, Into, PartialEq, Eq, Hash)]
19struct NodeTrackId(usize);
20
21struct NodeTracker {
22 who: HandlerId,
23 node_ref: NodeRef,
24 size: RectSize,
25}
26
27impl NodeTracker {
28 fn new(who: HandlerId, node_ref: NodeRef) -> Self {
29 Self {
30 who,
31 node_ref,
32 size: RectSize::default(),
33 }
34 }
35
36 fn changed(&mut self) -> Option<RectSize> {
37 if let Some(element) = self.node_ref.cast::<Element>() {
38 let rect = element.get_bounding_client_rect();
39 let real_size = RectSize {
40 width: rect.width() as usize,
41 height: rect.height() as usize,
42 };
43 if self.size != real_size {
44 self.size = real_size;
45 return Some(self.size.clone());
46 }
47 }
48 None
49 }
50}
51
52pub struct GraphicsAgent {
53 link: AgentLink<Self>,
54 reframer: Reframer,
55 #[allow(dead_code)]
58 resize_task: ResizeTask,
59 on_frame: HashSet<HandlerId>,
61
62 #[allow(dead_code)]
63 check_sizes: IntervalTask,
64 tracking_nodes: TypedSlab<NodeTrackId, NodeTracker>,
65 assigned_ids: HashMap<HandlerId, HashSet<NodeTrackId>>,
66}
67
68pub enum Msg {
69 RenderFrame(f64),
70 Resized,
71 CheckSizes,
72}
73
74#[derive(Debug)]
75pub enum GraphicsRequest {
76 OnFrame(bool),
78 TrackSize(NodeRef),
79}
80
81#[derive(Debug)]
82pub enum GraphicsResponse {
83 Frame,
85 SizeChanged(RectSize),
86}
87
88impl Agent for GraphicsAgent {
89 type Reach = Context<Self>;
90 type Message = Msg;
91 type Input = GraphicsRequest;
92 type Output = GraphicsResponse;
93
94 fn create(link: AgentLink<Self>) -> Self {
95 let callback = link.callback(Msg::RenderFrame);
96 let reframer = Reframer::new(callback);
97 let callback = link.callback(|_| Msg::Resized);
98 let resize_task = ResizeService::register(callback);
99 let callback = link.callback(|_| Msg::CheckSizes);
100 let check_sizes = IntervalService::spawn(Duration::from_millis(400), callback);
101 Self {
102 link,
103 reframer,
104 resize_task,
106 on_frame: HashSet::new(),
108
109 check_sizes,
110 tracking_nodes: TypedSlab::new(),
111 assigned_ids: HashMap::new(),
112 }
113 }
114
115 fn update(&mut self, msg: Self::Message) {
116 match msg {
117 Msg::RenderFrame(_) => {
118 for who in self.on_frame.iter() {
119 self.link.respond(*who, GraphicsResponse::Frame);
120 }
121 self.reframer.request_next_frame();
122 }
123 Msg::Resized | Msg::CheckSizes => {
131 for (_, tracker) in self.tracking_nodes.iter_mut() {
134 if let Some(size) = tracker.changed() {
135 self.link
136 .respond(tracker.who, GraphicsResponse::SizeChanged(size));
137 }
138 }
139 }
140 }
141 }
142
143 fn handle_input(&mut self, request: Self::Input, who: HandlerId) {
144 match request {
145 GraphicsRequest::TrackSize(node_ref) => {
146 let mut tracker = NodeTracker::new(who, node_ref);
147 if let Some(size) = tracker.changed() {
149 self.link
150 .respond(tracker.who, GraphicsResponse::SizeChanged(size));
151 }
152 let track_id = self.tracking_nodes.insert(tracker);
154 self.assigned_ids.entry(who).or_default().insert(track_id);
155 }
156 GraphicsRequest::OnFrame(active) => {
175 if active {
176 self.on_frame.insert(who);
177 if self.on_frame.len() == 1 {
178 self.reframer.request_next_frame();
179 }
180 self.link.respond(who, GraphicsResponse::Frame);
181 } else {
182 self.on_frame.remove(&who);
183 if self.on_frame.is_empty() {
184 self.reframer.interrupt();
185 }
186 }
187 }
188 }
189 }
190
191 fn connected(&mut self, _id: HandlerId) {
192 }
194
195 fn disconnected(&mut self, id: HandlerId) {
196 self.handle_input(GraphicsRequest::OnFrame(false), id);
198 if let Some(track_ids) = self.assigned_ids.remove(&id) {
200 for track_id in track_ids {
201 self.tracking_nodes.remove(track_id);
202 }
203 }
204 }
205}