kano_web/
lib.rs

1//! Kano is a work-in-progress GUI application framework written for and in Rust.
2#![allow(non_snake_case, non_upper_case_globals)]
3
4use anyhow::anyhow;
5use gloo::events::EventListener;
6use js_sys::wasm_bindgen::*;
7use kano::platform::Platform;
8use wasm_bindgen::prelude::*;
9use web_sys::{window, Document};
10use web_sys::{Element, EventTarget};
11
12use kano::{Diff, Event, On, View};
13
14pub mod html;
15
16mod element;
17
18#[cfg(feature = "web-component")]
19pub mod web_component;
20
21mod js {
22    use super::*;
23
24    #[wasm_bindgen]
25    extern "C" {
26        // Use `js_namespace` here to bind `console.log(..)` instead of just
27        // `log(..)`
28        #[wasm_bindgen(js_namespace = console)]
29        pub fn log(s: &str);
30    }
31}
32
33pub struct Web {}
34
35impl Platform for Web {
36    type Cursor = WebCursor;
37
38    fn run_app<V: View<Self>, F: (FnOnce() -> V) + 'static>(func: F) -> anyhow::Result<()> {
39        console_error_panic_hook::set_once();
40
41        let mut cursor = WebCursor::Detached;
42        let state = kano::view::Func(func, ()).init(&mut cursor);
43
44        let WebCursor::Node(node, _) = cursor else {
45            return Err(anyhow!("No node rendered"));
46        };
47
48        document()
49            .body()
50            .unwrap()
51            .append_child(&node)
52            .map_err(|e| anyhow!("{e:?}"))?;
53
54        // Need to keep the initial state around, it keeps EventListeners alive
55        std::mem::forget(state);
56        Ok(())
57    }
58
59    fn log(s: &str) {
60        js::log(s);
61    }
62
63    fn spawn_task(task: impl std::future::Future<Output = ()> + 'static) {
64        wasm_bindgen_futures::spawn_local(task);
65    }
66}
67
68#[derive(Clone, Debug)]
69pub enum WebCursor {
70    Detached,
71    Node(web_sys::Node, Mode),
72    AfterLastChild(web_sys::Element, Mode),
73    AttrsOf(web_sys::Element, Mode),
74}
75
76#[derive(Clone, Copy, Debug)]
77pub enum Mode {
78    Append,
79    Diff,
80}
81
82impl WebCursor {
83    fn mode(&self) -> Mode {
84        match self {
85            WebCursor::AttrsOf(_, mode) => *mode,
86            WebCursor::AfterLastChild(_, mode) => *mode,
87            WebCursor::Node(_, mode) => *mode,
88            WebCursor::Detached => Mode::Append,
89        }
90    }
91}
92
93impl WebCursor {
94    fn element(&mut self, tag: &str) -> web_sys::Node {
95        match self.mode() {
96            Mode::Append => {
97                let element = document().create_element(tag).unwrap();
98                self.append_node(&element);
99                // log(&format!("new element cursor: {cursor:?}"));
100                element.into()
101            }
102            Mode::Diff => match self {
103                Self::Node(..) => self.handle(),
104                _ => panic!(),
105            },
106        }
107    }
108
109    fn enter_attrs(&mut self) {
110        match self {
111            WebCursor::Node(node, mode) => {
112                if let Some(element) = node.dyn_ref::<web_sys::Element>() {
113                    *self = WebCursor::AttrsOf(element.clone(), *mode);
114                } else {
115                    panic!("Non-element attributes");
116                }
117            }
118            WebCursor::AfterLastChild(..) => {
119                panic!("Entering attrs of empty children");
120            }
121            WebCursor::AttrsOf(..) | WebCursor::Detached => panic!(),
122        }
123    }
124
125    fn exit_attrs(&mut self) {
126        match self {
127            WebCursor::AttrsOf(element, mode) => {
128                *self = WebCursor::Node(element.dyn_ref::<web_sys::Node>().unwrap().clone(), *mode);
129            }
130            WebCursor::AfterLastChild(..) => panic!(),
131            WebCursor::Node(..) => panic!(),
132            WebCursor::Detached => panic!(),
133        }
134    }
135}
136
137impl kano::platform::Cursor for WebCursor {
138    type TextHandle = web_sys::Node;
139    type EventHandle = gloo::events::EventListener;
140
141    fn from_text_handle(handle: &web_sys::Node) -> Self {
142        Self::Node(handle.clone(), Mode::Append)
143    }
144
145    fn empty(&mut self) {
146        match &self {
147            WebCursor::AttrsOf(..) => {}
148            _ => {
149                let comment = document().create_comment("");
150                self.append_node(&comment);
151            }
152        }
153    }
154
155    fn text(&mut self, text: &str) -> web_sys::Node {
156        match self.mode() {
157            Mode::Append => {
158                let text_node = document().create_text_node(text);
159                self.append_node(&text_node);
160                text_node.into()
161            }
162            Mode::Diff => {
163                self.update_text(text);
164                self.handle()
165            }
166        }
167    }
168
169    fn update_text(&mut self, text: &str) {
170        match self {
171            Self::Node(node, _) => {
172                node.set_node_value(Some(text));
173            }
174            _ => panic!(),
175        }
176    }
177
178    fn on_event(&mut self, on_event: On) -> EventListener {
179        match self {
180            WebCursor::AttrsOf(element, _mode) => {
181                Web::log("on_event");
182                let event_target: &EventTarget = element.dyn_ref().unwrap();
183                let event_type = match on_event.event() {
184                    Event::Click => "click",
185                    Event::MouseOver => "mouseover",
186                };
187
188                EventListener::new(&event_target, event_type, move |_| {
189                    on_event.invoke();
190                })
191            }
192            WebCursor::Node(..) => panic!(),
193            WebCursor::Detached => panic!(),
194            WebCursor::AfterLastChild(..) => panic!(),
195        }
196    }
197
198    fn enter_children(&mut self) {
199        match self {
200            WebCursor::Node(node, mode) => {
201                if let Some(child) = node.first_child() {
202                    // log(&format!("enter child: had child {child:?}"));
203                    *self = WebCursor::Node(child, *mode);
204                } else if let Some(element) = node.dyn_ref::<web_sys::Element>() {
205                    // log(&format!("enter child: had no children"));
206                    *self = WebCursor::AfterLastChild(element.clone(), *mode);
207                } else {
208                    panic!("No children");
209                }
210            }
211            WebCursor::AfterLastChild(_, _) | WebCursor::Detached => {
212                panic!("Enter empty children");
213            }
214            WebCursor::AttrsOf(_, _) => {}
215        }
216    }
217
218    fn exit_children(&mut self) {
219        // log("exit child");
220        match self {
221            WebCursor::Node(node, mode) => {
222                let parent = node.parent_element().unwrap();
223                *self = WebCursor::Node(parent.dyn_into().unwrap(), *mode);
224            }
225            WebCursor::AfterLastChild(element, mode) => {
226                *self = WebCursor::Node(element.dyn_ref::<web_sys::Node>().unwrap().clone(), *mode);
227            }
228            WebCursor::AttrsOf(..) => {}
229            WebCursor::Detached => panic!("no children"),
230        }
231    }
232
233    fn next_sibling(&mut self) {
234        match &self {
235            WebCursor::AfterLastChild(..) => {}
236            WebCursor::Detached => panic!(),
237            WebCursor::AttrsOf(..) => {
238                // FIXME!
239            }
240            WebCursor::Node(node, mode) => {
241                if let Some(next) = node.next_sibling() {
242                    *self = WebCursor::Node(next, *mode);
243                } else {
244                    let parent = node.parent_element().unwrap();
245                    *self = WebCursor::AfterLastChild(parent, *mode);
246                }
247            }
248        }
249    }
250
251    fn remove(&mut self) {
252        match &self {
253            WebCursor::AfterLastChild(..) => panic!(),
254            WebCursor::Detached => panic!(),
255            WebCursor::AttrsOf(..) => {
256                todo!()
257            }
258            WebCursor::Node(node, mode) => {
259                let next = if let Some(next) = node.next_sibling() {
260                    WebCursor::Node(next, *mode)
261                } else {
262                    let parent = node.parent_element().unwrap();
263                    WebCursor::AfterLastChild(parent, *mode)
264                };
265
266                if let Some(element) = node.dyn_ref::<Element>() {
267                    element.remove();
268                } else {
269                    let parent = node.parent_element().unwrap();
270                    parent.remove_child(node).unwrap();
271                }
272
273                *self = next;
274            }
275        }
276    }
277
278    fn enter_diff(&mut self) {
279        match self {
280            WebCursor::AttrsOf(_, mode) => {
281                *mode = Mode::Diff;
282            }
283            WebCursor::AfterLastChild(_, mode) => {
284                *mode = Mode::Diff;
285            }
286            WebCursor::Node(_, mode) => {
287                *mode = Mode::Diff;
288            }
289            WebCursor::Detached => {}
290        }
291    }
292
293    fn exit_diff(&mut self) {
294        match self {
295            WebCursor::AttrsOf(_, mode) => {
296                *mode = Mode::Append;
297            }
298            WebCursor::AfterLastChild(_, mode) => {
299                *mode = Mode::Append;
300            }
301            WebCursor::Node(_, mode) => {
302                *mode = Mode::Append;
303            }
304            WebCursor::Detached => {}
305        }
306    }
307
308    fn replace(&mut self, func: impl FnOnce(&mut Self)) {
309        let mut replacement_cursor = WebCursor::Detached;
310        func(&mut replacement_cursor);
311
312        match (&self, replacement_cursor) {
313            (WebCursor::Detached, _) => {}
314            (WebCursor::Node(node, _mode), WebCursor::Node(replacement, mode2)) => {
315                let parent = node.parent_element().unwrap();
316                parent.replace_child(&replacement, node).unwrap();
317
318                *self = WebCursor::Node(replacement, mode2);
319            }
320            (WebCursor::Node(_node, _), WebCursor::Detached) => {
321                panic!();
322            }
323            (WebCursor::AttrsOf(_el, _), _) => {
324                panic!()
325            }
326            (WebCursor::AfterLastChild(..), _) => {
327                panic!();
328            }
329            _ => panic!(),
330        }
331    }
332}
333
334impl WebCursor {
335    fn append_node(&mut self, appendee: &web_sys::Node) {
336        // log(&format!("append at Cursor: {self:?}"));
337        match self {
338            Self::Detached => {
339                *self = Self::Node(appendee.clone(), Mode::Append);
340            }
341            Self::AfterLastChild(element, mode) => {
342                // log("append at empty");
343                element.append_child(appendee).expect("A");
344                *self = Self::Node(appendee.clone(), *mode);
345            }
346            Self::Node(node, mode) => {
347                // log("append after node");
348                node.parent_element()
349                    .expect("parent element of node")
350                    .insert_before(appendee, node.next_sibling().as_ref())
351                    .expect("insert_before");
352                *self = Self::Node(appendee.clone(), *mode);
353            }
354            Self::AttrsOf(..) => panic!("append to attrs"),
355        }
356    }
357
358    fn handle(&self) -> web_sys::Node {
359        match self {
360            Self::Detached => panic!(),
361            Self::Node(node, _) => node.clone(),
362            Self::AfterLastChild(..) => panic!(),
363            Self::AttrsOf(..) => panic!(),
364        }
365    }
366}
367
368fn document() -> Document {
369    window().unwrap().document().unwrap()
370}