From aa29fa2f4a37278af3e98b4719952dc32fc3ffdb Mon Sep 17 00:00:00 2001 From: Muhammad Ragib Hasin Date: Mon, 16 Sep 2024 16:28:58 +0600 Subject: [PATCH 01/10] Add Board widget for absolute positioning --- masonry/src/widget/board.rs | 633 +++++++++++++++++++++++++++++++ masonry/src/widget/mod.rs | 2 + xilem/examples/board.rs | 80 ++++ xilem/src/view/board.rs | 717 ++++++++++++++++++++++++++++++++++++ xilem/src/view/mod.rs | 3 + 5 files changed, 1435 insertions(+) create mode 100644 masonry/src/widget/board.rs create mode 100644 xilem/examples/board.rs create mode 100644 xilem/src/view/board.rs diff --git a/masonry/src/widget/board.rs b/masonry/src/widget/board.rs new file mode 100644 index 000000000..ede66cb6e --- /dev/null +++ b/masonry/src/widget/board.rs @@ -0,0 +1,633 @@ +// Copyright 2018 the Xilem Authors and the Druid Authors +// SPDX-License-Identifier: Apache-2.0 + +//! A widget that arranges its children in a one-dimensional array. + +use accesskit::Role; +use smallvec::SmallVec; +use tracing::{trace_span, warn, Span}; +use vello::kurbo::{ + self, Affine, Arc, BezPath, Circle, CircleSegment, CubicBez, Ellipse, Line, PathEl, PathSeg, + QuadBez, RoundedRect, Shape as _, Stroke, Vec2, +}; +use vello::peniko::{Brush, Fill}; +use vello::Scene; + +use crate::widget::WidgetMut; +use crate::{ + AccessCtx, AccessEvent, BoxConstraints, EventCtx, LayoutCtx, LifeCycle, LifeCycleCtx, PaintCtx, + Point, PointerEvent, Rect, Size, StatusChange, TextEvent, Widget, WidgetId, WidgetPod, +}; + +/// A container with absolute positioning layout. +pub struct Board { + children: Vec, + origin: Point, + scale: Vec2, +} + +/// Parameters for an item in a [`Board`] container. +#[derive(Debug, Copy, Clone, PartialEq)] +pub struct BoardParams { + origin: Point, + size: Size, +} + +pub struct Shape { + shape: KurboShape, + transform: Affine, + fill_style: Fill, + fill_brush: Brush, + fill_brush_transform: Option, + stroke_style: Stroke, + stroke_brush: Brush, + stroke_brush_transform: Option, +} + +#[derive(Debug, Clone, PartialEq)] +pub enum KurboShape { + PathSeg(PathSeg), + Arc(Arc), + BezPath(BezPath), + Circle(Circle), + CircleSegment(CircleSegment), + CubicBez(CubicBez), + Ellipse(Ellipse), + Line(Line), + QuadBez(QuadBez), + Rect(Rect), + RoundedRect(RoundedRect), +} + +// --- MARK: IMPL BOARD --- +impl Board { + /// Create a new Board oriented with viewport origin set to (0, 0) and scale (1, 1). + pub fn new() -> Self { + Board { + children: Vec::new(), + origin: Point::ZERO, + scale: Vec2::new(1., 1.), + } + } + + /// Builder-style method to add a positioned child to the container. + pub fn with_child_pod( + mut self, + widget: WidgetPod>, + params: impl Into, + ) -> Self { + // TODO - dedup? + self.children.push(Child::Widget(widget, params.into())); + self + } + + /// Builder-style method to add a Kurbo shape to the container. + pub fn with_shape_child(mut self, shape: Shape) -> Self { + self.children + .push(Child::Shape(WidgetPod::new(Box::new(shape)))); + self + } + + pub fn len(&self) -> usize { + self.children.len() + } + + pub fn is_empty(&self) -> bool { + self.len() == 0 + } +} + +impl Default for Board { + fn default() -> Self { + Self::new() + } +} + +// --- MARK: IMPL SHAPE --- +impl Shape { + pub fn new(shape: impl Into) -> Self { + Shape { + shape: shape.into(), + transform: Default::default(), + fill_style: Fill::NonZero, + fill_brush: Default::default(), + fill_brush_transform: Default::default(), + stroke_style: Default::default(), + stroke_brush: Default::default(), + stroke_brush_transform: Default::default(), + } + } + + pub fn shape(&self) -> &KurboShape { + &self.shape + } + + pub fn set_transform(&mut self, transform: Affine) { + self.transform = transform; + } + + pub fn set_fill_style(&mut self, fill_style: Fill) { + self.fill_style = fill_style; + } + + pub fn set_fill_brush(&mut self, fill_brush: Brush) { + self.fill_brush = fill_brush; + } + + pub fn set_fill_brush_transform(&mut self, fill_brush_transform: Option) { + self.fill_brush_transform = fill_brush_transform; + } + + pub fn set_stroke_style(&mut self, stroke_style: Stroke) { + self.stroke_style = stroke_style; + } + + pub fn set_stroke_brush(&mut self, stroke_brush: Brush) { + self.stroke_brush = stroke_brush; + } + + pub fn set_stroke_brush_transform(&mut self, stroke_brush_transform: Option) { + self.stroke_brush_transform = stroke_brush_transform; + } +} + +// --- MARK: WIDGETMUT--- +impl<'a> WidgetMut<'a, Board> { + /// Set the origin. + pub fn set_origin(&mut self, origin: Point) { + self.widget.origin = origin; + self.ctx.request_layout(); + } + + /// Set the scale. + pub fn set_scale(&mut self, scale: Vec2) { + self.widget.scale = scale; + self.ctx.request_layout(); + } + + /// Add a positioned child widget. + pub fn add_child(&mut self, child: impl Widget, params: impl Into) { + let child = Child::Widget(WidgetPod::new(Box::new(child)), params.into()); + + self.widget.children.push(child); + self.ctx.children_changed(); + } + + /// Add a Kurbo shape. + pub fn add_shape_child(&mut self, shape: Shape) { + self.widget + .children + .push(Child::Shape(WidgetPod::new(Box::new(shape)))); + self.ctx.children_changed(); + } + + pub fn insert_child(&mut self, idx: usize, child: impl Widget, params: impl Into) { + self.insert_child_pod(idx, WidgetPod::new(Box::new(child)), params); + } + + pub fn insert_child_pod( + &mut self, + idx: usize, + child: WidgetPod>, + params: impl Into, + ) { + let child = Child::Widget(child, params.into()); + self.widget.children.insert(idx, child); + self.ctx.children_changed(); + } + + pub fn insert_shape_child(&mut self, idx: usize, shape: Shape) { + self.widget + .children + .insert(idx, Child::Shape(WidgetPod::new(Box::new(shape)))); + self.ctx.children_changed(); + } + + pub fn remove_child(&mut self, idx: usize) { + let (Child::Widget(widget, _) | Child::Shape(widget)) = self.widget.children.remove(idx); + self.ctx.remove_child(widget); + self.ctx.request_layout(); + } + + // FIXME - Remove Box + pub fn child_mut(&mut self, idx: usize) -> WidgetMut<'_, Box> { + let (Child::Widget(widget, _) | Child::Shape(widget)) = &mut self.widget.children[idx]; + self.ctx.get_mut(widget) + } + + /// Updates the position parameters for the child at `idx`, + /// + /// # Panics + /// + /// Panics if the element at `idx` is not a widget. + pub fn update_child_board_params(&mut self, idx: usize, new_params: impl Into) { + if let Child::Widget(_, params) = &mut self.widget.children[idx] { + *params = new_params.into(); + self.ctx.children_changed(); + } + } + + pub fn clear(&mut self) { + if !self.widget.children.is_empty() { + self.ctx.request_layout(); + + for child in self.widget.children.drain(..) { + if let Child::Widget(widget, _) = child { + self.ctx.remove_child(widget); + } + } + } + } +} + +impl<'a> WidgetMut<'a, Shape> { + pub fn set_shape(&mut self, shape: KurboShape) { + self.widget.shape = shape; + self.ctx.request_layout(); + self.ctx.request_paint(); + self.ctx.request_accessibility_update(); + } + + pub fn set_transform(&mut self, transform: Affine) { + self.widget.transform = transform; + self.ctx.request_paint(); + } + + pub fn set_fill_style(&mut self, fill_style: Fill) { + self.widget.fill_style = fill_style; + self.ctx.request_paint(); + } + + pub fn set_fill_brush(&mut self, fill_brush: Brush) { + self.widget.fill_brush = fill_brush; + self.ctx.request_paint(); + } + + pub fn set_fill_brush_transform(&mut self, fill_brush_transform: Option) { + self.widget.fill_brush_transform = fill_brush_transform; + self.ctx.request_paint(); + } + + pub fn set_stroke_style(&mut self, stroke_style: Stroke) { + self.widget.stroke_style = stroke_style; + self.ctx.request_paint(); + } + + pub fn set_stroke_brush(&mut self, stroke_brush: Brush) { + self.widget.stroke_brush = stroke_brush; + self.ctx.request_paint(); + } + + pub fn set_stroke_brush_transform(&mut self, stroke_brush_transform: Option) { + self.widget.stroke_brush_transform = stroke_brush_transform; + self.ctx.request_paint(); + } +} + +// --- MARK: IMPL WIDGET--- +impl Widget for Board { + fn on_pointer_event(&mut self, _ctx: &mut EventCtx, _event: &PointerEvent) {} + + fn on_text_event(&mut self, _ctx: &mut EventCtx, _event: &TextEvent) {} + + fn on_access_event(&mut self, _ctx: &mut EventCtx, _event: &AccessEvent) {} + + fn on_status_change(&mut self, _ctx: &mut LifeCycleCtx, _event: &StatusChange) {} + + fn lifecycle(&mut self, ctx: &mut LifeCycleCtx, event: &LifeCycle) { + for child in self.children.iter_mut().filter_map(|x| x.widget_mut()) { + child.lifecycle(ctx, event); + } + } + + fn layout(&mut self, ctx: &mut LayoutCtx, bc: &BoxConstraints) -> Size { + bc.debug_check("Board"); + + for child in &mut self.children { + match child { + Child::Widget(widget, params) => { + if ctx.child_needs_layout(widget) { + ctx.run_layout(widget, &BoxConstraints::tight(params.size)); + } else { + ctx.skip_layout(widget); + }; + ctx.place_child(widget, self.origin + params.origin.to_vec2()); + } + Child::Shape(shape) => { + let raw_widget = ctx.get_raw_ref(shape); + let params = BoardParams::from( + raw_widget + .widget() + .as_ref() + .as_dyn_any() + .downcast_ref::() + .unwrap() + .shape() + .bounding_box(), + ); + if ctx.child_needs_layout(shape) { + ctx.run_layout(shape, &BoxConstraints::tight(params.size)); + } else { + ctx.skip_layout(shape); + }; + ctx.place_child(shape, self.origin); + } + } + } + + bc.max() + } + + fn paint(&mut self, _ctx: &mut PaintCtx, _scene: &mut Scene) {} + + fn accessibility_role(&self) -> Role { + Role::GenericContainer + } + + fn accessibility(&mut self, _ctx: &mut AccessCtx) {} + + fn children_ids(&self) -> SmallVec<[WidgetId; 16]> { + self.children + .iter() + .filter_map(|child| child.widget()) + .map(|widget_pod| widget_pod.id()) + .collect() + } + + fn make_trace_span(&self) -> Span { + trace_span!("Board") + } +} + +impl Widget for Shape { + fn on_pointer_event(&mut self, _ctx: &mut EventCtx, _event: &PointerEvent) {} + fn on_text_event(&mut self, _ctx: &mut EventCtx, _event: &TextEvent) {} + fn on_access_event(&mut self, _ctx: &mut EventCtx, _event: &AccessEvent) {} + fn on_status_change(&mut self, _ctx: &mut LifeCycleCtx, _event: &StatusChange) {} + fn lifecycle(&mut self, _ctx: &mut LifeCycleCtx, _event: &LifeCycle) {} + + fn layout(&mut self, _ctx: &mut LayoutCtx, bc: &BoxConstraints) -> Size { + let size = self.shape.bounding_box().size(); + if !bc.contains(size) { + warn!("The shape is oversized"); + } + size + } + + fn paint(&mut self, _ctx: &mut PaintCtx, scene: &mut Scene) { + scene.fill( + self.fill_style, + self.transform, + &self.fill_brush, + self.fill_brush_transform, + &self.shape, + ); + scene.stroke( + &self.stroke_style, + self.transform, + &self.stroke_brush, + self.stroke_brush_transform, + &self.shape, + ); + } + + fn accessibility_role(&self) -> Role { + Role::GraphicsSymbol + } + + fn accessibility(&mut self, _ctx: &mut AccessCtx) {} + + fn children_ids(&self) -> SmallVec<[WidgetId; 16]> { + SmallVec::new() + } + + fn make_trace_span(&self) -> Span { + trace_span!("Shape") + } +} + +// --- MARK: OTHER IMPLS--- +impl BoardParams { + /// Create a `BoardParams` with a specific `origin` and `size`. + pub fn new(origin: impl Into, size: impl Into) -> Self { + BoardParams { + origin: origin.into(), + size: size.into(), + } + } +} + +impl From for BoardParams { + fn from(rect: Rect) -> Self { + BoardParams { + origin: rect.origin(), + size: rect.size(), + } + } +} + +enum Child { + Widget(WidgetPod>, BoardParams), + Shape(WidgetPod>), +} + +impl Child { + fn widget_mut(&mut self) -> Option<&mut WidgetPod>> { + match self { + Child::Widget(widget, _) | Child::Shape(widget) => Some(widget), + } + } + fn widget(&self) -> Option<&WidgetPod>> { + match self { + Child::Widget(widget, _) | Child::Shape(widget) => Some(widget), + } + } +} + +macro_rules! for_all_variants { + ($self:expr; $i:ident => $e:expr) => { + match $self { + Self::PathSeg($i) => $e, + Self::Arc($i) => $e, + Self::BezPath($i) => $e, + Self::Circle($i) => $e, + Self::CircleSegment($i) => $e, + Self::CubicBez($i) => $e, + Self::Ellipse($i) => $e, + Self::Line($i) => $e, + Self::QuadBez($i) => $e, + Self::Rect($i) => $e, + Self::RoundedRect($i) => $e, + } + }; +} + +impl kurbo::Shape for KurboShape { + type PathElementsIter<'iter> = PathElementsIter<'iter>; + + fn path_elements(&self, tolerance: f64) -> Self::PathElementsIter<'_> { + match self { + Self::PathSeg(i) => PathElementsIter::PathSeg(i.path_elements(tolerance)), + Self::Arc(i) => PathElementsIter::Arc(i.path_elements(tolerance)), + Self::BezPath(i) => PathElementsIter::BezPath(i.path_elements(tolerance)), + Self::Circle(i) => PathElementsIter::Circle(i.path_elements(tolerance)), + Self::CircleSegment(i) => PathElementsIter::CircleSegment(i.path_elements(tolerance)), + Self::CubicBez(i) => PathElementsIter::CubicBez(i.path_elements(tolerance)), + Self::Ellipse(i) => PathElementsIter::Ellipse(i.path_elements(tolerance)), + Self::Line(i) => PathElementsIter::Line(i.path_elements(tolerance)), + Self::QuadBez(i) => PathElementsIter::QuadBez(i.path_elements(tolerance)), + Self::Rect(i) => PathElementsIter::Rect(i.path_elements(tolerance)), + Self::RoundedRect(i) => PathElementsIter::RoundedRect(i.path_elements(tolerance)), + } + } + + fn area(&self) -> f64 { + for_all_variants!(self; i => i.area()) + } + + fn perimeter(&self, accuracy: f64) -> f64 { + for_all_variants!(self; i => i.perimeter(accuracy)) + } + + fn winding(&self, pt: Point) -> i32 { + for_all_variants!(self; i => i.winding(pt)) + } + + fn bounding_box(&self) -> Rect { + for_all_variants!(self; i => i.bounding_box()) + } + + fn to_path(&self, tolerance: f64) -> BezPath { + for_all_variants!(self; i => i.to_path(tolerance)) + } + + fn into_path(self, tolerance: f64) -> BezPath { + for_all_variants!(self; i => i.into_path(tolerance)) + } + + fn contains(&self, pt: Point) -> bool { + for_all_variants!(self; i => i.contains(pt)) + } + + fn as_line(&self) -> Option { + for_all_variants!(self; i => i.as_line()) + } + + fn as_rect(&self) -> Option { + for_all_variants!(self; i => i.as_rect()) + } + + fn as_rounded_rect(&self) -> Option { + for_all_variants!(self; i => i.as_rounded_rect()) + } + + fn as_circle(&self) -> Option { + for_all_variants!(self; i => i.as_circle()) + } + + fn as_path_slice(&self) -> Option<&[PathEl]> { + for_all_variants!(self; i => i.as_path_slice()) + } +} + +macro_rules! impl_from_shape { + ($t:ident) => { + impl From for KurboShape { + fn from(value: kurbo::$t) -> Self { + KurboShape::$t(value) + } + } + }; +} + +impl_from_shape!(PathSeg); +impl_from_shape!(Arc); +impl_from_shape!(BezPath); +impl_from_shape!(Circle); +impl_from_shape!(CircleSegment); +impl_from_shape!(CubicBez); +impl_from_shape!(Ellipse); +impl_from_shape!(Line); +impl_from_shape!(QuadBez); +impl_from_shape!(Rect); +impl_from_shape!(RoundedRect); + +pub enum PathElementsIter<'i> { + PathSeg(::PathElementsIter<'i>), + Arc(::PathElementsIter<'i>), + BezPath(::PathElementsIter<'i>), + Circle(::PathElementsIter<'i>), + CircleSegment(::PathElementsIter<'i>), + CubicBez(::PathElementsIter<'i>), + Ellipse(::PathElementsIter<'i>), + Line(::PathElementsIter<'i>), + QuadBez(::PathElementsIter<'i>), + Rect(::PathElementsIter<'i>), + RoundedRect(::PathElementsIter<'i>), +} + +impl<'i> Iterator for PathElementsIter<'i> { + type Item = PathEl; + + fn next(&mut self) -> Option { + for_all_variants!(self; i => i.next()) + } +} + +// --- MARK: TESTS --- +#[cfg(test)] +mod tests { + use vello::{kurbo::Circle, peniko::Brush}; + + use super::*; + use crate::assert_render_snapshot; + use crate::testing::TestHarness; + use crate::widget::Button; + + #[test] + fn kurbo_shape_circle() { + let mut widget = Shape::new(Circle::new((50., 50.), 30.)); + widget.set_fill_brush(Brush::Solid(vello::peniko::Color::CHARTREUSE)); + widget.set_stroke_style(Stroke::new(2.).with_dashes(0., [2., 1.])); + widget.set_stroke_brush(Brush::Solid(vello::peniko::Color::PALE_VIOLET_RED)); + + let mut harness = TestHarness::create(widget); + + assert_render_snapshot!(harness, "kurbo_shape_circle"); + } + + #[test] + fn board_absolute_placement_snapshots() { + let widget = Board::new() + .with_child_pod( + WidgetPod::new(Box::new(Button::new("hello"))), + Rect::new(10., 10., 60., 40.), + ) + .with_child_pod( + WidgetPod::new(Box::new(Button::new("world"))), + Rect::new(30., 30., 80., 60.), + ); + + let mut harness = TestHarness::create(widget); + + assert_render_snapshot!(harness, "absolute_placement"); + } + + #[test] + fn board_shape_placement_snapshots() { + let mut shape = Shape::new(Circle::new((70., 50.), 30.)); + shape.set_fill_brush(Brush::Solid(vello::peniko::Color::NAVY)); + shape.set_stroke_style(Stroke::new(2.).with_dashes(0., [2., 1.])); + shape.set_stroke_brush(Brush::Solid(vello::peniko::Color::PALE_VIOLET_RED)); + let widget = Board::new() + .with_child_pod( + WidgetPod::new(Box::new(Button::new("hello"))), + Rect::new(10., 10., 60., 40.), + ) + .with_shape_child(shape); + + let mut harness = TestHarness::create(widget); + + assert_render_snapshot!(harness, "shape_placement"); + } +} diff --git a/masonry/src/widget/mod.rs b/masonry/src/widget/mod.rs index 7a3720ca2..2499868b1 100644 --- a/masonry/src/widget/mod.rs +++ b/masonry/src/widget/mod.rs @@ -14,6 +14,7 @@ mod widget_state; mod tests; mod align; +mod board; mod button; mod checkbox; mod flex; @@ -34,6 +35,7 @@ mod widget_arena; pub use self::image::Image; pub use align::Align; +pub use board::{Board, BoardParams, KurboShape, Shape}; pub use button::Button; pub use checkbox::Checkbox; pub use flex::{Axis, CrossAxisAlignment, Flex, FlexParams, MainAxisAlignment}; diff --git a/xilem/examples/board.rs b/xilem/examples/board.rs new file mode 100644 index 000000000..5684797e5 --- /dev/null +++ b/xilem/examples/board.rs @@ -0,0 +1,80 @@ +// Copyright 2024 the Xilem Authors +// SPDX-License-Identifier: Apache-2.0 + +use masonry::{ + widget::{CrossAxisAlignment, MainAxisAlignment}, + Size, +}; +use winit::error::EventLoopError; +use xilem::view::{ + board, button, flex, label, Axis, BoardExt, BoardParams, FlexExt as _, FlexSpacer, ShapeExt, +}; +use xilem::{Color, EventLoop, WidgetView, Xilem}; + +struct AppState { + buttons: Vec, + clicked: Option, +} + +impl AppState { + fn view(&mut self) -> impl WidgetView { + flex(( + FlexSpacer::Fixed(30.0), + flex(( + button("B", |state: &mut AppState| state.buttons.push(true)), + button("C", |state: &mut AppState| state.buttons.push(false)), + button("-", |state: &mut AppState| { + state.buttons.pop(); + state.clicked = None; + }), + label(self.clicked.map_or_else( + || String::from("Nothing has been clicked."), + |i| format!("Button {i} has been clicked."), + )), + )) + .direction(Axis::Horizontal), + FlexSpacer::Fixed(10.0), + board( + self.buttons + .iter() + .copied() + .enumerate() + .map(|(i, is_button)| { + let origin = i as f64 * 15. + 10.; + let size = Size::new(30., 30.); + if is_button { + button(i.to_string(), move |state: &mut AppState| { + state.clicked = Some(i); + }) + .positioned(BoardParams::new((origin, origin), size)) + .into_any_board() + } else { + vello::kurbo::Circle::new((origin + 15., origin + 15.), 15.) + .view() + .fill_brush(Color::NAVY) + .stroke_brush(Color::PAPAYA_WHIP) + .stroke_style(vello::kurbo::Stroke::new(2.)) + .into_any_board() + } + }) + .collect::>(), + ) + .flex(1.), + )) + .direction(Axis::Vertical) + .cross_axis_alignment(CrossAxisAlignment::Center) + .main_axis_alignment(MainAxisAlignment::Center) + } +} + +fn main() -> Result<(), EventLoopError> { + let app = Xilem::new( + AppState { + buttons: Vec::new(), + clicked: None, + }, + AppState::view, + ); + app.run_windowed(EventLoop::with_user_event(), "Board".into())?; + Ok(()) +} diff --git a/xilem/src/view/board.rs b/xilem/src/view/board.rs new file mode 100644 index 000000000..dd07f91bc --- /dev/null +++ b/xilem/src/view/board.rs @@ -0,0 +1,717 @@ +// Copyright 2024 the Xilem Authors +// SPDX-License-Identifier: Apache-2.0 + +use std::marker::PhantomData; + +use masonry::{ + widget::{self, KurboShape, Shape, WidgetMut}, + Widget, +}; +use vello::kurbo::{Affine, Point, Stroke, Vec2}; +use vello::peniko::{Brush, Fill}; +use xilem_core::{ + AppendVec, DynMessage, ElementSplice, MessageResult, Mut, SuperElement, View, ViewElement, + ViewId, ViewMarker, ViewPathTracker, ViewSequence, +}; + +use crate::{AnyWidgetView, Pod, ViewCtx, WidgetView}; + +pub use masonry::widget::{Axis, BoardParams, CrossAxisAlignment, MainAxisAlignment}; + +pub fn board>( + sequence: Seq, +) -> Board { + Board { + sequence, + origin: Point::ZERO, + scale: Vec2::new(1., 1.), + phantom: PhantomData, + } +} + +pub struct Board { + sequence: Seq, + origin: Point, + scale: Vec2, + phantom: PhantomData (State, Action)>, +} + +impl ViewMarker for Board {} +impl View for Board +where + State: 'static, + Action: 'static, + Seq: BoardSequence, +{ + type Element = Pod; + + type ViewState = Seq::SeqState; + + fn build(&self, ctx: &mut ViewCtx) -> (Self::Element, Self::ViewState) { + let mut elements = AppendVec::default(); + let mut widget = widget::Board::new(); + let seq_state = self.sequence.seq_build(ctx, &mut elements); + for child in elements.into_inner() { + widget = match child { + BoardElement::View(pod, params) => widget.with_child_pod(pod.inner, params), + BoardElement::Shape(shape) => widget.with_shape_child(shape), + } + } + (Pod::new(widget), seq_state) + } + + fn rebuild<'el>( + &self, + prev: &Self, + view_state: &mut Self::ViewState, + ctx: &mut ViewCtx, + mut element: Mut<'el, Self::Element>, + ) -> Mut<'el, Self::Element> { + if prev.origin != self.origin { + element.set_origin(self.origin); + ctx.mark_changed(); + } + if prev.scale != self.scale { + element.set_scale(self.scale); + ctx.mark_changed(); + } + // TODO: Re-use scratch space? + let mut splice = BoardSplice::new(element); + self.sequence + .seq_rebuild(&prev.sequence, view_state, ctx, &mut splice); + debug_assert!(splice.scratch.is_empty()); + splice.element + } + + fn teardown( + &self, + view_state: &mut Self::ViewState, + ctx: &mut ViewCtx, + element: Mut<'_, Self::Element>, + ) { + let mut splice = BoardSplice::new(element); + self.sequence.seq_teardown(view_state, ctx, &mut splice); + debug_assert!(splice.scratch.into_inner().is_empty()); + } + + fn message( + &self, + view_state: &mut Self::ViewState, + id_path: &[xilem_core::ViewId], + message: DynMessage, + app_state: &mut State, + ) -> MessageResult { + self.sequence + .seq_message(view_state, id_path, message, app_state) + } +} + +#[allow(clippy::large_enum_variant)] +pub enum BoardElement { + View(Pod>, BoardParams), + Shape(Shape), +} + +pub struct BoardElementMut<'w> { + parent: WidgetMut<'w, widget::Board>, + idx: usize, +} + +struct BoardSplice<'w> { + idx: usize, + element: WidgetMut<'w, widget::Board>, + scratch: AppendVec, +} + +impl<'w> BoardSplice<'w> { + fn new(element: WidgetMut<'w, widget::Board>) -> Self { + Self { + idx: 0, + element, + scratch: AppendVec::default(), + } + } +} + +impl ViewElement for BoardElement { + type Mut<'w> = BoardElementMut<'w>; +} + +impl SuperElement for BoardElement { + fn upcast(child: BoardElement) -> Self { + child + } + + fn with_downcast_val( + mut this: Mut<'_, Self>, + f: impl FnOnce(Mut<'_, BoardElement>) -> R, + ) -> (Self::Mut<'_>, R) { + let r = { + let parent = this.parent.reborrow_mut(); + let reborrow = BoardElementMut { + idx: this.idx, + parent, + }; + f(reborrow) + }; + (this, r) + } +} + +// impl SuperElement> for BoardElement { +// fn upcast(child: Pod) -> Self { +// BoardElement { +// element: child.inner.boxed().into(), +// params: BoardParams::default(), +// } +// } + +// fn with_downcast_val( +// mut this: Mut<'_, Self>, +// f: impl FnOnce(Mut<'_, Pod>) -> R, +// ) -> (Mut<'_, Self>, R) { +// let ret = { +// let mut child = this +// .parent +// .child_mut(this.idx) +// .expect("This is supposed to be a widget"); +// let downcast = child.downcast(); +// f(downcast) +// }; + +// (this, ret) +// } +// } + +impl ElementSplice for BoardSplice<'_> { + fn insert(&mut self, element: BoardElement) { + match element { + BoardElement::View(pod, params) => { + self.element.insert_child_pod(self.idx, pod.inner, params); + } + BoardElement::Shape(shape) => self.element.insert_shape_child(self.idx, shape), + } + self.idx += 1; + } + + fn with_scratch(&mut self, f: impl FnOnce(&mut AppendVec) -> R) -> R { + let ret = f(&mut self.scratch); + for element in self.scratch.drain() { + match element { + BoardElement::View(pod, params) => { + self.element.insert_child_pod(self.idx, pod.inner, params); + } + BoardElement::Shape(shape) => self.element.insert_shape_child(self.idx, shape), + } + self.idx += 1; + } + ret + } + + fn mutate(&mut self, f: impl FnOnce(Mut<'_, BoardElement>) -> R) -> R { + let child = BoardElementMut { + parent: self.element.reborrow_mut(), + idx: self.idx, + }; + let ret = f(child); + self.idx += 1; + ret + } + + fn delete(&mut self, f: impl FnOnce(Mut<'_, BoardElement>) -> R) -> R { + let ret = { + let child = BoardElementMut { + parent: self.element.reborrow_mut(), + idx: self.idx, + }; + f(child) + }; + self.element.remove_child(self.idx); + ret + } + + fn skip(&mut self, n: usize) { + self.idx += n; + } +} + +/// An ordered sequence of views for a [`Board`] view. +/// See [`ViewSequence`] for more technical details. +pub trait BoardSequence: + ViewSequence +{ +} + +impl BoardSequence for Seq where + Seq: ViewSequence +{ +} + +/// A trait which extends a [`WidgetView`] with methods to provide parameters for a positioned item, +/// or being able to use it interchangeably with a shape. +pub trait BoardExt: WidgetView { + /// Makes this view absolutely positioned in a `Board`. + fn positioned(self, params: impl Into) -> BoardViewItem + where + State: 'static, + Action: 'static, + Self: Sized, + { + board_item(self, params) + } +} + +impl> BoardExt for V {} + +/// A `WidgetView` that can be used within a [`Board`] [`View`] +pub struct BoardViewItem { + view: V, + params: BoardParams, + phantom: PhantomData (State, Action)>, +} + +/// Makes this view absolutely positioned in a `Board`. +pub fn board_item( + view: V, + params: impl Into, +) -> BoardViewItem +where + State: 'static, + Action: 'static, + V: WidgetView, +{ + BoardViewItem { + params: params.into(), + view, + phantom: PhantomData, + } +} + +impl From> for AnyBoardChild +where + State: 'static, + Action: 'static, + V: WidgetView, +{ + fn from(value: BoardViewItem) -> Self { + AnyBoardChild::View(board_item(value.view.boxed(), value.params)) + } +} + +impl ViewMarker for BoardViewItem {} +impl View for BoardViewItem +where + State: 'static, + Action: 'static, + V: WidgetView, +{ + type Element = BoardElement; + + type ViewState = V::ViewState; + + fn build(&self, ctx: &mut ViewCtx) -> (Self::Element, Self::ViewState) { + let (pod, state) = self.view.build(ctx); + ( + BoardElement::View(pod.inner.boxed().into(), self.params), + state, + ) + } + + fn rebuild<'el>( + &self, + prev: &Self, + view_state: &mut Self::ViewState, + ctx: &mut ViewCtx, + mut element: Mut<'el, Self::Element>, + ) -> Mut<'el, Self::Element> { + { + if self.params != prev.params { + element + .parent + .update_child_board_params(element.idx, self.params); + } + let mut child = element.parent.child_mut(element.idx); + self.view + .rebuild(&prev.view, view_state, ctx, child.downcast()); + } + element + } + + fn teardown( + &self, + view_state: &mut Self::ViewState, + ctx: &mut ViewCtx, + mut element: Mut<'_, Self::Element>, + ) { + let mut child = element.parent.child_mut(element.idx); + self.view.teardown(view_state, ctx, child.downcast()); + } + + fn message( + &self, + view_state: &mut Self::ViewState, + id_path: &[xilem_core::ViewId], + message: DynMessage, + app_state: &mut State, + ) -> MessageResult { + self.view.message(view_state, id_path, message, app_state) + } +} + +pub struct ShapeItem { + shape: KurboShape, + transform: Affine, + fill_style: Fill, + fill_brush: Brush, + fill_brush_transform: Option, + stroke_style: Stroke, + stroke_brush: Brush, + stroke_brush_transform: Option, +} + +impl ShapeItem { + pub fn transform(mut self, transform: Affine) -> Self { + self.transform = transform; + self + } + pub fn fill_style(mut self, fill_style: Fill) -> Self { + self.fill_style = fill_style; + self + } + pub fn fill_brush(mut self, fill_brush: impl Into) -> Self { + self.fill_brush = fill_brush.into(); + self + } + pub fn fill_brush_transform(mut self, fill_brush_transform: impl Into>) -> Self { + self.fill_brush_transform = fill_brush_transform.into(); + self + } + pub fn stroke_style(mut self, stroke_style: Stroke) -> Self { + self.stroke_style = stroke_style; + self + } + pub fn stroke_brush(mut self, stroke_brush: impl Into) -> Self { + self.stroke_brush = stroke_brush.into(); + self + } + pub fn stroke_brush_transform( + mut self, + stroke_brush_transform: impl Into>, + ) -> Self { + self.stroke_brush_transform = stroke_brush_transform.into(); + self + } + + pub fn into_any_board(self) -> AnyBoardChild { + AnyBoardChild::Shape(Box::new(self)) + } +} + +pub trait ShapeExt { + fn view(self) -> ShapeItem; +} + +impl ShapeExt for T +where + KurboShape: From, +{ + fn view(self) -> ShapeItem { + ShapeItem { + shape: self.into(), + transform: Default::default(), + fill_style: Fill::NonZero, + fill_brush: Default::default(), + fill_brush_transform: Default::default(), + stroke_style: Default::default(), + stroke_brush: Default::default(), + stroke_brush_transform: Default::default(), + } + } +} + +impl From for AnyBoardChild { + fn from(shape: ShapeItem) -> Self { + AnyBoardChild::Shape(Box::new(shape)) + } +} + +impl ViewMarker for ShapeItem {} +// This impl doesn't require a view id, as it neither receives, nor sends any messages +// If this should ever change, it's necessary to adjust the `AnyBoardChild` `View` impl +impl View for ShapeItem { + type Element = BoardElement; + type ViewState = (); + + fn build(&self, _ctx: &mut ViewCtx) -> (Self::Element, Self::ViewState) { + let Self { + shape, + transform, + fill_style, + fill_brush, + fill_brush_transform, + stroke_style, + stroke_brush, + stroke_brush_transform, + } = self; + let mut shape = Shape::new(shape.clone()); + shape.set_transform(*transform); + shape.set_fill_style(*fill_style); + shape.set_fill_brush(fill_brush.clone()); + shape.set_fill_brush_transform(*fill_brush_transform); + shape.set_stroke_style(stroke_style.clone()); + shape.set_stroke_brush(stroke_brush.clone()); + shape.set_stroke_brush_transform(*stroke_brush_transform); + (BoardElement::Shape(shape), ()) + } + + fn rebuild<'el>( + &self, + prev: &Self, + _: &mut Self::ViewState, + ctx: &mut ViewCtx, + mut element: Mut<'el, Self::Element>, + ) -> Mut<'el, Self::Element> { + { + let mut child = element.parent.child_mut(element.idx); + let mut child = child.downcast::(); + if self.shape != prev.shape { + child.set_shape(self.shape.clone()); + ctx.mark_changed(); + } + if self.transform != prev.transform { + child.set_transform(self.transform); + ctx.mark_changed(); + } + if self.fill_style != prev.fill_style { + child.set_fill_style(self.fill_style); + ctx.mark_changed(); + } + if self.fill_brush != prev.fill_brush { + child.set_fill_brush(self.fill_brush.clone()); + ctx.mark_changed(); + } + if self.fill_brush_transform != prev.fill_brush_transform { + child.set_fill_brush_transform(self.fill_brush_transform); + ctx.mark_changed(); + } + if self.stroke_style.width != prev.stroke_style.width + || self.stroke_style.join != prev.stroke_style.join + || self.stroke_style.miter_limit != prev.stroke_style.miter_limit + || self.stroke_style.start_cap != prev.stroke_style.start_cap + || self.stroke_style.end_cap != prev.stroke_style.end_cap + || self.stroke_style.dash_pattern != prev.stroke_style.dash_pattern + || self.stroke_style.dash_offset != prev.stroke_style.dash_offset + { + child.set_stroke_style(self.stroke_style.clone()); + ctx.mark_changed(); + } + if self.stroke_brush != prev.stroke_brush { + child.set_stroke_brush(self.stroke_brush.clone()); + ctx.mark_changed(); + } + if self.stroke_brush_transform != prev.stroke_brush_transform { + child.set_stroke_brush_transform(self.stroke_brush_transform); + ctx.mark_changed(); + } + } + + element + } + + fn teardown(&self, _: &mut Self::ViewState, _: &mut ViewCtx, _: Mut<'_, Self::Element>) {} + + fn message( + &self, + _: &mut Self::ViewState, + _: &[xilem_core::ViewId], + msg: DynMessage, + _: &mut State, + ) -> MessageResult { + MessageResult::Stale(msg) + } +} + +/// A widget-type-erased positioned child [`View`], can be used within a [`Board`] [`View`] +pub enum AnyBoardChild { + View(BoardViewItem>, State, Action>), + Shape(Box), +} + +impl BoardViewItem +where + State: 'static, + Action: 'static, + V: WidgetView, +{ + /// Turns this [`BoardItem`] into an [`AnyBoardChild`] + pub fn into_any_board(self) -> AnyBoardChild { + AnyBoardChild::View(board_item(Box::new(self.view), self.params)) + } +} + +#[doc(hidden)] // Implementation detail, public because of trait visibility rules +pub struct AnyBoardChildState { + /// Just the optional view state of the positioned item view + #[allow(clippy::type_complexity)] + inner: Option< + >, State, Action> as View< + State, + Action, + ViewCtx, + >>::ViewState, + >, + /// The generational id handling is essentially very similar to that of the `Option`, + /// where `None` would represent a Spacer, and `Some` a view + generation: u64, +} + +impl ViewMarker for AnyBoardChild {} +impl View for AnyBoardChild +where + State: 'static, + Action: 'static, +{ + type Element = BoardElement; + + type ViewState = AnyBoardChildState; + + fn build(&self, ctx: &mut ViewCtx) -> (Self::Element, Self::ViewState) { + let generation = 0; + let (element, view_state) = match self { + AnyBoardChild::View(view_item) => { + let (element, state) = + ctx.with_id(ViewId::new(generation), |ctx| view_item.build(ctx)); + (element, Some(state)) + } + AnyBoardChild::Shape(shape_item) => { + let (element, _) = ctx.with_id(ViewId::new(generation), |ctx| { + View::<(), (), ViewCtx>::build(shape_item, ctx) + }); + (element, None) + } + }; + ( + element, + AnyBoardChildState { + inner: view_state, + generation, + }, + ) + } + + fn rebuild<'el>( + &self, + prev: &Self, + view_state: &mut Self::ViewState, + ctx: &mut ViewCtx, + mut element: Mut<'el, Self::Element>, + ) -> Mut<'el, Self::Element> { + match (prev, self) { + (AnyBoardChild::View(prev), AnyBoardChild::View(this)) => ctx + .with_id(ViewId::new(view_state.generation), |ctx| { + this.rebuild(prev, view_state.inner.as_mut().unwrap(), ctx, element) + }), + (AnyBoardChild::Shape(prev), AnyBoardChild::Shape(this)) => { + View::<(), (), ViewCtx>::rebuild(this, prev, &mut (), ctx, element) + } + (AnyBoardChild::View(prev_view), AnyBoardChild::Shape(new_shape)) => { + // Run teardown with the old path + ctx.with_id(ViewId::new(view_state.generation), |ctx| { + prev_view.teardown( + view_state.inner.as_mut().unwrap(), + ctx, + BoardElementMut { + parent: element.parent.reborrow_mut(), + idx: element.idx, + }, + ); + }); + element.parent.remove_child(element.idx); + view_state.inner = None; + view_state.generation = view_state.generation.wrapping_add(1); + let (spacer_element, ()) = ctx.with_id(ViewId::new(view_state.generation), |ctx| { + View::<(), (), ViewCtx>::build(new_shape, ctx) + }); + + match spacer_element { + BoardElement::View(_, _) => unreachable!(), + BoardElement::Shape(shape) => { + element.parent.insert_shape_child(element.idx, shape); + } + }; + element + } + (AnyBoardChild::Shape(prev_shape), AnyBoardChild::View(new_view)) => { + // Run teardown with the old path + ctx.with_id(ViewId::new(view_state.generation), |ctx| { + View::<(), (), ViewCtx>::teardown( + prev_shape, + &mut (), + ctx, + BoardElementMut { + parent: element.parent.reborrow_mut(), + idx: element.idx, + }, + ); + }); + element.parent.remove_child(element.idx); + view_state.inner = None; + view_state.generation = view_state.generation.wrapping_add(1); + let (view_element, child_state) = ctx + .with_id(ViewId::new(view_state.generation), |ctx| { + new_view.build(ctx) + }); + view_state.inner = Some(child_state); + match view_element { + BoardElement::View(pod, params) => { + element + .parent + .insert_child_pod(element.idx, pod.inner, params); + } + BoardElement::Shape(_) => unreachable!(), + }; + element + } + } + } + + fn teardown( + &self, + view_state: &mut Self::ViewState, + ctx: &mut ViewCtx, + element: Mut<'_, Self::Element>, + ) { + match self { + AnyBoardChild::View(view_item) => { + view_item.teardown(view_state.inner.as_mut().unwrap(), ctx, element); + } + AnyBoardChild::Shape(shape_item) => { + View::<(), (), ViewCtx>::teardown(shape_item, &mut (), ctx, element); + } + } + } + + fn message( + &self, + view_state: &mut Self::ViewState, + id_path: &[xilem_core::ViewId], + message: DynMessage, + app_state: &mut State, + ) -> MessageResult { + let (start, rest) = id_path + .split_first() + .expect("Id path has elements for AnyBoardChild"); + if start.routing_id() != view_state.generation { + // The message was sent to a previous edition of the inner value + return MessageResult::Stale(message); + } + match self { + AnyBoardChild::View(view_item) => { + view_item.message(view_state.inner.as_mut().unwrap(), rest, message, app_state) + } + AnyBoardChild::Shape(shape_item) => { + shape_item.message(&mut (), rest, message, app_state) + } + } + } +} diff --git a/xilem/src/view/mod.rs b/xilem/src/view/mod.rs index b879de45f..b1c548fb8 100644 --- a/xilem/src/view/mod.rs +++ b/xilem/src/view/mod.rs @@ -9,6 +9,9 @@ pub use task::*; mod worker; pub use worker::*; +mod board; +pub use board::*; + mod button; pub use button::*; From 845f856d8f51e8e3aeeb1b9f069b39b93eb7412d Mon Sep 17 00:00:00 2001 From: Muhammad Ragib Hasin Date: Mon, 16 Sep 2024 17:19:08 +0600 Subject: [PATCH 02/10] Fix clippy --- masonry/src/widget/board.rs | 2 +- xilem/src/view/board.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/masonry/src/widget/board.rs b/masonry/src/widget/board.rs index ede66cb6e..0216816b2 100644 --- a/masonry/src/widget/board.rs +++ b/masonry/src/widget/board.rs @@ -284,7 +284,7 @@ impl<'a> WidgetMut<'a, Shape> { } } -// --- MARK: IMPL WIDGET--- +// --- MARK: IMPL WIDGET --- impl Widget for Board { fn on_pointer_event(&mut self, _ctx: &mut EventCtx, _event: &PointerEvent) {} diff --git a/xilem/src/view/board.rs b/xilem/src/view/board.rs index dd07f91bc..9f81abe1f 100644 --- a/xilem/src/view/board.rs +++ b/xilem/src/view/board.rs @@ -16,7 +16,7 @@ use xilem_core::{ use crate::{AnyWidgetView, Pod, ViewCtx, WidgetView}; -pub use masonry::widget::{Axis, BoardParams, CrossAxisAlignment, MainAxisAlignment}; +pub use masonry::widget::BoardParams; pub fn board>( sequence: Seq, From 991f3e5a90e2ad6d927772d942b6939e5b075c47 Mon Sep 17 00:00:00 2001 From: Muhammad Ragib Hasin Date: Mon, 16 Sep 2024 17:54:04 +0600 Subject: [PATCH 03/10] Add test snapshots --- .../masonry__widget__board__tests__absolute_placement.png | 3 +++ .../masonry__widget__board__tests__kurbo_shape_circle.png | 3 +++ .../masonry__widget__board__tests__shape_placement.png | 3 +++ 3 files changed, 9 insertions(+) create mode 100644 masonry/src/widget/screenshots/masonry__widget__board__tests__absolute_placement.png create mode 100644 masonry/src/widget/screenshots/masonry__widget__board__tests__kurbo_shape_circle.png create mode 100644 masonry/src/widget/screenshots/masonry__widget__board__tests__shape_placement.png diff --git a/masonry/src/widget/screenshots/masonry__widget__board__tests__absolute_placement.png b/masonry/src/widget/screenshots/masonry__widget__board__tests__absolute_placement.png new file mode 100644 index 000000000..37313b233 --- /dev/null +++ b/masonry/src/widget/screenshots/masonry__widget__board__tests__absolute_placement.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:1816caeab124dbfef4d81ec1e261788e968b5613a4c6c4bf937622b464aaac06 +size 7127 diff --git a/masonry/src/widget/screenshots/masonry__widget__board__tests__kurbo_shape_circle.png b/masonry/src/widget/screenshots/masonry__widget__board__tests__kurbo_shape_circle.png new file mode 100644 index 000000000..1bfb1bf90 --- /dev/null +++ b/masonry/src/widget/screenshots/masonry__widget__board__tests__kurbo_shape_circle.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e164f191260a765fa72ed3366e246b447a48a696ca45333dbe639922926bbd84 +size 6807 diff --git a/masonry/src/widget/screenshots/masonry__widget__board__tests__shape_placement.png b/masonry/src/widget/screenshots/masonry__widget__board__tests__shape_placement.png new file mode 100644 index 000000000..2ffbf9895 --- /dev/null +++ b/masonry/src/widget/screenshots/masonry__widget__board__tests__shape_placement.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d52e9a782b51e4c3d845e8a5b1d3b9e4eeb478c4e95213cfd02de5545b41401e +size 8085 From b32e0f4d888f1978749e0cdb0ef401be80da8f64 Mon Sep 17 00:00:00 2001 From: Muhammad Ragib Hasin Date: Tue, 17 Sep 2024 23:35:03 +0600 Subject: [PATCH 04/10] Rewrite using the idea from @Philipp-M --- masonry/src/widget/board.rs | 233 +++++----- masonry/src/widget/mod.rs | 2 +- ...dget__board__tests__kurbo_shape_circle.png | 4 +- masonry/src/widget/widget_pod.rs | 27 ++ xilem/Cargo.toml | 2 +- xilem/examples/board.rs | 8 +- xilem/src/view/board.rs | 414 +++++------------- xilem/src/view/board/kurbo_shape.rs | 103 +++++ xilem/src/view/board/style_modifier.rs | 342 +++++++++++++++ 9 files changed, 695 insertions(+), 440 deletions(-) create mode 100644 xilem/src/view/board/kurbo_shape.rs create mode 100644 xilem/src/view/board/style_modifier.rs diff --git a/masonry/src/widget/board.rs b/masonry/src/widget/board.rs index 0216816b2..f0f8ad055 100644 --- a/masonry/src/widget/board.rs +++ b/masonry/src/widget/board.rs @@ -8,7 +8,7 @@ use smallvec::SmallVec; use tracing::{trace_span, warn, Span}; use vello::kurbo::{ self, Affine, Arc, BezPath, Circle, CircleSegment, CubicBez, Ellipse, Line, PathEl, PathSeg, - QuadBez, RoundedRect, Shape as _, Stroke, Vec2, + QuadBez, RoundedRect, Shape as _, Stroke, }; use vello::peniko::{Brush, Fill}; use vello::Scene; @@ -22,8 +22,6 @@ use crate::{ /// A container with absolute positioning layout. pub struct Board { children: Vec, - origin: Point, - scale: Vec2, } /// Parameters for an item in a [`Board`] container. @@ -33,10 +31,10 @@ pub struct BoardParams { size: Size, } -pub struct Shape { - shape: KurboShape, +pub struct KurboShape { + shape: ConcreteShape, transform: Affine, - fill_style: Fill, + fill_mode: Fill, fill_brush: Brush, fill_brush_transform: Option, stroke_style: Stroke, @@ -45,7 +43,7 @@ pub struct Shape { } #[derive(Debug, Clone, PartialEq)] -pub enum KurboShape { +pub enum ConcreteShape { PathSeg(PathSeg), Arc(Arc), BezPath(BezPath), @@ -65,8 +63,6 @@ impl Board { pub fn new() -> Self { Board { children: Vec::new(), - origin: Point::ZERO, - scale: Vec2::new(1., 1.), } } @@ -77,14 +73,19 @@ impl Board { params: impl Into, ) -> Self { // TODO - dedup? - self.children.push(Child::Widget(widget, params.into())); + self.children.push(Child { + widget, + params: params.into(), + }); self } /// Builder-style method to add a Kurbo shape to the container. - pub fn with_shape_child(mut self, shape: Shape) -> Self { - self.children - .push(Child::Shape(WidgetPod::new(Box::new(shape)))); + pub fn with_shape_pod(mut self, shape: WidgetPod) -> Self { + self.children.push(Child { + params: shape.as_ref().unwrap().shape.bounding_box().into(), + widget: shape.boxed(), + }); self } @@ -104,12 +105,12 @@ impl Default for Board { } // --- MARK: IMPL SHAPE --- -impl Shape { - pub fn new(shape: impl Into) -> Self { - Shape { +impl KurboShape { + pub fn new(shape: impl Into) -> Self { + KurboShape { shape: shape.into(), transform: Default::default(), - fill_style: Fill::NonZero, + fill_mode: Fill::NonZero, fill_brush: Default::default(), fill_brush_transform: Default::default(), stroke_style: Default::default(), @@ -118,7 +119,7 @@ impl Shape { } } - pub fn shape(&self) -> &KurboShape { + pub fn shape(&self) -> &ConcreteShape { &self.shape } @@ -126,8 +127,8 @@ impl Shape { self.transform = transform; } - pub fn set_fill_style(&mut self, fill_style: Fill) { - self.fill_style = fill_style; + pub fn set_fill_mode(&mut self, fill_mode: Fill) { + self.fill_mode = fill_mode; } pub fn set_fill_brush(&mut self, fill_brush: Brush) { @@ -153,31 +154,21 @@ impl Shape { // --- MARK: WIDGETMUT--- impl<'a> WidgetMut<'a, Board> { - /// Set the origin. - pub fn set_origin(&mut self, origin: Point) { - self.widget.origin = origin; - self.ctx.request_layout(); - } - - /// Set the scale. - pub fn set_scale(&mut self, scale: Vec2) { - self.widget.scale = scale; - self.ctx.request_layout(); - } - /// Add a positioned child widget. pub fn add_child(&mut self, child: impl Widget, params: impl Into) { - let child = Child::Widget(WidgetPod::new(Box::new(child)), params.into()); - - self.widget.children.push(child); + self.widget.children.push(Child { + widget: WidgetPod::new(Box::new(child)), + params: params.into(), + }); self.ctx.children_changed(); } /// Add a Kurbo shape. - pub fn add_shape_child(&mut self, shape: Shape) { - self.widget - .children - .push(Child::Shape(WidgetPod::new(Box::new(shape)))); + pub fn add_shape_child(&mut self, shape: Box) { + self.widget.children.push(Child { + params: shape.shape.bounding_box().into(), + widget: WidgetPod::new(shape), + }); self.ctx.children_changed(); } @@ -191,27 +182,40 @@ impl<'a> WidgetMut<'a, Board> { child: WidgetPod>, params: impl Into, ) { - let child = Child::Widget(child, params.into()); + let child = Child { + widget: child, + params: params.into(), + }; self.widget.children.insert(idx, child); self.ctx.children_changed(); } - pub fn insert_shape_child(&mut self, idx: usize, shape: Shape) { - self.widget - .children - .insert(idx, Child::Shape(WidgetPod::new(Box::new(shape)))); + pub fn insert_shape_pod(&mut self, idx: usize, shape: WidgetPod) { + let child = Child { + params: shape.as_ref().unwrap().shape.bounding_box().into(), + widget: shape.boxed(), + }; + self.widget.children.insert(idx, child); self.ctx.children_changed(); } pub fn remove_child(&mut self, idx: usize) { - let (Child::Widget(widget, _) | Child::Shape(widget)) = self.widget.children.remove(idx); + let Child { widget, .. } = self.widget.children.remove(idx); self.ctx.remove_child(widget); self.ctx.request_layout(); } + // FIXME: unsure about correctness of keeping child params unchanged + pub fn replace_child(&mut self, idx: usize, new_widget: WidgetPod>) { + let Child { widget, .. } = &mut self.widget.children[idx]; + let old_widget = std::mem::replace(widget, new_widget); + self.ctx.remove_child(old_widget); + self.ctx.request_layout(); + } + // FIXME - Remove Box pub fn child_mut(&mut self, idx: usize) -> WidgetMut<'_, Box> { - let (Child::Widget(widget, _) | Child::Shape(widget)) = &mut self.widget.children[idx]; + let Child { widget, .. } = &mut self.widget.children[idx]; self.ctx.get_mut(widget) } @@ -221,10 +225,9 @@ impl<'a> WidgetMut<'a, Board> { /// /// Panics if the element at `idx` is not a widget. pub fn update_child_board_params(&mut self, idx: usize, new_params: impl Into) { - if let Child::Widget(_, params) = &mut self.widget.children[idx] { - *params = new_params.into(); - self.ctx.children_changed(); - } + // FIXME: should check if the child is a graphics or rugular widget + self.widget.children[idx].params = new_params.into(); + self.ctx.children_changed(); } pub fn clear(&mut self) { @@ -232,16 +235,48 @@ impl<'a> WidgetMut<'a, Board> { self.ctx.request_layout(); for child in self.widget.children.drain(..) { - if let Child::Widget(widget, _) = child { - self.ctx.remove_child(widget); - } + self.ctx.remove_child(child.widget); } } } } -impl<'a> WidgetMut<'a, Shape> { - pub fn set_shape(&mut self, shape: KurboShape) { +impl<'a> WidgetMut<'a, KurboShape> { + pub fn update_from(&mut self, shape: &KurboShape) { + if self.widget.shape != shape.shape { + self.set_shape(shape.shape.clone()); + } + if self.widget.transform != shape.transform { + self.set_transform(shape.transform); + } + if self.widget.fill_mode != shape.fill_mode { + self.set_fill_mode(shape.fill_mode); + } + if self.widget.fill_brush != shape.fill_brush { + self.set_fill_brush(shape.fill_brush.clone()); + } + if self.widget.fill_brush_transform != shape.fill_brush_transform { + self.set_fill_brush_transform(shape.fill_brush_transform); + } + if self.widget.stroke_style.width != shape.stroke_style.width + || self.widget.stroke_style.join != shape.stroke_style.join + || self.widget.stroke_style.miter_limit != shape.stroke_style.miter_limit + || self.widget.stroke_style.start_cap != shape.stroke_style.start_cap + || self.widget.stroke_style.end_cap != shape.stroke_style.end_cap + || self.widget.stroke_style.dash_pattern != shape.stroke_style.dash_pattern + || self.widget.stroke_style.dash_offset != shape.stroke_style.dash_offset + { + self.set_stroke_style(shape.stroke_style.clone()); + } + if self.widget.stroke_brush != shape.stroke_brush { + self.set_stroke_brush(shape.stroke_brush.clone()); + } + if self.widget.stroke_brush_transform != shape.stroke_brush_transform { + self.set_stroke_brush_transform(shape.stroke_brush_transform); + } + } + + pub fn set_shape(&mut self, shape: ConcreteShape) { self.widget.shape = shape; self.ctx.request_layout(); self.ctx.request_paint(); @@ -253,8 +288,8 @@ impl<'a> WidgetMut<'a, Shape> { self.ctx.request_paint(); } - pub fn set_fill_style(&mut self, fill_style: Fill) { - self.widget.fill_style = fill_style; + pub fn set_fill_mode(&mut self, fill_mode: Fill) { + self.widget.fill_mode = fill_mode; self.ctx.request_paint(); } @@ -295,44 +330,17 @@ impl Widget for Board { fn on_status_change(&mut self, _ctx: &mut LifeCycleCtx, _event: &StatusChange) {} fn lifecycle(&mut self, ctx: &mut LifeCycleCtx, event: &LifeCycle) { - for child in self.children.iter_mut().filter_map(|x| x.widget_mut()) { - child.lifecycle(ctx, event); + for child in &mut self.children { + child.widget.lifecycle(ctx, event); } } fn layout(&mut self, ctx: &mut LayoutCtx, bc: &BoxConstraints) -> Size { bc.debug_check("Board"); - for child in &mut self.children { - match child { - Child::Widget(widget, params) => { - if ctx.child_needs_layout(widget) { - ctx.run_layout(widget, &BoxConstraints::tight(params.size)); - } else { - ctx.skip_layout(widget); - }; - ctx.place_child(widget, self.origin + params.origin.to_vec2()); - } - Child::Shape(shape) => { - let raw_widget = ctx.get_raw_ref(shape); - let params = BoardParams::from( - raw_widget - .widget() - .as_ref() - .as_dyn_any() - .downcast_ref::() - .unwrap() - .shape() - .bounding_box(), - ); - if ctx.child_needs_layout(shape) { - ctx.run_layout(shape, &BoxConstraints::tight(params.size)); - } else { - ctx.skip_layout(shape); - }; - ctx.place_child(shape, self.origin); - } - } + for Child { widget, params } in &mut self.children { + ctx.run_layout(widget, &BoxConstraints::tight(params.size)); + ctx.place_child(widget, params.origin); } bc.max() @@ -349,8 +357,7 @@ impl Widget for Board { fn children_ids(&self) -> SmallVec<[WidgetId; 16]> { self.children .iter() - .filter_map(|child| child.widget()) - .map(|widget_pod| widget_pod.id()) + .map(|child| child.widget.id()) .collect() } @@ -359,7 +366,7 @@ impl Widget for Board { } } -impl Widget for Shape { +impl Widget for KurboShape { fn on_pointer_event(&mut self, _ctx: &mut EventCtx, _event: &PointerEvent) {} fn on_text_event(&mut self, _ctx: &mut EventCtx, _event: &TextEvent) {} fn on_access_event(&mut self, _ctx: &mut EventCtx, _event: &AccessEvent) {} @@ -375,16 +382,19 @@ impl Widget for Shape { } fn paint(&mut self, _ctx: &mut PaintCtx, scene: &mut Scene) { + let transform = self + .transform + .then_translate(-self.shape.bounding_box().origin().to_vec2()); scene.fill( - self.fill_style, - self.transform, + self.fill_mode, + transform, &self.fill_brush, self.fill_brush_transform, &self.shape, ); scene.stroke( &self.stroke_style, - self.transform, + transform, &self.stroke_brush, self.stroke_brush_transform, &self.shape, @@ -426,22 +436,9 @@ impl From for BoardParams { } } -enum Child { - Widget(WidgetPod>, BoardParams), - Shape(WidgetPod>), -} - -impl Child { - fn widget_mut(&mut self) -> Option<&mut WidgetPod>> { - match self { - Child::Widget(widget, _) | Child::Shape(widget) => Some(widget), - } - } - fn widget(&self) -> Option<&WidgetPod>> { - match self { - Child::Widget(widget, _) | Child::Shape(widget) => Some(widget), - } - } +struct Child { + widget: WidgetPod>, + params: BoardParams, } macro_rules! for_all_variants { @@ -462,7 +459,7 @@ macro_rules! for_all_variants { }; } -impl kurbo::Shape for KurboShape { +impl kurbo::Shape for ConcreteShape { type PathElementsIter<'iter> = PathElementsIter<'iter>; fn path_elements(&self, tolerance: f64) -> Self::PathElementsIter<'_> { @@ -532,9 +529,9 @@ impl kurbo::Shape for KurboShape { macro_rules! impl_from_shape { ($t:ident) => { - impl From for KurboShape { + impl From for ConcreteShape { fn from(value: kurbo::$t) -> Self { - KurboShape::$t(value) + ConcreteShape::$t(value) } } }; @@ -586,7 +583,7 @@ mod tests { #[test] fn kurbo_shape_circle() { - let mut widget = Shape::new(Circle::new((50., 50.), 30.)); + let mut widget = KurboShape::new(Circle::new((50., 50.), 30.)); widget.set_fill_brush(Brush::Solid(vello::peniko::Color::CHARTREUSE)); widget.set_stroke_style(Stroke::new(2.).with_dashes(0., [2., 1.])); widget.set_stroke_brush(Brush::Solid(vello::peniko::Color::PALE_VIOLET_RED)); @@ -615,7 +612,7 @@ mod tests { #[test] fn board_shape_placement_snapshots() { - let mut shape = Shape::new(Circle::new((70., 50.), 30.)); + let mut shape = KurboShape::new(Circle::new((70., 50.), 30.)); shape.set_fill_brush(Brush::Solid(vello::peniko::Color::NAVY)); shape.set_stroke_style(Stroke::new(2.).with_dashes(0., [2., 1.])); shape.set_stroke_brush(Brush::Solid(vello::peniko::Color::PALE_VIOLET_RED)); @@ -624,10 +621,12 @@ mod tests { WidgetPod::new(Box::new(Button::new("hello"))), Rect::new(10., 10., 60., 40.), ) - .with_shape_child(shape); + .with_shape_pod(WidgetPod::new(shape)); let mut harness = TestHarness::create(widget); assert_render_snapshot!(harness, "shape_placement"); } + + // TODO: add test for KurboShape in Flex } diff --git a/masonry/src/widget/mod.rs b/masonry/src/widget/mod.rs index 2499868b1..c6d687db5 100644 --- a/masonry/src/widget/mod.rs +++ b/masonry/src/widget/mod.rs @@ -35,7 +35,7 @@ mod widget_arena; pub use self::image::Image; pub use align::Align; -pub use board::{Board, BoardParams, KurboShape, Shape}; +pub use board::{Board, BoardParams, ConcreteShape, KurboShape}; pub use button::Button; pub use checkbox::Checkbox; pub use flex::{Axis, CrossAxisAlignment, Flex, FlexParams, MainAxisAlignment}; diff --git a/masonry/src/widget/screenshots/masonry__widget__board__tests__kurbo_shape_circle.png b/masonry/src/widget/screenshots/masonry__widget__board__tests__kurbo_shape_circle.png index 1bfb1bf90..72057cb16 100644 --- a/masonry/src/widget/screenshots/masonry__widget__board__tests__kurbo_shape_circle.png +++ b/masonry/src/widget/screenshots/masonry__widget__board__tests__kurbo_shape_circle.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:e164f191260a765fa72ed3366e246b447a48a696ca45333dbe639922926bbd84 -size 6807 +oid sha256:fd30d3d36b9ad1de1429d7210842f7ee71fd671039b6e671a234c2aba504f2d1 +size 6677 diff --git a/masonry/src/widget/widget_pod.rs b/masonry/src/widget/widget_pod.rs index 24ad7dfa7..46dd9bad5 100644 --- a/masonry/src/widget/widget_pod.rs +++ b/masonry/src/widget/widget_pod.rs @@ -57,6 +57,33 @@ impl WidgetPod { pub fn id(&self) -> WidgetId { self.id } + + /// Take the inner widget, if it has not been inserted yet. + pub fn inner(self) -> Option { + if let WidgetPodInner::Created(w) = self.inner { + Some(w) + } else { + None + } + } + + /// Get access to the inner widget, if it has not been inserted yet. + pub fn as_ref(&self) -> Option<&W> { + if let WidgetPodInner::Created(w) = &self.inner { + Some(w) + } else { + None + } + } + + /// Get access to the inner widget, if it has not been inserted yet. + pub fn as_mut(&mut self) -> Option<&mut W> { + if let WidgetPodInner::Created(w) = &mut self.inner { + Some(w) + } else { + None + } + } } impl WidgetPod { diff --git a/xilem/Cargo.toml b/xilem/Cargo.toml index 28a599c76..df6ec5996 100644 --- a/xilem/Cargo.toml +++ b/xilem/Cargo.toml @@ -76,7 +76,7 @@ crate-type = ["cdylib"] workspace = true [dependencies] -xilem_core.workspace = true +xilem_core = { workspace = true, features = ["kurbo"] } masonry.workspace = true winit.workspace = true tracing.workspace = true diff --git a/xilem/examples/board.rs b/xilem/examples/board.rs index 5684797e5..e7b321c2b 100644 --- a/xilem/examples/board.rs +++ b/xilem/examples/board.rs @@ -7,7 +7,7 @@ use masonry::{ }; use winit::error::EventLoopError; use xilem::view::{ - board, button, flex, label, Axis, BoardExt, BoardParams, FlexExt as _, FlexSpacer, ShapeExt, + board, button, flex, label, Axis, BoardExt, BoardParams, FlexExt as _, FlexSpacer, GraphicsExt, }; use xilem::{Color, EventLoop, WidgetView, Xilem}; @@ -50,10 +50,8 @@ impl AppState { .into_any_board() } else { vello::kurbo::Circle::new((origin + 15., origin + 15.), 15.) - .view() - .fill_brush(Color::NAVY) - .stroke_brush(Color::PAPAYA_WHIP) - .stroke_style(vello::kurbo::Stroke::new(2.)) + .fill(Color::NAVY) + .stroke(Color::PAPAYA_WHIP, vello::kurbo::Stroke::new(2.)) .into_any_board() } }) diff --git a/xilem/src/view/board.rs b/xilem/src/view/board.rs index 9f81abe1f..51ed354d3 100644 --- a/xilem/src/view/board.rs +++ b/xilem/src/view/board.rs @@ -4,11 +4,9 @@ use std::marker::PhantomData; use masonry::{ - widget::{self, KurboShape, Shape, WidgetMut}, + widget::{self, KurboShape, WidgetMut}, Widget, }; -use vello::kurbo::{Affine, Point, Stroke, Vec2}; -use vello::peniko::{Brush, Fill}; use xilem_core::{ AppendVec, DynMessage, ElementSplice, MessageResult, Mut, SuperElement, View, ViewElement, ViewId, ViewMarker, ViewPathTracker, ViewSequence, @@ -18,21 +16,23 @@ use crate::{AnyWidgetView, Pod, ViewCtx, WidgetView}; pub use masonry::widget::BoardParams; +mod kurbo_shape; +mod style_modifier; + +pub use kurbo_shape::{AnyGraphicsView, GraphicsView}; +pub use style_modifier::{fill, stroke, transform, Fill, GraphicsExt, Stroke, Transform}; + pub fn board>( sequence: Seq, ) -> Board { Board { sequence, - origin: Point::ZERO, - scale: Vec2::new(1., 1.), phantom: PhantomData, } } pub struct Board { sequence: Seq, - origin: Point, - scale: Vec2, phantom: PhantomData (State, Action)>, } @@ -54,7 +54,7 @@ where for child in elements.into_inner() { widget = match child { BoardElement::View(pod, params) => widget.with_child_pod(pod.inner, params), - BoardElement::Shape(shape) => widget.with_shape_child(shape), + BoardElement::Graphics(pod) => widget.with_shape_pod(pod.inner), } } (Pod::new(widget), seq_state) @@ -65,16 +65,8 @@ where prev: &Self, view_state: &mut Self::ViewState, ctx: &mut ViewCtx, - mut element: Mut<'el, Self::Element>, + element: Mut<'el, Self::Element>, ) -> Mut<'el, Self::Element> { - if prev.origin != self.origin { - element.set_origin(self.origin); - ctx.mark_changed(); - } - if prev.scale != self.scale { - element.set_scale(self.scale); - ctx.mark_changed(); - } // TODO: Re-use scratch space? let mut splice = BoardSplice::new(element); self.sequence @@ -106,31 +98,10 @@ where } } -#[allow(clippy::large_enum_variant)] +#[expect(clippy::large_enum_variant)] pub enum BoardElement { View(Pod>, BoardParams), - Shape(Shape), -} - -pub struct BoardElementMut<'w> { - parent: WidgetMut<'w, widget::Board>, - idx: usize, -} - -struct BoardSplice<'w> { - idx: usize, - element: WidgetMut<'w, widget::Board>, - scratch: AppendVec, -} - -impl<'w> BoardSplice<'w> { - fn new(element: WidgetMut<'w, widget::Board>) -> Self { - Self { - idx: 0, - element, - scratch: AppendVec::default(), - } - } + Graphics(Pod), } impl ViewElement for BoardElement { @@ -158,30 +129,26 @@ impl SuperElement for BoardElement { } } -// impl SuperElement> for BoardElement { -// fn upcast(child: Pod) -> Self { -// BoardElement { -// element: child.inner.boxed().into(), -// params: BoardParams::default(), -// } -// } - -// fn with_downcast_val( -// mut this: Mut<'_, Self>, -// f: impl FnOnce(Mut<'_, Pod>) -> R, -// ) -> (Mut<'_, Self>, R) { -// let ret = { -// let mut child = this -// .parent -// .child_mut(this.idx) -// .expect("This is supposed to be a widget"); -// let downcast = child.downcast(); -// f(downcast) -// }; - -// (this, ret) -// } -// } +pub struct BoardElementMut<'w> { + parent: WidgetMut<'w, widget::Board>, + idx: usize, +} + +struct BoardSplice<'w> { + idx: usize, + element: WidgetMut<'w, widget::Board>, + scratch: AppendVec, +} + +impl<'w> BoardSplice<'w> { + fn new(element: WidgetMut<'w, widget::Board>) -> Self { + Self { + idx: 0, + element, + scratch: AppendVec::default(), + } + } +} impl ElementSplice for BoardSplice<'_> { fn insert(&mut self, element: BoardElement) { @@ -189,7 +156,9 @@ impl ElementSplice for BoardSplice<'_> { BoardElement::View(pod, params) => { self.element.insert_child_pod(self.idx, pod.inner, params); } - BoardElement::Shape(shape) => self.element.insert_shape_child(self.idx, shape), + BoardElement::Graphics(pod) => { + self.element.insert_shape_pod(self.idx, pod.inner); + } } self.idx += 1; } @@ -201,7 +170,9 @@ impl ElementSplice for BoardSplice<'_> { BoardElement::View(pod, params) => { self.element.insert_child_pod(self.idx, pod.inner, params); } - BoardElement::Shape(shape) => self.element.insert_shape_child(self.idx, shape), + BoardElement::Graphics(pod) => { + self.element.insert_shape_pod(self.idx, pod.inner); + } } self.idx += 1; } @@ -251,55 +222,55 @@ impl BoardSequence for Seq where /// or being able to use it interchangeably with a shape. pub trait BoardExt: WidgetView { /// Makes this view absolutely positioned in a `Board`. - fn positioned(self, params: impl Into) -> BoardViewItem + fn positioned(self, params: impl Into) -> PositionedView where State: 'static, Action: 'static, Self: Sized, { - board_item(self, params) + positioned(self, params) } } impl> BoardExt for V {} /// A `WidgetView` that can be used within a [`Board`] [`View`] -pub struct BoardViewItem { +pub struct PositionedView { view: V, params: BoardParams, phantom: PhantomData (State, Action)>, } /// Makes this view absolutely positioned in a `Board`. -pub fn board_item( +pub fn positioned( view: V, params: impl Into, -) -> BoardViewItem +) -> PositionedView where State: 'static, Action: 'static, V: WidgetView, { - BoardViewItem { - params: params.into(), + PositionedView { view, + params: params.into(), phantom: PhantomData, } } -impl From> for AnyBoardChild +impl From> for AnyBoardChild where State: 'static, Action: 'static, V: WidgetView, { - fn from(value: BoardViewItem) -> Self { - AnyBoardChild::View(board_item(value.view.boxed(), value.params)) + fn from(value: PositionedView) -> Self { + AnyBoardChild::View(positioned(value.view.boxed(), value.params)) } } -impl ViewMarker for BoardViewItem {} -impl View for BoardViewItem +impl ViewMarker for PositionedView {} +impl View for PositionedView where State: 'static, Action: 'static, @@ -358,186 +329,13 @@ where } } -pub struct ShapeItem { - shape: KurboShape, - transform: Affine, - fill_style: Fill, - fill_brush: Brush, - fill_brush_transform: Option, - stroke_style: Stroke, - stroke_brush: Brush, - stroke_brush_transform: Option, -} - -impl ShapeItem { - pub fn transform(mut self, transform: Affine) -> Self { - self.transform = transform; - self - } - pub fn fill_style(mut self, fill_style: Fill) -> Self { - self.fill_style = fill_style; - self - } - pub fn fill_brush(mut self, fill_brush: impl Into) -> Self { - self.fill_brush = fill_brush.into(); - self - } - pub fn fill_brush_transform(mut self, fill_brush_transform: impl Into>) -> Self { - self.fill_brush_transform = fill_brush_transform.into(); - self - } - pub fn stroke_style(mut self, stroke_style: Stroke) -> Self { - self.stroke_style = stroke_style; - self - } - pub fn stroke_brush(mut self, stroke_brush: impl Into) -> Self { - self.stroke_brush = stroke_brush.into(); - self - } - pub fn stroke_brush_transform( - mut self, - stroke_brush_transform: impl Into>, - ) -> Self { - self.stroke_brush_transform = stroke_brush_transform.into(); - self - } - - pub fn into_any_board(self) -> AnyBoardChild { - AnyBoardChild::Shape(Box::new(self)) - } -} - -pub trait ShapeExt { - fn view(self) -> ShapeItem; -} - -impl ShapeExt for T -where - KurboShape: From, -{ - fn view(self) -> ShapeItem { - ShapeItem { - shape: self.into(), - transform: Default::default(), - fill_style: Fill::NonZero, - fill_brush: Default::default(), - fill_brush_transform: Default::default(), - stroke_style: Default::default(), - stroke_brush: Default::default(), - stroke_brush_transform: Default::default(), - } - } -} - -impl From for AnyBoardChild { - fn from(shape: ShapeItem) -> Self { - AnyBoardChild::Shape(Box::new(shape)) - } -} - -impl ViewMarker for ShapeItem {} -// This impl doesn't require a view id, as it neither receives, nor sends any messages -// If this should ever change, it's necessary to adjust the `AnyBoardChild` `View` impl -impl View for ShapeItem { - type Element = BoardElement; - type ViewState = (); - - fn build(&self, _ctx: &mut ViewCtx) -> (Self::Element, Self::ViewState) { - let Self { - shape, - transform, - fill_style, - fill_brush, - fill_brush_transform, - stroke_style, - stroke_brush, - stroke_brush_transform, - } = self; - let mut shape = Shape::new(shape.clone()); - shape.set_transform(*transform); - shape.set_fill_style(*fill_style); - shape.set_fill_brush(fill_brush.clone()); - shape.set_fill_brush_transform(*fill_brush_transform); - shape.set_stroke_style(stroke_style.clone()); - shape.set_stroke_brush(stroke_brush.clone()); - shape.set_stroke_brush_transform(*stroke_brush_transform); - (BoardElement::Shape(shape), ()) - } - - fn rebuild<'el>( - &self, - prev: &Self, - _: &mut Self::ViewState, - ctx: &mut ViewCtx, - mut element: Mut<'el, Self::Element>, - ) -> Mut<'el, Self::Element> { - { - let mut child = element.parent.child_mut(element.idx); - let mut child = child.downcast::(); - if self.shape != prev.shape { - child.set_shape(self.shape.clone()); - ctx.mark_changed(); - } - if self.transform != prev.transform { - child.set_transform(self.transform); - ctx.mark_changed(); - } - if self.fill_style != prev.fill_style { - child.set_fill_style(self.fill_style); - ctx.mark_changed(); - } - if self.fill_brush != prev.fill_brush { - child.set_fill_brush(self.fill_brush.clone()); - ctx.mark_changed(); - } - if self.fill_brush_transform != prev.fill_brush_transform { - child.set_fill_brush_transform(self.fill_brush_transform); - ctx.mark_changed(); - } - if self.stroke_style.width != prev.stroke_style.width - || self.stroke_style.join != prev.stroke_style.join - || self.stroke_style.miter_limit != prev.stroke_style.miter_limit - || self.stroke_style.start_cap != prev.stroke_style.start_cap - || self.stroke_style.end_cap != prev.stroke_style.end_cap - || self.stroke_style.dash_pattern != prev.stroke_style.dash_pattern - || self.stroke_style.dash_offset != prev.stroke_style.dash_offset - { - child.set_stroke_style(self.stroke_style.clone()); - ctx.mark_changed(); - } - if self.stroke_brush != prev.stroke_brush { - child.set_stroke_brush(self.stroke_brush.clone()); - ctx.mark_changed(); - } - if self.stroke_brush_transform != prev.stroke_brush_transform { - child.set_stroke_brush_transform(self.stroke_brush_transform); - ctx.mark_changed(); - } - } - - element - } - - fn teardown(&self, _: &mut Self::ViewState, _: &mut ViewCtx, _: Mut<'_, Self::Element>) {} - - fn message( - &self, - _: &mut Self::ViewState, - _: &[xilem_core::ViewId], - msg: DynMessage, - _: &mut State, - ) -> MessageResult { - MessageResult::Stale(msg) - } -} - /// A widget-type-erased positioned child [`View`], can be used within a [`Board`] [`View`] pub enum AnyBoardChild { - View(BoardViewItem>, State, Action>), - Shape(Box), + View(PositionedView>, State, Action>), + Graphics(Box>), } -impl BoardViewItem +impl PositionedView where State: 'static, Action: 'static, @@ -545,21 +343,19 @@ where { /// Turns this [`BoardItem`] into an [`AnyBoardChild`] pub fn into_any_board(self) -> AnyBoardChild { - AnyBoardChild::View(board_item(Box::new(self.view), self.params)) + AnyBoardChild::View(positioned(Box::new(self.view), self.params)) } } #[doc(hidden)] // Implementation detail, public because of trait visibility rules pub struct AnyBoardChildState { - /// Just the optional view state of the positioned item view #[allow(clippy::type_complexity)] - inner: Option< - >, State, Action> as View< - State, - Action, - ViewCtx, - >>::ViewState, - >, + inner: >, State, Action> as View< + State, + Action, + ViewCtx, + >>::ViewState, + /// The generational id handling is essentially very similar to that of the `Option`, /// where `None` would represent a Spacer, and `Some` a view generation: u64, @@ -579,15 +375,12 @@ where let generation = 0; let (element, view_state) = match self { AnyBoardChild::View(view_item) => { - let (element, state) = - ctx.with_id(ViewId::new(generation), |ctx| view_item.build(ctx)); - (element, Some(state)) + ctx.with_id(ViewId::new(generation), |ctx| view_item.build(ctx)) } - AnyBoardChild::Shape(shape_item) => { - let (element, _) = ctx.with_id(ViewId::new(generation), |ctx| { - View::<(), (), ViewCtx>::build(shape_item, ctx) - }); - (element, None) + AnyBoardChild::Graphics(shape_item) => { + let (element, state) = + ctx.with_id(ViewId::new(generation), |ctx| shape_item.build(ctx)); + (BoardElement::Graphics(element), state) } }; ( @@ -609,16 +402,22 @@ where match (prev, self) { (AnyBoardChild::View(prev), AnyBoardChild::View(this)) => ctx .with_id(ViewId::new(view_state.generation), |ctx| { - this.rebuild(prev, view_state.inner.as_mut().unwrap(), ctx, element) + this.rebuild(prev, &mut view_state.inner, ctx, element) }), - (AnyBoardChild::Shape(prev), AnyBoardChild::Shape(this)) => { - View::<(), (), ViewCtx>::rebuild(this, prev, &mut (), ctx, element) + (AnyBoardChild::Graphics(prev), AnyBoardChild::Graphics(this)) => { + { + let mut child = element.parent.child_mut(element.idx); + ctx.with_id(ViewId::new(view_state.generation), |ctx| { + this.rebuild(prev, &mut view_state.inner, ctx, child.downcast()) + }); + } + element } - (AnyBoardChild::View(prev_view), AnyBoardChild::Shape(new_shape)) => { + (AnyBoardChild::View(prev_view), AnyBoardChild::Graphics(new_shape)) => { // Run teardown with the old path ctx.with_id(ViewId::new(view_state.generation), |ctx| { prev_view.teardown( - view_state.inner.as_mut().unwrap(), + &mut view_state.inner, ctx, BoardElementMut { parent: element.parent.reborrow_mut(), @@ -627,50 +426,36 @@ where ); }); element.parent.remove_child(element.idx); - view_state.inner = None; view_state.generation = view_state.generation.wrapping_add(1); - let (spacer_element, ()) = ctx.with_id(ViewId::new(view_state.generation), |ctx| { - View::<(), (), ViewCtx>::build(new_shape, ctx) + let (child, child_state) = ctx.with_id(ViewId::new(view_state.generation), |ctx| { + new_shape.build(ctx) }); - - match spacer_element { - BoardElement::View(_, _) => unreachable!(), - BoardElement::Shape(shape) => { - element.parent.insert_shape_child(element.idx, shape); - } - }; + view_state.inner = child_state; + element.parent.insert_shape_pod(element.idx, child.inner); element } - (AnyBoardChild::Shape(prev_shape), AnyBoardChild::View(new_view)) => { + (AnyBoardChild::Graphics(prev_shape), AnyBoardChild::View(new_view)) => { // Run teardown with the old path - ctx.with_id(ViewId::new(view_state.generation), |ctx| { - View::<(), (), ViewCtx>::teardown( - prev_shape, - &mut (), - ctx, - BoardElementMut { - parent: element.parent.reborrow_mut(), - idx: element.idx, - }, - ); - }); + { + let mut child = element.parent.child_mut(element.idx); + ctx.with_id(ViewId::new(view_state.generation), |ctx| { + prev_shape.teardown(&mut view_state.inner, ctx, child.downcast()); + }); + } element.parent.remove_child(element.idx); - view_state.inner = None; view_state.generation = view_state.generation.wrapping_add(1); let (view_element, child_state) = ctx .with_id(ViewId::new(view_state.generation), |ctx| { new_view.build(ctx) }); - view_state.inner = Some(child_state); - match view_element { - BoardElement::View(pod, params) => { - element - .parent - .insert_child_pod(element.idx, pod.inner, params); - } - BoardElement::Shape(_) => unreachable!(), + view_state.inner = child_state; + let BoardElement::View(pod, params) = view_element else { + unreachable!() }; element + .parent + .insert_child_pod(element.idx, pod.inner, params); + element } } } @@ -679,14 +464,15 @@ where &self, view_state: &mut Self::ViewState, ctx: &mut ViewCtx, - element: Mut<'_, Self::Element>, + mut element: Mut<'_, Self::Element>, ) { match self { AnyBoardChild::View(view_item) => { - view_item.teardown(view_state.inner.as_mut().unwrap(), ctx, element); + view_item.teardown(&mut view_state.inner, ctx, element); } - AnyBoardChild::Shape(shape_item) => { - View::<(), (), ViewCtx>::teardown(shape_item, &mut (), ctx, element); + AnyBoardChild::Graphics(shape_item) => { + let mut child = element.parent.child_mut(element.idx); + shape_item.teardown(&mut view_state.inner, ctx, child.downcast()); } } } @@ -707,10 +493,10 @@ where } match self { AnyBoardChild::View(view_item) => { - view_item.message(view_state.inner.as_mut().unwrap(), rest, message, app_state) + view_item.message(&mut view_state.inner, rest, message, app_state) } - AnyBoardChild::Shape(shape_item) => { - shape_item.message(&mut (), rest, message, app_state) + AnyBoardChild::Graphics(shape_item) => { + shape_item.message(&mut view_state.inner, rest, message, app_state) } } } diff --git a/xilem/src/view/board/kurbo_shape.rs b/xilem/src/view/board/kurbo_shape.rs new file mode 100644 index 000000000..20475720d --- /dev/null +++ b/xilem/src/view/board/kurbo_shape.rs @@ -0,0 +1,103 @@ +// Copyright 2024 the Xilem Authors +// SPDX-License-Identifier: Apache-2.0 + +//! Implementation of the View trait for various kurbo shapes. + +use masonry::widget::KurboShape; +use vello::kurbo; +use xilem_core::{AnyElement, AnyView, DynMessage, MessageResult, Mut, OrphanView, SuperElement}; + +use crate::{Pod, ViewCtx, WidgetView}; + +pub trait GraphicsView: WidgetView {} + +impl GraphicsView for V where + V: WidgetView + Send + Sync +{ +} + +pub type AnyGraphicsView = + dyn AnyView> + Send + Sync; + +impl SuperElement> for Pod { + fn upcast(child: Pod) -> Self { + child + } + + fn with_downcast_val( + mut this: Mut<'_, Self>, + f: impl FnOnce(Mut<'_, Pod>) -> R, + ) -> (Self::Mut<'_>, R) { + let r = f(this.reborrow_mut()); + (this, r) + } +} + +impl AnyElement> for Pod { + fn replace_inner(mut this: Self::Mut<'_>, child: Pod) -> Self::Mut<'_> { + this.update_from(child.inner.as_ref().unwrap()); + this + } +} + +macro_rules! impl_orphan_view { + ($t:ident) => { + impl OrphanView + for ViewCtx + { + type OrphanViewState = (); + type OrphanElement = Pod; + + fn orphan_build( + view: &kurbo::$t, + _ctx: &mut ViewCtx, + ) -> (Self::OrphanElement, Self::OrphanViewState) { + (Pod::new(KurboShape::new(view.clone())), ()) + } + + fn orphan_rebuild<'el>( + new: &kurbo::$t, + prev: &kurbo::$t, + (): &mut Self::OrphanViewState, + ctx: &mut ViewCtx, + mut element: Mut<'el, Self::OrphanElement>, + ) -> Mut<'el, Self::OrphanElement> { + if new != prev { + element.set_shape(new.clone().into()); + ctx.mark_changed(); + } + element + } + + fn orphan_teardown( + _view: &kurbo::$t, + (): &mut Self::OrphanViewState, + _ctx: &mut ViewCtx, + _element: Mut<'_, Self::OrphanElement>, + ) { + } + + fn orphan_message( + _view: &kurbo::$t, + (): &mut Self::OrphanViewState, + _id_path: &[xilem_core::ViewId], + message: DynMessage, + _app_state: &mut State, + ) -> MessageResult { + MessageResult::Stale(message) + } + } + }; +} + +impl_orphan_view!(PathSeg); +impl_orphan_view!(Arc); +impl_orphan_view!(BezPath); +impl_orphan_view!(Circle); +impl_orphan_view!(CircleSegment); +impl_orphan_view!(CubicBez); +impl_orphan_view!(Ellipse); +impl_orphan_view!(Line); +impl_orphan_view!(QuadBez); +impl_orphan_view!(Rect); +impl_orphan_view!(RoundedRect); diff --git a/xilem/src/view/board/style_modifier.rs b/xilem/src/view/board/style_modifier.rs new file mode 100644 index 000000000..87c0d0c90 --- /dev/null +++ b/xilem/src/view/board/style_modifier.rs @@ -0,0 +1,342 @@ +// Copyright 2024 the Xilem Authors +// SPDX-License-Identifier: Apache-2.0 + +use std::marker::PhantomData; + +use masonry::{widget::KurboShape, Affine}; +use vello::{ + kurbo, + peniko::{self, Brush}, +}; +use xilem_core::{AnyView, DynMessage, MessageResult, Mut, View, ViewId, ViewMarker}; + +use crate::{Pod, ViewCtx}; + +use super::{AnyBoardChild, GraphicsView}; + +pub struct Transform { + child: V, + transform: Affine, + phantom: PhantomData (State, Action)>, +} + +pub struct Fill { + child: V, + mode: peniko::Fill, + brush: Brush, + brush_transform: Option, + phantom: PhantomData (State, Action)>, +} + +pub struct Stroke { + child: V, + style: kurbo::Stroke, + brush: Brush, + brush_transform: Option, + phantom: PhantomData (State, Action)>, +} + +pub fn transform(child: V, transform: Affine) -> Transform { + Transform { + child, + transform, + phantom: PhantomData, + } +} + +pub fn fill(child: V, brush: impl Into) -> Fill { + Fill { + child, + mode: peniko::Fill::NonZero, + brush: brush.into(), + brush_transform: None, + phantom: PhantomData, + } +} + +pub fn stroke( + child: V, + brush: impl Into, + style: kurbo::Stroke, +) -> Stroke { + Stroke { + child, + style, + brush: brush.into(), + brush_transform: None, + phantom: PhantomData, + } +} + +impl Fill { + pub fn mode(mut self, mode: peniko::Fill) -> Self { + self.mode = mode; + self + } + + pub fn brush_transform(mut self, brush_transform: Affine) -> Self { + self.brush_transform = Some(brush_transform); + self + } +} + +impl Stroke { + pub fn brush_transform(mut self, brush_transform: Affine) -> Self { + self.brush_transform = Some(brush_transform); + self + } +} + +pub trait GraphicsExt: GraphicsView + Sized { + fn transform(self, affine: Affine) -> Transform { + transform(self, affine) + } + + fn fill(self, brush: impl Into) -> Fill { + fill(self, brush) + } + + fn stroke(self, brush: impl Into, style: kurbo::Stroke) -> Stroke { + stroke(self, brush, style) + } + + fn into_any_board(self) -> AnyBoardChild + where + Self: AnyView> + Send + Sync + 'static, + { + AnyBoardChild::Graphics(Box::new(self)) + } +} + +impl GraphicsExt for kurbo::PathSeg {} +impl GraphicsExt for kurbo::Arc {} +impl GraphicsExt for kurbo::BezPath {} +impl GraphicsExt for kurbo::Circle {} +impl GraphicsExt for kurbo::CircleSegment {} +impl GraphicsExt for kurbo::CubicBez {} +impl GraphicsExt for kurbo::Ellipse {} +impl GraphicsExt for kurbo::Line {} +impl GraphicsExt for kurbo::QuadBez {} +impl GraphicsExt for kurbo::Rect {} +impl GraphicsExt for kurbo::RoundedRect {} + +impl> GraphicsExt + for Transform +{ +} +impl> GraphicsExt + for Fill +{ +} +impl> GraphicsExt + for Stroke +{ +} + +impl ViewMarker for Transform {} +impl View for Transform +where + State: 'static, + Action: 'static, + V: GraphicsView, +{ + type ViewState = V::ViewState; + type Element = Pod; + + fn build(&self, ctx: &mut ViewCtx) -> (Self::Element, Self::ViewState) { + let (mut element, state) = self.child.build(ctx); + element + .inner + .as_mut() + .unwrap() + .set_transform(self.transform); + (element, state) + } + + fn rebuild<'el>( + &self, + prev: &Self, + child_state: &mut Self::ViewState, + ctx: &mut ViewCtx, + element: Mut<'el, Self::Element>, + ) -> Mut<'el, Self::Element> { + let mut element = self.child.rebuild(&prev.child, child_state, ctx, element); + if self.transform != prev.transform { + element.set_transform(self.transform); + ctx.mark_changed(); + } + element + } + + fn teardown( + &self, + child_state: &mut Self::ViewState, + ctx: &mut ViewCtx, + element: Mut<'_, Self::Element>, + ) { + self.child.teardown(child_state, ctx, element); + } + + fn message( + &self, + child_state: &mut Self::ViewState, + id_path: &[ViewId], + message: DynMessage, + app_state: &mut State, + ) -> MessageResult { + self.child.message(child_state, id_path, message, app_state) + } +} + +impl ViewMarker for Fill {} +impl View for Fill +where + State: 'static, + Action: 'static, + V: GraphicsView, +{ + type ViewState = V::ViewState; + type Element = Pod; + + fn build(&self, ctx: &mut ViewCtx) -> (Self::Element, Self::ViewState) { + let (mut element, state) = self.child.build(ctx); + element.inner.as_mut().unwrap().set_fill_mode(self.mode); + element + .inner + .as_mut() + .unwrap() + .set_fill_brush(self.brush.clone()); + element + .inner + .as_mut() + .unwrap() + .set_fill_brush_transform(self.brush_transform); + (element, state) + } + + fn rebuild<'el>( + &self, + prev: &Self, + child_state: &mut Self::ViewState, + ctx: &mut ViewCtx, + element: Mut<'el, Self::Element>, + ) -> Mut<'el, Self::Element> { + let mut element = self.child.rebuild(&prev.child, child_state, ctx, element); + { + if self.mode != prev.mode { + element.set_fill_mode(self.mode); + ctx.mark_changed(); + } + if self.brush != prev.brush { + element.set_fill_brush(self.brush.clone()); + ctx.mark_changed(); + } + if self.brush_transform != prev.brush_transform { + element.set_fill_brush_transform(self.brush_transform); + ctx.mark_changed(); + } + } + element + } + + fn teardown( + &self, + child_state: &mut Self::ViewState, + ctx: &mut ViewCtx, + element: Mut<'_, Self::Element>, + ) { + self.child.teardown(child_state, ctx, element); + } + + fn message( + &self, + child_state: &mut Self::ViewState, + id_path: &[ViewId], + message: DynMessage, + app_state: &mut State, + ) -> MessageResult { + self.child.message(child_state, id_path, message, app_state) + } +} + +impl ViewMarker for Stroke {} +impl View for Stroke +where + State: 'static, + Action: 'static, + V: GraphicsView, +{ + type ViewState = V::ViewState; + type Element = Pod; + + fn build(&self, ctx: &mut ViewCtx) -> (Self::Element, Self::ViewState) { + let (mut element, state) = self.child.build(ctx); + element + .inner + .as_mut() + .unwrap() + .set_stroke_style(self.style.clone()); + element + .inner + .as_mut() + .unwrap() + .set_stroke_brush(self.brush.clone()); + element + .inner + .as_mut() + .unwrap() + .set_stroke_brush_transform(self.brush_transform); + (element, state) + } + + fn rebuild<'el>( + &self, + prev: &Self, + child_state: &mut Self::ViewState, + ctx: &mut ViewCtx, + element: Mut<'el, Self::Element>, + ) -> Mut<'el, Self::Element> { + let mut element = self.child.rebuild(&prev.child, child_state, ctx, element); + { + if self.style.width != prev.style.width + || self.style.join != prev.style.join + || self.style.miter_limit != prev.style.miter_limit + || self.style.start_cap != prev.style.start_cap + || self.style.end_cap != prev.style.end_cap + || self.style.dash_pattern != prev.style.dash_pattern + || self.style.dash_offset != prev.style.dash_offset + { + element.set_stroke_style(self.style.clone()); + ctx.mark_changed(); + } + if self.brush != prev.brush { + element.set_stroke_brush(self.brush.clone()); + ctx.mark_changed(); + } + if self.brush_transform != prev.brush_transform { + element.set_stroke_brush_transform(self.brush_transform); + ctx.mark_changed(); + } + } + element + } + + fn teardown( + &self, + child_state: &mut Self::ViewState, + ctx: &mut ViewCtx, + element: Mut<'_, Self::Element>, + ) { + self.child.teardown(child_state, ctx, element); + } + + fn message( + &self, + child_state: &mut Self::ViewState, + id_path: &[ViewId], + message: DynMessage, + app_state: &mut State, + ) -> MessageResult { + self.child.message(child_state, id_path, message, app_state) + } +} From 6571b0aaff59e75c3a0a52eb6ee4b442ee5d0ffe Mon Sep 17 00:00:00 2001 From: Philipp Mildenberger Date: Tue, 17 Sep 2024 22:26:03 +0200 Subject: [PATCH 05/10] dirty sketch of SvgElement trait --- masonry/src/widget/board.rs | 287 ++++++++++++++++--------- masonry/src/widget/mod.rs | 2 +- xilem/src/view/board/style_modifier.rs | 22 +- 3 files changed, 190 insertions(+), 121 deletions(-) diff --git a/masonry/src/widget/board.rs b/masonry/src/widget/board.rs index f0f8ad055..673c164cd 100644 --- a/masonry/src/widget/board.rs +++ b/masonry/src/widget/board.rs @@ -3,6 +3,8 @@ //! A widget that arranges its children in a one-dimensional array. +use std::ops::{Deref as _, DerefMut as _}; + use accesskit::Role; use smallvec::SmallVec; use tracing::{trace_span, warn, Span}; @@ -21,7 +23,7 @@ use crate::{ /// A container with absolute positioning layout. pub struct Board { - children: Vec, + children: Vec>>, } /// Parameters for an item in a [`Board`] container. @@ -57,6 +59,164 @@ pub enum ConcreteShape { RoundedRect(RoundedRect), } +pub trait SvgElement: Widget { + fn origin(&self) -> Point; // relative to parents transform + fn size(&self) -> Size; +} + +impl SvgElement for KurboShape { + fn origin(&self) -> Point { + self.shape.bounding_box().origin() + } + + fn size(&self) -> Size { + self.shape.bounding_box().size() + } +} + +impl SvgElement for Box { + fn origin(&self) -> Point { + self.deref().origin() + } + + fn size(&self) -> Size { + self.deref().size() + } +} + +impl Widget for Box { + fn on_pointer_event(&mut self, ctx: &mut EventCtx, event: &PointerEvent) { + self.deref_mut().on_pointer_event(ctx, event); + } + + fn on_text_event(&mut self, ctx: &mut EventCtx, event: &TextEvent) { + self.deref_mut().on_text_event(ctx, event); + } + + fn on_access_event(&mut self, ctx: &mut EventCtx, event: &AccessEvent) { + self.deref_mut().on_access_event(ctx, event); + } + + fn on_status_change(&mut self, ctx: &mut LifeCycleCtx, event: &StatusChange) { + self.deref_mut().on_status_change(ctx, event); + } + + fn lifecycle(&mut self, ctx: &mut LifeCycleCtx, event: &LifeCycle) { + self.deref_mut().lifecycle(ctx, event); + } + + fn layout(&mut self, ctx: &mut LayoutCtx, bc: &BoxConstraints) -> Size { + self.deref_mut().layout(ctx, bc) + } + + fn compose(&mut self, ctx: &mut crate::ComposeCtx) { + self.deref_mut().compose(ctx); + } + + fn paint(&mut self, ctx: &mut PaintCtx, scene: &mut Scene) { + self.deref_mut().paint(ctx, scene); + } + + fn accessibility_role(&self) -> Role { + self.deref().accessibility_role() + } + + fn accessibility(&mut self, ctx: &mut AccessCtx) { + self.deref_mut().accessibility(ctx); + } + + fn type_name(&self) -> &'static str { + self.deref().type_name() + } + + fn short_type_name(&self) -> &'static str { + self.deref().short_type_name() + } + + fn children_ids(&self) -> SmallVec<[WidgetId; 16]> { + self.deref().children_ids() + } + + fn skip_pointer(&self) -> bool { + self.deref().skip_pointer() + } + + fn make_trace_span(&self) -> Span { + self.deref().make_trace_span() + } + + fn get_debug_text(&self) -> Option { + self.deref().get_debug_text() + } + + fn get_cursor(&self) -> cursor_icon::CursorIcon { + self.deref().get_cursor() + } + + fn as_any(&self) -> &dyn std::any::Any { + self.deref().as_any() + } + + fn as_mut_any(&mut self) -> &mut dyn std::any::Any { + self.deref_mut().as_mut_any() + } +} + +pub struct PositionedElement { + inner: W, + params: BoardParams, +} + +impl PositionedElement { + pub fn new(widget: W, params: impl Into) -> Self { + PositionedElement { + inner: widget, + params: params.into(), + } + } +} + +impl SvgElement for PositionedElement { + fn origin(&self) -> Point { + self.params.origin + } + + fn size(&self) -> Size { + self.params.size + } +} + +// TODO Should also implement the other methods... +impl Widget for PositionedElement { + fn on_status_change(&mut self, ctx: &mut LifeCycleCtx, event: &StatusChange) { + self.inner.on_status_change(ctx, event); + } + + fn lifecycle(&mut self, ctx: &mut LifeCycleCtx, event: &LifeCycle) { + self.inner.lifecycle(ctx, event); + } + + fn layout(&mut self, ctx: &mut LayoutCtx, bc: &BoxConstraints) -> Size { + self.inner.layout(ctx, bc) + } + + fn paint(&mut self, ctx: &mut PaintCtx, scene: &mut Scene) { + self.inner.paint(ctx, scene); + } + + fn accessibility_role(&self) -> Role { + self.inner.accessibility_role() + } + + fn accessibility(&mut self, ctx: &mut AccessCtx) { + self.inner.accessibility(ctx); + } + + fn children_ids(&self) -> SmallVec<[WidgetId; 16]> { + self.inner.children_ids() + } +} + // --- MARK: IMPL BOARD --- impl Board { /// Create a new Board oriented with viewport origin set to (0, 0) and scale (1, 1). @@ -67,25 +227,8 @@ impl Board { } /// Builder-style method to add a positioned child to the container. - pub fn with_child_pod( - mut self, - widget: WidgetPod>, - params: impl Into, - ) -> Self { - // TODO - dedup? - self.children.push(Child { - widget, - params: params.into(), - }); - self - } - - /// Builder-style method to add a Kurbo shape to the container. - pub fn with_shape_pod(mut self, shape: WidgetPod) -> Self { - self.children.push(Child { - params: shape.as_ref().unwrap().shape.bounding_box().into(), - widget: shape.boxed(), - }); + pub fn with_child_pod(mut self, child: WidgetPod>) -> Self { + self.children.push(child); self } @@ -155,79 +298,25 @@ impl KurboShape { // --- MARK: WIDGETMUT--- impl<'a> WidgetMut<'a, Board> { /// Add a positioned child widget. - pub fn add_child(&mut self, child: impl Widget, params: impl Into) { - self.widget.children.push(Child { - widget: WidgetPod::new(Box::new(child)), - params: params.into(), - }); - self.ctx.children_changed(); - } - - /// Add a Kurbo shape. - pub fn add_shape_child(&mut self, shape: Box) { - self.widget.children.push(Child { - params: shape.shape.bounding_box().into(), - widget: WidgetPod::new(shape), - }); + pub fn add_child(&mut self, child: impl SvgElement) { + self.widget.children.push(WidgetPod::new(Box::new(child))); self.ctx.children_changed(); } - pub fn insert_child(&mut self, idx: usize, child: impl Widget, params: impl Into) { - self.insert_child_pod(idx, WidgetPod::new(Box::new(child)), params); - } - - pub fn insert_child_pod( - &mut self, - idx: usize, - child: WidgetPod>, - params: impl Into, - ) { - let child = Child { - widget: child, - params: params.into(), - }; - self.widget.children.insert(idx, child); - self.ctx.children_changed(); - } - - pub fn insert_shape_pod(&mut self, idx: usize, shape: WidgetPod) { - let child = Child { - params: shape.as_ref().unwrap().shape.bounding_box().into(), - widget: shape.boxed(), - }; + pub fn insert_child(&mut self, idx: usize, child: WidgetPod>) { self.widget.children.insert(idx, child); self.ctx.children_changed(); } pub fn remove_child(&mut self, idx: usize) { - let Child { widget, .. } = self.widget.children.remove(idx); + let widget = self.widget.children.remove(idx); self.ctx.remove_child(widget); self.ctx.request_layout(); } - // FIXME: unsure about correctness of keeping child params unchanged - pub fn replace_child(&mut self, idx: usize, new_widget: WidgetPod>) { - let Child { widget, .. } = &mut self.widget.children[idx]; - let old_widget = std::mem::replace(widget, new_widget); - self.ctx.remove_child(old_widget); - self.ctx.request_layout(); - } - // FIXME - Remove Box - pub fn child_mut(&mut self, idx: usize) -> WidgetMut<'_, Box> { - let Child { widget, .. } = &mut self.widget.children[idx]; - self.ctx.get_mut(widget) - } - - /// Updates the position parameters for the child at `idx`, - /// - /// # Panics - /// - /// Panics if the element at `idx` is not a widget. - pub fn update_child_board_params(&mut self, idx: usize, new_params: impl Into) { - // FIXME: should check if the child is a graphics or rugular widget - self.widget.children[idx].params = new_params.into(); - self.ctx.children_changed(); + pub fn child_mut(&mut self, idx: usize) -> WidgetMut<'_, Box> { + self.ctx.get_mut(&mut self.widget.children[idx]) } pub fn clear(&mut self) { @@ -235,7 +324,7 @@ impl<'a> WidgetMut<'a, Board> { self.ctx.request_layout(); for child in self.widget.children.drain(..) { - self.ctx.remove_child(child.widget); + self.ctx.remove_child(child); } } } @@ -331,16 +420,19 @@ impl Widget for Board { fn lifecycle(&mut self, ctx: &mut LifeCycleCtx, event: &LifeCycle) { for child in &mut self.children { - child.widget.lifecycle(ctx, event); + child.lifecycle(ctx, event); } } fn layout(&mut self, ctx: &mut LayoutCtx, bc: &BoxConstraints) -> Size { bc.debug_check("Board"); - for Child { widget, params } in &mut self.children { - ctx.run_layout(widget, &BoxConstraints::tight(params.size)); - ctx.place_child(widget, params.origin); + for widget in &mut self.children { + ctx.run_layout( + widget, + &BoxConstraints::tight(widget.as_ref().unwrap().size()), + ); + ctx.place_child(widget, widget.as_ref().unwrap().origin()); } bc.max() @@ -355,10 +447,7 @@ impl Widget for Board { fn accessibility(&mut self, _ctx: &mut AccessCtx) {} fn children_ids(&self) -> SmallVec<[WidgetId; 16]> { - self.children - .iter() - .map(|child| child.widget.id()) - .collect() + self.children.iter().map(|child| child.id()).collect() } fn make_trace_span(&self) -> Span { @@ -596,14 +685,14 @@ mod tests { #[test] fn board_absolute_placement_snapshots() { let widget = Board::new() - .with_child_pod( - WidgetPod::new(Box::new(Button::new("hello"))), + .with_child_pod(WidgetPod::new(Box::new(PositionedElement::new( + Button::new("hello"), Rect::new(10., 10., 60., 40.), - ) - .with_child_pod( - WidgetPod::new(Box::new(Button::new("world"))), + )))) + .with_child_pod(WidgetPod::new(Box::new(PositionedElement::new( + Button::new("hello"), Rect::new(30., 30., 80., 60.), - ); + )))); let mut harness = TestHarness::create(widget); @@ -617,11 +706,11 @@ mod tests { shape.set_stroke_style(Stroke::new(2.).with_dashes(0., [2., 1.])); shape.set_stroke_brush(Brush::Solid(vello::peniko::Color::PALE_VIOLET_RED)); let widget = Board::new() - .with_child_pod( - WidgetPod::new(Box::new(Button::new("hello"))), + .with_child_pod(WidgetPod::new(Box::new(PositionedElement::new( + Button::new("hello"), Rect::new(10., 10., 60., 40.), - ) - .with_shape_pod(WidgetPod::new(shape)); + )))) + .with_child_pod(WidgetPod::new(Box::new(shape))); let mut harness = TestHarness::create(widget); diff --git a/masonry/src/widget/mod.rs b/masonry/src/widget/mod.rs index c6d687db5..e83e5d913 100644 --- a/masonry/src/widget/mod.rs +++ b/masonry/src/widget/mod.rs @@ -35,7 +35,7 @@ mod widget_arena; pub use self::image::Image; pub use align::Align; -pub use board::{Board, BoardParams, ConcreteShape, KurboShape}; +pub use board::{Board, BoardParams, ConcreteShape, KurboShape, PositionedElement, SvgElement}; pub use button::Button; pub use checkbox::Checkbox; pub use flex::{Axis, CrossAxisAlignment, Flex, FlexParams, MainAxisAlignment}; diff --git a/xilem/src/view/board/style_modifier.rs b/xilem/src/view/board/style_modifier.rs index 87c0d0c90..402d9cce9 100644 --- a/xilem/src/view/board/style_modifier.rs +++ b/xilem/src/view/board/style_modifier.rs @@ -108,28 +108,8 @@ pub trait GraphicsExt: GraphicsView + Sized { } } -impl GraphicsExt for kurbo::PathSeg {} -impl GraphicsExt for kurbo::Arc {} -impl GraphicsExt for kurbo::BezPath {} -impl GraphicsExt for kurbo::Circle {} -impl GraphicsExt for kurbo::CircleSegment {} -impl GraphicsExt for kurbo::CubicBez {} -impl GraphicsExt for kurbo::Ellipse {} -impl GraphicsExt for kurbo::Line {} -impl GraphicsExt for kurbo::QuadBez {} -impl GraphicsExt for kurbo::Rect {} -impl GraphicsExt for kurbo::RoundedRect {} - -impl> GraphicsExt - for Transform -{ -} -impl> GraphicsExt - for Fill -{ -} impl> GraphicsExt - for Stroke + for V { } From 4140de138027e7b0a610ed2321d34da286bbe5ec Mon Sep 17 00:00:00 2001 From: Muhammad Ragib Hasin Date: Wed, 18 Sep 2024 11:15:20 +0600 Subject: [PATCH 06/10] Simplify BoardElement --- masonry/src/widget/board.rs | 125 +++++++++++++++++++++++++++---- masonry/src/widget/widget_pod.rs | 6 ++ xilem/src/view/board.rs | 83 ++++++++++---------- 3 files changed, 156 insertions(+), 58 deletions(-) diff --git a/masonry/src/widget/board.rs b/masonry/src/widget/board.rs index 673c164cd..2e3e093cc 100644 --- a/masonry/src/widget/board.rs +++ b/masonry/src/widget/board.rs @@ -29,8 +29,8 @@ pub struct Board { /// Parameters for an item in a [`Board`] container. #[derive(Debug, Copy, Clone, PartialEq)] pub struct BoardParams { - origin: Point, - size: Size, + pub origin: Point, + pub size: Size, } pub struct KurboShape { @@ -62,6 +62,9 @@ pub enum ConcreteShape { pub trait SvgElement: Widget { fn origin(&self) -> Point; // relative to parents transform fn size(&self) -> Size; + + fn set_origin(&mut self, origin: Point); // relative to parents transform + fn set_size(&mut self, size: Size); } impl SvgElement for KurboShape { @@ -72,6 +75,14 @@ impl SvgElement for KurboShape { fn size(&self) -> Size { self.shape.bounding_box().size() } + + fn set_origin(&mut self, _: Point) { + panic!("a shape doens not support setting its origin after creation") + } + + fn set_size(&mut self, _: Size) { + panic!("a shape doens not support setting its size after creation") + } } impl SvgElement for Box { @@ -82,6 +93,14 @@ impl SvgElement for Box { fn size(&self) -> Size { self.deref().size() } + + fn set_origin(&mut self, origin: Point) { + self.deref_mut().set_origin(origin); + } + + fn set_size(&mut self, size: Size) { + self.deref_mut().set_size(size); + } } impl Widget for Box { @@ -162,6 +181,62 @@ impl Widget for Box { } } +impl<'a> WidgetMut<'a, Box> { + /// Attempt to downcast to `WidgetMut` of concrete Widget type. + pub fn try_downcast(&mut self) -> Option> { + Some(WidgetMut { + ctx: self.ctx.reborrow_mut(), + widget: self.widget.as_mut_any().downcast_mut()?, + }) + } + + /// Downcasts to `WidgetMut` of concrete Widget type. + /// + /// ## Panics + /// + /// Panics if the downcast fails, with an error message that shows the + /// discrepancy between the expected and actual types. + pub fn downcast_positioned(&mut self) -> WidgetMut<'_, W2> { + let w1_name = self.widget.type_name(); + match self.widget.as_mut_any().downcast_mut() { + Some(PositionedElement { inner: widget, .. }) => WidgetMut { + ctx: self.ctx.reborrow_mut(), + widget, + }, + None => { + panic!( + "failed to downcast widget: expected widget of type `{}`, found `{}`", + std::any::type_name::(), + w1_name, + ); + } + } + } + + /// Downcasts to `WidgetMut` of concrete Widget type. + /// + /// ## Panics + /// + /// Panics if the downcast fails, with an error message that shows the + /// discrepancy between the expected and actual types. + pub fn downcast(&mut self) -> WidgetMut<'_, W2> { + let w1_name = self.widget.type_name(); + match self.widget.as_mut_any().downcast_mut() { + Some(widget) => WidgetMut { + ctx: self.ctx.reborrow_mut(), + widget, + }, + None => { + panic!( + "failed to downcast widget: expected widget of type `{}`, found `{}`", + std::any::type_name::(), + w1_name, + ); + } + } + } +} + pub struct PositionedElement { inner: W, params: BoardParams, @@ -176,6 +251,26 @@ impl PositionedElement { } } +impl WidgetPod { + pub fn positioned(self, params: impl Into) -> WidgetPod> { + let id = self.id(); + WidgetPod::new_with_id( + Box::new(PositionedElement { + inner: self.inner().unwrap(), + params: params.into(), + }), + id, + ) + } +} + +impl WidgetPod { + pub fn svg_boxed(self) -> WidgetPod> { + let id = self.id(); + WidgetPod::new_with_id(Box::new(self.inner().unwrap()), id) + } +} + impl SvgElement for PositionedElement { fn origin(&self) -> Point { self.params.origin @@ -184,6 +279,14 @@ impl SvgElement for PositionedElement { fn size(&self) -> Size { self.params.size } + + fn set_origin(&mut self, origin: Point) { + self.params.origin = origin; + } + + fn set_size(&mut self, size: Size) { + self.params.size = size; + } } // TODO Should also implement the other methods... @@ -427,12 +530,13 @@ impl Widget for Board { fn layout(&mut self, ctx: &mut LayoutCtx, bc: &BoxConstraints) -> Size { bc.debug_check("Board"); - for widget in &mut self.children { - ctx.run_layout( - widget, - &BoxConstraints::tight(widget.as_ref().unwrap().size()), - ); - ctx.place_child(widget, widget.as_ref().unwrap().origin()); + for child in &mut self.children { + let (size, origin) = { + let child_ref = ctx.get_raw_ref(child); + (child_ref.widget().size(), child_ref.widget().origin()) + }; + ctx.run_layout(child, &BoxConstraints::tight(size)); + ctx.place_child(child, origin); } bc.max() @@ -525,11 +629,6 @@ impl From for BoardParams { } } -struct Child { - widget: WidgetPod>, - params: BoardParams, -} - macro_rules! for_all_variants { ($self:expr; $i:ident => $e:expr) => { match $self { diff --git a/masonry/src/widget/widget_pod.rs b/masonry/src/widget/widget_pod.rs index 46dd9bad5..9db9cb221 100644 --- a/masonry/src/widget/widget_pod.rs +++ b/masonry/src/widget/widget_pod.rs @@ -59,6 +59,8 @@ impl WidgetPod { } /// Take the inner widget, if it has not been inserted yet. + /// + /// Never call it outside of `Widget::lyfecycle` or `View::build` pub fn inner(self) -> Option { if let WidgetPodInner::Created(w) = self.inner { Some(w) @@ -68,6 +70,8 @@ impl WidgetPod { } /// Get access to the inner widget, if it has not been inserted yet. + /// + /// Never call it outside of `Widget::lyfecycle` or `View::build` pub fn as_ref(&self) -> Option<&W> { if let WidgetPodInner::Created(w) = &self.inner { Some(w) @@ -77,6 +81,8 @@ impl WidgetPod { } /// Get access to the inner widget, if it has not been inserted yet. + /// + /// Never call it outside of `Widget::lyfecycle` or `View::build` pub fn as_mut(&mut self) -> Option<&mut W> { if let WidgetPodInner::Created(w) = &mut self.inner { Some(w) diff --git a/xilem/src/view/board.rs b/xilem/src/view/board.rs index 51ed354d3..67aa61342 100644 --- a/xilem/src/view/board.rs +++ b/xilem/src/view/board.rs @@ -3,10 +3,7 @@ use std::marker::PhantomData; -use masonry::{ - widget::{self, KurboShape, WidgetMut}, - Widget, -}; +use masonry::widget::{self, SvgElement, WidgetMut}; use xilem_core::{ AppendVec, DynMessage, ElementSplice, MessageResult, Mut, SuperElement, View, ViewElement, ViewId, ViewMarker, ViewPathTracker, ViewSequence, @@ -51,11 +48,8 @@ where let mut elements = AppendVec::default(); let mut widget = widget::Board::new(); let seq_state = self.sequence.seq_build(ctx, &mut elements); - for child in elements.into_inner() { - widget = match child { - BoardElement::View(pod, params) => widget.with_child_pod(pod.inner, params), - BoardElement::Graphics(pod) => widget.with_shape_pod(pod.inner), - } + for BoardElement { element } in elements.into_inner() { + widget = widget.with_child_pod(element.inner); } (Pod::new(widget), seq_state) } @@ -98,10 +92,8 @@ where } } -#[expect(clippy::large_enum_variant)] -pub enum BoardElement { - View(Pod>, BoardParams), - Graphics(Pod), +pub struct BoardElement { + element: Pod>, } impl ViewElement for BoardElement { @@ -151,29 +143,15 @@ impl<'w> BoardSplice<'w> { } impl ElementSplice for BoardSplice<'_> { - fn insert(&mut self, element: BoardElement) { - match element { - BoardElement::View(pod, params) => { - self.element.insert_child_pod(self.idx, pod.inner, params); - } - BoardElement::Graphics(pod) => { - self.element.insert_shape_pod(self.idx, pod.inner); - } - } + fn insert(&mut self, BoardElement { element }: BoardElement) { + self.element.insert_child(self.idx, element.inner); self.idx += 1; } fn with_scratch(&mut self, f: impl FnOnce(&mut AppendVec) -> R) -> R { let ret = f(&mut self.scratch); - for element in self.scratch.drain() { - match element { - BoardElement::View(pod, params) => { - self.element.insert_child_pod(self.idx, pod.inner, params); - } - BoardElement::Graphics(pod) => { - self.element.insert_shape_pod(self.idx, pod.inner); - } - } + for BoardElement { element } in self.scratch.drain() { + self.element.insert_child(self.idx, element.inner); self.idx += 1; } ret @@ -283,7 +261,9 @@ where fn build(&self, ctx: &mut ViewCtx) -> (Self::Element, Self::ViewState) { let (pod, state) = self.view.build(ctx); ( - BoardElement::View(pod.inner.boxed().into(), self.params), + BoardElement { + element: pod.inner.positioned(self.params).into(), + }, state, ) } @@ -296,14 +276,22 @@ where mut element: Mut<'el, Self::Element>, ) -> Mut<'el, Self::Element> { { - if self.params != prev.params { - element - .parent - .update_child_board_params(element.idx, self.params); - } + // if self.params != prev.params { + // element + // .parent + // .update_child_board_params(element.idx, self.params); + // } let mut child = element.parent.child_mut(element.idx); self.view - .rebuild(&prev.view, view_state, ctx, child.downcast()); + .rebuild(&prev.view, view_state, ctx, child.downcast_positioned()); + if self.params.origin != prev.params.origin { + child.widget.set_origin(self.params.origin); + child.ctx.request_layout(); + } + if self.params.size != prev.params.size { + child.widget.set_size(self.params.size); + child.ctx.request_layout(); + } } element } @@ -315,7 +303,8 @@ where mut element: Mut<'_, Self::Element>, ) { let mut child = element.parent.child_mut(element.idx); - self.view.teardown(view_state, ctx, child.downcast()); + self.view + .teardown(view_state, ctx, child.downcast_positioned()); } fn message( @@ -380,7 +369,12 @@ where AnyBoardChild::Graphics(shape_item) => { let (element, state) = ctx.with_id(ViewId::new(generation), |ctx| shape_item.build(ctx)); - (BoardElement::Graphics(element), state) + ( + BoardElement { + element: element.inner.svg_boxed().into(), + }, + state, + ) } }; ( @@ -431,7 +425,9 @@ where new_shape.build(ctx) }); view_state.inner = child_state; - element.parent.insert_shape_pod(element.idx, child.inner); + element + .parent + .insert_child(element.idx, child.inner.svg_boxed()); element } (AnyBoardChild::Graphics(prev_shape), AnyBoardChild::View(new_view)) => { @@ -449,12 +445,9 @@ where new_view.build(ctx) }); view_state.inner = child_state; - let BoardElement::View(pod, params) = view_element else { - unreachable!() - }; element .parent - .insert_child_pod(element.idx, pod.inner, params); + .insert_child(element.idx, view_element.element.inner); element } } From 267b0f3618a6425305c2dba2dc9b6a17349fa4a5 Mon Sep 17 00:00:00 2001 From: Muhammad Ragib Hasin Date: Wed, 18 Sep 2024 11:34:33 +0600 Subject: [PATCH 07/10] Removed AnyBoardChild --- masonry/src/widget/board.rs | 29 +++- xilem/src/view/board.rs | 218 +++++-------------------- xilem/src/view/board/kurbo_shape.rs | 12 +- xilem/src/view/board/style_modifier.rs | 14 +- 4 files changed, 86 insertions(+), 187 deletions(-) diff --git a/masonry/src/widget/board.rs b/masonry/src/widget/board.rs index 2e3e093cc..66bae8a01 100644 --- a/masonry/src/widget/board.rs +++ b/masonry/src/widget/board.rs @@ -289,7 +289,6 @@ impl SvgElement for PositionedElement { } } -// TODO Should also implement the other methods... impl Widget for PositionedElement { fn on_status_change(&mut self, ctx: &mut LifeCycleCtx, event: &StatusChange) { self.inner.on_status_change(ctx, event); @@ -318,6 +317,34 @@ impl Widget for PositionedElement { fn children_ids(&self) -> SmallVec<[WidgetId; 16]> { self.inner.children_ids() } + + fn on_pointer_event(&mut self, ctx: &mut EventCtx, event: &PointerEvent) { + self.inner.on_pointer_event(ctx, event); + } + + fn on_text_event(&mut self, ctx: &mut EventCtx, event: &TextEvent) { + self.inner.on_text_event(ctx, event); + } + + fn on_access_event(&mut self, ctx: &mut EventCtx, event: &AccessEvent) { + self.inner.on_access_event(ctx, event); + } + + fn compose(&mut self, ctx: &mut crate::ComposeCtx) { + self.inner.compose(ctx); + } + + fn skip_pointer(&self) -> bool { + self.inner.skip_pointer() + } + + fn get_debug_text(&self) -> Option { + self.inner.get_debug_text() + } + + fn get_cursor(&self) -> cursor_icon::CursorIcon { + self.inner.get_cursor() + } } // --- MARK: IMPL BOARD --- diff --git a/xilem/src/view/board.rs b/xilem/src/view/board.rs index 67aa61342..014a11603 100644 --- a/xilem/src/view/board.rs +++ b/xilem/src/view/board.rs @@ -3,20 +3,20 @@ use std::marker::PhantomData; -use masonry::widget::{self, SvgElement, WidgetMut}; +use masonry::widget::{self, KurboShape, SvgElement, WidgetMut}; use xilem_core::{ - AppendVec, DynMessage, ElementSplice, MessageResult, Mut, SuperElement, View, ViewElement, - ViewId, ViewMarker, ViewPathTracker, ViewSequence, + AnyElement, AppendVec, DynMessage, ElementSplice, MessageResult, Mut, SuperElement, View, + ViewElement, ViewMarker, ViewSequence, }; -use crate::{AnyWidgetView, Pod, ViewCtx, WidgetView}; +use crate::{Pod, ViewCtx, WidgetView}; pub use masonry::widget::BoardParams; mod kurbo_shape; mod style_modifier; -pub use kurbo_shape::{AnyGraphicsView, GraphicsView}; +pub use kurbo_shape::{AnyBoardView, GraphicsView}; pub use style_modifier::{fill, stroke, transform, Fill, GraphicsExt, Stroke, Transform}; pub fn board>( @@ -121,6 +121,41 @@ impl SuperElement for BoardElement { } } +impl AnyElement for BoardElement { + fn replace_inner(mut this: Self::Mut<'_>, child: BoardElement) -> Self::Mut<'_> { + this.parent.remove_child(this.idx); + this.parent.insert_child(this.idx, child.element.inner); + this + } +} + +impl SuperElement> for BoardElement { + fn upcast(child: Pod) -> Self { + BoardElement { + element: child.inner.svg_boxed().into(), + } + } + + fn with_downcast_val( + mut this: Mut<'_, Self>, + f: impl FnOnce(Mut<'_, Pod>) -> R, + ) -> (Self::Mut<'_>, R) { + let r = { + let mut child = this.parent.child_mut(this.idx); + f(child.downcast()) + }; + (this, r) + } +} + +impl AnyElement> for BoardElement { + fn replace_inner(mut this: Self::Mut<'_>, child: Pod) -> Self::Mut<'_> { + this.parent.remove_child(this.idx); + this.parent.insert_child(this.idx, child.inner.svg_boxed()); + this + } +} + pub struct BoardElementMut<'w> { parent: WidgetMut<'w, widget::Board>, idx: usize, @@ -236,14 +271,14 @@ where } } -impl From> for AnyBoardChild +impl From> for Box> where State: 'static, Action: 'static, V: WidgetView, { fn from(value: PositionedView) -> Self { - AnyBoardChild::View(positioned(value.view.boxed(), value.params)) + Box::new(positioned(value.view, value.params)) } } @@ -318,12 +353,6 @@ where } } -/// A widget-type-erased positioned child [`View`], can be used within a [`Board`] [`View`] -pub enum AnyBoardChild { - View(PositionedView>, State, Action>), - Graphics(Box>), -} - impl PositionedView where State: 'static, @@ -331,166 +360,7 @@ where V: WidgetView, { /// Turns this [`BoardItem`] into an [`AnyBoardChild`] - pub fn into_any_board(self) -> AnyBoardChild { - AnyBoardChild::View(positioned(Box::new(self.view), self.params)) - } -} - -#[doc(hidden)] // Implementation detail, public because of trait visibility rules -pub struct AnyBoardChildState { - #[allow(clippy::type_complexity)] - inner: >, State, Action> as View< - State, - Action, - ViewCtx, - >>::ViewState, - - /// The generational id handling is essentially very similar to that of the `Option`, - /// where `None` would represent a Spacer, and `Some` a view - generation: u64, -} - -impl ViewMarker for AnyBoardChild {} -impl View for AnyBoardChild -where - State: 'static, - Action: 'static, -{ - type Element = BoardElement; - - type ViewState = AnyBoardChildState; - - fn build(&self, ctx: &mut ViewCtx) -> (Self::Element, Self::ViewState) { - let generation = 0; - let (element, view_state) = match self { - AnyBoardChild::View(view_item) => { - ctx.with_id(ViewId::new(generation), |ctx| view_item.build(ctx)) - } - AnyBoardChild::Graphics(shape_item) => { - let (element, state) = - ctx.with_id(ViewId::new(generation), |ctx| shape_item.build(ctx)); - ( - BoardElement { - element: element.inner.svg_boxed().into(), - }, - state, - ) - } - }; - ( - element, - AnyBoardChildState { - inner: view_state, - generation, - }, - ) - } - - fn rebuild<'el>( - &self, - prev: &Self, - view_state: &mut Self::ViewState, - ctx: &mut ViewCtx, - mut element: Mut<'el, Self::Element>, - ) -> Mut<'el, Self::Element> { - match (prev, self) { - (AnyBoardChild::View(prev), AnyBoardChild::View(this)) => ctx - .with_id(ViewId::new(view_state.generation), |ctx| { - this.rebuild(prev, &mut view_state.inner, ctx, element) - }), - (AnyBoardChild::Graphics(prev), AnyBoardChild::Graphics(this)) => { - { - let mut child = element.parent.child_mut(element.idx); - ctx.with_id(ViewId::new(view_state.generation), |ctx| { - this.rebuild(prev, &mut view_state.inner, ctx, child.downcast()) - }); - } - element - } - (AnyBoardChild::View(prev_view), AnyBoardChild::Graphics(new_shape)) => { - // Run teardown with the old path - ctx.with_id(ViewId::new(view_state.generation), |ctx| { - prev_view.teardown( - &mut view_state.inner, - ctx, - BoardElementMut { - parent: element.parent.reborrow_mut(), - idx: element.idx, - }, - ); - }); - element.parent.remove_child(element.idx); - view_state.generation = view_state.generation.wrapping_add(1); - let (child, child_state) = ctx.with_id(ViewId::new(view_state.generation), |ctx| { - new_shape.build(ctx) - }); - view_state.inner = child_state; - element - .parent - .insert_child(element.idx, child.inner.svg_boxed()); - element - } - (AnyBoardChild::Graphics(prev_shape), AnyBoardChild::View(new_view)) => { - // Run teardown with the old path - { - let mut child = element.parent.child_mut(element.idx); - ctx.with_id(ViewId::new(view_state.generation), |ctx| { - prev_shape.teardown(&mut view_state.inner, ctx, child.downcast()); - }); - } - element.parent.remove_child(element.idx); - view_state.generation = view_state.generation.wrapping_add(1); - let (view_element, child_state) = ctx - .with_id(ViewId::new(view_state.generation), |ctx| { - new_view.build(ctx) - }); - view_state.inner = child_state; - element - .parent - .insert_child(element.idx, view_element.element.inner); - element - } - } - } - - fn teardown( - &self, - view_state: &mut Self::ViewState, - ctx: &mut ViewCtx, - mut element: Mut<'_, Self::Element>, - ) { - match self { - AnyBoardChild::View(view_item) => { - view_item.teardown(&mut view_state.inner, ctx, element); - } - AnyBoardChild::Graphics(shape_item) => { - let mut child = element.parent.child_mut(element.idx); - shape_item.teardown(&mut view_state.inner, ctx, child.downcast()); - } - } - } - - fn message( - &self, - view_state: &mut Self::ViewState, - id_path: &[xilem_core::ViewId], - message: DynMessage, - app_state: &mut State, - ) -> MessageResult { - let (start, rest) = id_path - .split_first() - .expect("Id path has elements for AnyBoardChild"); - if start.routing_id() != view_state.generation { - // The message was sent to a previous edition of the inner value - return MessageResult::Stale(message); - } - match self { - AnyBoardChild::View(view_item) => { - view_item.message(&mut view_state.inner, rest, message, app_state) - } - AnyBoardChild::Graphics(shape_item) => { - shape_item.message(&mut view_state.inner, rest, message, app_state) - } - } + pub fn into_any_board(self) -> Box> { + self.into() } } diff --git a/xilem/src/view/board/kurbo_shape.rs b/xilem/src/view/board/kurbo_shape.rs index 20475720d..9e4ea19b6 100644 --- a/xilem/src/view/board/kurbo_shape.rs +++ b/xilem/src/view/board/kurbo_shape.rs @@ -3,21 +3,23 @@ //! Implementation of the View trait for various kurbo shapes. -use masonry::widget::KurboShape; +use masonry::widget::{KurboShape, SvgElement}; use vello::kurbo; use xilem_core::{AnyElement, AnyView, DynMessage, MessageResult, Mut, OrphanView, SuperElement}; use crate::{Pod, ViewCtx, WidgetView}; -pub trait GraphicsView: WidgetView {} +use super::BoardElement; + +pub trait GraphicsView: WidgetView {} impl GraphicsView for V where - V: WidgetView + Send + Sync + V: WidgetView + Send + Sync { } -pub type AnyGraphicsView = - dyn AnyView> + Send + Sync; +pub type AnyBoardView = + dyn AnyView + Send + Sync; impl SuperElement> for Pod { fn upcast(child: Pod) -> Self { diff --git a/xilem/src/view/board/style_modifier.rs b/xilem/src/view/board/style_modifier.rs index 402d9cce9..510da292c 100644 --- a/xilem/src/view/board/style_modifier.rs +++ b/xilem/src/view/board/style_modifier.rs @@ -12,7 +12,7 @@ use xilem_core::{AnyView, DynMessage, MessageResult, Mut, View, ViewId, ViewMark use crate::{Pod, ViewCtx}; -use super::{AnyBoardChild, GraphicsView}; +use super::{AnyBoardView, BoardElement, GraphicsView}; pub struct Transform { child: V, @@ -100,11 +100,11 @@ pub trait GraphicsExt: GraphicsView + Sized { stroke(self, brush, style) } - fn into_any_board(self) -> AnyBoardChild + fn into_any_board(self) -> Box> where - Self: AnyView> + Send + Sync + 'static, + Self: AnyView + Send + Sync + 'static, { - AnyBoardChild::Graphics(Box::new(self)) + Box::new(self) } } @@ -118,7 +118,7 @@ impl View for Transform, + V: GraphicsView, { type ViewState = V::ViewState; type Element = Pod; @@ -173,7 +173,7 @@ impl View for Fill where State: 'static, Action: 'static, - V: GraphicsView, + V: GraphicsView, { type ViewState = V::ViewState; type Element = Pod; @@ -244,7 +244,7 @@ impl View for Stroke where State: 'static, Action: 'static, - V: GraphicsView, + V: GraphicsView, { type ViewState = V::ViewState; type Element = Pod; From eec3b5323908fb897377a2a664abbb90d824c0ef Mon Sep 17 00:00:00 2001 From: Muhammad Ragib Hasin Date: Wed, 18 Sep 2024 12:09:42 +0600 Subject: [PATCH 08/10] Fix typos --- masonry/src/widget/board.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/masonry/src/widget/board.rs b/masonry/src/widget/board.rs index 66bae8a01..cf139fabd 100644 --- a/masonry/src/widget/board.rs +++ b/masonry/src/widget/board.rs @@ -77,11 +77,11 @@ impl SvgElement for KurboShape { } fn set_origin(&mut self, _: Point) { - panic!("a shape doens not support setting its origin after creation") + panic!("a shape doesn't not support setting its origin after creation") } fn set_size(&mut self, _: Size) { - panic!("a shape doens not support setting its size after creation") + panic!("a shape doesn't not support setting its size after creation") } } From dcb6255dc0c2d41a933b7ecb052fd03d67ca6010 Mon Sep 17 00:00:00 2001 From: Muhammad Ragib Hasin Date: Wed, 18 Sep 2024 15:34:57 +0600 Subject: [PATCH 09/10] Rearrange code as @PoignardAzur suggested --- masonry/src/widget/board.rs | 722 +++++------------- masonry/src/widget/kurbo.rs | 406 ++++++++++ masonry/src/widget/mod.rs | 4 +- ...get__kurbo__tests__kurbo_shape_circle.png} | 0 xilem/src/view/board.rs | 75 +- xilem/src/view/board/kurbo_shape.rs | 28 +- xilem/src/view/board/style_modifier.rs | 34 +- 7 files changed, 627 insertions(+), 642 deletions(-) create mode 100644 masonry/src/widget/kurbo.rs rename masonry/src/widget/screenshots/{masonry__widget__board__tests__kurbo_shape_circle.png => masonry__widget__kurbo__tests__kurbo_shape_circle.png} (100%) diff --git a/masonry/src/widget/board.rs b/masonry/src/widget/board.rs index cf139fabd..ed13cb0fb 100644 --- a/masonry/src/widget/board.rs +++ b/masonry/src/widget/board.rs @@ -1,18 +1,13 @@ // Copyright 2018 the Xilem Authors and the Druid Authors // SPDX-License-Identifier: Apache-2.0 -//! A widget that arranges its children in a one-dimensional array. +//! A widget that arranges its children in fixed positions. use std::ops::{Deref as _, DerefMut as _}; use accesskit::Role; use smallvec::SmallVec; -use tracing::{trace_span, warn, Span}; -use vello::kurbo::{ - self, Affine, Arc, BezPath, Circle, CircleSegment, CubicBez, Ellipse, Line, PathEl, PathSeg, - QuadBez, RoundedRect, Shape as _, Stroke, -}; -use vello::peniko::{Brush, Fill}; +use tracing::{trace_span, Span}; use vello::Scene; use crate::widget::WidgetMut; @@ -33,151 +28,86 @@ pub struct BoardParams { pub size: Size, } -pub struct KurboShape { - shape: ConcreteShape, - transform: Affine, - fill_mode: Fill, - fill_brush: Brush, - fill_brush_transform: Option, - stroke_style: Stroke, - stroke_brush: Brush, - stroke_brush_transform: Option, -} - -#[derive(Debug, Clone, PartialEq)] -pub enum ConcreteShape { - PathSeg(PathSeg), - Arc(Arc), - BezPath(BezPath), - Circle(Circle), - CircleSegment(CircleSegment), - CubicBez(CubicBez), - Ellipse(Ellipse), - Line(Line), - QuadBez(QuadBez), - Rect(Rect), - RoundedRect(RoundedRect), +/// Wrapper of a regular widget for use in [`Board`] +pub struct PositionedElement { + inner: W, + params: BoardParams, } +/// A trait representing a widget which knows its origin and size. pub trait SvgElement: Widget { - fn origin(&self) -> Point; // relative to parents transform + // Origin of the widget relative to its parent + fn origin(&self) -> Point; + // Size of the widget fn size(&self) -> Size; - fn set_origin(&mut self, origin: Point); // relative to parents transform + // Sets the origin of the widget relative to its parent + fn set_origin(&mut self, origin: Point); + // Sets the size of the widget fn set_size(&mut self, size: Size); } -impl SvgElement for KurboShape { - fn origin(&self) -> Point { - self.shape.bounding_box().origin() +// --- MARK: IMPL BOARD --- +impl Board { + /// Create a new empty Board. + pub fn new() -> Self { + Board { + children: Vec::new(), + } } - fn size(&self) -> Size { - self.shape.bounding_box().size() + /// Builder-style method to add a positioned child to the container. + pub fn with_child_pod(mut self, child: WidgetPod>) -> Self { + self.children.push(child); + self } - fn set_origin(&mut self, _: Point) { - panic!("a shape doesn't not support setting its origin after creation") + pub fn len(&self) -> usize { + self.children.len() } - fn set_size(&mut self, _: Size) { - panic!("a shape doesn't not support setting its size after creation") + pub fn is_empty(&self) -> bool { + self.len() == 0 } } -impl SvgElement for Box { - fn origin(&self) -> Point { - self.deref().origin() - } - - fn size(&self) -> Size { - self.deref().size() - } - - fn set_origin(&mut self, origin: Point) { - self.deref_mut().set_origin(origin); - } - - fn set_size(&mut self, size: Size) { - self.deref_mut().set_size(size); +impl Default for Board { + fn default() -> Self { + Self::new() } } -impl Widget for Box { - fn on_pointer_event(&mut self, ctx: &mut EventCtx, event: &PointerEvent) { - self.deref_mut().on_pointer_event(ctx, event); - } - - fn on_text_event(&mut self, ctx: &mut EventCtx, event: &TextEvent) { - self.deref_mut().on_text_event(ctx, event); - } - - fn on_access_event(&mut self, ctx: &mut EventCtx, event: &AccessEvent) { - self.deref_mut().on_access_event(ctx, event); - } - - fn on_status_change(&mut self, ctx: &mut LifeCycleCtx, event: &StatusChange) { - self.deref_mut().on_status_change(ctx, event); - } - - fn lifecycle(&mut self, ctx: &mut LifeCycleCtx, event: &LifeCycle) { - self.deref_mut().lifecycle(ctx, event); - } - - fn layout(&mut self, ctx: &mut LayoutCtx, bc: &BoxConstraints) -> Size { - self.deref_mut().layout(ctx, bc) - } - - fn compose(&mut self, ctx: &mut crate::ComposeCtx) { - self.deref_mut().compose(ctx); - } - - fn paint(&mut self, ctx: &mut PaintCtx, scene: &mut Scene) { - self.deref_mut().paint(ctx, scene); - } - - fn accessibility_role(&self) -> Role { - self.deref().accessibility_role() - } - - fn accessibility(&mut self, ctx: &mut AccessCtx) { - self.deref_mut().accessibility(ctx); - } - - fn type_name(&self) -> &'static str { - self.deref().type_name() - } - - fn short_type_name(&self) -> &'static str { - self.deref().short_type_name() - } - - fn children_ids(&self) -> SmallVec<[WidgetId; 16]> { - self.deref().children_ids() - } - - fn skip_pointer(&self) -> bool { - self.deref().skip_pointer() +// --- MARK: WIDGETMUT--- +impl<'a> WidgetMut<'a, Board> { + /// Add a positioned child widget. + pub fn add_child(&mut self, child: impl SvgElement) { + self.widget.children.push(WidgetPod::new(Box::new(child))); + self.ctx.children_changed(); } - fn make_trace_span(&self) -> Span { - self.deref().make_trace_span() + pub fn insert_child(&mut self, idx: usize, child: WidgetPod>) { + self.widget.children.insert(idx, child); + self.ctx.children_changed(); } - fn get_debug_text(&self) -> Option { - self.deref().get_debug_text() + pub fn remove_child(&mut self, idx: usize) { + let widget = self.widget.children.remove(idx); + self.ctx.remove_child(widget); + self.ctx.request_layout(); } - fn get_cursor(&self) -> cursor_icon::CursorIcon { - self.deref().get_cursor() + pub fn child_mut(&mut self, idx: usize) -> WidgetMut<'_, Box> { + self.ctx.get_mut(&mut self.widget.children[idx]) } - fn as_any(&self) -> &dyn std::any::Any { - self.deref().as_any() - } + pub fn clear(&mut self) { + if !self.widget.children.is_empty() { + self.ctx.request_layout(); - fn as_mut_any(&mut self) -> &mut dyn std::any::Any { - self.deref_mut().as_mut_any() + for child in self.widget.children.drain(..) { + self.ctx.remove_child(child); + } + } } } @@ -237,58 +167,52 @@ impl<'a> WidgetMut<'a, Box> { } } -pub struct PositionedElement { - inner: W, - params: BoardParams, -} +// --- MARK: IMPL WIDGET BOARD --- +impl Widget for Board { + fn on_pointer_event(&mut self, _ctx: &mut EventCtx, _event: &PointerEvent) {} + fn on_text_event(&mut self, _ctx: &mut EventCtx, _event: &TextEvent) {} + fn on_access_event(&mut self, _ctx: &mut EventCtx, _event: &AccessEvent) {} + fn on_status_change(&mut self, _ctx: &mut LifeCycleCtx, _event: &StatusChange) {} -impl PositionedElement { - pub fn new(widget: W, params: impl Into) -> Self { - PositionedElement { - inner: widget, - params: params.into(), + fn lifecycle(&mut self, ctx: &mut LifeCycleCtx, event: &LifeCycle) { + for child in &mut self.children { + child.lifecycle(ctx, event); } } -} -impl WidgetPod { - pub fn positioned(self, params: impl Into) -> WidgetPod> { - let id = self.id(); - WidgetPod::new_with_id( - Box::new(PositionedElement { - inner: self.inner().unwrap(), - params: params.into(), - }), - id, - ) - } -} + fn layout(&mut self, ctx: &mut LayoutCtx, bc: &BoxConstraints) -> Size { + bc.debug_check("Board"); -impl WidgetPod { - pub fn svg_boxed(self) -> WidgetPod> { - let id = self.id(); - WidgetPod::new_with_id(Box::new(self.inner().unwrap()), id) - } -} + for child in &mut self.children { + let (size, origin) = { + let child_ref = ctx.get_raw_ref(child); + (child_ref.widget().size(), child_ref.widget().origin()) + }; + ctx.run_layout(child, &BoxConstraints::tight(size)); + ctx.place_child(child, origin); + } -impl SvgElement for PositionedElement { - fn origin(&self) -> Point { - self.params.origin + bc.max() } - fn size(&self) -> Size { - self.params.size + fn paint(&mut self, _ctx: &mut PaintCtx, _scene: &mut Scene) {} + + fn accessibility_role(&self) -> Role { + Role::GenericContainer } - fn set_origin(&mut self, origin: Point) { - self.params.origin = origin; + fn accessibility(&mut self, _ctx: &mut AccessCtx) {} + + fn children_ids(&self) -> SmallVec<[WidgetId; 16]> { + self.children.iter().map(|child| child.id()).collect() } - fn set_size(&mut self, size: Size) { - self.params.size = size; + fn make_trace_span(&self) -> Span { + trace_span!("Board") } } +// --- MARK: IMPL WIDGET POSITIONEDELEMENT --- impl Widget for PositionedElement { fn on_status_change(&mut self, ctx: &mut LifeCycleCtx, event: &StatusChange) { self.inner.on_status_change(ctx, event); @@ -347,292 +271,118 @@ impl Widget for PositionedElement { } } -// --- MARK: IMPL BOARD --- -impl Board { - /// Create a new Board oriented with viewport origin set to (0, 0) and scale (1, 1). - pub fn new() -> Self { - Board { - children: Vec::new(), - } - } - - /// Builder-style method to add a positioned child to the container. - pub fn with_child_pod(mut self, child: WidgetPod>) -> Self { - self.children.push(child); - self - } - - pub fn len(&self) -> usize { - self.children.len() - } - - pub fn is_empty(&self) -> bool { - self.len() == 0 - } -} - -impl Default for Board { - fn default() -> Self { - Self::new() - } -} - -// --- MARK: IMPL SHAPE --- -impl KurboShape { - pub fn new(shape: impl Into) -> Self { - KurboShape { - shape: shape.into(), - transform: Default::default(), - fill_mode: Fill::NonZero, - fill_brush: Default::default(), - fill_brush_transform: Default::default(), - stroke_style: Default::default(), - stroke_brush: Default::default(), - stroke_brush_transform: Default::default(), - } - } - - pub fn shape(&self) -> &ConcreteShape { - &self.shape - } - - pub fn set_transform(&mut self, transform: Affine) { - self.transform = transform; - } - - pub fn set_fill_mode(&mut self, fill_mode: Fill) { - self.fill_mode = fill_mode; - } - - pub fn set_fill_brush(&mut self, fill_brush: Brush) { - self.fill_brush = fill_brush; - } - - pub fn set_fill_brush_transform(&mut self, fill_brush_transform: Option) { - self.fill_brush_transform = fill_brush_transform; +impl SvgElement for PositionedElement { + fn origin(&self) -> Point { + self.params.origin } - pub fn set_stroke_style(&mut self, stroke_style: Stroke) { - self.stroke_style = stroke_style; + fn size(&self) -> Size { + self.params.size } - pub fn set_stroke_brush(&mut self, stroke_brush: Brush) { - self.stroke_brush = stroke_brush; + fn set_origin(&mut self, origin: Point) { + self.params.origin = origin; } - pub fn set_stroke_brush_transform(&mut self, stroke_brush_transform: Option) { - self.stroke_brush_transform = stroke_brush_transform; + fn set_size(&mut self, size: Size) { + self.params.size = size; } } -// --- MARK: WIDGETMUT--- -impl<'a> WidgetMut<'a, Board> { - /// Add a positioned child widget. - pub fn add_child(&mut self, child: impl SvgElement) { - self.widget.children.push(WidgetPod::new(Box::new(child))); - self.ctx.children_changed(); +// --- MARK: IMPL WIDGET SVGELEMENT --- +impl Widget for Box { + fn on_pointer_event(&mut self, ctx: &mut EventCtx, event: &PointerEvent) { + self.deref_mut().on_pointer_event(ctx, event); } - pub fn insert_child(&mut self, idx: usize, child: WidgetPod>) { - self.widget.children.insert(idx, child); - self.ctx.children_changed(); + fn on_text_event(&mut self, ctx: &mut EventCtx, event: &TextEvent) { + self.deref_mut().on_text_event(ctx, event); } - pub fn remove_child(&mut self, idx: usize) { - let widget = self.widget.children.remove(idx); - self.ctx.remove_child(widget); - self.ctx.request_layout(); + fn on_access_event(&mut self, ctx: &mut EventCtx, event: &AccessEvent) { + self.deref_mut().on_access_event(ctx, event); } - // FIXME - Remove Box - pub fn child_mut(&mut self, idx: usize) -> WidgetMut<'_, Box> { - self.ctx.get_mut(&mut self.widget.children[idx]) + fn on_status_change(&mut self, ctx: &mut LifeCycleCtx, event: &StatusChange) { + self.deref_mut().on_status_change(ctx, event); } - pub fn clear(&mut self) { - if !self.widget.children.is_empty() { - self.ctx.request_layout(); - - for child in self.widget.children.drain(..) { - self.ctx.remove_child(child); - } - } + fn lifecycle(&mut self, ctx: &mut LifeCycleCtx, event: &LifeCycle) { + self.deref_mut().lifecycle(ctx, event); } -} -impl<'a> WidgetMut<'a, KurboShape> { - pub fn update_from(&mut self, shape: &KurboShape) { - if self.widget.shape != shape.shape { - self.set_shape(shape.shape.clone()); - } - if self.widget.transform != shape.transform { - self.set_transform(shape.transform); - } - if self.widget.fill_mode != shape.fill_mode { - self.set_fill_mode(shape.fill_mode); - } - if self.widget.fill_brush != shape.fill_brush { - self.set_fill_brush(shape.fill_brush.clone()); - } - if self.widget.fill_brush_transform != shape.fill_brush_transform { - self.set_fill_brush_transform(shape.fill_brush_transform); - } - if self.widget.stroke_style.width != shape.stroke_style.width - || self.widget.stroke_style.join != shape.stroke_style.join - || self.widget.stroke_style.miter_limit != shape.stroke_style.miter_limit - || self.widget.stroke_style.start_cap != shape.stroke_style.start_cap - || self.widget.stroke_style.end_cap != shape.stroke_style.end_cap - || self.widget.stroke_style.dash_pattern != shape.stroke_style.dash_pattern - || self.widget.stroke_style.dash_offset != shape.stroke_style.dash_offset - { - self.set_stroke_style(shape.stroke_style.clone()); - } - if self.widget.stroke_brush != shape.stroke_brush { - self.set_stroke_brush(shape.stroke_brush.clone()); - } - if self.widget.stroke_brush_transform != shape.stroke_brush_transform { - self.set_stroke_brush_transform(shape.stroke_brush_transform); - } + fn layout(&mut self, ctx: &mut LayoutCtx, bc: &BoxConstraints) -> Size { + self.deref_mut().layout(ctx, bc) } - pub fn set_shape(&mut self, shape: ConcreteShape) { - self.widget.shape = shape; - self.ctx.request_layout(); - self.ctx.request_paint(); - self.ctx.request_accessibility_update(); + fn compose(&mut self, ctx: &mut crate::ComposeCtx) { + self.deref_mut().compose(ctx); } - pub fn set_transform(&mut self, transform: Affine) { - self.widget.transform = transform; - self.ctx.request_paint(); + fn paint(&mut self, ctx: &mut PaintCtx, scene: &mut Scene) { + self.deref_mut().paint(ctx, scene); } - pub fn set_fill_mode(&mut self, fill_mode: Fill) { - self.widget.fill_mode = fill_mode; - self.ctx.request_paint(); + fn accessibility_role(&self) -> Role { + self.deref().accessibility_role() } - pub fn set_fill_brush(&mut self, fill_brush: Brush) { - self.widget.fill_brush = fill_brush; - self.ctx.request_paint(); + fn accessibility(&mut self, ctx: &mut AccessCtx) { + self.deref_mut().accessibility(ctx); } - pub fn set_fill_brush_transform(&mut self, fill_brush_transform: Option) { - self.widget.fill_brush_transform = fill_brush_transform; - self.ctx.request_paint(); + fn type_name(&self) -> &'static str { + self.deref().type_name() } - pub fn set_stroke_style(&mut self, stroke_style: Stroke) { - self.widget.stroke_style = stroke_style; - self.ctx.request_paint(); + fn short_type_name(&self) -> &'static str { + self.deref().short_type_name() } - pub fn set_stroke_brush(&mut self, stroke_brush: Brush) { - self.widget.stroke_brush = stroke_brush; - self.ctx.request_paint(); + fn children_ids(&self) -> SmallVec<[WidgetId; 16]> { + self.deref().children_ids() } - pub fn set_stroke_brush_transform(&mut self, stroke_brush_transform: Option) { - self.widget.stroke_brush_transform = stroke_brush_transform; - self.ctx.request_paint(); + fn skip_pointer(&self) -> bool { + self.deref().skip_pointer() } -} - -// --- MARK: IMPL WIDGET --- -impl Widget for Board { - fn on_pointer_event(&mut self, _ctx: &mut EventCtx, _event: &PointerEvent) {} - - fn on_text_event(&mut self, _ctx: &mut EventCtx, _event: &TextEvent) {} - - fn on_access_event(&mut self, _ctx: &mut EventCtx, _event: &AccessEvent) {} - fn on_status_change(&mut self, _ctx: &mut LifeCycleCtx, _event: &StatusChange) {} - - fn lifecycle(&mut self, ctx: &mut LifeCycleCtx, event: &LifeCycle) { - for child in &mut self.children { - child.lifecycle(ctx, event); - } + fn make_trace_span(&self) -> Span { + self.deref().make_trace_span() } - fn layout(&mut self, ctx: &mut LayoutCtx, bc: &BoxConstraints) -> Size { - bc.debug_check("Board"); - - for child in &mut self.children { - let (size, origin) = { - let child_ref = ctx.get_raw_ref(child); - (child_ref.widget().size(), child_ref.widget().origin()) - }; - ctx.run_layout(child, &BoxConstraints::tight(size)); - ctx.place_child(child, origin); - } - - bc.max() + fn get_debug_text(&self) -> Option { + self.deref().get_debug_text() } - fn paint(&mut self, _ctx: &mut PaintCtx, _scene: &mut Scene) {} - - fn accessibility_role(&self) -> Role { - Role::GenericContainer + fn get_cursor(&self) -> cursor_icon::CursorIcon { + self.deref().get_cursor() } - fn accessibility(&mut self, _ctx: &mut AccessCtx) {} - - fn children_ids(&self) -> SmallVec<[WidgetId; 16]> { - self.children.iter().map(|child| child.id()).collect() + fn as_any(&self) -> &dyn std::any::Any { + self.deref().as_any() } - fn make_trace_span(&self) -> Span { - trace_span!("Board") + fn as_mut_any(&mut self) -> &mut dyn std::any::Any { + self.deref_mut().as_mut_any() } } -impl Widget for KurboShape { - fn on_pointer_event(&mut self, _ctx: &mut EventCtx, _event: &PointerEvent) {} - fn on_text_event(&mut self, _ctx: &mut EventCtx, _event: &TextEvent) {} - fn on_access_event(&mut self, _ctx: &mut EventCtx, _event: &AccessEvent) {} - fn on_status_change(&mut self, _ctx: &mut LifeCycleCtx, _event: &StatusChange) {} - fn lifecycle(&mut self, _ctx: &mut LifeCycleCtx, _event: &LifeCycle) {} - - fn layout(&mut self, _ctx: &mut LayoutCtx, bc: &BoxConstraints) -> Size { - let size = self.shape.bounding_box().size(); - if !bc.contains(size) { - warn!("The shape is oversized"); - } - size - } - - fn paint(&mut self, _ctx: &mut PaintCtx, scene: &mut Scene) { - let transform = self - .transform - .then_translate(-self.shape.bounding_box().origin().to_vec2()); - scene.fill( - self.fill_mode, - transform, - &self.fill_brush, - self.fill_brush_transform, - &self.shape, - ); - scene.stroke( - &self.stroke_style, - transform, - &self.stroke_brush, - self.stroke_brush_transform, - &self.shape, - ); +impl SvgElement for Box { + fn origin(&self) -> Point { + self.deref().origin() } - fn accessibility_role(&self) -> Role { - Role::GraphicsSymbol + fn size(&self) -> Size { + self.deref().size() } - fn accessibility(&mut self, _ctx: &mut AccessCtx) {} - - fn children_ids(&self) -> SmallVec<[WidgetId; 16]> { - SmallVec::new() + fn set_origin(&mut self, origin: Point) { + self.deref_mut().set_origin(origin); } - fn make_trace_span(&self) -> Span { - trace_span!("Shape") + fn set_size(&mut self, size: Size) { + self.deref_mut().set_size(size); } } @@ -656,171 +406,41 @@ impl From for BoardParams { } } -macro_rules! for_all_variants { - ($self:expr; $i:ident => $e:expr) => { - match $self { - Self::PathSeg($i) => $e, - Self::Arc($i) => $e, - Self::BezPath($i) => $e, - Self::Circle($i) => $e, - Self::CircleSegment($i) => $e, - Self::CubicBez($i) => $e, - Self::Ellipse($i) => $e, - Self::Line($i) => $e, - Self::QuadBez($i) => $e, - Self::Rect($i) => $e, - Self::RoundedRect($i) => $e, - } - }; -} - -impl kurbo::Shape for ConcreteShape { - type PathElementsIter<'iter> = PathElementsIter<'iter>; - - fn path_elements(&self, tolerance: f64) -> Self::PathElementsIter<'_> { - match self { - Self::PathSeg(i) => PathElementsIter::PathSeg(i.path_elements(tolerance)), - Self::Arc(i) => PathElementsIter::Arc(i.path_elements(tolerance)), - Self::BezPath(i) => PathElementsIter::BezPath(i.path_elements(tolerance)), - Self::Circle(i) => PathElementsIter::Circle(i.path_elements(tolerance)), - Self::CircleSegment(i) => PathElementsIter::CircleSegment(i.path_elements(tolerance)), - Self::CubicBez(i) => PathElementsIter::CubicBez(i.path_elements(tolerance)), - Self::Ellipse(i) => PathElementsIter::Ellipse(i.path_elements(tolerance)), - Self::Line(i) => PathElementsIter::Line(i.path_elements(tolerance)), - Self::QuadBez(i) => PathElementsIter::QuadBez(i.path_elements(tolerance)), - Self::Rect(i) => PathElementsIter::Rect(i.path_elements(tolerance)), - Self::RoundedRect(i) => PathElementsIter::RoundedRect(i.path_elements(tolerance)), - } - } - - fn area(&self) -> f64 { - for_all_variants!(self; i => i.area()) - } - - fn perimeter(&self, accuracy: f64) -> f64 { - for_all_variants!(self; i => i.perimeter(accuracy)) - } - - fn winding(&self, pt: Point) -> i32 { - for_all_variants!(self; i => i.winding(pt)) - } - - fn bounding_box(&self) -> Rect { - for_all_variants!(self; i => i.bounding_box()) - } - - fn to_path(&self, tolerance: f64) -> BezPath { - for_all_variants!(self; i => i.to_path(tolerance)) - } - - fn into_path(self, tolerance: f64) -> BezPath { - for_all_variants!(self; i => i.into_path(tolerance)) - } - - fn contains(&self, pt: Point) -> bool { - for_all_variants!(self; i => i.contains(pt)) - } - - fn as_line(&self) -> Option { - for_all_variants!(self; i => i.as_line()) - } - - fn as_rect(&self) -> Option { - for_all_variants!(self; i => i.as_rect()) - } - - fn as_rounded_rect(&self) -> Option { - for_all_variants!(self; i => i.as_rounded_rect()) - } - - fn as_circle(&self) -> Option { - for_all_variants!(self; i => i.as_circle()) - } - - fn as_path_slice(&self) -> Option<&[PathEl]> { - for_all_variants!(self; i => i.as_path_slice()) - } -} - -macro_rules! impl_from_shape { - ($t:ident) => { - impl From for ConcreteShape { - fn from(value: kurbo::$t) -> Self { - ConcreteShape::$t(value) - } - } - }; -} - -impl_from_shape!(PathSeg); -impl_from_shape!(Arc); -impl_from_shape!(BezPath); -impl_from_shape!(Circle); -impl_from_shape!(CircleSegment); -impl_from_shape!(CubicBez); -impl_from_shape!(Ellipse); -impl_from_shape!(Line); -impl_from_shape!(QuadBez); -impl_from_shape!(Rect); -impl_from_shape!(RoundedRect); - -pub enum PathElementsIter<'i> { - PathSeg(::PathElementsIter<'i>), - Arc(::PathElementsIter<'i>), - BezPath(::PathElementsIter<'i>), - Circle(::PathElementsIter<'i>), - CircleSegment(::PathElementsIter<'i>), - CubicBez(::PathElementsIter<'i>), - Ellipse(::PathElementsIter<'i>), - Line(::PathElementsIter<'i>), - QuadBez(::PathElementsIter<'i>), - Rect(::PathElementsIter<'i>), - RoundedRect(::PathElementsIter<'i>), -} - -impl<'i> Iterator for PathElementsIter<'i> { - type Item = PathEl; - - fn next(&mut self) -> Option { - for_all_variants!(self; i => i.next()) +impl WidgetPod { + pub fn positioned(self, params: impl Into) -> WidgetPod> { + let id = self.id(); + WidgetPod::new_with_id( + Box::new(PositionedElement { + inner: self.inner().unwrap(), + params: params.into(), + }), + id, + ) } } // --- MARK: TESTS --- #[cfg(test)] mod tests { - use vello::{kurbo::Circle, peniko::Brush}; + use vello::kurbo::{Circle, Stroke}; + use vello::peniko::Brush; use super::*; use crate::assert_render_snapshot; use crate::testing::TestHarness; - use crate::widget::Button; - - #[test] - fn kurbo_shape_circle() { - let mut widget = KurboShape::new(Circle::new((50., 50.), 30.)); - widget.set_fill_brush(Brush::Solid(vello::peniko::Color::CHARTREUSE)); - widget.set_stroke_style(Stroke::new(2.).with_dashes(0., [2., 1.])); - widget.set_stroke_brush(Brush::Solid(vello::peniko::Color::PALE_VIOLET_RED)); - - let mut harness = TestHarness::create(widget); - - assert_render_snapshot!(harness, "kurbo_shape_circle"); - } + use crate::widget::{Button, KurboShape}; #[test] fn board_absolute_placement_snapshots() { - let widget = Board::new() - .with_child_pod(WidgetPod::new(Box::new(PositionedElement::new( - Button::new("hello"), - Rect::new(10., 10., 60., 40.), - )))) - .with_child_pod(WidgetPod::new(Box::new(PositionedElement::new( - Button::new("hello"), - Rect::new(30., 30., 80., 60.), - )))); - - let mut harness = TestHarness::create(widget); + let board = Board::new() + .with_child_pod( + WidgetPod::new(Button::new("hello")).positioned(Rect::new(10., 10., 60., 40.)), + ) + .with_child_pod( + WidgetPod::new(Button::new("world")).positioned(Rect::new(30., 30., 80., 60.)), + ); + + let mut harness = TestHarness::create(board); assert_render_snapshot!(harness, "absolute_placement"); } @@ -831,17 +451,15 @@ mod tests { shape.set_fill_brush(Brush::Solid(vello::peniko::Color::NAVY)); shape.set_stroke_style(Stroke::new(2.).with_dashes(0., [2., 1.])); shape.set_stroke_brush(Brush::Solid(vello::peniko::Color::PALE_VIOLET_RED)); - let widget = Board::new() - .with_child_pod(WidgetPod::new(Box::new(PositionedElement::new( - Button::new("hello"), - Rect::new(10., 10., 60., 40.), - )))) + + let board = Board::new() + .with_child_pod( + WidgetPod::new(Button::new("hello")).positioned(Rect::new(10., 10., 60., 40.)), + ) .with_child_pod(WidgetPod::new(Box::new(shape))); - let mut harness = TestHarness::create(widget); + let mut harness = TestHarness::create(board); assert_render_snapshot!(harness, "shape_placement"); } - - // TODO: add test for KurboShape in Flex } diff --git a/masonry/src/widget/kurbo.rs b/masonry/src/widget/kurbo.rs new file mode 100644 index 000000000..6d0799d2b --- /dev/null +++ b/masonry/src/widget/kurbo.rs @@ -0,0 +1,406 @@ +// Copyright 2024 the Xilem Authors +// SPDX-License-Identifier: Apache-2.0 + +//! A widget representing a `kurbo::Shape`. + +use accesskit::Role; +use smallvec::SmallVec; +use tracing::{trace_span, warn, Span}; +use vello::kurbo::{ + self, Affine, Arc, BezPath, Circle, CircleSegment, CubicBez, Ellipse, Line, PathEl, PathSeg, + QuadBez, RoundedRect, Shape, Stroke, +}; +use vello::peniko::{Brush, Fill}; +use vello::Scene; + +use crate::widget::{SvgElement, WidgetMut, WidgetPod}; +use crate::{ + AccessCtx, AccessEvent, BoxConstraints, EventCtx, LayoutCtx, LifeCycle, LifeCycleCtx, PaintCtx, + Point, PointerEvent, Rect, Size, StatusChange, TextEvent, Widget, WidgetId, +}; + +/// A widget representing a `kurbo::Shape`. +pub struct KurboShape { + shape: ConcreteShape, + transform: Affine, + fill: Option, + stroke: Option, +} + +struct FillParams { + mode: Fill, + brush: Brush, + brush_transform: Option, +} + +#[derive(Default)] +struct StrokeParams { + style: Stroke, + brush: Brush, + brush_transform: Option, +} + +/// A concrete type for all built-in `kurbo::Shape`s. +// TODO: Adopt `kurbo::ConcreteShape` once https://github.com/linebender/kurbo/pull/331 merges +#[derive(Debug, Clone, PartialEq)] +pub enum ConcreteShape { + PathSeg(PathSeg), + Arc(Arc), + BezPath(BezPath), + Circle(Circle), + CircleSegment(CircleSegment), + CubicBez(CubicBez), + Ellipse(Ellipse), + Line(Line), + QuadBez(QuadBez), + Rect(Rect), + RoundedRect(RoundedRect), +} + +// --- MARK: IMPL KURBOSHAPE --- +impl KurboShape { + pub fn new(shape: impl Into) -> Self { + KurboShape { + shape: shape.into(), + transform: Default::default(), + fill: None, + stroke: None, + } + } + + pub fn shape(&self) -> &ConcreteShape { + &self.shape + } + + pub fn set_transform(&mut self, transform: Affine) { + self.transform = transform; + } + + pub fn set_fill_mode(&mut self, fill_mode: Fill) { + self.fill.get_or_insert_with(Default::default).mode = fill_mode; + } + + pub fn set_fill_brush(&mut self, fill_brush: Brush) { + self.fill.get_or_insert_with(Default::default).brush = fill_brush; + } + + pub fn set_fill_brush_transform(&mut self, fill_brush_transform: Option) { + self.fill + .get_or_insert_with(Default::default) + .brush_transform = fill_brush_transform; + } + + pub fn set_stroke_style(&mut self, stroke_style: Stroke) { + self.stroke.get_or_insert_with(Default::default).style = stroke_style; + } + + pub fn set_stroke_brush(&mut self, stroke_brush: Brush) { + self.stroke.get_or_insert_with(Default::default).brush = stroke_brush; + } + + pub fn set_stroke_brush_transform(&mut self, stroke_brush_transform: Option) { + self.stroke + .get_or_insert_with(Default::default) + .brush_transform = stroke_brush_transform; + } +} + +// MARK: WIDGETMUT +impl<'a> WidgetMut<'a, KurboShape> { + pub fn set_shape(&mut self, shape: ConcreteShape) { + self.widget.shape = shape; + self.ctx.request_layout(); + self.ctx.request_paint(); + self.ctx.request_accessibility_update(); + } + + pub fn set_transform(&mut self, transform: Affine) { + self.widget.transform = transform; + self.ctx.request_paint(); + } + + pub fn set_fill_mode(&mut self, fill_mode: Fill) { + self.widget.fill.get_or_insert_with(Default::default).mode = fill_mode; + self.ctx.request_paint(); + } + + pub fn set_fill_brush(&mut self, fill_brush: Brush) { + self.widget.fill.get_or_insert_with(Default::default).brush = fill_brush; + self.ctx.request_paint(); + } + + pub fn set_fill_brush_transform(&mut self, fill_brush_transform: Option) { + self.widget + .fill + .get_or_insert_with(Default::default) + .brush_transform = fill_brush_transform; + self.ctx.request_paint(); + } + + pub fn set_stroke_style(&mut self, stroke_style: Stroke) { + self.widget + .stroke + .get_or_insert_with(Default::default) + .style = stroke_style; + self.ctx.request_paint(); + } + + pub fn set_stroke_brush(&mut self, stroke_brush: Brush) { + self.widget + .stroke + .get_or_insert_with(Default::default) + .brush = stroke_brush; + self.ctx.request_paint(); + } + + pub fn set_stroke_brush_transform(&mut self, stroke_brush_transform: Option) { + self.widget + .stroke + .get_or_insert_with(Default::default) + .brush_transform = stroke_brush_transform; + self.ctx.request_paint(); + } +} + +// MARK: IMPL WIDGET +impl Widget for KurboShape { + fn on_pointer_event(&mut self, _ctx: &mut EventCtx, _event: &PointerEvent) {} + fn on_text_event(&mut self, _ctx: &mut EventCtx, _event: &TextEvent) {} + fn on_access_event(&mut self, _ctx: &mut EventCtx, _event: &AccessEvent) {} + fn on_status_change(&mut self, _ctx: &mut LifeCycleCtx, _event: &StatusChange) {} + fn lifecycle(&mut self, _ctx: &mut LifeCycleCtx, _event: &LifeCycle) {} + + fn layout(&mut self, _ctx: &mut LayoutCtx, bc: &BoxConstraints) -> Size { + let size = self.shape.bounding_box().size(); + if !bc.contains(size) { + warn!("The shape is oversized"); + } + size + } + + fn paint(&mut self, _ctx: &mut PaintCtx, scene: &mut Scene) { + let transform = self + .transform + .then_translate(-self.shape.bounding_box().origin().to_vec2()); + if let Some(FillParams { + mode, + brush, + brush_transform, + }) = &self.fill + { + scene.fill(*mode, transform, brush, *brush_transform, &self.shape); + } + if let Some(StrokeParams { + style, + brush, + brush_transform, + }) = &self.stroke + { + scene.stroke(style, transform, brush, *brush_transform, &self.shape); + } + } + + fn accessibility_role(&self) -> Role { + Role::GraphicsSymbol + } + + fn accessibility(&mut self, _ctx: &mut AccessCtx) {} + + fn children_ids(&self) -> SmallVec<[WidgetId; 16]> { + SmallVec::new() + } + + fn make_trace_span(&self) -> Span { + trace_span!("KurboShape") + } +} + +impl SvgElement for KurboShape { + fn origin(&self) -> Point { + self.shape.bounding_box().origin() + } + + fn size(&self) -> Size { + self.shape.bounding_box().size() + } + + fn set_origin(&mut self, _: Point) { + panic!("a shape does not support setting its origin after creation") + } + + fn set_size(&mut self, _: Size) { + panic!("a shape does not support setting its size after creation") + } +} + +// --- MARK: OTHER IMPLS --- +impl Default for FillParams { + fn default() -> Self { + Self { + mode: Fill::NonZero, + brush: Default::default(), + brush_transform: Default::default(), + } + } +} + +impl WidgetPod { + pub fn svg_boxed(self) -> WidgetPod> { + let id = self.id(); + WidgetPod::new_with_id(Box::new(self.inner().unwrap()), id) + } +} + +macro_rules! for_all_variants { + ($self:expr; $i:ident => $e:expr) => { + match $self { + Self::PathSeg($i) => $e, + Self::Arc($i) => $e, + Self::BezPath($i) => $e, + Self::Circle($i) => $e, + Self::CircleSegment($i) => $e, + Self::CubicBez($i) => $e, + Self::Ellipse($i) => $e, + Self::Line($i) => $e, + Self::QuadBez($i) => $e, + Self::Rect($i) => $e, + Self::RoundedRect($i) => $e, + } + }; +} + +impl Shape for ConcreteShape { + type PathElementsIter<'iter> = PathElementsIter<'iter>; + + fn path_elements(&self, tolerance: f64) -> Self::PathElementsIter<'_> { + match self { + Self::PathSeg(i) => PathElementsIter::PathSeg(i.path_elements(tolerance)), + Self::Arc(i) => PathElementsIter::Arc(i.path_elements(tolerance)), + Self::BezPath(i) => PathElementsIter::BezPath(i.path_elements(tolerance)), + Self::Circle(i) => PathElementsIter::Circle(i.path_elements(tolerance)), + Self::CircleSegment(i) => PathElementsIter::CircleSegment(i.path_elements(tolerance)), + Self::CubicBez(i) => PathElementsIter::CubicBez(i.path_elements(tolerance)), + Self::Ellipse(i) => PathElementsIter::Ellipse(i.path_elements(tolerance)), + Self::Line(i) => PathElementsIter::Line(i.path_elements(tolerance)), + Self::QuadBez(i) => PathElementsIter::QuadBez(i.path_elements(tolerance)), + Self::Rect(i) => PathElementsIter::Rect(i.path_elements(tolerance)), + Self::RoundedRect(i) => PathElementsIter::RoundedRect(i.path_elements(tolerance)), + } + } + + fn area(&self) -> f64 { + for_all_variants!(self; i => i.area()) + } + + fn perimeter(&self, accuracy: f64) -> f64 { + for_all_variants!(self; i => i.perimeter(accuracy)) + } + + fn winding(&self, pt: Point) -> i32 { + for_all_variants!(self; i => i.winding(pt)) + } + + fn bounding_box(&self) -> Rect { + for_all_variants!(self; i => i.bounding_box()) + } + + fn to_path(&self, tolerance: f64) -> BezPath { + for_all_variants!(self; i => i.to_path(tolerance)) + } + + fn into_path(self, tolerance: f64) -> BezPath { + for_all_variants!(self; i => i.into_path(tolerance)) + } + + fn contains(&self, pt: Point) -> bool { + for_all_variants!(self; i => i.contains(pt)) + } + + fn as_line(&self) -> Option { + for_all_variants!(self; i => i.as_line()) + } + + fn as_rect(&self) -> Option { + for_all_variants!(self; i => i.as_rect()) + } + + fn as_rounded_rect(&self) -> Option { + for_all_variants!(self; i => i.as_rounded_rect()) + } + + fn as_circle(&self) -> Option { + for_all_variants!(self; i => i.as_circle()) + } + + fn as_path_slice(&self) -> Option<&[PathEl]> { + for_all_variants!(self; i => i.as_path_slice()) + } +} + +macro_rules! impl_from_shape { + ($t:ident) => { + impl From for ConcreteShape { + fn from(value: kurbo::$t) -> Self { + ConcreteShape::$t(value) + } + } + }; +} + +impl_from_shape!(PathSeg); +impl_from_shape!(Arc); +impl_from_shape!(BezPath); +impl_from_shape!(Circle); +impl_from_shape!(CircleSegment); +impl_from_shape!(CubicBez); +impl_from_shape!(Ellipse); +impl_from_shape!(Line); +impl_from_shape!(QuadBez); +impl_from_shape!(Rect); +impl_from_shape!(RoundedRect); + +pub enum PathElementsIter<'i> { + PathSeg(::PathElementsIter<'i>), + Arc(::PathElementsIter<'i>), + BezPath(::PathElementsIter<'i>), + Circle(::PathElementsIter<'i>), + CircleSegment(::PathElementsIter<'i>), + CubicBez(::PathElementsIter<'i>), + Ellipse(::PathElementsIter<'i>), + Line(::PathElementsIter<'i>), + QuadBez(::PathElementsIter<'i>), + Rect(::PathElementsIter<'i>), + RoundedRect(::PathElementsIter<'i>), +} + +impl<'i> Iterator for PathElementsIter<'i> { + type Item = PathEl; + + fn next(&mut self) -> Option { + for_all_variants!(self; i => i.next()) + } +} + +// --- MARK: TESTS --- +#[cfg(test)] +mod tests { + use vello::{kurbo::Circle, peniko::Brush}; + + use super::*; + use crate::assert_render_snapshot; + use crate::testing::TestHarness; + + #[test] + fn kurbo_shape_circle() { + let mut widget = KurboShape::new(Circle::new((50., 50.), 30.)); + widget.set_fill_brush(Brush::Solid(vello::peniko::Color::CHARTREUSE)); + widget.set_stroke_style(Stroke::new(2.).with_dashes(0., [2., 1.])); + widget.set_stroke_brush(Brush::Solid(vello::peniko::Color::PALE_VIOLET_RED)); + + let mut harness = TestHarness::create(widget); + + assert_render_snapshot!(harness, "kurbo_shape_circle"); + } + + // TODO: add test for KurboShape in Flex +} diff --git a/masonry/src/widget/mod.rs b/masonry/src/widget/mod.rs index e83e5d913..76b90e4e2 100644 --- a/masonry/src/widget/mod.rs +++ b/masonry/src/widget/mod.rs @@ -20,6 +20,7 @@ mod checkbox; mod flex; mod grid; mod image; +mod kurbo; mod label; mod portal; mod progress_bar; @@ -35,11 +36,12 @@ mod widget_arena; pub use self::image::Image; pub use align::Align; -pub use board::{Board, BoardParams, ConcreteShape, KurboShape, PositionedElement, SvgElement}; +pub use board::{Board, BoardParams, PositionedElement, SvgElement}; pub use button::Button; pub use checkbox::Checkbox; pub use flex::{Axis, CrossAxisAlignment, Flex, FlexParams, MainAxisAlignment}; pub use grid::{Grid, GridParams}; +pub use kurbo::{ConcreteShape, KurboShape}; pub use label::{Label, LineBreaking}; pub use portal::Portal; pub use progress_bar::ProgressBar; diff --git a/masonry/src/widget/screenshots/masonry__widget__board__tests__kurbo_shape_circle.png b/masonry/src/widget/screenshots/masonry__widget__kurbo__tests__kurbo_shape_circle.png similarity index 100% rename from masonry/src/widget/screenshots/masonry__widget__board__tests__kurbo_shape_circle.png rename to masonry/src/widget/screenshots/masonry__widget__kurbo__tests__kurbo_shape_circle.png diff --git a/xilem/src/view/board.rs b/xilem/src/view/board.rs index 014a11603..116b5f88b 100644 --- a/xilem/src/view/board.rs +++ b/xilem/src/view/board.rs @@ -5,8 +5,8 @@ use std::marker::PhantomData; use masonry::widget::{self, KurboShape, SvgElement, WidgetMut}; use xilem_core::{ - AnyElement, AppendVec, DynMessage, ElementSplice, MessageResult, Mut, SuperElement, View, - ViewElement, ViewMarker, ViewSequence, + AnyElement, AnyView, AppendVec, DynMessage, ElementSplice, MessageResult, Mut, SuperElement, + View, ViewElement, ViewMarker, ViewSequence, }; use crate::{Pod, ViewCtx, WidgetView}; @@ -16,7 +16,7 @@ pub use masonry::widget::BoardParams; mod kurbo_shape; mod style_modifier; -pub use kurbo_shape::{AnyBoardView, GraphicsView}; +pub use kurbo_shape::GraphicsView; pub use style_modifier::{fill, stroke, transform, Fill, GraphicsExt, Stroke, Transform}; pub fn board>( @@ -92,10 +92,34 @@ where } } +pub type AnyBoardView = + dyn AnyView + Send + Sync; + pub struct BoardElement { element: Pod>, } +pub struct BoardElementMut<'w> { + parent: WidgetMut<'w, widget::Board>, + idx: usize, +} + +struct BoardSplice<'w> { + idx: usize, + element: WidgetMut<'w, widget::Board>, + scratch: AppendVec, +} + +impl<'w> BoardSplice<'w> { + fn new(element: WidgetMut<'w, widget::Board>) -> Self { + Self { + idx: 0, + element, + scratch: AppendVec::default(), + } + } +} + impl ViewElement for BoardElement { type Mut<'w> = BoardElementMut<'w>; } @@ -156,27 +180,6 @@ impl AnyElement> for BoardElement { } } -pub struct BoardElementMut<'w> { - parent: WidgetMut<'w, widget::Board>, - idx: usize, -} - -struct BoardSplice<'w> { - idx: usize, - element: WidgetMut<'w, widget::Board>, - scratch: AppendVec, -} - -impl<'w> BoardSplice<'w> { - fn new(element: WidgetMut<'w, widget::Board>) -> Self { - Self { - idx: 0, - element, - scratch: AppendVec::default(), - } - } -} - impl ElementSplice for BoardSplice<'_> { fn insert(&mut self, BoardElement { element }: BoardElement) { self.element.insert_child(self.idx, element.inner); @@ -282,6 +285,18 @@ where } } +impl PositionedView +where + State: 'static, + Action: 'static, + V: WidgetView, +{ + /// Turns this [`BoardItem`] into an [`AnyBoardChild`] + pub fn into_any_board(self) -> Box> { + self.into() + } +} + impl ViewMarker for PositionedView {} impl View for PositionedView where @@ -352,15 +367,3 @@ where self.view.message(view_state, id_path, message, app_state) } } - -impl PositionedView -where - State: 'static, - Action: 'static, - V: WidgetView, -{ - /// Turns this [`BoardItem`] into an [`AnyBoardChild`] - pub fn into_any_board(self) -> Box> { - self.into() - } -} diff --git a/xilem/src/view/board/kurbo_shape.rs b/xilem/src/view/board/kurbo_shape.rs index 9e4ea19b6..7bed6a332 100644 --- a/xilem/src/view/board/kurbo_shape.rs +++ b/xilem/src/view/board/kurbo_shape.rs @@ -5,12 +5,10 @@ use masonry::widget::{KurboShape, SvgElement}; use vello::kurbo; -use xilem_core::{AnyElement, AnyView, DynMessage, MessageResult, Mut, OrphanView, SuperElement}; +use xilem_core::{DynMessage, MessageResult, Mut, OrphanView}; use crate::{Pod, ViewCtx, WidgetView}; -use super::BoardElement; - pub trait GraphicsView: WidgetView {} impl GraphicsView for V where @@ -18,30 +16,6 @@ impl GraphicsView for V where { } -pub type AnyBoardView = - dyn AnyView + Send + Sync; - -impl SuperElement> for Pod { - fn upcast(child: Pod) -> Self { - child - } - - fn with_downcast_val( - mut this: Mut<'_, Self>, - f: impl FnOnce(Mut<'_, Pod>) -> R, - ) -> (Self::Mut<'_>, R) { - let r = f(this.reborrow_mut()); - (this, r) - } -} - -impl AnyElement> for Pod { - fn replace_inner(mut this: Self::Mut<'_>, child: Pod) -> Self::Mut<'_> { - this.update_from(child.inner.as_ref().unwrap()); - this - } -} - macro_rules! impl_orphan_view { ($t:ident) => { impl OrphanView diff --git a/xilem/src/view/board/style_modifier.rs b/xilem/src/view/board/style_modifier.rs index 510da292c..50364911c 100644 --- a/xilem/src/view/board/style_modifier.rs +++ b/xilem/src/view/board/style_modifier.rs @@ -180,17 +180,10 @@ where fn build(&self, ctx: &mut ViewCtx) -> (Self::Element, Self::ViewState) { let (mut element, state) = self.child.build(ctx); - element.inner.as_mut().unwrap().set_fill_mode(self.mode); - element - .inner - .as_mut() - .unwrap() - .set_fill_brush(self.brush.clone()); - element - .inner - .as_mut() - .unwrap() - .set_fill_brush_transform(self.brush_transform); + let element_mut = element.inner.as_mut().unwrap(); + element_mut.set_fill_mode(self.mode); + element_mut.set_fill_brush(self.brush.clone()); + element_mut.set_fill_brush_transform(self.brush_transform); (element, state) } @@ -251,21 +244,10 @@ where fn build(&self, ctx: &mut ViewCtx) -> (Self::Element, Self::ViewState) { let (mut element, state) = self.child.build(ctx); - element - .inner - .as_mut() - .unwrap() - .set_stroke_style(self.style.clone()); - element - .inner - .as_mut() - .unwrap() - .set_stroke_brush(self.brush.clone()); - element - .inner - .as_mut() - .unwrap() - .set_stroke_brush_transform(self.brush_transform); + let element_mut = element.inner.as_mut().unwrap(); + element_mut.set_stroke_style(self.style.clone()); + element_mut.set_stroke_brush(self.brush.clone()); + element_mut.set_stroke_brush_transform(self.brush_transform); (element, state) } From 8726882a7220bc3699b3ada8bf2f87699e3e91e5 Mon Sep 17 00:00:00 2001 From: Muhammad Ragib Hasin Date: Wed, 18 Sep 2024 22:52:53 +0600 Subject: [PATCH 10/10] Make unboxed PositionedView possible --- masonry/src/widget/board.rs | 57 +++++++++++++------------- xilem/src/view/board.rs | 81 ++++++++++++++++++++++--------------- 2 files changed, 76 insertions(+), 62 deletions(-) diff --git a/masonry/src/widget/board.rs b/masonry/src/widget/board.rs index ed13cb0fb..681c4d831 100644 --- a/masonry/src/widget/board.rs +++ b/masonry/src/widget/board.rs @@ -111,6 +111,15 @@ impl<'a> WidgetMut<'a, Board> { } } +impl<'a, W: Widget> WidgetMut<'a, PositionedElement> { + pub fn inner_mut(&mut self) -> WidgetMut<'_, W> { + WidgetMut { + ctx: self.ctx.reborrow_mut(), + widget: &mut self.widget.inner, + } + } +} + impl<'a> WidgetMut<'a, Box> { /// Attempt to downcast to `WidgetMut` of concrete Widget type. pub fn try_downcast(&mut self) -> Option> { @@ -120,29 +129,6 @@ impl<'a> WidgetMut<'a, Box> { }) } - /// Downcasts to `WidgetMut` of concrete Widget type. - /// - /// ## Panics - /// - /// Panics if the downcast fails, with an error message that shows the - /// discrepancy between the expected and actual types. - pub fn downcast_positioned(&mut self) -> WidgetMut<'_, W2> { - let w1_name = self.widget.type_name(); - match self.widget.as_mut_any().downcast_mut() { - Some(PositionedElement { inner: widget, .. }) => WidgetMut { - ctx: self.ctx.reborrow_mut(), - widget, - }, - None => { - panic!( - "failed to downcast widget: expected widget of type `{}`, found `{}`", - std::any::type_name::(), - w1_name, - ); - } - } - } - /// Downcasts to `WidgetMut` of concrete Widget type. /// /// ## Panics @@ -407,18 +393,25 @@ impl From for BoardParams { } impl WidgetPod { - pub fn positioned(self, params: impl Into) -> WidgetPod> { + pub fn positioned(self, params: impl Into) -> WidgetPod> { let id = self.id(); WidgetPod::new_with_id( - Box::new(PositionedElement { + PositionedElement { inner: self.inner().unwrap(), params: params.into(), - }), + }, id, ) } } +impl WidgetPod> { + pub fn svg_boxed(self) -> WidgetPod> { + let id = self.id(); + WidgetPod::new_with_id(Box::new(self.inner().unwrap()), id) + } +} + // --- MARK: TESTS --- #[cfg(test)] mod tests { @@ -434,10 +427,14 @@ mod tests { fn board_absolute_placement_snapshots() { let board = Board::new() .with_child_pod( - WidgetPod::new(Button::new("hello")).positioned(Rect::new(10., 10., 60., 40.)), + WidgetPod::new(Button::new("hello")) + .positioned(Rect::new(10., 10., 60., 40.)) + .svg_boxed(), ) .with_child_pod( - WidgetPod::new(Button::new("world")).positioned(Rect::new(30., 30., 80., 60.)), + WidgetPod::new(Button::new("world")) + .positioned(Rect::new(30., 30., 80., 60.)) + .svg_boxed(), ); let mut harness = TestHarness::create(board); @@ -454,7 +451,9 @@ mod tests { let board = Board::new() .with_child_pod( - WidgetPod::new(Button::new("hello")).positioned(Rect::new(10., 10., 60., 40.)), + WidgetPod::new(Button::new("hello")) + .positioned(Rect::new(10., 10., 60., 40.)) + .svg_boxed(), ) .with_child_pod(WidgetPod::new(Box::new(shape))); diff --git a/xilem/src/view/board.rs b/xilem/src/view/board.rs index 116b5f88b..0d6ace3b4 100644 --- a/xilem/src/view/board.rs +++ b/xilem/src/view/board.rs @@ -3,7 +3,10 @@ use std::marker::PhantomData; -use masonry::widget::{self, KurboShape, SvgElement, WidgetMut}; +use masonry::{ + widget::{self, KurboShape, PositionedElement, SvgElement, WidgetMut}, + Widget, +}; use xilem_core::{ AnyElement, AnyView, AppendVec, DynMessage, ElementSplice, MessageResult, Mut, SuperElement, View, ViewElement, ViewMarker, ViewSequence, @@ -153,6 +156,33 @@ impl AnyElement for BoardElement { } } +impl SuperElement>> for BoardElement { + fn upcast(child: Pod>) -> Self { + BoardElement { + element: child.inner.svg_boxed().into(), + } + } + + fn with_downcast_val( + mut this: Mut<'_, Self>, + f: impl FnOnce(Mut<'_, Pod>>) -> R, + ) -> (Self::Mut<'_>, R) { + let r = { + let mut child = this.parent.child_mut(this.idx); + f(child.downcast()) + }; + (this, r) + } +} + +impl AnyElement>> for BoardElement { + fn replace_inner(mut this: Self::Mut<'_>, child: Pod>) -> Self::Mut<'_> { + this.parent.remove_child(this.idx); + this.parent.insert_child(this.idx, child.inner.svg_boxed()); + this + } +} + impl SuperElement> for BoardElement { fn upcast(child: Pod) -> Self { BoardElement { @@ -250,14 +280,14 @@ pub trait BoardExt: WidgetView { impl> BoardExt for V {} -/// A `WidgetView` that can be used within a [`Board`] [`View`] +/// A [`WidgetView`] that can be used within a [`Board`] [`View`] pub struct PositionedView { view: V, params: BoardParams, phantom: PhantomData (State, Action)>, } -/// Makes this view absolutely positioned in a `Board`. +/// Makes this view absolutely positioned in a [`Board`]. pub fn positioned( view: V, params: impl Into, @@ -280,8 +310,8 @@ where Action: 'static, V: WidgetView, { - fn from(value: PositionedView) -> Self { - Box::new(positioned(value.view, value.params)) + fn from(view: PositionedView) -> Self { + Box::new(positioned(view.view, view.params)) } } @@ -291,7 +321,7 @@ where Action: 'static, V: WidgetView, { - /// Turns this [`BoardItem`] into an [`AnyBoardChild`] + /// Turns this [`PositionedView`] into a boxed [`AnyBoardView`]. pub fn into_any_board(self) -> Box> { self.into() } @@ -304,18 +334,13 @@ where Action: 'static, V: WidgetView, { - type Element = BoardElement; + type Element = Pod>; type ViewState = V::ViewState; fn build(&self, ctx: &mut ViewCtx) -> (Self::Element, Self::ViewState) { let (pod, state) = self.view.build(ctx); - ( - BoardElement { - element: pod.inner.positioned(self.params).into(), - }, - state, - ) + (pod.inner.positioned(self.params).into(), state) } fn rebuild<'el>( @@ -325,23 +350,15 @@ where ctx: &mut ViewCtx, mut element: Mut<'el, Self::Element>, ) -> Mut<'el, Self::Element> { - { - // if self.params != prev.params { - // element - // .parent - // .update_child_board_params(element.idx, self.params); - // } - let mut child = element.parent.child_mut(element.idx); - self.view - .rebuild(&prev.view, view_state, ctx, child.downcast_positioned()); - if self.params.origin != prev.params.origin { - child.widget.set_origin(self.params.origin); - child.ctx.request_layout(); - } - if self.params.size != prev.params.size { - child.widget.set_size(self.params.size); - child.ctx.request_layout(); - } + self.view + .rebuild(&prev.view, view_state, ctx, element.inner_mut()); + if self.params.origin != prev.params.origin { + element.widget.set_origin(self.params.origin); + element.ctx.request_layout(); + } + if self.params.size != prev.params.size { + element.widget.set_size(self.params.size); + element.ctx.request_layout(); } element } @@ -352,9 +369,7 @@ where ctx: &mut ViewCtx, mut element: Mut<'_, Self::Element>, ) { - let mut child = element.parent.child_mut(element.idx); - self.view - .teardown(view_state, ctx, child.downcast_positioned()); + self.view.teardown(view_state, ctx, element.inner_mut()); } fn message(