diff --git a/Cargo.lock b/Cargo.lock index d585651..5d808a5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1080,6 +1080,7 @@ dependencies = [ "slotmap", "tokio", "viewbuilder-macros", + "web-sys", "winit", ] @@ -1224,6 +1225,14 @@ dependencies = [ "pkg-config", ] +[[package]] +name = "web-examples" +version = "0.1.0" +dependencies = [ + "concoct", + "viewbuilder", +] + [[package]] name = "web-sys" version = "0.3.66" diff --git a/Cargo.toml b/Cargo.toml index 23957ef..62ad9d0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,13 +9,15 @@ repository = "https://github.com/matthunz/viewbuilder" [workspace] members = [ ".", - "macros" + "macros", + "web-examples" ] [features] native = [] -web = [] -full = ["native"] +web = ["dep:web-sys"] +full = ["native", "web"] +web-sys = ["dep:web-sys"] [dependencies] kurbo = "0.9.5" @@ -24,6 +26,7 @@ slotmap = "1.0.7" winit = { version = "0.28.1" } concoct = { git = "https://github.com/concoct-rs/concoct" } viewbuilder-macros = { path = "macros" } +web-sys = { version = "0.3.66", optional = true, features = ["Document", "HtmlElement", "Text", "Window"] } [package.metadata.docs.rs] features = ["full"] @@ -31,4 +34,4 @@ rustdoc-args = ["--cfg", "docsrs"] [[example]] name = "app" -required-features = ["native"] \ No newline at end of file +required-features = ["native"] diff --git a/examples/app.rs b/examples/app.rs index 2912793..9c0ba01 100644 --- a/examples/app.rs +++ b/examples/app.rs @@ -1,7 +1,7 @@ use concoct::{Context, Handle, Object, Slot}; use viewbuilder::native::{ view::{LinearLayout, Text}, - window, UserInterface, Window, + window, Window, }; use winit::dpi::PhysicalSize; diff --git a/macros/src/lib.rs b/macros/src/lib.rs index c110599..9de276d 100644 --- a/macros/src/lib.rs +++ b/macros/src/lib.rs @@ -8,12 +8,12 @@ pub fn main(_attrs: TokenStream, input: TokenStream) -> TokenStream { let stmts = input.block.stmts; let expanded = quote! { fn main() { - let ui = UserInterface::default(); - let _guard = ui.enter(); + let viewbuilder_macros_ui = viewbuilder::native::UserInterface::default(); + let _guard = viewbuilder_macros_ui.enter(); #(#stmts)* - ui.run() + viewbuilder_macros_ui.run() } }; expanded.into() diff --git a/src/web/mod.rs b/src/web/mod.rs index 8b13789..064ddf4 100644 --- a/src/web/mod.rs +++ b/src/web/mod.rs @@ -1 +1,152 @@ +use concoct::{Handle, Object, Slot}; +use web_sys::wasm_bindgen::JsCast; +pub trait View { + fn node(&self) -> &web_sys::Node; + + fn set_parent(&mut self, parent: web_sys::Element); +} + +pub trait IntoView { + type View: View; + + fn into_view(self) -> Handle; +} + +impl IntoView for V +where + V: View + Object + 'static, +{ + type View = V; + + fn into_view(self) -> Handle { + self.spawn() + } +} + +impl IntoView for Handle { + type View = V; + + fn into_view(self) -> Handle { + self + } +} + +pub trait ViewGroup { + type Handles; + + fn view_group(self) -> Self::Handles; +} + +impl ViewGroup for V { + type Handles = Handle; + + fn view_group(self) -> Self::Handles { + self.into_view() + } +} + +impl ViewGroup for (A, B, C) { + type Handles = (Handle, Handle, Handle); + + fn view_group(self) -> Self::Handles { + (self.0.into_view(), self.1.into_view(), self.2.into_view()) + } +} + +pub struct MouseEvent; + +pub struct ElementBuilder {} + +impl ElementBuilder { + pub fn on_click(&mut self, _f: Handle>) -> &mut Self { + self + } + + pub fn child(&mut self, _view: impl ViewGroup) -> &mut Self { + self + } + + pub fn build(&mut self) -> Element { + todo!() + } +} + +pub struct Element { + pub element: web_sys::Element, + parent: Option, +} + +impl Element { + pub fn new(tag: &str) -> Self { + Self { + element: web_sys::window() + .unwrap() + .document() + .unwrap() + .create_element(tag) + .unwrap(), + parent: None, + } + } + + pub fn builder() -> ElementBuilder { + ElementBuilder {} + } +} + +impl Object for Element {} + +impl View for Element { + fn node(&self) -> &web_sys::Node { + self.element.unchecked_ref() + } + + fn set_parent(&mut self, parent: web_sys::Element) { + self.parent = Some(parent.clone()); + parent.append_child(&self.element).unwrap(); + } +} + +pub struct AppendChild(pub Handle); + +impl Slot> for Element { + fn handle(&mut self, _handle: concoct::Context, msg: AppendChild) { + self.element.append_child(msg.0.borrow().node()).unwrap(); + } +} + +pub struct Text { + pub node: web_sys::Text, + parent: Option, +} + +impl Text { + pub fn new(content: &str) -> Self { + Self { + node: web_sys::window() + .unwrap() + .document() + .unwrap() + .create_text_node(content), + parent: None, + } + } +} + +impl View for Text { + fn node(&self) -> &web_sys::Node { + &self.node + } + + fn set_parent(&mut self, parent: web_sys::Element) { + self.parent = Some(parent.clone()); + parent.append_child(&self.node).unwrap(); + } +} + +impl Object for Text {} + +impl Slot for Text { + fn handle(&mut self, _handle: concoct::Context, _msg: String) {} +} diff --git a/web-examples/Cargo.toml b/web-examples/Cargo.toml new file mode 100644 index 0000000..d834740 --- /dev/null +++ b/web-examples/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "web-examples" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +viewbuilder = { path = "../", features = ["full"] } +concoct = { git = "https://github.com/concoct-rs/concoct" } diff --git a/web-examples/src/main.rs b/web-examples/src/main.rs new file mode 100644 index 0000000..29be5c2 --- /dev/null +++ b/web-examples/src/main.rs @@ -0,0 +1,75 @@ +use concoct::{Handle, Object, Slot}; +use viewbuilder::web::{self, Element, Text}; + +#[derive(Clone, Copy)] +enum Message { + Increment, + Decrement, +} + +struct Counter { + value: i32, + text: Handle, +} + +impl Object for Counter {} + +impl Slot for Counter { + fn handle(&mut self, _cx: concoct::Context, msg: Message) { + match msg { + Message::Increment => self.value += 1, + Message::Decrement => self.value -= 1, + }; + self.text.send(self.value.to_string()); + } +} + +struct CounterButton { + msg: Message, + counter: Handle, +} + +impl Object for CounterButton {} + +impl Slot for CounterButton { + fn handle(&mut self, _cx: concoct::Context, _msg: web::MouseEvent) { + self.counter.send(self.msg); + } +} + +#[viewbuilder::main] +fn main() { + let text = Text::new("0").spawn(); + + let counter = Counter { + value: 0, + text: text.clone(), + } + .spawn(); + + let increment_button = CounterButton { + msg: Message::Increment, + counter: counter.clone(), + } + .spawn(); + let decrement_button = CounterButton { + msg: Message::Decrement, + counter, + } + .spawn(); + + Element::builder() + .child(( + text, + Element::builder() + .on_click(increment_button) + .child(Text::new("Up High!")) + .build(), + Element::builder() + .on_click(decrement_button) + .child(Text::new("Down Low!")) + .build(), + )) + .build() + .spawn(); +}