rate_ui/widget/
mod.rs

1mod graphics;
2mod lazy_bridge;
3mod live;
4mod wired_bridge;
5pub mod wired_widget;
6
7use crate::agents::graphics::{GraphicsAgent, GraphicsResponse};
8use crate::agents::live::wire::WireEnvelope;
9use crate::agents::live::{LiveAgent, LiveResponse};
10use anyhow::Error;
11use lazy_bridge::LazyBridge;
12pub use lazy_bridge::OnBridgeEvent;
13use rill_protocol::io::client::ClientReqId;
14use std::hash::Hash;
15use std::time::Duration;
16pub use wired_bridge::OnWireEvent;
17use wired_bridge::WiredBridge;
18use yew::services::timeout::{TimeoutService, TimeoutTask};
19use yew::{Callback, Component, ComponentLink, Html, Properties, ShouldRender};
20
21pub trait Widget: Default + 'static {
22    type Event;
23    // TODO: Don't `Clone` since the reference required
24    type Tag: Clone + Eq + Hash;
25    type Properties: Properties + PartialEq;
26    type Meta: Default;
27
28    fn init(&mut self, _ctx: &mut Context<Self>) {}
29
30    fn on_props(&mut self, _ctx: &mut Context<Self>) {}
31
32    fn on_event(&mut self, _event: Self::Event, _ctx: &mut Context<Self>) {}
33
34    fn view(&self, ctx: &Context<Self>) -> Html;
35
36    // TODO: Replate to the trait `OnRendered`
37    fn rendered(&mut self, _first: bool) -> Result<(), Error> {
38        Ok(())
39    }
40}
41
42pub type Context<T> = WidgetContext<T>;
43
44pub struct WidgetContext<T: Widget> {
45    props: T::Properties,
46    link: ComponentLink<WidgetRuntime<T>>,
47
48    live: WiredBridge<ClientReqId, LiveAgent, T>,
49    graphics: LazyBridge<GraphicsAgent, T>,
50    should_render: bool,
51    rendered: bool,
52    scheduled: Option<TimeoutTask>,
53
54    drop_hooks: Vec<DropHook<T>>,
55
56    // TODO: Store router state here
57    // keep them together with `Meta`
58    // provide access to it and store in the
59    meta: T::Meta,
60}
61
62pub type DropHook<T> = Box<dyn FnOnce(&mut T, &mut Context<T>)>;
63
64impl<T: Widget> Drop for WidgetRuntime<T> {
65    fn drop(&mut self) {
66        if !self.context.drop_hooks.is_empty() {
67            let hooks: Vec<_> = self.context.drop_hooks.drain(..).collect();
68            for hook in hooks {
69                hook(&mut self.widget, &mut self.context);
70            }
71        }
72        /*
73        for (req_id, _tag) in self.wires_from_live.drain() {
74            let req = LiveRequest::TerminateWire;
75            let envelope = WireEnvelope::new(req_id, req);
76            self.context.connection.send(envelope);
77        }
78        */
79    }
80}
81
82impl<T: Widget> WidgetContext<T> {
83    /// Schedule a timeout.
84    ///
85    /// Note: It's impossible to move to a separate module,
86    /// because it requires a link to a `Component`.
87    pub fn schedule(&mut self, ms: u64, msg: T::Event) {
88        let dur = Duration::from_millis(ms);
89        let generator = move |_| Msg::Event(msg);
90        let callback = self.link.callback_once(generator);
91        let task = TimeoutService::spawn(dur, callback);
92        self.scheduled = Some(task);
93    }
94
95    pub fn is_scheduled(&self) -> bool {
96        self.scheduled.is_some()
97    }
98
99    pub fn unschedule(&mut self) {
100        self.scheduled.take();
101    }
102}
103
104impl<T: Widget> WidgetContext<T> {
105    pub fn properties(&self) -> &T::Properties {
106        &self.props
107    }
108
109    pub fn meta(&self) -> &T::Meta {
110        &self.meta
111    }
112
113    pub fn meta_mut(&mut self) -> &mut T::Meta {
114        &mut self.meta
115    }
116
117    // TODO: Rename to `schedule_redraw`
118    pub fn redraw(&mut self) {
119        self.should_render = true;
120    }
121
122    pub fn is_rendered(&self) -> bool {
123        self.rendered
124    }
125
126    pub fn callback<F, IN>(&self, f: F) -> Callback<IN>
127    where
128        F: Fn(IN) -> T::Event + 'static,
129    {
130        let generator = move |event| Msg::Event(f(event));
131        self.link.callback(generator)
132    }
133
134    pub fn event<IN>(&self, msg: impl Into<T::Event>) -> Callback<IN>
135    where
136        T::Event: Clone,
137    {
138        let msg = msg.into();
139        let generator = move |_| Msg::Event(msg.clone());
140        self.link.callback(generator)
141    }
142
143    pub fn notification<IN>(&self) -> Callback<IN>
144    where
145        T: NotificationHandler<IN>,
146        IN: 'static,
147    {
148        let generator = move |event| {
149            let holder = NotificationImpl { event: Some(event) };
150            Msg::InPlace(Box::new(holder))
151        };
152        self.link.callback(generator)
153    }
154
155    /* not necessary right now
156    pub fn send(&mut self, event: T::Event) {
157        self.link.send_message(Msg::Event(event));
158    }
159    */
160}
161
162pub trait NotificationHandler<IN>: Widget {
163    fn handle(&mut self, event: IN, context: &mut Context<Self>) -> Result<(), Error>;
164}
165
166struct NotificationImpl<IN> {
167    event: Option<IN>,
168}
169
170impl<T, IN> WidgetCallbackFn<T> for NotificationImpl<IN>
171where
172    T: NotificationHandler<IN> + Widget,
173{
174    fn handle(&mut self, widget: &mut T, context: &mut Context<T>) -> Result<(), Error> {
175        if let Some(event) = self.event.take() {
176            widget.handle(event, context)?;
177        }
178        Ok(())
179    }
180}
181
182pub trait WidgetCallbackFn<T: Widget> {
183    fn handle(&mut self, widget: &mut T, context: &mut Context<T>) -> Result<(), Error>;
184}
185
186pub enum Msg<T: Widget> {
187    // TODO: Implement handlers as traits. Envelope-based.
188    LiveIncomingWired(WireEnvelope<ClientReqId, LiveResponse>),
189    GraphicsIncoming(GraphicsResponse),
190    Event(T::Event),
191    InPlace(Box<dyn WidgetCallbackFn<T>>),
192}
193
194pub struct WidgetRuntime<T: Widget> {
195    widget: T,
196    context: WidgetContext<T>,
197}
198
199impl<T: Widget> Component for WidgetRuntime<T> {
200    type Message = Msg<T>;
201    type Properties = T::Properties;
202
203    fn create(props: Self::Properties, link: ComponentLink<Self>) -> Self {
204        let mut context = WidgetContext {
205            props,
206            link,
207
208            live: WiredBridge::default(),
209            graphics: LazyBridge::default(),
210            //router: LazyBridge::default(),
211            should_render: false,
212            rendered: false,
213            scheduled: None,
214
215            drop_hooks: Vec::new(),
216            meta: T::Meta::default(),
217            //router_state: T::RouterState::default(),
218        };
219        let mut widget = T::default();
220        widget.init(&mut context);
221        widget.on_props(&mut context);
222        Self { widget, context }
223    }
224
225    fn update(&mut self, msg: Self::Message) -> ShouldRender {
226        self.context.should_render = false;
227        match msg {
228            Msg::LiveIncomingWired(envelope) => {
229                let req_id = envelope.id;
230                if let Some(tag) = self.context.live.registry().tag(&req_id) {
231                    match &envelope.data {
232                        LiveResponse::WireDone => {
233                            self.context.live.registry().remove(&req_id);
234                        }
235                        LiveResponse::Forwarded(_) => {
236                            // TODO: Extract response here and use it in handler
237                        }
238                    }
239                    if let Some(handler) = self.context.live.handler() {
240                        if let Err(err) =
241                            handler(&mut self.widget, tag.as_ref(), envelope, &mut self.context)
242                        {
243                            log::error!("Live handler failed: {}", err);
244                        }
245                    }
246                }
247            }
248            Msg::GraphicsIncoming(response) => {
249                if let Some(handler) = self.context.graphics.handler() {
250                    if let Err(err) = handler(&mut self.widget, response, &mut self.context) {
251                        log::error!("Graphics handler failed: {}", err);
252                    }
253                }
254            }
255            Msg::InPlace(mut func) => {
256                if let Err(err) = func.handle(&mut self.widget, &mut self.context) {
257                    log::error!("Widget callback failed: {}", err);
258                }
259            }
260            Msg::Event(event) => {
261                self.widget.on_event(event, &mut self.context);
262            }
263        }
264        self.context.should_render
265    }
266
267    fn change(&mut self, props: Self::Properties) -> ShouldRender {
268        if props != self.context.props {
269            self.context.props = props;
270            self.widget.on_props(&mut self.context);
271            self.context.should_render
272        } else {
273            false
274        }
275    }
276
277    fn view(&self) -> Html {
278        self.widget.view(&self.context)
279    }
280
281    fn rendered(&mut self, first_render: bool) {
282        if first_render {
283            self.context.rendered = true;
284        }
285        if let Err(err) = self.widget.rendered(first_render) {
286            log::error!("Rendering failed: {}", err);
287        }
288    }
289}