ferroui_base/layout/
layout_helper.rs1use super::{Layoutable, MinMax};
2use crate::utilities::MathUtilities;
3use crate::{Point, Rect, Size, Thickness, Visual};
4
5pub struct LayoutHelper;
7
8impl LayoutHelper {
9 pub const LAYOUT_EPSILON: f64 = 0.00000153;
12
13 pub fn apply_layout_constraints(control: &Layoutable, constraints: Size) -> Size {
16 Self::apply_layout_constraints_min_max(MinMax::new(control), constraints)
17 }
18
19 pub(crate) fn apply_layout_constraints_min_max(min_max: MinMax, constraints: Size) -> Size {
20 Size::new(
21 MathUtilities::clamp(constraints.width, min_max.min_width, min_max.max_width),
22 MathUtilities::clamp(constraints.height, min_max.min_height, min_max.max_height),
23 )
24 }
25
26 pub fn measure_child_with_border(
29 control: Option<&Layoutable>,
30 available_size: Size,
31 mut padding: Thickness,
32 mut border_thickness: Thickness,
33 ) -> Size {
34 if let Some(scale) = Self::is_parent_layout_rounded(control) {
35 padding = Self::round_layout_thickness(padding, scale);
36 border_thickness = Self::round_layout_thickness(border_thickness, scale);
37 }
38
39 match control {
40 Some(control) => {
41 control.measure(available_size.deflate(padding + border_thickness));
42 control.desired_size().inflate(padding + border_thickness)
43 }
44 None => Size::default().inflate(padding + border_thickness),
45 }
46 }
47
48 pub fn measure_child(control: Option<&Layoutable>, available_size: Size, mut padding: Thickness) -> Size {
51 if let Some(scale) = Self::is_parent_layout_rounded(control) {
52 padding = Self::round_layout_thickness(padding, scale);
53 }
54
55 match control {
56 Some(control) => {
57 control.measure(available_size.deflate(padding));
58 control.desired_size().inflate(padding)
59 }
60 None => Size::new(padding.left + padding.right, padding.bottom + padding.top),
61 }
62 }
63
64 pub fn arrange_child_with_border(
66 child: Option<&Layoutable>,
67 available_size: Size,
68 mut padding: Thickness,
69 mut border_thickness: Thickness,
70 ) -> Size {
71 if let Some(scale) = Self::is_parent_layout_rounded(child) {
72 padding = Self::round_layout_thickness(padding, scale);
73 border_thickness = Self::round_layout_thickness(border_thickness, scale);
74 }
75 Self::arrange_child_internal(child, available_size, padding + border_thickness)
76 }
77
78 pub fn arrange_child(child: Option<&Layoutable>, available_size: Size, mut padding: Thickness) -> Size {
80 if let Some(scale) = Self::is_parent_layout_rounded(child) {
81 padding = Self::round_layout_thickness(padding, scale);
82 }
83 Self::arrange_child_internal(child, available_size, padding)
84 }
85
86 fn arrange_child_internal(child: Option<&Layoutable>, available_size: Size, padding: Thickness) -> Size {
87 if let Some(child) = child {
88 child.arrange(Rect::from_size(available_size).deflate_thickness(padding));
89 }
90 available_size
91 }
92
93 fn is_parent_layout_rounded(child: Option<&Layoutable>) -> Option<f64> {
96 let parent = child?.visual_parent()?;
97 let parent = parent.downcast_ref::<Layoutable>()?;
98 if !parent.use_layout_rounding() {
99 return None;
100 }
101 Some(Self::get_layout_scale(parent))
102 }
103
104 pub fn invalidate_self_and_children_measure(control: &Layoutable) {
106 fn inner(target: &Visual) {
107 if let Some(layoutable) = target.downcast_ref::<Layoutable>() {
108 layoutable.invalidate_measure();
109 }
110 if let Some(children) = target.visual_children_snapshot() {
111 for child in children.iter() {
112 inner(child);
113 }
114 }
115 }
116 inner(control);
117 }
118
119 pub fn get_layout_scale(control: &Layoutable) -> f64 {
122 control.get_layout_root().map_or(1.0, |root| root.layout_scaling())
123 }
124
125 pub fn round_layout_size_up(size: Size, dpi_scale: f64) -> Size {
127 if dpi_scale == 1.0 {
129 Size::new(size.width.ceil(), size.height.ceil())
130 } else {
131 Size::new(
132 (Self::round_to_8_digits(size.width) * dpi_scale).ceil() / dpi_scale,
133 (Self::round_to_8_digits(size.height) * dpi_scale).ceil() / dpi_scale,
134 )
135 }
136 }
137
138 pub fn round_layout_thickness(thickness: Thickness, dpi_scale: f64) -> Thickness {
140 if dpi_scale == 1.0 {
142 Thickness::new(
143 thickness.left.round_ties_even(),
144 thickness.top.round_ties_even(),
145 thickness.right.round_ties_even(),
146 thickness.bottom.round_ties_even(),
147 )
148 } else {
149 Thickness::new(
150 (thickness.left * dpi_scale).round_ties_even() / dpi_scale,
151 (thickness.top * dpi_scale).round_ties_even() / dpi_scale,
152 (thickness.right * dpi_scale).round_ties_even() / dpi_scale,
153 (thickness.bottom * dpi_scale).round_ties_even() / dpi_scale,
154 )
155 }
156 }
157
158 #[inline]
160 pub fn round_layout_point(point: Point, dpi_scale: f64) -> Point {
161 if dpi_scale == 1.0 {
163 Point::new(point.x.round_ties_even(), point.y.round_ties_even())
164 } else {
165 Point::new(
166 (point.x * dpi_scale).round_ties_even() / dpi_scale,
167 (point.y * dpi_scale).round_ties_even() / dpi_scale,
168 )
169 }
170 }
171
172 pub fn round_layout_value(value: f64, dpi_scale: f64) -> f64 {
175 if dpi_scale == 1.0 {
177 value.round_ties_even()
178 } else {
179 (value * dpi_scale).round_ties_even() / dpi_scale
180 }
181 }
182
183 pub fn round_layout_value_up(value: f64, dpi_scale: f64) -> f64 {
186 if dpi_scale == 1.0 {
188 value.ceil()
189 } else {
190 (Self::round_to_8_digits(value) * dpi_scale).ceil() / dpi_scale
191 }
192 }
193
194 #[inline]
195 fn round_to_8_digits(value: f64) -> f64 {
196 if !value.is_finite() {
202 return value;
203 }
204 (value * 1e8).trunc() / 1e8
205 }
206
207 pub fn validate_scaling(scaling: f64) -> f64 {
210 if MathUtilities::is_negative_or_non_finite(scaling) || MathUtilities::is_zero(scaling) {
211 panic!("Invalid render scaling value {scaling}");
212 }
213 if MathUtilities::is_one(scaling) {
214 return 1.0;
217 }
218 scaling
219 }
220}