mirror of https://github.com/linebender/xilem
xilem_web: Add support for opacity in `svg::Fill` and dashes, opacity in `svg::Stroke` (#493)
Uses `ViewState` for a little bit of optimization (of allocations mostly).
This commit is contained in:
parent
2f23ee117a
commit
a52c3c7b3c
|
@ -8,6 +8,8 @@ use xilem_core::{MessageResult, Mut, View, ViewId, ViewMarker, ViewPathTracker};
|
|||
|
||||
use crate::{DynMessage, ElementAsRef, OptionalAction, ViewCtx};
|
||||
|
||||
/// Use a distinctive number here, to be able to catch bugs.
|
||||
/// In case the generational-id view path in `View::Message` lead to a wrong view
|
||||
const ON_EVENT_VIEW_ID: ViewId = ViewId::new(0x2357_1113);
|
||||
|
||||
/// Wraps a [`View`] `V` and attaches an event listener.
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
// Copyright 2023 the Xilem Authors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use std::borrow::Cow;
|
||||
use std::marker::PhantomData;
|
||||
use std::{borrow::Cow, fmt::Write as _};
|
||||
|
||||
use peniko::Brush;
|
||||
use xilem_core::{MessageResult, Mut, View, ViewId, ViewMarker};
|
||||
|
@ -48,6 +48,24 @@ pub fn stroke<State, Action, V>(
|
|||
}
|
||||
}
|
||||
|
||||
/// Rather general join string function, might be reused somewhere else as well...
|
||||
fn join(iter: &mut impl Iterator<Item: std::fmt::Display>, sep: &str) -> String {
|
||||
match iter.next() {
|
||||
None => String::new(),
|
||||
Some(first_elt) => {
|
||||
// estimate lower bound of capacity needed
|
||||
let (lower, _) = iter.size_hint();
|
||||
let mut result = String::with_capacity(sep.len() * lower);
|
||||
write!(&mut result, "{}", first_elt).unwrap();
|
||||
iter.for_each(|elt| {
|
||||
result.push_str(sep);
|
||||
write!(&mut result, "{}", elt).unwrap();
|
||||
});
|
||||
result
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn brush_to_string(brush: &Brush) -> String {
|
||||
match brush {
|
||||
Brush::Solid(color) => {
|
||||
|
@ -61,6 +79,14 @@ fn brush_to_string(brush: &Brush) -> String {
|
|||
}
|
||||
}
|
||||
|
||||
fn add_opacity_to_element(brush: &Brush, element: &mut impl WithAttributes, attr: &'static str) {
|
||||
let opacity = match brush {
|
||||
Brush::Solid(color) if color.a != u8::MAX => Some(color.a as f64 / 255.0),
|
||||
_ => None,
|
||||
};
|
||||
element.set_attribute(attr.into(), opacity.into_attr_value());
|
||||
}
|
||||
|
||||
impl<V, State, Action> ViewMarker for Fill<V, State, Action> {}
|
||||
impl<State, Action, V> View<State, Action, ViewCtx, DynMessage> for Fill<V, State, Action>
|
||||
where
|
||||
|
@ -76,6 +102,7 @@ where
|
|||
let brush_svg_repr = Cow::from(brush_to_string(&self.brush));
|
||||
element.start_attribute_modifier();
|
||||
element.set_attribute("fill".into(), brush_svg_repr.clone().into_attr_value());
|
||||
add_opacity_to_element(&self.brush, &mut element, "fill-opacity");
|
||||
element.end_attribute_modifier();
|
||||
(element, (brush_svg_repr, child_state))
|
||||
}
|
||||
|
@ -93,6 +120,7 @@ where
|
|||
*brush_svg_repr = Cow::from(brush_to_string(&self.brush));
|
||||
}
|
||||
element.set_attribute("fill".into(), brush_svg_repr.clone().into_attr_value());
|
||||
add_opacity_to_element(&self.brush, &mut element, "fill-opacity");
|
||||
element.end_attribute_modifier();
|
||||
element
|
||||
}
|
||||
|
@ -117,6 +145,12 @@ where
|
|||
}
|
||||
}
|
||||
|
||||
pub struct StrokeState<ChildState> {
|
||||
brush_svg_repr: Cow<'static, str>,
|
||||
stroke_dash_pattern_svg_repr: Option<Cow<'static, str>>,
|
||||
child_state: ChildState,
|
||||
}
|
||||
|
||||
impl<V, State, Action> ViewMarker for Stroke<V, State, Action> {}
|
||||
impl<State, Action, V> View<State, Action, ViewCtx, DynMessage> for Stroke<V, State, Action>
|
||||
where
|
||||
|
@ -124,33 +158,66 @@ where
|
|||
Action: 'static,
|
||||
V: View<State, Action, ViewCtx, DynMessage, Element: ElementWithAttributes>,
|
||||
{
|
||||
type ViewState = (Cow<'static, str>, V::ViewState);
|
||||
type ViewState = StrokeState<V::ViewState>;
|
||||
type Element = V::Element;
|
||||
|
||||
fn build(&self, ctx: &mut ViewCtx) -> (Self::Element, Self::ViewState) {
|
||||
let (mut element, child_state) = self.child.build(ctx);
|
||||
let brush_svg_repr = Cow::from(brush_to_string(&self.brush));
|
||||
|
||||
element.start_attribute_modifier();
|
||||
|
||||
let brush_svg_repr = Cow::from(brush_to_string(&self.brush));
|
||||
element.set_attribute("stroke".into(), brush_svg_repr.clone().into_attr_value());
|
||||
let stroke_dash_pattern_svg_repr = (!self.style.dash_pattern.is_empty())
|
||||
.then(|| Cow::from(join(&mut self.style.dash_pattern.iter(), " ")));
|
||||
let dash_pattern = stroke_dash_pattern_svg_repr.clone().into_attr_value();
|
||||
element.set_attribute("stroke-dasharray".into(), dash_pattern);
|
||||
let dash_offset = (self.style.dash_offset != 0.0).then_some(self.style.dash_offset);
|
||||
element.set_attribute("stroke-dashoffset".into(), dash_offset.into_attr_value());
|
||||
element.set_attribute("stroke-width".into(), self.style.width.into_attr_value());
|
||||
add_opacity_to_element(&self.brush, &mut element, "stroke-opacity");
|
||||
|
||||
element.end_attribute_modifier();
|
||||
(element, (brush_svg_repr, child_state))
|
||||
(
|
||||
element,
|
||||
StrokeState {
|
||||
brush_svg_repr,
|
||||
stroke_dash_pattern_svg_repr,
|
||||
child_state,
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
fn rebuild<'el>(
|
||||
&self,
|
||||
prev: &Self,
|
||||
(brush_svg_repr, child_state): &mut Self::ViewState,
|
||||
StrokeState {
|
||||
brush_svg_repr,
|
||||
stroke_dash_pattern_svg_repr,
|
||||
child_state,
|
||||
}: &mut Self::ViewState,
|
||||
ctx: &mut ViewCtx,
|
||||
mut element: Mut<'el, Self::Element>,
|
||||
) -> Mut<'el, Self::Element> {
|
||||
element.start_attribute_modifier();
|
||||
|
||||
let mut element = self.child.rebuild(&prev.child, child_state, ctx, element);
|
||||
|
||||
if self.brush != prev.brush {
|
||||
*brush_svg_repr = Cow::from(brush_to_string(&self.brush));
|
||||
}
|
||||
element.set_attribute("stroke".into(), brush_svg_repr.clone().into_attr_value());
|
||||
if self.style.dash_pattern != prev.style.dash_pattern {
|
||||
*stroke_dash_pattern_svg_repr = (!self.style.dash_pattern.is_empty())
|
||||
.then(|| Cow::from(join(&mut self.style.dash_pattern.iter(), " ")));
|
||||
}
|
||||
let dash_pattern = stroke_dash_pattern_svg_repr.clone().into_attr_value();
|
||||
element.set_attribute("stroke-dasharray".into(), dash_pattern);
|
||||
let dash_offset = (self.style.dash_offset != 0.0).then_some(self.style.dash_offset);
|
||||
element.set_attribute("stroke-dashoffset".into(), dash_offset.into_attr_value());
|
||||
element.set_attribute("stroke-width".into(), self.style.width.into_attr_value());
|
||||
add_opacity_to_element(&self.brush, &mut element, "stroke-opacity");
|
||||
|
||||
element.end_attribute_modifier();
|
||||
element
|
||||
}
|
||||
|
@ -161,16 +228,18 @@ where
|
|||
ctx: &mut ViewCtx,
|
||||
element: Mut<'_, Self::Element>,
|
||||
) {
|
||||
self.child.teardown(&mut view_state.1, ctx, element);
|
||||
self.child
|
||||
.teardown(&mut view_state.child_state, ctx, element);
|
||||
}
|
||||
|
||||
fn message(
|
||||
&self,
|
||||
(_, child_state): &mut Self::ViewState,
|
||||
view_state: &mut Self::ViewState,
|
||||
id_path: &[ViewId],
|
||||
message: DynMessage,
|
||||
app_state: &mut State,
|
||||
) -> MessageResult<Action, DynMessage> {
|
||||
self.child.message(child_state, id_path, message, app_state)
|
||||
self.child
|
||||
.message(&mut view_state.child_state, id_path, message, app_state)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -66,12 +66,16 @@ fn app_logic(state: &mut AppState) -> impl DomView<AppState> {
|
|||
.stroke(Color::BLUE, Default::default()),
|
||||
Rect::new(320.0, 100.0, 420.0, 200.0).class("red"),
|
||||
Rect::new(state.x, state.y, state.x + 100., state.y + 100.)
|
||||
.fill(Color::rgba8(100, 100, 255, 100))
|
||||
.pointer(|s: &mut AppState, msg| s.grab.handle(&mut s.x, &mut s.y, &msg)),
|
||||
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::Line::new((310.0, 210.0), (410.0, 310.0)).stroke(
|
||||
Color::YELLOW_GREEN,
|
||||
kurbo::Stroke::new(1.0).with_dashes(state.x, [7.0, 1.0]),
|
||||
),
|
||||
kurbo::Circle::new((460.0, 260.0), 45.0).on_click(|_, _| {
|
||||
web_sys::console::log_1(&"circle clicked".into());
|
||||
}),
|
||||
|
|
Loading…
Reference in New Issue