diff --git a/Cargo.lock b/Cargo.lock index 60ce8ae23..3c9690a1a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -589,7 +589,7 @@ dependencies = [ "console_error_panic_hook", "wasm-bindgen", "web-sys", - "xilem_html", + "xilem_web", ] [[package]] @@ -599,7 +599,7 @@ dependencies = [ "console_error_panic_hook", "wasm-bindgen", "web-sys", - "xilem_html", + "xilem_web", ] [[package]] @@ -1437,7 +1437,7 @@ dependencies = [ "console_error_panic_hook", "wasm-bindgen", "web-sys", - "xilem_html", + "xilem_web", ] [[package]] @@ -2136,7 +2136,7 @@ dependencies = [ "console_error_panic_hook", "wasm-bindgen", "web-sys", - "xilem_svg", + "xilem_web", ] [[package]] @@ -2271,7 +2271,7 @@ dependencies = [ "tracing-wasm", "wasm-bindgen", "web-sys", - "xilem_html", + "xilem_web", ] [[package]] @@ -3051,7 +3051,7 @@ name = "xilem_core" version = "0.1.0" [[package]] -name = "xilem_html" +name = "xilem_web" version = "0.1.0" dependencies = [ "bitflags 2.3.3", @@ -3059,16 +3059,6 @@ dependencies = [ "kurbo 0.9.5", "log", "paste", - "wasm-bindgen", - "web-sys", - "xilem_core", -] - -[[package]] -name = "xilem_svg" -version = "0.1.0" -dependencies = [ - "bitflags 2.3.3", "peniko 0.1.0 (git+https://github.com/linebender/peniko?rev=629fc3325b016a8c98b1cd6204cb4ddf1c6b3daa)", "wasm-bindgen", "web-sys", diff --git a/Cargo.toml b/Cargo.toml index 43c28c360..1ffa1057d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,13 +1,12 @@ [workspace] members = [ "crates/xilem_core", - "crates/xilem_html", - "crates/xilem_html/web_examples/counter", - "crates/xilem_html/web_examples/counter_custom_element", - "crates/xilem_html/web_examples/todomvc", - "crates/xilem_html/web_examples/mathml_svg", - "crates/xilem_svg", - "crates/xilem_svg/web_examples/svgtoy", + "crates/xilem_web", + "crates/xilem_web/web_examples/counter", + "crates/xilem_web/web_examples/counter_custom_element", + "crates/xilem_web/web_examples/todomvc", + "crates/xilem_web/web_examples/mathml_svg", + "crates/xilem_web/web_examples/svgtoy", ] [workspace.package] diff --git a/crates/xilem_svg/.gitignore b/crates/xilem_svg/.gitignore deleted file mode 100644 index 849ddff3b..000000000 --- a/crates/xilem_svg/.gitignore +++ /dev/null @@ -1 +0,0 @@ -dist/ diff --git a/crates/xilem_svg/Cargo.toml b/crates/xilem_svg/Cargo.toml deleted file mode 100644 index 86565dfb7..000000000 --- a/crates/xilem_svg/Cargo.toml +++ /dev/null @@ -1,37 +0,0 @@ -[package] -name = "xilem_svg" -version = "0.1.0" -description = "SVG DOM frontend for the Xilem Rust UI framework." -keywords = ["xilem", "svg", "dom", "web", "ui"] -categories = ["gui", "web-programming"] -publish = false # Until it's ready -license.workspace = true -edition.workspace = true -homepage.workspace = true -repository.workspace = true - -[package.metadata.docs.rs] -all-features = true -rustdoc-args = ["--cfg", "docsrs"] -default-target = "x86_64-pc-windows-msvc" -# rustdoc-scrape-examples tracking issue https://github.com/rust-lang/rust/issues/88791 -cargo-args = ["-Zunstable-options", "-Zrustdoc-scrape-examples"] - -[dependencies] -xilem_core.workspace = true -bitflags = "2" -wasm-bindgen = "0.2.84" -peniko = { git = "https://github.com/linebender/peniko", rev = "629fc3325b016a8c98b1cd6204cb4ddf1c6b3daa" } - -[dependencies.web-sys] -version = "0.3.4" -features = [ - "console", - "Document", - "Element", - "HtmlElement", - "Node", - "PointerEvent", - "SvgElement", - "Window", -] diff --git a/crates/xilem_svg/README.md b/crates/xilem_svg/README.md deleted file mode 100644 index a9cf3661a..000000000 --- a/crates/xilem_svg/README.md +++ /dev/null @@ -1,7 +0,0 @@ -# Xilemsvg prototype - -This is a proof of concept showing how to use `xilem_core` to render interactive vector graphics into SVG DOM nodes, running in a browser. It is provided as a library and some examples. - -The easiest way to run the examples is to use [Trunk]. Go into the appropriate subdirectory of `web_examples`, run `trunk serve`, then navigate the browser to the link provided (usually `http://localhost:8080`). - -[Trunk]: https://trunkrs.dev/ diff --git a/crates/xilem_svg/src/app.rs b/crates/xilem_svg/src/app.rs deleted file mode 100644 index d3cf25ba3..000000000 --- a/crates/xilem_svg/src/app.rs +++ /dev/null @@ -1,119 +0,0 @@ -// Copyright 2023 the Druid Authors. -// SPDX-License-Identifier: Apache-2.0 - -use std::{cell::RefCell, rc::Rc}; - -use crate::{ - context::Cx, - view::{DomElement, View}, - Message, -}; -use xilem_core::Id; - -pub struct App, F: FnMut(&mut T) -> V>(Rc>>); - -struct AppInner, F: FnMut(&mut T) -> V> { - data: T, - app_logic: F, - view: Option, - id: Option, - state: Option, - element: Option, - cx: Cx, -} - -pub(crate) trait AppRunner { - fn handle_message(&self, message: Message); - - fn clone_box(&self) -> Box; -} - -impl + 'static, F: FnMut(&mut T) -> V + 'static> Clone for App { - fn clone(&self) -> Self { - App(self.0.clone()) - } -} - -impl + 'static, F: FnMut(&mut T) -> V + 'static> App { - pub fn new(data: T, app_logic: F) -> Self { - let inner = AppInner::new(data, app_logic); - let app = App(Rc::new(RefCell::new(inner))); - app.0.borrow_mut().cx.set_runner(app.clone()); - app - } - - pub fn run(self) { - self.0.borrow_mut().ensure_app(); - // Latter may not be necessary, we have an rc loop. - std::mem::forget(self) - } -} - -impl, F: FnMut(&mut T) -> V> AppInner { - pub fn new(data: T, app_logic: F) -> Self { - let cx = Cx::new(); - AppInner { - data, - app_logic, - view: None, - id: None, - state: None, - element: None, - cx, - } - } - - fn ensure_app(&mut self) { - if self.view.is_none() { - let view = (self.app_logic)(&mut self.data); - let (id, state, element) = view.build(&mut self.cx); - self.view = Some(view); - self.id = Some(id); - self.state = Some(state); - - let body = self.cx.document().body().unwrap(); - let svg = self - .cx - .document() - .create_element_ns(Some("http://www.w3.org/2000/svg"), "svg") - .unwrap(); - svg.set_attribute("width", "800").unwrap(); - svg.set_attribute("height", "600").unwrap(); - body.append_child(&svg).unwrap(); - svg.append_child(element.as_element_ref()).unwrap(); - self.element = Some(element); - } - } -} - -impl + 'static, F: FnMut(&mut T) -> V + 'static> AppRunner for App { - // For now we handle the message synchronously, but it would also - // make sense to to batch them (for example with requestAnimFrame). - fn handle_message(&self, message: Message) { - let mut inner_guard = self.0.borrow_mut(); - let inner = &mut *inner_guard; - if let Some(view) = &mut inner.view { - view.message( - &message.id_path[1..], - inner.state.as_mut().unwrap(), - message.body, - &mut inner.data, - ); - let new_view = (inner.app_logic)(&mut inner.data); - let _changed = new_view.rebuild( - &mut inner.cx, - view, - inner.id.as_mut().unwrap(), - inner.state.as_mut().unwrap(), - inner.element.as_mut().unwrap(), - ); - // Not sure we have to do anything on changed, the rebuild - // traversal should cause the DOM to update. - *view = new_view; - } - } - - fn clone_box(&self) -> Box { - Box::new(self.clone()) - } -} diff --git a/crates/xilem_svg/src/class.rs b/crates/xilem_svg/src/class.rs deleted file mode 100644 index ac7ad4add..000000000 --- a/crates/xilem_svg/src/class.rs +++ /dev/null @@ -1,73 +0,0 @@ -// Copyright 2023 the Druid Authors. -// SPDX-License-Identifier: Apache-2.0 - -use std::{any::Any, marker::PhantomData}; - -use xilem_core::{Id, MessageResult}; - -use crate::{ - context::{ChangeFlags, Cx}, - view::{DomElement, View, ViewMarker}, -}; - -pub struct Class { - child: V, - // This could reasonably be static Cow also, but keep things simple - class: String, - phantom: PhantomData, -} - -pub fn class(child: V, class: impl Into) -> Class { - Class { - child, - class: class.into(), - phantom: Default::default(), - } -} - -impl ViewMarker for Class {} - -// TODO: make generic over A (probably requires Phantom) -impl> View for Class { - type State = V::State; - type Element = V::Element; - - fn build(&self, cx: &mut Cx) -> (Id, Self::State, Self::Element) { - let (id, child_state, element) = self.child.build(cx); - element - .as_element_ref() - .set_attribute("class", &self.class) - .unwrap(); - (id, child_state, element) - } - - fn rebuild( - &self, - cx: &mut Cx, - prev: &Self, - id: &mut Id, - state: &mut Self::State, - element: &mut V::Element, - ) -> ChangeFlags { - let prev_id = *id; - let mut changed = self.child.rebuild(cx, &prev.child, id, state, element); - if self.class != prev.class || prev_id != *id { - element - .as_element_ref() - .set_attribute("class", &self.class) - .unwrap(); - changed.insert(ChangeFlags::OTHER_CHANGE); - } - changed - } - - fn message( - &self, - id_path: &[Id], - state: &mut Self::State, - message: Box, - app_state: &mut T, - ) -> MessageResult<()> { - self.child.message(id_path, state, message, app_state) - } -} diff --git a/crates/xilem_svg/src/clicked.rs b/crates/xilem_svg/src/clicked.rs deleted file mode 100644 index 8fe513eb0..000000000 --- a/crates/xilem_svg/src/clicked.rs +++ /dev/null @@ -1,91 +0,0 @@ -// Copyright 2023 the Druid Authors. -// SPDX-License-Identifier: Apache-2.0 - -use std::{any::Any, marker::PhantomData}; - -use wasm_bindgen::{prelude::Closure, JsCast}; -use web_sys::SvgElement; - -use xilem_core::{Id, MessageResult}; - -use crate::{ - context::{ChangeFlags, Cx}, - view::{DomElement, View, ViewMarker}, -}; - -pub struct Clicked { - child: V, - callback: F, - phantom: PhantomData, -} - -pub struct ClickedState { - // Closure is retained so it can be called by environment - #[allow(unused)] - closure: Closure, - child_state: S, -} - -struct ClickedMsg; - -pub fn clicked>(child: V, callback: F) -> Clicked { - Clicked { - child, - callback, - phantom: Default::default(), - } -} - -impl ViewMarker for Clicked {} - -impl> View for Clicked { - type State = ClickedState; - - type Element = V::Element; - - fn build(&self, cx: &mut Cx) -> (Id, Self::State, Self::Element) { - let (id, child_state, element) = self.child.build(cx); - let thunk = cx.with_id(id, |cx| cx.message_thunk()); - let closure = - Closure::wrap(Box::new(move || thunk.push_message(ClickedMsg)) as Box); - element - .as_element_ref() - .dyn_ref::() - .expect("not an svg element") - .set_onclick(Some(closure.as_ref().unchecked_ref())); - let state = ClickedState { - closure, - child_state, - }; - (id, state, element) - } - - fn rebuild( - &self, - cx: &mut Cx, - prev: &Self, - id: &mut Id, - state: &mut Self::State, - element: &mut Self::Element, - ) -> ChangeFlags { - // TODO: if the child id changes (as can happen with AnyView), reinstall closure - self.child - .rebuild(cx, &prev.child, id, &mut state.child_state, element) - } - - fn message( - &self, - id_path: &[Id], - state: &mut Self::State, - message: Box, - app_state: &mut T, - ) -> MessageResult<()> { - if message.downcast_ref::().is_some() { - (self.callback)(app_state); - MessageResult::Action(()) - } else { - self.child - .message(id_path, &mut state.child_state, message, app_state) - } - } -} diff --git a/crates/xilem_svg/src/common_attrs.rs b/crates/xilem_svg/src/common_attrs.rs deleted file mode 100644 index a11d82e51..000000000 --- a/crates/xilem_svg/src/common_attrs.rs +++ /dev/null @@ -1,163 +0,0 @@ -// Copyright 2023 the Druid Authors. -// SPDX-License-Identifier: Apache-2.0 - -use std::{any::Any, marker::PhantomData}; - -use peniko::Brush; -use xilem_core::{Id, MessageResult}; - -use crate::{ - context::{ChangeFlags, Cx}, - view::{DomElement, View, ViewMarker}, -}; - -pub struct Fill { - child: V, - brush: Brush, - phantom: PhantomData, -} - -pub struct Stroke { - child: V, - brush: Brush, - style: peniko::kurbo::Stroke, - phantom: PhantomData, -} - -pub fn fill(child: V, brush: impl Into) -> Fill { - Fill { - child, - brush: brush.into(), - phantom: Default::default(), - } -} - -pub fn stroke( - child: V, - brush: impl Into, - style: peniko::kurbo::Stroke, -) -> Stroke { - Stroke { - child, - brush: brush.into(), - style, - phantom: Default::default(), - } -} - -fn brush_to_string(brush: &Brush) -> String { - match brush { - Brush::Solid(color) => { - if color.a == 0 { - "none".into() - } else { - format!("#{:02x}{:02x}{:02x}", color.r, color.g, color.b) - } - } - _ => todo!("gradients not implemented"), - } -} - -impl ViewMarker for Fill {} - -// TODO: make generic over A (probably requires Phantom) -impl> View for Fill { - type State = V::State; - type Element = V::Element; - - fn build(&self, cx: &mut Cx) -> (Id, Self::State, Self::Element) { - let (id, child_state, element) = self.child.build(cx); - element - .as_element_ref() - .set_attribute("fill", &brush_to_string(&self.brush)) - .unwrap(); - (id, child_state, element) - } - - fn rebuild( - &self, - cx: &mut Cx, - prev: &Self, - id: &mut Id, - state: &mut Self::State, - element: &mut V::Element, - ) -> ChangeFlags { - let prev_id = *id; - let mut changed = self.child.rebuild(cx, &prev.child, id, state, element); - if self.brush != prev.brush || prev_id != *id { - element - .as_element_ref() - .set_attribute("fill", &brush_to_string(&self.brush)) - .unwrap(); - changed.insert(ChangeFlags::OTHER_CHANGE); - } - changed - } - - fn message( - &self, - id_path: &[Id], - state: &mut Self::State, - message: Box, - app_state: &mut T, - ) -> MessageResult<()> { - self.child.message(id_path, state, message, app_state) - } -} - -impl ViewMarker for Stroke {} - -// TODO: make generic over A (probably requires Phantom) -impl> View for Stroke { - type State = V::State; - type Element = V::Element; - - fn build(&self, cx: &mut Cx) -> (Id, Self::State, Self::Element) { - let (id, child_state, element) = self.child.build(cx); - element - .as_element_ref() - .set_attribute("stroke", &brush_to_string(&self.brush)) - .unwrap(); - element - .as_element_ref() - .set_attribute("stroke-width", &format!("{}", self.style.width)) - .unwrap(); - (id, child_state, element) - } - - fn rebuild( - &self, - cx: &mut Cx, - prev: &Self, - id: &mut Id, - state: &mut Self::State, - element: &mut V::Element, - ) -> ChangeFlags { - let prev_id = *id; - let mut changed = self.child.rebuild(cx, &prev.child, id, state, element); - if self.brush != prev.brush || prev_id != *id { - element - .as_element_ref() - .set_attribute("stroke", &brush_to_string(&self.brush)) - .unwrap(); - changed.insert(ChangeFlags::OTHER_CHANGE); - } - if self.style.width != prev.style.width || prev_id != *id { - element - .as_element_ref() - .set_attribute("stroke-width", &format!("{}", self.style.width)) - .unwrap(); - } - changed - } - - fn message( - &self, - id_path: &[Id], - state: &mut Self::State, - message: Box, - app_state: &mut T, - ) -> MessageResult<()> { - self.child.message(id_path, state, message, app_state) - } -} diff --git a/crates/xilem_svg/src/context.rs b/crates/xilem_svg/src/context.rs deleted file mode 100644 index 82bc75a47..000000000 --- a/crates/xilem_svg/src/context.rs +++ /dev/null @@ -1,110 +0,0 @@ -use std::any::Any; - -use bitflags::bitflags; -use web_sys::Document; - -use xilem_core::{Id, IdPath}; - -use crate::{app::AppRunner, Message}; - -// Note: xilem has derive Clone here. Not sure. -pub struct Cx { - id_path: IdPath, - document: Document, - app_ref: Option>, -} - -pub(crate) struct MessageThunk { - id_path: IdPath, - app_ref: Box, -} - -bitflags! { - #[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)] - pub struct ChangeFlags: u32 { - const STRUCTURE = 1; - const OTHER_CHANGE = 2; - } -} - -impl ChangeFlags { - pub fn tree_structure() -> Self { - ChangeFlags::STRUCTURE - } -} - -impl Cx { - pub fn new() -> Self { - let window = web_sys::window().expect("no global `window` exists"); - let document = window.document().expect("should have a document on window"); - Cx { - id_path: Vec::new(), - document, - app_ref: None, - } - } - - pub fn push(&mut self, id: Id) { - self.id_path.push(id); - } - - pub fn pop(&mut self) { - self.id_path.pop(); - } - - #[allow(unused)] - pub fn id_path(&self) -> &IdPath { - &self.id_path - } - - /// Run some logic with an id added to the id path. - /// - /// This is an ergonomic helper that ensures proper nesting of the id path. - pub fn with_id T>(&mut self, id: Id, f: F) -> T { - self.push(id); - let result = f(self); - self.pop(); - result - } - - /// Allocate a new id and run logic with the new id added to the id path. - /// - /// Also an ergonomic helper. - pub fn with_new_id T>(&mut self, f: F) -> (Id, T) { - let id = Id::next(); - self.push(id); - let result = f(self); - self.pop(); - (id, result) - } - - pub fn document(&self) -> &Document { - &self.document - } - - pub(crate) fn message_thunk(&self) -> MessageThunk { - MessageThunk { - id_path: self.id_path.clone(), - app_ref: self.app_ref.as_ref().unwrap().clone_box(), - } - } - pub(crate) fn set_runner(&mut self, runner: impl AppRunner + 'static) { - self.app_ref = Some(Box::new(runner)); - } -} - -impl Default for Cx { - fn default() -> Self { - Self::new() - } -} - -impl MessageThunk { - pub fn push_message(&self, message_body: impl Any + Send + 'static) { - let message = Message { - id_path: self.id_path.clone(), - body: Box::new(message_body), - }; - self.app_ref.handle_message(message); - } -} diff --git a/crates/xilem_svg/src/group.rs b/crates/xilem_svg/src/group.rs deleted file mode 100644 index 4cb25b2d6..000000000 --- a/crates/xilem_svg/src/group.rs +++ /dev/null @@ -1,91 +0,0 @@ -// Copyright 2023 the Druid Authors. -// SPDX-License-Identifier: Apache-2.0 - -//! Group - -use web_sys::Element; - -use xilem_core::{Id, MessageResult, VecSplice}; - -use crate::{ - context::{ChangeFlags, Cx}, - view::{Pod, View, ViewMarker, ViewSequence}, -}; - -pub struct Group { - children: VS, -} - -pub struct GroupState { - state: S, - elements: Vec, -} - -pub fn group(children: VS) -> Group { - Group { children } -} - -impl ViewMarker for Group {} - -impl View for Group -where - VS: ViewSequence, -{ - type State = GroupState; - type Element = web_sys::Element; - - fn build(&self, cx: &mut Cx) -> (Id, Self::State, Element) { - let el = cx - .document() - .create_element_ns(Some("http://www.w3.org/2000/svg"), "g") - .unwrap(); - let mut elements = vec![]; - let (id, state) = cx.with_new_id(|cx| self.children.build(cx, &mut elements)); - for child in &elements { - el.append_child(child.0.as_element_ref()).unwrap(); - } - let group_state = GroupState { state, elements }; - (id, group_state, el) - } - - fn rebuild( - &self, - cx: &mut Cx, - prev: &Self, - id: &mut Id, - state: &mut Self::State, - element: &mut Element, - ) -> ChangeFlags { - let mut scratch = vec![]; - let mut splice = VecSplice::new(&mut state.elements, &mut scratch); - let mut changed = cx.with_id(*id, |cx| { - self.children - .rebuild(cx, &prev.children, &mut state.state, &mut splice) - }); - if changed.contains(ChangeFlags::STRUCTURE) { - // This is crude and will result in more DOM traffic than needed. - // The right thing to do is diff the new state of the children id - // vector against the old, and derive DOM mutations from that. - while let Some(child) = element.first_child() { - _ = element.remove_child(&child); - } - for child in &state.elements { - _ = element.append_child(child.0.as_element_ref()); - } - // TODO: we may want to propagate that something changed - changed.remove(ChangeFlags::STRUCTURE); - } - changed - } - - fn message( - &self, - id_path: &[Id], - state: &mut Self::State, - message: Box, - app_state: &mut T, - ) -> MessageResult { - self.children - .message(id_path, &mut state.state, message, app_state) - } -} diff --git a/crates/xilem_svg/src/kurbo_shape.rs b/crates/xilem_svg/src/kurbo_shape.rs deleted file mode 100644 index 30a266935..000000000 --- a/crates/xilem_svg/src/kurbo_shape.rs +++ /dev/null @@ -1,253 +0,0 @@ -// Copyright 2023 the Druid Authors. -// SPDX-License-Identifier: Apache-2.0 - -//! Implementation of the View trait for various kurbo shapes. - -use peniko::kurbo::{BezPath, Circle, Line, Rect}; -use web_sys::Element; - -use xilem_core::{Id, MessageResult}; - -use crate::{ - context::{ChangeFlags, Cx}, - view::{View, ViewMarker}, -}; - -impl ViewMarker for Line {} - -impl View for Line { - type State = (); - type Element = Element; - - fn build(&self, cx: &mut Cx) -> (Id, Self::State, Element) { - let el = cx - .document() - .create_element_ns(Some("http://www.w3.org/2000/svg"), "line") - .unwrap(); - el.set_attribute("x1", &format!("{}", self.p0.x)).unwrap(); - el.set_attribute("y1", &format!("{}", self.p0.y)).unwrap(); - el.set_attribute("x2", &format!("{}", self.p1.x)).unwrap(); - el.set_attribute("y2", &format!("{}", self.p1.y)).unwrap(); - let id = Id::next(); - (id, (), el) - } - - fn rebuild( - &self, - _cx: &mut Cx, - prev: &Self, - _id: &mut Id, - _state: &mut Self::State, - element: &mut Element, - ) -> ChangeFlags { - let mut is_changed = ChangeFlags::default(); - if self.p0.x != prev.p0.x { - element - .set_attribute("x1", &format!("{}", self.p0.x)) - .unwrap(); - is_changed |= ChangeFlags::OTHER_CHANGE; - } - if self.p0.y != prev.p0.y { - element - .set_attribute("y1", &format!("{}", self.p0.y)) - .unwrap(); - is_changed |= ChangeFlags::OTHER_CHANGE; - } - if self.p1.x != prev.p1.x { - element - .set_attribute("x2", &format!("{}", self.p1.x)) - .unwrap(); - is_changed |= ChangeFlags::OTHER_CHANGE; - } - if self.p1.y != prev.p1.y { - element - .set_attribute("y2", &format!("{}", self.p1.y)) - .unwrap(); - is_changed |= ChangeFlags::OTHER_CHANGE; - } - is_changed - } - - fn message( - &self, - _id_path: &[Id], - _state: &mut Self::State, - message: Box, - _app_state: &mut T, - ) -> MessageResult<()> { - MessageResult::Stale(message) - } -} - -impl ViewMarker for Rect {} - -impl View for Rect { - type State = (); - type Element = Element; - - fn build(&self, cx: &mut Cx) -> (Id, Self::State, Element) { - let el = cx - .document() - .create_element_ns(Some("http://www.w3.org/2000/svg"), "rect") - .unwrap(); - el.set_attribute("x", &format!("{}", self.x0)).unwrap(); - el.set_attribute("y", &format!("{}", self.y0)).unwrap(); - let size = self.size(); - el.set_attribute("width", &format!("{}", size.width)) - .unwrap(); - el.set_attribute("height", &format!("{}", size.height)) - .unwrap(); - let id = Id::next(); - (id, (), el) - } - - fn rebuild( - &self, - _cx: &mut Cx, - prev: &Self, - _id: &mut Id, - _state: &mut Self::State, - element: &mut Element, - ) -> ChangeFlags { - let mut is_changed = ChangeFlags::default(); - if self.x0 != prev.x0 { - element.set_attribute("x", &format!("{}", self.x0)).unwrap(); - is_changed |= ChangeFlags::OTHER_CHANGE; - } - if self.y0 != prev.y0 { - element.set_attribute("y", &format!("{}", self.y0)).unwrap(); - is_changed |= ChangeFlags::OTHER_CHANGE; - } - let size = self.size(); - let prev_size = prev.size(); - if size.width != prev_size.width { - element - .set_attribute("width", &format!("{}", size.width)) - .unwrap(); - is_changed |= ChangeFlags::OTHER_CHANGE; - } - if size.height != prev_size.height { - element - .set_attribute("height", &format!("{}", size.height)) - .unwrap(); - is_changed |= ChangeFlags::OTHER_CHANGE; - } - is_changed - } - - fn message( - &self, - _id_path: &[Id], - _state: &mut Self::State, - message: Box, - _app_state: &mut T, - ) -> MessageResult<()> { - MessageResult::Stale(message) - } -} - -impl ViewMarker for Circle {} - -impl View for Circle { - type State = (); - type Element = Element; - - fn build(&self, cx: &mut Cx) -> (Id, Self::State, Element) { - let el = cx - .document() - .create_element_ns(Some("http://www.w3.org/2000/svg"), "circle") - .unwrap(); - el.set_attribute("cx", &format!("{}", self.center.x)) - .unwrap(); - el.set_attribute("cy", &format!("{}", self.center.y)) - .unwrap(); - el.set_attribute("r", &format!("{}", self.radius)).unwrap(); - let id = Id::next(); - (id, (), el) - } - - fn rebuild( - &self, - _cx: &mut Cx, - prev: &Self, - _id: &mut Id, - _state: &mut Self::State, - element: &mut Element, - ) -> ChangeFlags { - let mut is_changed = ChangeFlags::default(); - if self.center.x != prev.center.x { - element - .set_attribute("cx", &format!("{}", self.center.x)) - .unwrap(); - is_changed |= ChangeFlags::OTHER_CHANGE; - } - if self.center.y != prev.center.y { - element - .set_attribute("cy", &format!("{}", self.center.y)) - .unwrap(); - is_changed |= ChangeFlags::OTHER_CHANGE; - } - if self.radius != prev.radius { - element - .set_attribute("r", &format!("{}", self.radius)) - .unwrap(); - is_changed |= ChangeFlags::OTHER_CHANGE; - } - is_changed - } - - fn message( - &self, - _id_path: &[Id], - _state: &mut Self::State, - message: Box, - _app_state: &mut T, - ) -> MessageResult<()> { - MessageResult::Stale(message) - } -} - -impl ViewMarker for BezPath {} - -impl View for BezPath { - type State = (); - type Element = Element; - - fn build(&self, cx: &mut Cx) -> (Id, Self::State, Element) { - let el = cx - .document() - .create_element_ns(Some("http://www.w3.org/2000/svg"), "path") - .unwrap(); - el.set_attribute("d", &self.to_svg()).unwrap(); - let id = Id::next(); - (id, (), el) - } - - fn rebuild( - &self, - _d: &mut Cx, - prev: &Self, - _id: &mut Id, - _state: &mut Self::State, - element: &mut Element, - ) -> ChangeFlags { - let mut is_changed = ChangeFlags::default(); - if self != prev { - element.set_attribute("d", &self.to_svg()).unwrap(); - is_changed |= ChangeFlags::OTHER_CHANGE; - } - is_changed - } - - fn message( - &self, - _id_path: &[Id], - _state: &mut Self::State, - message: Box, - _app_state: &mut T, - ) -> MessageResult<()> { - MessageResult::Stale(message) - } -} - -// TODO: RoundedRect diff --git a/crates/xilem_svg/src/lib.rs b/crates/xilem_svg/src/lib.rs deleted file mode 100644 index f8cb7d90a..000000000 --- a/crates/xilem_svg/src/lib.rs +++ /dev/null @@ -1,29 +0,0 @@ -// Copyright 2023 the Druid Authors. -// SPDX-License-Identifier: Apache-2.0 - -//! An experimental library for making reactive SVG graphics. - -mod app; -mod class; -mod clicked; -mod common_attrs; -mod context; -mod group; -mod kurbo_shape; -mod pointer; -mod view; -mod view_ext; - -pub use peniko; -pub use peniko::kurbo; - -pub use app::App; -pub use context::Cx; -pub use group::group; -pub use pointer::{PointerDetails, PointerMsg}; -pub use view::{AnyView, Memoize, View, ViewMarker, ViewSequence}; -pub use view_ext::ViewExt; - -pub use context::ChangeFlags; - -xilem_core::message!(Send); diff --git a/crates/xilem_svg/src/view.rs b/crates/xilem_svg/src/view.rs deleted file mode 100644 index f847e9a12..000000000 --- a/crates/xilem_svg/src/view.rs +++ /dev/null @@ -1,80 +0,0 @@ -// Copyright 2023 the Druid Authors. -// SPDX-License-Identifier: Apache-2.0 - -//! Integration with xilem_core. This instantiates the View and related -//! traits for DOM node generation. - -use std::ops::Deref; - -use crate::{context::Cx, ChangeFlags}; - -// A possible refinement of xilem_core is to allow a single concrete type -// for a view element, rather than an associated type with a bound. -pub trait DomElement { - fn into_pod(self) -> Pod; - fn as_element_ref(&self) -> &web_sys::Element; -} - -pub trait AnyElement { - fn as_any_mut(&mut self) -> &mut dyn std::any::Any; - - fn as_element_ref(&self) -> &web_sys::Element; -} - -impl AnyElement for web_sys::Element { - fn as_any_mut(&mut self) -> &mut dyn std::any::Any { - self - } - - fn as_element_ref(&self) -> &web_sys::Element { - self - } -} - -impl DomElement for web_sys::Element { - fn into_pod(self) -> Pod { - Pod(Box::new(self)) - } - - fn as_element_ref(&self) -> &web_sys::Element { - self - } -} - -impl DomElement for Box { - fn into_pod(self) -> Pod { - Pod(self) - } - - fn as_element_ref(&self) -> &web_sys::Element { - self.deref().as_element_ref() - } -} - -/// A container that holds a DOM element. -/// -/// This implementation may be overkill (it's possibly enough that everything is -/// just a `web_sys::Element`), but does allow element types that contain other -/// data, if needed. -pub struct Pod(pub Box); - -impl Pod { - fn new(node: impl DomElement) -> Self { - node.into_pod() - } - - fn downcast_mut(&mut self) -> Option<&mut T> { - self.0.as_any_mut().downcast_mut() - } - - fn mark(&mut self, flags: ChangeFlags) -> ChangeFlags { - flags - } -} - -xilem_core::generate_view_trait! {View, DomElement, Cx, ChangeFlags;} -xilem_core::generate_viewsequence_trait! {ViewSequence, View, ViewMarker, DomElement, Cx, ChangeFlags, Pod;} -xilem_core::generate_anyview_trait! {AnyView, View, ViewMarker, Cx, ChangeFlags, AnyElement, BoxedView;} -xilem_core::generate_memoize_view! {Memoize, MemoizeState, View, ViewMarker, Cx, ChangeFlags, s, memoize;} -xilem_core::generate_adapt_view! {View, Cx, ChangeFlags;} -xilem_core::generate_adapt_state_view! {View, Cx, ChangeFlags;} diff --git a/crates/xilem_svg/src/view_ext.rs b/crates/xilem_svg/src/view_ext.rs deleted file mode 100644 index edfd870c3..000000000 --- a/crates/xilem_svg/src/view_ext.rs +++ /dev/null @@ -1,36 +0,0 @@ -// Copyright 2023 the Druid Authors. -// SPDX-License-Identifier: Apache-2.0 - -use peniko::Brush; - -use crate::{ - class::Class, - clicked::Clicked, - common_attrs::{Fill, Stroke}, - pointer::{Pointer, PointerMsg}, - view::View, -}; - -pub trait ViewExt: View + Sized { - fn clicked(self, f: F) -> Clicked { - crate::clicked::clicked(self, f) - } - - fn pointer(self, f: F) -> Pointer { - crate::pointer::pointer(self, f) - } - - fn class(self, class: impl Into) -> Class { - crate::class::class(self, class) - } - - fn fill(self, brush: impl Into) -> Fill { - crate::common_attrs::fill(self, brush) - } - - fn stroke(self, brush: impl Into, style: peniko::kurbo::Stroke) -> Stroke { - crate::common_attrs::stroke(self, brush, style) - } -} - -impl> ViewExt for V {} diff --git a/crates/xilem_html/.gitignore b/crates/xilem_web/.gitignore similarity index 100% rename from crates/xilem_html/.gitignore rename to crates/xilem_web/.gitignore diff --git a/crates/xilem_html/Cargo.toml b/crates/xilem_web/Cargo.toml similarity index 97% rename from crates/xilem_html/Cargo.toml rename to crates/xilem_web/Cargo.toml index dd4619a54..18fb95ee1 100644 --- a/crates/xilem_html/Cargo.toml +++ b/crates/xilem_web/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "xilem_html" +name = "xilem_web" version = "0.1.0" description = "HTML DOM frontend for the Xilem Rust UI framework." keywords = ["xilem", "html", "dom", "web", "ui"] @@ -25,6 +25,7 @@ wasm-bindgen = "0.2.87" paste = "1" log = "0.4.19" gloo = { version = "0.8.1", default-features = false, features = ["events", "utils"] } +peniko = { git = "https://github.com/linebender/peniko", rev = "629fc3325b016a8c98b1cd6204cb4ddf1c6b3daa" } [dependencies.web-sys] version = "0.3.4" diff --git a/crates/xilem_html/README.md b/crates/xilem_web/README.md similarity index 86% rename from crates/xilem_html/README.md rename to crates/xilem_web/README.md index 2eaa5eab1..9c20492b8 100644 --- a/crates/xilem_html/README.md +++ b/crates/xilem_web/README.md @@ -1,6 +1,6 @@ -# `xilem_html` prototype +# `xilem_web` prototype -This is an early prototype of a potential implementation of the Xilem architecture using HTML elements +This is an early prototype of a potential implementation of the Xilem architecture using DOM elements as Xilem elements (unfortunately the two concepts have the same name). This uses xilem_core under the hood, and offers a proof that it can be used outside of `xilem` proper. diff --git a/crates/xilem_html/src/app.rs b/crates/xilem_web/src/app.rs similarity index 100% rename from crates/xilem_html/src/app.rs rename to crates/xilem_web/src/app.rs diff --git a/crates/xilem_html/src/attribute.rs b/crates/xilem_web/src/attribute.rs similarity index 89% rename from crates/xilem_html/src/attribute.rs rename to crates/xilem_web/src/attribute.rs index b05f33975..1fb455605 100644 --- a/crates/xilem_html/src/attribute.rs +++ b/crates/xilem_web/src/attribute.rs @@ -22,7 +22,7 @@ impl, T, A> View for Attr { type Element = E::Element; fn build(&self, cx: &mut Cx) -> (Id, Self::State, Self::Element) { - cx.add_new_attribute_to_current_element(&self.name, &self.value); + cx.add_attr_to_element(&self.name, &self.value); self.element.build(cx) } @@ -34,7 +34,7 @@ impl, T, A> View for Attr { state: &mut Self::State, element: &mut Self::Element, ) -> ChangeFlags { - cx.add_new_attribute_to_current_element(&self.name, &self.value); + cx.add_attr_to_element(&self.name, &self.value); self.element.rebuild(cx, &prev.element, id, state, element) } diff --git a/crates/xilem_html/src/attribute_value.rs b/crates/xilem_web/src/attribute_value.rs similarity index 70% rename from crates/xilem_html/src/attribute_value.rs rename to crates/xilem_web/src/attribute_value.rs index eb78bd517..3428a5076 100644 --- a/crates/xilem_html/src/attribute_value.rs +++ b/crates/xilem_web/src/attribute_value.rs @@ -24,13 +24,13 @@ impl AttributeValue { } pub trait IntoAttributeValue: Sized { - fn into_attribute_value(self) -> Option; + fn into_attr_value(self) -> Option; } impl IntoAttributeValue for Option { - fn into_attribute_value(self) -> Option { + fn into_attr_value(self) -> Option { if let Some(value) = self { - T::into_attribute_value(value) + T::into_attr_value(value) } else { None } @@ -38,55 +38,55 @@ impl IntoAttributeValue for Option { } impl IntoAttributeValue for bool { - fn into_attribute_value(self) -> Option { + fn into_attr_value(self) -> Option { self.then_some(AttributeValue::True) } } impl IntoAttributeValue for AttributeValue { - fn into_attribute_value(self) -> Option { + fn into_attr_value(self) -> Option { Some(self) } } impl IntoAttributeValue for u32 { - fn into_attribute_value(self) -> Option { + fn into_attr_value(self) -> Option { Some(AttributeValue::U32(self)) } } impl IntoAttributeValue for i32 { - fn into_attribute_value(self) -> Option { + fn into_attr_value(self) -> Option { Some(AttributeValue::I32(self)) } } impl IntoAttributeValue for f32 { - fn into_attribute_value(self) -> Option { + fn into_attr_value(self) -> Option { Some(AttributeValue::F32(self)) } } impl IntoAttributeValue for f64 { - fn into_attribute_value(self) -> Option { + fn into_attr_value(self) -> Option { Some(AttributeValue::F64(self)) } } impl IntoAttributeValue for String { - fn into_attribute_value(self) -> Option { + fn into_attr_value(self) -> Option { Some(AttributeValue::String(self.into())) } } impl IntoAttributeValue for CowStr { - fn into_attribute_value(self) -> Option { + fn into_attr_value(self) -> Option { Some(AttributeValue::String(self)) } } impl IntoAttributeValue for &'static str { - fn into_attribute_value(self) -> Option { + fn into_attr_value(self) -> Option { Some(AttributeValue::String(self.into())) } } diff --git a/crates/xilem_html/src/context.rs b/crates/xilem_web/src/context.rs similarity index 97% rename from crates/xilem_html/src/context.rs rename to crates/xilem_web/src/context.rs index 4929c9c78..90088edd0 100644 --- a/crates/xilem_html/src/context.rs +++ b/crates/xilem_web/src/context.rs @@ -133,11 +133,7 @@ impl Cx { // TODO Not sure how multiple attribute definitions with the same name should be handled (e.g. `e.attr("class", "a").attr("class", "b")`) // Currently the outer most (in the example above "b") defines the attribute (when it isn't `None`, in that case the inner attr defines the value) - pub(crate) fn add_new_attribute_to_current_element( - &mut self, - name: &CowStr, - value: &Option, - ) { + pub(crate) fn add_attr_to_element(&mut self, name: &CowStr, value: &Option) { if let Some(value) = value { // could be slightly optimized via something like this: `new_attrs.entry(name).or_insert_with(|| value)` if !self.current_element_attributes.contains_key(name) { diff --git a/crates/xilem_html/src/diff.rs b/crates/xilem_web/src/diff.rs similarity index 100% rename from crates/xilem_html/src/diff.rs rename to crates/xilem_web/src/diff.rs diff --git a/crates/xilem_html/src/elements.rs b/crates/xilem_web/src/elements.rs similarity index 100% rename from crates/xilem_html/src/elements.rs rename to crates/xilem_web/src/elements.rs diff --git a/crates/xilem_html/src/events.rs b/crates/xilem_web/src/events.rs similarity index 100% rename from crates/xilem_html/src/events.rs rename to crates/xilem_web/src/events.rs diff --git a/crates/xilem_html/src/interfaces.rs b/crates/xilem_web/src/interfaces.rs similarity index 85% rename from crates/xilem_html/src/interfaces.rs rename to crates/xilem_web/src/interfaces.rs index 7794fb5db..56a37038a 100644 --- a/crates/xilem_html/src/interfaces.rs +++ b/crates/xilem_web/src/interfaces.rs @@ -1,4 +1,4 @@ -use crate::{View, ViewMarker}; +use crate::{Pointer, PointerMsg, View, ViewMarker}; use std::borrow::Cow; use gloo::events::EventListenerOptions; @@ -62,6 +62,10 @@ where OnEvent::new_with_options(self, event, handler, options) } + fn pointer(self, f: F) -> Pointer { + crate::pointer::pointer(self, f) + } + // TODO should the API be "functional" in the sense, that new attributes are wrappers around the type, // or should they modify the underlying instance (e.g. via the following methods)? // The disadvantage that "functional" brings in, is that elements are not modifiable (i.e. attributes can't be simply added etc.) @@ -82,7 +86,7 @@ where Attr { element: self, name: name.into(), - value: value.into_attribute_value(), + value: value.into_attr_value(), phantom: std::marker::PhantomData, } } @@ -98,7 +102,7 @@ where // https://html.spec.whatwg.org/multipage/webappapis.html#idl-definitions // // I didn't include the events on the window, since we aren't attaching - // any events to the window in xilem_html + // any events to the window in xilem_web event_handler_mixin!( (OnAbort, on_abort, "abort", Event), (OnAuxClick, on_auxclick, "auxclick", PointerEvent), @@ -448,21 +452,74 @@ dom_interface_macro_and_trait_definitions!( SvgDefsElement { methods: {}, child_interfaces: {} }, SvgForeignObjectElement { methods: {}, child_interfaces: {} }, SvgGeometryElement { - methods: {}, + methods: { + fn stroke(self, brush: impl Into, style: peniko::kurbo::Stroke) -> crate::svg::Stroke { + crate::svg::stroke(self, brush, style) + } + }, child_interfaces: { - SvgCircleElement { methods: {}, child_interfaces: {} }, - SvgEllipseElement { methods: {}, child_interfaces: {} }, + SvgCircleElement { + methods: { + fn fill(self, brush: impl Into) -> crate::svg::Fill { + crate::svg::fill(self, brush) + } + }, + child_interfaces: {} + }, + SvgEllipseElement { + methods: { + fn fill(self, brush: impl Into) -> crate::svg::Fill { + crate::svg::fill(self, brush) + } + }, + child_interfaces: {} + }, SvgLineElement { methods: {}, child_interfaces: {} }, - SvgPathElement { methods: {}, child_interfaces: {} }, - SvgPolygonElement { methods: {}, child_interfaces: {} }, - SvgPolylineElement { methods: {}, child_interfaces: {} }, - SvgRectElement { methods: {}, child_interfaces: {} }, + SvgPathElement { + methods: { + fn fill(self, brush: impl Into) -> crate::svg::Fill { + crate::svg::fill(self, brush) + } + }, + child_interfaces: {} + }, + SvgPolygonElement { + methods: { + fn fill(self, brush: impl Into) -> crate::svg::Fill { + crate::svg::fill(self, brush) + } + }, + child_interfaces: {} + }, + SvgPolylineElement { + methods: { + fn fill(self, brush: impl Into) -> crate::svg::Fill { + crate::svg::fill(self, brush) + } + }, + child_interfaces: {} + }, + SvgRectElement { + methods: { + fn fill(self, brush: impl Into) -> crate::svg::Fill { + crate::svg::fill(self, brush) + } + }, + child_interfaces: {} + }, } }, SvgImageElement { methods: {}, child_interfaces: {} }, SvgSwitchElement { methods: {}, child_interfaces: {} }, SvgTextContentElement { - methods: {}, + methods: { + fn fill(self, brush: impl Into) -> crate::svg::Fill { + crate::svg::fill(self, brush) + } + fn stroke(self, brush: impl Into, style: peniko::kurbo::Stroke) -> crate::svg::Stroke { + crate::svg::stroke(self, brush, style) + } + }, child_interfaces: { SvgTextPathElement { methods: {}, child_interfaces: {} }, SvgTextPositioningElement { @@ -476,7 +533,17 @@ dom_interface_macro_and_trait_definitions!( }, SvgUseElement { methods: {}, child_interfaces: {} }, SvgaElement { methods: {}, child_interfaces: {} }, - SvggElement { methods: {}, child_interfaces: {} }, + SvggElement { + methods: { + fn fill(self, brush: impl Into) -> crate::svg::Fill { + crate::svg::fill(self, brush) + } + fn stroke(self, brush: impl Into, style: peniko::kurbo::Stroke) -> crate::svg::Stroke { + crate::svg::stroke(self, brush, style) + } + }, + child_interfaces: {} + }, SvgsvgElement { methods: {}, child_interfaces: {} }, } }, diff --git a/crates/xilem_html/src/lib.rs b/crates/xilem_web/src/lib.rs similarity index 91% rename from crates/xilem_html/src/lib.rs rename to crates/xilem_web/src/lib.rs index b21dbf55f..9c77c2966 100644 --- a/crates/xilem_html/src/lib.rs +++ b/crates/xilem_web/src/lib.rs @@ -17,6 +17,8 @@ pub mod events; pub mod interfaces; mod one_of; mod optional_action; +mod pointer; +pub mod svg; mod vecmap; mod view; mod view_ext; @@ -32,9 +34,10 @@ pub use one_of::{ OneSeqOf5, OneSeqOf6, OneSeqOf7, OneSeqOf8, }; pub use optional_action::{Action, OptionalAction}; +pub use pointer::{Pointer, PointerDetails, PointerMsg}; pub use view::{ - memoize, static_view, Adapt, AdaptState, AdaptThunk, AnyView, Memoize, MemoizeState, Pod, View, - ViewMarker, ViewSequence, + memoize, static_view, Adapt, AdaptState, AdaptThunk, AnyView, BoxedView, Memoize, MemoizeState, + Pod, View, ViewMarker, ViewSequence, }; pub use view_ext::ViewExt; diff --git a/crates/xilem_html/src/one_of.rs b/crates/xilem_web/src/one_of.rs similarity index 100% rename from crates/xilem_html/src/one_of.rs rename to crates/xilem_web/src/one_of.rs diff --git a/crates/xilem_html/src/optional_action.rs b/crates/xilem_web/src/optional_action.rs similarity index 100% rename from crates/xilem_html/src/optional_action.rs rename to crates/xilem_web/src/optional_action.rs diff --git a/crates/xilem_svg/src/pointer.rs b/crates/xilem_web/src/pointer.rs similarity index 75% rename from crates/xilem_svg/src/pointer.rs rename to crates/xilem_web/src/pointer.rs index 7cdd29521..7c738a64f 100644 --- a/crates/xilem_svg/src/pointer.rs +++ b/crates/xilem_web/src/pointer.rs @@ -12,13 +12,14 @@ use xilem_core::{Id, MessageResult}; use crate::{ context::{ChangeFlags, Cx}, - view::{DomElement, View, ViewMarker}, + interfaces::Element, + view::{DomNode, View, ViewMarker}, }; -pub struct Pointer { +pub struct Pointer { child: V, callback: F, - phantom: PhantomData, + phantom: PhantomData (T, A)>, } pub struct PointerState { @@ -60,10 +61,10 @@ impl PointerDetails { } } -pub fn pointer>( +pub fn pointer>( child: V, callback: F, -) -> Pointer { +) -> Pointer { Pointer { child, callback, @@ -71,25 +72,35 @@ pub fn pointer>( } } -impl ViewMarker for Pointer {} +crate::interfaces::impl_dom_interfaces_for_ty!( + Element, + Pointer, + vars: , + vars_on_ty: , + bounds: { + F: Fn(&mut T, PointerMsg) -> A, + } +); + +impl ViewMarker for Pointer {} +impl crate::interfaces::sealed::Sealed for Pointer {} -impl> View for Pointer { +impl A, V: View> View for Pointer { type State = PointerState; type Element = V::Element; fn build(&self, cx: &mut Cx) -> (Id, Self::State, Self::Element) { let (id, child_state, element) = self.child.build(cx); let thunk = cx.with_id(id, |cx| cx.message_thunk()); - let el_clone = element.as_element_ref().clone(); + let el = element.as_node_ref().dyn_ref::().unwrap(); + let el_clone = el.clone(); let down_closure = Closure::new(move |e: PointerEvent| { thunk.push_message(PointerMsg::Down(PointerDetails::from_pointer_event(&e))); el_clone.set_pointer_capture(e.pointer_id()).unwrap(); e.prevent_default(); e.stop_propagation(); }); - element - .as_element_ref() - .add_event_listener_with_callback("pointerdown", down_closure.as_ref().unchecked_ref()) + el.add_event_listener_with_callback("pointerdown", down_closure.as_ref().unchecked_ref()) .unwrap(); let thunk = cx.with_id(id, |cx| cx.message_thunk()); let move_closure = Closure::new(move |e: PointerEvent| { @@ -97,9 +108,7 @@ impl> View for Pointer> View for Pointer> View for Pointer, app_state: &mut T, - ) -> MessageResult<()> { + ) -> MessageResult { match message.downcast() { - Ok(msg) => { - (self.callback)(app_state, *msg); - MessageResult::Action(()) - } + Ok(msg) => MessageResult::Action((self.callback)(app_state, *msg)), Err(message) => self .child .message(id_path, &mut state.child_state, message, app_state), diff --git a/crates/xilem_web/src/svg/common_attrs.rs b/crates/xilem_web/src/svg/common_attrs.rs new file mode 100644 index 000000000..69278d4a4 --- /dev/null +++ b/crates/xilem_web/src/svg/common_attrs.rs @@ -0,0 +1,192 @@ +// Copyright 2023 the Druid Authors. +// SPDX-License-Identifier: Apache-2.0 + +use std::borrow::Cow; +use std::{any::Any, marker::PhantomData}; + +use peniko::Brush; +use xilem_core::{Id, MessageResult}; + +use crate::{ + interfaces::{ + Element, SvgCircleElement, SvgElement, SvgEllipseElement, SvgGeometryElement, + SvgGraphicsElement, SvgLineElement, SvgPathElement, SvgPolygonElement, SvgPolylineElement, + SvgRectElement, SvgTextContentElement, SvgTextElement, SvgTextPathElement, + SvgTextPositioningElement, SvggElement, SvgtSpanElement, + }, + ChangeFlags, Cx, IntoAttributeValue, View, ViewMarker, +}; + +pub struct Fill { + child: V, + // This could reasonably be static Cow also, but keep things simple + brush: Brush, + phantom: PhantomData (T, A)>, +} + +pub struct Stroke { + child: V, + // This could reasonably be static Cow also, but keep things simple + brush: Brush, + style: peniko::kurbo::Stroke, + phantom: PhantomData (T, A)>, +} + +pub fn fill(child: V, brush: impl Into) -> Fill { + Fill { + child, + brush: brush.into(), + phantom: Default::default(), + } +} + +pub fn stroke( + child: V, + brush: impl Into, + style: peniko::kurbo::Stroke, +) -> Stroke { + Stroke { + child, + brush: brush.into(), + style, + phantom: Default::default(), + } +} + +fn brush_to_string(brush: &Brush) -> String { + match brush { + Brush::Solid(color) => { + if color.a == 0 { + "none".into() + } else { + format!("#{:02x}{:02x}{:02x}", color.r, color.g, color.b) + } + } + _ => todo!("gradients not implemented"), + } +} + +// manually implement interfaces, because multiple independent DOM interfaces use the View +impl> Element for Fill {} +impl> SvgElement for Fill {} +impl> SvgGraphicsElement for Fill {} +impl> SvggElement for Fill {} +// descendants of SvgGeometryElement (with the exception of SvgLineElement) +impl> SvgGeometryElement for Fill {} +impl> SvgCircleElement for Fill {} +impl> SvgEllipseElement for Fill {} +impl> SvgPathElement for Fill {} +impl> SvgPolygonElement for Fill {} +impl> SvgPolylineElement for Fill {} +impl> SvgRectElement for Fill {} +// descendants of SvgTextContentElement +impl> SvgTextContentElement for Fill {} +impl> SvgTextPathElement for Fill {} +impl> SvgTextPositioningElement for Fill {} +impl> SvgTextElement for Fill {} +impl> SvgtSpanElement for Fill {} + +impl ViewMarker for Fill {} +impl crate::interfaces::sealed::Sealed for Fill {} + +impl> View for Fill { + type State = (Cow<'static, str>, V::State); + type Element = V::Element; + + fn build(&self, cx: &mut Cx) -> (Id, Self::State, Self::Element) { + let brush_svg_repr = Cow::from(brush_to_string(&self.brush)); + cx.add_attr_to_element(&"fill".into(), &brush_svg_repr.clone().into_attr_value()); + let (id, child_state, element) = self.child.build(cx); + (id, (brush_svg_repr, child_state), element) + } + + fn rebuild( + &self, + cx: &mut Cx, + prev: &Self, + id: &mut Id, + (brush_svg_repr, child_state): &mut Self::State, + element: &mut V::Element, + ) -> ChangeFlags { + if self.brush != prev.brush { + *brush_svg_repr = Cow::from(brush_to_string(&self.brush)); + } + cx.add_attr_to_element(&"fill".into(), &brush_svg_repr.clone().into_attr_value()); + self.child + .rebuild(cx, &prev.child, id, child_state, element) + } + + fn message( + &self, + id_path: &[Id], + (_, child_state): &mut Self::State, + message: Box, + app_state: &mut T, + ) -> MessageResult { + self.child.message(id_path, child_state, message, app_state) + } +} + +// manually implement interfaces, because multiple independent DOM interfaces use the View +impl> Element for Stroke {} +impl> SvgElement for Stroke {} +impl> SvgGraphicsElement for Stroke {} +impl> SvggElement for Stroke {} +// descendants of SvgGeometryElement +impl> SvgGeometryElement for Stroke {} +impl> SvgCircleElement for Stroke {} +impl> SvgEllipseElement for Stroke {} +impl> SvgLineElement for Stroke {} +impl> SvgPathElement for Stroke {} +impl> SvgPolygonElement for Stroke {} +impl> SvgPolylineElement for Stroke {} +impl> SvgRectElement for Stroke {} +// descendants of SvgTextContentElement +impl> SvgTextContentElement for Stroke {} +impl> SvgTextPathElement for Stroke {} +impl> SvgTextPositioningElement for Stroke {} +impl> SvgTextElement for Stroke {} +impl> SvgtSpanElement for Stroke {} + +impl ViewMarker for Stroke {} +impl crate::interfaces::sealed::Sealed for Stroke {} + +impl> View for Stroke { + type State = (Cow<'static, str>, V::State); + type Element = V::Element; + + fn build(&self, cx: &mut Cx) -> (Id, Self::State, Self::Element) { + let brush_svg_repr = Cow::from(brush_to_string(&self.brush)); + cx.add_attr_to_element(&"stroke".into(), &brush_svg_repr.clone().into_attr_value()); + cx.add_attr_to_element(&"stroke-width".into(), &self.style.width.into_attr_value()); + let (id, child_state, element) = self.child.build(cx); + (id, (brush_svg_repr, child_state), element) + } + + fn rebuild( + &self, + cx: &mut Cx, + prev: &Self, + id: &mut Id, + (brush_svg_repr, child_state): &mut Self::State, + element: &mut V::Element, + ) -> ChangeFlags { + if self.brush != prev.brush { + *brush_svg_repr = Cow::from(brush_to_string(&self.brush)); + } + cx.add_attr_to_element(&"stroke".into(), &brush_svg_repr.clone().into_attr_value()); + cx.add_attr_to_element(&"stroke-width".into(), &self.style.width.into_attr_value()); + self.child + .rebuild(cx, &prev.child, id, child_state, element) + } + + fn message( + &self, + id_path: &[Id], + (_, child_state): &mut Self::State, + message: Box, + app_state: &mut T, + ) -> MessageResult { + self.child.message(id_path, child_state, message, app_state) + } +} diff --git a/crates/xilem_web/src/svg/kurbo_shape.rs b/crates/xilem_web/src/svg/kurbo_shape.rs new file mode 100644 index 000000000..cd7c92029 --- /dev/null +++ b/crates/xilem_web/src/svg/kurbo_shape.rs @@ -0,0 +1,208 @@ +// Copyright 2023 the Druid Authors. +// SPDX-License-Identifier: Apache-2.0 + +//! Implementation of the View trait for various kurbo shapes. + +use peniko::kurbo::{BezPath, Circle, Line, Rect}; +use std::borrow::Cow; + +use xilem_core::{Id, MessageResult}; + +use crate::{ + context::{ChangeFlags, Cx}, + interfaces::sealed::Sealed, + vecmap::VecMap, + view::{View, ViewMarker}, + AttributeValue, IntoAttributeValue, SVG_NS, +}; + +macro_rules! generate_dom_interface_impl { + ($dom_interface:ident, ($ty_name:ident)) => { + impl $crate::interfaces::$dom_interface for $ty_name {} + }; +} + +generate_dom_interface_impl!(SvgLineElement, (Line)); +crate::interfaces::for_all_svg_line_element_ancestors!(generate_dom_interface_impl, (Line)); + +impl ViewMarker for Line {} +impl Sealed for Line {} + +impl View for Line { + type State = VecMap, AttributeValue>; + type Element = web_sys::Element; + + fn build(&self, cx: &mut Cx) -> (Id, Self::State, Self::Element) { + cx.add_attr_to_element(&"x1".into(), &self.p0.x.into_attr_value()); + cx.add_attr_to_element(&"y1".into(), &self.p0.y.into_attr_value()); + cx.add_attr_to_element(&"x2".into(), &self.p1.x.into_attr_value()); + cx.add_attr_to_element(&"y2".into(), &self.p1.y.into_attr_value()); + let (el, attributes) = cx.build_element(SVG_NS, "line"); + let id = Id::next(); + (id, attributes, el) + } + + fn rebuild( + &self, + cx: &mut Cx, + _prev: &Self, + _id: &mut Id, + attributes: &mut Self::State, + element: &mut Self::Element, + ) -> ChangeFlags { + cx.add_attr_to_element(&"x1".into(), &self.p0.x.into_attr_value()); + cx.add_attr_to_element(&"y1".into(), &self.p0.y.into_attr_value()); + cx.add_attr_to_element(&"x2".into(), &self.p1.x.into_attr_value()); + cx.add_attr_to_element(&"y2".into(), &self.p1.y.into_attr_value()); + cx.rebuild_element(element, attributes) + } + + fn message( + &self, + _id_path: &[Id], + _state: &mut Self::State, + message: Box, + _app_state: &mut T, + ) -> MessageResult { + MessageResult::Stale(message) + } +} + +generate_dom_interface_impl!(SvgRectElement, (Rect)); +crate::interfaces::for_all_svg_rect_element_ancestors!(generate_dom_interface_impl, (Rect)); + +impl ViewMarker for Rect {} +impl Sealed for Rect {} + +impl View for Rect { + type State = VecMap, AttributeValue>; + type Element = web_sys::Element; + + fn build(&self, cx: &mut Cx) -> (Id, Self::State, Self::Element) { + cx.add_attr_to_element(&"x".into(), &self.x0.into_attr_value()); + cx.add_attr_to_element(&"y".into(), &self.y0.into_attr_value()); + let size = self.size(); + cx.add_attr_to_element(&"width".into(), &size.width.into_attr_value()); + cx.add_attr_to_element(&"height".into(), &size.height.into_attr_value()); + let (el, attributes) = cx.build_element(SVG_NS, "rect"); + let id = Id::next(); + (id, attributes, el) + } + + fn rebuild( + &self, + cx: &mut Cx, + _prev: &Self, + _id: &mut Id, + attributes: &mut Self::State, + element: &mut Self::Element, + ) -> ChangeFlags { + cx.add_attr_to_element(&"x".into(), &self.x0.into_attr_value()); + cx.add_attr_to_element(&"y".into(), &self.y0.into_attr_value()); + let size = self.size(); + cx.add_attr_to_element(&"width".into(), &size.width.into_attr_value()); + cx.add_attr_to_element(&"height".into(), &size.height.into_attr_value()); + cx.rebuild_element(element, attributes) + } + + fn message( + &self, + _id_path: &[Id], + _state: &mut Self::State, + message: Box, + _app_state: &mut T, + ) -> MessageResult { + MessageResult::Stale(message) + } +} + +generate_dom_interface_impl!(SvgCircleElement, (Circle)); +crate::interfaces::for_all_svg_circle_element_ancestors!(generate_dom_interface_impl, (Circle)); + +impl ViewMarker for Circle {} +impl Sealed for Circle {} + +impl View for Circle { + type State = VecMap, AttributeValue>; + type Element = web_sys::Element; + + fn build(&self, cx: &mut Cx) -> (Id, Self::State, Self::Element) { + cx.add_attr_to_element(&"cx".into(), &self.center.x.into_attr_value()); + cx.add_attr_to_element(&"cy".into(), &self.center.y.into_attr_value()); + cx.add_attr_to_element(&"r".into(), &self.radius.into_attr_value()); + let (el, attributes) = cx.build_element(SVG_NS, "circle"); + let id = Id::next(); + (id, attributes, el) + } + + fn rebuild( + &self, + cx: &mut Cx, + _prev: &Self, + _id: &mut Id, + attributes: &mut Self::State, + element: &mut Self::Element, + ) -> ChangeFlags { + cx.add_attr_to_element(&"cx".into(), &self.center.x.into_attr_value()); + cx.add_attr_to_element(&"cy".into(), &self.center.y.into_attr_value()); + cx.add_attr_to_element(&"r".into(), &self.radius.into_attr_value()); + cx.rebuild_element(element, attributes) + } + + fn message( + &self, + _id_path: &[Id], + _state: &mut Self::State, + message: Box, + _app_state: &mut T, + ) -> MessageResult { + MessageResult::Stale(message) + } +} + +generate_dom_interface_impl!(SvgPathElement, (BezPath)); +crate::interfaces::for_all_svg_path_element_ancestors!(generate_dom_interface_impl, (BezPath)); + +impl ViewMarker for BezPath {} +impl Sealed for BezPath {} + +impl View for BezPath { + type State = (Cow<'static, str>, VecMap, AttributeValue>); + type Element = web_sys::Element; + + fn build(&self, cx: &mut Cx) -> (Id, Self::State, Self::Element) { + let svg_repr = Cow::from(self.to_svg()); + cx.add_attr_to_element(&"d".into(), &svg_repr.clone().into_attr_value()); + let (el, attributes) = cx.build_element(SVG_NS, "path"); + let id = Id::next(); + (id, (svg_repr, attributes), el) + } + + fn rebuild( + &self, + cx: &mut Cx, + prev: &Self, + _id: &mut Id, + (svg_repr, attributes): &mut Self::State, + element: &mut Self::Element, + ) -> ChangeFlags { + // slight optimization to avoid serialization/allocation + if self != prev { + *svg_repr = Cow::from(self.to_svg()); + } + cx.add_attr_to_element(&"d".into(), &svg_repr.clone().into_attr_value()); + cx.rebuild_element(element, attributes) + } + + fn message( + &self, + _id_path: &[Id], + _state: &mut Self::State, + message: Box, + _app_state: &mut T, + ) -> MessageResult { + MessageResult::Stale(message) + } +} + +// TODO: RoundedRect diff --git a/crates/xilem_web/src/svg/mod.rs b/crates/xilem_web/src/svg/mod.rs new file mode 100644 index 000000000..f1b2815c2 --- /dev/null +++ b/crates/xilem_web/src/svg/mod.rs @@ -0,0 +1,6 @@ +pub(crate) mod common_attrs; +pub(crate) mod kurbo_shape; + +pub use common_attrs::{fill, stroke, Fill, Stroke}; +pub use peniko; +pub use peniko::kurbo; diff --git a/crates/xilem_html/src/vecmap.rs b/crates/xilem_web/src/vecmap.rs similarity index 100% rename from crates/xilem_html/src/vecmap.rs rename to crates/xilem_web/src/vecmap.rs diff --git a/crates/xilem_html/src/view.rs b/crates/xilem_web/src/view.rs similarity index 100% rename from crates/xilem_html/src/view.rs rename to crates/xilem_web/src/view.rs diff --git a/crates/xilem_html/src/view_ext.rs b/crates/xilem_web/src/view_ext.rs similarity index 100% rename from crates/xilem_html/src/view_ext.rs rename to crates/xilem_web/src/view_ext.rs diff --git a/crates/xilem_html/web_examples/counter/Cargo.toml b/crates/xilem_web/web_examples/counter/Cargo.toml similarity index 86% rename from crates/xilem_html/web_examples/counter/Cargo.toml rename to crates/xilem_web/web_examples/counter/Cargo.toml index c280b3fda..ccd520cf9 100644 --- a/crates/xilem_html/web_examples/counter/Cargo.toml +++ b/crates/xilem_web/web_examples/counter/Cargo.toml @@ -9,4 +9,4 @@ edition.workspace = true console_error_panic_hook = "0.1" wasm-bindgen = "0.2.87" web-sys = "0.3.64" -xilem_html = { path = "../.." } +xilem_web = { path = "../.." } diff --git a/crates/xilem_html/web_examples/mathml_svg/index.html b/crates/xilem_web/web_examples/counter/index.html similarity index 100% rename from crates/xilem_html/web_examples/mathml_svg/index.html rename to crates/xilem_web/web_examples/counter/index.html diff --git a/crates/xilem_html/web_examples/counter/src/main.rs b/crates/xilem_web/web_examples/counter/src/main.rs similarity index 98% rename from crates/xilem_html/web_examples/counter/src/main.rs rename to crates/xilem_web/web_examples/counter/src/main.rs index ef5073865..850a75024 100644 --- a/crates/xilem_html/web_examples/counter/src/main.rs +++ b/crates/xilem_web/web_examples/counter/src/main.rs @@ -1,4 +1,4 @@ -use xilem_html::{ +use xilem_web::{ document_body, elements::html as el, interfaces::{Element, HtmlButtonElement}, diff --git a/crates/xilem_html/web_examples/counter_custom_element/Cargo.toml b/crates/xilem_web/web_examples/counter_custom_element/Cargo.toml similarity index 87% rename from crates/xilem_html/web_examples/counter_custom_element/Cargo.toml rename to crates/xilem_web/web_examples/counter_custom_element/Cargo.toml index 343df66da..4a1179f00 100644 --- a/crates/xilem_html/web_examples/counter_custom_element/Cargo.toml +++ b/crates/xilem_web/web_examples/counter_custom_element/Cargo.toml @@ -9,4 +9,4 @@ edition.workspace = true console_error_panic_hook = "0.1" wasm-bindgen = "0.2.87" web-sys = "0.3.64" -xilem_html = { path = "../.." } +xilem_web = { path = "../.." } diff --git a/crates/xilem_html/web_examples/counter_custom_element/index.html b/crates/xilem_web/web_examples/counter_custom_element/index.html similarity index 80% rename from crates/xilem_html/web_examples/counter_custom_element/index.html rename to crates/xilem_web/web_examples/counter_custom_element/index.html index 8774c9e45..05a6790e1 100644 --- a/crates/xilem_html/web_examples/counter_custom_element/index.html +++ b/crates/xilem_web/web_examples/counter_custom_element/index.html @@ -17,5 +17,5 @@

This is like the counter example, but does not use the typed - elements/events/attrs in xilem_html, instead using strings

+ elements/events/attrs in xilem_web, instead using strings

\ No newline at end of file diff --git a/crates/xilem_html/web_examples/counter_custom_element/src/main.rs b/crates/xilem_web/web_examples/counter_custom_element/src/main.rs similarity index 98% rename from crates/xilem_html/web_examples/counter_custom_element/src/main.rs rename to crates/xilem_web/web_examples/counter_custom_element/src/main.rs index 1e0242332..ff9f04790 100644 --- a/crates/xilem_html/web_examples/counter_custom_element/src/main.rs +++ b/crates/xilem_web/web_examples/counter_custom_element/src/main.rs @@ -1,4 +1,4 @@ -use xilem_html::{ +use xilem_web::{ document_body, elements::custom_element, interfaces::{Element, HtmlElement}, diff --git a/crates/xilem_html/web_examples/mathml_svg/Cargo.toml b/crates/xilem_web/web_examples/mathml_svg/Cargo.toml similarity index 86% rename from crates/xilem_html/web_examples/mathml_svg/Cargo.toml rename to crates/xilem_web/web_examples/mathml_svg/Cargo.toml index 32998c599..195a9ca79 100644 --- a/crates/xilem_html/web_examples/mathml_svg/Cargo.toml +++ b/crates/xilem_web/web_examples/mathml_svg/Cargo.toml @@ -9,4 +9,4 @@ edition.workspace = true console_error_panic_hook = "0.1" wasm-bindgen = "0.2.87" web-sys = "0.3.64" -xilem_html = { path = "../.." } +xilem_web = { path = "../.." } diff --git a/crates/xilem_html/web_examples/counter/index.html b/crates/xilem_web/web_examples/mathml_svg/index.html similarity index 100% rename from crates/xilem_html/web_examples/counter/index.html rename to crates/xilem_web/web_examples/mathml_svg/index.html diff --git a/crates/xilem_html/web_examples/mathml_svg/src/main.rs b/crates/xilem_web/web_examples/mathml_svg/src/main.rs similarity index 99% rename from crates/xilem_html/web_examples/mathml_svg/src/main.rs rename to crates/xilem_web/web_examples/mathml_svg/src/main.rs index cc68d186c..ab00d7998 100644 --- a/crates/xilem_html/web_examples/mathml_svg/src/main.rs +++ b/crates/xilem_web/web_examples/mathml_svg/src/main.rs @@ -1,5 +1,5 @@ use wasm_bindgen::{JsCast, UnwrapThrowExt}; -use xilem_html::{ +use xilem_web::{ document_body, elements::html, elements::mathml as ml, elements::svg, interfaces::*, App, }; diff --git a/crates/xilem_svg/web_examples/svgtoy/Cargo.toml b/crates/xilem_web/web_examples/svgtoy/Cargo.toml similarity index 86% rename from crates/xilem_svg/web_examples/svgtoy/Cargo.toml rename to crates/xilem_web/web_examples/svgtoy/Cargo.toml index 6e0fb2706..0f0dc259e 100644 --- a/crates/xilem_svg/web_examples/svgtoy/Cargo.toml +++ b/crates/xilem_web/web_examples/svgtoy/Cargo.toml @@ -9,4 +9,4 @@ edition.workspace = true console_error_panic_hook = "0.1" wasm-bindgen = "0.2.87" web-sys = "0.3.64" -xilem_svg = { path = "../.." } +xilem_web = { path = "../.." } diff --git a/crates/xilem_svg/web_examples/svgtoy/index.html b/crates/xilem_web/web_examples/svgtoy/index.html similarity index 100% rename from crates/xilem_svg/web_examples/svgtoy/index.html rename to crates/xilem_web/web_examples/svgtoy/index.html diff --git a/crates/xilem_svg/web_examples/svgtoy/src/main.rs b/crates/xilem_web/web_examples/svgtoy/src/main.rs similarity index 82% rename from crates/xilem_svg/web_examples/svgtoy/src/main.rs rename to crates/xilem_web/web_examples/svgtoy/src/main.rs index 06cc20b96..509d3ffb8 100644 --- a/crates/xilem_svg/web_examples/svgtoy/src/main.rs +++ b/crates/xilem_web/web_examples/svgtoy/src/main.rs @@ -1,11 +1,15 @@ // Copyright 2023 the Druid Authors. // SPDX-License-Identifier: Apache-2.0 -use xilem_svg::{ - group, - kurbo::{self, Rect}, - peniko::Color, - App, PointerMsg, View, ViewExt, +use xilem_web::{ + document_body, + elements::svg::{g, svg}, + interfaces::*, + svg::{ + kurbo::{self, Rect}, + peniko::Color, + }, + App, PointerMsg, View, }; #[derive(Default)] @@ -53,8 +57,8 @@ fn app_logic(state: &mut AppState) -> impl View { let v = (0..10) .map(|i| Rect::from_origin_size((10.0 * i as f64, 150.0), (8.0, 8.0))) .collect::>(); - group(( - Rect::new(100.0, 100.0, 200.0, 200.0).clicked(|_| { + svg(g(( + Rect::new(100.0, 100.0, 200.0, 200.0).on_click(|_, _| { web_sys::console::log_1(&"app logic clicked".into()); }), Rect::new(210.0, 100.0, 310.0, 200.0) @@ -63,20 +67,21 @@ fn app_logic(state: &mut AppState) -> impl View { Rect::new(320.0, 100.0, 420.0, 200.0).class("red"), Rect::new(state.x, state.y, state.x + 100., state.y + 100.) .pointer(|s: &mut AppState, msg| s.grab.handle(&mut s.x, &mut s.y, &msg)), - group(v), + g(v), Rect::new(210.0, 210.0, 310.0, 310.0).pointer(|_, e| { web_sys::console::log_1(&format!("pointer event {e:?}").into()); }), kurbo::Line::new((310.0, 210.0), (410.0, 310.0)), - kurbo::Circle::new((460.0, 260.0), 45.0).clicked(|_| { + kurbo::Circle::new((460.0, 260.0), 45.0).on_click(|_, _| { web_sys::console::log_1(&"circle clicked".into()); }), - )) - //button(format!("Count {}", count), |count| *count += 1) + ))) + .attr("width", 800) + .attr("height", 600) } pub fn main() { console_error_panic_hook::set_once(); let app = App::new(AppState::default(), app_logic); - app.run(); + app.run(&document_body()); } diff --git a/crates/xilem_html/web_examples/todomvc/Cargo.toml b/crates/xilem_web/web_examples/todomvc/Cargo.toml similarity index 92% rename from crates/xilem_html/web_examples/todomvc/Cargo.toml rename to crates/xilem_web/web_examples/todomvc/Cargo.toml index f26cd466a..7aa04c4b0 100644 --- a/crates/xilem_html/web_examples/todomvc/Cargo.toml +++ b/crates/xilem_web/web_examples/todomvc/Cargo.toml @@ -13,4 +13,4 @@ tracing = "0.1.37" tracing-wasm = "0.2.1" wasm-bindgen = "0.2.87" web-sys = { version = "0.3.64", features = ["Storage", "Window"] } -xilem_html = { path = "../.." } +xilem_web = { path = "../.." } diff --git a/crates/xilem_html/web_examples/todomvc/README.md b/crates/xilem_web/web_examples/todomvc/README.md similarity index 100% rename from crates/xilem_html/web_examples/todomvc/README.md rename to crates/xilem_web/web_examples/todomvc/README.md diff --git a/crates/xilem_html/web_examples/todomvc/index.html b/crates/xilem_web/web_examples/todomvc/index.html similarity index 100% rename from crates/xilem_html/web_examples/todomvc/index.html rename to crates/xilem_web/web_examples/todomvc/index.html diff --git a/crates/xilem_html/web_examples/todomvc/src/main.rs b/crates/xilem_web/web_examples/todomvc/src/main.rs similarity index 99% rename from crates/xilem_html/web_examples/todomvc/src/main.rs rename to crates/xilem_web/web_examples/todomvc/src/main.rs index 771e10ecf..b49cc3bf8 100644 --- a/crates/xilem_html/web_examples/todomvc/src/main.rs +++ b/crates/xilem_web/web_examples/todomvc/src/main.rs @@ -3,7 +3,7 @@ mod state; use state::{AppState, Filter, Todo}; use wasm_bindgen::JsCast; -use xilem_html::{ +use xilem_web::{ elements::html as el, get_element_by_id, interfaces::*, Action, Adapt, App, MessageResult, View, }; diff --git a/crates/xilem_html/web_examples/todomvc/src/state.rs b/crates/xilem_web/web_examples/todomvc/src/state.rs similarity index 100% rename from crates/xilem_html/web_examples/todomvc/src/state.rs rename to crates/xilem_web/web_examples/todomvc/src/state.rs