From 28f3ce479f16bac16e2064c7a5370d7418f30b22 Mon Sep 17 00:00:00 2001 From: Daniel McNab <36049421+DJMcNab@users.noreply.github.com> Date: Wed, 15 Jan 2025 16:07:08 +0000 Subject: [PATCH 1/8] Start work on the transformed view --- xilem/src/view/transform.rs | 126 ++++++++++++++++++++++++++++++------ 1 file changed, 108 insertions(+), 18 deletions(-) diff --git a/xilem/src/view/transform.rs b/xilem/src/view/transform.rs index 4cd342cd6..479640a0f 100644 --- a/xilem/src/view/transform.rs +++ b/xilem/src/view/transform.rs @@ -1,43 +1,133 @@ // Copyright 2025 the Xilem Authors // SPDX-License-Identifier: Apache-2.0 -use crate::Affine; +use std::marker::PhantomData; -/// An extension trait, to allow common transformations of the views transform. -pub trait Transformable: Sized { - fn transform_mut(&mut self) -> &mut Affine; +use crate::{ + core::{DynMessage, View, ViewMarker}, + Affine, Pod, ViewCtx, WidgetView, +}; + +/// A view which transforms the widget created by child. +/// +/// Each widget can only be transformed once, and using this +/// function in a nesting pattern may cause panics. +/// This is due to the efficient way that Xilem calculates field changes, +/// which is incompatible with composing non-idempotent changes to the same field. +/// +/// The transform can be set using the methods on the return type. +/// Transformations apply in order. +/// That is, calling [`rotate`](Transformed::rotate) then [`translate`](Transformed::translate) +/// will move the rotated widget. +pub fn transformed(child: Child) -> Transformed +where + Child: WidgetView, +{ + Transformed { + child, + transform: Affine::IDENTITY, + phantom: PhantomData, + } +} +/// The view for [`transformed`]. +pub struct Transformed { + child: V, + transform: Affine, + phantom: PhantomData<(State, Action)>, +} + +impl Transformed { #[must_use] - fn rotate(mut self, radians: f64) -> Self { - let transform = self.transform_mut(); - *transform = transform.then_rotate(radians); + /// Rotate the widget by `radians` radians about the origin of its natural location. + pub fn rotate(mut self, radians: f64) -> Self { + self.transform = self.transform.then_rotate(radians); self } + /// Scale the widget by `uniform` in each axis. #[must_use] - fn scale(mut self, uniform: f64) -> Self { - let transform = self.transform_mut(); - *transform = transform.then_scale(uniform); + pub fn scale(mut self, uniform: f64) -> Self { + self.transform = self.transform.then_scale(uniform); self } #[must_use] - fn scale_non_uniform(mut self, x: f64, y: f64) -> Self { - let transform = self.transform_mut(); - *transform = transform.then_scale_non_uniform(x, y); + /// Scale the widget by the given amount in each (2d) axis. + pub fn scale_non_uniform(mut self, x: f64, y: f64) -> Self { + self.transform = self.transform.then_scale_non_uniform(x, y); self } #[must_use] - fn translate(mut self, v: impl Into) -> Self { - let transform = self.transform_mut(); - *transform = transform.then_translate(v.into()); + /// Displace the widget by `v` from its natural location. + pub fn translate(mut self, v: impl Into) -> Self { + self.transform = self.transform.then_translate(v.into()); self } #[must_use] - fn transform(mut self, v: impl Into) -> Self { - *self.transform_mut() *= v.into(); + /// Apply an arbitrary 2d transform to the widget. + pub fn transform(mut self, v: impl Into) -> Self { + self.transform *= v.into(); self } } + +impl ViewMarker for Transformed {} +impl View for Transformed +where + Child: WidgetView, + State: 'static, + Action: 'static, +{ + type Element = Pod; + type ViewState = Child::ViewState; + + fn build(&self, ctx: &mut ViewCtx) -> (Self::Element, Self::ViewState) { + let (mut child_pod, child_state) = self.child.build(ctx); + // TODO: Use a marker identity value to detect this more properly + if child_pod.transform != Affine::IDENTITY { + panic!("Tried to create a `Transformed` with an already controlled Transform"); + } + child_pod.transform = self.transform; + (child_pod, child_state) + } + + fn rebuild( + &self, + prev: &Self, + view_state: &mut Self::ViewState, + ctx: &mut ViewCtx, + mut element: xilem_core::Mut<'_, Self::Element>, + ) { + if self.transform != prev.transform { + element.ctx.set_transform(self.transform); + } + self.child.rebuild(&prev.child, view_state, ctx, element); + } + + fn teardown( + &self, + view_state: &mut Self::ViewState, + ctx: &mut ViewCtx, + element: xilem_core::Mut<'_, Self::Element>, + ) { + self.child.teardown(view_state, ctx, element); + } + + fn message( + &self, + view_state: &mut Self::ViewState, + id_path: &[xilem_core::ViewId], + message: DynMessage, + app_state: &mut State, + ) -> xilem_core::MessageResult { + self.child.message(view_state, id_path, message, app_state) + } +} + +/// An extension trait, to allow common transformations of the views transform. +pub trait Transformable: Sized { + fn transform_mut(&mut self) -> &mut Affine; +} From 27626ec9deb518341b7cbe31ce51c91983fa3bd1 Mon Sep 17 00:00:00 2001 From: Daniel McNab <36049421+DJMcNab@users.noreply.github.com> Date: Thu, 16 Jan 2025 11:50:43 +0000 Subject: [PATCH 2/8] Add the new way of handling transforms --- xilem/examples/transforms.rs | 3 ++- xilem/src/lib.rs | 29 +++++++++++++++++++++++++---- xilem/src/view/transform.rs | 4 ++-- 3 files changed, 29 insertions(+), 7 deletions(-) diff --git a/xilem/examples/transforms.rs b/xilem/examples/transforms.rs index 83c7b4665..f5e0a25dd 100644 --- a/xilem/examples/transforms.rs +++ b/xilem/examples/transforms.rs @@ -6,7 +6,7 @@ use std::f64::consts::{PI, TAU}; use winit::error::EventLoopError; -use xilem::view::{button, grid, label, sized_box, GridExt as _, Transformable as _}; +use xilem::view::{button, grid, label, sized_box, GridExt as _}; use xilem::{Color, EventLoop, Vec2, WidgetView, Xilem}; struct TransformsGame { @@ -46,6 +46,7 @@ impl TransformsGame { // Note that the order of the transformations is relevant. let transformed_status = sized_box(status) .background(Color::new(bg_color)) + .transformed() .translate(self.translation) .rotate(self.rotation) .scale(self.scale); diff --git a/xilem/src/lib.rs b/xilem/src/lib.rs index 9972a80fe..6f8d1806c 100644 --- a/xilem/src/lib.rs +++ b/xilem/src/lib.rs @@ -45,6 +45,7 @@ use std::sync::Arc; use masonry::dpi::LogicalSize; use masonry::widget::{RootWidget, WidgetMut}; use masonry::{event_loop_runner, Widget, WidgetId, WidgetPod}; +use view::{transformed, Transformed}; use winit::error::EventLoopError; use winit::window::{Window, WindowAttributes}; @@ -188,7 +189,7 @@ where pub struct Pod { pub widget: W, pub id: WidgetId, - pub transform: Affine, + pub transform: Option, } impl Pod { @@ -200,10 +201,18 @@ impl Pod { } } fn into_widget_pod(self) -> WidgetPod { - WidgetPod::new_with_id_and_transform(self.widget, self.id, self.transform) + WidgetPod::new_with_id_and_transform( + self.widget, + self.id, + self.transform.unwrap_or_default(), + ) } fn boxed_widget_pod(self) -> WidgetPod> { - WidgetPod::new_with_id_and_transform(Box::new(self.widget), self.id, self.transform) + WidgetPod::new_with_id_and_transform( + Box::new(self.widget), + self.id, + self.transform.unwrap_or_default(), + ) } fn boxed(self) -> Pod> { Pod { @@ -257,6 +266,18 @@ pub trait WidgetView: { Box::new(self) } + + /// This widget with a 2d transform applied. + /// + /// The returned view has methods to control individual components of + /// the transform, as well as apply an arbitrary transform. + /// See [`transformed`] for more details. + fn transformed(self) -> Transformed + where + Self: Sized, + { + transformed(self) + } } impl WidgetView for V @@ -323,7 +344,7 @@ impl ViewCtx { } pub fn new_pod_with_transform(&mut self, widget: W, transform: Affine) -> Pod { let mut pod = Pod::new(widget); - pod.transform = transform; + pod.transform = Some(transform); pod } diff --git a/xilem/src/view/transform.rs b/xilem/src/view/transform.rs index 479640a0f..555468d46 100644 --- a/xilem/src/view/transform.rs +++ b/xilem/src/view/transform.rs @@ -87,10 +87,10 @@ where fn build(&self, ctx: &mut ViewCtx) -> (Self::Element, Self::ViewState) { let (mut child_pod, child_state) = self.child.build(ctx); // TODO: Use a marker identity value to detect this more properly - if child_pod.transform != Affine::IDENTITY { + if child_pod.transform.is_some() { panic!("Tried to create a `Transformed` with an already controlled Transform"); } - child_pod.transform = self.transform; + child_pod.transform = Some(self.transform); (child_pod, child_state) } From 04c4650d39746fb61edc5417f77ae6a9ad268f28 Mon Sep 17 00:00:00 2001 From: Daniel McNab <36049421+DJMcNab@users.noreply.github.com> Date: Thu, 16 Jan 2025 12:02:05 +0000 Subject: [PATCH 3/8] Remove existing solution --- xilem/src/lib.rs | 5 ----- xilem/src/one_of.rs | 30 +----------------------------- xilem/src/view/button.rs | 22 ++-------------------- xilem/src/view/checkbox.rs | 20 ++------------------ xilem/src/view/flex.rs | 17 ++--------------- xilem/src/view/grid.rs | 17 ++--------------- xilem/src/view/image.rs | 18 ++---------------- xilem/src/view/label.rs | 18 ++---------------- xilem/src/view/portal.rs | 20 ++------------------ xilem/src/view/progress_bar.rs | 23 +++-------------------- xilem/src/view/prose.rs | 18 ++---------------- xilem/src/view/sized_box.rs | 17 ++--------------- xilem/src/view/spinner.rs | 21 +++------------------ xilem/src/view/textbox.rs | 17 ++--------------- xilem/src/view/transform.rs | 5 ----- xilem/src/view/variable_label.rs | 8 +------- xilem/src/view/zstack.rs | 18 ++---------------- 17 files changed, 30 insertions(+), 264 deletions(-) diff --git a/xilem/src/lib.rs b/xilem/src/lib.rs index 6f8d1806c..94e7ebaad 100644 --- a/xilem/src/lib.rs +++ b/xilem/src/lib.rs @@ -342,11 +342,6 @@ impl ViewCtx { pub fn new_pod(&mut self, widget: W) -> Pod { Pod::new(widget) } - pub fn new_pod_with_transform(&mut self, widget: W, transform: Affine) -> Pod { - let mut pod = Pod::new(widget); - pod.transform = Some(transform); - pod - } pub fn with_leaf_action_widget( &mut self, diff --git a/xilem/src/one_of.rs b/xilem/src/one_of.rs index 11ae0d69f..7be2d3455 100644 --- a/xilem/src/one_of.rs +++ b/xilem/src/one_of.rs @@ -13,8 +13,7 @@ use vello::Scene; use crate::core::one_of::OneOf; use crate::core::Mut; -use crate::view::Transformable; -use crate::{Affine, Pod, ViewCtx}; +use crate::{Pod, ViewCtx}; impl< A: Widget, @@ -143,33 +142,6 @@ impl< } } -impl Transformable for OneOf -where - A: Transformable, - B: Transformable, - C: Transformable, - D: Transformable, - E: Transformable, - F: Transformable, - G: Transformable, - H: Transformable, - I: Transformable, -{ - fn transform_mut(&mut self) -> &mut Affine { - match self { - Self::A(w) => w.transform_mut(), - Self::B(w) => w.transform_mut(), - Self::C(w) => w.transform_mut(), - Self::D(w) => w.transform_mut(), - Self::E(w) => w.transform_mut(), - Self::F(w) => w.transform_mut(), - Self::G(w) => w.transform_mut(), - Self::H(w) => w.transform_mut(), - Self::I(w) => w.transform_mut(), - } - } -} - impl crate::core::one_of::PhantomElementCtx for ViewCtx { type PhantomElement = Pod>; } diff --git a/xilem/src/view/button.rs b/xilem/src/view/button.rs index 987aafaeb..995de654e 100644 --- a/xilem/src/view/button.rs +++ b/xilem/src/view/button.rs @@ -7,9 +7,7 @@ use xilem_core::ViewPathTracker; use crate::core::{DynMessage, Mut, View, ViewMarker}; use crate::view::Label; -use crate::{Affine, MessageResult, Pod, ViewCtx, ViewId}; - -use super::Transformable; +use crate::{MessageResult, Pod, ViewCtx, ViewId}; /// A button which calls `callback` when the primary mouse button (normally left) is pressed. pub fn button( @@ -19,7 +17,6 @@ pub fn button( { Button { label: label.into(), - transform: Affine::IDENTITY, callback: move |state: &mut State, button| match button { PointerButton::Primary => MessageResult::Action(callback(state)), _ => MessageResult::Nop, @@ -35,7 +32,6 @@ pub fn button_any_pointer( { Button { label: label.into(), - transform: Affine::IDENTITY, callback: move |state: &mut State, button| MessageResult::Action(callback(state, button)), } } @@ -45,16 +41,9 @@ pub struct Button { // N.B. This widget is *implemented* to handle any kind of view with an element // type of `Label` even though it currently does not do so. label: Label, - transform: Affine, callback: F, } -impl Transformable for Button { - fn transform_mut(&mut self) -> &mut Affine { - &mut self.transform - } -} - const LABEL_VIEW_ID: ViewId = ViewId::new(0); impl ViewMarker for Button {} @@ -70,10 +59,7 @@ where View::::build(&self.label, ctx) }); ctx.with_leaf_action_widget(|ctx| { - ctx.new_pod_with_transform( - widget::Button::from_label_pod(child.into_widget_pod()), - self.transform, - ) + ctx.new_pod(widget::Button::from_label_pod(child.into_widget_pod())) }) } @@ -84,10 +70,6 @@ where ctx: &mut ViewCtx, mut element: Mut, ) { - if prev.transform != self.transform { - element.set_transform(self.transform); - } - ctx.with_id(LABEL_VIEW_ID, |ctx| { View::::rebuild( &self.label, diff --git a/xilem/src/view/checkbox.rs b/xilem/src/view/checkbox.rs index 439067e68..e2174e53d 100644 --- a/xilem/src/view/checkbox.rs +++ b/xilem/src/view/checkbox.rs @@ -5,9 +5,7 @@ use masonry::text::ArcStr; use masonry::widget; use crate::core::{DynMessage, Mut, ViewMarker}; -use crate::{Affine, MessageResult, Pod, View, ViewCtx, ViewId}; - -use super::Transformable; +use crate::{MessageResult, Pod, View, ViewCtx, ViewId}; pub fn checkbox( label: impl Into, @@ -21,7 +19,6 @@ where label: label.into(), callback, checked, - transform: Affine::IDENTITY, } } @@ -30,13 +27,6 @@ pub struct Checkbox { label: ArcStr, checked: bool, callback: F, - transform: Affine, -} - -impl Transformable for Checkbox { - fn transform_mut(&mut self) -> &mut Affine { - &mut self.transform - } } impl ViewMarker for Checkbox {} @@ -49,10 +39,7 @@ where fn build(&self, ctx: &mut ViewCtx) -> (Self::Element, Self::ViewState) { ctx.with_leaf_action_widget(|ctx| { - ctx.new_pod_with_transform( - widget::Checkbox::new(self.checked, self.label.clone()), - self.transform, - ) + ctx.new_pod(widget::Checkbox::new(self.checked, self.label.clone())) }) } @@ -63,9 +50,6 @@ where _ctx: &mut ViewCtx, mut element: Mut, ) { - if prev.transform != self.transform { - element.set_transform(self.transform); - } if prev.label != self.label { widget::Checkbox::set_text(&mut element, self.label.clone()); } diff --git a/xilem/src/view/flex.rs b/xilem/src/view/flex.rs index fdbf8a1de..615d836f7 100644 --- a/xilem/src/view/flex.rs +++ b/xilem/src/view/flex.rs @@ -11,7 +11,7 @@ use crate::core::{ AppendVec, DynMessage, ElementSplice, MessageResult, Mut, SuperElement, View, ViewElement, ViewId, ViewMarker, ViewPathTracker, ViewSequence, }; -use crate::{Affine, AnyWidgetView, Pod, ViewCtx, WidgetView}; +use crate::{AnyWidgetView, Pod, ViewCtx, WidgetView}; pub fn flex>( sequence: Seq, @@ -19,7 +19,6 @@ pub fn flex>( Flex { sequence, axis: Axis::Vertical, - transform: Affine::IDENTITY, cross_axis_alignment: CrossAxisAlignment::Center, main_axis_alignment: MainAxisAlignment::Start, fill_major_axis: false, @@ -32,7 +31,6 @@ pub fn flex>( pub struct Flex { sequence: Seq, axis: Axis, - transform: Affine, cross_axis_alignment: CrossAxisAlignment, main_axis_alignment: MainAxisAlignment, fill_major_axis: bool, @@ -87,12 +85,6 @@ impl Flex { } } -impl Transformable for Flex { - fn transform_mut(&mut self) -> &mut Affine { - &mut self.transform - } -} - impl ViewMarker for Flex {} impl View for Flex where @@ -121,7 +113,7 @@ where FlexElement::FlexSpacer(flex) => widget.with_flex_spacer(flex), } } - let pod = ctx.new_pod_with_transform(widget, self.transform); + let pod = ctx.new_pod(widget); (pod, seq_state) } @@ -132,9 +124,6 @@ where ctx: &mut ViewCtx, mut element: Mut, ) { - if prev.transform != self.transform { - element.set_transform(self.transform); - } if prev.axis != self.axis { widget::Flex::set_direction(&mut element, self.axis); } @@ -646,8 +635,6 @@ mod hidden { } use hidden::AnyFlexChildState; -use super::Transformable; - impl ViewMarker for AnyFlexChild {} impl View for AnyFlexChild where diff --git a/xilem/src/view/grid.rs b/xilem/src/view/grid.rs index 408dd8423..ffbc01fc5 100644 --- a/xilem/src/view/grid.rs +++ b/xilem/src/view/grid.rs @@ -10,9 +10,7 @@ use crate::core::{ AppendVec, DynMessage, ElementSplice, MessageResult, Mut, SuperElement, View, ViewElement, ViewId, ViewMarker, ViewSequence, }; -use crate::{Affine, Pod, ViewCtx, WidgetView}; - -use super::Transformable; +use crate::{Pod, ViewCtx, WidgetView}; pub fn grid>( sequence: Seq, @@ -25,7 +23,6 @@ pub fn grid>( phantom: PhantomData, height, width, - transform: Affine::IDENTITY, } } @@ -35,7 +32,6 @@ pub struct Grid { spacing: f64, width: i32, height: i32, - transform: Affine, /// Used to associate the State and Action in the call to `.grid()` with the State and Action /// used in the View implementation, to allow inference to flow backwards, allowing State and /// Action to be inferred properly @@ -54,12 +50,6 @@ impl Grid { } } -impl Transformable for Grid { - fn transform_mut(&mut self) -> &mut Affine { - &mut self.transform - } -} - impl ViewMarker for Grid {} impl View for Grid @@ -84,7 +74,7 @@ where } } } - let pod = ctx.new_pod_with_transform(widget, self.transform); + let pod = ctx.new_pod(widget); (pod, seq_state) } @@ -95,9 +85,6 @@ where ctx: &mut ViewCtx, mut element: Mut, ) { - if prev.transform != self.transform { - element.set_transform(self.transform); - } if prev.height != self.height { widget::Grid::set_height(&mut element, self.height); } diff --git a/xilem/src/view/image.rs b/xilem/src/view/image.rs index 200b4e65d..4bbc4dfdd 100644 --- a/xilem/src/view/image.rs +++ b/xilem/src/view/image.rs @@ -6,9 +6,7 @@ use masonry::widget::{self, ObjectFit}; use crate::core::{DynMessage, Mut, ViewMarker}; -use crate::{Affine, MessageResult, Pod, View, ViewCtx, ViewId}; - -use super::Transformable; +use crate::{MessageResult, Pod, View, ViewCtx, ViewId}; /// Displays the bitmap `image`. /// @@ -27,7 +25,6 @@ pub fn image(image: &vello::peniko::Image) -> Image { // easier than documenting that cloning is cheap. image: image.clone(), object_fit: ObjectFit::default(), - transform: Affine::IDENTITY, } } @@ -38,7 +35,6 @@ pub fn image(image: &vello::peniko::Image) -> Image { pub struct Image { image: vello::peniko::Image, object_fit: ObjectFit, - transform: Affine, } impl Image { @@ -49,20 +45,13 @@ impl Image { } } -impl Transformable for Image { - fn transform_mut(&mut self) -> &mut Affine { - &mut self.transform - } -} - impl ViewMarker for Image {} impl View for Image { type Element = Pod; type ViewState = (); fn build(&self, ctx: &mut ViewCtx) -> (Self::Element, Self::ViewState) { - let pod = - ctx.new_pod_with_transform(widget::Image::new(self.image.clone()), self.transform); + let pod = ctx.new_pod(widget::Image::new(self.image.clone())); (pod, ()) } @@ -73,9 +62,6 @@ impl View for Image { _: &mut ViewCtx, mut element: Mut, ) { - if prev.transform != self.transform { - element.set_transform(self.transform); - } if prev.object_fit != self.object_fit { widget::Image::set_fit_mode(&mut element, self.object_fit); } diff --git a/xilem/src/view/label.rs b/xilem/src/view/label.rs index ee6705d4d..cafd59d74 100644 --- a/xilem/src/view/label.rs +++ b/xilem/src/view/label.rs @@ -7,9 +7,7 @@ use masonry::widget::{self, LineBreaking}; use vello::peniko::Brush; use crate::core::{DynMessage, Mut, ViewMarker}; -use crate::{Affine, Color, MessageResult, Pod, TextAlignment, View, ViewCtx, ViewId}; - -use super::Transformable; +use crate::{Color, MessageResult, Pod, TextAlignment, View, ViewCtx, ViewId}; pub fn label(label: impl Into) -> Label { Label { @@ -20,7 +18,6 @@ pub fn label(label: impl Into) -> Label { weight: FontWeight::NORMAL, font: FontStack::List(std::borrow::Cow::Borrowed(&[])), line_break_mode: LineBreaking::Overflow, - transform: Affine::IDENTITY, } } @@ -33,7 +30,6 @@ pub struct Label { weight: FontWeight, font: FontStack<'static>, line_break_mode: LineBreaking, // TODO: add more attributes of `masonry::widget::Label` - transform: Affine, } impl Label { @@ -75,12 +71,6 @@ impl Label { } } -impl Transformable for Label { - fn transform_mut(&mut self) -> &mut Affine { - &mut self.transform - } -} - impl From for Label where T: Into, @@ -96,7 +86,7 @@ impl View for Label { type ViewState = (); fn build(&self, ctx: &mut ViewCtx) -> (Self::Element, Self::ViewState) { - let widget_pod = ctx.new_pod_with_transform( + let widget_pod = ctx.new_pod( widget::Label::new(self.label.clone()) .with_brush(self.text_brush.clone()) .with_alignment(self.alignment) @@ -104,7 +94,6 @@ impl View for Label { .with_style(StyleProperty::FontWeight(self.weight)) .with_style(StyleProperty::FontStack(self.font.clone())) .with_line_break_mode(self.line_break_mode), - self.transform, ); (widget_pod, ()) } @@ -116,9 +105,6 @@ impl View for Label { _ctx: &mut ViewCtx, mut element: Mut, ) { - if prev.transform != self.transform { - element.set_transform(self.transform); - } if prev.label != self.label { widget::Label::set_text(&mut element, self.label.clone()); } diff --git a/xilem/src/view/portal.rs b/xilem/src/view/portal.rs index 2d4531e07..b9763c264 100644 --- a/xilem/src/view/portal.rs +++ b/xilem/src/view/portal.rs @@ -6,9 +6,7 @@ use std::marker::PhantomData; use masonry::widget; use crate::core::{DynMessage, Mut, ViewMarker}; -use crate::{Affine, MessageResult, Pod, View, ViewCtx, ViewId, WidgetView}; - -use super::Transformable; +use crate::{MessageResult, Pod, View, ViewCtx, ViewId, WidgetView}; /// A view which puts `child` into a scrollable region. /// @@ -19,7 +17,6 @@ where { Portal { child, - transform: Affine::IDENTITY, phantom: PhantomData, } } @@ -27,7 +24,6 @@ where #[must_use = "View values do nothing unless provided to Xilem."] pub struct Portal { child: V, - transform: Affine, phantom: PhantomData<(State, Action)>, } @@ -45,10 +41,7 @@ where // The Portal `View` doesn't get any messages directly (yet - scroll events?), so doesn't need to // use ctx.with_id. let (child, child_state) = self.child.build(ctx); - let widget_pod = ctx.new_pod_with_transform( - widget::Portal::new_pod(child.into_widget_pod()), - self.transform, - ); + let widget_pod = ctx.new_pod(widget::Portal::new_pod(child.into_widget_pod())); (widget_pod, child_state) } @@ -59,9 +52,6 @@ where ctx: &mut ViewCtx, mut element: Mut, ) { - if prev.transform != self.transform { - element.set_transform(self.transform); - } let child_element = widget::Portal::child_mut(&mut element); self.child .rebuild(&prev.child, view_state, ctx, child_element); @@ -87,9 +77,3 @@ where self.child.message(view_state, id_path, message, app_state) } } - -impl Transformable for Portal { - fn transform_mut(&mut self) -> &mut Affine { - &mut self.transform - } -} diff --git a/xilem/src/view/progress_bar.rs b/xilem/src/view/progress_bar.rs index ef83c3ecf..7917788b8 100644 --- a/xilem/src/view/progress_bar.rs +++ b/xilem/src/view/progress_bar.rs @@ -4,26 +4,14 @@ use masonry::widget; use crate::core::{DynMessage, Mut, ViewMarker}; -use crate::{Affine, MessageResult, Pod, View, ViewCtx, ViewId}; - -use super::Transformable; +use crate::{MessageResult, Pod, View, ViewCtx, ViewId}; pub fn progress_bar(progress: Option) -> ProgressBar { - ProgressBar { - progress, - transform: Affine::IDENTITY, - } + ProgressBar { progress } } pub struct ProgressBar { progress: Option, - transform: Affine, -} - -impl Transformable for ProgressBar { - fn transform_mut(&mut self) -> &mut Affine { - &mut self.transform - } } impl ViewMarker for ProgressBar {} @@ -32,9 +20,7 @@ impl View for ProgressBar { type ViewState = (); fn build(&self, ctx: &mut ViewCtx) -> (Self::Element, Self::ViewState) { - ctx.with_leaf_action_widget(|ctx| { - ctx.new_pod_with_transform(widget::ProgressBar::new(self.progress), self.transform) - }) + ctx.with_leaf_action_widget(|ctx| ctx.new_pod(widget::ProgressBar::new(self.progress))) } fn rebuild( @@ -44,9 +30,6 @@ impl View for ProgressBar { _ctx: &mut ViewCtx, mut element: Mut, ) { - if prev.transform != self.transform { - element.set_transform(self.transform); - } if prev.progress != self.progress { widget::ProgressBar::set_progress(&mut element, self.progress); } diff --git a/xilem/src/view/prose.rs b/xilem/src/view/prose.rs index 749ee4f05..191ab4d01 100644 --- a/xilem/src/view/prose.rs +++ b/xilem/src/view/prose.rs @@ -6,9 +6,7 @@ use masonry::widget::{self, LineBreaking}; use vello::peniko::Brush; use crate::core::{DynMessage, Mut, ViewMarker}; -use crate::{Affine, Color, MessageResult, Pod, TextAlignment, View, ViewCtx, ViewId}; - -use super::Transformable; +use crate::{Color, MessageResult, Pod, TextAlignment, View, ViewCtx, ViewId}; pub fn prose(content: impl Into) -> Prose { Prose { @@ -17,7 +15,6 @@ pub fn prose(content: impl Into) -> Prose { alignment: TextAlignment::default(), text_size: masonry::theme::TEXT_SIZE_NORMAL, line_break_mode: LineBreaking::WordWrap, - transform: Affine::IDENTITY, } } @@ -39,7 +36,6 @@ pub struct Prose { alignment: TextAlignment, text_size: f32, line_break_mode: LineBreaking, - transform: Affine, // TODO: disabled: bool, // TODO: add more attributes of `masonry::widget::Prose` } @@ -71,12 +67,6 @@ fn line_break_clips(linebreaking: LineBreaking) -> bool { matches!(linebreaking, LineBreaking::Clip | LineBreaking::WordWrap) } -impl Transformable for Prose { - fn transform_mut(&mut self) -> &mut Affine { - &mut self.transform - } -} - impl ViewMarker for Prose {} impl View for Prose { type Element = Pod; @@ -88,10 +78,9 @@ impl View for Prose { .with_alignment(self.alignment) .with_style(StyleProperty::FontSize(self.text_size)) .with_word_wrap(self.line_break_mode == LineBreaking::WordWrap); - let widget_pod = ctx.new_pod_with_transform( + let widget_pod = ctx.new_pod( widget::Prose::from_text_area(text_area) .with_clip(line_break_clips(self.line_break_mode)), - self.transform, ); (widget_pod, ()) } @@ -103,9 +92,6 @@ impl View for Prose { _ctx: &mut ViewCtx, mut element: Mut, ) { - if prev.transform != self.transform { - element.set_transform(self.transform); - } let mut text_area = widget::Prose::text_mut(&mut element); if prev.content != self.content { widget::TextArea::reset_text(&mut text_area, &self.content); diff --git a/xilem/src/view/sized_box.rs b/xilem/src/view/sized_box.rs index a1b33a6dd..829d3f2a1 100644 --- a/xilem/src/view/sized_box.rs +++ b/xilem/src/view/sized_box.rs @@ -9,9 +9,7 @@ use vello::kurbo::RoundedRectRadii; use vello::peniko::Brush; use crate::core::{DynMessage, Mut, View, ViewId, ViewMarker}; -use crate::{Affine, Pod, ViewCtx, WidgetView}; - -use super::Transformable; +use crate::{Pod, ViewCtx, WidgetView}; /// A widget with predefined size. /// @@ -31,7 +29,6 @@ where corner_radius: RoundedRectRadii::from_single_radius(0.0), padding: Padding::ZERO, phantom: PhantomData, - transform: Affine::IDENTITY, } } @@ -45,7 +42,6 @@ pub struct SizedBox { corner_radius: RoundedRectRadii, padding: Padding, phantom: PhantomData (State, Action)>, - transform: Affine, } impl SizedBox { @@ -125,12 +121,6 @@ impl SizedBox { } } -impl Transformable for SizedBox { - fn transform_mut(&mut self) -> &mut Affine { - &mut self.transform - } -} - impl ViewMarker for SizedBox {} impl View for SizedBox where @@ -154,7 +144,7 @@ where if let Some(border) = &self.border { widget = widget.border(border.brush.clone(), border.width); } - let pod = ctx.new_pod_with_transform(widget, self.transform); + let pod = ctx.new_pod(widget); (pod, child_state) } @@ -165,9 +155,6 @@ where ctx: &mut ViewCtx, mut element: Mut, ) { - if prev.transform != self.transform { - element.set_transform(self.transform); - } if self.width != prev.width { match self.width { Some(width) => widget::SizedBox::set_width(&mut element, width), diff --git a/xilem/src/view/spinner.rs b/xilem/src/view/spinner.rs index d2319f8f1..ddf983c0e 100644 --- a/xilem/src/view/spinner.rs +++ b/xilem/src/view/spinner.rs @@ -4,9 +4,7 @@ use masonry::{widget, Color}; use crate::core::{DynMessage, Mut, ViewMarker}; -use crate::{Affine, MessageResult, Pod, View, ViewCtx, ViewId}; - -use super::Transformable; +use crate::{MessageResult, Pod, View, ViewCtx, ViewId}; /// An indefinite spinner. /// @@ -34,10 +32,7 @@ use super::Transformable; /// } /// ``` pub fn spinner() -> Spinner { - Spinner { - color: None, - transform: Affine::IDENTITY, - } + Spinner { color: None } } /// The [`View`] created by [`spinner`]. @@ -46,7 +41,6 @@ pub fn spinner() -> Spinner { #[must_use = "View values do nothing unless provided to Xilem."] pub struct Spinner { color: Option, - transform: Affine, } impl Spinner { @@ -57,19 +51,13 @@ impl Spinner { } } -impl Transformable for Spinner { - fn transform_mut(&mut self) -> &mut Affine { - &mut self.transform - } -} - impl ViewMarker for Spinner {} impl View for Spinner { type Element = Pod; type ViewState = (); fn build(&self, ctx: &mut ViewCtx) -> (Self::Element, Self::ViewState) { - let pod = ctx.new_pod_with_transform(widget::Spinner::new(), self.transform); + let pod = ctx.new_pod(widget::Spinner::new()); (pod, ()) } @@ -80,9 +68,6 @@ impl View for Spinner { _: &mut ViewCtx, mut element: Mut, ) { - if prev.transform != self.transform { - element.set_transform(self.transform); - } if prev.color != self.color { match self.color { Some(color) => widget::Spinner::set_color(&mut element, color), diff --git a/xilem/src/view/textbox.rs b/xilem/src/view/textbox.rs index 8ce8f8c2a..03b9472ed 100644 --- a/xilem/src/view/textbox.rs +++ b/xilem/src/view/textbox.rs @@ -5,9 +5,7 @@ use masonry::widget; use vello::peniko::Brush; use crate::core::{DynMessage, Mut, View, ViewMarker}; -use crate::{Affine, Color, MessageResult, Pod, TextAlignment, ViewCtx, ViewId}; - -use super::Transformable; +use crate::{Color, MessageResult, Pod, TextAlignment, ViewCtx, ViewId}; // FIXME - A major problem of the current approach (always setting the textbox contents) // is that if the user forgets to hook up the modify the state's contents in the callback, @@ -26,7 +24,6 @@ where on_enter: None, text_brush: Color::WHITE.into(), alignment: TextAlignment::default(), - transform: Affine::IDENTITY, // TODO?: disabled: false, } } @@ -38,7 +35,6 @@ pub struct Textbox { on_enter: Option>, text_brush: Brush, alignment: TextAlignment, - transform: Affine, // TODO: add more attributes of `masonry::widget::TextBox` } @@ -63,12 +59,6 @@ impl Textbox { } } -impl Transformable for Textbox { - fn transform_mut(&mut self) -> &mut Affine { - &mut self.transform - } -} - impl ViewMarker for Textbox {} impl View for Textbox { type Element = Pod; @@ -84,7 +74,7 @@ impl View for Textbox View for Textbox, ) { - if prev.transform != self.transform { - element.set_transform(self.transform); - } let mut text_area = widget::Textbox::text_mut(&mut element); // Unlike the other properties, we don't compare to the previous value; diff --git a/xilem/src/view/transform.rs b/xilem/src/view/transform.rs index 555468d46..054b6f60c 100644 --- a/xilem/src/view/transform.rs +++ b/xilem/src/view/transform.rs @@ -126,8 +126,3 @@ where self.child.message(view_state, id_path, message, app_state) } } - -/// An extension trait, to allow common transformations of the views transform. -pub trait Transformable: Sized { - fn transform_mut(&mut self) -> &mut Affine; -} diff --git a/xilem/src/view/variable_label.rs b/xilem/src/view/variable_label.rs index e2a73c1c6..1c8b697ee 100644 --- a/xilem/src/view/variable_label.rs +++ b/xilem/src/view/variable_label.rs @@ -10,7 +10,7 @@ use xilem_core::ViewPathTracker; use crate::core::{DynMessage, Mut, ViewMarker}; use crate::{MessageResult, Pod, View, ViewCtx, ViewId}; -use super::{label, Label, Transformable}; +use super::{label, Label}; /// A view for displaying non-editable text, with a variable [weight](masonry::parley::style::FontWeight). pub fn variable_label(text: impl Into) -> VariableLabel { @@ -81,12 +81,6 @@ impl VariableLabel { } } -impl Transformable for VariableLabel { - fn transform_mut(&mut self) -> &mut crate::Affine { - self.label.transform_mut() - } -} - impl ViewMarker for VariableLabel {} impl View for VariableLabel { type Element = Pod; diff --git a/xilem/src/view/zstack.rs b/xilem/src/view/zstack.rs index e2f48d90e..160a4dd8b 100644 --- a/xilem/src/view/zstack.rs +++ b/xilem/src/view/zstack.rs @@ -10,7 +10,7 @@ use crate::{ AppendVec, DynMessage, ElementSplice, Mut, SuperElement, View, ViewElement, ViewMarker, ViewSequence, }, - Affine, Pod, ViewCtx, WidgetView, + Pod, ViewCtx, WidgetView, }; use masonry::{ widget::{self, Alignment, ChildAlignment, WidgetMut}, @@ -18,8 +18,6 @@ use masonry::{ }; use xilem_core::{MessageResult, ViewId}; -use super::Transformable; - /// A widget that lays out its children on top of each other. /// The children are laid out back to front. /// @@ -42,7 +40,6 @@ pub fn zstack>(sequence: Seq) ZStack { sequence, alignment: Alignment::default(), - transform: Affine::IDENTITY, } } @@ -53,7 +50,6 @@ pub fn zstack>(sequence: Seq) pub struct ZStack { sequence: Seq, alignment: Alignment, - transform: Affine, } impl ZStack { @@ -82,7 +78,7 @@ where for child in elements.into_inner() { widget = widget.with_child_pod(child.widget.into_widget_pod(), child.alignment); } - let pod = ctx.new_pod_with_transform(widget, self.transform); + let pod = ctx.new_pod(widget); (pod, seq_state) } @@ -93,10 +89,6 @@ where ctx: &mut ViewCtx, mut element: Mut, ) { - if self.transform != prev.transform { - element.set_transform(self.transform); - } - if self.alignment != prev.alignment { widget::ZStack::set_alignment(&mut element, self.alignment); } @@ -130,12 +122,6 @@ where } } -impl Transformable for ZStack { - fn transform_mut(&mut self) -> &mut Affine { - &mut self.transform - } -} - // --- MARK: ZStackExt --- /// A trait that extends a [`WidgetView`] with methods to provide parameters for a parent [`ZStack`]. From fe95af704c602113e02099eab2f0e869ae693d09 Mon Sep 17 00:00:00 2001 From: Daniel McNab <36049421+DJMcNab@users.noreply.github.com> Date: Fri, 17 Jan 2025 09:00:36 +0000 Subject: [PATCH 4/8] Make `Transformed` support nesting --- masonry/src/contexts.rs | 5 ++++ xilem/src/lib.rs | 19 +++++------- xilem/src/view/transform.rs | 59 +++++++++++++++++++++++++++++-------- 3 files changed, 60 insertions(+), 23 deletions(-) diff --git a/masonry/src/contexts.rs b/masonry/src/contexts.rs index ae6c25ab7..1a4d8efd4 100644 --- a/masonry/src/contexts.rs +++ b/masonry/src/contexts.rs @@ -163,6 +163,11 @@ impl_context_method!( .expect("get_child_state: child not found"); child_state_ref.item } + + /// The current (local) transform of this widget. + pub fn transform(&self) -> Affine { + self.widget_state.transform + } } ); diff --git a/xilem/src/lib.rs b/xilem/src/lib.rs index 94e7ebaad..67e55c8a4 100644 --- a/xilem/src/lib.rs +++ b/xilem/src/lib.rs @@ -189,7 +189,12 @@ where pub struct Pod { pub widget: W, pub id: WidgetId, - pub transform: Option, + /// The transform the widget will be created with. + /// + /// If changing transforms of widgets, prefer to use [`WidgetView::transformed`]. + /// This has a protocol to ensure that multiple views changing the + /// transform interoperate successfully. + pub transform: Affine, } impl Pod { @@ -201,18 +206,10 @@ impl Pod { } } fn into_widget_pod(self) -> WidgetPod { - WidgetPod::new_with_id_and_transform( - self.widget, - self.id, - self.transform.unwrap_or_default(), - ) + WidgetPod::new_with_id_and_transform(self.widget, self.id, self.transform) } fn boxed_widget_pod(self) -> WidgetPod> { - WidgetPod::new_with_id_and_transform( - Box::new(self.widget), - self.id, - self.transform.unwrap_or_default(), - ) + WidgetPod::new_with_id_and_transform(Box::new(self.widget), self.id, self.transform) } fn boxed(self) -> Pod> { Pod { diff --git a/xilem/src/view/transform.rs b/xilem/src/view/transform.rs index 054b6f60c..5af650f86 100644 --- a/xilem/src/view/transform.rs +++ b/xilem/src/view/transform.rs @@ -74,6 +74,20 @@ impl Transformed { } } +mod private { + use crate::Affine; + + /// The View state for the [Transformed](super::Transformed) + #[expect( + unnameable_types, + reason = "This type has no public API, and is only public due to trait visibility rules" + )] + pub struct TransformedState { + pub(super) child: ChildState, + pub(super) previous_transform: Affine, + } +} + impl ViewMarker for Transformed {} impl View for Transformed where @@ -82,16 +96,16 @@ where Action: 'static, { type Element = Pod; - type ViewState = Child::ViewState; + type ViewState = private::TransformedState; fn build(&self, ctx: &mut ViewCtx) -> (Self::Element, Self::ViewState) { let (mut child_pod, child_state) = self.child.build(ctx); - // TODO: Use a marker identity value to detect this more properly - if child_pod.transform.is_some() { - panic!("Tried to create a `Transformed` with an already controlled Transform"); - } - child_pod.transform = Some(self.transform); - (child_pod, child_state) + let state = private::TransformedState { + child: child_state, + previous_transform: child_pod.transform, + }; + child_pod.transform = self.transform * state.previous_transform; + (child_pod, state) } fn rebuild( @@ -101,10 +115,30 @@ where ctx: &mut ViewCtx, mut element: xilem_core::Mut<'_, Self::Element>, ) { - if self.transform != prev.transform { - element.ctx.set_transform(self.transform); + let initial_transform = element.ctx.transform(); + self.child.rebuild( + &prev.child, + &mut view_state.child, + ctx, + element.reborrow_mut(), + ); + let transform_changed = element.ctx.transform() != initial_transform; + // We detect a child view changing the transform by comparing the + // resulting transform before and after the child view runs. + if transform_changed { + // If it has changed the transform, then we know that it will only be due to effects + // "below us" (that is, it will have restarted from scratch). + // We update our stored understanding of the transforms below us + view_state.previous_transform = element.ctx.transform(); + // This is a convention used to communicate with ourselves, which could + // break down if any other view handles transforms differently. + // However, we document against this in `Pod::transform`. + } + if self.transform != prev.transform || transform_changed { + element + .ctx + .set_transform(self.transform * view_state.previous_transform); } - self.child.rebuild(&prev.child, view_state, ctx, element); } fn teardown( @@ -113,7 +147,7 @@ where ctx: &mut ViewCtx, element: xilem_core::Mut<'_, Self::Element>, ) { - self.child.teardown(view_state, ctx, element); + self.child.teardown(&mut view_state.child, ctx, element); } fn message( @@ -123,6 +157,7 @@ where message: DynMessage, app_state: &mut State, ) -> xilem_core::MessageResult { - self.child.message(view_state, id_path, message, app_state) + self.child + .message(&mut view_state.child, id_path, message, app_state) } } From 70e8efd74e7545ca381dba0527c204c563348cfa Mon Sep 17 00:00:00 2001 From: Daniel McNab <36049421+DJMcNab@users.noreply.github.com> Date: Fri, 17 Jan 2025 09:15:58 +0000 Subject: [PATCH 5/8] "Test" the new ability --- xilem/examples/transforms.rs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/xilem/examples/transforms.rs b/xilem/examples/transforms.rs index f5e0a25dd..c088d07f5 100644 --- a/xilem/examples/transforms.rs +++ b/xilem/examples/transforms.rs @@ -48,7 +48,12 @@ impl TransformsGame { .background(Color::new(bg_color)) .transformed() .translate(self.translation) + // In an actual app, you wouldn't use `.transformed` here. + // This is here to validate that Xilem's support for nested `Transformed` + // values works as expected. + .transformed() .rotate(self.rotation) + .transformed() .scale(self.scale); let controls = ( From 05190d6b591dab0187edcbe8f54c1c83d9276278 Mon Sep 17 00:00:00 2001 From: Daniel McNab <36049421+DJMcNab@users.noreply.github.com> Date: Fri, 17 Jan 2025 16:26:40 +0000 Subject: [PATCH 6/8] Expose `transform_has_changed` and use that instead --- masonry/src/contexts.rs | 9 +++++++++ xilem/src/view/transform.rs | 6 ++---- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/masonry/src/contexts.rs b/masonry/src/contexts.rs index 1a4d8efd4..3d9dbcfc0 100644 --- a/masonry/src/contexts.rs +++ b/masonry/src/contexts.rs @@ -236,6 +236,15 @@ impl MutateCtx<'_> { widget_children: self.widget_children.reborrow_mut(), } } + + /// Whether the (local) transform of this widget has been modified since + /// the last time this widget's transformation was resolved. + /// + /// This is exposed for Xilem, and is more likely to change or be removed + /// in major releases of Masonry. + pub fn transform_has_changed(&self) -> bool { + self.widget_state.transform_changed + } } // --- MARK: WIDGET_REF --- diff --git a/xilem/src/view/transform.rs b/xilem/src/view/transform.rs index 5af650f86..8a2e974eb 100644 --- a/xilem/src/view/transform.rs +++ b/xilem/src/view/transform.rs @@ -115,16 +115,14 @@ where ctx: &mut ViewCtx, mut element: xilem_core::Mut<'_, Self::Element>, ) { - let initial_transform = element.ctx.transform(); self.child.rebuild( &prev.child, &mut view_state.child, ctx, element.reborrow_mut(), ); - let transform_changed = element.ctx.transform() != initial_transform; - // We detect a child view changing the transform by comparing the - // resulting transform before and after the child view runs. + let transform_changed = element.ctx.transform_has_changed(); + // If the child view changed the transform, we know we're out of date. if transform_changed { // If it has changed the transform, then we know that it will only be due to effects // "below us" (that is, it will have restarted from scratch). From 77aebb4e484dffe342811c12f62c23ab9b8b8be6 Mon Sep 17 00:00:00 2001 From: Daniel McNab <36049421+DJMcNab@users.noreply.github.com> Date: Fri, 17 Jan 2025 17:28:47 +0000 Subject: [PATCH 7/8] Make extension method `.transform` --- xilem/examples/transforms.rs | 20 +++++++++----------- xilem/src/lib.rs | 10 +++++----- 2 files changed, 14 insertions(+), 16 deletions(-) diff --git a/xilem/examples/transforms.rs b/xilem/examples/transforms.rs index c088d07f5..edfc8648e 100644 --- a/xilem/examples/transforms.rs +++ b/xilem/examples/transforms.rs @@ -6,8 +6,8 @@ use std::f64::consts::{PI, TAU}; use winit::error::EventLoopError; -use xilem::view::{button, grid, label, sized_box, GridExt as _}; -use xilem::{Color, EventLoop, Vec2, WidgetView, Xilem}; +use xilem::view::{button, grid, label, sized_box, transformed, GridExt as _}; +use xilem::{Affine, Color, EventLoop, Vec2, WidgetView, Xilem}; struct TransformsGame { rotation: f64, @@ -41,20 +41,18 @@ impl TransformsGame { [1.0, 0.0, 0.0, 0.2] }; + let status = sized_box(status).background(Color::new(bg_color)); // Every view can be transformed similar as with CSS transforms in the web. // Currently only 2D transforms are supported. // Note that the order of the transformations is relevant. - let transformed_status = sized_box(status) - .background(Color::new(bg_color)) - .transformed() - .translate(self.translation) - // In an actual app, you wouldn't use `.transformed` here. + let transformed_status = transformed( + // In an actual app, you wouldn't use both `transformed` and `.transform`. // This is here to validate that Xilem's support for nested `Transformed` // values works as expected. - .transformed() - .rotate(self.rotation) - .transformed() - .scale(self.scale); + status.transform(Affine::translate(self.translation)), + ) + .rotate(self.rotation) + .scale(self.scale); let controls = ( button("↶", |this: &mut Self| { diff --git a/xilem/src/lib.rs b/xilem/src/lib.rs index 67e55c8a4..949217693 100644 --- a/xilem/src/lib.rs +++ b/xilem/src/lib.rs @@ -266,14 +266,14 @@ pub trait WidgetView: /// This widget with a 2d transform applied. /// - /// The returned view has methods to control individual components of - /// the transform, as well as apply an arbitrary transform. - /// See [`transformed`] for more details. - fn transformed(self) -> Transformed + /// See [`transformed`] for similar functionality with a builder-API using this. + /// The return type is the same as for `transformed`, and so also has these + /// builder methods. + fn transform(self, by: Affine) -> Transformed where Self: Sized, { - transformed(self) + transformed(self).transform(by) } } From 8eda8fbbf904e8f2dca69ef499fbb503826847cb Mon Sep 17 00:00:00 2001 From: Daniel McNab <36049421+DJMcNab@users.noreply.github.com> Date: Fri, 17 Jan 2025 17:37:45 +0000 Subject: [PATCH 8/8] Fixup docs --- xilem/src/lib.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/xilem/src/lib.rs b/xilem/src/lib.rs index 949217693..f5f554a7f 100644 --- a/xilem/src/lib.rs +++ b/xilem/src/lib.rs @@ -191,7 +191,8 @@ pub struct Pod { pub id: WidgetId, /// The transform the widget will be created with. /// - /// If changing transforms of widgets, prefer to use [`WidgetView::transformed`]. + /// If changing transforms of widgets, prefer to use [`transformed`] + /// (or [`WidgetView::transform`]). /// This has a protocol to ensure that multiple views changing the /// transform interoperate successfully. pub transform: Affine,