From bb904fb948872bde860cd2b086ac4ed75f583b3e Mon Sep 17 00:00:00 2001 From: Kaur Kuut Date: Sat, 2 May 2026 14:41:11 +0300 Subject: [PATCH 1/4] Change widget local coordinates to logical pixels. --- masonry/examples/custom_widget.rs | 10 +- masonry/examples/layers.rs | 6 +- masonry/src/doc/color_rectangle.rs | 15 +- .../src/doc/implementing_container_widget.md | 21 ++- masonry/src/doc/implementing_widget.md | 15 +- masonry/src/doc/vertical_stack.rs | 19 +- masonry/src/layers/selector_menu.rs | 31 ++- masonry/src/layers/tooltip.rs | 6 +- masonry/src/properties/object_fit.rs | 26 +-- masonry/src/tests/action.rs | 4 +- masonry/src/tests/compose.rs | 6 +- masonry/src/tests/event.rs | 2 +- masonry/src/tests/layout.rs | 6 +- masonry/src/tests/paint.rs | 14 +- masonry/src/tests/update.rs | 23 ++- masonry/src/widgets/align.rs | 8 +- masonry/src/widgets/badge.rs | 6 +- masonry/src/widgets/badged.rs | 6 +- masonry/src/widgets/button.rs | 12 +- masonry/src/widgets/canvas.rs | 10 +- masonry/src/widgets/checkbox.rs | 38 ++-- masonry/src/widgets/collapse_panel.rs | 71 ++++--- masonry/src/widgets/disclosure_button.rs | 17 +- masonry/src/widgets/divider.rs | 37 ++-- masonry/src/widgets/flex.rs | 136 +++++++------- masonry/src/widgets/grid.rs | 28 +-- masonry/src/widgets/image.rs | 28 +-- masonry/src/widgets/indexed_stack.rs | 8 +- masonry/src/widgets/label.rs | 14 +- masonry/src/widgets/pagination.rs | 10 +- masonry/src/widgets/passthrough.rs | 6 +- masonry/src/widgets/portal.rs | 39 ++-- masonry/src/widgets/progress_bar.rs | 16 +- masonry/src/widgets/prose.rs | 6 +- masonry/src/widgets/radio_button.rs | 38 ++-- masonry/src/widgets/radio_group.rs | 6 +- masonry/src/widgets/resize_observer.rs | 6 +- masonry/src/widgets/scroll_bar.rs | 36 ++-- masonry/src/widgets/selector.rs | 14 +- masonry/src/widgets/selector_item.rs | 6 +- masonry/src/widgets/sized_box.rs | 24 ++- masonry/src/widgets/slider.rs | 20 +- masonry/src/widgets/spinner.rs | 12 +- masonry/src/widgets/split.rs | 176 +++++++----------- masonry/src/widgets/step_input.rs | 30 ++- masonry/src/widgets/svg.rs | 28 +-- masonry/src/widgets/switch.rs | 31 +-- masonry/src/widgets/text_area.rs | 14 +- masonry/src/widgets/text_input.rs | 6 +- masonry/src/widgets/variable_label.rs | 6 +- masonry/src/widgets/virtual_scroll.rs | 16 +- masonry/src/widgets/zstack.rs | 8 +- masonry_core/src/app/layer_stack.rs | 8 +- masonry_core/src/core/contexts.rs | 43 +---- masonry_core/src/core/widget.rs | 24 +-- masonry_core/src/layout/dim.rs | 18 +- masonry_core/src/layout/layout_size.rs | 63 ++----- masonry_core/src/layout/len_def.rs | 77 +------- masonry_core/src/layout/len_req.rs | 41 +--- masonry_core/src/layout/length.rs | 45 ++++- masonry_core/src/layout/measurement_cache.rs | 12 +- masonry_core/src/layout/size_def.rs | 72 ++----- masonry_core/src/passes/event.rs | 1 - masonry_core/src/passes/layout.rs | 137 ++++---------- masonry_core/src/properties/border_width.rs | 60 +++--- masonry_core/src/properties/padding.rs | 60 +++--- masonry_testing/src/modular_widget.rs | 27 ++- masonry_testing/src/recorder_widget.rs | 8 +- masonry_testing/src/wrapper_widget.rs | 6 +- xilem_masonry/src/one_of.rs | 6 +- 70 files changed, 740 insertions(+), 1145 deletions(-) diff --git a/masonry/examples/custom_widget.rs b/masonry/examples/custom_widget.rs index 0d2671cd7c..8c85743b71 100644 --- a/masonry/examples/custom_widget.rs +++ b/masonry/examples/custom_widget.rs @@ -16,7 +16,7 @@ use masonry::core::{ }; use masonry::imaging::Painter; use masonry::kurbo::{Affine, Axis, BezPath, Point, Rect, Size, Stroke}; -use masonry::layout::LenReq; +use masonry::layout::{AsUnit, LenReq, Length}; use masonry::parley::FontFamilyName; use masonry::parley::style::{FontFamily, GenericFamily, StyleProperty}; use masonry::peniko::{Color, ImageBrush, ImageFormat}; @@ -80,16 +80,16 @@ impl Widget for CustomWidget { _props: &PropertiesRef<'_>, axis: Axis, len_req: LenReq, - _cross_length: Option, - ) -> f64 { + _cross_length: Option, + ) -> Length { // We currently just define our preferred min/max, // but often it takes actual work to derive these. let min_size = Size::new(100., 50.); let max_size = Size::new(200., 200.); // Measurement is per axis, so we only care about a single dimension right now - let min_length = min_size.get_coord(axis); - let max_length = max_size.get_coord(axis); + let min_length = min_size.get_coord(axis).px(); + let max_length = max_size.get_coord(axis).px(); // Return a result based on the parent's request match len_req { diff --git a/masonry/examples/layers.rs b/masonry/examples/layers.rs index e5115b3b18..5d0e72e197 100644 --- a/masonry/examples/layers.rs +++ b/masonry/examples/layers.rs @@ -15,7 +15,7 @@ use masonry::core::{ use masonry::imaging::Painter; use masonry::kurbo::{Axis, Point, Size, Vec2}; use masonry::layers::Tooltip; -use masonry::layout::{AsUnit, LayoutSize, LenReq, SizeDef}; +use masonry::layout::{AsUnit, LayoutSize, LenReq, Length, SizeDef}; use masonry::parley::FontWeight; use masonry::peniko::Color; use masonry::properties::{Background, BorderColor, BorderWidth, ContentColor}; @@ -117,8 +117,8 @@ impl Widget for OverlayBox { _props: &PropertiesRef<'_>, axis: Axis, len_req: LenReq, - cross_length: Option, - ) -> f64 { + cross_length: Option, + ) -> Length { let auto_length = len_req.into(); let context_size = LayoutSize::maybe(axis.cross(), cross_length); diff --git a/masonry/src/doc/color_rectangle.rs b/masonry/src/doc/color_rectangle.rs index 930ab9bff0..be3498409c 100644 --- a/masonry/src/doc/color_rectangle.rs +++ b/masonry/src/doc/color_rectangle.rs @@ -25,13 +25,14 @@ use masonry::core::{Update, UpdateCtx}; // --- use masonry::core::{LayoutCtx, MeasureCtx, PropertiesRef}; use masonry::kurbo::{Axis, Size}; -use masonry::layout::LenReq; +use masonry::layout::{LenReq, Length}; // --- use masonry::accesskit::{Node, Role}; use masonry::core::{AccessCtx, PaintCtx}; use masonry::imaging::Painter; // --- use masonry::core::WidgetId; +use masonry_core::layout::AsUnit; use tracing::{Span, trace_span}; // --- use masonry::core::{ChildrenIds, RegisterCtx}; @@ -130,16 +131,12 @@ impl Widget for ColorRectangle { _props: &PropertiesRef<'_>, axis: Axis, len_req: LenReq, - _cross_length: Option, - ) -> f64 { - // TODO: Remove HACK: Until scale factor rework happens, just pretend it's always 1.0. - // https://github.com/linebender/xilem/issues/1264 - let scale = 1.0; - + _cross_length: Option, + ) -> Length { match len_req { LenReq::MinContent | LenReq::MaxContent => match axis { - Axis::Horizontal => 200. * scale, - Axis::Vertical => 100. * scale, + Axis::Horizontal => 200.px(), + Axis::Vertical => 100.px(), }, LenReq::FitContent(space) => space, } diff --git a/masonry/src/doc/implementing_container_widget.md b/masonry/src/doc/implementing_container_widget.md index c41430a20b..ae4fdad8d1 100644 --- a/masonry/src/doc/implementing_container_widget.md +++ b/masonry/src/doc/implementing_container_widget.md @@ -54,7 +54,7 @@ A container widget needs to pay special attention to these methods: trait Widget { // ... - fn measure(&mut self, ctx: &mut MeasureCtx<'_>, props: &PropertiesRef<'_>, axis: Axis, len_req: LenReq, cross_length: Option) -> f64; + fn measure(&mut self, ctx: &mut MeasureCtx<'_>, props: &PropertiesRef<'_>, axis: Axis, len_req: LenReq, cross_length: Option) -> Length; fn layout(&mut self, ctx: &mut LayoutCtx<'_>, props: &PropertiesRef<'_>, size: Size); fn compose(&mut self, ctx: &mut ComposeCtx<'_>); @@ -87,7 +87,7 @@ For our `VerticalStack`, we'll lay out our children in a vertical line, with a g ```rust,ignore use masonry::core::{LayoutCtx, MeasureCtx, PropertiesRef}; use masonry::kurbo::{Axis, Point, Size}; -use masonry::layout::{LayoutSize, LenDef, LenReq, SizeDef}; +use masonry::layout::{AsUnit, LayoutSize, Length, LenDef, LenReq, SizeDef}; impl Widget for VerticalStack { // ... @@ -98,28 +98,29 @@ impl Widget for VerticalStack { _props: &PropertiesRef<'_>, axis: Axis, len_req: LenReq, - cross_length: Option, - ) -> f64 { + cross_length: Option, + ) -> Length { let (len_req, min_result) = match len_req { - LenReq::MinContent | LenReq::MaxContent => (len_req, 0.), + LenReq::MinContent | LenReq::MaxContent => (len_req, Length::ZERO), LenReq::FitContent(space) => (LenReq::MinContent, space), }; let auto_length = len_req.into(); let context_size = LayoutSize::maybe(axis.cross(), cross_length); - let mut length: f64 = 0.; + let mut length = Length::ZERO; for child in &mut self.children { let child_length = ctx.compute_length(child, auto_length, context_size, axis, cross_length); match axis { Axis::Horizontal => length = length.max(child_length), - Axis::Vertical => length += child_length, + Axis::Vertical => length = length.saturating_add(child_length), } } if axis == Axis::Vertical { let gap_count = (self.children.len() - 1) as f64; - length += gap_count * self.gap; + let gaps_length = gap_count * self.gap; + length = length.saturating_add(gaps_length.px()); } min_result.max(length) @@ -135,8 +136,8 @@ impl Widget for VerticalStack { let total_child_vertical_space = size.height - self.gap * gap_count; let child_vertical_space = total_child_vertical_space / self.children.len() as f64; - let width_def = LenDef::FitContent(size.width); - let height_def = LenDef::FitContent(child_vertical_space.max(0.)); + let width_def = LenDef::FitContent(size.width.px()); + let height_def = LenDef::FitContent(child_vertical_space.max(0.).px()); let auto_size = SizeDef::new(width_def, height_def); let context_size = size.into(); diff --git a/masonry/src/doc/implementing_widget.md b/masonry/src/doc/implementing_widget.md index 44e135cf35..c72e1be550 100644 --- a/masonry/src/doc/implementing_widget.md +++ b/masonry/src/doc/implementing_widget.md @@ -32,7 +32,7 @@ trait Widget { fn on_anim_frame(&mut self, ctx: &mut UpdateCtx<'_>, props: &mut PropertiesMut<'_>, interval: u64); fn update(&mut self, ctx: &mut UpdateCtx<'_>, props: &mut PropertiesMut<'_>, event: &Update); - fn measure(&mut self, ctx: &mut MeasureCtx<'_>, props: &PropertiesRef<'_>, axis: Axis, len_req: LenReq, cross_length: Option) -> f64; + fn measure(&mut self, ctx: &mut MeasureCtx<'_>, props: &PropertiesRef<'_>, axis: Axis, len_req: LenReq, cross_length: Option) -> Length; fn layout(&mut self, ctx: &mut LayoutCtx<'_>, props: &PropertiesRef<'_>, size: Size); fn paint(&mut self, ctx: &mut PaintCtx<'_>, props: &PropertiesRef<'_>, painter: &mut Painter<'_>); @@ -169,7 +169,7 @@ Next we implement layout: ```rust,ignore use masonry::core::{LayoutCtx, MeasureCtx, PropertiesRef}; use masonry::kurbo::{Axis, Size}; -use masonry::layout::LenReq; +use masonry::layout::{Length, LenReq}; impl Widget for ColorRectangle { // ... @@ -180,16 +180,13 @@ impl Widget for ColorRectangle { _props: &PropertiesRef<'_>, axis: Axis, len_req: LenReq, - _cross_length: Option, - ) -> f64 { - // TODO: Remove HACK: Until scale factor rework happens, just pretend it's always 1.0. - // https://github.com/linebender/xilem/issues/1264 - let scale = 1.0; + _cross_length: Option, + ) -> Length { match len_req { LenReq::MinContent | LenReq::MaxContent => match axis { - Axis::Horizontal => 200. * scale, - Axis::Vertical => 100. * scale, + Axis::Horizontal => 200.px(), + Axis::Vertical => 100.px(), } LenReq::FitContent(space) => space, } diff --git a/masonry/src/doc/vertical_stack.rs b/masonry/src/doc/vertical_stack.rs index 880d572188..cb19b86416 100644 --- a/masonry/src/doc/vertical_stack.rs +++ b/masonry/src/doc/vertical_stack.rs @@ -21,7 +21,7 @@ use masonry::core::{Widget, WidgetPod}; // --- use masonry::core::{LayoutCtx, MeasureCtx, PropertiesRef}; use masonry::kurbo::{Axis, Point, Size}; -use masonry::layout::{LayoutSize, LenDef, LenReq, SizeDef}; +use masonry::layout::{AsUnit, LayoutSize, LenDef, LenReq, Length, SizeDef}; // --- use masonry::core::ComposeCtx; // --- @@ -94,28 +94,29 @@ impl Widget for VerticalStack { _props: &PropertiesRef<'_>, axis: Axis, len_req: LenReq, - cross_length: Option, - ) -> f64 { + cross_length: Option, + ) -> Length { let (len_req, min_result) = match len_req { - LenReq::MinContent | LenReq::MaxContent => (len_req, 0.), + LenReq::MinContent | LenReq::MaxContent => (len_req, Length::ZERO), LenReq::FitContent(space) => (LenReq::MinContent, space), }; let auto_length = len_req.into(); let context_size = LayoutSize::maybe(axis.cross(), cross_length); - let mut length: f64 = 0.; + let mut length = Length::ZERO; for child in &mut self.children { let child_length = ctx.compute_length(child, auto_length, context_size, axis, cross_length); match axis { Axis::Horizontal => length = length.max(child_length), - Axis::Vertical => length += child_length, + Axis::Vertical => length = length.saturating_add(child_length), } } if axis == Axis::Vertical { let gap_count = (self.children.len() - 1) as f64; - length += gap_count * self.gap; + let gaps_length = gap_count * self.gap; + length = length.saturating_add(gaps_length.px()); } min_result.max(length) @@ -131,8 +132,8 @@ impl Widget for VerticalStack { let total_child_vertical_space = size.height - self.gap * gap_count; let child_vertical_space = total_child_vertical_space / self.children.len() as f64; - let width_def = LenDef::FitContent(size.width); - let height_def = LenDef::FitContent(child_vertical_space.max(0.)); + let width_def = LenDef::FitContent(size.width.px()); + let height_def = LenDef::FitContent(child_vertical_space.max(0.).px()); let auto_size = SizeDef::new(width_def, height_def); let context_size = size.into(); diff --git a/masonry/src/layers/selector_menu.rs b/masonry/src/layers/selector_menu.rs index 4603222115..951bbe3895 100644 --- a/masonry/src/layers/selector_menu.rs +++ b/masonry/src/layers/selector_menu.rs @@ -12,7 +12,7 @@ use crate::core::{ }; use crate::imaging::Painter; use crate::kurbo::{Axis, Point, Size}; -use crate::layout::{LayoutSize, LenDef, LenReq, SizeDef}; +use crate::layout::{AsUnit, LayoutSize, LenDef, LenReq, Length, SizeDef}; use crate::properties::Gap; use crate::widgets::{SelectionChanged, Selector}; @@ -236,58 +236,51 @@ impl Widget for SelectorMenu { props: &PropertiesRef<'_>, axis: Axis, len_req: LenReq, - cross_length: Option, - ) -> f64 { - // TODO: Remove HACK: Until scale factor rework happens, just pretend it's always 1.0. - // https://github.com/linebender/xilem/issues/1264 - let scale = 1.0; - + cross_length: Option, + ) -> Length { let cache = ctx.property_cache(); let gap = props.get::(cache); - let gap_length = gap.gap.dp(scale); + let gap_length = gap.gap.get(); let (len_req, min_result) = match len_req { - LenReq::MinContent | LenReq::MaxContent => (len_req, 0.), + LenReq::MinContent | LenReq::MaxContent => (len_req, Length::ZERO), LenReq::FitContent(space) => (LenReq::MinContent, space), }; let auto_length = len_req.into(); let context_size = LayoutSize::maybe(axis.cross(), cross_length); - let mut length: f64 = 0.; + let mut length = Length::ZERO; for child in &mut self.children { let child_length = ctx.compute_length(child, auto_length, context_size, axis, cross_length); match axis { Axis::Horizontal => length = length.max(child_length), - Axis::Vertical => length += child_length, + Axis::Vertical => length = length.saturating_add(child_length), } } if axis == Axis::Vertical { let gap_count = (self.children.len() - 1) as f64; - length += gap_count * gap_length; + let gaps_length = gap_count * gap_length; + length = length.saturating_add(gaps_length.px()); } min_result.max(length) } fn layout(&mut self, ctx: &mut LayoutCtx<'_>, props: &PropertiesRef<'_>, size: Size) { - // TODO: Remove HACK: Until scale factor rework happens, just pretend it's always 1.0. - // https://github.com/linebender/xilem/issues/1264 - let scale = 1.0; - let cache = ctx.property_cache(); let gap = props.get::(cache); let gap_count = (self.children.len() - 1) as f64; - let gap_length = gap.gap.dp(scale); + let gap_length = gap.gap.get(); let total_child_vertical_space = size.height - gap_length * gap_count; let child_vertical_space = total_child_vertical_space / self.children.len() as f64; - let width_def = LenDef::FitContent(size.width); - let height_def = LenDef::FitContent(child_vertical_space.max(0.)); + let width_def = LenDef::FitContent(size.width.px()); + let height_def = LenDef::FitContent(child_vertical_space.max(0.).px()); let auto_size = SizeDef::new(width_def, height_def); let context_size = size.into(); diff --git a/masonry/src/layers/tooltip.rs b/masonry/src/layers/tooltip.rs index 2e489919d2..78953fff61 100644 --- a/masonry/src/layers/tooltip.rs +++ b/masonry/src/layers/tooltip.rs @@ -13,7 +13,7 @@ use crate::core::{ }; use crate::imaging::Painter; use crate::kurbo::{Axis, Size}; -use crate::layout::{LayoutSize, LenReq, SizeDef}; +use crate::layout::{LayoutSize, LenReq, Length, SizeDef}; /// A [`Layer`] representing a simple tooltip showing some content until the mouse moves. pub struct Tooltip { @@ -94,8 +94,8 @@ impl Widget for Tooltip { _props: &PropertiesRef<'_>, axis: Axis, len_req: LenReq, - cross_length: Option, - ) -> f64 { + cross_length: Option, + ) -> Length { let auto_length = len_req.into(); let context_size = LayoutSize::maybe(axis.cross(), cross_length); diff --git a/masonry/src/properties/object_fit.rs b/masonry/src/properties/object_fit.rs index caf9f3bbf4..673fada38c 100644 --- a/masonry/src/properties/object_fit.rs +++ b/masonry/src/properties/object_fit.rs @@ -3,7 +3,7 @@ use crate::core::Property; use crate::kurbo::{Affine, Axis, Size}; -use crate::layout::LenReq; +use crate::layout::{LenReq, Length}; use crate::util::Sanitize; // These are based on https://developer.mozilla.org/en-US/docs/Web/CSS/object-fit @@ -121,7 +121,7 @@ impl ObjectFit { Affine::new([scalex, 0., 0., scaley, origin_x, origin_y]) } - /// Calculates the length of `axis`. + /// Calculates the [`Length`] of `axis`. /// /// `preferred_size` is the natural size that is used for /// both aspect ratio and minimum preferred size. @@ -129,15 +129,15 @@ impl ObjectFit { self, axis: Axis, len_req: LenReq, - cross_length: Option, + cross_length: Option, preferred_size: Size, - ) -> f64 { + ) -> Length { let preferred_length = preferred_size.get_coord(axis); let (space, space_or_preferred) = match len_req { - LenReq::MinContent => return preferred_length, + LenReq::MinContent => return Length::px(preferred_length), LenReq::MaxContent => (f64::INFINITY, preferred_length), - LenReq::FitContent(space) => (space, space), + LenReq::FitContent(space) => (space.get(), space.get()), }; let mut ar = preferred_size.get_coord(axis) / preferred_size.get_coord(axis.cross()); @@ -145,11 +145,11 @@ impl ObjectFit { ar = 1.; } - match self { + let length = match self { // Use all available space or if cross is known attempt to maintain AR, // but don't exceed available space (will letterbox cross). Self::Contain => cross_length - .map(|cl| (cl * ar).min(space)) + .map(|cl| (cl.get() * ar).min(space)) .unwrap_or(space_or_preferred), // Always use all available space. Self::Cover | Self::Stretch => space_or_preferred, @@ -157,7 +157,7 @@ impl ObjectFit { // Greedily take all horizontal space unless cross is known. Self::FitHeight => match axis { Axis::Horizontal => cross_length - .map(|cl| (cl * ar).min(space)) + .map(|cl| (cl.get() * ar).min(space)) .unwrap_or(space_or_preferred), Axis::Vertical => space_or_preferred, }, @@ -166,16 +166,18 @@ impl ObjectFit { Self::FitWidth => match axis { Axis::Horizontal => space_or_preferred, Axis::Vertical => cross_length - .map(|cl| (cl * ar).min(space)) + .map(|cl| (cl.get() * ar).min(space)) .unwrap_or(space_or_preferred), }, // None == preferred size Self::None => preferred_length, // ScaleDown == min(Contain, None) Self::ScaleDown => cross_length - .map(|cl| (cl * ar).min(space)) + .map(|cl| (cl.get() * ar).min(space)) .unwrap_or(space_or_preferred) .min(preferred_length), - } + }; + + Length::px(length) } } diff --git a/masonry/src/tests/action.rs b/masonry/src/tests/action.rs index 0c3cd15447..212307e9b4 100644 --- a/masonry/src/tests/action.rs +++ b/masonry/src/tests/action.rs @@ -8,7 +8,7 @@ use assert_matches::assert_matches; use crate::core::{ChildrenIds, Widget}; use crate::kurbo::Point; -use crate::layout::{AsUnit, LayoutSize}; +use crate::layout::{AsUnit, LayoutSize, Length}; use crate::properties::Dimensions; use crate::testing::{ModularWidget, TestHarness}; use crate::theme::test_property_set; @@ -61,7 +61,7 @@ fn action_source_removed() { ctx.compute_length(child, auto_length, context_size, axis, cross_length) } else { - 0. + Length::ZERO } }) .layout_fn(move |child, ctx, _props, size| { diff --git a/masonry/src/tests/compose.rs b/masonry/src/tests/compose.rs index fb28558f57..09d69484fd 100644 --- a/masonry/src/tests/compose.rs +++ b/masonry/src/tests/compose.rs @@ -2,11 +2,11 @@ // SPDX-License-Identifier: Apache-2.0 use assert_matches::assert_matches; -use masonry_testing::{ModularWidget, Record, TestHarness, TestWidgetExt}; use crate::core::{ChildrenIds, NewWidget, Widget, WidgetPod, WidgetTag}; use crate::kurbo::{Affine, Point, Vec2}; -use crate::layout::SizeDef; +use crate::layout::{Length, SizeDef}; +use crate::testing::{ModularWidget, Record, TestHarness, TestWidgetExt}; use crate::theme::test_property_set; use crate::widgets::SizedBox; @@ -29,7 +29,7 @@ fn request_compose() { }; let parent = ModularWidget::new(child) - .measure_fn(|_state, _ctx, _props, _axis, _len_req, _cross_length| 0.) + .measure_fn(|_state, _ctx, _props, _axis, _len_req, _cross_length| Length::ZERO) .layout_fn(|state, ctx, _props, size| { let child_size = ctx.compute_size(&mut state.child, SizeDef::fit(size), size.into()); ctx.run_layout(&mut state.child, child_size); diff --git a/masonry/src/tests/event.rs b/masonry/src/tests/event.rs index 1afdae6e0e..28d2db98d3 100644 --- a/masonry/src/tests/event.rs +++ b/masonry/src/tests/event.rs @@ -28,7 +28,7 @@ fn create_capture_target() -> ModularWidget<()> { ctx.capture_pointer(); } }) - .measure_fn(|_, _, _, _, _, _| 10.) + .measure_fn(|_, _, _, _, _, _| 10.px()) } #[test] diff --git a/masonry/src/tests/layout.rs b/masonry/src/tests/layout.rs index 37e23274df..1427426de1 100644 --- a/masonry/src/tests/layout.rs +++ b/masonry/src/tests/layout.rs @@ -53,7 +53,7 @@ fn layout_simple() { #[test] fn forget_to_recurse_layout() { let widget = ModularWidget::new_parent(Flex::row().prepare()) - .measure_fn(|_, _, _, _, _, _| 0.) + .measure_fn(|_, _, _, _, _, _| Length::ZERO) .layout_fn(|_child, _ctx, _, _| { // We forget to call ctx.run_layout(); }) @@ -83,7 +83,7 @@ fn forget_to_call_place_child() { #[test] fn call_place_child_before_layout() { let widget = ModularWidget::new_parent(Flex::row().prepare()) - .measure_fn(|_, _, _, _, _, _| 0.) + .measure_fn(|_, _, _, _, _, _| Length::ZERO) .layout_fn(|child, ctx, _, _| { // We call ctx.place_child(), but forget run_layout ctx.place_child(child, Point::ORIGIN); @@ -229,7 +229,7 @@ fn layout_insets() { let parent_tag = WidgetTag::named("parent"); let child_widget = ModularWidget::new(()) - .measure_fn(|_, _, _, _, _, _| BOX_WIDTH) + .measure_fn(|_, _, _, _, _, _| BOX_WIDTH.px()) .layout_fn(|_, ctx, _, _| { // this widget paints twenty points above and below its layout bounds ctx.set_paint_insets(Insets::uniform_xy(0., 20.)); diff --git a/masonry/src/tests/paint.rs b/masonry/src/tests/paint.rs index 708406250f..e25f242dee 100644 --- a/masonry/src/tests/paint.rs +++ b/masonry/src/tests/paint.rs @@ -76,7 +76,7 @@ fn paint_order() { let children = vec![child1, child2, child3]; let parent = NewWidget::new( ModularWidget::new_multi_parent(children) - .measure_fn(|_, _, _, _, _, _| SQUARE_SIZE * 2.) + .measure_fn(|_, _, _, _, _, _| (SQUARE_SIZE * 2.).px()) .layout_fn(move |children, ctx, _props, size| { let mut pos = Point::ZERO; for child in children { @@ -122,7 +122,7 @@ fn paint_clipping() { let parent = NewWidget::new( ModularWidget::new(()) - .measure_fn(|_, _, _, _, _, _| SQUARE_SIZE) + .measure_fn(|_, _, _, _, _, _| SQUARE_SIZE.px()) .layout_fn(|_, ctx, _, size| { ctx.set_clip_path(size.to_rect()); }) @@ -159,14 +159,14 @@ fn paint_clipping() { fn make_layer_split_tree(isolate_trailing_box: bool) -> NewWidget { let leading = NewWidget::new( ModularWidget::new(()) - .measure_fn(|_, _, _, _, _, _| 20.) + .measure_fn(|_, _, _, _, _, _| 20.px()) .paint_fn(|_, ctx, _, scene| { scene.fill(ctx.content_box(), RED).draw(); }), ); let trailing = NewWidget::new( ModularWidget::new(isolate_trailing_box) - .measure_fn(|_, _, _, _, _, _| 20.) + .measure_fn(|_, _, _, _, _, _| 20.px()) .paint_fn(|isolate, ctx, _, scene| { if *isolate { ctx.set_paint_layer_mode(PaintLayerMode::IsolatedScene); @@ -184,14 +184,14 @@ fn make_layer_split_tree(isolate_trailing_box: bool) -> NewWidget { fn make_external_placeholder_tree() -> NewWidget { let leading = NewWidget::new( ModularWidget::new(()) - .measure_fn(|_, _, _, _, _, _| 20.) + .measure_fn(|_, _, _, _, _, _| 20.px()) .paint_fn(|_, ctx, _, scene| { scene.fill(ctx.content_box(), RED).draw(); }), ); let placeholder = NewWidget::new( ModularWidget::new(()) - .measure_fn(|_, _, _, _, _, _| 20.) + .measure_fn(|_, _, _, _, _, _| 20.px()) .layout_fn(|_, ctx, _, size| { ctx.set_clip_path(size.to_rect()); }) @@ -201,7 +201,7 @@ fn make_external_placeholder_tree() -> NewWidget { ); let trailing = NewWidget::new( ModularWidget::new(()) - .measure_fn(|_, _, _, _, _, _| 20.) + .measure_fn(|_, _, _, _, _, _| 20.px()) .paint_fn(|_, ctx, _, scene| { scene.fill(ctx.content_box(), BLUE).draw(); }), diff --git a/masonry/src/tests/update.rs b/masonry/src/tests/update.rs index 4b5d8c460b..f937564495 100644 --- a/masonry/src/tests/update.rs +++ b/masonry/src/tests/update.rs @@ -4,17 +4,17 @@ use std::sync::mpsc; use assert_matches::assert_matches; -use masonry_testing::{ - DebugName, ModularWidget, PRIMARY_MOUSE, Record, TestHarness, TestWidgetExt, assert_any, - assert_debug_panics, -}; use crate::core::pointer::PointerEvent; use crate::core::{ CursorIcon, Ime, NewWidget, PropertySet, TextEvent, Update, Widget, WidgetId, WidgetPod, WidgetTag, }; -use crate::layout::Length; +use crate::layout::{AsUnit, Length}; +use crate::testing::{ + DebugName, ModularWidget, PRIMARY_MOUSE, Record, TestHarness, TestWidgetExt, assert_any, + assert_debug_panics, +}; use crate::theme::test_property_set; use crate::widgets::{Button, Flex, Label, SizedBox, TextArea}; @@ -557,7 +557,7 @@ fn create_icon_widget() -> ModularWidget<()> { } }) .cursor_icon(CursorIcon::Crosshair) - .measure_fn(|_, _, _, _, _, _| 10.) + .measure_fn(|_, _, _, _, _, _| 10.px()) } #[test] @@ -648,12 +648,11 @@ fn change_hovered_when_widget_changes() { let parent_tag = WidgetTag::named("parent"); let child = - NewWidget::new(ModularWidget::new(BOX_SIZE).measure_fn(|size, _, _, _, _, _| size.get())) + NewWidget::new(ModularWidget::new(BOX_SIZE).measure_fn(|size, _, _, _, _, _| *size)) .with_tag(child_tag); - let parent = NewWidget::new( - ModularWidget::new_parent(child).measure_fn(|_, _, _, _, _, _| BOX_SIZE.get()), - ) - .with_tag(parent_tag); + let parent = + NewWidget::new(ModularWidget::new_parent(child).measure_fn(|_, _, _, _, _, _| BOX_SIZE)) + .with_tag(parent_tag); let mut harness = TestHarness::create(test_property_set(), parent); let child_id = harness.get_widget(child_tag).id(); @@ -697,7 +696,7 @@ fn make_reporter_parent( ctx.set_handled(); } }) - .measure_fn(|_, _, _, _, _, _| 100.) + .measure_fn(|_, _, _, _, _, _| 100.px()) .update_fn(move |_, _, _, event| { sender.send((event.short_name().to_string(), n)).unwrap(); }) diff --git a/masonry/src/widgets/align.rs b/masonry/src/widgets/align.rs index 68a12183b0..d1e60e1ff7 100644 --- a/masonry/src/widgets/align.rs +++ b/masonry/src/widgets/align.rs @@ -17,7 +17,7 @@ use crate::core::{ use crate::core::{MeasureCtx, WidgetMut}; use crate::imaging::Painter; use crate::kurbo::{Axis, Rect, Size}; -use crate::layout::{LayoutSize, LenReq, SizeDef, UnitPoint}; +use crate::layout::{AsUnit, LayoutSize, LenReq, Length, SizeDef, UnitPoint}; // TODO - Have child widget type as generic argument @@ -125,8 +125,8 @@ impl Widget for Align { _props: &PropertiesRef<'_>, axis: Axis, len_req: LenReq, - cross_length: Option, - ) -> f64 { + cross_length: Option, + ) -> Length { let auto_length = len_req.into(); let context_size = LayoutSize::maybe(axis.cross(), cross_length); @@ -152,7 +152,7 @@ impl Widget for Align { Axis::Horizontal => self.width_factor, Axis::Vertical => self.height_factor, } { - length = child_length * factor; + length = (child_length.get() * factor).px(); } // Never return a length larger than the bounds diff --git a/masonry/src/widgets/badge.rs b/masonry/src/widgets/badge.rs index 1d15b8b59d..39bd8e30b1 100644 --- a/masonry/src/widgets/badge.rs +++ b/masonry/src/widgets/badge.rs @@ -14,7 +14,7 @@ use crate::core::{ }; use crate::imaging::Painter; use crate::kurbo::{Axis, Size}; -use crate::layout::{LayoutSize, LenReq, SizeDef}; +use crate::layout::{LayoutSize, LenReq, Length, SizeDef}; use crate::parley::style::FontWeight; use crate::widgets::Label; @@ -165,8 +165,8 @@ impl Widget for Badge { _props: &PropertiesRef<'_>, axis: Axis, len_req: LenReq, - cross_length: Option, - ) -> f64 { + cross_length: Option, + ) -> Length { let auto_length = len_req.into(); let context_size = LayoutSize::maybe(axis.cross(), cross_length); diff --git a/masonry/src/widgets/badged.rs b/masonry/src/widgets/badged.rs index 3e2f681b88..c41912f53f 100644 --- a/masonry/src/widgets/badged.rs +++ b/masonry/src/widgets/badged.rs @@ -11,7 +11,7 @@ use crate::core::{ }; use crate::imaging::Painter; use crate::kurbo::{Axis, Point, Size, Vec2}; -use crate::layout::{LayoutSize, LenReq, SizeDef}; +use crate::layout::{LayoutSize, LenReq, Length, SizeDef}; /// Where a badge is placed relative to the content. #[derive(Clone, Copy, Debug, PartialEq, Eq)] @@ -185,8 +185,8 @@ impl Widget for Badged { _props: &PropertiesRef<'_>, axis: Axis, len_req: LenReq, - cross_length: Option, - ) -> f64 { + cross_length: Option, + ) -> Length { let auto_length = len_req.into(); let context_size = LayoutSize::maybe(axis.cross(), cross_length); ctx.compute_length( diff --git a/masonry/src/widgets/button.rs b/masonry/src/widgets/button.rs index b1ed5da9f5..076c4ae61d 100644 --- a/masonry/src/widgets/button.rs +++ b/masonry/src/widgets/button.rs @@ -18,7 +18,7 @@ use crate::core::{ }; use crate::imaging::Painter; use crate::kurbo::{Axis, Size}; -use crate::layout::{LayoutSize, LenReq, SizeDef}; +use crate::layout::{LayoutSize, LenReq, Length, SizeDef}; use crate::theme; use crate::widgets::Label; @@ -178,12 +178,8 @@ impl Widget for Button { _props: &PropertiesRef<'_>, axis: Axis, len_req: LenReq, - cross_length: Option, - ) -> f64 { - // TODO: Remove HACK: Until scale factor rework happens, just pretend it's always 1.0. - // https://github.com/linebender/xilem/issues/1264 - let scale = 1.0; - + cross_length: Option, + ) -> Length { let auto_length = len_req.into(); let context_size = LayoutSize::maybe(axis.cross(), cross_length); @@ -202,7 +198,7 @@ impl Widget for Button { // we make sure we will have at least the same height as the default text input. match axis { Axis::Horizontal => length, - Axis::Vertical => length.max(theme::BASIC_WIDGET_HEIGHT.dp(scale)), + Axis::Vertical => length.max(theme::BASIC_WIDGET_HEIGHT), } } diff --git a/masonry/src/widgets/canvas.rs b/masonry/src/widgets/canvas.rs index 7c4ce0b83f..d66136e36b 100644 --- a/masonry/src/widgets/canvas.rs +++ b/masonry/src/widgets/canvas.rs @@ -92,16 +92,12 @@ impl Widget for Canvas { _props: &PropertiesRef<'_>, _axis: Axis, len_req: LenReq, - _cross_length: Option, - ) -> f64 { - // TODO: Remove HACK: Until scale factor rework happens, just pretend it's always 1.0. - // https://github.com/linebender/xilem/issues/1264 - let scale = 1.0; - + _cross_length: Option, + ) -> Length { // We use all the available space or fall back to our const preferred size. match len_req { LenReq::FitContent(space) => space, - _ => DEFAULT_LENGTH.dp(scale), + _ => DEFAULT_LENGTH, } } diff --git a/masonry/src/widgets/checkbox.rs b/masonry/src/widgets/checkbox.rs index e006e51116..dd742371ef 100644 --- a/masonry/src/widgets/checkbox.rs +++ b/masonry/src/widgets/checkbox.rs @@ -16,7 +16,7 @@ use crate::core::{ }; use crate::imaging::Painter; use crate::kurbo::{Axis, BezPath, Cap, Dashes, Join, Point, Size, Stroke}; -use crate::layout::{LayoutSize, LenReq, SizeDef}; +use crate::layout::{LayoutSize, LenReq, Length, SizeDef}; use crate::properties::{ BorderColor, BorderWidth, CheckmarkColor, CheckmarkStrokeWidth, CornerRadius, }; @@ -187,25 +187,21 @@ impl Widget for Checkbox { _props: &PropertiesRef<'_>, axis: Axis, len_req: LenReq, - cross_length: Option, - ) -> f64 { - // TODO: Remove HACK: Until scale factor rework happens, just pretend it's always 1.0. - // https://github.com/linebender/xilem/issues/1264 - let scale = 1.0; - - let check_side = theme::BASIC_WIDGET_HEIGHT.dp(scale); - let check_padding = theme::WIDGET_CONTROL_COMPONENT_PADDING.dp(scale); + cross_length: Option, + ) -> Length { + let check_side = theme::BASIC_WIDGET_HEIGHT; + let check_padding = theme::WIDGET_CONTROL_COMPONENT_PADDING; let calc_other_length = |axis| match axis { - Axis::Horizontal => check_side + check_padding, - Axis::Vertical => 0., + Axis::Horizontal => check_side.saturating_add(check_padding), + Axis::Vertical => Length::ZERO, }; let other_length = calc_other_length(axis); let cross = axis.cross(); let cross_space = cross_length.map(|cross_length| { let cross_other_length = calc_other_length(cross); - (cross_length - cross_other_length).max(0.) + cross_length.saturating_sub(cross_other_length) }); let auto_length = len_req.reduce(other_length).into(); @@ -220,18 +216,14 @@ impl Widget for Checkbox { ); match axis { - Axis::Horizontal => label_length + other_length, - Axis::Vertical => label_length.max(check_side) + other_length, + Axis::Horizontal => label_length.saturating_add(other_length), + Axis::Vertical => label_length.max(check_side).saturating_add(other_length), } } fn layout(&mut self, ctx: &mut LayoutCtx<'_>, _props: &PropertiesRef<'_>, size: Size) { - // TODO: Remove HACK: Until scale factor rework happens, just pretend it's always 1.0. - // https://github.com/linebender/xilem/issues/1264 - let scale = 1.0; - - let check_side = theme::BASIC_WIDGET_HEIGHT.dp(scale); - let check_padding = theme::WIDGET_CONTROL_COMPONENT_PADDING.dp(scale); + let check_side = theme::BASIC_WIDGET_HEIGHT.get(); + let check_padding = theme::WIDGET_CONTROL_COMPONENT_PADDING.get(); let space = Size::new( (size.width - (check_side + check_padding)).max(0.), @@ -293,11 +285,7 @@ impl Widget for Checkbox { props: &PropertiesRef<'_>, painter: &mut Painter<'_>, ) { - // TODO: Remove HACK: Until scale factor rework happens, just pretend it's always 1.0. - // https://github.com/linebender/xilem/issues/1264 - let scale = 1.0; - - let check_side = theme::BASIC_WIDGET_HEIGHT.dp(scale); + let check_side = theme::BASIC_WIDGET_HEIGHT.get(); let check_size = Size::new(check_side, check_side); let cache = ctx.property_cache(); diff --git a/masonry/src/widgets/collapse_panel.rs b/masonry/src/widgets/collapse_panel.rs index c379831f68..2f14ed4173 100644 --- a/masonry/src/widgets/collapse_panel.rs +++ b/masonry/src/widgets/collapse_panel.rs @@ -9,7 +9,7 @@ use crate::core::{ }; use crate::imaging::Painter; use crate::kurbo::{Axis, Line, Point, Size, Stroke}; -use crate::layout::{LayoutSize, LenDef, LenReq, Length, SizeDef}; +use crate::layout::{AsUnit, LayoutSize, LenDef, LenReq, Length, SizeDef}; use crate::properties::{BorderColor, BorderWidth, Dimensions, Padding}; use crate::widgets::{DisclosureButton, Label}; use crate::{accesskit, theme}; @@ -134,29 +134,30 @@ impl Widget for CollapsePanel { props: &PropertiesRef<'_>, axis: Axis, len_req: LenReq, - cross_length: Option, - ) -> f64 { - // TODO: Remove HACK: Until scale factor rework happens, just pretend it's always 1.0. - // https://github.com/linebender/xilem/issues/1264 - let scale = 1.0; - + cross_length: Option, + ) -> Length { let cache = ctx.property_cache(); let border = props.get::(cache); let header_x_padding = theme::WIDGET_CONTROL_COMPONENT_PADDING; - let header_x_padding_length = header_x_padding.dp(scale) * 2.; - let btn_length = BUTTON_LENGTH.dp(scale); + let header_x_padding_length = header_x_padding.saturating_mul(Length::const_px(2.)); + let btn_length = BUTTON_LENGTH; - let separator_height = - border.width * scale + SEPARATOR_PAD.length(Axis::Vertical).dp(scale); + let separator_height = border + .width + .px() + .saturating_add(SEPARATOR_PAD.length(Axis::Vertical)); let space: LenDef = len_req.into(); let cross = axis.cross(); let label_cross_space = match cross { // If we know the horizontal space, then we can derive the label's horizontal space. - Axis::Horizontal => cross_length - .map(|cross_length| (cross_length - header_x_padding_length - btn_length).max(0.)), + Axis::Horizontal => cross_length.map(|cross_length| { + cross_length + .saturating_sub(header_x_padding_length) + .saturating_sub(btn_length) + }), // Even if we know our vertical space, we don't know the child's height. // So we can't provide an accurate height for the label. Axis::Vertical => None, @@ -164,7 +165,7 @@ impl Widget for CollapsePanel { // We don't give any special context to the label, just our full size let label_context_size = LayoutSize::maybe(cross, cross_length); let label_auto_length = match axis { - Axis::Horizontal => space.reduce(header_x_padding_length + btn_length), + Axis::Horizontal => space.reduce(header_x_padding_length.saturating_add(btn_length)), Axis::Vertical => space, }; let label_length = ctx.compute_length( @@ -176,7 +177,9 @@ impl Widget for CollapsePanel { ); let header_length = match axis { - Axis::Horizontal => btn_length + label_length + header_x_padding_length, + Axis::Horizontal => btn_length + .saturating_add(label_length) + .saturating_add(header_x_padding_length), Axis::Vertical => btn_length.max(label_length), }; @@ -195,7 +198,7 @@ impl Widget for CollapsePanel { let child_context_size = LayoutSize::maybe(cross, child_cross_space); let child_auto_length = match axis { Axis::Horizontal => space, - Axis::Vertical => space.reduce(header_length + separator_height), + Axis::Vertical => space.reduce(header_length.saturating_add(separator_height)), }; ctx.compute_length( &mut self.child, @@ -205,7 +208,7 @@ impl Widget for CollapsePanel { child_cross_space, ) } else { - 0. + Length::ZERO }; match axis { @@ -213,7 +216,9 @@ impl Widget for CollapsePanel { Axis::Vertical => { let mut length = header_length; if !is_collapsed { - length += child_length + separator_height; + length = length + .saturating_add(child_length) + .saturating_add(separator_height); } length } @@ -221,19 +226,14 @@ impl Widget for CollapsePanel { } fn layout(&mut self, ctx: &mut LayoutCtx<'_>, props: &PropertiesRef<'_>, size: Size) { - // TODO: Remove HACK: Until scale factor rework happens, just pretend it's always 1.0. - // https://github.com/linebender/xilem/issues/1264 - let scale = 1.0; - let cache = ctx.property_cache(); let border = props.get::(cache); let header_x_padding = theme::WIDGET_CONTROL_COMPONENT_PADDING; - let separator_height = - border.width * scale + SEPARATOR_PAD.length(Axis::Vertical).dp(scale); + let separator_height = border.width + SEPARATOR_PAD.length(Axis::Vertical).get(); - let button_width = BUTTON_LENGTH.dp(scale); - let header_padding_width = header_x_padding.dp(scale); + let button_width = BUTTON_LENGTH.get(); + let header_padding_width = header_x_padding.get(); // Square button let button_size = Size::new(button_width, button_width); @@ -241,9 +241,11 @@ impl Widget for CollapsePanel { let label_auto_size = SizeDef::new( LenDef::FitContent( - (size.width - header_padding_width * 2. - button_size.width).max(0.), + (size.width - header_padding_width * 2. - button_size.width) + .max(0.) + .px(), ), - LenDef::FitContent(size.height), + LenDef::FitContent(size.height.px()), ); let label_size = ctx.compute_size(&mut self.header_label, label_auto_size, size.into()); @@ -283,8 +285,7 @@ impl Widget for CollapsePanel { let child_origin = Point::new(0.0, header_height + separator_height); ctx.place_child(&mut self.child, child_origin); - self.separator_line_y = - Some(header_height + SEPARATOR_PAD.top * scale + border.width * scale * 0.5); + self.separator_line_y = Some(header_height + SEPARATOR_PAD.top + border.width * 0.5); } else { self.separator_line_y = None; } @@ -298,10 +299,6 @@ impl Widget for CollapsePanel { props: &PropertiesRef<'_>, painter: &mut Painter<'_>, ) { - // TODO: Remove HACK: Until scale factor rework happens, just pretend it's always 1.0. - // https://github.com/linebender/xilem/issues/1264 - let scale = 1.0; - if let Some(y) = self.separator_line_y { let cache = ctx.property_cache(); let border_width = *props.get::(cache); @@ -310,9 +307,9 @@ impl Widget for CollapsePanel { let border_box = ctx.border_box(); // Only paint the line if it would have a positive width - if SEPARATOR_PAD.length(Axis::Horizontal).dp(scale) < border_box.width() { - let x1 = border_box.x0 + SEPARATOR_PAD.left * scale; - let x2 = border_box.x1 - SEPARATOR_PAD.right * scale; + if SEPARATOR_PAD.length(Axis::Horizontal).get() < border_box.width() { + let x1 = border_box.x0 + SEPARATOR_PAD.left; + let x2 = border_box.x1 - SEPARATOR_PAD.right; let line = Line::new((x1, y), (x2, y)); painter .stroke(line, &Stroke::new(border_width.width), border_color.color) diff --git a/masonry/src/widgets/disclosure_button.rs b/masonry/src/widgets/disclosure_button.rs index 263e87c08c..9632285ac3 100644 --- a/masonry/src/widgets/disclosure_button.rs +++ b/masonry/src/widgets/disclosure_button.rs @@ -144,13 +144,9 @@ impl Widget for DisclosureButton { _props: &PropertiesRef<'_>, _axis: Axis, len_req: LenReq, - _cross_length: Option, - ) -> f64 { - // TODO: Remove HACK: Until scale factor rework happens, just pretend it's always 1.0. - // https://github.com/linebender/xilem/issues/1264 - let scale = 1.0; - - let length = DEFAULT_LENGTH.dp(scale); + _cross_length: Option, + ) -> Length { + let length = DEFAULT_LENGTH; match len_req { LenReq::MinContent | LenReq::MaxContent => length, @@ -166,9 +162,6 @@ impl Widget for DisclosureButton { props: &PropertiesRef<'_>, painter: &mut Painter<'_>, ) { - // TODO: Remove HACK: Until scale factor rework happens, just pretend it's always 1.0. - // https://github.com/linebender/xilem/issues/1264 - let scale = 1.0; let cache = ctx.property_cache(); let button_color = props.get::(cache); @@ -190,7 +183,7 @@ impl Widget for DisclosureButton { painter .stroke( arrow, - &Stroke::new(2.0 * scale).with_join(Join::Miter), + &Stroke::new(2.0).with_join(Join::Miter), button_color.color, ) .transform(affine) @@ -200,7 +193,7 @@ impl Widget for DisclosureButton { // TODO: Perhaps change the color of the arrow instead? let rect = ctx.border_box().to_rounded_rect(2.0); painter - .stroke(rect, &Stroke::new(1.0 * scale), BrushRef::Solid(LIGHT_BLUE)) + .stroke(rect, &Stroke::new(1.0), BrushRef::Solid(LIGHT_BLUE)) .draw(); } } diff --git a/masonry/src/widgets/divider.rs b/masonry/src/widgets/divider.rs index 5e72fdb45d..2c7db8a5e8 100644 --- a/masonry/src/widgets/divider.rs +++ b/masonry/src/widgets/divider.rs @@ -21,7 +21,7 @@ use crate::layout::{LayoutSize, Length, SizeDef, UnitPoint}; use crate::properties::ContentColor; use crate::widgets::Label; -// TODO: Do proper hairline layout/paint after scale rework has happened. +// TODO: Use snap-aware hairline layout/paint once Masonry has presentation snapping helpers. /// A line to divide your content. /// @@ -606,27 +606,23 @@ impl Widget for Divider { _props: &PropertiesRef<'_>, axis: Axis, len_req: LenReq, - cross_length: Option, - ) -> f64 { - // TODO: Remove HACK: Until scale factor rework happens, just pretend it's always 1.0. - // https://github.com/linebender/xilem/issues/1264 - let scale = 1.0; - - const DEFAULT_LENGTH: f64 = 100.; - let thickness = self.thickness.map(|t| t.dp(scale)).unwrap_or(1.); + cross_length: Option, + ) -> Length { + const DEFAULT_LENGTH: Length = Length::const_px(100.); + let thickness = self.thickness.unwrap_or(Length::const_px(1.)); let content_length = if let Some(content) = &mut self.content { let auto_length = len_req.into(); let context_size = LayoutSize::maybe(axis.cross(), cross_length); ctx.compute_length(content, auto_length, context_size, axis, cross_length) } else { - 0. + Length::ZERO }; if axis == self.axis { match len_req { LenReq::MinContent => content_length, - LenReq::MaxContent => (DEFAULT_LENGTH * scale).max(content_length), + LenReq::MaxContent => DEFAULT_LENGTH.max(content_length), LenReq::FitContent(space) => space.max(content_length), } } else { @@ -635,10 +631,6 @@ impl Widget for Divider { } fn layout(&mut self, ctx: &mut LayoutCtx<'_>, _props: &PropertiesRef<'_>, size: Size) { - // TODO: Remove HACK: Until scale factor rework happens, just pretend it's always 1.0. - // https://github.com/linebender/xilem/issues/1264 - let scale = 1.0; - // Clear any previously laid out lines. self.lines.clear(); @@ -663,10 +655,9 @@ impl Widget for Divider { }); } - let thickness = self.thickness.map(|t| t.dp(scale)).unwrap_or(1.); + let thickness = self.thickness.map(|t| t.get()).unwrap_or(1.); let cross_pos = size.get_coord(self.axis.cross()) * 0.5; - let mut dashes: SmallVec<[f64; 4]> = - self.dash_pattern.iter().map(|l| l.dp(scale)).collect(); + let mut dashes: SmallVec<[f64; 4]> = self.dash_pattern.iter().map(|l| l.get()).collect(); if let Some(content) = &mut self.content { let content_size = ctx.compute_size(content, SizeDef::fit(size), size.into()); @@ -688,7 +679,7 @@ impl Widget for Divider { ctx.derive_baselines(content); - let pad = self.pad.dp(scale); + let pad = self.pad.get(); let mut line_space = size.get_coord(self.axis) - self.total_cap_overhang(thickness) - content_size.get_coord(self.axis) @@ -745,16 +736,12 @@ impl Widget for Divider { props: &PropertiesRef<'_>, painter: &mut Painter<'_>, ) { - // TODO: Remove HACK: Until scale factor rework happens, just pretend it's always 1.0. - // https://github.com/linebender/xilem/issues/1264 - let scale = 1.0; - - // TODO: Remove HACK: After scale factor rework this can be a simple 1. + // TODO: Replace with snap-aware paint helper once that exists. let one_dp = 1. / ctx.get_scale_factor(); let cache = ctx.property_cache(); let color = props.get::(cache); - let thickness = self.thickness.map(|t| t.dp(scale)).unwrap_or(one_dp); + let thickness = self.thickness.map(|t| t.get()).unwrap_or(one_dp); for line in &self.lines { let style = Stroke { diff --git a/masonry/src/widgets/flex.rs b/masonry/src/widgets/flex.rs index f9e867ae39..e06ca2a8bc 100644 --- a/masonry/src/widgets/flex.rs +++ b/masonry/src/widgets/flex.rs @@ -13,7 +13,7 @@ use crate::core::{ }; use crate::imaging::Painter; use crate::kurbo::{Axis, Size}; -use crate::layout::{LayoutSize, LenDef, LenReq, Length}; +use crate::layout::{AsUnit, LayoutSize, LenDef, LenReq, Length}; use crate::properties::Gap; use crate::properties::types::{CrossAxisAlignment, MainAxisAlignment}; use crate::util::Sanitize; @@ -118,7 +118,7 @@ enum Child { /// Ephemeral resolved basis. /// /// It is a logic error to read this value before writing to it in the same method. - basis_resolved: f64, + basis_resolved: Length, }, Spacer { flex: f64, @@ -126,11 +126,11 @@ enum Child { /// Ephemeral resolved basis. /// /// It is a logic error to read this value before writing to it in the same method. - basis_resolved: f64, + basis_resolved: Length, /// Ephemeral resolved length. /// /// It is a logic error to read this value before writing to it in the same method. - length_resolved: f64, + length_resolved: Length, }, } @@ -200,8 +200,8 @@ impl Flex { let new_child = Child::Spacer { flex: 0., basis: len, - basis_resolved: 0., - length_resolved: 0., + basis_resolved: Length::ZERO, + length_resolved: Length::ZERO, }; self.children.push(new_child); self @@ -220,8 +220,8 @@ impl Flex { let new_child = Child::Spacer { flex, basis: Length::ZERO, - basis_resolved: 0., - length_resolved: 0., + basis_resolved: Length::ZERO, + length_resolved: Length::ZERO, }; self.children.push(new_child); self @@ -266,8 +266,8 @@ impl Flex { let new_child = Child::Spacer { flex: 0., basis: len, - basis_resolved: 0., - length_resolved: 0., + basis_resolved: Length::ZERO, + length_resolved: Length::ZERO, }; this.widget.children.push(new_child); this.ctx.request_layout(); @@ -286,8 +286,8 @@ impl Flex { let new_child = Child::Spacer { flex, basis: Length::ZERO, - basis_resolved: 0., - length_resolved: 0., + basis_resolved: Length::ZERO, + length_resolved: Length::ZERO, }; this.widget.children.push(new_child); this.ctx.request_layout(); @@ -315,8 +315,8 @@ impl Flex { let new_child = Child::Spacer { flex: 0., basis: len, - basis_resolved: 0., - length_resolved: 0., + basis_resolved: Length::ZERO, + length_resolved: Length::ZERO, }; this.widget.children.insert(idx, new_child); this.ctx.request_layout(); @@ -337,8 +337,8 @@ impl Flex { let new_child = Child::Spacer { flex, basis: Length::ZERO, - basis_resolved: 0., - length_resolved: 0., + basis_resolved: Length::ZERO, + length_resolved: Length::ZERO, }; this.widget.children.insert(idx, new_child); this.ctx.request_layout(); @@ -373,8 +373,8 @@ impl Flex { let new_child = Child::Spacer { flex: 0., basis: len, - basis_resolved: 0., - length_resolved: 0., + basis_resolved: Length::ZERO, + length_resolved: Length::ZERO, }; let old_child = std::mem::replace(&mut this.widget.children[idx], new_child); if let Child::Widget { widget, .. } = old_child { @@ -399,8 +399,8 @@ impl Flex { let new_child = Child::Spacer { flex, basis: Length::ZERO, - basis_resolved: 0., - length_resolved: 0., + basis_resolved: Length::ZERO, + length_resolved: Length::ZERO, }; let old_child = std::mem::replace(&mut this.widget.children[idx], new_child); if let Child::Widget { widget, .. } = old_child { @@ -501,8 +501,8 @@ impl CollectionWidget for Flex { Child::Spacer { flex: 0., basis: Length::ZERO, - basis_resolved: 0., - length_resolved: 0., + basis_resolved: Length::ZERO, + length_resolved: Length::ZERO, }, ); let widget = match child_val { @@ -632,7 +632,7 @@ fn new_child(params: impl Into, child: WidgetPod) -> Chi alignment: params.alignment, flex: params.flex, basis: params.basis, - basis_resolved: 0., + basis_resolved: Length::ZERO, } } @@ -702,12 +702,8 @@ impl Widget for Flex { len_req: LenReq, // The usual cross_length input has been named perp_length here, // to remove the collision with flex cross, which might not match. - perp_length: Option, - ) -> f64 { - // TODO: Remove HACK: Until scale factor rework happens, just pretend it's always 1.0. - // https://github.com/linebender/xilem/issues/1264 - let scale = 1.0; - + perp_length: Option, + ) -> Length { let perp = measure_axis.cross(); let main = self.direction; let cross = main.cross(); @@ -715,7 +711,7 @@ impl Widget for Flex { let cache = ctx.property_cache(); let gap = props.get::(cache); - let gap_length = gap.gap.dp(scale); + let gap_length = gap.gap.get(); let gap_count = self.children.len().saturating_sub(1); let (main_space, cross_space) = if perp == main { @@ -726,7 +722,7 @@ impl Widget for Flex { let context_size = LayoutSize::maybe(perp, perp_length); let (len_req, min_result) = match len_req { - LenReq::MinContent | LenReq::MaxContent => (len_req, 0.), + LenReq::MinContent | LenReq::MaxContent => (len_req, Length::ZERO), // We always want to use up all offered space but may need even more, // so we implement FitContent as space.max(MinContent). LenReq::FitContent(space) => (LenReq::MinContent, space), @@ -762,7 +758,7 @@ impl Widget for Flex { FlexBasis::Zero => { // TODO: When min/max constraints become a real thing, // then need to account for them here. - *basis_resolved = 0.; + *basis_resolved = Length::ZERO; } }, Child::Spacer { @@ -770,13 +766,13 @@ impl Widget for Flex { basis_resolved, .. } => { - *basis_resolved = basis.dp(scale); + *basis_resolved = *basis; } } } } - let mut length = 0.; + let mut length = Length::ZERO; if measure_axis == main { // Calculate the main axis length @@ -809,7 +805,7 @@ impl Widget for Flex { ); // Flexible children with a zero basis want to reach // their target length purely with flex space. - child_length / *flex + child_length.get() / *flex } } } else { @@ -828,7 +824,7 @@ impl Widget for Flex { } // Calculate the total space needed for all children - length += self + let total_space_needed = self .children .iter() .map(|child| match child { @@ -841,12 +837,14 @@ impl Widget for Flex { flex, basis_resolved, .. - } => *basis_resolved + *flex * flex_fraction, + } => basis_resolved.get() + *flex * flex_fraction, }) .sum::(); + length = length.saturating_add(total_space_needed.px()); // Add all the gap lengths - length += gap_count as f64 * gap_length; + let gap_lengths = gap_count as f64 * gap_length; + length = length.saturating_add(gap_lengths.px()); } else { // Calculate the cross axis length @@ -869,17 +867,18 @@ impl Widget for Flex { .. } => { flex_sum += *flex; - main_space -= *basis_resolved; + main_space = main_space.saturating_sub(*basis_resolved); } } } // Subtract gap lengths - main_space -= gap_count as f64 * gap_length; + let gap_lengths = gap_count as f64 * gap_length; + main_space = main_space.saturating_sub(gap_lengths.px()); // Calculate the flex fraction, i.e. the amount of space per one flex factor if flex_sum > 0. { - main_space.max(0.) / flex_sum + main_space.get() / flex_sum } else { 0. } @@ -894,8 +893,9 @@ impl Widget for Flex { basis_resolved, .. } => { - let child_main_length = flex_fraction - .map(|flex_fraction| *basis_resolved + *flex * flex_fraction); + let child_main_length = flex_fraction.map(|flex_fraction| { + basis_resolved.saturating_add((*flex * flex_fraction).px()) + }); let cross_auto = len_req.into(); let child_cross_length = ctx.compute_length( @@ -920,27 +920,25 @@ impl Widget for Flex { } fn layout(&mut self, ctx: &mut LayoutCtx<'_>, props: &PropertiesRef<'_>, size: Size) { - // TODO: Remove HACK: Until scale factor rework happens, just pretend it's always 1.0. - // https://github.com/linebender/xilem/issues/1264 - let scale = 1.0; - let cache = ctx.property_cache(); let gap = props.get::(cache); - let gap_length = gap.gap.dp(scale); + let gap_length = gap.gap.get(); let gap_count = self.children.len().saturating_sub(1); let main = self.direction; let cross = main.cross(); - let cross_space = size.get_coord(cross); + let cross_space = size.get_coord(cross).px(); - let mut main_space = size.get_coord(main) - gap_count as f64 * gap_length; + let mut main_space = (size.get_coord(main) - gap_count as f64 * gap_length) + .max(0.) + .px(); let mut flex_sum = 0.; // Helper function to calculate child size when main length is decided let compute_child_size = |ctx: &mut LayoutCtx<'_>, child: &mut WidgetPod, - child_main_length: f64, + child_main_length: Length, alignment: &Option| { let cross_auto = match alignment.unwrap_or(self.cross_alignment) { // Cross stretch is merely an auto fallback, not an immediate choice. @@ -957,7 +955,7 @@ impl Widget for Flex { Some(child_main_length), ); - main.pack_size(child_main_length, child_cross_length) + main.pack_size(child_main_length.get(), child_cross_length.get()) }; // Sum flex factors, resolve bases, subtract bases from main space, @@ -982,13 +980,13 @@ impl Widget for Flex { main, Some(cross_space), ); - main_space -= *basis_resolved; + main_space = main_space.saturating_sub(*basis_resolved); } FlexBasis::Zero => { // TODO: When min/max constraints become a real thing, // then need to account for them here, and also // subtract the result for main_space. - *basis_resolved = 0.; + *basis_resolved = Length::ZERO; } } if *flex == 0. { @@ -1007,8 +1005,8 @@ impl Widget for Flex { basis_resolved, length_resolved, } => { - *basis_resolved = basis.dp(scale); - main_space -= *basis_resolved; + *basis_resolved = *basis; + main_space = main_space.saturating_sub(*basis_resolved); if *flex == 0. { *length_resolved = *basis_resolved; @@ -1021,7 +1019,7 @@ impl Widget for Flex { // Calculate the flex fraction, i.e. the amount of space per one flex factor let flex_fraction = if flex_sum > 0. { - main_space.max(0.) / flex_sum + main_space.get() / flex_sum } else { 0. }; @@ -1040,12 +1038,14 @@ impl Widget for Flex { // When Flex gets configurable grow/shrink support, // and min/max style constraints get implemented, // this distribution will need to evolve into a looped solver. - let child_main_length = *basis_resolved + *flex * flex_fraction; + let child_main_length = + basis_resolved.saturating_add((*flex * flex_fraction).px()); let child_size = compute_child_size(ctx, widget, child_main_length, alignment); ctx.run_layout(widget, child_size); - main_space -= child_main_length - *basis_resolved; + main_space = main_space + .saturating_sub(child_main_length.saturating_sub(*basis_resolved)); } Child::Spacer { flex, @@ -1053,9 +1053,11 @@ impl Widget for Flex { length_resolved, .. } if *flex > 0. => { - let child_main_length = *basis_resolved + *flex * flex_fraction; + let child_main_length = + basis_resolved.saturating_add((*flex * flex_fraction).px()); *length_resolved = child_main_length; - main_space -= *length_resolved - *basis_resolved; + main_space = + main_space.saturating_sub(length_resolved.saturating_sub(*basis_resolved)); } _ => (), } @@ -1068,7 +1070,7 @@ impl Widget for Flex { .filter(|child| child.is_widget()) .count(); let (space_before, space_between) = - get_spacing(self.main_alignment, main_space.max(0.), widget_count); + get_spacing(self.main_alignment, main_space.get(), widget_count); // Determine the shared cross alignment baselines. // As we currently only support the horizontal-tb writing mode, we do it only for rows. @@ -1128,11 +1130,11 @@ impl Widget for Flex { let (_, last_baseline) = ctx.child_layout_baselines(widget); let descent = child_size.get_coord(cross) - last_baseline; let end_gap = alignment_descent.unwrap() - descent; - let cross_unused = cross_space - child_size.get_coord(cross); + let cross_unused = cross_space.get() - child_size.get_coord(cross); cross_unused - end_gap } _ => { - let cross_unused = cross_space - child_size.get_coord(cross); + let cross_unused = cross_space.get() - child_size.get_coord(cross); alignment.offset(cross_unused) } }; @@ -1147,7 +1149,7 @@ impl Widget for Flex { Child::Spacer { length_resolved, .. } => { - main_offset += *length_resolved; + main_offset += length_resolved.get(); main_offset += gap_length; previous_was_widget = false; } @@ -1744,8 +1746,8 @@ mod tests { let def = Def { ascent, descent }; ModularWidget::new(def) .measure_fn(|s, _, _, axis, _, _| match axis { - Axis::Horizontal => 10., - Axis::Vertical => s.ascent + s.descent, + Axis::Horizontal => 10.px(), + Axis::Vertical => (s.ascent + s.descent).px(), }) .layout_fn(|s, ctx, _, _| { ctx.set_baselines(s.ascent, s.ascent); diff --git a/masonry/src/widgets/grid.rs b/masonry/src/widgets/grid.rs index 0ed691c942..e32d21a182 100644 --- a/masonry/src/widgets/grid.rs +++ b/masonry/src/widgets/grid.rs @@ -13,7 +13,7 @@ use crate::core::{ }; use crate::imaging::Painter; use crate::kurbo::{Axis, Point, Size}; -use crate::layout::{LayoutSize, LenReq, SizeDef}; +use crate::layout::{AsUnit, LayoutSize, LenReq, Length, SizeDef}; use crate::properties::Gap; use crate::util::debug_panic; @@ -302,25 +302,21 @@ impl Widget for Grid { props: &PropertiesRef<'_>, axis: Axis, len_req: LenReq, - cross_length: Option, - ) -> f64 { - // TODO: Remove HACK: Until scale factor rework happens, just pretend it's always 1.0. - // https://github.com/linebender/xilem/issues/1264 - let scale = 1.0; - + cross_length: Option, + ) -> Length { let cache = ctx.property_cache(); let gap = props.get::(cache); - let gap_length = gap.gap.dp(scale); + let gap_length = gap.gap.get(); let cross = axis.cross(); let cross_track_cells = self.track_cells(cross) as f64; let cross_cell_length = cross_length .filter(|_| cross_track_cells > 0.) // Guard against div by zero - .map(|cross_length| (cross_length + gap_length) / cross_track_cells); + .map(|cross_length| (cross_length.get() + gap_length) / cross_track_cells); let (len_req, min_result) = match len_req { - LenReq::MinContent | LenReq::MaxContent => (len_req, 0.), + LenReq::MinContent | LenReq::MaxContent => (len_req, Length::ZERO), // We always want to use up all offered space but may need even more, // so we implement FitContent as space.max(MinContent). LenReq::FitContent(space) => (LenReq::MinContent, space), @@ -336,7 +332,7 @@ impl Widget for Grid { let length = cross_area_cells * cross_cell_length - gap_length; // Guard against the derived area length becoming negative, // which can happen if total space can't fit all cells and gaps. - length.max(0.) + length.max(0.).px() }); let auto_length = len_req.into(); @@ -350,26 +346,22 @@ impl Widget for Grid { cross_area_length, ); - (child_length + gap_length) / area_cells + (child_length.get() + gap_length) / area_cells }; cell_length = cell_length.max(desired_cell_length); } let track_cells = self.track_cells(axis) as f64; - let length = track_cells * cell_length - gap_length; + let length = (track_cells * cell_length - gap_length).px(); min_result.max(length) } fn layout(&mut self, ctx: &mut LayoutCtx<'_>, props: &PropertiesRef<'_>, size: Size) { - // TODO: Remove HACK: Until scale factor rework happens, just pretend it's always 1.0. - // https://github.com/linebender/xilem/issues/1264 - let scale = 1.0; - let cache = ctx.property_cache(); let gap = props.get::(cache); - let gap_length = gap.gap.dp(scale); + let gap_length = gap.gap.get(); let cell_width = (size.width + gap_length) / self.grid_column_count as f64; let cell_height = (size.height + gap_length) / self.grid_row_count as f64; diff --git a/masonry/src/widgets/image.rs b/masonry/src/widgets/image.rs index dab06afffc..af8a1d6154 100644 --- a/masonry/src/widgets/image.rs +++ b/masonry/src/widgets/image.rs @@ -13,7 +13,7 @@ use crate::core::{ }; use crate::imaging::Painter; use crate::kurbo::{Axis, Size}; -use crate::layout::LenReq; +use crate::layout::{LenReq, Length}; use crate::peniko::ImageBrush; use crate::properties::ObjectFit; @@ -113,18 +113,14 @@ impl Image { impl Image { /// Returns the preferred size of the image. /// - /// The returned size is in device pixels. + /// The returned size is in logical pixels. /// - /// This takes into account both [`IMAGE_SCALE`] and `scale`, and so the result - /// isn't just the image data size which would be const across scale factors. - /// - /// This method's result will be stable in relation to other widgets at any scale factor. - /// - /// Basically it provides logical pixels in device pixel space. - fn preferred_size(&self, scale: f64) -> Size { + /// This takes into account [`IMAGE_SCALE`], so a high-resolution resource can + /// have a stable preferred logical size. + fn preferred_size(&self) -> Size { Size::new( - self.image_data.image.width as f64 * scale / IMAGE_SCALE, - self.image_data.image.height as f64 * scale / IMAGE_SCALE, + self.image_data.image.width as f64 / IMAGE_SCALE, + self.image_data.image.height as f64 / IMAGE_SCALE, ) } } @@ -157,15 +153,11 @@ impl Widget for Image { props: &PropertiesRef<'_>, axis: Axis, len_req: LenReq, - cross_length: Option, - ) -> f64 { - // TODO: Remove HACK: Until scale factor rework happens, just pretend it's always 1.0. - // https://github.com/linebender/xilem/issues/1264 - let scale = 1.0; - + cross_length: Option, + ) -> Length { let cache = ctx.property_cache(); let object_fit = props.get::(cache); - let preferred_size = self.preferred_size(scale); + let preferred_size = self.preferred_size(); object_fit.measure(axis, len_req, cross_length, preferred_size) } diff --git a/masonry/src/widgets/indexed_stack.rs b/masonry/src/widgets/indexed_stack.rs index 7356eccf89..ee27d358f1 100644 --- a/masonry/src/widgets/indexed_stack.rs +++ b/masonry/src/widgets/indexed_stack.rs @@ -13,7 +13,7 @@ use crate::core::{ }; use crate::imaging::Painter; use crate::kurbo::{Axis, Point, Size}; -use crate::layout::{LayoutSize, LenReq, SizeDef}; +use crate::layout::{LayoutSize, LenReq, Length, SizeDef}; // TODO - Rename "active" widget to "visible" widget? // Active already means something else. @@ -231,8 +231,8 @@ impl Widget for IndexedStack { _props: &PropertiesRef<'_>, axis: Axis, len_req: LenReq, - cross_length: Option, - ) -> f64 { + cross_length: Option, + ) -> Length { if !self.children.is_empty() { let auto_length = len_req.into(); let context_size = LayoutSize::maybe(axis.cross(), cross_length); @@ -245,7 +245,7 @@ impl Widget for IndexedStack { cross_length, ) } else { - 0. + Length::ZERO } } diff --git a/masonry/src/widgets/label.rs b/masonry/src/widgets/label.rs index 325bce4662..a0bb99d2a0 100644 --- a/masonry/src/widgets/label.rs +++ b/masonry/src/widgets/label.rs @@ -16,7 +16,7 @@ use crate::core::{ }; use crate::imaging::Painter; use crate::kurbo::{Affine, Axis, Point, Size}; -use crate::layout::LenReq; +use crate::layout::{AsUnit, LenReq, Length}; use crate::parley::{FontContext, Layout, LayoutAccessibility, LayoutContext}; use crate::properties::{ContentColor, LineBreaking}; use crate::theme::default_text_styles; @@ -499,8 +499,8 @@ impl Widget for Label { props: &PropertiesRef<'_>, axis: Axis, len_req: LenReq, - cross_length: Option, - ) -> f64 { + cross_length: Option, + ) -> Length { // Currently we only support the common horizontal-tb writing mode, // so we hardcode the assumption that inline axis is horizontal. let inline = Axis::Horizontal; @@ -517,7 +517,7 @@ impl Widget for Label { // This is a common optimization also present on the web. match len_req { // Zero space will get us the length of longest unbreakable word - LenReq::MinContent => Some(0.), + LenReq::MinContent => Some(Length::ZERO), // Unbounded space will get us the length of the unwrapped string LenReq::MaxContent => None, // Attempt to wrap according to the parent's request @@ -528,7 +528,7 @@ impl Widget for Label { // If there is no explicit cross_length present, we fall back to inline defaults. match len_req { // Fallback is inline axis MinContent - LenReq::MinContent => cross_length.or(Some(0.)), + LenReq::MinContent => cross_length.or(Some(Length::ZERO)), // Fallback is inline axis MaxContent, even for FitContent, because // as we don't have the inline space bound we'll consider it unbounded. LenReq::MaxContent | LenReq::FitContent(_) => cross_length, @@ -538,7 +538,7 @@ impl Widget for Label { // If we're never wrapping, then there's no max advance. LineBreaking::Clip | LineBreaking::Overflow => None, } - .map(|v| v as f32); + .map(|v| v.get() as f32); let (font_ctx, layout_ctx) = ctx.text_contexts(); let layout_idx = self.build_and_break(font_ctx, layout_ctx, max_advance); @@ -550,7 +550,7 @@ impl Widget for Label { layout.layout.height() // Block length }; - length as f64 + length.px() } fn layout(&mut self, ctx: &mut LayoutCtx<'_>, props: &PropertiesRef<'_>, size: Size) { diff --git a/masonry/src/widgets/pagination.rs b/masonry/src/widgets/pagination.rs index 7b9b9fa780..ad5bc20016 100644 --- a/masonry/src/widgets/pagination.rs +++ b/masonry/src/widgets/pagination.rs @@ -13,7 +13,7 @@ use crate::core::{ }; use crate::imaging::Painter; use crate::kurbo::{Axis, Point, Size}; -use crate::layout::{LayoutSize, LenReq, SizeDef}; +use crate::layout::{LayoutSize, LenReq, Length, SizeDef}; use crate::widgets::{Button, ButtonPress, Label}; /// Pagination for navigating between different page numbers. @@ -315,9 +315,9 @@ impl Widget for Pagination { _props: &PropertiesRef<'_>, axis: Axis, len_req: LenReq, - cross_length: Option, - ) -> f64 { - let mut length = 0.; + cross_length: Option, + ) -> Length { + let mut length = Length::ZERO; let context_size = LayoutSize::maybe(axis.cross(), cross_length); @@ -331,7 +331,7 @@ impl Widget for Pagination { cross_length, ); length = match axis { - Axis::Horizontal => length + button_length, + Axis::Horizontal => length.saturating_add(button_length), Axis::Vertical => length.max(button_length), }; } diff --git a/masonry/src/widgets/passthrough.rs b/masonry/src/widgets/passthrough.rs index d3882b7e6c..09a9b4f300 100644 --- a/masonry/src/widgets/passthrough.rs +++ b/masonry/src/widgets/passthrough.rs @@ -10,7 +10,7 @@ use crate::core::{ }; use crate::imaging::Painter; use crate::kurbo::{Axis, Point, Size}; -use crate::layout::LenReq; +use crate::layout::{LenReq, Length}; /// A pass-through container that hosts exactly one child, which may be replaced dynamically. /// @@ -93,8 +93,8 @@ impl Widget for Passthrough { _props: &PropertiesRef<'_>, axis: Axis, _len_req: LenReq, - cross_length: Option, - ) -> f64 { + cross_length: Option, + ) -> Length { ctx.redirect_measurement(&mut self.inner, axis, cross_length) } diff --git a/masonry/src/widgets/portal.rs b/masonry/src/widgets/portal.rs index 9a894d90a2..3132d6dcbe 100644 --- a/masonry/src/widgets/portal.rs +++ b/masonry/src/widgets/portal.rs @@ -15,7 +15,7 @@ use crate::core::{ use crate::dpi::{LogicalPosition, PhysicalPosition}; use crate::imaging::Painter; use crate::kurbo::{Axis, Point, Rect, Size, Vec2}; -use crate::layout::{LayoutSize, LenDef, LenReq, SizeDef}; +use crate::layout::{AsUnit, LayoutSize, LenDef, LenReq, Length, SizeDef}; use crate::widgets::ScrollBar; // TODO - refactor - see https://github.com/linebender/xilem/issues/366 @@ -414,8 +414,6 @@ impl Widget for Portal { match *event { PointerEvent::Scroll(PointerScrollEvent { delta, .. }) => { - // TODO - Remove reference to scale factor. - // See https://github.com/linebender/xilem/issues/1264 let scale_factor = ctx.get_scale_factor(); let line_px = PhysicalPosition { x: 120.0 * scale_factor, @@ -470,12 +468,8 @@ impl Widget for Portal { // the arrow/page/home/end keys. && !scrollbar_target { - // TODO: Remove HACK: Until scale factor rework happens, just pretend it's always 1.0. - // https://github.com/linebender/xilem/issues/1264 - let scale = 1.0; - - let line = 120.0 * scale; - let page_y = portal_size.height * scale; + let line = 120.0; + let page_y = portal_size.height; use crate::core::keyboard::{Key, NamedKey}; let mut did_scroll = false; @@ -580,23 +574,19 @@ impl Widget for Portal { | accesskit::Action::ScrollRight ) { - // TODO: Remove HACK: Until scale factor rework happens, just pretend it's always 1.0. - // https://github.com/linebender/xilem/issues/1264 - let scale = 1.0; - let unit = if let Some(accesskit::ActionData::ScrollUnit(unit)) = &event.data { *unit } else { accesskit::ScrollUnit::Item }; - let line = 120.0 * scale; + let line = 120.0; let amount = match unit { accesskit::ScrollUnit::Item => line, accesskit::ScrollUnit::Page => match event.action { accesskit::Action::ScrollLeft | accesskit::Action::ScrollRight => { - portal_size.width * scale + portal_size.width } - _ => portal_size.height * scale, + _ => portal_size.height, }, }; @@ -662,10 +652,10 @@ impl Widget for Portal { _props: &PropertiesRef<'_>, axis: Axis, len_req: LenReq, - cross_length: Option, - ) -> f64 { + cross_length: Option, + ) -> Length { match len_req { - LenReq::MinContent => 0., + LenReq::MinContent => Length::ZERO, LenReq::MaxContent => { let context_size = LayoutSize::maybe(axis.cross(), cross_length); let auto_length = len_req.into(); @@ -691,11 +681,11 @@ impl Widget for Portal { fn layout(&mut self, ctx: &mut LayoutCtx<'_>, _props: &PropertiesRef<'_>, size: Size) { let auto_size = SizeDef::new( match self.constrain_horizontal { - true => LenDef::FitContent(size.width), + true => LenDef::FitContent(size.width.px()), false => LenDef::MaxContent, }, match self.constrain_vertical { - true => LenDef::FitContent(size.height), + true => LenDef::FitContent(size.height.px()), false => LenDef::MaxContent, }, ); @@ -882,11 +872,12 @@ mod tests { let context_size = LayoutSize::maybe(axis.cross(), cross_length); let other = match axis { - Axis::Horizontal => 0., - Axis::Vertical => top_pad, + Axis::Horizontal => Length::ZERO, + Axis::Vertical => top_pad.px(), }; - ctx.compute_length(child, auto_length, context_size, axis, cross_length) + other + ctx.compute_length(child, auto_length, context_size, axis, cross_length) + .saturating_add(other) }) .layout_fn(move |child, ctx, _props, size| { let child_size = ctx.compute_size(child, SizeDef::fit(size), size.into()); diff --git a/masonry/src/widgets/progress_bar.rs b/masonry/src/widgets/progress_bar.rs index 890f837317..34a891ac1b 100644 --- a/masonry/src/widgets/progress_bar.rs +++ b/masonry/src/widgets/progress_bar.rs @@ -15,7 +15,7 @@ use crate::core::{ }; use crate::imaging::Painter; use crate::kurbo::{Axis, Size}; -use crate::layout::{LayoutSize, LenReq, SizeDef}; +use crate::layout::{LayoutSize, LenReq, Length, SizeDef}; use crate::peniko::{Color, Gradient}; use crate::properties::{BarColor, BorderColor, BorderWidth, CornerRadius, LineBreaking}; use crate::theme; @@ -139,14 +139,10 @@ impl Widget for ProgressBar { _props: &PropertiesRef<'_>, axis: Axis, len_req: LenReq, - cross_length: Option, - ) -> f64 { + cross_length: Option, + ) -> Length { // TODO: Move this to theme? - const DEFAULT_WIDTH: f64 = 400.; // In logical pixels - - // TODO: Remove HACK: Until scale factor rework happens, just pretend it's always 1.0. - // https://github.com/linebender/xilem/issues/1264 - let scale = 1.0; + const DEFAULT_WIDTH: Length = Length::const_px(400.); let auto_length = len_req.into(); let context_size = LayoutSize::maybe(axis.cross(), cross_length); @@ -161,10 +157,10 @@ impl Widget for ProgressBar { let potential_length = match axis { Axis::Horizontal => match len_req { - LenReq::MinContent | LenReq::MaxContent => DEFAULT_WIDTH * scale, + LenReq::MinContent | LenReq::MaxContent => DEFAULT_WIDTH, LenReq::FitContent(space) => space, }, - Axis::Vertical => theme::BASIC_WIDGET_HEIGHT.dp(scale), + Axis::Vertical => theme::BASIC_WIDGET_HEIGHT, }; // Make sure we always report a length big enough to fit our painting diff --git a/masonry/src/widgets/prose.rs b/masonry/src/widgets/prose.rs index 7d6ec20be4..d1aeec79a1 100644 --- a/masonry/src/widgets/prose.rs +++ b/masonry/src/widgets/prose.rs @@ -12,7 +12,7 @@ use crate::core::{ }; use crate::imaging::Painter; use crate::kurbo::{Axis, Point, Size}; -use crate::layout::LenReq; +use crate::layout::{LenReq, Length}; use crate::widgets::TextArea; /// The prose widget displays immutable text which can be @@ -147,8 +147,8 @@ impl Widget for Prose { _props: &PropertiesRef<'_>, axis: Axis, _len_req: LenReq, - cross_length: Option, - ) -> f64 { + cross_length: Option, + ) -> Length { ctx.redirect_measurement(&mut self.text, axis, cross_length) } diff --git a/masonry/src/widgets/radio_button.rs b/masonry/src/widgets/radio_button.rs index 20c3ca08eb..62063d56d8 100644 --- a/masonry/src/widgets/radio_button.rs +++ b/masonry/src/widgets/radio_button.rs @@ -16,7 +16,7 @@ use crate::core::{ }; use crate::imaging::Painter; use crate::kurbo::{Axis, Cap, Circle, Dashes, Join, Point, Size, Stroke}; -use crate::layout::{LayoutSize, LenReq, SizeDef}; +use crate::layout::{LayoutSize, LenReq, Length, SizeDef}; use crate::properties::{BorderColor, BorderWidth, CheckmarkColor, CheckmarkStrokeWidth}; use crate::theme; use crate::widgets::{Label, RadioGroup}; @@ -236,25 +236,21 @@ impl Widget for RadioButton { _props: &PropertiesRef<'_>, axis: Axis, len_req: LenReq, - cross_length: Option, - ) -> f64 { - // TODO: Remove HACK: Until scale factor rework happens, just pretend it's always 1.0. - // https://github.com/linebender/xilem/issues/1264 - let scale = 1.0; - - let check_side = theme::BASIC_WIDGET_HEIGHT.dp(scale); - let check_padding = theme::WIDGET_CONTROL_COMPONENT_PADDING.dp(scale); + cross_length: Option, + ) -> Length { + let check_side = theme::BASIC_WIDGET_HEIGHT; + let check_padding = theme::WIDGET_CONTROL_COMPONENT_PADDING; let calc_other_length = |axis| match axis { - Axis::Horizontal => check_side + check_padding, - Axis::Vertical => 0., + Axis::Horizontal => check_side.saturating_add(check_padding), + Axis::Vertical => Length::ZERO, }; let other_length = calc_other_length(axis); let cross = axis.cross(); let cross_space = cross_length.map(|cross_length| { let cross_other_length = calc_other_length(cross); - (cross_length - cross_other_length).max(0.) + cross_length.saturating_sub(cross_other_length) }); let auto_length = len_req.reduce(other_length).into(); @@ -269,18 +265,14 @@ impl Widget for RadioButton { ); match axis { - Axis::Horizontal => label_length + other_length, - Axis::Vertical => label_length.max(check_side) + other_length, + Axis::Horizontal => label_length.saturating_add(other_length), + Axis::Vertical => label_length.max(check_side).saturating_add(other_length), } } fn layout(&mut self, ctx: &mut LayoutCtx<'_>, _props: &PropertiesRef<'_>, size: Size) { - // TODO: Remove HACK: Until scale factor rework happens, just pretend it's always 1.0. - // https://github.com/linebender/xilem/issues/1264 - let scale = 1.0; - - let check_side = theme::BASIC_WIDGET_HEIGHT.dp(scale); - let check_padding = theme::WIDGET_CONTROL_COMPONENT_PADDING.dp(scale); + let check_side = theme::BASIC_WIDGET_HEIGHT.get(); + let check_padding = theme::WIDGET_CONTROL_COMPONENT_PADDING.get(); let space = Size::new( (size.width - (check_side + check_padding)).max(0.), @@ -342,16 +334,12 @@ impl Widget for RadioButton { props: &PropertiesRef<'_>, painter: &mut Painter<'_>, ) { - // TODO: Remove HACK: Until scale factor rework happens, just pretend it's always 1.0. - // https://github.com/linebender/xilem/issues/1264 - let scale = 1.0; - let cache = ctx.property_cache(); let border_color = *props.get::(cache); let border_width = *props.get::(cache); let brush = *props.get::(cache); - let check_side = theme::BASIC_WIDGET_HEIGHT.dp(scale); + let check_side = theme::BASIC_WIDGET_HEIGHT.get(); let check_size = Size::new(check_side, check_side); let border_circle = Circle::new( diff --git a/masonry/src/widgets/radio_group.rs b/masonry/src/widgets/radio_group.rs index f79a11905a..7745087308 100644 --- a/masonry/src/widgets/radio_group.rs +++ b/masonry/src/widgets/radio_group.rs @@ -10,7 +10,7 @@ use crate::core::{ }; use crate::imaging::Painter; use crate::kurbo::{Axis, Point, Size}; -use crate::layout::LenReq; +use crate::layout::{LenReq, Length}; /// A radio group container that holds radio buttons. pub struct RadioGroup { @@ -53,8 +53,8 @@ impl Widget for RadioGroup { _props: &PropertiesRef<'_>, axis: Axis, _len_req: LenReq, - cross_length: Option, - ) -> f64 { + cross_length: Option, + ) -> Length { ctx.redirect_measurement(&mut self.child, axis, cross_length) } diff --git a/masonry/src/widgets/resize_observer.rs b/masonry/src/widgets/resize_observer.rs index 1558922279..b56d0fa73b 100644 --- a/masonry/src/widgets/resize_observer.rs +++ b/masonry/src/widgets/resize_observer.rs @@ -9,7 +9,7 @@ use crate::core::{ }; use crate::imaging::Painter; use crate::kurbo::{Axis, Point, Size}; -use crate::layout::LenReq; +use crate::layout::{LenReq, Length}; /// A widget which sends a [`LayoutChanged`] whenever its size changes. /// @@ -126,8 +126,8 @@ impl Widget for ResizeObserver { _props: &PropertiesRef<'_>, axis: Axis, _len_req: LenReq, - cross_length: Option, - ) -> f64 { + cross_length: Option, + ) -> Length { ctx.redirect_measurement(&mut self.child, axis, cross_length) } diff --git a/masonry/src/widgets/scroll_bar.rs b/masonry/src/widgets/scroll_bar.rs index f6b3f86a34..9f17a86291 100644 --- a/masonry/src/widgets/scroll_bar.rs +++ b/masonry/src/widgets/scroll_bar.rs @@ -13,7 +13,7 @@ use crate::core::{ }; use crate::imaging::Painter; use crate::kurbo::{Axis, Point, Rect, Size, Stroke}; -use crate::layout::LenReq; +use crate::layout::{AsUnit, LenReq, Length}; use crate::theme; // TODO @@ -236,12 +236,8 @@ impl Widget for ScrollBar { if event.state != KeyState::Down { return; } - - // TODO: Remove HACK: Until scale factor rework happens, just pretend it's always 1.0. - // https://github.com/linebender/xilem/issues/1264 - let scale = 1.0; - let line = 120.0 * scale; - let page = self.portal_size * scale; + let line = 120.0; + let page = self.portal_size; let mut changed = false; match (&event.key, self.axis) { @@ -310,17 +306,13 @@ impl Widget for ScrollBar { if !action_matches_axis { return; } - - // TODO: Remove HACK: Until scale factor rework happens, just pretend it's always 1.0. - // https://github.com/linebender/xilem/issues/1264 - let scale = 1.0; let unit = if let Some(accesskit::ActionData::ScrollUnit(unit)) = &event.data { *unit } else { accesskit::ScrollUnit::Item }; - let line = 120.0 * scale; - let page = self.portal_size * scale; + let line = 120.0; + let page = self.portal_size; let amount = match unit { accesskit::ScrollUnit::Item => line, accesskit::ScrollUnit::Page => page, @@ -352,23 +344,19 @@ impl Widget for ScrollBar { _props: &PropertiesRef<'_>, axis: Axis, len_req: LenReq, - _cross_length: Option, - ) -> f64 { - // TODO: Remove HACK: Until scale factor rework happens, just pretend it's always 1.0. - // https://github.com/linebender/xilem/issues/1264 - let scale = 1.0; - + _cross_length: Option, + ) -> Length { if axis == self.axis { - // TODO: Consider .max(theme::SCROLLBAR_MIN_SIZE * scale) + // TODO: Consider .max(theme::SCROLLBAR_MIN_SIZE) match len_req { - LenReq::MinContent | LenReq::MaxContent => self.portal_size, + LenReq::MinContent | LenReq::MaxContent => self.portal_size.px(), LenReq::FitContent(space) => space, } } else { - let scrollbar_width = theme::SCROLLBAR_WIDTH * scale; - let cursor_padding = theme::SCROLLBAR_PAD * scale; + let scrollbar_width = theme::SCROLLBAR_WIDTH; + let cursor_padding = theme::SCROLLBAR_PAD; - scrollbar_width + cursor_padding * 2.0 + (scrollbar_width + cursor_padding * 2.0).px() } } diff --git a/masonry/src/widgets/selector.rs b/masonry/src/widgets/selector.rs index 7c14099f64..008d709138 100644 --- a/masonry/src/widgets/selector.rs +++ b/masonry/src/widgets/selector.rs @@ -13,7 +13,7 @@ use crate::core::{ use crate::imaging::Painter; use crate::kurbo::{Axis, Size, Vec2}; use crate::layers::SelectorMenu; -use crate::layout::{LayoutSize, LenReq, SizeDef}; +use crate::layout::{AsUnit, LayoutSize, LenReq, Length, SizeDef}; use crate::theme; use crate::util::debug_panic; use crate::widgets::{Label, SelectorItem}; @@ -227,12 +227,8 @@ impl Widget for Selector { _props: &PropertiesRef<'_>, axis: Axis, len_req: LenReq, - cross_length: Option, - ) -> f64 { - // TODO: Remove HACK: Until scale factor rework happens, just pretend it's always 1.0. - // https://github.com/linebender/xilem/issues/1264 - let scale = 1.0; - + cross_length: Option, + ) -> Length { let auto_length = len_req.into(); let context_size = LayoutSize::maybe(axis.cross(), cross_length); @@ -251,8 +247,8 @@ impl Widget for Selector { // we make sure we will have at least the same height as the default text input. // We also set a minimum width. match axis { - Axis::Horizontal => length.max(theme::SELECTOR_MIN_WIDTH * scale), - Axis::Vertical => length.max(theme::BORDERED_WIDGET_HEIGHT * scale), + Axis::Horizontal => length.max(theme::SELECTOR_MIN_WIDTH.px()), + Axis::Vertical => length.max(theme::BORDERED_WIDGET_HEIGHT.px()), } } diff --git a/masonry/src/widgets/selector_item.rs b/masonry/src/widgets/selector_item.rs index 223561dffa..5c47952585 100644 --- a/masonry/src/widgets/selector_item.rs +++ b/masonry/src/widgets/selector_item.rs @@ -13,7 +13,7 @@ use crate::core::{ }; use crate::imaging::Painter; use crate::kurbo::{Axis, Size}; -use crate::layout::{LayoutSize, LenReq, SizeDef}; +use crate::layout::{LayoutSize, LenReq, Length, SizeDef}; use crate::widgets::Label; /// An option in a [`SelectorMenu`](crate::layers::SelectorMenu). @@ -91,8 +91,8 @@ impl Widget for SelectorItem { _props: &PropertiesRef<'_>, axis: Axis, len_req: LenReq, - cross_length: Option, - ) -> f64 { + cross_length: Option, + ) -> Length { let auto_length = len_req.into(); let context_size = LayoutSize::maybe(axis.cross(), cross_length); diff --git a/masonry/src/widgets/sized_box.rs b/masonry/src/widgets/sized_box.rs index e63e62420a..a7f9bb338b 100644 --- a/masonry/src/widgets/sized_box.rs +++ b/masonry/src/widgets/sized_box.rs @@ -225,22 +225,20 @@ impl Widget for SizedBox { props: &PropertiesRef<'_>, axis: Axis, len_req: LenReq, - cross_length: Option, - ) -> f64 { - // TODO: Remove HACK: Until scale factor rework happens, just pretend it's always 1.0. - // https://github.com/linebender/xilem/issues/1264 - let scale = 1.0; - + cross_length: Option, + ) -> Length { let cache = ctx.property_cache(); let border = props.get::(cache); let padding = props.get::(cache); - let border_length = border.length(axis).dp(scale); - let padding_length = padding.length(axis).dp(scale); + let border_length = border.length(axis); + let padding_length = padding.length(axis); // First see if we have an explicitly defined length if let Some(length) = self.length(axis) { - return (length.dp(scale) - border_length - padding_length).max(0.); + return length + .saturating_sub(border_length) + .saturating_sub(padding_length); } // Otherwise measure the child @@ -254,9 +252,9 @@ impl Widget for SizedBox { Axis::Vertical => self.height, }; length.map(|length| { - let cross_border_length = border.length(cross).dp(scale); - let cross_padding_length = padding.length(cross).dp(scale); - (length.dp(scale) - cross_border_length - cross_padding_length).max(0.) + length + .saturating_sub(border.length(cross)) + .saturating_sub(padding.length(cross)) }) }); @@ -265,7 +263,7 @@ impl Widget for SizedBox { ctx.compute_length(child, auto_length, context_size, axis, cross_length) } else { - 0. + Length::ZERO } } diff --git a/masonry/src/widgets/slider.rs b/masonry/src/widgets/slider.rs index b676c5a4c6..eaa2a65511 100644 --- a/masonry/src/widgets/slider.rs +++ b/masonry/src/widgets/slider.rs @@ -16,7 +16,7 @@ use crate::core::{ }; use crate::imaging::{Composite, GroupRef, Painter}; use crate::kurbo::{Axis, Circle, Point, Rect, Size, Stroke}; -use crate::layout::LenReq; +use crate::layout::{AsUnit, LenReq, Length}; use crate::properties::{Background, BarColor, ThumbColor, ThumbRadius, TrackThickness}; use crate::theme; @@ -335,16 +335,12 @@ impl Widget for Slider { props: &PropertiesRef<'_>, axis: Axis, len_req: LenReq, - _cross_length: Option, - ) -> f64 { - // TODO: Remove HACK: Until scale factor rework happens, just pretend it's always 1.0. - // https://github.com/linebender/xilem/issues/1264 - let scale = 1.0; - + _cross_length: Option, + ) -> Length { match axis { Axis::Horizontal => match len_req { // TODO: Move this 100. to theme? - LenReq::MinContent | LenReq::MaxContent => 100. * scale, + LenReq::MinContent | LenReq::MaxContent => Length::const_px(100.), LenReq::FitContent(space) => space, }, Axis::Vertical => { @@ -352,12 +348,12 @@ impl Widget for Slider { let thumb_radius = props.get::(cache); let track_thickness = props.get::(cache); - let thumb_length = thumb_radius.0 * 2.0 * scale; - let track_length = track_thickness.0 * scale; + let thumb_length = thumb_radius.0 * 2.0; + let track_length = track_thickness.0; // TODO: Move the padding 16. to theme or make it otherwise configurable? - let padding_length = 16. * scale; + let padding_length = 16.; - thumb_length.max(track_length) + padding_length + (thumb_length.max(track_length) + padding_length).px() } } } diff --git a/masonry/src/widgets/spinner.rs b/masonry/src/widgets/spinner.rs index bfa471e9bf..f56afeb48a 100644 --- a/masonry/src/widgets/spinner.rs +++ b/masonry/src/widgets/spinner.rs @@ -14,7 +14,7 @@ use crate::core::{ }; use crate::imaging::Painter; use crate::kurbo::{Axis, Cap, Line, Point, Size, Stroke, Vec2}; -use crate::layout::LenReq; +use crate::layout::{LenReq, Length}; use crate::properties::ContentColor; use crate::theme; @@ -87,17 +87,13 @@ impl Widget for Spinner { _props: &PropertiesRef<'_>, _axis: Axis, len_req: LenReq, - cross_length: Option, - ) -> f64 { - // TODO: Remove HACK: Until scale factor rework happens, just pretend it's always 1.0. - // https://github.com/linebender/xilem/issues/1264 - let scale = 1.0; - + cross_length: Option, + ) -> Length { match len_req { // For preferred length we try to keep a square aspect ratio, // and when the cross length is unknown we fall back to the theme's default. LenReq::MinContent | LenReq::MaxContent => { - cross_length.unwrap_or(theme::BASIC_WIDGET_HEIGHT.dp(scale)) + cross_length.unwrap_or(theme::BASIC_WIDGET_HEIGHT) } LenReq::FitContent(space) => space, } diff --git a/masonry/src/widgets/split.rs b/masonry/src/widgets/split.rs index 5c34436842..fc814e60e1 100644 --- a/masonry/src/widgets/split.rs +++ b/masonry/src/widgets/split.rs @@ -181,20 +181,20 @@ impl Split { impl Split { /// Returns the thickness of the splitter bar area. #[inline] - fn bar_area(&self, scale: f64) -> f64 { - self.bar_thickness.max(self.min_bar_area).dp(scale) + fn bar_area(&self) -> f64 { + self.bar_thickness.max(self.min_bar_area).get() } /// Returns the splitter bar center point. - fn bar_center(&self, length: f64, scale: f64) -> f64 { - let (edge1, edge2) = self.bar_edges(length, scale); + fn bar_center(&self, length: f64) -> f64 { + let (edge1, edge2) = self.bar_edges(length); (edge1 + edge2) * 0.5 } /// Returns the location of the edges of the splitter bar, /// given the specified total length. - fn bar_edges(&self, length: f64, scale: f64) -> (f64, f64) { - let bar_thickness = self.bar_thickness.dp(scale); + fn bar_edges(&self, length: f64) -> (f64, f64) { + let bar_thickness = self.bar_thickness.get(); let reduced_length = length - bar_thickness; let edge = reduced_length * self.split_point_effective; (edge, edge + bar_thickness) @@ -202,10 +202,10 @@ impl Split { /// Returns the location of the edges of the splitter bar area, /// given the specified total length. - fn bar_area_edges(&self, length: f64, scale: f64) -> (f64, f64) { - let (edge1, edge2) = self.bar_edges(length, scale); + fn bar_area_edges(&self, length: f64) -> (f64, f64) { + let (edge1, edge2) = self.bar_edges(length); let (space1, space2) = (edge1.max(0.), (length - edge2).max(0.)); - let padding = self.bar_area(scale) - self.bar_thickness.dp(scale); + let padding = self.bar_area() - self.bar_thickness.get(); // Half the padding to the first edge let pad1 = (0.5 * padding).min(space1); @@ -218,16 +218,16 @@ impl Split { } /// Returns `true` if the provided position is on the splitter bar area. - fn bar_area_hit_test(&self, length: f64, pos: f64, scale: f64) -> bool { - let (edge1, edge2) = self.bar_area_edges(length, scale); + fn bar_area_hit_test(&self, length: f64, pos: f64) -> bool { + let (edge1, edge2) = self.bar_area_edges(length); pos >= edge1 && pos <= edge2 } /// Returns the minimum and maximum split coordinate of the provided length. - fn split_side_limits(&self, length: f64, scale: f64) -> (f64, f64) { + fn split_side_limits(&self, length: f64) -> (f64, f64) { let (min_child1, min_child2) = self.min_lengths; - let mut min_limit = min_child1.dp(scale); - let mut max_limit = (length - min_child2.dp(scale)).max(0.0); + let mut min_limit = min_child1.get(); + let mut max_limit = (length - min_child2.get()).max(0.0); if min_limit > max_limit { min_limit = 0.5 * (min_limit + max_limit); @@ -237,22 +237,22 @@ impl Split { (min_limit, max_limit) } - fn calc_effective_split_point(&self, length: f64, scale: f64) -> f64 { - let (min_limit, max_limit) = self.split_side_limits(length, scale); + fn calc_effective_split_point(&self, length: f64) -> f64 { + let (min_limit, max_limit) = self.split_side_limits(length); if length <= f64::EPSILON { 0.5 } else { let child1_len = match self.split_point_chosen { SplitPoint::Fraction(frac) => length * frac, - SplitPoint::FromStart(len) => len.dp(scale), - SplitPoint::FromEnd(len) => length - len.dp(scale), + SplitPoint::FromStart(len) => len.get(), + SplitPoint::FromEnd(len) => length - len.get(), }; (child1_len / length).clamp(min_limit / length, max_limit / length) } } - fn set_chosen_from_child1_len(&mut self, length: f64, child1_len: f64, scale: f64) { - let (min_limit, max_limit) = self.split_side_limits(length, scale); + fn set_chosen_from_child1_len(&mut self, length: f64, child1_len: f64) { + let (min_limit, max_limit) = self.split_side_limits(length); let child1_len = child1_len.clamp(min_limit, max_limit); match self.split_point_chosen { @@ -264,27 +264,20 @@ impl Split { }); } SplitPoint::FromStart(_) => { - let logical = child1_len / scale; - self.split_point_chosen = SplitPoint::FromStart(Length::px(logical)); + self.split_point_chosen = SplitPoint::FromStart(Length::px(child1_len)); } SplitPoint::FromEnd(_) => { let child2_len = (length - child1_len).max(0.0); - let logical = child2_len / scale; - self.split_point_chosen = SplitPoint::FromEnd(Length::px(logical)); + self.split_point_chosen = SplitPoint::FromEnd(Length::px(child2_len)); } } } - fn update_split_point_from_bar_center( - &mut self, - total_length: f64, - bar_center: f64, - scale: f64, - ) { - let bar_thickness = self.bar_thickness.dp(scale); + fn update_split_point_from_bar_center(&mut self, total_length: f64, bar_center: f64) { + let bar_thickness = self.bar_thickness.get(); let split_space = (total_length - bar_thickness).max(0.0); let child1_len = bar_center - bar_thickness * 0.5; - self.set_chosen_from_child1_len(split_space, child1_len, scale); + self.set_chosen_from_child1_len(split_space, child1_len); } /// Returns the color of the splitter bar. @@ -299,29 +292,23 @@ impl Split { } } - fn paint_focus_bar(&mut self, ctx: &mut PaintCtx<'_>, scene: &mut Painter<'_>, scale: f64) { + fn paint_focus_bar(&mut self, ctx: &mut PaintCtx<'_>, scene: &mut Painter<'_>) { let length = ctx.content_box_size().get_coord(self.split_axis); - let (edge1, edge2) = self.bar_edges(length, scale); + let (edge1, edge2) = self.bar_edges(length); let mut rect = ctx.border_box(); rect.set_coords(self.split_axis, edge1, edge2); - let rect = rect.inset(2.0 * scale); + let rect = rect.inset(2.0); let focus_color = theme::FOCUS_COLOR.with_alpha(if ctx.is_active() { 1.0 } else { 0.5 }); - let focus_stroke = Stroke::new(1.0 * scale).with_join(Join::Miter); + let focus_stroke = Stroke::new(1.0).with_join(Join::Miter); scene.stroke(rect, &focus_stroke, focus_color).draw(); } - fn paint_solid_bar( - &mut self, - ctx: &mut PaintCtx<'_>, - scene: &mut Painter<'_>, - scale: f64, - color: Color, - ) { + fn paint_solid_bar(&mut self, ctx: &mut PaintCtx<'_>, scene: &mut Painter<'_>, color: Color) { let length = ctx.content_box_size().get_coord(self.split_axis); - let (edge1, edge2) = self.bar_edges(length, scale); + let (edge1, edge2) = self.bar_edges(length); let mut rect = ctx.border_box(); rect.set_coords(self.split_axis, edge1, edge2); @@ -329,19 +316,13 @@ impl Split { scene.fill(rect, color).draw(); } - fn paint_stroked_bar( - &mut self, - ctx: &mut PaintCtx<'_>, - scene: &mut Painter<'_>, - scale: f64, - color: Color, - ) { + fn paint_stroked_bar(&mut self, ctx: &mut PaintCtx<'_>, scene: &mut Painter<'_>, color: Color) { let length = ctx.content_box_size().get_coord(self.split_axis); // Set the line width to a third of the splitter bar thickness, // because we'll paint two equal lines at the edges. - let line_width = self.bar_thickness.dp(scale) / 3.0; + let line_width = self.bar_thickness.get() / 3.0; let line_midpoint = line_width / 2.0; - let (edge1, edge2) = self.bar_edges(length, scale); + let (edge1, edge2) = self.bar_edges(length); let edge1_line_pos = edge1 + line_midpoint; let edge2_line_pos = edge2 - line_midpoint; @@ -475,10 +456,6 @@ where _props: &mut PropertiesMut<'_>, event: &PointerEvent, ) { - // TODO: Remove HACK: Until scale factor rework happens, just pretend it's always 1.0. - // https://github.com/linebender/xilem/issues/1264 - let scale = 1.0; - if self.draggable { match event { PointerEvent::Down(PointerButtonEvent { state, .. }) => { @@ -486,12 +463,12 @@ where .local_position(state.position) .get_coord(self.split_axis); let length = ctx.content_box_size().get_coord(self.split_axis); - if self.bar_area_hit_test(length, pos, scale) { + if self.bar_area_hit_test(length, pos) { ctx.set_handled(); ctx.capture_pointer(); ctx.request_focus(); // Save the delta between the click position and the bar center. - self.click_offset = pos - self.bar_center(length, scale); + self.click_offset = pos - self.bar_center(length); } } PointerEvent::Move(PointerUpdate { current, .. }) if ctx.is_active() => { @@ -501,7 +478,7 @@ where let length = ctx.content_box_size().get_coord(self.split_axis); // If widget has pointer capture, assume always it's hovered let effective_center = pos - self.click_offset; - self.update_split_point_from_bar_center(length, effective_center, scale); + self.update_split_point_from_bar_center(length, effective_center); ctx.request_layout(); } PointerEvent::Up(..) | PointerEvent::Cancel(..) => { @@ -529,12 +506,8 @@ where return; } - // TODO: Remove HACK: Until scale factor rework happens, just pretend it's always 1.0. - // https://github.com/linebender/xilem/issues/1264 - let scale = 1.0; - let length = ctx.content_box_size().get_coord(self.split_axis); - let bar_thickness = self.bar_thickness.dp(scale); + let bar_thickness = self.bar_thickness.get(); let split_space = (length - bar_thickness).max(0.0); if split_space <= f64::EPSILON { return; @@ -563,15 +536,15 @@ where child1_len += delta; } Key::Named(NamedKey::Home) => { - child1_len = self.split_side_limits(split_space, scale).0; + child1_len = self.split_side_limits(split_space).0; } Key::Named(NamedKey::End) => { - child1_len = self.split_side_limits(split_space, scale).1; + child1_len = self.split_side_limits(split_space).1; } _ => return, } - self.set_chosen_from_child1_len(split_space, child1_len, scale); + self.set_chosen_from_child1_len(split_space, child1_len); ctx.request_layout(); } @@ -585,12 +558,8 @@ where return; } - // TODO: Remove HACK: Until scale factor rework happens, just pretend it's always 1.0. - // https://github.com/linebender/xilem/issues/1264 - let scale = 1.0; - let length = ctx.content_box_size().get_coord(self.split_axis); - let bar_thickness = self.bar_thickness.dp(scale); + let bar_thickness = self.bar_thickness.get(); let split_space = (length - bar_thickness).max(0.0); if split_space <= f64::EPSILON { return; @@ -614,7 +583,7 @@ where _ => return, } - self.set_chosen_from_child1_len(split_space, child1_len, scale); + self.set_chosen_from_child1_len(split_space, child1_len); ctx.request_layout(); } @@ -641,19 +610,13 @@ where _props: &PropertiesRef<'_>, axis: Axis, len_req: LenReq, - cross_length: Option, - ) -> f64 { - // TODO: Remove HACK: Until scale factor rework happens, just pretend it's always 1.0. - // https://github.com/linebender/xilem/issues/1264 - let scale = 1.0; - - let bar_thickness = self.bar_thickness.dp(scale); - + cross_length: Option, + ) -> Length { if let LenReq::FitContent(space) = len_req { // We always want to use up all offered space if axis == self.split_axis { // Don't go below the bar thickness, which we always want to paint. - return space.max(bar_thickness); + return space.max(self.bar_thickness); } return space; } @@ -665,10 +628,13 @@ where .map(|cross_length| { // We need to split the cross length if it's our split axis if cross == self.split_axis { - let cross_space = (cross_length - bar_thickness).max(0.); - let split_point = self.calc_effective_split_point(cross_space, scale); - let child1_cross_space = cross_space * split_point; - (child1_cross_space, cross_space - child1_cross_space) + let cross_space = cross_length.saturating_sub(self.bar_thickness); + let split_point = self.calc_effective_split_point(cross_space.get()); + let child1_cross_space = cross_space.saturating_mul(split_point.px()); + ( + child1_cross_space, + cross_space.saturating_sub(child1_cross_space), + ) } else { (cross_length, cross_length) } @@ -693,23 +659,21 @@ where ); if axis == self.split_axis { - child1_length + child2_length + bar_thickness + child1_length + .saturating_add(child2_length) + .saturating_add(self.bar_thickness) } else { child1_length.max(child2_length) } } fn layout(&mut self, ctx: &mut LayoutCtx<'_>, _props: &PropertiesRef<'_>, size: Size) { - // TODO: Remove HACK: Until scale factor rework happens, just pretend it's always 1.0. - // https://github.com/linebender/xilem/issues/1264 - let scale = 1.0; - - let bar_thickness = self.bar_thickness.dp(scale); + let bar_thickness = self.bar_thickness.get(); let split_space = (size.get_coord(self.split_axis) - bar_thickness).max(0.); let cross_space = size.get_coord(self.split_axis.cross()); // Update our effective split point to respect our size - self.split_point_effective = self.calc_effective_split_point(split_space, scale); + self.split_point_effective = self.calc_effective_split_point(split_space); let child1_split_space = (split_space * self.split_point_effective).max(0.); let child2_split_space = (split_space - child1_split_space).max(0.); @@ -735,20 +699,16 @@ where _props: &PropertiesRef<'_>, painter: &mut Painter<'_>, ) { - // TODO: Remove HACK: Until scale factor rework happens, just pretend it's always 1.0. - // https://github.com/linebender/xilem/issues/1264 - let scale = 1.0; - // TODO - Paint differently if the bar is draggable and hovered. let bar_color = self.bar_color(ctx); if self.solid { - self.paint_solid_bar(ctx, painter, scale, bar_color); + self.paint_solid_bar(ctx, painter, bar_color); } else { - self.paint_stroked_bar(ctx, painter, scale, bar_color); + self.paint_stroked_bar(ctx, painter, bar_color); } if ctx.is_focus_target() && self.draggable && !ctx.is_disabled() { - self.paint_focus_bar(ctx, painter, scale); + self.paint_focus_bar(ctx, painter); } // TODO: Child painting should probably be clipped, in such a way that // one child won't overflow across the split bar onto the other child. @@ -756,13 +716,9 @@ where } fn get_cursor(&self, ctx: &QueryCtx<'_>, pos: Point) -> CursorIcon { - // TODO: Remove HACK: Until scale factor rework happens, just pretend it's always 1.0. - // https://github.com/linebender/xilem/issues/1264 - let scale = 1.0; - let length = ctx.content_box_size().get_coord(self.split_axis); let local_pos = ctx.to_local(pos).get_coord(self.split_axis); - let is_bar_area_hovered = self.bar_area_hit_test(length, local_pos, scale); + let is_bar_area_hovered = self.bar_area_hit_test(length, local_pos); if self.draggable && (ctx.is_active() || is_bar_area_hovered) { match self.split_axis { @@ -784,14 +740,10 @@ where _props: &PropertiesRef<'_>, node: &mut Node, ) { - // TODO: Remove HACK: Until scale factor rework happens, just pretend it's always 1.0. - // https://github.com/linebender/xilem/issues/1264 - let scale = 1.0; - let length = ctx.content_box_size().get_coord(self.split_axis); - let bar_thickness = self.bar_thickness.dp(scale); + let bar_thickness = self.bar_thickness.get(); let split_space = (length - bar_thickness).max(0.0); - let (min_limit, max_limit) = self.split_side_limits(split_space, scale); + let (min_limit, max_limit) = self.split_side_limits(split_space); let child1_len = split_space * self.split_point_effective; node.set_orientation(match self.split_axis { diff --git a/masonry/src/widgets/step_input.rs b/masonry/src/widgets/step_input.rs index 9cb9cb39d8..b03814044e 100644 --- a/masonry/src/widgets/step_input.rs +++ b/masonry/src/widgets/step_input.rs @@ -16,7 +16,7 @@ use crate::core::{ }; use crate::imaging::Painter; use crate::kurbo::{Affine, Axis, BezPath, Cap, Line, Point, Size, Stroke}; -use crate::layout::{LayoutSize, LenReq, Length, SizeDef}; +use crate::layout::{AsUnit, LayoutSize, LenReq, Length, SizeDef}; use crate::peniko::Gradient; use crate::properties::{BackwardColor, ContentColor, ForwardColor, HeatColor, StepInputStyle}; use crate::theme; @@ -1316,19 +1316,15 @@ impl Widget for StepInput { props: &PropertiesRef<'_>, axis: Axis, len_req: LenReq, - cross_length: Option, - ) -> f64 { - // TODO: Remove HACK: Until scale factor rework happens, just pretend it's always 1.0. - // https://github.com/linebender/xilem/issues/1264 - let scale = 1.0; - - let content_height = theme::BASIC_WIDGET_HEIGHT.dp(scale); + cross_length: Option, + ) -> Length { + let content_height = theme::BASIC_WIDGET_HEIGHT; let cache = ctx.property_cache(); let style = props.get::(cache); let (len_req, min_result) = match len_req { - LenReq::MinContent | LenReq::MaxContent => (len_req, 0.), + LenReq::MinContent | LenReq::MaxContent => (len_req, Length::ZERO), // We always want to use up all offered space but may need even more, // so we implement FitContent as space.max(MinContent). LenReq::FitContent(space) => (LenReq::MinContent, space), @@ -1339,7 +1335,7 @@ impl Widget for StepInput { StepInputStyle::Basic => { let vertical_space = cross_length.unwrap_or(content_height); let (btn_length, btn_edge_pad) = - Self::basic_button_length(vertical_space, None); + Self::basic_button_length(vertical_space.get(), None); match len_req { LenReq::MinContent => 2. * (btn_length + 2. * btn_edge_pad), LenReq::MaxContent => 2. * (btn_length * 3.), @@ -1349,14 +1345,15 @@ impl Widget for StepInput { StepInputStyle::Flow => { let vertical_space = cross_length.unwrap_or(content_height); let (arrow_width, _, arrow_edge_pad) = - Self::flow_button_length(vertical_space, None); + Self::flow_button_length(vertical_space.get(), None); match len_req { LenReq::MinContent => 2. * (2. * arrow_width + arrow_edge_pad), LenReq::MaxContent => 2. * (4. * arrow_width + arrow_edge_pad), LenReq::FitContent(_) => unreachable!(), } } - }, + } + .px(), Axis::Vertical => content_height, }; @@ -1369,19 +1366,18 @@ impl Widget for StepInput { let context_size = LayoutSize::maybe(axis.cross(), cross_length); let cross = axis.cross(); let label_cross_length = match cross { - Axis::Horizontal => { - cross_length.map(|cross_length| (cross_length - calc_button_length(cross)).max(0.)) - } + Axis::Horizontal => cross_length + .map(|cross_length| cross_length.saturating_sub(calc_button_length(cross))), Axis::Vertical => cross_length, }; let label_length = if let Some(label) = self.label.as_mut() { ctx.compute_length(label, auto_length, context_size, axis, label_cross_length) } else { - 0. + Length::ZERO }; let length = match axis { - Axis::Horizontal => label_length + button_length, + Axis::Horizontal => label_length.saturating_add(button_length), Axis::Vertical => label_length.max(button_length), }; diff --git a/masonry/src/widgets/svg.rs b/masonry/src/widgets/svg.rs index 92e7cfdd74..5b6449eacf 100644 --- a/masonry/src/widgets/svg.rs +++ b/masonry/src/widgets/svg.rs @@ -16,7 +16,7 @@ use crate::core::{ }; use crate::imaging::Painter; use crate::kurbo::{Affine, Axis, Size}; -use crate::layout::LenReq; +use crate::layout::{LenReq, Length}; use crate::peniko::{ImageAlphaType, ImageBrush, ImageData, ImageFormat}; use crate::properties::ObjectFit; @@ -108,19 +108,15 @@ impl Svg { impl Svg { /// Returns the preferred size of the SVG. /// - /// The returned size is in device pixels. + /// The returned size is in logical pixels. /// - /// This takes into account both [`SVG_SCALE`] and `scale`, and so the result - /// isn't just the SVG data size which would be const across scale factors. - /// - /// This method's result will be stable in relation to other widgets at any scale factor. - /// - /// Basically it provides logical pixels in device pixel space. - fn preferred_size(&self, scale: f64) -> Size { + /// This takes into account [`SVG_SCALE`], so rasterization can use a stable + /// preferred logical size. + fn preferred_size(&self) -> Size { let size = self.tree.size(); Size::new( - size.width() as f64 * scale / SVG_SCALE, - size.height() as f64 * scale / SVG_SCALE, + size.width() as f64 / SVG_SCALE, + size.height() as f64 / SVG_SCALE, ) } } @@ -153,15 +149,11 @@ impl Widget for Svg { props: &PropertiesRef<'_>, axis: Axis, len_req: LenReq, - cross_length: Option, - ) -> f64 { - // TODO: Remove HACK: Until scale factor rework happens, just pretend it's always 1.0. - // https://github.com/linebender/xilem/issues/1264 - let scale = 1.0; - + cross_length: Option, + ) -> Length { let cache = ctx.property_cache(); let object_fit = props.get::(cache); - let preferred_size = self.preferred_size(scale); + let preferred_size = self.preferred_size(); object_fit.measure(axis, len_req, cross_length, preferred_size) } diff --git a/masonry/src/widgets/switch.rs b/masonry/src/widgets/switch.rs index 7458a61633..6ccf0b55b3 100644 --- a/masonry/src/widgets/switch.rs +++ b/masonry/src/widgets/switch.rs @@ -15,7 +15,7 @@ use crate::core::{ }; use crate::imaging::Painter; use crate::kurbo::{Axis, Circle, Join, Point, Rect, Size, Stroke}; -use crate::layout::LenReq; +use crate::layout::{AsUnit, LenReq, Length}; use crate::properties::{ Background, BorderColor, BorderWidth, CornerRadius, ThumbColor, ThumbRadius, TrackThickness, }; @@ -80,10 +80,7 @@ impl Switch { /// Calculates the track dimensions based on properties. /// /// Returns `(track_width, track_height)`. - fn track_dimensions(track_thickness: f64, thumb_radius: f64, scale: f64) -> (f64, f64) { - let track_thickness = track_thickness * scale; - let thumb_radius = thumb_radius * scale; - + fn track_dimensions(track_thickness: f64, thumb_radius: f64) -> (f64, f64) { // The track height is the larger of track_thickness or thumb diameter let track_height = track_thickness.max(thumb_radius * 2.0); // The track width is approximately 2x the height (pill shape) @@ -193,22 +190,18 @@ impl Widget for Switch { props: &PropertiesRef<'_>, axis: Axis, _len_req: LenReq, - _cross_length: Option, - ) -> f64 { - // TODO: Remove HACK: Until scale factor rework happens, just pretend it's always 1.0. - // https://github.com/linebender/xilem/issues/1264 - let scale = 1.0; - + _cross_length: Option, + ) -> Length { let cache = ctx.property_cache(); let track_thickness = props.get::(cache).0; let thumb_radius = props.get::(cache).0; - let (track_width, track_height) = - Self::track_dimensions(track_thickness, thumb_radius, scale); + let (track_width, track_height) = Self::track_dimensions(track_thickness, thumb_radius); match axis { Axis::Horizontal => track_width, Axis::Vertical => track_height, } + .px() } fn layout(&mut self, _ctx: &mut LayoutCtx<'_>, _props: &PropertiesRef<'_>, _size: Size) {} @@ -228,10 +221,6 @@ impl Widget for Switch { props: &PropertiesRef<'_>, painter: &mut Painter<'_>, ) { - // TODO: Remove HACK: Until scale factor rework happens, just pretend it's always 1.0. - // https://github.com/linebender/xilem/issues/1264 - let scale = 1.0; - let is_disabled = ctx.is_disabled(); let size = ctx.border_box_size(); @@ -241,10 +230,10 @@ impl Widget for Switch { let track_thickness_val = props.get::(cache).0; let thumb_radius_val = props.get::(cache).0; let (track_width, track_height) = - Self::track_dimensions(track_thickness_val, thumb_radius_val, scale); - let thumb_radius = thumb_radius_val * scale; - let border_width = props.get::(cache).width * scale; - let corner_radius = props.get::(cache).radius * scale; + Self::track_dimensions(track_thickness_val, thumb_radius_val); + let thumb_radius = thumb_radius_val; + let border_width = props.get::(cache).width; + let corner_radius = props.get::(cache).radius; let thumb_color = props.get::(cache).0; // Center the track within the available space diff --git a/masonry/src/widgets/text_area.rs b/masonry/src/widgets/text_area.rs index 5c870daa90..9876ff05f4 100644 --- a/masonry/src/widgets/text_area.rs +++ b/masonry/src/widgets/text_area.rs @@ -16,7 +16,7 @@ use crate::core::{ }; use crate::imaging::Painter; use crate::kurbo::{Affine, Axis, Point, Rect, Size}; -use crate::layout::LenReq; +use crate::layout::{AsUnit, LenReq, Length}; use crate::parley::PlainEditor; use crate::parley::editing::{Generation, SplitString}; use crate::properties::{CaretColor, ContentColor, SelectionColor}; @@ -876,8 +876,8 @@ impl Widget for TextArea { _props: &PropertiesRef<'_>, axis: Axis, len_req: LenReq, - cross_length: Option, - ) -> f64 { + cross_length: Option, + ) -> Length { // Currently we only support the common horizontal-tb writing mode, // so we hardcode the assumption that inline axis is horizontal. let inline = Axis::Horizontal; @@ -894,7 +894,7 @@ impl Widget for TextArea { // This is a common optimization also present on the web. match len_req { // Zero space will get us the length of longest unbreakable word - LenReq::MinContent => Some(0.), + LenReq::MinContent => Some(Length::ZERO), // Unbounded space will get us the length of the unwrapped string LenReq::MaxContent => None, // Attempt to wrap according to the parent's request @@ -905,7 +905,7 @@ impl Widget for TextArea { // If there is no explicit cross_length present, we fall back to inline defaults. match len_req { // Fallback is inline axis MinContent - LenReq::MinContent => cross_length.or(Some(0.)), + LenReq::MinContent => cross_length.or(Some(Length::ZERO)), // Fallback is inline axis MaxContent, even for FitContent, because // as we don't have the inline space bound we'll consider it unbounded. LenReq::MaxContent | LenReq::FitContent(_) => cross_length, @@ -915,7 +915,7 @@ impl Widget for TextArea { // If we're never wrapping, then there's no max advance. false => None, } - .map(|v| v as f32); + .map(|v| v.get() as f32); let mut reset_max_advance = None; if self.last_max_advance != max_advance { @@ -944,7 +944,7 @@ impl Widget for TextArea { self.editor.refresh_layout(fctx, lctx); } - length + length.px() } fn layout(&mut self, ctx: &mut LayoutCtx<'_>, _props: &PropertiesRef<'_>, size: Size) { diff --git a/masonry/src/widgets/text_input.rs b/masonry/src/widgets/text_input.rs index 1fd6ced2ca..09287af15a 100644 --- a/masonry/src/widgets/text_input.rs +++ b/masonry/src/widgets/text_input.rs @@ -15,7 +15,7 @@ use crate::core::{ }; use crate::imaging::Painter; use crate::kurbo::{Axis, Point, Size}; -use crate::layout::{LayoutSize, LenReq}; +use crate::layout::{LayoutSize, LenReq, Length}; use crate::properties::{CaretColor, ContentColor, LineBreaking, PlaceholderColor, SelectionColor}; use crate::widgets::{Label, TextArea}; @@ -268,8 +268,8 @@ impl Widget for TextInput { _props: &PropertiesRef<'_>, axis: Axis, len_req: LenReq, - cross_length: Option, - ) -> f64 { + cross_length: Option, + ) -> Length { match len_req { LenReq::MaxContent | LenReq::MinContent => { let auto_length = len_req.into(); diff --git a/masonry/src/widgets/variable_label.rs b/masonry/src/widgets/variable_label.rs index 610f1157da..b4153ae869 100644 --- a/masonry/src/widgets/variable_label.rs +++ b/masonry/src/widgets/variable_label.rs @@ -13,7 +13,7 @@ use crate::core::{ }; use crate::imaging::Painter; use crate::kurbo::{Axis, Point, Size}; -use crate::layout::LenReq; +use crate::layout::{LenReq, Length}; use crate::parley::style::FontWeight; use crate::widgets::Label; @@ -231,8 +231,8 @@ impl Widget for VariableLabel { _props: &PropertiesRef<'_>, axis: Axis, _len_req: LenReq, - cross_length: Option, - ) -> f64 { + cross_length: Option, + ) -> Length { ctx.redirect_measurement(&mut self.label, axis, cross_length) } diff --git a/masonry/src/widgets/virtual_scroll.rs b/masonry/src/widgets/virtual_scroll.rs index 9d15376a79..34eba2ec17 100644 --- a/masonry/src/widgets/virtual_scroll.rs +++ b/masonry/src/widgets/virtual_scroll.rs @@ -15,7 +15,7 @@ use crate::core::{ use crate::dpi::PhysicalPosition; use crate::imaging::Painter; use crate::kurbo::{Axis, Point, Size, Vec2}; -use crate::layout::{LenDef, LenReq, SizeDef}; +use crate::layout::{LenDef, LenReq, Length, SizeDef}; use crate::util::debug_panic; /// The action type sent by the [`VirtualScroll`] widget. @@ -537,8 +537,6 @@ impl Widget for VirtualScroll { match event { PointerEvent::Scroll(PointerScrollEvent { delta, .. }) => { let size = ctx.content_box_size(); - // TODO - Remove reference to scale factor. - // See https://github.com/linebender/xilem/issues/1264 let scale_factor = ctx.get_scale_factor(); let line_px = PhysicalPosition { x: 120.0 * scale_factor, @@ -655,8 +653,8 @@ impl Widget for VirtualScroll { _props: &PropertiesRef<'_>, _axis: Axis, len_req: LenReq, - _cross_length: Option, - ) -> f64 { + _cross_length: Option, + ) -> Length { // Our preferred size is a const square in logical pixels. // // It is not clear that a data-derived result would be better. @@ -672,14 +670,10 @@ impl Widget for VirtualScroll { // Still, we would run into complexities with ensuring they are loaded in time for measure. // // So, for now, we just use a simple O(1) default. - const DEFAULT_LENGTH: f64 = 100.; - - // TODO: Remove HACK: Until scale factor rework happens, just pretend it's always 1.0. - // https://github.com/linebender/xilem/issues/1264 - let scale = 1.0; + const DEFAULT_LENGTH: Length = Length::const_px(100.); match len_req { - LenReq::MinContent | LenReq::MaxContent => DEFAULT_LENGTH * scale, + LenReq::MinContent | LenReq::MaxContent => DEFAULT_LENGTH, LenReq::FitContent(space) => space, } } diff --git a/masonry/src/widgets/zstack.rs b/masonry/src/widgets/zstack.rs index 6c04f3bf72..512d26935e 100644 --- a/masonry/src/widgets/zstack.rs +++ b/masonry/src/widgets/zstack.rs @@ -11,7 +11,7 @@ use crate::core::{ }; use crate::imaging::Painter; use crate::kurbo::{Axis, Rect, Size}; -use crate::layout::{LayoutSize, LenReq, SizeDef, UnitPoint}; +use crate::layout::{LayoutSize, LenReq, Length, SizeDef, UnitPoint}; struct Child { widget: WidgetPod, @@ -218,12 +218,12 @@ impl Widget for ZStack { _props: &PropertiesRef<'_>, axis: Axis, len_req: LenReq, - cross_length: Option, - ) -> f64 { + cross_length: Option, + ) -> Length { let auto_length = len_req.into(); let context_size = LayoutSize::maybe(axis.cross(), cross_length); - let mut length: f64 = 0.; + let mut length = Length::ZERO; for child in &mut self.children { let child_length = ctx.compute_length( &mut child.widget, diff --git a/masonry_core/src/app/layer_stack.rs b/masonry_core/src/app/layer_stack.rs index de72931804..5264968093 100644 --- a/masonry_core/src/app/layer_stack.rs +++ b/masonry_core/src/app/layer_stack.rs @@ -12,7 +12,7 @@ use crate::core::{ RegisterCtx, UpdateCtx, Widget, WidgetId, WidgetMut, WidgetPod, }; use crate::imaging::Painter; -use crate::layout::{LenReq, SizeDef}; +use crate::layout::{LenReq, Length, SizeDef}; /// A widget representing the top-level stack of visible layers owned by [`RenderRoot`](crate::app::RenderRoot). /// @@ -180,12 +180,12 @@ impl Widget for LayerStack { _props: &PropertiesRef<'_>, axis: Axis, _len_req: LenReq, - cross_length: Option, - ) -> f64 { + cross_length: Option, + ) -> Length { // First child is the base layer, for which we do measure passthrough. let Some(base_layer) = self.layers.first_mut() else { debug_panic!("Missing first layer"); - return 0.; + return Length::ZERO; }; // Let the base layer handle the response ctx.redirect_measurement(&mut base_layer.root, axis, cross_length) diff --git a/masonry_core/src/core/contexts.rs b/masonry_core/src/core/contexts.rs index c687c657a8..9fa1b3ba7d 100644 --- a/masonry_core/src/core/contexts.rs +++ b/masonry_core/src/core/contexts.rs @@ -26,7 +26,7 @@ use crate::core::{ WidgetState, }; use crate::kurbo::{Affine, Axis, Insets, Point, Rect, Size, Vec2}; -use crate::layout::{LayoutSize, LenDef, SizeDef}; +use crate::layout::{LayoutSize, LenDef, Length, SizeDef}; use crate::passes::layout::{place_widget, resolve_length, resolve_size, run_layout_on}; use crate::peniko::Color; use crate::util::{ParentLinkedList, get_debug_color}; @@ -504,8 +504,6 @@ impl EventCtx<'_> { /// Converts the given position from the window's coordinate space /// to this widget's content-box coordinate space. pub fn local_position(&self, p: PhysicalPosition) -> Point { - // TODO: Remove this .to_logical() conversion when scale refactor work happens. - // https://github.com/linebender/xilem/issues/1264 let LogicalPosition { x, y } = p.to_logical(self.global_state.scale_factor); self.to_local(Point { x, y }) } @@ -607,9 +605,7 @@ impl AccessCtx<'_> { // --- MARK: COMPUTE LENGTH impl_context_method!(MeasureCtx<'_>, LayoutCtx<'_>, { - /// Computes the `child`'s preferred border-box length on the given `axis`. - /// - /// The returned length will be finite, non-negative, and in device pixels. + /// Computes the `child`'s preferred border-box [`Length`] on the given `axis`. /// /// Container widgets usually call this method as part of their [`measure`] logic, /// to help them calculate their own length on the given `axis`. They call it as part @@ -624,10 +620,8 @@ impl_context_method!(MeasureCtx<'_>, LayoutCtx<'_>, { /// to ask the child to fit inside the available space. Sometimes a different fallback /// makes more sense, e.g. `Grid` uses [`LenDef::Fixed`] to fall back to the exact /// allocated child area size. - /// `auto_length` values must be finite, non-negative, and in device pixels. - /// An invalid `auto_length` will fall back to [`LenDef::MaxContent`]. /// - /// `context_size` is the size, in device pixels, that is used to resolve relative sizes. + /// `context_size` is the size that is used to resolve relative sizes. /// For example [`Ratio(0.5)`] will result in half the context size. /// This is usually the container widget's content-box size, i.e. excluding borders and padding. /// Examples of exceptions include `Grid` which will provide the child's area size, @@ -636,14 +630,6 @@ impl_context_method!(MeasureCtx<'_>, LayoutCtx<'_>, { /// /// `cross_length` is the length of the cross axis and is critical information for certain /// widgets, e.g. for text max advance or to keep an aspect ratio. - /// If present, `cross_length` must be finite, non-negative, and in device pixels. - /// An invalid `cross_length` will fall back to `None`. - /// - /// # Panics - /// - /// Panics if `auto_length` is non-finite or negative and debug assertions are enabled. - /// - /// Panics if `cross_length` is non-finite or negative and debug assertions are enabled. /// /// [`measure`]: Widget::measure /// [`layout`]: Widget::layout @@ -656,8 +642,8 @@ impl_context_method!(MeasureCtx<'_>, LayoutCtx<'_>, { auto_length: LenDef, context_size: LayoutSize, axis: Axis, - cross_length: Option, - ) -> f64 { + cross_length: Option, + ) -> Length { let id = child.id(); let node = self.children.item_mut(id).unwrap(); resolve_length( @@ -759,13 +745,6 @@ impl MeasureCtx<'_> { /// This is because the redirection introduces new inputs in the form of [`auto_length`] /// and [`context_size`] that are not part of the cache key. /// - /// If present, `cross_length` must be finite, non-negative, and in device pixels. - /// An invalid `cross_length` will fall back to `None`. - /// - /// # Panics - /// - /// Panics if `cross_length` is non-finite or negative and debug assertions are enabled. - /// /// [`measure`]: Widget::measure /// [`compute_length`]: Self::compute_length /// [`auto_length`]: Self::auto_length @@ -774,8 +753,8 @@ impl MeasureCtx<'_> { &mut self, child: &mut WidgetPod, axis: Axis, - cross_length: Option, - ) -> f64 { + cross_length: Option, + ) -> Length { // We're adding two new variables, auto_length and context_size, into the measure function, // which are not part of the cache key. Hence, we need to not cache. self.cache_result = false; @@ -819,7 +798,7 @@ impl LayoutCtx<'_> { /// Computes the `child`'s preferred border-box size. /// - /// The returned size will be finite, non-negative, and in device pixels. + /// The returned size will be finite, non-negative, and in logical pixels. /// /// Container widgets usually call this method as part of their [`layout`] logic, but /// ultimately they can disregard the result and pass a different size to [`run_layout`]. @@ -830,7 +809,7 @@ impl LayoutCtx<'_> { /// available space. However sometimes a different fallback makes more sense, e.g. /// `Grid` uses [`SizeDef::fixed`] to fall back to the exact allocated child area size. /// - /// `context_size` is the size, in device pixels, that is used to resolve relative sizes. + /// `context_size` is the size that is used to resolve relative sizes. /// For example [`Ratio(0.5)`] will result in half the context size. /// This is usually the container widget's content-box size, i.e. excluding borders and padding. /// Examples of exceptions include `Grid` which will provide the child's area size, @@ -869,7 +848,7 @@ impl LayoutCtx<'_> { /// If the chosen border-box `size` is smaller than what is required to fit the child's /// borders and padding, then the `size` will be expanded to meet those constraints. /// - /// The provided `size` must be finite, non-negative, and in device pixels. + /// The provided `size` must be finite, non-negative, and in logical pixels. /// Non-finite or negative size will fall back to zero with a logged warning. /// /// # Panics @@ -1364,8 +1343,6 @@ impl_context_method!( ); impl_context_method!(AccessCtx<'_>, EventCtx<'_>, PaintCtx<'_>, { - // TODO - Once Masonry uses physical coordinates, add this method everywhere. - // See https://github.com/linebender/xilem/issues/1264 /// Returns DPI scaling factor. /// /// This is not required for most widgets, and should be used only for precise diff --git a/masonry_core/src/core/widget.rs b/masonry_core/src/core/widget.rs index 1ffae7da26..6bfa04578f 100644 --- a/masonry_core/src/core/widget.rs +++ b/masonry_core/src/core/widget.rs @@ -18,7 +18,7 @@ use crate::core::{ QueryCtx, RegisterCtx, TextEvent, Update, UpdateCtx, WidgetMut, WidgetRef, pre_paint, }; use crate::imaging::Painter; -use crate::layout::LenReq; +use crate::layout::{LenReq, Length}; /// A unique identifier for a single [`Widget`]. /// @@ -246,10 +246,7 @@ pub trait Widget: AsDynWidget + Any { /// Handles a property being added, changed, or removed. fn property_changed(&mut self, ctx: &mut UpdateCtx<'_>, property_type: TypeId) {} - /// Computes the content-box length that the widget wants to be on the given `axis`. - /// - /// The returned length must be finite, non-negative, and in device pixels. - /// If an invalid length is returned, Masonry will treat it as zero. + /// Computes the content-box [`Length`] that the widget wants to be on the given `axis`. /// /// The goal of this method is for a parent to learn how its children want to be sized. /// All the inputs are hints towards what the parent is planning for its child. @@ -271,7 +268,7 @@ pub trait Widget: AsDynWidget + Any { /// then you should use [`redirect_measurement`] to have the child answer for you. /// /// The `cross_length`, if present, says that the cross-axis of the given `axis` of this - /// measured widget's content-box can be presumed to be `cross_length` long, in device pixels. + /// measured widget's content-box can be presumed to be `cross_length` long. /// This information is often very useful for measuring `axis` and should be used. /// However, ultimately it may end up not materializing. That is to say, it is /// a valid assumption for the duration of this `measure` call but there is @@ -310,15 +307,6 @@ pub trait Widget: AsDynWidget + Any { /// or you need to request layout for reasons that don't affect the result of this computation, /// then your widget should also have its own inner cache layer to avoid redoing the same work. /// - /// As for the inputs provided to `measure`, `len_req` must be [sanitized] and - /// `cross_length`, if present, must be [sanitized] and in device pixels. - /// When Masonry calls `measure` during the layout pass, it guarantees that for these inputs. - /// - /// # Panics - /// - /// Masonry will panic if `measure` returns a non-finite or negative value - /// and debug assertions are enabled. - /// /// [`compute_length`]: MeasureCtx::compute_length /// [`redirect_measurement`]: MeasureCtx::redirect_measurement /// [sanitized]: crate::util::Sanitize @@ -332,8 +320,8 @@ pub trait Widget: AsDynWidget + Any { props: &PropertiesRef<'_>, axis: Axis, len_req: LenReq, - cross_length: Option, - ) -> f64; + cross_length: Option, + ) -> Length; /// Lays out the widget with the given content-box `size`. /// @@ -361,7 +349,7 @@ pub trait Widget: AsDynWidget + Any { /// Container widgets must not add or remove children during layout. /// Doing so is a logic error and may lead to panics. /// - /// The `size` given to this method must be finite, non-negative, and in device pixels. + /// The `size` given to this method must be finite, non-negative, and in logical pixels. /// When Masonry calls `layout` during the layout pass, it will guarantee that for `size`. /// /// [`compute_size`]: LayoutCtx::compute_size diff --git a/masonry_core/src/layout/dim.rs b/masonry_core/src/layout/dim.rs index c0c9006e7e..913103e17d 100644 --- a/masonry_core/src/layout/dim.rs +++ b/masonry_core/src/layout/dim.rs @@ -13,9 +13,7 @@ pub enum Dim { /// [`measure`]: crate::core::Widget::measure #[default] Auto, - /// Specific fixed length. - /// - /// The value is in logical pixels, represented by a [`Length`]. + /// Specific fixed [`Length`]. Fixed(Length), /// Multiple of context length. /// @@ -72,11 +70,17 @@ impl From for Dim { impl Dim { /// Resolves, if possible, into a [`LenDef`]. /// - /// If `context_length` is provided, it must be in device pixels. - pub fn resolve(&self, scale: f64, context_length: Option) -> Option { + /// # Panics + /// + /// Panics if ratio resolves to a non-finite or negative value and debug assertions are enabled. + /// This can happen if the numbers are huge, e.g. a logical size of `f64::MAX` scaled by `1.5`. + pub fn resolve(&self, context_length: Option) -> Option { match self { - Self::Fixed(length) => Some(LenDef::Fixed(length.dp(scale))), - Self::Ratio(mul) => context_length.map(|cl| LenDef::Fixed(cl * *mul)), + Self::Fixed(length) => Some(LenDef::Fixed(*length)), + Self::Ratio(mul) => context_length.map(|cl| match Length::try_px(cl.get() * *mul) { + Some(length) => LenDef::Fixed(length), + None => LenDef::MaxContent, + }), Self::Stretch => context_length.map(LenDef::Fixed), Self::MinContent => Some(LenDef::MinContent), Self::MaxContent => Some(LenDef::MaxContent), diff --git a/masonry_core/src/layout/layout_size.rs b/masonry_core/src/layout/layout_size.rs index 9ba9b9ae4d..533eebf6f6 100644 --- a/masonry_core/src/layout/layout_size.rs +++ b/masonry_core/src/layout/layout_size.rs @@ -3,24 +3,24 @@ use kurbo::{Axis, Size}; -use crate::util::Sanitize; +use crate::layout::Length; /// Layout width and height. /// /// A length may be missing if it has not been computed yet, i.e. it depends on the child. -/// -/// The lengths, if present, are always finite, non-negative, and in device pixels. #[derive(Copy, Clone, Debug, Default, PartialEq)] pub struct LayoutSize { - width: Option, - height: Option, + width: Option, + height: Option, } impl From for LayoutSize { + #[track_caller] fn from(size: Size) -> Self { - let width = Some(size.width).sanitize("LayoutSize width"); - let height = Some(size.height).sanitize("LayoutSize height"); - Self { width, height } + Self { + width: Some(Length::px(size.width)), + height: Some(Length::px(size.height)), + } } } @@ -32,52 +32,29 @@ impl LayoutSize { }; /// Creates a new [`LayoutSize`] with the given lengths. - /// - /// The lengths must be finite, non-negative, and in device pixels. - /// Invalid lengths will result in `None`. - /// - /// # Panics - /// - /// Panics if `width` or `height` are non-finite or negative - /// and debug assertions are enabled. - pub fn new(width: f64, height: f64) -> Self { - let width = Some(width).sanitize("LayoutSize width"); - let height = Some(height).sanitize("LayoutSize height"); - Self { width, height } + pub fn new(width: Length, height: Length) -> Self { + Self { + width: Some(width), + height: Some(height), + } } /// Creates a new [`LayoutSize`] with only the given `axis` set to `length`. - /// - /// The length must be finite, non-negative, and in device pixels. - /// An invalid length will result in `None`. - /// - /// # Panics - /// - /// Panics if `length` is non-finite or negative and debug assertions are enabled. - pub fn one(axis: Axis, length: f64) -> Self { - let length = Some(length).sanitize("LayoutSize length"); + pub fn one(axis: Axis, length: Length) -> Self { match axis { Axis::Horizontal => Self { - width: length, + width: Some(length), height: None, }, Axis::Vertical => Self { width: None, - height: length, + height: Some(length), }, } } /// Creates a new [`LayoutSize`] with only the given `axis` set to `length`. - /// - /// The length, if present, must be finite, non-negative, and in device pixels. - /// An invalid length will result in `None`. - /// - /// # Panics - /// - /// Panics if `length` is present but non-finite or negative - /// and debug assertions are enabled. - pub fn maybe(axis: Axis, length: Option) -> Self { + pub fn maybe(axis: Axis, length: Option) -> Self { let Some(length) = length else { return Self { width: None, @@ -88,11 +65,7 @@ impl LayoutSize { } /// Returns the [`Length`] of the provided `axis`. - /// - /// The returned value will be finite, non-negative, and in device pixels. - /// - /// [`Length`]: crate::layout::Length - pub const fn length(&self, axis: Axis) -> Option { + pub const fn length(&self, axis: Axis) -> Option { match axis { Axis::Horizontal => self.width, Axis::Vertical => self.height, diff --git a/masonry_core/src/layout/len_def.rs b/masonry_core/src/layout/len_def.rs index 4672cc6368..3a98c70471 100644 --- a/masonry_core/src/layout/len_def.rs +++ b/masonry_core/src/layout/len_def.rs @@ -1,8 +1,7 @@ // Copyright 2025 the Xilem Authors // SPDX-License-Identifier: Apache-2.0 -use crate::layout::LenReq; -use crate::util::Sanitize; +use crate::layout::{LenReq, Length}; /// Widget border-box length definition. /// @@ -11,16 +10,12 @@ use crate::util::Sanitize; /// /// This is how a parent specifies [`Dim::Auto`] behavior for its children. /// -/// All the values must be finite, non-negative, and in device pixels. -/// /// [`Dim`]: crate::layout::Dim /// [`Dim::Auto`]: crate::layout::Dim::Auto #[derive(Copy, Clone, Debug, PartialEq)] pub enum LenDef { - /// Specific fixed border-box length. - /// - /// The value must be finite, non-negative, and in device pixels. - Fixed(f64), + /// Specific fixed border-box [`Length`]. + Fixed(Length), /// Minimum preferred border-box length. /// /// This will result in a [`measure`] invocation, which can be slow. @@ -35,12 +30,10 @@ pub enum LenDef { MaxContent, /// The border-box should fit in the specified available space. /// - /// The value must be finite, non-negative, and in device pixels. - /// /// This will result in a [`measure`] invocation, which can be slow. /// /// [`measure`]: crate::core::Widget::measure - FitContent(f64), + FitContent(Length), } impl From for LenDef { @@ -55,14 +48,7 @@ impl From for LenDef { impl LenDef { /// Returns the specific fixed border-box length if it is present. - /// - /// The length will be in device pixels. - /// - /// Whether the length can be non-finite or negative depends on whether - /// this [`LenDef`] has been [sanitized]. - /// - /// [sanitized]: LenDef::sanitize - pub fn fixed(&self) -> Option { + pub fn fixed(&self) -> Option { match self { Self::Fixed(val) => Some(*val), _ => None, @@ -74,62 +60,15 @@ impl LenDef { /// [`Fixed`] and [`FitContent`] will have their value reduced by `delta`, but clamped to zero. /// [`MinContent`] and [`MaxContent`] are returned as-is. /// - /// The provided `delta` must be in device pixels. - /// /// [`Fixed`]: Self::Fixed /// [`FitContent`]: Self::FitContent /// [`MinContent`]: Self::MinContent /// [`MaxContent`]: Self::MaxContent - pub fn reduce(self, delta: f64) -> Self { - match self { - Self::Fixed(val) => Self::Fixed((val - delta).max(0.)), - Self::MinContent | Self::MaxContent => self, - Self::FitContent(space) => Self::FitContent((space - delta).max(0.)), - } - } -} - -impl Sanitize for LenDef { - /// Returns a valid instance of [`LenDef`]. - /// - /// It will return [`MaxContent`] if the [`Fixed`] or [`FitContent`] - /// values are non-finite or negative. - /// - /// This method is called by Masonry during the layout pass, - /// when a widget's length is being resolved. - /// - /// # Panics - /// - /// Panics if the [`Fixed`] or [`FitContent`] values are non-finite or negative - /// and debug assertions are enabled. - /// - /// [`Fixed`]: Self::Fixed - /// [`FitContent`]: Self::FitContent - /// [`MaxContent`]: Self::MaxContent - #[track_caller] - fn sanitize(self, name: &str) -> Self { + pub fn reduce(self, delta: Length) -> Self { match self { - Self::Fixed(val) => { - if val.is_finite() && val >= 0. { - self - } else { - debug_panic!( - "{name} `Fixed` value must be finite and non-negative. Received: {val}" - ); - Self::MaxContent - } - } + Self::Fixed(val) => Self::Fixed(val.saturating_sub(delta)), Self::MinContent | Self::MaxContent => self, - Self::FitContent(space) => { - if space.is_finite() && space >= 0. { - self - } else { - debug_panic!( - "{name} `FitContent` value must be finite and non-negative. Received: {space}" - ); - Self::MaxContent - } - } + Self::FitContent(space) => Self::FitContent(space.saturating_sub(delta)), } } } diff --git a/masonry_core/src/layout/len_req.rs b/masonry_core/src/layout/len_req.rs index 7e50438873..06e4912f56 100644 --- a/masonry_core/src/layout/len_req.rs +++ b/masonry_core/src/layout/len_req.rs @@ -1,7 +1,7 @@ // Copyright 2025 the Xilem Authors // SPDX-License-Identifier: Apache-2.0 -use crate::util::Sanitize; +use crate::layout::Length; /// Widget length measurement algorithm request. /// @@ -13,9 +13,7 @@ pub enum LenReq { /// The widget should measure its maximum preferred length. MaxContent, /// The widget should attempt to fit into the specified available space. - /// - /// The space value must be finite, non-negative, and in device pixels. - FitContent(f64), + FitContent(Length), } impl LenReq { @@ -24,44 +22,13 @@ impl LenReq { /// [`FitContent`] will have its value reduced by `delta`, but clamped to zero. /// [`MinContent`] and [`MaxContent`] are returned as-is. /// - /// The provided `delta` must be in device pixels. - /// /// [`FitContent`]: Self::FitContent /// [`MinContent`]: Self::MinContent /// [`MaxContent`]: Self::MaxContent - pub fn reduce(self, delta: f64) -> Self { - match self { - Self::MinContent | Self::MaxContent => self, - Self::FitContent(space) => Self::FitContent((space - delta).max(0.)), - } - } -} - -impl Sanitize for LenReq { - /// Returns a valid instance of [`LenReq`]. - /// - /// It will return [`MaxContent`] if the [`FitContent`] value is non-finite or negative. - /// - /// # Panics - /// - /// Panics if [`FitContent`] value is non-finite or negative and debug assertions are enabled. - /// - /// [`FitContent`]: Self::FitContent - /// [`MaxContent`]: Self::MaxContent - #[track_caller] - fn sanitize(self, name: &str) -> Self { + pub fn reduce(self, delta: Length) -> Self { match self { Self::MinContent | Self::MaxContent => self, - Self::FitContent(space) => { - if space.is_finite() && space >= 0. { - self - } else { - debug_panic!( - "{name} `space` must be finite and non-negative. Received: {space}" - ); - Self::MaxContent - } - } + Self::FitContent(space) => Self::FitContent(space.saturating_sub(delta)), } } } diff --git a/masonry_core/src/layout/length.rs b/masonry_core/src/layout/length.rs index 0f7e83fd67..c50e12e8f8 100644 --- a/masonry_core/src/layout/length.rs +++ b/masonry_core/src/layout/length.rs @@ -4,7 +4,7 @@ /// A value representing a width, height, or similar distance value. /// /// It is always finite and non-negative. -#[derive(Clone, Copy, PartialEq)] +#[derive(Default, Clone, Copy, PartialEq)] pub struct Length { value: f64, } @@ -47,6 +47,16 @@ impl Length { Self { value } } + /// Creates a length, in logical pixels. + /// + /// Returns `None` if the provided `value` is non-finite or negative. + pub const fn try_px(value: f64) -> Option { + if value < 0. || !value.is_finite() { + return None; + } + Some(Self { value }) + } + /// Creates a length, in logical pixels. /// /// Can be called from const contexts. @@ -91,4 +101,37 @@ impl Length { other } } + + /// Returns `max` if `self` is greater than `max`, and `min` if `self` is less than `min`. + /// Otherwise this returns `self`. + /// + /// # Panics + /// + /// Panics if `min` is greater than `max`. + pub const fn clamp(self, min: Self, max: Self) -> Self { + Self { + value: self.value.clamp(min.value, max.value), + } + } + + /// Adds the `other` value but the result doesn't go above the maximum value. + pub const fn saturating_add(self, other: Self) -> Self { + Self { + value: (self.value + other.value).min(f64::MAX), + } + } + + /// Subtracts the `other` value but the result doesn't go below zero. + pub const fn saturating_sub(self, other: Self) -> Self { + Self { + value: (self.value - other.value).max(0.), + } + } + + /// Multiplies by the `other` value but the result doesn't go above the maximum value. + pub const fn saturating_mul(self, other: Self) -> Self { + Self { + value: (self.value * other.value).min(f64::MAX), + } + } } diff --git a/masonry_core/src/layout/measurement_cache.rs b/masonry_core/src/layout/measurement_cache.rs index 32c5884740..134651e08f 100644 --- a/masonry_core/src/layout/measurement_cache.rs +++ b/masonry_core/src/layout/measurement_cache.rs @@ -2,7 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 use crate::kurbo::Axis; -use crate::layout::LenReq; +use crate::layout::{LenReq, Length}; /// At the time of choosing this capacity, /// 10 * 48 bytes = 480 bytes for the whole buffer. @@ -22,7 +22,7 @@ const CAPACITY: usize = 10; /// but even if there are that is fine, because we clear the cache regularly. #[derive(Clone, Debug)] pub(crate) struct MeasurementCache { - entries: Vec<(MeasurementInputs, f64)>, + entries: Vec<(MeasurementInputs, Length)>, } /// All the inputs that change [`measure`] output. @@ -35,12 +35,12 @@ pub(crate) struct MeasurementCache { pub(crate) struct MeasurementInputs { axis: Axis, len_req: LenReq, - cross_length: Option, + cross_length: Option, } impl MeasurementInputs { /// Creates a new [`MeasurementInputs`] with the provided data. - pub(crate) const fn new(axis: Axis, len_req: LenReq, cross_length: Option) -> Self { + pub(crate) const fn new(axis: Axis, len_req: LenReq, cross_length: Option) -> Self { Self { axis, len_req, @@ -58,7 +58,7 @@ impl MeasurementCache { } /// Inserts the `result` for the given `inputs` into the cache. - pub(crate) fn insert(&mut self, inputs: MeasurementInputs, result: f64) { + pub(crate) fn insert(&mut self, inputs: MeasurementInputs, result: Length) { if let Some(index) = self.entries.iter().position(|e| e.0 == inputs) { if index > 0 { // Keep recently referenced entries in front @@ -78,7 +78,7 @@ impl MeasurementCache { } /// Gets the cached result for the given `inputs`. - pub(crate) fn get(&mut self, inputs: &MeasurementInputs) -> Option { + pub(crate) fn get(&mut self, inputs: &MeasurementInputs) -> Option { let index = self.entries.iter().position(|e| &e.0 == inputs)?; if index > 0 { // Keep recently referenced entries in front diff --git a/masonry_core/src/layout/size_def.rs b/masonry_core/src/layout/size_def.rs index c0b6410718..3169f04f58 100644 --- a/masonry_core/src/layout/size_def.rs +++ b/masonry_core/src/layout/size_def.rs @@ -3,15 +3,13 @@ use kurbo::{Axis, Size}; -use crate::layout::LenDef; +use crate::layout::{LenDef, Length}; use crate::util::Sanitize; /// Widget border-box size definition. /// /// This is how a parent specifies [`Dim::Auto`] behavior for its children. /// -/// The inner [`LenDef`] values will already be [sanitized] and are safe to read. -/// /// [`Dim::Auto`]: crate::layout::Dim::Auto /// [sanitized]: Sanitize #[derive(Copy, Clone, Debug, PartialEq)] @@ -38,17 +36,7 @@ impl SizeDef { }; /// Creates a new [`SizeDef`] with the given `width` and `height`. - /// - /// All the [`LenDef`] values must be finite, non-negative, and in device pixels. - /// Invalid [`LenDef`] values will fall back to [`LenDef::MaxContent`]. - /// - /// # Panics - /// - /// Panics if `width` or `height` contain non-finite or negative values - /// and debug assertions are enabled. pub fn new(width: LenDef, height: LenDef) -> Self { - let width = width.sanitize("SizeDef width"); - let height = height.sanitize("SizeDef height"); Self { width, height } } @@ -56,16 +44,18 @@ impl SizeDef { /// /// See [`LenDef::FitContent`] for details. /// - /// The `size` must be finite, non-negative, and in device pixels. - /// An invalid `size` dimension value will fall back to [`LenDef::MaxContent`]. + /// The `size` must be finite, non-negative, and in logical pixels. + /// An invalid `size` dimension value will fall back to zero. /// /// # Panics /// /// Panics if `size` contains non-finite or negative values and debug assertions are enabled. pub fn fit(size: Size) -> Self { + let width = size.width.sanitize("SizeDef::fit width"); + let height = size.height.sanitize("SizeDef::fit height"); Self::new( - LenDef::FitContent(size.width), - LenDef::FitContent(size.height), + LenDef::FitContent(Length::px(width)), + LenDef::FitContent(Length::px(height)), ) } @@ -73,28 +63,25 @@ impl SizeDef { /// /// See [`LenDef::Fixed`] for details. /// - /// The `size` must be finite, non-negative, and in device pixels. - /// An invalid `size` dimension value will fall back to [`LenDef::MaxContent`]. + /// The `size` must be finite, non-negative, and in logical pixels. + /// An invalid `size` dimension value will fall back to zero. /// /// # Panics /// /// Panics if `size` contains non-finite or negative values and debug assertions are enabled. pub fn fixed(size: Size) -> Self { - Self::new(LenDef::Fixed(size.width), LenDef::Fixed(size.height)) + let width = size.width.sanitize("SizeDef::fixed width"); + let height = size.height.sanitize("SizeDef::fixed height"); + Self::new( + LenDef::Fixed(Length::px(width)), + LenDef::Fixed(Length::px(height)), + ) } /// Creates a new [`SizeDef`] with `axis` set to [`LenDef`]. /// /// The other axis will be [`LenDef::MaxContent`]. - /// - /// [`LenDef`] values must be finite, non-negative, and in device pixels. - /// Invalid [`LenDef`] values will fall back to [`LenDef::MaxContent`]. - /// - /// # Panics - /// - /// Panics if `len_def` contains non-finite or negative values and debug assertions are enabled. pub fn one(axis: Axis, len_def: LenDef) -> Self { - let len_def = len_def.sanitize("SizeDef::one len_def"); match axis { Axis::Horizontal => Self { width: len_def, @@ -108,13 +95,6 @@ impl SizeDef { } /// Returns the [`SizeDef`] with `axis` set to [`LenDef`]. - /// - /// [`LenDef`] values must be finite, non-negative, and in device pixels. - /// Invalid [`LenDef`] values will fall back to [`LenDef::MaxContent`]. - /// - /// # Panics - /// - /// Panics if `len_def` contains non-finite or negative values and debug assertions are enabled. pub fn with(self, axis: Axis, len_def: LenDef) -> Self { match axis { Axis::Horizontal => self.with_width(len_def), @@ -123,36 +103,18 @@ impl SizeDef { } /// Returns the [`SizeDef`] with the width set to [`LenDef`]. - /// - /// [`LenDef`] values must be finite, non-negative, and in device pixels. - /// Invalid [`LenDef`] values will fall back to [`LenDef::MaxContent`]. - /// - /// # Panics - /// - /// Panics if `len_def` contains non-finite or negative values and debug assertions are enabled. pub fn with_width(mut self, len_def: LenDef) -> Self { - self.width = len_def.sanitize("SizeDef width"); + self.width = len_def; self } /// Returns the [`SizeDef`] with the height set to [`LenDef`]. - /// - /// [`LenDef`] values must be finite, non-negative, and in device pixels. - /// Invalid [`LenDef`] values will fall back to [`LenDef::MaxContent`]. - /// - /// # Panics - /// - /// Panics if `len_def` contains non-finite or negative values and debug assertions are enabled. pub fn with_height(mut self, len_def: LenDef) -> Self { - self.height = len_def.sanitize("SizeDef height"); + self.height = len_def; self } /// Returns the [`LenDef`] of the given `axis`. - /// - /// The result will already have been [sanitized]. - /// - /// [sanitized]: Sanitize pub const fn dim(&self, axis: Axis) -> LenDef { match axis { Axis::Horizontal => self.width, diff --git a/masonry_core/src/passes/event.rs b/masonry_core/src/passes/event.rs index d7b59cb588..72f1ff75fc 100644 --- a/masonry_core/src/passes/event.rs +++ b/masonry_core/src/passes/event.rs @@ -27,7 +27,6 @@ fn get_pointer_target( } if let Some(pointer_pos) = pointer_pos { - // TODO - Apply scale let pointer_pos = (pointer_pos.x, pointer_pos.y).into(); return root .get_widget(root.root_id()) diff --git a/masonry_core/src/passes/layout.rs b/masonry_core/src/passes/layout.rs index ad4975f8ef..bb7639f8dc 100644 --- a/masonry_core/src/passes/layout.rs +++ b/masonry_core/src/passes/layout.rs @@ -17,7 +17,7 @@ use crate::core::{ WidgetState, }; use crate::kurbo::{Axis, Insets, Point, Size}; -use crate::layout::{LayoutSize, LenDef, LenReq, MeasurementInputs, SizeDef}; +use crate::layout::{LayoutSize, LenDef, LenReq, Length, MeasurementInputs, SizeDef}; use crate::passes::{enter_span_if, recurse_on_children}; use crate::properties::{BorderWidth, BoxShadow, Dimensions, Padding}; use crate::util::Sanitize; @@ -25,64 +25,40 @@ use crate::util::Sanitize; // --- MARK: COMPUTE SIZE /// Measures the preferred border-box length of `widget` on the given `axis`. -/// -/// The returned length will be in device pixels. -/// Given that it will be the result of measuring, -/// it must be [sanitized] before passing it back to a widget. -/// -/// `len_req` must be [sanitized] before being passed to this function. -/// -/// `cross_length`, if present, must be [sanitized] and in device pixels. -/// -/// [sanitized]: Sanitize fn measure_border_box( widget: &mut dyn Widget, ctx: &mut MeasureCtx<'_>, props: &PropertiesRef<'_>, axis: Axis, len_req: LenReq, - cross_length: Option, -) -> f64 { - // TODO: Remove HACK: Until scale factor rework happens, just pretend it's always 1.0. - // https://github.com/linebender/xilem/issues/1264 - let scale = 1.0; - + cross_length: Option, +) -> Length { let cache = ctx.property_cache(); let border = props.get::(cache); let padding = props.get::(cache); - let border_length = border.length(axis).dp(scale); - let padding_length = padding.length(axis).dp(scale); + let border_and_padding_length = border.length(axis).saturating_add(padding.length(axis)); // Reduce the border-box length by the border and padding length to get the content-box length. - let len_req = len_req.reduce(border_length + padding_length); + let len_req = len_req.reduce(border_and_padding_length); let cross_length = cross_length.map(|cross_length| { let cross = axis.cross(); - let cross_border_length = border.length(cross).dp(scale); - let cross_padding_length = padding.length(cross).dp(scale); - (cross_length - cross_border_length - cross_padding_length).max(0.) + cross_length + .saturating_sub(border.length(cross)) + .saturating_sub(padding.length(cross)) }); // Measure the content-box length. let content_length = widget.measure(ctx, props, axis, len_req, cross_length); // Add border and padding to the content-box length to return the border-box length. - content_length + border_length + padding_length + content_length.saturating_add(border_and_padding_length) } /// Resolves the [`LenDef`] of the provided `axis`. /// /// Unless `Fixed`, this will result in a [`measure`] invocation. /// -/// The returned length will be in device pixels. -/// Given that it can be the result of measuring, -/// it must be [sanitized] before passing it back to a widget. -/// -/// `len_def` must be [sanitized] before being passed to this function. -/// -/// `cross_length`, if present, must be [sanitized] and in device pixels. -/// -/// [sanitized]: Sanitize /// [`measure`]: Widget::measure fn resolve_len_def( widget: &mut dyn Widget, @@ -90,8 +66,8 @@ fn resolve_len_def( props: &PropertiesRef<'_>, axis: Axis, len_def: LenDef, - cross_length: Option, -) -> f64 { + cross_length: Option, +) -> Length { let len_req = match len_def { LenDef::Fixed(val) => return val, LenDef::MinContent => LenReq::MinContent, @@ -139,28 +115,14 @@ fn resolve_len_def( /// Resolves the widget's preferred border-box length on the given `axis`. /// -/// The returned length will be finite, non-negative, and in device pixels. -/// /// `auto_length` specifies the fallback behavior if a widget's dimension is [`Dim::Auto`]. /// -/// `context_size` must be in device pixels. -/// -/// `cross_length`, if present, must be finite, non-negative, and in device pixels. -/// Invalid `cross_length` value is fall back to `None`. -/// /// # Panics /// -/// Panics if `auto_length` has a non-finite or negative value and debug assertions are enabled. -/// -/// Panics if `cross_length` is non-finite or negative and debug assertions are enabled. -/// /// Panics if a dimension resolves to a non-finite or negative value /// and debug assertions are enabled. This can happen if the involved numbers are huge, /// e.g. a logical size of `f64::MAX` scaled by `1.5`. /// -/// Panics if [`Widget::measure`] returned a non-finite or negative length -/// and debug assertions are enabled. -/// /// [`Dim::Auto`]: crate::layout::Dim::Auto pub(crate) fn resolve_length( global_state: &mut RenderRootState, @@ -169,18 +131,8 @@ pub(crate) fn resolve_length( auto_length: LenDef, context_size: LayoutSize, axis: Axis, - cross_length: Option, -) -> f64 { - // TODO: Remove HACK: Until scale factor rework happens, just pretend it's always 1.0. - // https://github.com/linebender/xilem/issues/1264 - let scale = 1.0; - - // Sanitize inputs early & always, to quickly catch bugs, - // because not every code path will use these values. - let auto_length = auto_length.sanitize("auto_length"); - let cross_length = cross_length.sanitize("cross_length"); - // LayoutSize encapsulates sanitization already. - + cross_length: Option, +) -> Length { // Get the dimensions let class_set = &node.item.class_set; let cache = &mut node.item.state.property_cache; @@ -199,9 +151,8 @@ pub(crate) fn resolve_length( // Resolve the dimension on the given axis let len_def = dims .dim(axis) - .resolve(scale, context_size.length(axis)) - .unwrap_or(auto_length) - .sanitize("len_def"); + .resolve(context_size.length(axis)) + .unwrap_or(auto_length); // Return immediately if we already have a fixed length if let LenDef::Fixed(length) = len_def { @@ -224,22 +175,19 @@ pub(crate) fn resolve_length( let cross_length = cross_length.or_else(|| { let cross = axis.cross(); dims.dim(cross) - .resolve(scale, context_size.length(cross)) - .and_then(|cross_len_def| cross_len_def.sanitize("cross_len_def").fixed()) + .resolve(context_size.length(cross)) + .and_then(|cross_len_def| cross_len_def.fixed()) }); // Measure - let length = resolve_len_def(widget, &mut ctx, &props, axis, len_def, cross_length); - length.sanitize("measured length") + resolve_len_def(widget, &mut ctx, &props, axis, len_def, cross_length) } /// Resolves the widget's preferred border-box size. /// -/// The returned size will be finite, non-negative, and in device pixels. +/// The returned size will be finite, non-negative, and in logical pixels. /// -/// `size_def` specifies the fallback behavior if a widget's dimension is [`Dim::Auto`]. -/// -/// `context_size` must be in device pixels. +/// `auto_size` specifies the fallback behavior if a widget's dimension is [`Dim::Auto`]. /// /// # Panics /// @@ -247,9 +195,6 @@ pub(crate) fn resolve_length( /// and debug assertions are enabled. This can happen if the involved numbers are huge, /// e.g. a logical size of `f64::MAX` scaled by `1.5`. /// -/// Panics if [`Widget::measure`] returned a non-finite or negative length -/// and debug assertions are enabled. -/// /// [`Dim::Auto`]: crate::layout::Dim::Auto pub(crate) fn resolve_size( global_state: &mut RenderRootState, @@ -258,12 +203,6 @@ pub(crate) fn resolve_size( auto_size: SizeDef, context_size: LayoutSize, ) -> Size { - // TODO: Remove HACK: Until scale factor rework happens, just pretend it's always 1.0. - // https://github.com/linebender/xilem/issues/1264 - let scale = 1.0; - - // Input sanitization is not required, because SizeDef and LayoutSize encapsulate it. - // Currently we only support the common horizontal-tb writing mode, // so the assignments are hardcoded here, but the rest of the function adapts. let (inline, block) = (Axis::Horizontal, Axis::Vertical); @@ -287,15 +226,13 @@ pub(crate) fn resolve_size( let inline_auto = auto_size.dim(inline); let inline_def = dims .dim(inline) - .resolve(scale, context_size.length(inline)) - .unwrap_or(inline_auto) - .sanitize("inline_def"); + .resolve(context_size.length(inline)) + .unwrap_or(inline_auto); let block_auto = auto_size.dim(block); let block_def = dims .dim(block) - .resolve(scale, context_size.length(block)) - .unwrap_or(block_auto) - .sanitize("block_def"); + .resolve(context_size.length(block)) + .unwrap_or(block_auto); // Return immediately if we already have a fixed size let inline_length = inline_def.fixed(); @@ -303,7 +240,7 @@ pub(crate) fn resolve_size( if let Some(inline_length) = inline_length && let Some(block_length) = block_length { - return inline.pack_size(inline_length, block_length); + return inline.pack_size(inline_length.get(), block_length.get()); } // Otherwise fall back to measurement @@ -320,7 +257,6 @@ pub(crate) fn resolve_size( let inline_length = inline_length.unwrap_or_else(|| { resolve_len_def(widget, &mut ctx, &props, inline, inline_def, block_length) - .sanitize("measured inline length") }); // Update the auto length @@ -338,10 +274,9 @@ pub(crate) fn resolve_size( block_def, Some(inline_length), ) - .sanitize("measured block length") }); - inline.pack_size(inline_length, block_length) + inline.pack_size(inline_length.get(), block_length.get()) } // --- MARK: RUN LAYOUT @@ -353,7 +288,7 @@ pub(crate) fn resolve_size( /// If the chosen border-box `size` is smaller than what is required to fit the widget's /// borders and padding, then the `size` will be expanded to meet those constraints. /// -/// The provided `size` must be finite, non-negative, and in device pixels. +/// The provided `size` must be finite, non-negative, and in logical pixels. /// Non-finite or negative length will fall back to zero with a logged warning. /// /// # Panics @@ -400,10 +335,6 @@ pub(crate) fn run_layout_on( return; } - // TODO: Remove HACK: Until scale factor rework happens, just pretend it's always 1.0. - // https://github.com/linebender/xilem/issues/1264 - let scale = 1.0; - let stack = property_arena.get(state.property_stack_id, widget.type_id()); let props = PropertiesRef { local: properties, @@ -419,8 +350,8 @@ pub(crate) fn run_layout_on( // Force the border-box size to be large enough to actually contain the border and padding. let minimum_size = Size::ZERO; - let minimum_size = border_width.size_up(minimum_size, scale); - let minimum_size = padding.size_up(minimum_size, scale); + let minimum_size = border_width.size_up(minimum_size); + let minimum_size = padding.size_up(minimum_size); let border_box_size = minimum_size.max(chosen_size); if !state.needs_layout() && state.layout_border_box_size == border_box_size { @@ -475,13 +406,13 @@ pub(crate) fn run_layout_on( state.paint_insets = Insets::ZERO; // Compute the insets for deriving the content-box from the border-box - let border_box_insets = border_width.insets_up(Insets::ZERO, scale); - let border_box_insets = padding.insets_up(border_box_insets, scale); + let border_box_insets = border_width.insets_up(Insets::ZERO); + let border_box_insets = padding.insets_up(border_box_insets); state.border_box_insets = border_box_insets; // Compute the content-box size - let content_box_size = border_width.size_down(border_box_size, scale); - let content_box_size = padding.size_down(content_box_size, scale); + let content_box_size = border_width.size_down(border_box_size); + let content_box_size = padding.size_down(content_box_size); let mut ctx = LayoutCtx { global_state, @@ -634,8 +565,6 @@ pub(crate) fn run_layout_pass(root: &mut RenderRoot) { if let WindowSizePolicy::Content = root.global_state.size_policy { // We use the aligned border-box size, which means that transforms won't affect window size. let size = root_node.item.state.border_box_size(); - // TODO: Remove HACK: Until scale factor rework happens, we still need to scale here. - // https://github.com/linebender/xilem/issues/1264 let new_size = LogicalSize::new(size.width, size.height).to_physical(root.global_state.scale_factor); if root.global_state.size != new_size { diff --git a/masonry_core/src/properties/border_width.rs b/masonry_core/src/properties/border_width.rs index 1891bc39a4..2674a7ded7 100644 --- a/masonry_core/src/properties/border_width.rs +++ b/masonry_core/src/properties/border_width.rs @@ -42,77 +42,75 @@ impl BorderWidth { /// Expands the `size` by the border width. /// - /// The returned [`Size`] will be non-negative and in device pixels. + /// The returned [`Size`] will be non-negative and in logical pixels. /// - /// The provided `size` must be in device pixels. + /// The provided `size` must be in logical pixels. /// /// Helper function to be called in [`Widget::layout`]. - pub fn size_up(&self, size: Size, scale: f64) -> Size { - let width = size.width + Length::px(self.width).dp(scale) * 2.; - let height = size.height + Length::px(self.width).dp(scale) * 2.; + pub fn size_up(&self, size: Size) -> Size { + let width = size.width + self.width * 2.; + let height = size.height + self.width * 2.; Size::new(width, height) } /// Shrinks the `size` by the border width. /// - /// The returned [`Size`] will be non-negative and in device pixels. + /// The returned [`Size`] will be non-negative and in logical pixels. /// - /// The provided `size` must be in device pixels. + /// The provided `size` must be in logical pixels. /// /// Helper function to be called in [`Widget::layout`]. - pub fn size_down(&self, size: Size, scale: f64) -> Size { - let width = (size.width - Length::px(self.width).dp(scale) * 2.).max(0.); - let height = (size.height - Length::px(self.width).dp(scale) * 2.).max(0.); + pub fn size_down(&self, size: Size) -> Size { + let width = (size.width - self.width * 2.).max(0.); + let height = (size.height - self.width * 2.).max(0.); Size::new(width, height) } /// Returns the [`Insets`] for deriving an area with this border. /// - /// The returned [`Insets`] will be in device pixels. + /// The returned [`Insets`] will be in logical pixels. /// - /// The provided `insets` must be in device pixels. - pub fn insets_up(&self, insets: Insets, scale: f64) -> Insets { - let width = Length::px(self.width).dp(scale); + /// The provided `insets` must be in logical pixels. + pub fn insets_up(&self, insets: Insets) -> Insets { Insets { - x0: insets.x0 + width, - y0: insets.y0 + width, - x1: insets.x1 + width, - y1: insets.y1 + width, + x0: insets.x0 + self.width, + y0: insets.y0 + self.width, + x1: insets.x1 + self.width, + y1: insets.y1 + self.width, } } /// Raises the `baseline` by the border width. /// - /// The returned baseline will be in device pixels. + /// The returned baseline will be in logical pixels. /// - /// The provided `baseline` must be in device pixels. + /// The provided `baseline` must be in logical pixels. /// /// Helper function to be called in [`Widget::layout`]. - pub fn baseline_up(&self, baseline: f64, scale: f64) -> f64 { - baseline + Length::px(self.width).dp(scale) + pub fn baseline_up(&self, baseline: f64) -> f64 { + baseline + self.width } /// Lowers the `baseline` by the border width. /// - /// The returned baseline will be in device pixels. + /// The returned baseline will be in logical pixels. /// - /// The provided `baseline` must be in device pixels. + /// The provided `baseline` must be in logical pixels. /// /// Helper function to be called in [`Widget::layout`]. - pub fn baseline_down(&self, baseline: f64, scale: f64) -> f64 { - baseline - Length::px(self.width).dp(scale) + pub fn baseline_down(&self, baseline: f64) -> f64 { + baseline - self.width } /// Lowers the position by the border width. /// - /// The returned [`Point`] will be in device pixels. + /// The returned [`Point`] will be in logical pixels. /// - /// The provided `origin` must be in device pixels. + /// The provided `origin` must be in logical pixels. /// /// Helper function to be called in [`Widget::layout`]. - pub fn origin_down(&self, origin: Point, scale: f64) -> Point { - let width = Length::px(self.width).dp(scale); - origin + Vec2::new(width, width) + pub fn origin_down(&self, origin: Point) -> Point { + origin + Vec2::new(self.width, self.width) } /// Creates a rounded rectangle that is inset by the border width. diff --git a/masonry_core/src/properties/padding.rs b/masonry_core/src/properties/padding.rs index bbe5ea9bf9..0c8ceba83c 100644 --- a/masonry_core/src/properties/padding.rs +++ b/masonry_core/src/properties/padding.rs @@ -136,76 +136,74 @@ impl Padding { /// Expands the `size` by the padding amount. /// - /// The returned [`Size`] will be non-negative and in device pixels. + /// The returned [`Size`] will be non-negative and in logical pixels. /// - /// The provided `size` must be in device pixels. + /// The provided `size` must be in logical pixels. /// /// Helper function to be called in [`Widget::layout`]. - pub fn size_up(&self, size: Size, scale: f64) -> Size { - let width = size.width + Length::px(self.left + self.right).dp(scale); - let height = size.height + Length::px(self.top + self.bottom).dp(scale); + pub fn size_up(&self, size: Size) -> Size { + let width = size.width + self.left + self.right; + let height = size.height + self.top + self.bottom; Size::new(width, height) } /// Shrinks the `size` by the padding amount. /// - /// The returned [`Size`] will be non-negative and in device pixels. + /// The returned [`Size`] will be non-negative and in logical pixels. /// - /// The provided `size` must be in device pixels. + /// The provided `size` must be in logical pixels. /// /// Helper function to be called in [`Widget::layout`]. - pub fn size_down(&self, size: Size, scale: f64) -> Size { - let width = (size.width - Length::px(self.left + self.right).dp(scale)).max(0.); - let height = (size.height - Length::px(self.top + self.bottom).dp(scale)).max(0.); + pub fn size_down(&self, size: Size) -> Size { + let width = (size.width - self.left - self.right).max(0.); + let height = (size.height - self.top - self.bottom).max(0.); Size::new(width, height) } /// Returns the [`Insets`] for deriving an area with this padding. /// - /// The returned [`Insets`] will be in device pixels. + /// The returned [`Insets`] will be in logical pixels. /// - /// The provided `insets` must be in device pixels. - pub fn insets_up(&self, insets: Insets, scale: f64) -> Insets { + /// The provided `insets` must be in logical pixels. + pub fn insets_up(&self, insets: Insets) -> Insets { Insets { - x0: insets.x0 + Length::px(self.left).dp(scale), - y0: insets.y0 + Length::px(self.top).dp(scale), - x1: insets.x1 + Length::px(self.right).dp(scale), - y1: insets.y1 + Length::px(self.bottom).dp(scale), + x0: insets.x0 + self.left, + y0: insets.y0 + self.top, + x1: insets.x1 + self.right, + y1: insets.y1 + self.bottom, } } /// Raises the `baseline` by the padding amount. /// - /// The returned baseline will be in device pixels. + /// The returned baseline will be in logical pixels. /// - /// The provided `baseline` must be in device pixels. + /// The provided `baseline` must be in logical pixels. /// /// Helper function to be called in [`Widget::layout`]. - pub fn baseline_up(&self, baseline: f64, scale: f64) -> f64 { - baseline + Length::px(self.bottom).dp(scale) + pub fn baseline_up(&self, baseline: f64) -> f64 { + baseline + self.bottom } /// Lowers the `baseline` by the padding amount. /// - /// The returned baseline will be in device pixels. + /// The returned baseline will be in logical pixels. /// - /// The provided `baseline` must be in device pixels. + /// The provided `baseline` must be in logical pixels. /// /// Helper function to be called in [`Widget::layout`]. - pub fn baseline_down(&self, baseline: f64, scale: f64) -> f64 { - baseline - Length::px(self.bottom).dp(scale) + pub fn baseline_down(&self, baseline: f64) -> f64 { + baseline - self.bottom } /// Lowers the position by the padding amount. /// - /// The returned [`Point`] will be in device pixels. + /// The returned [`Point`] will be in logical pixels. /// - /// The provided `origin` must be in device pixels. + /// The provided `origin` must be in logical pixels. /// /// Helper function to be called in [`Widget::layout`]. - pub fn origin_down(&self, origin: Point, scale: f64) -> Point { - let x = Length::px(self.left).dp(scale); - let y = Length::px(self.top).dp(scale); - origin + Vec2::new(x, y) + pub fn origin_down(&self, origin: Point) -> Point { + origin + Vec2::new(self.left, self.top) } } diff --git a/masonry_testing/src/modular_widget.rs b/masonry_testing/src/modular_widget.rs index 5abb2a2613..dd62c7a26f 100644 --- a/masonry_testing/src/modular_widget.rs +++ b/masonry_testing/src/modular_widget.rs @@ -17,7 +17,7 @@ use masonry_core::core::{ }; use masonry_core::imaging::Painter; use masonry_core::kurbo::{Axis, Point, Size}; -use masonry_core::layout::{LayoutSize, LenReq, SizeDef}; +use masonry_core::layout::{LayoutSize, LenReq, Length, SizeDef}; use tracing::trace_span; pub(crate) type PointerEventFn = @@ -33,8 +33,14 @@ pub(crate) type RegisterChildrenFn = dyn FnMut(&mut S, &mut RegisterCtx<'_>); pub(crate) type UpdateFn = dyn FnMut(&mut S, &mut UpdateCtx<'_>, &mut PropertiesMut<'_>, &Update); pub(crate) type PropertyChangeFn = dyn FnMut(&mut S, &mut UpdateCtx<'_>, TypeId); -pub(crate) type MeasureFn = - dyn FnMut(&mut S, &mut MeasureCtx<'_>, &PropertiesRef<'_>, Axis, LenReq, Option) -> f64; +pub(crate) type MeasureFn = dyn FnMut( + &mut S, + &mut MeasureCtx<'_>, + &PropertiesRef<'_>, + Axis, + LenReq, + Option, +) -> Length; pub(crate) type LayoutFn = dyn FnMut(&mut S, &mut LayoutCtx<'_>, &PropertiesRef<'_>, Size); pub(crate) type ComposeFn = dyn FnMut(&mut S, &mut ComposeCtx<'_>); pub(crate) type PaintFn = @@ -147,7 +153,7 @@ impl ModularWidget>> { let auto_length = len_req.into(); let context_size = LayoutSize::maybe(axis.cross(), cross_length); - let mut length: f64 = 0.; + let mut length = Length::ZERO; for child in children { if ctx.child_is_stashed(child) { continue; @@ -299,7 +305,14 @@ impl ModularWidget { /// See [`Widget::measure`] pub fn measure_fn( mut self, - f: impl FnMut(&mut S, &mut MeasureCtx<'_>, &PropertiesRef<'_>, Axis, LenReq, Option) -> f64 + f: impl FnMut( + &mut S, + &mut MeasureCtx<'_>, + &PropertiesRef<'_>, + Axis, + LenReq, + Option, + ) -> Length + 'static, ) -> Self { self.measure = Some(Box::new(f)); @@ -454,8 +467,8 @@ impl Widget for ModularWidget { props: &PropertiesRef<'_>, axis: Axis, len_req: LenReq, - cross_length: Option, - ) -> f64 { + cross_length: Option, + ) -> Length { let Self { state, measure, .. } = self; measure .as_mut() diff --git a/masonry_testing/src/recorder_widget.rs b/masonry_testing/src/recorder_widget.rs index cf99b7f9aa..02a9e77063 100644 --- a/masonry_testing/src/recorder_widget.rs +++ b/masonry_testing/src/recorder_widget.rs @@ -26,7 +26,7 @@ use masonry_core::core::{ }; use masonry_core::imaging::Painter; use masonry_core::kurbo::{Axis, Point, Size}; -use masonry_core::layout::LenReq; +use masonry_core::layout::{LenReq, Length}; // TODO - Re-enable doc test. // Doc test is currently disabled because it depends on a parent crate. @@ -85,7 +85,7 @@ pub enum Record { /// Property change. PropertyChange(TypeId), /// Measure. Records the length returned by the measure method. - Measure(f64), + Measure(Length), /// Layout. Records the size given to the layout method. Layout(Size), /// Compose. @@ -235,8 +235,8 @@ impl Widget for Recorder { props: &PropertiesRef<'_>, axis: Axis, len_req: LenReq, - cross_length: Option, - ) -> f64 { + cross_length: Option, + ) -> Length { let length = self.child.measure(ctx, props, axis, len_req, cross_length); self.recording.push(Record::Measure(length)); length diff --git a/masonry_testing/src/wrapper_widget.rs b/masonry_testing/src/wrapper_widget.rs index 84dba8edcd..08913d8f98 100644 --- a/masonry_testing/src/wrapper_widget.rs +++ b/masonry_testing/src/wrapper_widget.rs @@ -16,7 +16,7 @@ use masonry_core::core::{ }; use masonry_core::imaging::Painter; use masonry_core::kurbo::{Axis, Point, Size}; -use masonry_core::layout::{LayoutSize, LenReq, SizeDef}; +use masonry_core::layout::{LayoutSize, LenReq, Length, SizeDef}; /// A basic wrapper widget that can replace its child. pub struct WrapperWidget { @@ -100,8 +100,8 @@ impl Widget for WrapperWidget { _props: &PropertiesRef<'_>, axis: Axis, len_req: LenReq, - cross_length: Option, - ) -> f64 { + cross_length: Option, + ) -> Length { let auto_length = len_req.into(); let context_size = LayoutSize::maybe(axis.cross(), cross_length); diff --git a/xilem_masonry/src/one_of.rs b/xilem_masonry/src/one_of.rs index 5e033b6b99..f26bec076e 100644 --- a/xilem_masonry/src/one_of.rs +++ b/xilem_masonry/src/one_of.rs @@ -11,7 +11,7 @@ use masonry::core::{ }; use masonry::imaging::Painter; use masonry::kurbo::{Axis, Point, Size}; -use masonry::layout::LenReq; +use masonry::layout::{LenReq, Length}; use crate::core::Mut; use crate::core::one_of::OneOf; @@ -256,8 +256,8 @@ impl< _props: &PropertiesRef<'_>, axis: Axis, _len_req: LenReq, - cross_length: Option, - ) -> f64 { + cross_length: Option, + ) -> Length { match self { Self::A(w) => ctx.redirect_measurement(w, axis, cross_length), Self::B(w) => ctx.redirect_measurement(w, axis, cross_length), From a2ff6fc7fe34a06e9f8aeec6f7af241ce59a20df Mon Sep 17 00:00:00 2001 From: Kaur Kuut Date: Sat, 2 May 2026 20:43:17 +0300 Subject: [PATCH 2/4] Remove `Length::saturating_mul`. --- masonry/src/widgets/collapse_panel.rs | 2 +- masonry/src/widgets/split.rs | 2 +- masonry_core/src/layout/length.rs | 7 ------- 3 files changed, 2 insertions(+), 9 deletions(-) diff --git a/masonry/src/widgets/collapse_panel.rs b/masonry/src/widgets/collapse_panel.rs index 2f14ed4173..969db31e86 100644 --- a/masonry/src/widgets/collapse_panel.rs +++ b/masonry/src/widgets/collapse_panel.rs @@ -140,7 +140,7 @@ impl Widget for CollapsePanel { let border = props.get::(cache); let header_x_padding = theme::WIDGET_CONTROL_COMPONENT_PADDING; - let header_x_padding_length = header_x_padding.saturating_mul(Length::const_px(2.)); + let header_x_padding_length = header_x_padding.saturating_add(header_x_padding); let btn_length = BUTTON_LENGTH; let separator_height = border diff --git a/masonry/src/widgets/split.rs b/masonry/src/widgets/split.rs index fc814e60e1..35226ce2ee 100644 --- a/masonry/src/widgets/split.rs +++ b/masonry/src/widgets/split.rs @@ -630,7 +630,7 @@ where if cross == self.split_axis { let cross_space = cross_length.saturating_sub(self.bar_thickness); let split_point = self.calc_effective_split_point(cross_space.get()); - let child1_cross_space = cross_space.saturating_mul(split_point.px()); + let child1_cross_space = (cross_space.get() * split_point).px(); ( child1_cross_space, cross_space.saturating_sub(child1_cross_space), diff --git a/masonry_core/src/layout/length.rs b/masonry_core/src/layout/length.rs index c50e12e8f8..f117a8845e 100644 --- a/masonry_core/src/layout/length.rs +++ b/masonry_core/src/layout/length.rs @@ -127,11 +127,4 @@ impl Length { value: (self.value - other.value).max(0.), } } - - /// Multiplies by the `other` value but the result doesn't go above the maximum value. - pub const fn saturating_mul(self, other: Self) -> Self { - Self { - value: (self.value * other.value).min(f64::MAX), - } - } } From 25e5520d0978dbc5f5425ec9e519443eafc80041 Mon Sep 17 00:00:00 2001 From: Kaur Kuut Date: Sun, 3 May 2026 14:12:00 +0300 Subject: [PATCH 3/4] Migrate all logical pixel `Property` values to `Length`. --- masonry/README.md | 2 +- masonry/examples/calc_masonry.rs | 6 +- masonry/examples/gallery/badge.rs | 14 +-- masonry/examples/gallery/image.rs | 4 +- masonry/examples/gallery/kitchen_sink.rs | 6 +- masonry/examples/gallery/main.rs | 13 +-- masonry/examples/gallery/spinner.rs | 4 +- masonry/examples/gallery/split.rs | 10 +- masonry/examples/gallery/step_input.rs | 2 +- masonry/examples/gallery/transforms.rs | 6 +- masonry/examples/grid_masonry.rs | 4 +- masonry/examples/layers.rs | 2 +- masonry/examples/to_do_list.rs | 2 +- masonry/src/lib.rs | 2 +- masonry/src/properties/slider.rs | 13 +-- masonry/src/tests/layout.rs | 10 +- masonry/src/tests/paint.rs | 2 +- masonry/src/tests/properties.rs | 2 +- masonry/src/tests/transforms.rs | 2 +- masonry/src/theme.rs | 40 ++++---- masonry/src/widgets/align.rs | 2 +- masonry/src/widgets/button.rs | 18 ++-- masonry/src/widgets/checkbox.rs | 7 +- masonry/src/widgets/collapse_panel.rs | 25 +++-- masonry/src/widgets/divider.rs | 4 +- masonry/src/widgets/flex.rs | 54 ++++++---- masonry/src/widgets/grid.rs | 10 +- masonry/src/widgets/progress_bar.rs | 11 ++- masonry/src/widgets/radio_button.rs | 4 +- masonry/src/widgets/sized_box.rs | 40 ++++---- masonry/src/widgets/slider.rs | 15 +-- masonry/src/widgets/split.rs | 4 +- masonry/src/widgets/step_input.rs | 6 +- masonry/src/widgets/switch.rs | 19 ++-- masonry/src/widgets/zstack.rs | 4 +- masonry_core/src/core/widget_paint.rs | 7 +- masonry_core/src/properties/border_width.rs | 48 +++++---- masonry_core/src/properties/box_shadow.rs | 16 +-- masonry_core/src/properties/corner_radius.rs | 11 ++- masonry_core/src/properties/padding.rs | 98 +++++++++---------- masonry_core/src/properties/types/gradient.rs | 8 +- placehero/src/avatars.rs | 2 +- placehero/src/components/thread.rs | 4 +- placehero/src/components/timeline.rs | 8 +- xilem/examples/calc.rs | 4 +- xilem/examples/emoji_picker.rs | 2 +- xilem/examples/http_cats.rs | 14 +-- xilem/examples/mason.rs | 4 +- xilem/examples/slider_demo.rs | 16 +-- xilem/examples/state_machine.rs | 2 +- xilem/examples/to_do_mvc.rs | 8 +- xilem/examples/transparent_window.rs | 2 +- xilem/examples/variable_clock.rs | 2 +- xilem/examples/virtual_cats.rs | 18 ++-- xilem/examples/widgets.rs | 4 +- xilem/stress_tests/property_stack.rs | 21 ++-- xilem_masonry/src/style.rs | 8 +- xilem_masonry/src/view/sized_box.rs | 5 +- xilem_masonry/src/widget_view.rs | 8 +- 59 files changed, 366 insertions(+), 323 deletions(-) diff --git a/masonry/README.md b/masonry/README.md index 108c2d6887..c953c4cf63 100644 --- a/masonry/README.md +++ b/masonry/README.md @@ -133,7 +133,7 @@ pub fn make_widget_tree() -> NewWidget { let list = Flex::column() .with_fixed( NewWidget::new(Flex::row().with(text_input, 1.0).with_fixed(button)) - .with_props(PropertySet::new().with(Padding::all(WIDGET_SPACING.get()))), + .with_props(PropertySet::new().with(Padding::all(WIDGET_SPACING))), ) .with_fixed_spacer(WIDGET_SPACING); diff --git a/masonry/examples/calc_masonry.rs b/masonry/examples/calc_masonry.rs index be14fb0d53..12f310872b 100644 --- a/masonry/examples/calc_masonry.rs +++ b/masonry/examples/calc_masonry.rs @@ -255,7 +255,7 @@ pub fn build_calc() -> NewWidget { NewWidget::new(root_widget).with_props( PropertySet::new() .with(Background::Color(AlphaColor::from_str("#794869").unwrap())) - .with(Padding::all(2.0)) + .with(Padding::all(2.px())) .with(Gap::new(1.px())), ) } @@ -274,7 +274,7 @@ fn custom_property_set() -> DefaultProperties { PropertySet::new() .with(Background::Color(BLUE)) .with(BorderColor::new(Color::TRANSPARENT)) - .with(BorderWidth::all(2.0)), + .with(BorderWidth::all(2.px())), ); stack.push( Selector::classes(&["op_button"]).with_active(true), @@ -285,7 +285,7 @@ fn custom_property_set() -> DefaultProperties { PropertySet::new() .with(Background::Color(GRAY)) .with(BorderColor::new(Color::TRANSPARENT)) - .with(BorderWidth::all(2.0)), + .with(BorderWidth::all(2.px())), ); stack.push( Selector::classes(&["digit_button"]).with_active(true), diff --git a/masonry/examples/gallery/badge.rs b/masonry/examples/gallery/badge.rs index b4c1a7a3df..b1f7670487 100644 --- a/masonry/examples/gallery/badge.rs +++ b/masonry/examples/gallery/badge.rs @@ -6,7 +6,7 @@ use masonry::core::{ ErasedAction, Handled, NewWidget, PropertySet, StyleProperty, Widget, WidgetId, WidgetTag, }; use masonry::kurbo::Vec2; -use masonry::layout::Length; +use masonry::layout::{AsUnit, Length}; use masonry::parley::style::FontWeight; use masonry::peniko::Color; use masonry::properties::types::CrossAxisAlignment; @@ -93,7 +93,7 @@ impl DemoPage for BadgeDemo { let outline_badge = NewWidget::new(Badge::with_text("99+")).with_props( PropertySet::new() .with(Background::Color(Color::TRANSPARENT)) - .with(BorderWidth { width: 1.0 }) + .with(BorderWidth { width: 1.px() }) .with(BorderColor { color: Color::from_rgb8(0x71, 0x71, 0x7a), }), @@ -155,8 +155,8 @@ impl DemoPage for BadgeDemo { .with_props( PropertySet::new() .with(Background::Color(Color::from_rgb8(0x3f, 0x3f, 0x46))) - .with(CornerRadius { radius: 999.0 }) - .with(Padding::all(0.0)), + .with(CornerRadius { radius: 999.px() }) + .with(Padding::ZERO), ); let online_dot = NewWidget::new(Badge::new( @@ -166,9 +166,9 @@ impl DemoPage for BadgeDemo { )) .with_props( PropertySet::new() - .with(Padding::all(0.0)) - .with(CornerRadius { radius: 999.0 }) - .with(BorderWidth { width: 0.0 }) + .with(Padding::ZERO) + .with(CornerRadius { radius: 999.px() }) + .with(BorderWidth { width: 0.px() }) .with(Background::Color(Color::from_rgb8(0x22, 0xc5, 0x5e))), ); diff --git a/masonry/examples/gallery/image.rs b/masonry/examples/gallery/image.rs index 1a3025d478..aa16d8cb39 100644 --- a/masonry/examples/gallery/image.rs +++ b/masonry/examples/gallery/image.rs @@ -2,7 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 use masonry::core::{NewWidget, PropertySet, StyleProperty, Widget}; -use masonry::layout::AsUnit as _; +use masonry::layout::AsUnit; use masonry::peniko::{ImageAlphaType, ImageData, ImageFormat}; use masonry::properties::ObjectFit; use masonry::properties::types::CrossAxisAlignment; @@ -54,7 +54,7 @@ impl DemoPage for ImageDemo { .prepare(), ) .with_fixed_spacer(CONTENT_GAP) - .with_fixed(SizedBox::new(image).size(420.0.px(), 280.0.px()).prepare()); + .with_fixed(SizedBox::new(image).size(420.px(), 280.px()).prepare()); wrap_in_shell(self.shell, NewWidget::new(body).erased()) } diff --git a/masonry/examples/gallery/kitchen_sink.rs b/masonry/examples/gallery/kitchen_sink.rs index 6bf2ff18d2..9e7a29c377 100644 --- a/masonry/examples/gallery/kitchen_sink.rs +++ b/masonry/examples/gallery/kitchen_sink.rs @@ -2,7 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 use masonry::core::{NewWidget, PropertySet, StyleProperty, Widget}; -use masonry::layout::{AsUnit as _, UnitPoint}; +use masonry::layout::{AsUnit, UnitPoint}; use masonry::peniko::Color; use masonry::properties::types::CrossAxisAlignment; use masonry::properties::{Background, Padding}; @@ -43,10 +43,10 @@ impl DemoPage for KitchenSinkDemo { let grid = NewWidget::new(SizedBox::new(grid.prepare())).with_props( PropertySet::new() .with(Background::Color(Color::from_rgb8(0x24, 0x24, 0x24))) - .with(Padding::all(12.0)), + .with(Padding::all(12.px())), ); - let bg = NewWidget::new(SizedBox::empty().size(220.0.px(), 120.0.px())).with_props( + let bg = NewWidget::new(SizedBox::empty().size(220.px(), 120.px())).with_props( PropertySet::one(Background::Color(Color::from_rgb8(0x44, 0x22, 0x66))), ); diff --git a/masonry/examples/gallery/main.rs b/masonry/examples/gallery/main.rs index beaf20028a..23c373ce44 100644 --- a/masonry/examples/gallery/main.rs +++ b/masonry/examples/gallery/main.rs @@ -32,6 +32,7 @@ mod transforms; use masonry::core::{ErasedAction, NewWidget, StyleProperty, Widget as _, WidgetId, WidgetTag}; use masonry::dpi::LogicalSize; +use masonry::layout::Length; use masonry::parley::style::FontWeight; use masonry::properties::Padding; use masonry::properties::types::CrossAxisAlignment; @@ -45,11 +46,11 @@ use masonry_winit::winit::window::Window; use crate::demo::{DemoPage, new_demo_shell_tags}; use crate::switch::SwitchDemo; -const SIDEBAR_WIDTH: masonry::layout::Length = masonry::layout::Length::const_px(240.0); -const SIDEBAR_SCROLLBAR_INSET: f64 = 12.0; -const LEFT_PANE_TOP_PADDING: f64 = 12.0; -const LEFT_PANE_LEFT_PADDING: f64 = 12.0; -const RIGHT_PANE_PADDING: f64 = 12.0; +const SIDEBAR_WIDTH: Length = Length::const_px(240.0); +const SIDEBAR_SCROLLBAR_INSET: Length = Length::const_px(12.0); +const LEFT_PANE_TOP_PADDING: Length = Length::const_px(12.0); +const LEFT_PANE_LEFT_PADDING: Length = Length::const_px(12.0); +const RIGHT_PANE_PADDING: Length = Length::const_px(12.0); const DEMO_TITLE_FONT_SIZE: f32 = 20.0; @@ -222,7 +223,7 @@ fn main() { // scrollbar doesn't sit on top of the buttons. let list = NewWidget::new(SizedBox::new(list.prepare())).with_props(Padding { top: LEFT_PANE_TOP_PADDING, - bottom: 0.0, + bottom: Length::ZERO, left: LEFT_PANE_LEFT_PADDING, right: SIDEBAR_SCROLLBAR_INSET, }); diff --git a/masonry/examples/gallery/spinner.rs b/masonry/examples/gallery/spinner.rs index fafe54a0fc..1048362e8b 100644 --- a/masonry/examples/gallery/spinner.rs +++ b/masonry/examples/gallery/spinner.rs @@ -2,7 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 use masonry::core::{NewWidget, Widget}; -use masonry::layout::AsUnit as _; +use masonry::layout::AsUnit; use masonry::properties::types::CrossAxisAlignment; use masonry::widgets::{Flex, SizedBox, Spinner}; @@ -32,7 +32,7 @@ impl DemoPage for SpinnerDemo { .cross_axis_alignment(CrossAxisAlignment::Center) .with_fixed( SizedBox::new(Spinner::new().prepare()) - .size(80.0.px(), 80.0.px()) + .size(80.px(), 80.px()) .prepare(), ); diff --git a/masonry/examples/gallery/split.rs b/masonry/examples/gallery/split.rs index 5a5e9ed939..142524cab0 100644 --- a/masonry/examples/gallery/split.rs +++ b/masonry/examples/gallery/split.rs @@ -2,7 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 use masonry::core::{NewWidget, PropertySet, StyleProperty, Widget}; -use masonry::layout::AsUnit as _; +use masonry::layout::AsUnit; use masonry::peniko::Color; use masonry::properties::{Background, Padding}; use masonry::widgets::{Label, SizedBox, Split}; @@ -37,7 +37,7 @@ impl DemoPage for SplitDemo { .with_props( PropertySet::new() .with(Background::Color(Color::from_rgb8(0x1f, 0x2a, 0x44))) - .with(Padding::all(12.0)), + .with(Padding::all(12.px())), ); let right = NewWidget::new(SizedBox::new( @@ -48,11 +48,11 @@ impl DemoPage for SplitDemo { .with_props( PropertySet::new() .with(Background::Color(Color::from_rgb8(0x2b, 0x3c, 0x2f))) - .with(Padding::all(12.0)), + .with(Padding::all(12.px())), ); - let body = SizedBox::new(Split::new(left, right).split_fraction(0.33).prepare()) - .height(260.0.px()); + let body = + SizedBox::new(Split::new(left, right).split_fraction(0.33).prepare()).height(260.px()); wrap_in_shell(self.shell, NewWidget::new(body).erased()) } diff --git a/masonry/examples/gallery/step_input.rs b/masonry/examples/gallery/step_input.rs index 3dad7c9f6b..785af4b872 100644 --- a/masonry/examples/gallery/step_input.rs +++ b/masonry/examples/gallery/step_input.rs @@ -72,7 +72,7 @@ impl DemoPage for StepInputDemo { stack.push( Selector::new(), - (BorderWidth::all(2.), CornerRadius::all(20.)), + (BorderWidth::all(2.px()), CornerRadius::all(20.px())), ); stack.push( diff --git a/masonry/examples/gallery/transforms.rs b/masonry/examples/gallery/transforms.rs index 4a424f059e..0dc94bed4b 100644 --- a/masonry/examples/gallery/transforms.rs +++ b/masonry/examples/gallery/transforms.rs @@ -6,7 +6,7 @@ use masonry::core::{ ErasedAction, Handled, NewWidget, PropertySet, StyleProperty, Widget, WidgetId, WidgetTag, }; use masonry::kurbo::{Affine, Vec2}; -use masonry::layout::AsUnit as _; +use masonry::layout::AsUnit; use masonry::peniko::Color; use masonry::properties::types::CrossAxisAlignment; use masonry::properties::{Background, Padding}; @@ -108,13 +108,13 @@ impl DemoPage for TransformsDemo { .with_style(StyleProperty::FontSize(14.0)) .prepare(), ) - .size(160.0.px(), 160.0.px()), + .size(160.px(), 160.px()), ) .with_tag(self.target) .with_props( PropertySet::new() .with(Background::Color(Color::from_rgb8(0x35, 0x35, 0x35))) - .with(Padding::all(12.0)), + .with(Padding::all(12.px())), ); let body = Flex::column() diff --git a/masonry/examples/grid_masonry.rs b/masonry/examples/grid_masonry.rs index 92dd95b08f..d287be9b17 100644 --- a/masonry/examples/grid_masonry.rs +++ b/masonry/examples/grid_masonry.rs @@ -11,7 +11,7 @@ use masonry::core::{ ErasedAction, NewWidget, PointerButton, PropertySet, StyleProperty, Widget as _, WidgetId, }; use masonry::dpi::LogicalSize; -use masonry::layout::Length; +use masonry::layout::{AsUnit, Length}; use masonry::peniko::Color; use masonry::properties::{BorderColor, BorderWidth, Gap}; use masonry::theme::default_property_set; @@ -69,7 +69,7 @@ pub fn make_grid(grid_gap: f64) -> NewWidget { let props = PropertySet::new() .with(BorderColor::new(Color::from_rgb8(40, 40, 80))) - .with(BorderWidth::all(1.0)); + .with(BorderWidth::all(1.px())); let label = SizedBox::new(NewWidget::new(label).with_props(props)); let button_inputs = vec![ diff --git a/masonry/examples/layers.rs b/masonry/examples/layers.rs index 5d0e72e197..3e577ba9fd 100644 --- a/masonry/examples/layers.rs +++ b/masonry/examples/layers.rs @@ -178,7 +178,7 @@ fn main() { .with_props(PropertySet::one(ContentColor::new(Color::BLACK))), )) .with_props(PropertySet::from(( - BorderWidth::all(1.), + BorderWidth::all(1.px()), BorderColor::new(Color::BLACK), Background::Color(Color::WHITE), ))) diff --git a/masonry/examples/to_do_list.rs b/masonry/examples/to_do_list.rs index 5a44311d74..21947a2a5a 100644 --- a/masonry/examples/to_do_list.rs +++ b/masonry/examples/to_do_list.rs @@ -77,7 +77,7 @@ pub fn make_widget_tree() -> NewWidget { let root = Flex::column() .with_fixed( NewWidget::new(Flex::row().with(text_input, 1.0).with_fixed(button)) - .with_props(PropertySet::new().with(Padding::all(WIDGET_SPACING.get()))), + .with_props(PropertySet::new().with(Padding::all(WIDGET_SPACING))), ) .with_fixed_spacer(WIDGET_SPACING) .with(portal, 1.0); diff --git a/masonry/src/lib.rs b/masonry/src/lib.rs index 57e24e8693..9baf3fe54a 100644 --- a/masonry/src/lib.rs +++ b/masonry/src/lib.rs @@ -103,7 +103,7 @@ //! let list = Flex::column() //! .with_fixed( //! NewWidget::new(Flex::row().with(text_input, 1.0).with_fixed(button)) -//! .with_props(PropertySet::new().with(Padding::all(WIDGET_SPACING.get()))), +//! .with_props(PropertySet::new().with(Padding::all(WIDGET_SPACING))), //! ) //! .with_fixed_spacer(WIDGET_SPACING); //! diff --git a/masonry/src/properties/slider.rs b/masonry/src/properties/slider.rs index 28f01de323..66a6eb4f66 100644 --- a/masonry/src/properties/slider.rs +++ b/masonry/src/properties/slider.rs @@ -4,16 +4,17 @@ use std::any::TypeId; use crate::core::{Property, UpdateCtx}; +use crate::layout::Length; use crate::peniko::Color; use crate::theme; -/// The thickness of a slider's track, in logical pixels. +/// The thickness of a slider's track. #[derive(Default, Clone, Copy, Debug, PartialEq)] -pub struct TrackThickness(pub f64); +pub struct TrackThickness(pub Length); impl Property for TrackThickness { fn static_default() -> &'static Self { - static DEFAULT: TrackThickness = TrackThickness(4.); + static DEFAULT: TrackThickness = TrackThickness(Length::const_px(4.)); &DEFAULT } } @@ -27,13 +28,13 @@ impl TrackThickness { } } -/// The radius of a slider's thumb, in logical pixels. +/// The radius of a slider's thumb. #[derive(Default, Clone, Copy, Debug, PartialEq)] -pub struct ThumbRadius(pub f64); +pub struct ThumbRadius(pub Length); impl Property for ThumbRadius { fn static_default() -> &'static Self { - static DEFAULT: ThumbRadius = ThumbRadius(6.); + static DEFAULT: ThumbRadius = ThumbRadius(Length::const_px(6.)); &DEFAULT } } diff --git a/masonry/src/tests/layout.rs b/masonry/src/tests/layout.rs index 1427426de1..d504523145 100644 --- a/masonry/src/tests/layout.rs +++ b/masonry/src/tests/layout.rs @@ -274,12 +274,12 @@ fn content_box() { let props = ( Dimensions::fixed(100.px(), 100.px()), Padding { - left: 1., - right: 2., - top: 3., - bottom: 4., + left: 1.px(), + right: 2.px(), + top: 3.px(), + bottom: 4.px(), }, - BorderWidth::all(1.), + BorderWidth::all(1.px()), ); let hero = NewWidget::new(Button::with_text("Hero")) diff --git a/masonry/src/tests/paint.rs b/masonry/src/tests/paint.rs index e25f242dee..5852d58953 100644 --- a/masonry/src/tests/paint.rs +++ b/masonry/src/tests/paint.rs @@ -330,7 +330,7 @@ fn paint_transparency() { GridParams::new(13, 0, 3, 1), ); - let props = (Padding::all(20.), Gap::new(10.px())); + let props = (Padding::all(20.px()), Gap::new(10.px())); let grid_a = grid_a.prepare().with_props(props); let grid_b = grid_b.prepare().with_props(props); diff --git a/masonry/src/tests/properties.rs b/masonry/src/tests/properties.rs index a82b6a0811..8f2b44b9ad 100644 --- a/masonry/src/tests/properties.rs +++ b/masonry/src/tests/properties.rs @@ -2,7 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 use crate::core::Widget as _; -use crate::layout::AsUnit as _; +use crate::layout::AsUnit; use crate::palette::css::BLUE; use crate::properties::{ContentColor, Dimensions, Gap}; use crate::widgets::Button; diff --git a/masonry/src/tests/transforms.rs b/masonry/src/tests/transforms.rs index b0c8dba71d..bf81b52bd3 100644 --- a/masonry/src/tests/transforms.rs +++ b/masonry/src/tests/transforms.rs @@ -21,7 +21,7 @@ fn blue_box(inner: impl Widget) -> impl Widget { let mut box_props = PropertySet::new(); box_props.insert(Background::Color(palette::css::BLUE)); box_props.insert(BorderColor::new(palette::css::TEAL)); - box_props.insert(BorderWidth::all(2.0)); + box_props.insert(BorderWidth::all(2.px())); WrapperWidget::new( SizedBox::new(inner.prepare()) diff --git a/masonry/src/theme.rs b/masonry/src/theme.rs index b58e751db6..ce8b6b47da 100644 --- a/masonry/src/theme.rs +++ b/masonry/src/theme.rs @@ -8,7 +8,7 @@ use crate::core::{ DefaultProperties, PropertySet, PropertyStack, Selector, StyleProperty, StyleSet, }; -use crate::layout::Length; +use crate::layout::{AsUnit, Length}; use crate::palette::css::DIM_GRAY; use crate::parley::{GenericFamily, LineHeight}; use crate::peniko::Color; @@ -23,7 +23,7 @@ use crate::widgets::*; /// it should clear with this color by default. pub const BACKGROUND_COLOR: Color = Color::from_rgb8(0x1D, 0x1D, 0x1D); -pub const BORDER_WIDTH: f64 = 1.; +pub const BORDER_WIDTH: Length = Length::const_px(1.); // Zync color variations from https://tailwindcss.com/docs/colors pub const ZYNC_900: Color = Color::from_rgb8(0x18, 0x18, 0x1b); @@ -59,9 +59,9 @@ pub fn default_property_set() -> DefaultProperties { let mut properties = DefaultProperties::new(); // Badge - properties.insert::(Padding::from_vh(3., 5.)); - properties.insert::(CornerRadius { radius: 999. }); - properties.insert::(BorderWidth { width: 0. }); + properties.insert::(Padding::from_vh(3.px(), 5.px())); + properties.insert::(CornerRadius { radius: 999.px() }); + properties.insert::(BorderWidth { width: 0.px() }); properties.insert::(Background::Color(ACCENT_COLOR)); properties.insert::(BorderColor { color: ZYNC_700 }); { @@ -74,8 +74,8 @@ pub fn default_property_set() -> DefaultProperties { } // Button - properties.insert::(Padding::from_vh(6., 16.)); - properties.insert::(CornerRadius { radius: 6. }); + properties.insert::(Padding::from_vh(6.px(), 16.px())); + properties.insert::(CornerRadius { radius: 6.px() }); properties.insert::(BorderWidth { width: BORDER_WIDTH, }); @@ -103,7 +103,7 @@ pub fn default_property_set() -> DefaultProperties { } // Checkbox - properties.insert::(CornerRadius { radius: 4. }); + properties.insert::(CornerRadius { radius: 4.px() }); properties.insert::(BorderWidth { width: BORDER_WIDTH, }); @@ -142,21 +142,21 @@ pub fn default_property_set() -> DefaultProperties { Length::const_px(16.), Length::const_px(16.), )); - properties.insert::(Padding::all(4.)); + properties.insert::(Padding::all(4.px())); // Divider properties.insert::(ContentColor::new(ZYNC_500)); // Switch - properties.insert::(CornerRadius { radius: 10. }); // Full pill shape + properties.insert::(CornerRadius { radius: 10.px() }); // Full pill shape properties.insert::(BorderWidth { width: BORDER_WIDTH, }); properties.insert::(Background::Color(ZYNC_700)); properties.insert::(BorderColor { color: ZYNC_700 }); properties.insert::(ThumbColor(Color::WHITE)); - properties.insert::(ThumbRadius(8.0)); - properties.insert::(TrackThickness(20.0)); + properties.insert::(ThumbRadius(8.px())); + properties.insert::(TrackThickness(20.px())); { let mut stack = PropertyStack::new(); stack.push( @@ -186,8 +186,8 @@ pub fn default_property_set() -> DefaultProperties { use crate::widgets::Selector as SelectorButton; // Selector - properties.insert::(Padding::from_vh(6., 16.)); - properties.insert::(CornerRadius { radius: 2. }); + properties.insert::(Padding::from_vh(6.px(), 16.px())); + properties.insert::(CornerRadius { radius: 2.px() }); properties.insert::(BorderWidth { width: BORDER_WIDTH, }); @@ -216,7 +216,7 @@ pub fn default_property_set() -> DefaultProperties { } // SelectorItem - properties.insert::(Padding::from_vh(6., 16.)); + properties.insert::(Padding::from_vh(6.px(), 16.px())); properties.insert::(Background::Color(ZYNC_900)); { let mut stack = PropertyStack::new(); @@ -238,8 +238,8 @@ pub fn default_property_set() -> DefaultProperties { properties.insert::(Gap::ZERO); // TextInput - properties.insert::(Padding::from_vh(6., 12.)); - properties.insert::(CornerRadius { radius: 4. }); + properties.insert::(Padding::from_vh(6.px(), 12.px())); + properties.insert::(CornerRadius { radius: 4.px() }); properties.insert::(BorderWidth { width: BORDER_WIDTH, }); @@ -309,7 +309,7 @@ pub fn default_property_set() -> DefaultProperties { } // ProgressBar - properties.insert::(CornerRadius { radius: 2. }); + properties.insert::(CornerRadius { radius: 2.px() }); properties.insert::(BorderWidth { width: BORDER_WIDTH, }); @@ -381,8 +381,8 @@ pub fn default_text_styles(styles: &mut StyleSet) { } fn default_step_input_style(properties: &mut DefaultProperties) { - properties.insert::, _>(Padding::from_vh(6., 0.)); - properties.insert::, _>(CornerRadius { radius: 6. }); + properties.insert::, _>(Padding::from_vh(6.px(), 0.px())); + properties.insert::, _>(CornerRadius { radius: 6.px() }); properties.insert::, _>(BorderWidth { width: BORDER_WIDTH, }); diff --git a/masonry/src/widgets/align.rs b/masonry/src/widgets/align.rs index d1e60e1ff7..bf6445f851 100644 --- a/masonry/src/widgets/align.rs +++ b/masonry/src/widgets/align.rs @@ -258,7 +258,7 @@ mod tests { .with_tag(align_tag) .with_props(( Dimensions::fixed(50.px(), 50.px()), - BorderWidth::all(2.), + BorderWidth::all(2.px()), BorderColor::new(palette::css::BLACK), )); let root = Align::centered(align).prepare(); diff --git a/masonry/src/widgets/button.rs b/masonry/src/widgets/button.rs index 076c4ae61d..fca9869eea 100644 --- a/masonry/src/widgets/button.rs +++ b/masonry/src/widgets/button.rs @@ -369,9 +369,9 @@ mod tests { harness.edit_root_widget(|mut button| { button.insert_prop(BorderColor { color: red }); - button.insert_prop(BorderWidth { width: 5.0 }); - button.insert_prop(CornerRadius { radius: 20.0 }); - button.insert_prop(Padding::from_vh(3., 8.)); + button.insert_prop(BorderWidth { width: 5.px() }); + button.insert_prop(CornerRadius { radius: 20.px() }); + button.insert_prop(Padding::from_vh(3.px(), 8.px())); let mut label = Button::child_mut(&mut button); label.insert_prop(ContentColor::new(red)); @@ -403,7 +403,7 @@ mod tests { ); let root_widget = NewWidget::new(grid).with_props( PropertySet::new() - .with(Padding::all(20.0)) + .with(Padding::all(20.px())) .with(Gap::new(40.px())), ); @@ -423,19 +423,19 @@ mod tests { { let mut button = Grid::get_mut(&mut grid, 1); let mut button = button.downcast::