rate_ui/agents/graphics/
agent.rs

1use 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    // TODO: Is it safe for the task to remove this service here?
56    //resizer: ResizeService,
57    #[allow(dead_code)]
58    resize_task: ResizeTask,
59    //on_resize: HashSet<HandlerId>,
60    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    //OnResize(bool),
77    OnFrame(bool),
78    TrackSize(NodeRef),
79}
80
81#[derive(Debug)]
82pub enum GraphicsResponse {
83    //Resized,
84    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            //resizer,
105            resize_task,
106            //on_resize: HashSet::new(),
107            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            /*
124            Msg::Resized => {
125                for who in self.on_resize.iter() {
126                    self.link.respond(*who, GraphicsResponse::Resized);
127                }
128            }
129            */
130            Msg::Resized | Msg::CheckSizes => {
131                // Checking this on resize, because if window size changes
132                // doesn't mean the component size changed as well.
133                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                // Send the size and relayout immediately
148                if let Some(size) = tracker.changed() {
149                    self.link
150                        .respond(tracker.who, GraphicsResponse::SizeChanged(size));
151                }
152                // Keep the node for size tracking
153                let track_id = self.tracking_nodes.insert(tracker);
154                self.assigned_ids.entry(who).or_default().insert(track_id);
155            }
156            /*
157            GraphicsRequest::OnResize(active) => {
158                if active {
159                    self.on_resize.insert(who);
160                    if self.on_resize.len() == 1 {
161                        let callback = self.link.callback(|_| Msg::Resized);
162                        let resize_task = self.resizer.register(callback);
163                        self.resize_task = Some(resize_task);
164                    }
165                    self.link.respond(who, GraphicsResponse::Resized);
166                } else {
167                    self.on_resize.remove(&who);
168                    if self.on_resize.is_empty() {
169                        self.resize_task.take();
170                    }
171                }
172            }
173            */
174            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        //log::trace!("Connected to Graphics: {:?}", id);
193    }
194
195    fn disconnected(&mut self, id: HandlerId) {
196        //log::trace!("Disconnected from Graphics: {:?}", id);
197        self.handle_input(GraphicsRequest::OnFrame(false), id);
198        //self.handle_input(GraphicsRequest::OnResize(false), id);
199        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}