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 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 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 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 }
80}
81
82impl<T: Widget> WidgetContext<T> {
83 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 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 }
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 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 should_render: false,
212 rendered: false,
213 scheduled: None,
214
215 drop_hooks: Vec::new(),
216 meta: T::Meta::default(),
217 };
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 }
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}