Skip to main content

azul_layout/solver3/
geometry.rs

1//! TODO: Move these to CSS module
2
3use azul_core::{
4    geom::{LogicalPosition, LogicalRect, LogicalSize},
5    ui_solver::ResolvedOffsets,
6};
7use azul_css::props::{
8    basic::{pixel::PixelValue, PhysicalSize, PropertyContext, ResolutionContext, SizeMetric},
9    layout::LayoutWritingMode,
10};
11
12/// Represents the CSS `box-sizing` property.
13#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
14pub enum BoxSizing {
15    #[default]
16    ContentBox,
17    BorderBox,
18}
19
20#[derive(Debug, Clone, PartialEq, PartialOrd)]
21pub struct PositionedRectangle {
22    /// The outer bounds of the rectangle
23    pub bounds: LogicalRect,
24    /// Margin of the rectangle.
25    pub margin: ResolvedOffsets,
26    /// Border widths of the rectangle.
27    pub border: ResolvedOffsets,
28    /// Padding of the rectangle.
29    pub padding: ResolvedOffsets,
30}
31
32/// Represents the four edges of a box for properties like margin, padding, border.
33#[derive(Debug, Clone, Copy, Default)]
34pub struct EdgeSizes {
35    pub top: f32,
36    pub right: f32,
37    pub bottom: f32,
38    pub left: f32,
39}
40
41impl EdgeSizes {
42    /// Returns the size of the edge at the start of the main/block axis.
43    pub fn main_start(&self, wm: LayoutWritingMode) -> f32 {
44        match wm {
45            LayoutWritingMode::HorizontalTb => self.top,
46            LayoutWritingMode::VerticalRl | LayoutWritingMode::VerticalLr => self.left,
47        }
48    }
49
50    /// Returns the size of the edge at the end of the main/block axis.
51    pub fn main_end(&self, wm: LayoutWritingMode) -> f32 {
52        match wm {
53            LayoutWritingMode::HorizontalTb => self.bottom,
54            LayoutWritingMode::VerticalRl | LayoutWritingMode::VerticalLr => self.right,
55        }
56    }
57
58    /// Returns the sum of the start and end sizes on the main/block axis.
59    pub fn main_sum(&self, wm: LayoutWritingMode) -> f32 {
60        self.main_start(wm) + self.main_end(wm)
61    }
62
63    /// Returns the size of the edge at the start of the cross/inline axis.
64    pub fn cross_start(&self, wm: LayoutWritingMode) -> f32 {
65        match wm {
66            LayoutWritingMode::HorizontalTb => self.left,
67            LayoutWritingMode::VerticalRl | LayoutWritingMode::VerticalLr => self.top,
68        }
69    }
70
71    /// Returns the size of the edge at the end of the cross/inline axis.
72    pub fn cross_end(&self, wm: LayoutWritingMode) -> f32 {
73        match wm {
74            LayoutWritingMode::HorizontalTb => self.right,
75            LayoutWritingMode::VerticalRl | LayoutWritingMode::VerticalLr => self.bottom,
76        }
77    }
78
79    /// Returns the sum of the start and end sizes on the cross/inline axis.
80    pub fn cross_sum(&self, wm: LayoutWritingMode) -> f32 {
81        self.cross_start(wm) + self.cross_end(wm)
82    }
83}
84
85// ============================================================================
86// UNRESOLVED VALUE TYPES (for lazy resolution during layout)
87// ============================================================================
88
89/// An unresolved CSS margin value.
90///
91/// Margins can be `auto` (for centering) or a length value that needs
92/// resolution against the containing block.
93#[derive(Debug, Clone, Copy, Default, PartialEq)]
94pub enum UnresolvedMargin {
95    /// margin: 0 (default)
96    #[default]
97    Zero,
98    /// margin: auto (for centering, CSS 2.2 § 10.3.3)
99    Auto,
100    /// A length value (px, %, em, vh, etc.)
101    Length(PixelValue),
102}
103
104impl UnresolvedMargin {
105    /// Returns true if this is an auto margin
106    pub fn is_auto(&self) -> bool {
107        matches!(self, UnresolvedMargin::Auto)
108    }
109
110    /// Resolve this margin value to pixels.
111    ///
112    /// - `Auto` returns 0.0 (actual auto margin calculation happens in layout)
113    /// - `Zero` returns 0.0
114    /// - `Length` is resolved using the resolution context
115    pub fn resolve(&self, ctx: &ResolutionContext) -> f32 {
116        match self {
117            UnresolvedMargin::Zero => 0.0,
118            UnresolvedMargin::Auto => 0.0, // Auto is handled separately in layout
119            UnresolvedMargin::Length(pv) => pv.resolve_with_context(ctx, PropertyContext::Margin),
120        }
121    }
122}
123
124/// Unresolved edge sizes for margin/padding/border.
125///
126/// This stores the raw CSS values before resolution, allowing us to
127/// defer resolution until the containing block size is known.
128#[derive(Debug, Clone, Copy, Default)]
129pub struct UnresolvedEdge<T> {
130    pub top: T,
131    pub right: T,
132    pub bottom: T,
133    pub left: T,
134}
135
136impl<T> UnresolvedEdge<T> {
137    pub fn new(top: T, right: T, bottom: T, left: T) -> Self {
138        Self { top, right, bottom, left }
139    }
140}
141
142impl UnresolvedEdge<UnresolvedMargin> {
143    /// Resolve all margin edges to pixel values.
144    pub fn resolve(&self, ctx: &ResolutionContext) -> EdgeSizes {
145        EdgeSizes {
146            top: self.top.resolve(ctx),
147            right: self.right.resolve(ctx),
148            bottom: self.bottom.resolve(ctx),
149            left: self.left.resolve(ctx),
150        }
151    }
152
153    /// Extract which margins are set to `auto`.
154    pub fn get_margin_auto(&self) -> MarginAuto {
155        MarginAuto {
156            top: self.top.is_auto(),
157            right: self.right.is_auto(),
158            bottom: self.bottom.is_auto(),
159            left: self.left.is_auto(),
160        }
161    }
162}
163
164impl UnresolvedEdge<PixelValue> {
165    /// Resolve all edges to pixel values.
166    pub fn resolve(&self, ctx: &ResolutionContext, prop_ctx: PropertyContext) -> EdgeSizes {
167        EdgeSizes {
168            top: self.top.resolve_with_context(ctx, prop_ctx),
169            right: self.right.resolve_with_context(ctx, prop_ctx),
170            bottom: self.bottom.resolve_with_context(ctx, prop_ctx),
171            left: self.left.resolve_with_context(ctx, prop_ctx),
172        }
173    }
174}
175
176/// Parameters needed to resolve CSS values to pixels.
177#[derive(Debug, Clone, Copy)]
178pub struct ResolutionParams {
179    /// The containing block size (for % resolution)
180    pub containing_block: LogicalSize,
181    /// The viewport size (for vh/vw resolution)
182    pub viewport_size: LogicalSize,
183    /// The element's computed font-size (for em resolution)
184    pub element_font_size: f32,
185    /// The root element's font-size (for rem resolution)
186    pub root_font_size: f32,
187}
188
189impl ResolutionParams {
190    /// Create a ResolutionContext from these parameters.
191    pub fn to_resolution_context(&self) -> ResolutionContext {
192        ResolutionContext {
193            element_font_size: self.element_font_size,
194            parent_font_size: self.element_font_size, // For em in non-font properties
195            root_font_size: self.root_font_size,
196            element_size: None,
197            containing_block_size: PhysicalSize::new(
198                self.containing_block.width,
199                self.containing_block.height,
200            ),
201            viewport_size: PhysicalSize::new(
202                self.viewport_size.width,
203                self.viewport_size.height,
204            ),
205        }
206    }
207}
208
209// ============================================================================
210// UNRESOLVED BOX PROPS (new design)
211// ============================================================================
212
213/// Box properties with unresolved CSS values.
214///
215/// This stores the raw CSS values as parsed, deferring resolution until
216/// layout time when the containing block size is known.
217#[derive(Debug, Clone, Copy, Default)]
218pub struct UnresolvedBoxProps {
219    pub margin: UnresolvedEdge<UnresolvedMargin>,
220    pub padding: UnresolvedEdge<PixelValue>,
221    pub border: UnresolvedEdge<PixelValue>,
222}
223
224impl UnresolvedBoxProps {
225    /// Resolve all box properties to pixel values.
226    pub fn resolve(&self, params: &ResolutionParams) -> ResolvedBoxProps {
227        let ctx = params.to_resolution_context();
228        ResolvedBoxProps {
229            margin: self.margin.resolve(&ctx),
230            padding: self.padding.resolve(&ctx, PropertyContext::Padding),
231            border: self.border.resolve(&ctx, PropertyContext::BorderWidth),
232            margin_auto: self.margin.get_margin_auto(),
233        }
234    }
235}
236
237// ============================================================================
238// RESOLVED BOX PROPS (legacy name: BoxProps)
239// ============================================================================
240
241/// Tracks which margins are set to `auto` (for centering calculations).
242#[derive(Debug, Clone, Copy, Default)]
243pub struct MarginAuto {
244    pub left: bool,
245    pub right: bool,
246    pub top: bool,
247    pub bottom: bool,
248}
249
250/// A fully resolved representation of a node's box model properties.
251///
252/// All values are in pixels. This is the result of resolving `UnresolvedBoxProps`
253/// against a containing block.
254#[derive(Debug, Clone, Copy, Default)]
255pub struct ResolvedBoxProps {
256    pub margin: EdgeSizes,
257    pub padding: EdgeSizes,
258    pub border: EdgeSizes,
259    /// Tracks which margins are set to `auto`.
260    /// CSS 2.2 § 10.3.3: If both margin-left and margin-right are auto,
261    /// their used values are equal, centering the element within its container.
262    pub margin_auto: MarginAuto,
263}
264
265impl ResolvedBoxProps {
266    /// Calculates the inner content-box size from an outer border-box size,
267    /// correctly accounting for the specified writing mode.
268    pub fn inner_size(&self, outer_size: LogicalSize, wm: LayoutWritingMode) -> LogicalSize {
269        let outer_main = outer_size.main(wm);
270        let outer_cross = outer_size.cross(wm);
271
272        // The sum of padding and border along the cross (inline) axis.
273        let cross_axis_spacing = self.padding.cross_sum(wm) + self.border.cross_sum(wm);
274
275        // The sum of padding and border along the main (block) axis.
276        let main_axis_spacing = self.padding.main_sum(wm) + self.border.main_sum(wm);
277
278        let inner_main = (outer_main - main_axis_spacing).max(0.0);
279        let inner_cross = (outer_cross - cross_axis_spacing).max(0.0);
280
281        LogicalSize::from_main_cross(inner_main, inner_cross, wm)
282    }
283}
284
285/// Type alias for backwards compatibility.
286/// TODO: Remove this once all code uses ResolvedBoxProps directly.
287pub type BoxProps = ResolvedBoxProps;
288
289// Verwende die Typen aus azul_css für float und clear
290pub use azul_css::props::layout::{LayoutClear, LayoutFloat};
291
292/// Represents the intrinsic sizing information for an element, calculated
293/// without knowledge of the final containing block size.
294#[derive(Debug, Clone, Copy, Default)]
295pub struct IntrinsicSizes {
296    /// The narrowest possible width, e.g., the width of the longest word.
297    pub min_content_width: f32,
298    /// The preferred width if infinite horizontal space is available.
299    pub max_content_width: f32,
300    /// The width specified by CSS properties, if any.
301    pub preferred_width: Option<f32>,
302    /// The height of the element at its `min_content_width`.
303    pub min_content_height: f32,
304    /// The height of the element at its `max_content_width`.
305    pub max_content_height: f32,
306    /// The height specified by CSS properties, if any.
307    pub preferred_height: Option<f32>,
308}
309
310impl IntrinsicSizes {
311    /// Creates a zero-sized IntrinsicSizes.
312    pub fn zero() -> Self {
313        Self::default()
314    }
315}