Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Dust off xilem_svg #139

Merged
merged 6 commits into from
Nov 5, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 35 additions & 6 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 @@ -6,6 +6,7 @@ members = [
"crates/xilem_html/web_examples/counter_untyped",
"crates/xilem_html/web_examples/todomvc",
"crates/xilem_svg",
"crates/xilem_svg/web_examples/svgtoy",
]

[workspace.package]
Expand Down
5 changes: 1 addition & 4 deletions crates/xilem_svg/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -17,14 +17,11 @@ 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"]

[lib]
crate-type = ["cdylib"]

[dependencies]
xilem_core.workspace = true
kurbo.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"
Expand Down
4 changes: 2 additions & 2 deletions crates/xilem_svg/README.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# 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. A next step would be to factor it into a library so that applications can depend on it, but at the moment the test scene is baked in.
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 it is to use [Trunk]. Run `trunk serve`, then navigate the browser to the link provided (usually `http://localhost:8080`).
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/
8 changes: 0 additions & 8 deletions crates/xilem_svg/index.html

This file was deleted.

12 changes: 7 additions & 5 deletions crates/xilem_svg/src/class.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// Copyright 2023 the Druid Authors.
// SPDX-License-Identifier: Apache-2.0

use std::any::Any;
use std::{any::Any, marker::PhantomData};

use xilem_core::{Id, MessageResult};

Expand All @@ -10,23 +10,25 @@ use crate::{
view::{DomElement, View, ViewMarker},
};

pub struct Class<V> {
pub struct Class<T, V> {
child: V,
// This could reasonably be static Cow also, but keep things simple
class: String,
phantom: PhantomData<T>,
}

pub fn class<V>(child: V, class: impl Into<String>) -> Class<V> {
pub fn class<T, V>(child: V, class: impl Into<String>) -> Class<T, V> {
Class {
child,
class: class.into(),
phantom: Default::default(),
}
}

impl<V> ViewMarker for Class<V> {}
impl<T, V> ViewMarker for Class<T, V> {}

// TODO: make generic over A (probably requires Phantom)
impl<T, V: View<T>> View<T> for Class<V> {
impl<T, V: View<T>> View<T> for Class<T, V> {
type State = V::State;
type Element = V::Element;

Expand Down
17 changes: 11 additions & 6 deletions crates/xilem_svg/src/clicked.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// Copyright 2023 the Druid Authors.
// SPDX-License-Identifier: Apache-2.0

use std::any::Any;
use std::{any::Any, marker::PhantomData};

use wasm_bindgen::{prelude::Closure, JsCast};
use web_sys::SvgElement;
Expand All @@ -13,9 +13,10 @@ use crate::{
view::{DomElement, View, ViewMarker},
};

pub struct Clicked<V, F> {
pub struct Clicked<T, V, F> {
child: V,
callback: F,
phantom: PhantomData<T>,
}

pub struct ClickedState<S> {
Expand All @@ -27,13 +28,17 @@ pub struct ClickedState<S> {

struct ClickedMsg;

pub fn clicked<T, F: Fn(&mut T), V: View<T>>(child: V, callback: F) -> Clicked<V, F> {
Clicked { child, callback }
pub fn clicked<T, F: Fn(&mut T), V: View<T>>(child: V, callback: F) -> Clicked<T, V, F> {
Clicked {
child,
callback,
phantom: Default::default(),
}
}

impl<V, F> ViewMarker for Clicked<V, F> {}
impl<T, V, F> ViewMarker for Clicked<T, V, F> {}

impl<T, F: Fn(&mut T) + Send, V: View<T>> View<T> for Clicked<V, F> {
impl<T, F: Fn(&mut T) + Send, V: View<T>> View<T> for Clicked<T, V, F> {
type State = ClickedState<V::State>;

type Element = V::Element;
Expand Down
163 changes: 163 additions & 0 deletions crates/xilem_svg/src/common_attrs.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
// 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<T, V> {
child: V,
brush: Brush,
phantom: PhantomData<T>,
}

pub struct Stroke<T, V> {
child: V,
brush: Brush,
style: peniko::kurbo::Stroke,
phantom: PhantomData<T>,
}

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

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

impl<T, V> ViewMarker for Fill<T, V> {}

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

impl<T, V> ViewMarker for Stroke<T, V> {}

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