From 437afa87787b8c913b587a49be3c8c6fdb33e78c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc=20Esp=C3=ADn?= Date: Thu, 26 Oct 2023 23:07:52 +0200 Subject: [PATCH] feat: Enhanced layout alignments (#330) * feat: WIP Enhanced alignments * improvements * tweak * remove display attribute, remove 'both' direction, fix tests, fix docs, fix examples, fix components * feat: Updated examples * tests * fixed tests * update table component and example * clean up * clean up * improvements * improvements * fixes * fix tests * tweak * tweaks * unsized alignment test * document code * tweak * rename attributes * fixes and cleanup * fixes and improvements * typo * feat: Easily change the cursor with use_platform (#352) * fix: mouseenter wasn't being emitted when mouseover was emitted at the same time (#351) * Revert "feat: Easily change the cursor with use_platform (#352)" (#357) This reverts commit f801af8ab0cc71a63f656383da127699a2ddd0ec. * Revert "fix: mouseenter wasn't being emitted when mouseover was emitted at the same time (#351)" (#358) This reverts commit b5fb10f0255a844c3cf519989e99dad6ff090bbb. * clean up examples --- book/src/guides/layout.md | 28 +- book/src/guides/testing.md | 1 - crates/components/src/button.rs | 43 ++- crates/components/src/external_link.rs | 1 - crates/components/src/input.rs | 56 ++-- crates/components/src/network_image.rs | 7 +- crates/components/src/progress_bar.rs | 3 +- crates/components/src/slider.rs | 4 +- crates/components/src/switch.rs | 1 - crates/components/src/table.rs | 3 +- crates/components/src/tooltip.rs | 5 +- crates/core/src/node.rs | 35 ++- crates/core/src/render.rs | 2 +- crates/devtools/src/tabs/layout.rs | 24 +- crates/devtools/src/tabs/style.rs | 4 +- crates/dom/src/dom_adapter.rs | 3 +- crates/elements/src/elements.rs | 3 +- crates/hooks/src/use_theme.rs | 9 +- crates/state/src/layout.rs | 23 +- crates/state/src/values/alignment.rs | 17 ++ crates/state/src/values/display.rs | 16 -- crates/state/src/values/mod.rs | 4 +- crates/state/tests/parse_display.rs | 26 +- crates/testing/tests/test.rs | 1 - crates/torin/src/dom_adapter.rs | 8 +- crates/torin/src/geometry.rs | 91 ++++++- crates/torin/src/lib.rs | 2 + crates/torin/src/measure.rs | 312 +++++++++++++++++++++ crates/torin/src/measure_mode.rs | 215 +++++++++++++++ crates/torin/src/node.rs | 56 +++- crates/torin/src/torin.rs | 358 +------------------------ crates/torin/src/values/alignment.rs | 17 ++ crates/torin/src/values/direction.rs | 2 - crates/torin/src/values/display.rs | 15 -- crates/torin/src/values/mod.rs | 4 +- crates/torin/src/values/size.rs | 13 +- crates/torin/tests/test.rs | 273 ++++++++++++++++++- examples/alignment.rs | 85 ++++++ examples/animation.rs | 9 +- examples/app_dog.rs | 11 +- examples/canvas.rs | 165 +++++------- examples/cloned_editor.rs | 4 +- examples/counter.rs | 35 ++- examples/custom_font.rs | 3 +- examples/custom_theme.rs | 1 + examples/drag.rs | 1 - examples/editor.rs | 63 ++--- examples/frameless_window.rs | 4 +- examples/gesture_area.rs | 2 +- examples/i18n.rs | 4 +- examples/image.rs | 4 +- examples/margin.rs | 24 +- examples/material_design_app.rs | 6 +- examples/mouse_trace.rs | 8 +- examples/paragraph.rs | 3 +- examples/rotate.rs | 12 +- examples/sidebar.rs | 3 +- examples/table.rs | 2 - examples/text.rs | 5 +- examples/tic_tac_toe.rs | 8 +- examples/touch.rs | 4 +- examples/transition.rs | 4 +- 62 files changed, 1379 insertions(+), 776 deletions(-) create mode 100644 crates/state/src/values/alignment.rs delete mode 100644 crates/state/src/values/display.rs create mode 100644 crates/torin/src/measure.rs create mode 100644 crates/torin/src/measure_mode.rs create mode 100644 crates/torin/src/values/alignment.rs delete mode 100644 crates/torin/src/values/display.rs create mode 100644 examples/alignment.rs diff --git a/book/src/guides/layout.md b/book/src/guides/layout.md index bec69624c..f9fdcf1e1 100644 --- a/book/src/guides/layout.md +++ b/book/src/guides/layout.md @@ -2,9 +2,9 @@ Learn how the layout attributes work. -- [`width & height`](#width_&_height) -- [`min_width & min_height`](#min_width_&_min_height) -- [`max_width & max_height`](#max_width_&_max_height) +- [`width & height`](#width--height) +- [`min_width & min_height`](#min_width--min_height) +- [`max_width & max_height`](#max_width--max_height) - [`Size units`](#size_units) - [`Logical pixels`](#logical-pixels) - [`Percentages`](#percentages) @@ -12,7 +12,7 @@ Learn how the layout attributes work. - [`direction`](#direction) - [`padding`](#padding) - [`margin`](#margin) -- [`display`](#display) +- [`main_align & cross_align`](#main_align--cross_align) > ⚠️ Freya's layout is still somewhat limited. @@ -125,7 +125,7 @@ fn app(cx: Scope) -> Element { ### direction -Control how the inner elements will be stacked, possible values are `horizontal`, `vertical` (default) or `both` (default for text elements, e.g label, paragraph, text, etc). +Control how the inner elements will be stacked, possible values are `vertical` (default) and `horizontal`. ##### Usage @@ -168,9 +168,19 @@ fn app(cx: Scope) -> Element { ``` -### display +### main_align & cross_align -Control how the inner elements are displayed, possible values are `normal` (default) or `center`. +Control how the inner elements are positioned inside the element. You can combine it with the `direction` attribute to create complex flows. + +Possible values for both attributes are: +- `start` (default): At the begining of the axis +- `center`: At the center of the axis +- `end`: At the end of the axis + +When using the `vertical` direction, `main_align` will be the Y axis and `cross_align` will be the X axis. But when using the `horizontal` direction, the +`main_align` will be the X axis and the `cross_align` will be the Y axis. + +Example on how to center the inner elements in both axis: ```rust, no_run fn app(cx: Scope) -> Element { @@ -178,8 +188,8 @@ fn app(cx: Scope) -> Element { rect { width: "100%", height: "100%", - direction: "both", - display: "center", + main_align: "center", + cross_align: "center", rect { width: "50%", height: "50%", diff --git a/book/src/guides/testing.md b/book/src/guides/testing.md index 3f9a31129..aa2d05d9e 100644 --- a/book/src/guides/testing.md +++ b/book/src/guides/testing.md @@ -84,7 +84,6 @@ async fn event_test() { width: "100%", height: "100%", background: "red", - direction: "both", onclick: |_| { enabled.set(true); }, diff --git a/crates/components/src/button.rs b/crates/components/src/button.rs index cef514f0c..888362b60 100644 --- a/crates/components/src/button.rs +++ b/crates/components/src/button.rs @@ -6,6 +6,21 @@ use freya_hooks::{use_focus, use_get_theme}; /// [`Button`] component properties. #[derive(Props)] pub struct ButtonProps<'a> { + /// Padding for the Button. + #[props(default = "10 14".to_string(), into)] + pub padding: String, + /// Margin for the Button. + #[props(default = "4".to_string(), into)] + pub margin: String, + /// Corner radius for the Button. + #[props(default = "10".to_string(), into)] + pub corner_radius: String, + /// Width size for the Button. + #[props(default = "auto".to_string(), into)] + pub width: String, + /// Inner children for the Button. + #[props(default = "auto".to_string(), into)] + pub height: String, /// Inner children for the Button. pub children: Element<'a>, /// Handler for the `onclick` event. @@ -75,25 +90,37 @@ pub fn Button<'a>(cx: Scope<'a, ButtonProps<'a>>) -> Element { ButtonStatus::Idle => theme.button.background, }; let color = theme.button.font_theme.color; + let border_fill = theme.button.border_fill; + let ButtonProps { + width, + height, + corner_radius, + padding, + margin, + .. + } = &cx.props; render!( rect { - overflow: "clip", - margin: "2", onclick: onclick, onmouseenter: onmouseenter, onmouseleave: onmouseleave, focus_id: focus_id, + width: "{width}", + height: "{height}", + padding: "{padding}", + margin: "{margin}", focusable: "true", + overflow: "clip", role: "button", - width: "auto", - height: "auto", - direction: "both", color: "{color}", - shadow: "0 2 10 1 rgb(0, 0, 0, 45)", - corner_radius: "5", - padding: "8", + shadow: "0 4 5 0 rgb(0, 0, 0, 30)", + border: "1 solid {border_fill}", + corner_radius: "{corner_radius}", background: "{background}", + align: "center", + main_align: "center", + cross_align: "center", &cx.props.children } ) diff --git a/crates/components/src/external_link.rs b/crates/components/src/external_link.rs index ba35751d6..bd7e1e5c9 100644 --- a/crates/components/src/external_link.rs +++ b/crates/components/src/external_link.rs @@ -74,7 +74,6 @@ pub fn ExternalLink<'a>(cx: Scope<'a, ExternalLinkProps<'a>>) -> Element { render!( rect { - direction: "both", onmouseover: onmouseover, onmouseleave: onmouseleave, onclick: onclick, diff --git a/crates/components/src/input.rs b/crates/components/src/input.rs index 4d63045b8..ec78aef21 100644 --- a/crates/components/src/input.rs +++ b/crates/components/src/input.rs @@ -38,7 +38,7 @@ pub struct InputProps<'a> { #[props(default = "150".to_string(), into)] width: String, /// Height of the Input. Default 100. - #[props(default = "35".to_string(), into)] + #[props(default = "38".to_string(), into)] height: String, /// Max lines for the Input. Default 1. #[props(default = "1".to_string(), into)] @@ -151,36 +151,30 @@ pub fn Input<'a>(cx: Scope<'a, InputProps<'a>>) -> Element { rect { onkeydown: onkeydown, onclick: onclick, - width: "auto", - height: "auto", - direction: "both", - padding: "1.5", - rect { - width: "{width}", - height: "{height}", - direction: "vertical", - display: "center", - color: "{color}", - background: "{background}", - shadow: "0 3 15 0 rgb(0, 0, 0, 70)", - corner_radius: "5", - padding: "8", - cursor_reference: cursor_attr, - ScrollView { - scroll_with_arrows: false, - paragraph { - width: "100%", - cursor_id: "0", - cursor_index: "{cursor_char}", - cursor_mode: "editable", - cursor_color: "{color}", - max_lines: "{max_lines}", - onmouseover: onmouseover, - onmousedown: onmousedown, - highlights: highlights_attr, - text { - "{text}" - } + width: "{width}", + height: "{height}", + direction: "vertical", + color: "{color}", + background: "{background}", + shadow: "0 3 15 0 rgb(0, 0, 0, 70)", + corner_radius: "5", + padding: "8", + margin: "4", + cursor_reference: cursor_attr, + ScrollView { + scroll_with_arrows: false, + paragraph { + width: "100%", + cursor_id: "0", + cursor_index: "{cursor_char}", + cursor_mode: "editable", + cursor_color: "{color}", + max_lines: "{max_lines}", + onmouseover: onmouseover, + onmousedown: onmousedown, + highlights: highlights_attr, + text { + "{text}" } } } diff --git a/crates/components/src/network_image.rs b/crates/components/src/network_image.rs index b52ffbef6..13ce7c260 100644 --- a/crates/components/src/network_image.rs +++ b/crates/components/src/network_image.rs @@ -99,8 +99,8 @@ pub fn NetworkImage<'a>(cx: Scope<'a, NetworkImageProps<'a>>) -> Element<'a> { rect { height: "{height}", width: "{width}", - display: "center", - direction: "both", + main_align: "center", + cross_align: "center", Loader { } @@ -115,7 +115,8 @@ pub fn NetworkImage<'a>(cx: Scope<'a, NetworkImageProps<'a>>) -> Element<'a> { rect { height: "{height}", width: "{width}", - display: "center", + main_align: "center", + cross_align: "center", label { align: "center", "Error" diff --git a/crates/components/src/progress_bar.rs b/crates/components/src/progress_bar.rs index d2a217399..a43aa1b27 100644 --- a/crates/components/src/progress_bar.rs +++ b/crates/components/src/progress_bar.rs @@ -73,7 +73,8 @@ pub fn ProgressBar(cx: Scope) -> Element { width: "{progress}%", height: "100%", background: "{progress_background}", - display: "center", + main_align: "center", + cross_align: "center", overflow: "clip", if show_progress { rsx!( diff --git a/crates/components/src/slider.rs b/crates/components/src/slider.rs index e54c66235..97ad7c309 100644 --- a/crates/components/src/slider.rs +++ b/crates/components/src/slider.rs @@ -122,8 +122,8 @@ pub fn Slider<'a>(cx: Scope<'a, SliderProps>) -> Element<'a> { onglobalmouseover: onmouseover, onmouseleave: onmouseleave, onwheel: onwheel, - display: "center", - direction: "both", + main_align: "center", + cross_align: "center", padding: "1", rect { background: "{theme.background}", diff --git a/crates/components/src/switch.rs b/crates/components/src/switch.rs index 31ce004b1..ea72aa8bd 100644 --- a/crates/components/src/switch.rs +++ b/crates/components/src/switch.rs @@ -108,7 +108,6 @@ pub fn Switch<'a>(cx: Scope<'a, SwitchProps<'a>>) -> Element<'a> { corner_radius: "50", rect { background: "{circle}", - direction: "both", width: "18", height: "18", corner_radius: "50", diff --git a/crates/components/src/table.rs b/crates/components/src/table.rs index dee8fea24..da75853f5 100644 --- a/crates/components/src/table.rs +++ b/crates/components/src/table.rs @@ -154,7 +154,8 @@ pub fn TableCell<'a>(cx: Scope<'a, TableCellProps<'a>>) -> Element { overflow: "clip", padding: "5 25", width: "{width}%", - display: "center", + main_align: "center", + cross_align: "center", height: "35", align: "right", onclick: |e| { diff --git a/crates/components/src/tooltip.rs b/crates/components/src/tooltip.rs index 757103de4..18b731fd4 100644 --- a/crates/components/src/tooltip.rs +++ b/crates/components/src/tooltip.rs @@ -27,15 +27,14 @@ pub fn Tooltip<'a>(cx: Scope<'a, TooltipProps<'a>>) -> Element { height: "30", padding: "2", width: "170", - direction: "both", rect { - direction: "both", width: "100%", height: "100%", shadow: "0 0 10 5 rgb(0, 0, 0, 50)", corner_radius: "8", background: "{background}", - display: "center", + main_align: "center", + cross_align: "center", label { max_lines: "1", color: "{color}", diff --git a/crates/core/src/node.rs b/crates/core/src/node.rs index 7d8d4c103..6a1aaedc7 100644 --- a/crates/core/src/node.rs +++ b/crates/core/src/node.rs @@ -5,7 +5,7 @@ use freya_node_state::{ Border, CornerRadius, CursorSettings, Fill, FontStyleState, LayoutState, References, Shadow, Style, Transform, }; -use torin::{direction::DirectionMode, display::DisplayMode, gaps::Gaps, size::Size}; +use torin::{alignment::Alignment, direction::DirectionMode, gaps::Gaps, size::Size}; #[derive(Clone)] pub struct NodeState { @@ -77,8 +77,15 @@ impl<'a> Iterator for NodeStateIterator<'a> { AttributeType::Direction(&self.state.size.direction), )), 7 => Some(("padding", AttributeType::Measures(self.state.size.padding))), - 8 => Some(("display", AttributeType::Display(&self.state.size.display))), - 9 => { + 8 => Some(( + "main_alignment", + AttributeType::Alignment(&self.state.size.main_alignment), + )), + 9 => Some(( + "cross_alignment", + AttributeType::Alignment(&self.state.size.cross_alignment), + )), + 10 => { let background = &self.state.style.background; let fill = match *background { Fill::Color(_) => AttributeType::Color(background.clone()), @@ -86,33 +93,33 @@ impl<'a> Iterator for NodeStateIterator<'a> { }; Some(("background", fill)) } - 10 => Some(("border", AttributeType::Border(&self.state.style.border))), - 11 => Some(( + 11 => Some(("border", AttributeType::Border(&self.state.style.border))), + 12 => Some(( "corner_radius", AttributeType::CornerRadius(self.state.style.corner_radius), )), - 12 => Some(( + 13 => Some(( "color", AttributeType::Color(self.state.font_style.color.into()), )), - 13 => Some(( + 14 => Some(( "font_family", AttributeType::Text(self.state.font_style.font_family.join(",")), )), - 14 => Some(( + 15 => Some(( "font_size", AttributeType::Measure(self.state.font_style.font_size), )), - 15 => Some(( + 16 => Some(( "line_height", AttributeType::Measure(self.state.font_style.line_height), )), - 16 => Some(("offset_x", AttributeType::Measure(self.state.size.offset_x))), - 17 => Some(("offset_y", AttributeType::Measure(self.state.size.offset_y))), + 17 => Some(("offset_x", AttributeType::Measure(self.state.size.offset_x))), + 18 => Some(("offset_y", AttributeType::Measure(self.state.size.offset_y))), n => { let shadows = &self.state.style.shadows; let shadow = shadows - .get(n - 18) + .get(n - 19) .map(|shadow| ("shadow", AttributeType::Shadow(shadow))); if shadow.is_some() { @@ -120,7 +127,7 @@ impl<'a> Iterator for NodeStateIterator<'a> { } else { let text_shadows = &self.state.font_style.text_shadows; text_shadows - .get(n - 18 + shadows.len()) + .get(n - 19 + shadows.len()) .map(|text_shadow| ("text_shadow", AttributeType::TextShadow(text_shadow))) } } @@ -143,7 +150,7 @@ pub enum AttributeType<'a> { Measures(Gaps), CornerRadius(CornerRadius), Direction(&'a DirectionMode), - Display(&'a DisplayMode), + Alignment(&'a Alignment), Shadow(&'a Shadow), TextShadow(&'a TextShadow), Text(String), diff --git a/crates/core/src/render.rs b/crates/core/src/render.rs index dc4501b95..37519df12 100644 --- a/crates/core/src/render.rs +++ b/crates/core/src/render.rs @@ -52,7 +52,7 @@ pub fn process_render( render_hook( dom, node_id, - &areas.box_area(), + &areas.visible_area(), font_collection, viewports_collection, hook_options, diff --git a/crates/devtools/src/tabs/layout.rs b/crates/devtools/src/tabs/layout.rs index 636cfb7d3..90e1e2209 100644 --- a/crates/devtools/src/tabs/layout.rs +++ b/crates/devtools/src/tabs/layout.rs @@ -42,8 +42,8 @@ pub fn NodeInspectorLayout(cx: Scope, node_id: NodeId) -> Element { rect { width: "100%", height: "calc(100% - 25)", - display: "center", - direction: "both", + main_align: "center", + cross_align: "center", background: "rgb(40, 40, 40)", rect { width: "100%", @@ -51,8 +51,8 @@ pub fn NodeInspectorLayout(cx: Scope, node_id: NodeId) -> Element { background: "rgb(71, 180, 240)", corner_radius: "5", rect { - direction: "both", - display: "center", + main_align: "center", + cross_align: "center", width: "100%", height: "25", label { @@ -66,8 +66,8 @@ pub fn NodeInspectorLayout(cx: Scope, node_id: NodeId) -> Element { height: "calc(100% - 50)", direction: "horizontal", rect { - direction: "vertical", - display: "center", + main_align: "center", + cross_align: "center", width: "25", height: "100%", label { @@ -79,8 +79,8 @@ pub fn NodeInspectorLayout(cx: Scope, node_id: NodeId) -> Element { rect { width: "calc(100% - 50)", height: "100%", - display: "center", - direction: "both", + main_align: "center", + cross_align: "center", background: "rgb(40, 40, 40)", corner_radius: "5", label { @@ -88,8 +88,8 @@ pub fn NodeInspectorLayout(cx: Scope, node_id: NodeId) -> Element { } } rect { - direction: "vertical", - display: "center", + main_align: "center", + cross_align: "center", width: "25", height: "100%", label { @@ -100,8 +100,8 @@ pub fn NodeInspectorLayout(cx: Scope, node_id: NodeId) -> Element { } } rect { - direction: "both", - display: "center", + main_align: "center", + cross_align: "center", width: "100%", height: "25", label { diff --git a/crates/devtools/src/tabs/style.rs b/crates/devtools/src/tabs/style.rs index da54b1f50..fe1758324 100644 --- a/crates/devtools/src/tabs/style.rs +++ b/crates/devtools/src/tabs/style.rs @@ -114,12 +114,12 @@ pub fn NodeInspectorStyle(cx: Scope, node_id: NodeId) -> Element { } } } - AttributeType::Display(display) => { + AttributeType::Alignment(alignment) => { rsx!{ Property { key: "{i}", name: "{name}", - value: display.pretty() + value: alignment.pretty() } } } diff --git a/crates/dom/src/dom_adapter.rs b/crates/dom/src/dom_adapter.rs index e97279bb4..dbd95c85a 100644 --- a/crates/dom/src/dom_adapter.rs +++ b/crates/dom/src/dom_adapter.rs @@ -49,7 +49,8 @@ impl DOMAdapter for DioxusDOMAdapter<'_> { direction: size.direction, padding: size.padding, margin: size.margin, - display: size.display, + main_alignment: size.main_alignment, + cross_alignment: size.cross_alignment, offset_x: Length::new(size.offset_x), offset_y: Length::new(size.offset_y), has_layout_references: size.node_ref.is_some(), diff --git a/crates/elements/src/elements.rs b/crates/elements/src/elements.rs index b34f1a94c..e61c7bba0 100644 --- a/crates/elements/src/elements.rs +++ b/crates/elements/src/elements.rs @@ -177,8 +177,9 @@ builder_constructors! { font_style: String, font_weight: String, font_width: String, + main_align: String, + cross_align: String, align: String, - display: String, reference: Reference, cursor_reference: CursorReference, rotate: String, diff --git a/crates/hooks/src/use_theme.rs b/crates/hooks/src/use_theme.rs index d5ad94f77..39f530102 100644 --- a/crates/hooks/src/use_theme.rs +++ b/crates/hooks/src/use_theme.rs @@ -49,6 +49,7 @@ pub struct ButtonTheme { pub background: &'static str, pub hover_background: &'static str, pub font_theme: FontTheme, + pub border_fill: &'static str, } /// Theming properties for Fonts. @@ -174,11 +175,12 @@ pub const LIGHT_THEME: Theme = Theme { thumb_inner_background: "rgb(103, 80, 164)", }, button: ButtonTheme { - background: "rgb(220, 220, 220)", - hover_background: "rgb(200, 200, 200)", + background: "rgb(245, 245, 245)", + hover_background: "rgb(235, 235, 235)", font_theme: FontTheme { color: "rgb(10, 10, 10)", }, + border_fill: "rgb(210, 210, 210)", }, switch: SwitchTheme { background: "rgb(121, 116, 126)", @@ -252,8 +254,9 @@ pub const DARK_THEME: Theme = Theme { }, button: ButtonTheme { background: "rgb(35, 35, 35)", - hover_background: "rgb(80, 80, 80)", + hover_background: "rgb(45, 45, 45)", font_theme: FontTheme { color: "white" }, + border_fill: "rgb(80, 80, 80)", }, switch: SwitchTheme { background: "rgb(60, 60, 60)", diff --git a/crates/state/src/layout.rs b/crates/state/src/layout.rs index 3ace17f4b..0870e90cc 100644 --- a/crates/state/src/layout.rs +++ b/crates/state/src/layout.rs @@ -28,7 +28,8 @@ pub struct LayoutState { pub node_id: NodeId, pub offset_y: f32, pub offset_x: f32, - pub display: DisplayMode, + pub main_alignment: Alignment, + pub cross_alignment: Alignment, pub node_ref: Option>, } @@ -52,7 +53,8 @@ impl State for LayoutState { "direction", "offset_y", "offset_x", - "display", + "main_align", + "cross_align", "reference", "margin", ])) @@ -156,7 +158,6 @@ impl State for LayoutState { if let Some(value) = attr.value.as_text() { layout.direction = match value { "horizontal" => DirectionMode::Horizontal, - "both" => DirectionMode::Both, _ => DirectionMode::Vertical, } } @@ -175,10 +176,17 @@ impl State for LayoutState { } } } - "display" => { + "main_align" => { if let Some(value) = attr.value.as_text() { - if let Ok(display) = DisplayMode::parse(value) { - layout.display = display; + if let Ok(alignment) = Alignment::parse(value) { + layout.main_alignment = alignment; + } + } + } + "cross_align" => { + if let Some(value) = attr.value.as_text() { + if let Ok(alignment) = Alignment::parse(value) { + layout.cross_alignment = alignment; } } } @@ -208,7 +216,8 @@ impl State for LayoutState { || (layout.direction != self.direction) || (layout.offset_x != self.offset_x) || (layout.offset_y != self.offset_y) - || (layout.display != self.display); + || (layout.main_alignment != self.main_alignment) + || (layout.cross_alignment != self.cross_alignment); if changed { torin_layout.lock().unwrap().invalidate(node_view.node_id()); diff --git a/crates/state/src/values/alignment.rs b/crates/state/src/values/alignment.rs new file mode 100644 index 000000000..3bcf3fb4c --- /dev/null +++ b/crates/state/src/values/alignment.rs @@ -0,0 +1,17 @@ +use crate::Parse; +use torin::alignment::Alignment; + +#[derive(Debug, PartialEq, Eq)] +pub struct ParseAlignmentError; + +impl Parse for Alignment { + type Err = ParseAlignmentError; + + fn parse(value: &str) -> Result { + Ok(match value { + "center" => Alignment::Center, + "end" => Alignment::End, + _ => Alignment::Start, + }) + } +} diff --git a/crates/state/src/values/display.rs b/crates/state/src/values/display.rs deleted file mode 100644 index 083c6880e..000000000 --- a/crates/state/src/values/display.rs +++ /dev/null @@ -1,16 +0,0 @@ -use crate::Parse; -use torin::display::DisplayMode; - -#[derive(Debug, PartialEq, Eq)] -pub struct ParseDisplayModeError; - -impl Parse for DisplayMode { - type Err = ParseDisplayModeError; - - fn parse(value: &str) -> Result { - Ok(match value { - "center" => DisplayMode::Center, - _ => DisplayMode::Normal, - }) - } -} diff --git a/crates/state/src/values/mod.rs b/crates/state/src/values/mod.rs index 9a7c8a2fc..3891a5e41 100644 --- a/crates/state/src/values/mod.rs +++ b/crates/state/src/values/mod.rs @@ -1,10 +1,10 @@ mod accessibility; +mod alignment; mod border; mod color; mod corner_radius; mod cursor; mod decoration; -mod display; mod fill; mod font; mod gaps; @@ -15,12 +15,12 @@ mod size; mod text_shadow; pub use accessibility::*; +pub use alignment::*; pub use border::*; pub use color::*; pub use corner_radius::*; pub use cursor::*; pub use decoration::*; -pub use display::*; pub use fill::*; pub use font::*; pub use gaps::*; diff --git a/crates/state/tests/parse_display.rs b/crates/state/tests/parse_display.rs index c475adc3d..47960e2a0 100644 --- a/crates/state/tests/parse_display.rs +++ b/crates/state/tests/parse_display.rs @@ -1,20 +1,26 @@ use freya_node_state::Parse; -use torin::display::DisplayMode; +use torin::alignment::Alignment; #[test] -fn parse_normal_display() { - let display = DisplayMode::parse("normal"); - assert_eq!(display, Ok(DisplayMode::Normal)); +fn parse_normal_alignment() { + let alignment = Alignment::parse("start"); + assert_eq!(alignment, Ok(Alignment::Start)); } #[test] -fn parse_center_display() { - let display = DisplayMode::parse("center"); - assert_eq!(display, Ok(DisplayMode::Center)); +fn parse_center_alignment() { + let alignment = Alignment::parse("center"); + assert_eq!(alignment, Ok(Alignment::Center)); } #[test] -fn parse_fallback_display() { - let display = DisplayMode::parse("freya!!"); - assert_eq!(display, Ok(DisplayMode::Normal)); +fn parse_end_alignment() { + let alignment = Alignment::parse("end"); + assert_eq!(alignment, Ok(Alignment::End)); +} + +#[test] +fn parse_fallback_alignment() { + let alignment = Alignment::parse("Hello, World!"); + assert_eq!(alignment, Ok(Alignment::Start)); } diff --git a/crates/testing/tests/test.rs b/crates/testing/tests/test.rs index ca00f5533..182a27ed6 100644 --- a/crates/testing/tests/test.rs +++ b/crates/testing/tests/test.rs @@ -75,7 +75,6 @@ async fn simulate_events() { width: "100%", height: "100%", background: "red", - direction: "both", onclick: |_| { enabled.set(true); }, diff --git a/crates/torin/src/dom_adapter.rs b/crates/torin/src/dom_adapter.rs index 965a24c4d..98e94c004 100644 --- a/crates/torin/src/dom_adapter.rs +++ b/crates/torin/src/dom_adapter.rs @@ -3,7 +3,7 @@ pub use euclid::Rect; use crate::{ geometry::{Area, Size2D}, node::Node, - prelude::{BoxModel, Gaps}, + prelude::{AreaModel, Gaps}, }; /// Cached layout results of a Node @@ -23,9 +23,9 @@ pub struct NodeAreas { } impl NodeAreas { - // The area without any outer gap (e.g margin) - pub fn box_area(&self) -> Area { - self.area.box_area(&self.margin) + // The area without any margin + pub fn visible_area(&self) -> Area { + self.area.after_gaps(&self.margin) } } diff --git a/crates/torin/src/geometry.rs b/crates/torin/src/geometry.rs index 98735c3b7..b7f5f61d7 100644 --- a/crates/torin/src/geometry.rs +++ b/crates/torin/src/geometry.rs @@ -1,4 +1,4 @@ -use crate::prelude::Gaps; +use crate::prelude::{Alignment, DirectionMode, Gaps}; #[derive(PartialEq)] pub struct Measure; @@ -9,13 +9,25 @@ pub type Point2D = euclid::Point2D; pub type CursorPoint = euclid::Point2D; pub type Length = euclid::Length; -pub trait BoxModel { +pub trait AreaModel { // The area without any outer gap (e.g margin) - fn box_area(&self, margin: &Gaps) -> Area; + fn after_gaps(&self, margin: &Gaps) -> Area; + + fn move_with_offsets(&mut self, offset_x: &Length, offset_y: &Length); + + fn align_content( + &mut self, + available_area: &Area, + contents_area: &Size2D, + alignment: &Alignment, + direction: &DirectionMode, + alignment_direction: AlignmentDirection, + ); } -impl BoxModel for Area { - fn box_area(&self, margin: &Gaps) -> Area { +impl AreaModel for Area { + /// Get the area inside after including the gaps (margins or paddings) + fn after_gaps(&self, margin: &Gaps) -> Area { let origin = self.origin; let size = self.size; Area::new( @@ -26,4 +38,73 @@ impl BoxModel for Area { ), ) } + + /// Get the area inside after including the gaps (margins or paddings) + fn move_with_offsets(&mut self, offset_x: &Length, offset_y: &Length) { + self.origin.x += offset_x.get(); + self.origin.y += offset_y.get(); + } + + fn align_content( + &mut self, + available_area: &Area, + contents_size: &Size2D, + alignment: &Alignment, + direction: &DirectionMode, + alignment_direction: AlignmentDirection, + ) { + let axis = get_align_axis(direction, alignment_direction); + + match axis { + AlignAxis::Height => match alignment { + Alignment::Center => { + let new_origin_y = + (available_area.height() / 2.0) - (contents_size.height / 2.0); + + self.origin.y = available_area.min_y() + new_origin_y; + } + Alignment::End => { + self.origin.y = available_area.max_y() - contents_size.height; + } + _ => {} + }, + AlignAxis::Width => match alignment { + Alignment::Center => { + let new_origin_x = (available_area.width() / 2.0) - (contents_size.width / 2.0); + + self.origin.x = available_area.min_x() + new_origin_x; + } + Alignment::End => { + self.origin.x = available_area.max_x() - contents_size.width; + } + _ => {} + }, + } + } +} + +pub fn get_align_axis( + direction: &DirectionMode, + alignment_direction: AlignmentDirection, +) -> AlignAxis { + match direction { + DirectionMode::Vertical => match alignment_direction { + AlignmentDirection::Main => AlignAxis::Height, + AlignmentDirection::Cross => AlignAxis::Width, + }, + DirectionMode::Horizontal => match alignment_direction { + AlignmentDirection::Main => AlignAxis::Width, + AlignmentDirection::Cross => AlignAxis::Height, + }, + } +} + +pub enum AlignmentDirection { + Main, + Cross, +} + +pub enum AlignAxis { + Height, + Width, } diff --git a/crates/torin/src/lib.rs b/crates/torin/src/lib.rs index cd26c9db1..688e9ccc5 100644 --- a/crates/torin/src/lib.rs +++ b/crates/torin/src/lib.rs @@ -1,6 +1,8 @@ pub mod custom_measurer; pub mod dom_adapter; pub mod geometry; +mod measure; +mod measure_mode; pub mod node; pub mod scaled; pub mod torin; diff --git a/crates/torin/src/measure.rs b/crates/torin/src/measure.rs new file mode 100644 index 000000000..20f87eae6 --- /dev/null +++ b/crates/torin/src/measure.rs @@ -0,0 +1,312 @@ +pub use euclid::Rect; + +use crate::{ + custom_measurer::LayoutMeasurer, + dom_adapter::{DOMAdapter, NodeAreas, NodeKey}, + geometry::{Area, Size2D}, + measure_mode::MeasureMode, + node::Node, + prelude::{AlignmentDirection, AreaModel, Torin}, + size::Size, +}; + +/// Measure a Node layout +#[allow(clippy::too_many_arguments)] +#[inline(always)] +pub fn measure_node( + node_id: Key, + node: &Node, + layout: &mut Torin, + // Area occupied by it's parent + parent_area: &Area, + // Area that is available to use by the children of the parent + available_parent_area: &Area, + measurer: &mut Option>, + // Whether to cache the measurements of this Node's children + must_cache_inner_nodes: bool, + // Adapter for the provided DOM + dom_adapter: &mut impl DOMAdapter, +) -> (bool, NodeAreas) { + let must_run = layout.dirty.contains(&node_id) || layout.results.get(&node_id).is_none(); + if must_run { + // 1. Create the initial Node area + let mut area = Rect::new( + available_parent_area.origin, + Size2D::new(node.padding.horizontal(), node.padding.vertical()), + ); + + // 2. Compute the width and height given the size, the minimum size, the maximum size and margins + area.size.width = node.width.min_max( + area.size.width, + parent_area.size.width, + node.margin.left(), + node.margin.horizontal(), + &node.minimum_width, + &node.maximum_width, + ); + area.size.height = node.height.min_max( + area.size.height, + parent_area.size.height, + node.margin.top(), + node.margin.vertical(), + &node.minimum_height, + &node.maximum_height, + ); + + // 3. If available, run a custom layout measure function + // This is useful when you use third-party libraries (e.g. rust-skia, cosmic-text) to measure text layouts + // When a Node is measured by a custom measurer function the inner children will be skipped + let measure_inner_children = if let Some(measurer) = measurer { + let custom_area = + measurer.measure(node_id, node, &area, parent_area, available_parent_area); + + // 3.1. Compute the width and height again using the new custom area sizes + if let Some(custom_area) = custom_area { + if Size::Inner == node.width { + area.size.width = node.width.min_max( + custom_area.width(), + parent_area.size.width, + node.margin.left(), + node.margin.horizontal(), + &node.minimum_width, + &node.maximum_width, + ); + } + if Size::Inner == node.height { + area.size.height = node.height.min_max( + custom_area.height(), + parent_area.size.height, + node.margin.top(), + node.margin.vertical(), + &node.minimum_height, + &node.maximum_height, + ); + } + } + + // Do not measure inner children + custom_area.is_none() + } else { + true + }; + + // 4. Compute the inner area of the Node, which is basically the area inside the margins and paddings + let mut inner_area = { + let mut inner_area = area; + + // 4.1. When having an unsized bound we set it to whatever is still available in the parent's area + if Size::Inner == node.width { + inner_area.size.width = node.width.min_max( + available_parent_area.width(), + parent_area.size.width, + node.margin.left(), + node.margin.horizontal(), + &node.minimum_width, + &node.maximum_width, + ); + } + if Size::Inner == node.height { + inner_area.size.height = node.height.min_max( + available_parent_area.height(), + parent_area.size.height, + node.margin.top(), + node.margin.vertical(), + &node.minimum_height, + &node.maximum_height, + ); + } + + inner_area + .after_gaps(&node.padding) + .after_gaps(&node.margin) + }; + + let mut inner_sizes = Size2D::default(); + + if measure_inner_children { + // 5. Create an area containing the available space inside the inner area + let mut available_area = inner_area; + + // 5.1. Adjust the available area with the node offsets (mainly used by scrollviews) + available_area.move_with_offsets(&node.offset_x, &node.offset_y); + + let mut measurement_mode = MeasureMode::ParentIsNotCached { + area: &mut area, + inner_area: &mut inner_area, + }; + + // 6. Measure the layout of this Node's children + measure_inner_nodes( + &node_id, + node, + layout, + &mut available_area, + &mut inner_sizes, + measurer, + must_cache_inner_nodes, + &mut measurement_mode, + dom_adapter, + ); + } + + ( + must_cache_inner_nodes, + NodeAreas { + area, + margin: node.margin, + inner_area, + inner_sizes, + }, + ) + } else { + let areas = layout.get(node_id).unwrap().clone(); + + let mut inner_sizes = areas.inner_sizes; + let mut available_area = areas.inner_area; + + available_area.move_with_offsets(&node.offset_x, &node.offset_y); + + let mut measurement_mode = MeasureMode::ParentIsCached { + inner_area: &areas.inner_area, + }; + + measure_inner_nodes( + &node_id, + node, + layout, + &mut available_area, + &mut inner_sizes, + measurer, + must_cache_inner_nodes, + &mut measurement_mode, + dom_adapter, + ); + + (false, areas) + } +} + +/// Measure the children layouts of a Node +#[allow(clippy::too_many_arguments)] +#[inline(always)] +pub fn measure_inner_nodes( + parent_node_id: &Key, + parent_node: &Node, + layout: &mut Torin, + // Area available inside the Node + available_area: &mut Area, + // Accumulated sizes in both axis in the Node + inner_sizes: &mut Size2D, + measurer: &mut Option>, + // Whether to cache the measurements of this Node's children + must_cache_inner_nodes: bool, + mode: &mut MeasureMode, + // Adapter for the provided DOM + dom_adapter: &mut impl DOMAdapter, +) { + let mut measure_children = |mode: &mut MeasureMode, + available_area: &mut Area, + inner_sizes: &mut Size2D, + must_cache_inner_nodes: bool| { + let children = dom_adapter.children_of(parent_node_id); + + for child_id in children { + let inner_area = *mode.inner_area(); + + let child_data = dom_adapter.get_node(&child_id).unwrap(); + + let mut adapted_available_area = *available_area; + + if parent_node.cross_alignment.is_not_start() { + // 1. First measure: Cross axis is not aligned + let (_, child_areas) = measure_node( + child_id, + &child_data, + layout, + &inner_area, + available_area, + measurer, + false, + dom_adapter, + ); + + // 2. Align the Cross axis + adapted_available_area.align_content( + available_area, + &child_areas.area.size, + &parent_node.cross_alignment, + &parent_node.direction, + AlignmentDirection::Cross, + ); + } + + // 3. Second measure + let (child_revalidated, child_areas) = measure_node( + child_id, + &child_data, + layout, + &inner_area, + &adapted_available_area, + measurer, + must_cache_inner_nodes, + dom_adapter, + ); + + // Stack the child into its parent + mode.stack_into_node(parent_node, available_area, &child_areas.area, inner_sizes); + + // Cache the child layout if it was mutated and inner nodes must be cache + if child_revalidated && must_cache_inner_nodes { + layout.cache_node(child_id, child_areas); + } + } + }; + + { + // This is no the final measure, hence we make a temporary measurement mode + // so the affected values are not reused by the final measurement + let mut alignment_mode = mode.to_owned(); + let mut alignment_mode = alignment_mode.to_mut(); + let mut inner_sizes = *inner_sizes; + + if parent_node.main_alignment.is_not_start() || parent_node.cross_alignment.is_not_start() { + // 1. First measure: Main axis is not aligned + measure_children( + &mut alignment_mode, + &mut available_area.clone(), + &mut inner_sizes, + false, + ); + } + + if parent_node.cross_alignment.is_not_start() { + // 2. Adjust the available and inner areas of the Cross axis + alignment_mode.fit_bounds_when_unspecified_and_aligned( + parent_node, + AlignmentDirection::Cross, + available_area, + ); + } + + if parent_node.main_alignment.is_not_start() { + // 3. Adjust the available and inner areas of the Main axis + alignment_mode.fit_bounds_when_unspecified_and_aligned( + parent_node, + AlignmentDirection::Main, + available_area, + ); + + // 4. Align the Main axis + available_area.align_content( + alignment_mode.inner_area(), + &inner_sizes, + &parent_node.main_alignment, + &parent_node.direction, + AlignmentDirection::Main, + ); + } + } + + // 5. Second measure + measure_children(mode, available_area, inner_sizes, must_cache_inner_nodes); +} diff --git a/crates/torin/src/measure_mode.rs b/crates/torin/src/measure_mode.rs new file mode 100644 index 000000000..d320077ad --- /dev/null +++ b/crates/torin/src/measure_mode.rs @@ -0,0 +1,215 @@ +use crate::prelude::{ + get_align_axis, AlignAxis, AlignmentDirection, Area, DirectionMode, Node, Size, Size2D, +}; + +/// Measurement data for the inner Nodes of a Node +#[derive(Debug)] +pub enum MeasureMode<'a> { + ParentIsCached { + inner_area: &'a Area, + }, + ParentIsNotCached { + area: &'a mut Area, + inner_area: &'a mut Area, + }, +} + +impl<'a> MeasureMode<'a> { + /// Get a reference to the inner area + pub fn inner_area(&'a self) -> &'a Area { + match self { + Self::ParentIsCached { inner_area } => inner_area, + Self::ParentIsNotCached { inner_area, .. } => inner_area, + } + } + + /// Create an owned version of [MeasureMode] + pub fn to_owned(&self) -> OwnedMeasureMode { + match self { + MeasureMode::ParentIsCached { inner_area } => OwnedMeasureMode::ParentIsCached { + inner_area: *inner_area.to_owned(), + }, + MeasureMode::ParentIsNotCached { area, inner_area } => { + OwnedMeasureMode::ParentIsNotCached { + area: **area.clone(), + inner_area: **inner_area.clone(), + } + } + } + } + + /// This will fit the available area and inner area of a parent node when for example height is set to "auto", + /// direction is vertical and main_alignment is set to "center" or "end". + /// The intended usage is to call this after the first measurement and before the second, + /// this way the second measurement will align the content relatively to the parent element instead + /// of overflowing due to being aligned relatively to the upper parent element + pub fn fit_bounds_when_unspecified_and_aligned( + &mut self, + parent_node: &Node, + alignment_direction: AlignmentDirection, + available_area: &mut Area, + ) { + struct NodeData<'a> { + pub inner_origin: &'a mut f32, + pub inner_size: &'a mut f32, + pub area_origin: &'a mut f32, + pub area_size: &'a mut f32, + pub one_side_padding: f32, + pub two_sides_padding: f32, + pub one_side_margin: f32, + pub two_sides_margin: f32, + pub available_size: &'a mut f32, + } + + let axis = get_align_axis(&parent_node.direction, alignment_direction); + let (is_vertical_not_start, is_horizontal_not_start) = match parent_node.direction { + DirectionMode::Vertical => ( + parent_node.main_alignment.is_not_start(), + parent_node.cross_alignment.is_not_start(), + ), + DirectionMode::Horizontal => ( + parent_node.cross_alignment.is_not_start(), + parent_node.main_alignment.is_not_start(), + ), + }; + let params = if let MeasureMode::ParentIsNotCached { area, inner_area } = self { + match axis { + AlignAxis::Height if Size::Inner == parent_node.height && is_vertical_not_start => { + Some(NodeData { + inner_origin: &mut inner_area.origin.y, + inner_size: &mut inner_area.size.height, + area_origin: &mut area.origin.y, + area_size: &mut area.size.height, + one_side_padding: parent_node.padding.top(), + two_sides_padding: parent_node.padding.vertical(), + one_side_margin: parent_node.margin.top(), + two_sides_margin: parent_node.margin.vertical(), + available_size: &mut available_area.size.height, + }) + } + AlignAxis::Width if Size::Inner == parent_node.width && is_horizontal_not_start => { + Some(NodeData { + inner_origin: &mut inner_area.origin.x, + inner_size: &mut inner_area.size.width, + area_origin: &mut area.origin.x, + area_size: &mut area.size.width, + one_side_padding: parent_node.padding.left(), + two_sides_padding: parent_node.padding.horizontal(), + one_side_margin: parent_node.margin.left(), + two_sides_margin: parent_node.margin.horizontal(), + available_size: &mut available_area.size.width, + }) + } + _ => None, + } + } else { + None + }; + + if let Some(NodeData { + inner_origin, + inner_size, + area_origin, + area_size, + one_side_padding, + two_sides_padding, + one_side_margin, + two_sides_margin, + available_size, + }) = params + { + // Set the origin of the inner area to the origin of the area plus the padding and margin for the given axis + *inner_origin = *area_origin + one_side_padding + one_side_margin; + // Set the size of the inner area to the size of the area minus the padding and margin for the given axis + *inner_size = *area_size - two_sides_padding - two_sides_margin; + // Set the same available size as the inner area for the given axis + *available_size = *inner_size; + } + } + + /// Stack a Node into another Node + pub fn stack_into_node( + &mut self, + parent_node: &Node, + available_area: &mut Area, + content_area: &Area, + inner_sizes: &mut Size2D, + ) { + match parent_node.direction { + DirectionMode::Horizontal => { + // Move the available area + available_area.origin.x = content_area.max_x(); + available_area.size.width -= content_area.size.width; + + if let MeasureMode::ParentIsNotCached { area, inner_area } = self { + inner_sizes.height = content_area.height().max(inner_sizes.height); + inner_sizes.width += content_area.width(); + + // Keep the biggest height + if parent_node.height == Size::Inner { + area.size.height = area.size.height.max( + content_area.size.height + + parent_node.padding.vertical() + + parent_node.margin.vertical(), + ); + // Keep the inner area in sync + inner_area.size.height = area.size.height + - parent_node.padding.vertical() + - parent_node.margin.vertical(); + } + + // Accumulate width + if parent_node.width == Size::Inner { + area.size.width += content_area.size.width; + } + } + } + DirectionMode::Vertical => { + // Move the available area + available_area.origin.y = content_area.max_y(); + available_area.size.height -= content_area.size.height; + + if let MeasureMode::ParentIsNotCached { area, inner_area } = self { + inner_sizes.width = content_area.width().max(inner_sizes.width); + inner_sizes.height += content_area.height(); + + // Keep the biggest width + if parent_node.width == Size::Inner { + area.size.width = area.size.width.max( + content_area.size.width + + parent_node.padding.horizontal() + + parent_node.margin.horizontal(), + ); + // Keep the inner area in sync + inner_area.size.width = area.size.width + - parent_node.padding.horizontal() + - parent_node.margin.horizontal(); + } + + // Accumulate height + if parent_node.height == Size::Inner { + area.size.height += content_area.size.height; + } + } + } + } + } +} + +/// Just an owned version of [MeasureMode] +#[derive(Debug)] +pub enum OwnedMeasureMode { + ParentIsCached { inner_area: Area }, + ParentIsNotCached { area: Area, inner_area: Area }, +} + +impl OwnedMeasureMode { + pub fn to_mut(&mut self) -> MeasureMode<'_> { + match self { + Self::ParentIsCached { inner_area } => MeasureMode::ParentIsCached { inner_area }, + Self::ParentIsNotCached { area, inner_area } => { + MeasureMode::ParentIsNotCached { area, inner_area } + } + } + } +} diff --git a/crates/torin/src/node.rs b/crates/torin/src/node.rs index d4960c034..cd439cc7d 100644 --- a/crates/torin/src/node.rs +++ b/crates/torin/src/node.rs @@ -1,7 +1,7 @@ pub use euclid::Rect; use crate::{ - direction::DirectionMode, display::DisplayMode, gaps::Gaps, geometry::Length, size::Size, + alignment::Alignment, direction::DirectionMode, gaps::Gaps, geometry::Length, size::Size, }; /// Node layout configuration @@ -19,8 +19,9 @@ pub struct Node { pub maximum_width: Size, pub maximum_height: Size, - /// Inner layout mode - pub display: DisplayMode, + // Axis alignments for the children + pub main_alignment: Alignment, + pub cross_alignment: Alignment, /// Inner padding pub padding: Gaps, @@ -81,17 +82,19 @@ impl Node { } } - /// Construct a new Node given a size and a display - pub fn from_size_and_display_and_direction( + /// Construct a new Node given a size, alignments and a direction + pub fn from_size_and_alignments_and_direction( width: Size, height: Size, - display: DisplayMode, + main_alignment: Alignment, + cross_alignment: Alignment, direction: DirectionMode, ) -> Self { Self { width, height, - display, + main_alignment, + cross_alignment, direction, ..Default::default() } @@ -107,11 +110,48 @@ impl Node { } } + /// Construct a new Node given a size and a direction and some margin, + pub fn from_size_and_direction_and_margin( + width: Size, + height: Size, + direction: DirectionMode, + margin: Gaps, + ) -> Self { + Self { + width, + height, + direction, + margin, + ..Default::default() + } + } + + /// Construct a new Node given a size, alignments and a direction + pub fn from_size_and_alignments_and_direction_and_padding( + width: Size, + height: Size, + main_alignment: Alignment, + cross_alignment: Alignment, + direction: DirectionMode, + padding: Gaps, + ) -> Self { + Self { + width, + height, + main_alignment, + cross_alignment, + direction, + padding, + ..Default::default() + } + } + /// Has properties that depend on the inner Nodes? pub fn does_depend_on_inner(&self) -> bool { Size::Inner == self.width || Size::Inner == self.height || self.has_layout_references - || self.display == DisplayMode::Center + || self.cross_alignment.is_not_start() + || self.main_alignment.is_not_start() } } diff --git a/crates/torin/src/torin.rs b/crates/torin/src/torin.rs index 0e73993dd..462538a12 100644 --- a/crates/torin/src/torin.rs +++ b/crates/torin/src/torin.rs @@ -6,13 +6,10 @@ use tracing::info; use crate::{ custom_measurer::LayoutMeasurer, - direction::DirectionMode, - display::DisplayMode, dom_adapter::{DOMAdapter, NodeAreas, NodeKey}, geometry::{Area, Size2D}, - node::Node, - prelude::{BoxModel, Gaps}, - size::Size, + measure::measure_node, + prelude::Gaps, }; /// Contains the best Root node candidate from where to start measuring @@ -323,354 +320,3 @@ impl Torin { self.results.insert(node_id, areas); } } - -/// Measure this node and all it's children -/// The caller of this function is responsible of caching the Node's layout results -#[allow(clippy::too_many_arguments)] -#[inline(always)] -fn measure_node( - node_id: Key, - node: &Node, - layout: &mut Torin, - parent_area: &Area, - available_parent_area: &Area, - measurer: &mut Option>, - must_cache: bool, - dom_adapter: &mut impl DOMAdapter, -) -> (bool, NodeAreas) { - let must_run = layout.dirty.contains(&node_id) || layout.results.get(&node_id).is_none(); - if must_run { - let horizontal_padding = node.padding.horizontal(); - let vertical_padding = node.padding.vertical(); - - let mut area = Rect::new( - available_parent_area.origin, - Size2D::new(horizontal_padding, vertical_padding), - ); - - area.size.width = node.width.min_max( - area.size.width, - parent_area.size.width, - node.margin.horizontal(), - &node.minimum_width, - &node.maximum_width, - ); - area.size.height = node.height.min_max( - area.size.height, - parent_area.size.height, - node.margin.vertical(), - &node.minimum_height, - &node.maximum_height, - ); - - // Custom measure - let skip_inner = if let Some(measurer) = measurer { - let custom_measure = - measurer.measure(node_id, node, &area, parent_area, available_parent_area); - if let Some(new_area) = custom_measure { - if Size::Inner == node.width { - area.size.width = node.width.min_max( - new_area.width(), - parent_area.size.width, - node.margin.horizontal(), - &node.minimum_width, - &node.maximum_width, - ); - } - if Size::Inner == node.height { - area.size.height = node.height.min_max( - new_area.height(), - parent_area.size.height, - node.margin.vertical(), - &node.minimum_height, - &node.maximum_height, - ); - } - } - custom_measure.is_some() - } else { - false - }; - - let mut inner_sizes = Size2D::default(); - - // Node's inner area - let mut inner_area = { - let mut inner_area = area.box_area(&node.margin); - if Size::Inner == node.width { - inner_area.size.width = available_parent_area.width() - } - if Size::Inner == node.height { - inner_area.size.height = available_parent_area.height() - } - inner_area - }; - - // Apply padding - inner_area.origin.x += node.padding.left(); - inner_area.origin.y += node.padding.top(); - inner_area.size.width -= horizontal_padding; - inner_area.size.height -= vertical_padding; - - // Node's available inner area - let mut available_area = inner_area; - - // Apply scroll - available_area.origin.x += node.offset_x.get(); - available_area.origin.y += node.offset_y.get(); - - let mut measurement_mode = MeasureMode::ParentIsNotCached { - area: &mut area, - inner_area: &mut inner_area, - vertical_padding, - horizontal_padding, - }; - - if !skip_inner { - measure_inner_nodes( - &node_id, - node, - layout, - &mut available_area, - &mut inner_sizes, - measurer, - must_cache, - &mut measurement_mode, - dom_adapter, - ); - } - - ( - must_cache, - NodeAreas { - area, - margin: node.margin, - inner_area, - inner_sizes, - }, - ) - } else { - let areas = layout.get(node_id).unwrap().clone(); - - let mut inner_sizes = areas.inner_sizes; - let mut available_area = areas.inner_area; - - // TODO(marc2332): Should I also cache these? - available_area.origin.x += node.offset_x.get(); - available_area.origin.y += node.offset_y.get(); - - let mut measurement_mode = MeasureMode::ParentIsCached { - inner_area: &areas.inner_area, - }; - - measure_inner_nodes( - &node_id, - node, - layout, - &mut available_area, - &mut inner_sizes, - measurer, - must_cache, - &mut measurement_mode, - dom_adapter, - ); - - (false, areas) - } -} - -/// Measurement data for the inner Nodes of a Node -#[derive(Debug)] -enum MeasureMode<'a> { - ParentIsCached { - inner_area: &'a Area, - }, - ParentIsNotCached { - area: &'a mut Area, - inner_area: &'a mut Area, - vertical_padding: f32, - horizontal_padding: f32, - }, -} - -impl<'a> MeasureMode<'a> { - /// Get a reference to the inner area - pub fn inner_area(&'a self) -> &'a Area { - match self { - Self::ParentIsCached { inner_area } => inner_area, - Self::ParentIsNotCached { inner_area, .. } => inner_area, - } - } -} - -/// Measure the inner Nodes of a Node -#[allow(clippy::too_many_arguments)] -#[inline(always)] -fn measure_inner_nodes( - node_id: &Key, - node: &Node, - layout: &mut Torin, - available_area: &mut Area, - inner_sizes: &mut Size2D, - measurer: &mut Option>, - must_cache: bool, - mode: &mut MeasureMode, - dom_adapter: &mut impl DOMAdapter, -) { - let children = dom_adapter.children_of(node_id); - - // Center display - - if node.display == DisplayMode::Center { - let child_id = children.first(); - - if let Some(child_id) = child_id { - let inner_area = *mode.inner_area(); - let child_data = dom_adapter.get_node(child_id).unwrap(); - - let (_, child_areas) = measure_node( - *child_id, - &child_data, - layout, - &inner_area, - available_area, - measurer, - false, - dom_adapter, - ); - - // TODO(marc2332): Should I also reduce the width and heights? - match node.direction { - DirectionMode::Horizontal => { - let new_origin_x = - (inner_area.width() / 2.0) - (child_areas.area.width() / 2.0); - available_area.origin.x = inner_area.min_x() + new_origin_x; - } - DirectionMode::Vertical => { - let new_origin_y = - (inner_area.height() / 2.0) - (child_areas.area.height() / 2.0); - available_area.origin.y = inner_area.min_y() + new_origin_y; - } - DirectionMode::Both => { - let new_origin_x = - (inner_area.width() / 2.0) - (child_areas.area.width() / 2.0); - let new_origin_y = - (inner_area.height() / 2.0) - (child_areas.area.height() / 2.0); - available_area.origin.x = inner_area.min_x() + new_origin_x; - available_area.origin.y = inner_area.min_y() + new_origin_y; - } - } - } - } - - // Normal display - - for child_id in children { - let inner_area = *mode.inner_area(); - - let child_data = dom_adapter.get_node(&child_id).unwrap().clone(); - - let (child_revalidated, child_areas) = measure_node( - child_id, - &child_data, - layout, - &inner_area, - available_area, - measurer, - must_cache, - dom_adapter, - ); - - match node.direction { - DirectionMode::Horizontal => { - // Move the available area - available_area.origin.x = child_areas.area.max_x(); - available_area.size.width -= child_areas.area.size.width; - - if let MeasureMode::ParentIsNotCached { - area, - vertical_padding, - inner_area, - .. - } = mode - { - inner_sizes.height = child_areas.area.height(); - inner_sizes.width += child_areas.area.width(); - - // Keep the biggest height - if node.height == Size::Inner { - area.size.height = area - .size - .height - .max(child_areas.area.size.height + *vertical_padding); - // Keep the inner area in sync - inner_area.size.height = area.size.height - *vertical_padding; - } - - // Accumulate width - if node.width == Size::Inner { - area.size.width += child_areas.area.size.width; - } - } - } - DirectionMode::Vertical => { - // Move the available area - available_area.origin.y = child_areas.area.max_y(); - available_area.size.height -= child_areas.area.size.height; - - if let MeasureMode::ParentIsNotCached { - area, - horizontal_padding, - inner_area, - .. - } = mode - { - inner_sizes.width = child_areas.area.width(); - inner_sizes.height += child_areas.area.height(); - - // Keep the biggest width - if node.width == Size::Inner { - area.size.width = area - .size - .width - .max(child_areas.area.size.width + *horizontal_padding); - // Keep the inner area in sync - inner_area.size.width = area.size.width - *horizontal_padding; - } - - // Accumulate height - if node.height == Size::Inner { - area.size.height += child_areas.area.size.height; - } - } - } - DirectionMode::Both => { - // Move the available area - available_area.origin.x = child_areas.area.max_x(); - available_area.origin.y = child_areas.area.max_y(); - - available_area.size.width -= child_areas.area.size.width; - available_area.size.height -= child_areas.area.size.height; - - if let MeasureMode::ParentIsNotCached { area, .. } = mode { - inner_sizes.width += child_areas.area.width(); - inner_sizes.height += child_areas.area.height(); - - // Accumulate width - if node.width == Size::Inner { - area.size.width += child_areas.area.size.width; - } - - // Accumulate height - if node.height == Size::Inner { - area.size.height += child_areas.area.size.height; - } - } - } - } - - if child_revalidated && must_cache { - layout.cache_node(child_id, child_areas); - } - } -} diff --git a/crates/torin/src/values/alignment.rs b/crates/torin/src/values/alignment.rs new file mode 100644 index 000000000..083677313 --- /dev/null +++ b/crates/torin/src/values/alignment.rs @@ -0,0 +1,17 @@ +#[derive(PartialEq, Clone, Debug, Default)] +pub enum Alignment { + #[default] + Start, + Center, + End, +} + +impl Alignment { + pub fn is_not_start(&self) -> bool { + *self != Self::Start + } + + pub fn pretty(&self) -> String { + format!("{self:?}") + } +} diff --git a/crates/torin/src/values/direction.rs b/crates/torin/src/values/direction.rs index 2553d93f1..b219e1433 100644 --- a/crates/torin/src/values/direction.rs +++ b/crates/torin/src/values/direction.rs @@ -3,7 +3,6 @@ pub enum DirectionMode { #[default] Vertical, Horizontal, - Both, } impl DirectionMode { @@ -11,7 +10,6 @@ impl DirectionMode { match self { DirectionMode::Horizontal => "horizontal".to_string(), DirectionMode::Vertical => "vertical".to_string(), - DirectionMode::Both => "both".to_string(), } } } diff --git a/crates/torin/src/values/display.rs b/crates/torin/src/values/display.rs deleted file mode 100644 index 142a2d5a4..000000000 --- a/crates/torin/src/values/display.rs +++ /dev/null @@ -1,15 +0,0 @@ -#[derive(PartialEq, Clone, Debug, Copy, Default)] -pub enum DisplayMode { - #[default] - Normal, - Center, -} - -impl DisplayMode { - pub fn pretty(&self) -> String { - match self { - DisplayMode::Normal => "Normal".to_string(), - DisplayMode::Center => "Center".to_string(), - } - } -} diff --git a/crates/torin/src/values/mod.rs b/crates/torin/src/values/mod.rs index f69f06c73..24128385b 100644 --- a/crates/torin/src/values/mod.rs +++ b/crates/torin/src/values/mod.rs @@ -1,11 +1,11 @@ +pub mod alignment; pub mod direction; -pub mod display; pub mod gaps; pub mod size; pub mod prelude { + pub use crate::alignment::*; pub use crate::direction::*; - pub use crate::display::*; pub use crate::gaps::*; pub use crate::size::*; } diff --git a/crates/torin/src/values/size.rs b/crates/torin/src/values/size.rs index 7e59fe483..8dafc118f 100644 --- a/crates/torin/src/values/size.rs +++ b/crates/torin/src/values/size.rs @@ -34,9 +34,9 @@ impl Size { } } - pub fn eval(&self, parent_value: f32) -> Option { + pub fn eval(&self, parent_value: f32, parent_margin: f32) -> Option { match self { - Size::Pixels(px) => Some(px.get()), + Size::Pixels(px) => Some(px.get() + parent_margin), Size::Percentage(per) => Some(parent_value / 100.0 * per.get()), Size::DynamicCalculations(calculations) => { Some(run_calculations(calculations, parent_value)) @@ -49,14 +49,17 @@ impl Size { &self, value: f32, parent_value: f32, + single_margin: f32, margin: f32, minimum: &Self, maximum: &Self, ) -> f32 { - let value = self.eval(parent_value).unwrap_or(value) + margin; + let value = self.eval(parent_value, margin).unwrap_or(value + margin); - let minimum_value = minimum.eval(parent_value); - let maximum_value = maximum.eval(parent_value); + let minimum_value = minimum + .eval(parent_value, margin) + .map(|v| v + single_margin); + let maximum_value = maximum.eval(parent_value, margin); let mut final_value = value; diff --git a/crates/torin/tests/test.rs b/crates/torin/tests/test.rs index e366fbdf8..e8b192f2b 100644 --- a/crates/torin/tests/test.rs +++ b/crates/torin/tests/test.rs @@ -720,10 +720,11 @@ pub fn display_horizontal() { 0, None, vec![1], - Node::from_size_and_display_and_direction( + Node::from_size_and_alignments_and_direction( Size::Pixels(Length::new(200.0)), Size::Pixels(Length::new(200.0)), - DisplayMode::Center, + Alignment::Center, + Alignment::Center, DirectionMode::Horizontal, ), ); @@ -752,7 +753,7 @@ pub fn display_horizontal() { assert_eq!( layout.get(1).unwrap().area, - Rect::new(Point2D::new(50.0, 0.0), Size2D::new(100.0, 100.0)), + Rect::new(Point2D::new(50.0, 50.0), Size2D::new(100.0, 100.0)), ); } @@ -765,10 +766,11 @@ pub fn display_vertical_with_inner_children() { 0, None, vec![1], - Node::from_size_and_display_and_direction( + Node::from_size_and_alignments_and_direction( Size::Pixels(Length::new(200.0)), Size::Pixels(Length::new(200.0)), - DisplayMode::Center, + Alignment::Center, + Alignment::Center, DirectionMode::Vertical, ), ); @@ -808,12 +810,12 @@ pub fn display_vertical_with_inner_children() { assert_eq!( layout.get(1).unwrap().area, - Rect::new(Point2D::new(0.0, 50.0), Size2D::new(100.0, 100.0)), + Rect::new(Point2D::new(50.0, 50.0), Size2D::new(100.0, 100.0)), ); assert_eq!( layout.get(2).unwrap().area, - Rect::new(Point2D::new(5.0, 55.0), Size2D::new(90.0, 90.0)), + Rect::new(Point2D::new(55.0, 55.0), Size2D::new(90.0, 90.0)), ); } @@ -826,10 +828,9 @@ pub fn deep_tree() { 0, None, vec![1], - Node::from_size_and_display_and_direction( + Node::from_size_and_direction( Size::Pixels(Length::new(200.0)), Size::Pixels(Length::new(200.0)), - DisplayMode::Center, DirectionMode::Vertical, ), ); @@ -1066,7 +1067,7 @@ pub fn margin() { mocked_dom.add( 0, None, - vec![1], + vec![1, 2], Node::from_size_and_direction( Size::Percentage(Length::new(100.0)), Size::Percentage(Length::new(100.0)), @@ -1109,7 +1110,257 @@ pub fn margin() { ); assert_eq!( - node_areas.box_area(), + node_areas.visible_area(), Rect::new(Point2D::new(5.0, 5.0), Size2D::new(200.0, 200.0)), ); } + +#[test] +pub fn double_center_alignment() { + let (mut layout, mut measurer) = test_utils(); + + let mut mocked_dom = TestingDOM::default(); + mocked_dom.add( + 0, + None, + vec![1, 2], + Node::from_size_and_alignments_and_direction( + Size::Percentage(Length::new(100.0)), + Size::Percentage(Length::new(100.0)), + Alignment::Center, + Alignment::Center, + DirectionMode::Vertical, + ), + ); + mocked_dom.add( + 1, + Some(0), + vec![], + Node::from_size_and_direction( + Size::Pixels(Length::new(200.0)), + Size::Pixels(Length::new(200.0)), + DirectionMode::Vertical, + ), + ); + mocked_dom.add( + 2, + Some(0), + vec![], + Node::from_size_and_direction( + Size::Pixels(Length::new(300.0)), + Size::Pixels(Length::new(300.0)), + DirectionMode::Vertical, + ), + ); + + layout.measure( + 0, + Rect::new(Point2D::new(0.0, 0.0), Size2D::new(1000.0, 1000.0)), + &mut measurer, + &mut mocked_dom, + ); + + assert_eq!( + layout.get(1).unwrap().area, + Rect::new(Point2D::new(400.0, 250.0), Size2D::new(200.0, 200.0)), + ); + + assert_eq!( + layout.get(2).unwrap().area, + Rect::new(Point2D::new(350.0, 450.0), Size2D::new(300.0, 300.0)), + ); +} + +#[test] +pub fn double_end_alignment() { + let (mut layout, mut measurer) = test_utils(); + + let mut mocked_dom = TestingDOM::default(); + mocked_dom.add( + 0, + None, + vec![1, 2], + Node::from_size_and_alignments_and_direction( + Size::Percentage(Length::new(100.0)), + Size::Percentage(Length::new(100.0)), + Alignment::End, + Alignment::End, + DirectionMode::Vertical, + ), + ); + mocked_dom.add( + 1, + Some(0), + vec![], + Node::from_size_and_direction( + Size::Pixels(Length::new(200.0)), + Size::Pixels(Length::new(200.0)), + DirectionMode::Vertical, + ), + ); + mocked_dom.add( + 2, + Some(0), + vec![], + Node::from_size_and_direction( + Size::Pixels(Length::new(300.0)), + Size::Pixels(Length::new(300.0)), + DirectionMode::Vertical, + ), + ); + + layout.measure( + 0, + Rect::new(Point2D::new(0.0, 0.0), Size2D::new(1000.0, 1000.0)), + &mut measurer, + &mut mocked_dom, + ); + + assert_eq!( + layout.get(1).unwrap().area, + Rect::new(Point2D::new(800.0, 500.0), Size2D::new(200.0, 200.0)), + ); + + assert_eq!( + layout.get(2).unwrap().area, + Rect::new(Point2D::new(700.0, 700.0), Size2D::new(300.0, 300.0)), + ); +} + +#[test] +pub fn unsized_alignment() { + let (mut layout, mut measurer) = test_utils(); + + let mut mocked_dom = TestingDOM::default(); + mocked_dom.add( + 0, + None, + vec![1, 2], + Node::from_size_and_alignments_and_direction_and_padding( + Size::Inner, + Size::Inner, + Alignment::Center, + Alignment::End, + DirectionMode::Horizontal, + Gaps::new(15.0, 15.0, 15.0, 15.0), + ), + ); + mocked_dom.add( + 1, + Some(0), + vec![], + Node::from_size_and_direction( + Size::Pixels(Length::new(100.0)), + Size::Pixels(Length::new(50.0)), + DirectionMode::Vertical, + ), + ); + mocked_dom.add( + 2, + Some(0), + vec![], + Node::from_size_and_direction_and_margin( + Size::Pixels(Length::new(150.0)), + Size::Pixels(Length::new(80.0)), + DirectionMode::Vertical, + Gaps::new(10.0, 50.0, 20.0, 0.0), + ), + ); + + layout.measure( + 0, + Rect::new(Point2D::new(0.0, 0.0), Size2D::new(350.0, 190.0)), + &mut measurer, + &mut mocked_dom, + ); + + assert_eq!( + layout.get(1).unwrap().visible_area(), + Rect::new(Point2D::new(15.0, 75.0), Size2D::new(100.0, 50.0)), + ); + + assert_eq!( + layout.get(2).unwrap().visible_area(), + Rect::new(Point2D::new(115.0, 25.0), Size2D::new(150.0, 80.0)), + ); +} + +#[test] +pub fn unsized_parent_with_child_with_margin() { + let (mut layout, mut measurer) = test_utils(); + + let mut mocked_dom = TestingDOM::default(); + mocked_dom.add( + 0, + None, + vec![1], + Node::from_size_and_direction(Size::Inner, Size::Inner, DirectionMode::Vertical), + ); + mocked_dom.add( + 1, + Some(0), + vec![], + Node::from_size_and_margin( + Size::Percentage(Length::new(100.0)), + Size::Percentage(Length::new(100.0)), + Gaps::new(10.0, 20.0, 30.0, 40.0), + ), + ); + + layout.measure( + 0, + Rect::new(Point2D::new(0.0, 0.0), Size2D::new(1000.0, 1000.0)), + &mut measurer, + &mut mocked_dom, + ); + + assert_eq!( + layout.get(0).unwrap().visible_area(), + Rect::new(Point2D::new(0.0, 0.0), Size2D::new(1000.0, 1000.0)), + ); + + assert_eq!( + layout.get(1).unwrap().visible_area(), + Rect::new(Point2D::new(40.0, 10.0), Size2D::new(940.0, 960.0)), + ); +} + +#[test] +pub fn unsized_parent_with_padding() { + let (mut layout, mut measurer) = test_utils(); + + let mut mocked_dom = TestingDOM::default(); + mocked_dom.add( + 0, + None, + vec![1], + Node::from_size_and_padding(Size::Inner, Size::Inner, Gaps::new(10.0, 20.0, 30.0, 40.0)), + ); + mocked_dom.add( + 1, + Some(0), + vec![], + Node::from_size_and_direction( + Size::Percentage(Length::new(100.0)), + Size::Percentage(Length::new(100.0)), + DirectionMode::Vertical, + ), + ); + + layout.measure( + 0, + Rect::new(Point2D::new(0.0, 0.0), Size2D::new(1000.0, 1000.0)), + &mut measurer, + &mut mocked_dom, + ); + + assert_eq!( + layout.get(0).unwrap().visible_area().round(), + Rect::new(Point2D::new(0.0, 0.0), Size2D::new(1000.0, 1000.0)), + ); + + assert_eq!( + layout.get(1).unwrap().visible_area().round(), + Rect::new(Point2D::new(40.0, 10.0), Size2D::new(940.0, 960.0)), + ); +} diff --git a/examples/alignment.rs b/examples/alignment.rs new file mode 100644 index 000000000..136a977c9 --- /dev/null +++ b/examples/alignment.rs @@ -0,0 +1,85 @@ +#![cfg_attr( + all(not(debug_assertions), target_os = "windows"), + windows_subsystem = "windows" +)] + +use freya::prelude::*; + +fn main() { + launch(app); +} + +fn app(cx: Scope) -> Element { + render!( + rect { + height: "100%", + width: "100%", + cross_align: "end", + main_align: "end", + rect { + width: "65%", + height: "65%", + background: "yellow", + main_align: "start", + cross_align: "start", + overflow: "clip", + rect { + main_align: "end", + cross_align: "center", + background: "red", + direction: "horizontal", + width: "50%", + height: "50%", + overflow: "clip", + rect { + width: "50", + height: "50", + background: "green" + } + rect { + main_align: "center", + cross_align: "center", + background: "orange", + padding: "15", + rect { + width: "20", + height: "20", + background: "black" + } + rect { + width: "20", + height: "20", + background: "white" + } + } + } + rect { + cross_align: "end", + width: "100%%", + height: "50%", + rect { + main_align: "start", + cross_align: "center", + width: "50%", + height: "100%", + label { + "Some crabs" + } + label { + "🦀🦀" + } + label { + "Even more crabs" + } + label { + "🦀🦀🦀🦀🦀" + } + } + } + } + label { + "Hello, World!" + } + } + ) +} diff --git a/examples/animation.rs b/examples/animation.rs index b43a01106..f60b121dd 100644 --- a/examples/animation.rs +++ b/examples/animation.rs @@ -33,13 +33,12 @@ fn app(cx: Scope) -> Element { rect { overflow: "clip", background: "black", - direction: "both", width: "100%", height: "100%", offset_x: "{progress}", rect { - display: "center", - direction: "both", + main_align: "center", + cross_align: "center", height: "100%", width: "200", rect { @@ -48,8 +47,8 @@ fn app(cx: Scope) -> Element { background: "rgb(100, 100, 100)", padding: "25", corner_radius: "100", - display: "center", - direction: "both", + main_align: "center", + cross_align: "center", onclick: anim, label { font_size: "30", diff --git a/examples/app_dog.rs b/examples/app_dog.rs index 0f0b4cbb7..95431d94c 100644 --- a/examples/app_dog.rs +++ b/examples/app_dog.rs @@ -39,16 +39,13 @@ fn app(cx: Scope) -> Element { render!( rect { - overflow: "clip", background: "rgb(15, 15, 15)", width: "100%", height: "100%", - color: "white", rect { overflow: "clip", width: "100%", - height: "calc(100% - 58)", - corner_radius: "25", + height: "calc(100% - 60)", if let Some(dog_url) = dog_url.get() { rsx!( NetworkImage { @@ -59,10 +56,10 @@ fn app(cx: Scope) -> Element { } rect { overflow: "clip", - padding: "10", - height: "58", + height: "60", width: "100%", - direction: "horizontal", + main_align: "center", + cross_align: "center", Button { onclick: move |_| fetch(), label { diff --git a/examples/canvas.rs b/examples/canvas.rs index 0e2f1e55f..faa0b7b05 100644 --- a/examples/canvas.rs +++ b/examples/canvas.rs @@ -119,28 +119,23 @@ fn app(cx: Scope) -> Element { }) } rect { - background: "rgb(35, 35, 35)", + background: "rgb(25, 25, 25)", height: "100", width: "100%", - display: "center", - direction: "horizontal", + main_align: "center", + cross_align: "center", padding: "15", - rect { - layer: "-100", - padding: "10", - corner_radius: "7", - width: "170", - height: "100%", - corner_radius: "15", - display: "center", - direction: "both", - background: "rgb(20, 20, 20)", - Button { - onclick: create_node, - label { - color: "white", - "Create new node" - } + layer: "-100", + shadow: "0 -2 5 0 rgb(0, 0, 0, 30)", + direction: "horizontal", + label { + "Create as many editors you want!" + } + Button { + margin: "0 20", + onclick: create_node, + label { + "New Editor" } } } @@ -203,99 +198,63 @@ fn Editor(cx: Scope) -> Element { height: "100%", rect { width: "100%", - height: "50", - padding: "10", + height: "70", + padding: "5", direction: "horizontal", + cross_align: "center", rect { - height: "100%", - width: "100%", - direction: "horizontal", - padding: "5", - rect { - height: "40%", - display: "center", - width: "130", - Slider { - width: 100.0, - value: *font_size_percentage.get(), - onmoved: |p| { - font_size_percentage.set(p); - } - } - rect { - height: "auto", - width: "100%", - display: "center", - direction: "horizontal", - label { - "Font size" - } + width: "130", + cross_align: "center", + Slider { + width: 100.0, + value: *font_size_percentage.get(), + onmoved: |p| { + font_size_percentage.set(p); } } - rect { - height: "40%", - display: "center", - direction: "vertical", - width: "130", - Slider { - width: 100.0, - value: *line_height_percentage.get(), - onmoved: |p| { - line_height_percentage.set(p); - } - } - rect { - height: "auto", - width: "100%", - display: "center", - direction: "horizontal", - label { - "Line height" - } - } + label { + "Font size" } - rect { - height: "40%", - display: "center", - direction: "vertical", - width: "60", - Switch { - enabled: *is_bold.get(), - ontoggled: |_| { - is_bold.set(!is_bold.get()); - } - } - rect { - height: "auto", - width: "100%", - display: "center", - direction: "horizontal", - label { - "Bold" - } + } + rect { + width: "130", + cross_align: "center", + Slider { + width: 100.0, + value: *line_height_percentage.get(), + onmoved: |p| { + line_height_percentage.set(p); } } - rect { - height: "40%", - display: "center", - direction: "vertical", - width: "60", - Switch { - enabled: *is_italic.get(), - ontoggled: |_| { - is_italic.set(!is_italic.get()); - } + label { + "Line height" + } + } + rect { + width: "80", + cross_align: "center", + Switch { + enabled: *is_bold.get(), + ontoggled: |_| { + is_bold.set(!is_bold.get()); } - rect { - height: "auto", - width: "100%", - display: "center", - direction: "horizontal", - label { - "Italic" - } + } + label { + "Bold" + } + } + rect { + width: "80", + cross_align: "center", + Switch { + enabled: *is_italic.get(), + ontoggled: |_| { + is_italic.set(!is_italic.get()); } } + label { + "Italic" + } } } rect { @@ -370,7 +329,7 @@ fn Editor(cx: Scope) -> Element { rect { width: "{font_size * 2.0}", height: "100%", - display: "center", + main_align: "center", direction: "horizontal", label { font_size: "{font_size}", diff --git a/examples/cloned_editor.rs b/examples/cloned_editor.rs index e1cab0b05..59a0739e7 100644 --- a/examples/cloned_editor.rs +++ b/examples/cloned_editor.rs @@ -112,7 +112,7 @@ fn Body(cx: Scope) -> Element { rect { width: "30", height: "100%", - display: "center", + main_align: "center", direction: "horizontal", label { font_size: "15", @@ -196,7 +196,7 @@ fn Body(cx: Scope) -> Element { rect { width: "30", height: "100%", - display: "center", + main_align: "center", direction: "horizontal", label { font_size: "15", diff --git a/examples/counter.rs b/examples/counter.rs index fa7d24dfd..1b06b9622 100644 --- a/examples/counter.rs +++ b/examples/counter.rs @@ -6,7 +6,7 @@ use freya::prelude::*; fn main() { - launch(app); + launch_with_props(app, "Counter", (400.0, 350.0)); } fn app(cx: Scope) -> Element { @@ -14,24 +14,33 @@ fn app(cx: Scope) -> Element { render!( rect { - height: "20%", + height: "50%", width: "100%", - background: "rgb(233, 196, 106)", - padding: "12", - color: "rgb(20, 33, 61)", + main_align: "center", + cross_align: "center", + background: "rgb(0, 119, 182)", + color: "white", + shadow: "0 4 20 5 rgb(0, 0, 0, 80)", label { - font_size: "20", - "Number is: {count}" + font_size: "75", + font_weight: "bold", + "{count}" } } rect { - height: "80%", + height: "50%", width: "100%", - background: "rgb(168, 218, 220)", - color: "black", - padding: "12", - onclick: move |_| count += 1, - label { "Click to increase!" } + main_align: "center", + cross_align: "center", + direction: "horizontal", + Button { + onclick: move |_| count += 1, + label { "Increase" } + } + Button { + onclick: move |_| count -= 1, + label { "Decrease" } + } } ) } diff --git a/examples/custom_font.rs b/examples/custom_font.rs index d0b28fc0f..554a2b5d6 100644 --- a/examples/custom_font.rs +++ b/examples/custom_font.rs @@ -21,7 +21,8 @@ fn main() { fn app(cx: Scope) -> Element { render!( rect { - display: "center", + main_align: "center", + cross_align: "center", height: "100%", width: "100%", label { diff --git a/examples/custom_theme.rs b/examples/custom_theme.rs index ce0d5f452..4d62d301a 100644 --- a/examples/custom_theme.rs +++ b/examples/custom_theme.rs @@ -9,6 +9,7 @@ const CUSTOM_THEME: Theme = Theme { button: ButtonTheme { background: "rgb(230, 0, 0)", hover_background: "rgb(150, 0, 0)", + border_fill: "rgb(120, 0, 0)", font_theme: FontTheme { color: "white" }, }, ..LIGHT_THEME diff --git a/examples/drag.rs b/examples/drag.rs index 3466d2f52..90011468e 100644 --- a/examples/drag.rs +++ b/examples/drag.rs @@ -54,7 +54,6 @@ fn app(cx: Scope) -> Element { rect { overflow: "clip", background: "rgb(255, 166, 0)", - direction: "both", width: "100", height: "100", corner_radius: "15", diff --git a/examples/editor.rs b/examples/editor.rs index ef7428b2b..269492a0d 100644 --- a/examples/editor.rs +++ b/examples/editor.rs @@ -70,7 +70,7 @@ fn Body(cx: Scope) -> Element { height: "100%", width: "100%", direction: "horizontal", - padding: "5", + cross_align: "center", label { font_size: "30", "Editor" @@ -79,9 +79,8 @@ fn Body(cx: Scope) -> Element { width: "20", } rect { - height: "40%", - display: "center", width: "130", + cross_align: "center", Slider { width: 100.0, value: *font_size_percentage.get(), @@ -89,21 +88,13 @@ fn Body(cx: Scope) -> Element { font_size_percentage.set(p); } } - rect { - height: "auto", - width: "100%", - display: "center", - direction: "horizontal", - label { - "Font size" - } + label { + "Font size" } } rect { - height: "40%", - display: "center", - direction: "vertical", width: "130", + cross_align: "center", Slider { width: 100.0, value: *line_height_percentage.get(), @@ -111,56 +102,34 @@ fn Body(cx: Scope) -> Element { line_height_percentage.set(p); } } - rect { - height: "auto", - width: "100%", - display: "center", - direction: "horizontal", - label { - "Line height" - } + label { + "Line height" } } rect { - height: "40%", - display: "center", - direction: "vertical", - width: "60", + width: "80", + cross_align: "center", Switch { enabled: *is_bold.get(), ontoggled: |_| { is_bold.set(!is_bold.get()); } } - rect { - height: "auto", - width: "100%", - display: "center", - direction: "horizontal", - label { - "Bold" - } + label { + "Bold" } } rect { - height: "40%", - display: "center", - direction: "vertical", - width: "60", + width: "80", + cross_align: "center", Switch { enabled: *is_italic.get(), ontoggled: |_| { is_italic.set(!is_italic.get()); } } - rect { - height: "auto", - width: "100%", - display: "center", - direction: "horizontal", - label { - "Italic" - } + label { + "Italic" } } } @@ -238,7 +207,7 @@ fn Body(cx: Scope) -> Element { rect { width: "{font_size * 2.0}", height: "100%", - display: "center", + main_align: "center", direction: "horizontal", label { font_size: "{font_size}", diff --git a/examples/frameless_window.rs b/examples/frameless_window.rs index 3f3d40e8e..2a806bca6 100644 --- a/examples/frameless_window.rs +++ b/examples/frameless_window.rs @@ -23,8 +23,8 @@ fn app(cx: Scope) -> Element { rect { background: "white", padding: "10", - display: "center", - direction: "both", + main_align: "center", + cross_align: "center", width: "100%", height: "100%", corner_radius: "15", diff --git a/examples/gesture_area.rs b/examples/gesture_area.rs index dee0e15f1..a645267d9 100644 --- a/examples/gesture_area.rs +++ b/examples/gesture_area.rs @@ -18,7 +18,7 @@ fn app(cx: Scope) -> Element { width: "100%", height: "100%", direction: "vertical", - display: "center", + main_align: "center", label { align: "center", width: "100%", diff --git a/examples/i18n.rs b/examples/i18n.rs index 1e7a01816..aaa106198 100644 --- a/examples/i18n.rs +++ b/examples/i18n.rs @@ -26,8 +26,8 @@ fn Body(cx: Scope) -> Element { render!( rect { - direction: "both", - display: "center", + main_align: "center", + cross_align: "center", width: "100%", height: "100%", rect { diff --git a/examples/image.rs b/examples/image.rs index 86bfbafb4..c5e37f4e7 100644 --- a/examples/image.rs +++ b/examples/image.rs @@ -29,8 +29,8 @@ fn app(cx: Scope) -> Element { width: "100%", height: "100%", padding: "50", - display: "center", - direction: "both", + main_align: "center", + cross_align: "center", onwheel: onwheel, image { image_data: image_data, diff --git a/examples/margin.rs b/examples/margin.rs index f9ea721af..11c44a800 100644 --- a/examples/margin.rs +++ b/examples/margin.rs @@ -27,27 +27,11 @@ fn app(cx: Scope) -> Element { height: "100", background: "rgb(100, 50, 255)", } - Button { - label { - "Button" - } - } - Button { - label { - "Button" - } - } - Button { - label { - "Button" - } - } - Button { - label { - "Button" - } + rect { + width: "100", + height: "100", + background: "rgb(150, 150, 150)", } - } ) } diff --git a/examples/material_design_app.rs b/examples/material_design_app.rs index 48109d559..cb4d73631 100644 --- a/examples/material_design_app.rs +++ b/examples/material_design_app.rs @@ -53,8 +53,8 @@ fn FloatingButton<'a>(cx: Scope<'a, FloatingButtonProps<'a>>) -> Element<'a> { rect { height: "100%", width: "100%", - display: "center", - direction: "both", + main_align: "center", + cross_align: "center", height: "50", width: "50", background: "rgb(104, 24, 245)", @@ -127,7 +127,7 @@ fn Navbar<'a>(cx: Scope<'a, NavbarProps<'a>>) -> Element<'a> { background: "rgb(104, 24, 245)", direction: "vertical", color: "white", - display: "center", + main_align: "center", rect { width: "100%", direction: "horizontal", diff --git a/examples/mouse_trace.rs b/examples/mouse_trace.rs index 27c54a25f..f0aa75490 100644 --- a/examples/mouse_trace.rs +++ b/examples/mouse_trace.rs @@ -24,15 +24,15 @@ fn Box(cx: Scope) -> Element { background: "rgb(65, 53, 67)", width: "250", height: "250", - direction: "both", - display: "center", + main_align: "center", + cross_align: "center", corner_radius: "100", rect { background: "rgb(143, 67, 238)", width: "180", height: "180", - direction: "both", - display: "center", + main_align: "center", + cross_align: "center", corner_radius: "100", rect { background: "rgb(240, 235, 141)", diff --git a/examples/paragraph.rs b/examples/paragraph.rs index 217e3d146..ecb1e972b 100644 --- a/examples/paragraph.rs +++ b/examples/paragraph.rs @@ -17,8 +17,7 @@ fn app(cx: Scope) -> Element { color: "black", width: "100%", height: "100%", - display: "center", - direction: "horizontal", + cross_align: "center", rect { width: "50%", height: "100%", diff --git a/examples/rotate.rs b/examples/rotate.rs index d9f5a0289..a50800984 100644 --- a/examples/rotate.rs +++ b/examples/rotate.rs @@ -49,8 +49,8 @@ fn app(cx: Scope) -> Element { render!( rect { - direction: "both", - display: "center", + main_align: "center", + cross_align: "center", width: "100%", height: "100%", rect { @@ -58,15 +58,15 @@ fn app(cx: Scope) -> Element { background: "rgb(65, 53, 67)", width: "250", height: "250", - direction: "both", - display: "center", + main_align: "center", + cross_align: "center", rect { rotate: "{degrees.1}deg", background: "rgb(143, 67, 238)", width: "180", height: "180", - direction: "both", - display: "center", + main_align: "center", + cross_align: "center", rect { rotate: "{degrees.2}deg", background: "rgb(240, 235, 141)", diff --git a/examples/sidebar.rs b/examples/sidebar.rs index 55d6b41ef..5e3afb45d 100644 --- a/examples/sidebar.rs +++ b/examples/sidebar.rs @@ -27,7 +27,7 @@ fn Sidebar<'a>(cx: Scope<'a>, children: Element<'a>, sidebar: Element<'a>) -> El overflow: "clip", width: "200", height: "100%", - background: "rgb(50, 50, 50)", + background: "rgb(20, 20, 20)", corner_radius: "0 7 0 7", padding: "20", color: "{color}", @@ -92,7 +92,6 @@ fn SidebarItem<'a>( onmouseleave: onmouseleave, width: "100%", height: "auto", - direction: "both", color: "{color}", shadow: "0 2 10 1 rgb(0, 0, 0, 45)", corner_radius: "10", diff --git a/examples/table.rs b/examples/table.rs index 40acff1f4..929a0c5b9 100644 --- a/examples/table.rs +++ b/examples/table.rs @@ -100,8 +100,6 @@ fn app(cx: Scope) -> Element { order_direction: if *order.get() == *order_by { Some(*order_direction.get()) } else { None }, onclick: move |_| on_column_head_click(order_by), label { - width: "100%", - align: "center", "{text}" } } diff --git a/examples/text.rs b/examples/text.rs index 5fb1a8b8c..e778b708e 100644 --- a/examples/text.rs +++ b/examples/text.rs @@ -21,9 +21,7 @@ fn app(cx: Scope) -> Element { background: "black", color: "white", rect { - overflow: "clip", - width: "100%", - height: "60", + height: "80", Button { onclick: move |_| { percentage.set(20.0); @@ -46,7 +44,6 @@ fn app(cx: Scope) -> Element { height: "calc(100% - 60)", rect { background: "red", - direction: "both", label { font_size: "{font_size}", font_family: "Inter", diff --git a/examples/tic_tac_toe.rs b/examples/tic_tac_toe.rs index 296e07b6d..fef9d5660 100644 --- a/examples/tic_tac_toe.rs +++ b/examples/tic_tac_toe.rs @@ -160,8 +160,8 @@ fn app(cx: Scope) -> Element { rect { width: "100%", height: "100%", - display: "center", - direction: "both", + main_align: "center", + cross_align: "center", rect { width: "150", height: "170", @@ -184,8 +184,8 @@ fn app(cx: Scope) -> Element { corner_radius: "6", border: "2 solid rgb(40, 40, 40)", background: "rgb(250, 250, 250)", - display: "center", - direction: "both", + main_align: "center", + cross_align: "center", onclick: move |_| { let mut board = board.write(); if board.winner.is_none(){ diff --git a/examples/touch.rs b/examples/touch.rs index cac7372ba..a1df8ff75 100644 --- a/examples/touch.rs +++ b/examples/touch.rs @@ -32,8 +32,8 @@ fn app(cx: Scope) -> Element { rect { width: "100%", height: "100%", - direction: "both", - display: "center", + main_align: "center", + cross_align: "center", background: "rgb(35, 35, 35)", ontouchcancel: ontouchcancel, ontouchend: ontouchend, diff --git a/examples/transition.rs b/examples/transition.rs index d51dc16d2..65f4de7d7 100644 --- a/examples/transition.rs +++ b/examples/transition.rs @@ -38,7 +38,7 @@ fn app(cx: Scope) -> Element { rect { overflow: "clip", background: "black", - display: "center", + main_align: "center", width: "100%", height: "100%", offset_x: "{size}", @@ -48,7 +48,7 @@ fn app(cx: Scope) -> Element { background: "{background}", padding: "25", corner_radius: "100", - display: "center", + main_align: "center", onclick: anim, label { width: "100%",