maycoon_core/
layout.rs

1use nalgebra::Vector2;
2pub use taffy::{
3    AlignContent, AlignItems, AlignSelf, Dimension, Display, FlexDirection, FlexWrap, GridAutoFlow,
4    GridPlacement, JustifyContent, JustifyItems, JustifySelf, Layout, LengthPercentage,
5    LengthPercentageAuto, Line, NodeId, Overflow, Position, Rect, TaffyError, TaffyResult,
6    TaffyTree,
7};
8
9/// The tiniest difference two floats should have, in a layout context, to be considered "unequal".
10///
11/// This value is equal to half a pixel.
12///
13/// When comparing two floats in a layout context,
14/// we can check if the difference is higher than this threshold,
15/// in order to determine if redrawing is necessary.
16///
17/// See [equal] for more.
18pub const LAYOUT_EPSILON: f32 = 0.5;
19
20/// Checks if two floats are equal, within the [LAYOUT_EPSILON] threshold.
21///
22/// This is useful for comparing float values in a layout context,
23/// as floating point precision can cause small differences that are not significant
24/// and therefore need no redraw.
25///
26/// Example:
27/// ```
28/// # use maycoon_core::layout;
29///
30/// // Significant change. Should redraw.
31/// assert!(!layout::equal(10.0, 20.0));
32///
33/// // Insignificant change. Should not redraw.
34/// assert!(layout::equal(10.0, 10.005));
35/// ```
36#[inline(always)]
37pub const fn equal(x: f32, y: f32) -> bool {
38    (x - y).abs() < LAYOUT_EPSILON
39}
40
41/// Checks if the given point intersects the given layout.
42///
43/// This can be used to check if the cursor is hovering on a widget.
44///
45/// Example:
46/// ```
47/// # use taffy::Layout;
48/// # use maycoon_core::app::info::AppInfo;
49/// # use maycoon_core::layout;
50/// # let info = AppInfo::default();
51/// # let layout = Layout::default();
52///
53/// if let Some(cursor) = info.cursor_pos && layout::intersects(cursor, &layout) {
54///     println!("Hovering on widget!");
55/// }
56/// ```
57#[inline(always)]
58pub fn intersects(point: Vector2<f32>, layout: &Layout) -> bool {
59    point.x >= layout.location.x
60        && point.x <= layout.location.x + layout.size.width
61        && point.y >= layout.location.y
62        && point.y <= layout.location.y + layout.size.height
63}
64
65/// Defines different aspects and properties of a widget layout.
66#[derive(Clone, PartialEq, Debug)]
67pub struct LayoutStyle {
68    /// What layout strategy should be used?
69    pub display: Display,
70
71    /// How children overflowing their container should affect layout.
72    pub overflow: (Overflow, Overflow),
73
74    /// How much space (in points) should be reserved for scrollbars.
75    pub scrollbar_width: f32,
76
77    /// What should the position value of this struct use as a base offset?
78    pub position: Position,
79
80    /// How should the position of this element be tweaked relative to the layout defined?
81    pub inset: Rect<LengthPercentageAuto>,
82
83    /// Sets the initial size of the item.
84    pub size: Vector2<Dimension>,
85
86    /// Controls the minimum size of the item.
87    pub min_size: Vector2<Dimension>,
88
89    /// Controls the maximum size of the item.
90    pub max_size: Vector2<Dimension>,
91
92    /// Sets the preferred aspect ratio for the item
93    ///
94    /// The ratio is calculated as width divided by height.
95    pub aspect_ratio: Option<f32>,
96
97    /// How large should the margin be on each side?
98    pub margin: Rect<LengthPercentageAuto>,
99
100    /// How large should the padding be on each side?
101    pub padding: Rect<LengthPercentage>,
102
103    /// How large should the border be on each side?
104    pub border: Rect<LengthPercentage>,
105
106    /// How this node's children aligned in the cross/block axis?
107    pub align_items: Option<AlignItems>,
108
109    /// How this node should be aligned in the cross/block axis
110    /// Falls back to the parents [AlignItems] if not set.
111    pub align_self: Option<AlignSelf>,
112
113    /// How this node's children should be aligned in the inline axis.
114    pub justify_items: Option<AlignItems>,
115
116    /// How this node should be aligned in the inline axis
117    /// Falls back to the parents [JustifyItems] if not set.
118    pub justify_self: Option<AlignSelf>,
119
120    /// How should content contained within this item be aligned in the cross/block axis?
121    pub align_content: Option<AlignContent>,
122
123    /// How should content contained within this item be aligned in the main/inline axis?
124    pub justify_content: Option<JustifyContent>,
125
126    /// How large should the gaps between items in a grid or flex container be?
127    pub gap: Vector2<LengthPercentage>,
128
129    /// Which direction does the main axis flow in?
130    pub flex_direction: FlexDirection,
131
132    /// Should elements wrap, or stay in a single line?
133    pub flex_wrap: FlexWrap,
134
135    /// Sets the initial main axis size of the item.
136    pub flex_basis: Dimension,
137
138    /// The relative rate at which this item grows when it is expanding to fill space.
139    ///
140    /// 0.0 is the default value, and this value must be positive.
141    pub flex_grow: f32,
142
143    /// The relative rate at which this item shrinks when it is contracting to fit into space.
144    ///
145    /// 1.0 is the default value, and this value must be positive.
146    pub flex_shrink: f32,
147
148    /// Controls how items get placed into the grid for auto-placed items.
149    pub grid_auto_flow: GridAutoFlow,
150
151    /// Defines which row in the grid the item should start and end at.
152    pub grid_row: Line<GridPlacement>,
153
154    /// Defines which column in the grid the item should start and end at.
155    pub grid_column: Line<GridPlacement>,
156}
157
158impl Default for LayoutStyle {
159    #[inline(always)]
160    fn default() -> Self {
161        LayoutStyle {
162            display: Display::Flex,
163            overflow: (Overflow::Visible, Overflow::Visible),
164            scrollbar_width: 0.0,
165            position: Position::Relative,
166            inset: Rect::auto(),
167            margin: Rect::zero(),
168            padding: Rect::zero(),
169            border: Rect::zero(),
170            size: Vector2::new(Dimension::auto(), Dimension::auto()),
171            min_size: Vector2::new(Dimension::auto(), Dimension::auto()),
172            max_size: Vector2::new(Dimension::auto(), Dimension::auto()),
173            aspect_ratio: None,
174            gap: Vector2::new(LengthPercentage::length(0.0), LengthPercentage::length(0.0)),
175            align_items: None,
176            align_self: None,
177            justify_items: None,
178            justify_self: None,
179            align_content: None,
180            justify_content: None,
181            flex_direction: FlexDirection::Row,
182            flex_wrap: FlexWrap::NoWrap,
183            flex_grow: 0.0,
184            flex_shrink: 1.0,
185            flex_basis: Dimension::auto(),
186            grid_auto_flow: GridAutoFlow::Row,
187            grid_row: Line {
188                start: GridPlacement::Auto,
189                end: GridPlacement::Auto,
190            },
191            grid_column: Line {
192                start: GridPlacement::Auto,
193                end: GridPlacement::Auto,
194            },
195        }
196    }
197}
198
199impl From<LayoutStyle> for taffy::Style {
200    #[inline(always)]
201    fn from(value: LayoutStyle) -> Self {
202        taffy::Style {
203            display: value.display,
204            overflow: taffy::Point {
205                x: value.overflow.0,
206                y: value.overflow.1,
207            },
208            scrollbar_width: value.scrollbar_width,
209            position: value.position,
210            inset: value.inset,
211            margin: value.margin,
212            padding: value.padding,
213            border: value.border,
214            size: taffy::Size {
215                width: value.size.x,
216                height: value.size.y,
217            },
218            min_size: taffy::Size {
219                width: value.min_size.x,
220                height: value.min_size.y,
221            },
222            max_size: taffy::Size {
223                width: value.max_size.x,
224                height: value.max_size.y,
225            },
226            aspect_ratio: value.aspect_ratio,
227            gap: taffy::Size {
228                width: value.gap.x,
229                height: value.gap.y,
230            },
231            align_items: value.align_items,
232            align_self: value.align_self,
233            justify_items: value.justify_items,
234            justify_self: value.justify_self,
235            align_content: value.align_content,
236            justify_content: value.justify_content,
237            flex_direction: value.flex_direction,
238            flex_wrap: value.flex_wrap,
239            flex_grow: value.flex_grow,
240            flex_shrink: value.flex_shrink,
241            flex_basis: value.flex_basis,
242            grid_auto_flow: value.grid_auto_flow,
243            grid_row: value.grid_row,
244            grid_column: value.grid_column,
245            ..Default::default()
246        }
247    }
248}
249
250/// The computed layout with children nodes.
251#[derive(Debug)]
252pub struct LayoutNode {
253    /// The computed layout of this node.
254    pub layout: Layout,
255    /// The children of this node.
256    pub children: Vec<LayoutNode>,
257}
258
259/// The raw layout styles with children nodes.
260pub struct StyleNode {
261    /// The layout style of this node.
262    pub style: LayoutStyle,
263    /// The children of this node.
264    pub children: Vec<StyleNode>,
265}
266
267#[cfg(all(test, feature = "test"))]
268mod tests {
269    use crate::layout::{equal, intersects};
270    use nalgebra::Vector2;
271    use taffy::{Layout, Point, Size};
272
273    /// Test the [equal] function.
274    #[test_case::test_case(1.0, 1.0, true; "when equal and true")]
275    #[test_case::test_case(12.0, 12.4, true; "when positive and true")]
276    #[test_case::test_case(123.0, 123.5, false; "when positive and false")]
277    #[test_case::test_case(-1234.0, -1234.4, true; "when negative and true")]
278    #[test_case::test_case(-10.0, -10.5, false; "when negative and false")]
279    #[test_case::test_case(-1.0, 0.3, false; "when different and true")]
280    #[test_case::test_case(-0.25, 0.25, false; "when different and false")]
281    fn test_equal(x: f32, y: f32, eq: bool) {
282        assert_eq!(equal(x, y), eq);
283    }
284
285    /// Test the [intersects] function.
286    #[test_case::test_case(100.0, 500.0, true; "when point is on border")]
287    #[test_case::test_case(150.0, 550.0, true; "when point is inside")]
288    #[test_case::test_case(700.0, 700.0, false; "when point is outside")]
289    fn test_intersects(x: f32, y: f32, eq: bool) {
290        let layout = Layout {
291            location: Point { x: 100.0, y: 500.0 },
292            size: Size {
293                width: 500.0,
294                height: 100.0,
295            },
296            ..Default::default()
297        };
298
299        assert_eq!(intersects(Vector2::new(x, y), &layout), eq);
300    }
301}