Skip to content

Commit

Permalink
Merged xilem_svg into xilem_html
Browse files Browse the repository at this point in the history
  • Loading branch information
Philipp-M committed Nov 5, 2023
1 parent 2836c67 commit af8d58d
Show file tree
Hide file tree
Showing 13 changed files with 767 additions and 1 deletion.
11 changes: 11 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ members = [
"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/svgtoy",
"crates/xilem_svg",
"crates/xilem_svg/web_examples/svgtoy",
]
Expand Down
8 changes: 8 additions & 0 deletions crates/xilem_html/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ bitflags = "2"
wasm-bindgen = "0.2.87"
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"
Expand All @@ -36,6 +37,13 @@ features = [
"Node",
"NodeList",
"SvgElement",
"SvgGraphicsElement",
"SvggElement",
"SvgGeometryElement",
"SvgCircleElement",
"SvgLineElement",
"SvgRectElement",
"SvgPathElement",
"Text",
"Window",
"FocusEvent",
Expand Down
5 changes: 5 additions & 0 deletions crates/xilem_html/src/elements.rs
Original file line number Diff line number Diff line change
Expand Up @@ -449,4 +449,9 @@ define_elements!(
(MATHML_NS, Semantics, semantics, Element),
// SVG (TODO all SVG elements and their interfaces)
(SVG_NS, Svg, svg, SvgElement),
(SVG_NS, Group, g, SvggElement), // TODO `group` or `g`? (for consistency `g` seems to be the better option, xilem_svg uses `group`)
(SVG_NS, Rect, rect, SvgRectElement),
(SVG_NS, Path, path, SvgPathElement),
(SVG_NS, Circle, circle, SvgCircleElement),
(SVG_NS, Line, line, SvgLineElement),
);
22 changes: 21 additions & 1 deletion crates/xilem_html/src/interfaces.rs
Original file line number Diff line number Diff line change
Expand Up @@ -303,5 +303,25 @@ dom_interface_macro_and_trait_definitions!($,
self.attr("height", value)
}
},
SvgElement: Element {}
// TODO include all SVG interfaces...
SvgElement: Element {
// TODO consider stateful event views like this in general
fn pointer<F: Fn(&mut T, crate::svg::pointer::PointerMsg)>(self, f: F) -> crate::svg::pointer::Pointer<T, A, Self, F> {
crate::svg::pointer::pointer(self, f)
}
},
SvgGraphicsElement: SvgElement {},
SvgGeometryElement: SvgGraphicsElement {
fn fill(self, brush: impl Into<peniko::Brush>) -> crate::svg::common_attrs::Fill<T, A, Self> {
crate::svg::common_attrs::fill(self, brush)
}
fn stroke(self, brush: impl Into<peniko::Brush>, style: peniko::kurbo::Stroke) -> crate::svg::common_attrs::Stroke<T, A, Self> {
crate::svg::common_attrs::stroke(self, brush, style)
}
},
SvgRectElement: SvgGeometryElement {},
SvgPathElement: SvgGeometryElement {},
SvgCircleElement: SvgGeometryElement {},
SvgLineElement: SvgGeometryElement {},
SvggElement: SvgGraphicsElement {}
);
1 change: 1 addition & 0 deletions crates/xilem_html/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ mod app;
mod attribute;
mod attribute_value;
mod context;
pub mod svg;
mod diff;
pub mod elements;
pub mod events;
Expand Down
209 changes: 209 additions & 0 deletions crates/xilem_html/src/svg/common_attrs.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,209 @@
// 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 wasm_bindgen::JsCast;
use xilem_core::{Id, MessageResult};

use crate::IntoAttributeValue;
use crate::{
context::{ChangeFlags, Cx},
view::{DomNode, View, ViewMarker},
};

pub struct Fill<T, A, V> {
child: V,
// This could reasonably be static Cow also, but keep things simple
brush: Brush,
phantom: PhantomData<fn() -> (T, A)>,
}

pub struct Stroke<T, A, V> {
child: V,
// This could reasonably be static Cow also, but keep things simple
brush: Brush,
style: peniko::kurbo::Stroke,
phantom: PhantomData<fn() -> (T, A)>,
}

pub fn fill<T, A, V>(child: V, brush: impl Into<Brush>) -> Fill<T, A, V> {
Fill {
child,
brush: brush.into(),
phantom: Default::default(),
}
}

pub fn stroke<T, A, V>(
child: V,
brush: impl Into<Brush>,
style: peniko::kurbo::Stroke,
) -> Stroke<T, A, V> {
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"),
}
}

macro_rules! impl_dom_interface_for_ty {
($dom_interface:ident, $ty:ident) => {
impl<T, A, E: $crate::interfaces::$dom_interface<T, A>>
$crate::interfaces::$dom_interface<T, A> for $ty<T, A, E>
{
}
};
}

macro_rules! impl_dom_interfaces_for_ty {
($ty:ident: $($dom_interface:ident,)*) => {
$(impl_dom_interface_for_ty!($dom_interface, $ty);)*
}
}

// TODO it would be great to have a macro that does automatically derive all child interfaces of some DOM interface...
impl_dom_interfaces_for_ty!(Fill:
Element,
SvgElement,
SvgGraphicsElement,
SvgGeometryElement,
SvgRectElement,
SvgPathElement,
SvgCircleElement,
SvgLineElement,
);

impl<T, A, V> ViewMarker for Fill<T, A, V> {}
impl<T, A, V> crate::interfaces::sealed::Sealed for Fill<T, A, V> {}

// TODO: make generic over A (probably requires Phantom)
impl<T, A, V: View<T, A>> View<T, A> for Fill<T, A, V> {
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_node_ref()
.dyn_ref::<web_sys::Element>()
.unwrap()
.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_node_ref()
.dyn_ref::<web_sys::Element>()
.unwrap()
.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<dyn Any>,
app_state: &mut T,
) -> MessageResult<A> {
self.child.message(id_path, state, message, app_state)
}
}

impl_dom_interfaces_for_ty!(Stroke:
Element,
SvgElement,
SvgGraphicsElement,
SvgGeometryElement,
SvgRectElement,
SvgPathElement,
SvgCircleElement,
SvgLineElement,
);

impl<T, A, V> ViewMarker for Stroke<T, A, V> {}
impl<T, A, V> crate::interfaces::sealed::Sealed for Stroke<T, A, V> {}

impl<T, A, V: View<T, A>> View<T, A> for Stroke<T, A, V> {
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_new_attribute_to_current_element(
&"stroke".into(),
&brush_svg_repr.clone().into_attribute_value(),
);
cx.add_new_attribute_to_current_element(
&"stroke-width".into(),
&self.style.width.into_attribute_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_new_attribute_to_current_element(
&"stroke".into(),
&brush_svg_repr.clone().into_attribute_value(),
);
cx.add_new_attribute_to_current_element(
&"stroke-width".into(),
&self.style.width.into_attribute_value(),
);
self.child
.rebuild(cx, &prev.child, id, child_state, element)
}

fn message(
&self,
id_path: &[Id],
(_, child_state): &mut Self::State,
message: Box<dyn Any>,
app_state: &mut T,
) -> MessageResult<A> {
self.child.message(id_path, child_state, message, app_state)
}
}
Loading

0 comments on commit af8d58d

Please sign in to comment.