From f300a5d7d9837ab827e510153b89c9484bd256ad Mon Sep 17 00:00:00 2001 From: Dr George Atkinson Date: Thu, 28 Nov 2024 15:34:47 +0000 Subject: [PATCH] Auto min size (#33) * Add auto min-size * Add auto min-size * Fix for computing auto size from children * Fix min/max order * Fix auto min content size * Fmt * Clippy * Remove playground * cleanup --- .vscode/launch.json | 9 + Cargo.toml | 3 - examples/menu.rs | 55 +++ playground/Cargo.toml | 11 - playground/src/canvas.rs | 196 ---------- playground/src/main.rs | 731 ----------------------------------- playground/src/properties.rs | 414 -------------------- playground/src/theme.css | 217 ----------- playground/src/tree_panel.rs | 97 ----- src/layout.rs | 587 +++++++++++++++++----------- src/node.rs | 1 + tests/size_constraints.rs | 212 ++++++++++ 12 files changed, 628 insertions(+), 1905 deletions(-) create mode 100644 examples/menu.rs delete mode 100644 playground/Cargo.toml delete mode 100644 playground/src/canvas.rs delete mode 100644 playground/src/main.rs delete mode 100644 playground/src/properties.rs delete mode 100644 playground/src/theme.css delete mode 100644 playground/src/tree_panel.rs diff --git a/.vscode/launch.json b/.vscode/launch.json index f46c43b0..f49e8905 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -1,6 +1,7 @@ { "version": "0.2.0", "configurations": [ + { "type": "lldb", "request": "launch", @@ -8,6 +9,14 @@ "program": "${workspaceFolder}/target/debug/examples/basic", "args": [], "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Menu", + "program": "${workspaceFolder}/target/debug/examples/menu", + "args": [], + "cwd": "${workspaceFolder}" } ] } \ No newline at end of file diff --git a/Cargo.toml b/Cargo.toml index 2ca85a37..c7bcebfb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,9 +8,6 @@ description = "A UI layout engine" repository = "https://github.com/vizia/morphorm" keywords = ["ui", "gui", "layout", "flex"] -[workspace] -members = ["playground"] - [dependencies] smallvec = "1.11.0" diff --git a/examples/menu.rs b/examples/menu.rs new file mode 100644 index 00000000..f6e5baa9 --- /dev/null +++ b/examples/menu.rs @@ -0,0 +1,55 @@ +mod common; +use common::*; + +fn main() { + let mut world = World::default(); + + let root = world.add(None); + world.set_width(root, Units::Pixels(600.0)); + world.set_height(root, Units::Pixels(600.0)); + world.set_child_space(root, Units::Stretch(1.0)); + + let node = world.add(Some(root)); + world.set_width(node, Units::Auto); + world.set_height(node, Units::Auto); + + let child = world.add(Some(node)); + world.set_width(child, Units::Stretch(1.0)); + world.set_min_width(child, Units::Auto); + world.set_height(child, Units::Pixels(50.0)); + world.set_content_size(child, |_, _, height| { + let h = height.unwrap(); + (50.0, h) + }); + + let child = world.add(Some(node)); + world.set_width(child, Units::Stretch(1.0)); + world.set_min_width(child, Units::Auto); + world.set_height(child, Units::Pixels(50.0)); + world.set_content_size(child, |_, _, height| { + let h = height.unwrap(); + (60.0, h) + }); + + let child = world.add(Some(node)); + world.set_width(child, Units::Stretch(1.0)); + world.set_min_width(child, Units::Auto); + world.set_height(child, Units::Pixels(50.0)); + world.set_content_size(child, |_, _, height| { + let h = height.unwrap(); + (130.0, h) + }); + + let child = world.add(Some(node)); + world.set_width(child, Units::Stretch(1.0)); + world.set_min_width(child, Units::Auto); + world.set_height(child, Units::Pixels(50.0)); + world.set_content_size(child, |_, _, height| { + let h = height.unwrap(); + (80.0, h) + }); + + root.layout(&mut world.cache, &world.tree, &world.store, &mut ()); + + render(world, root); +} diff --git a/playground/Cargo.toml b/playground/Cargo.toml deleted file mode 100644 index 4d23f05d..00000000 --- a/playground/Cargo.toml +++ /dev/null @@ -1,11 +0,0 @@ -[package] -name = "playground" -version = "0.1.0" -edition = "2021" - -[dependencies] -# vizia = {path = "../../vizia"} -vizia = {git = "https://github.com/vizia/vizia"} -morphorm-ecs = {path = "../ecs"} -morphorm = {path = ".."} -slotmap = "1.0.6" \ No newline at end of file diff --git a/playground/src/canvas.rs b/playground/src/canvas.rs deleted file mode 100644 index ae45a61d..00000000 --- a/playground/src/canvas.rs +++ /dev/null @@ -1,196 +0,0 @@ -use vizia::prelude::*; - -use ecs::Store; -use morph::{Cache, Node}; - -use morphorm as morph; -use morphorm_ecs as ecs; -use vizia::vg; - -use crate::{AppData, AppEvent}; - -pub struct CanvasView {} - -impl CanvasView { - pub fn new(cx: &mut Context) -> Handle { - Self {}.build(cx, |_| {}) - } -} - -impl View for CanvasView { - fn element(&self) -> Option<&'static str> { - Some("canvas") - } - - fn event(&mut self, cx: &mut EventContext, event: &mut Event) { - event.map(|window_event, _| match window_event { - WindowEvent::GeometryChanged(geo) => { - if geo.contains(GeoChanged::WIDTH_CHANGED) || geo.contains(GeoChanged::HEIGHT_CHANGED) { - cx.emit(AppEvent::SetCanvasSize( - cx.cache.get_width(cx.current()) - 100.0, - cx.cache.get_height(cx.current()) - 100.0, - )); - } - } - - WindowEvent::MouseDown(button) if *button == MouseButton::Left => { - if let Some(app_data) = cx.data::() { - let posx = cx.cache.get_posx(cx.current()) + 50.0; - let posy = cx.cache.get_posy(cx.current()) + 50.0; - let selected = select_node( - &app_data.root_node, - &app_data.world.tree, - &app_data.world.cache, - posx, - posy, - cx.mouse().cursorx, - cx.mouse().cursory, - ); - // println!("selected: {:?}", selected); - if cx.modifiers().contains(Modifiers::SHIFT) { - if let Some(selected) = selected { - cx.emit(AppEvent::MultiSelectNode(*selected)); - } - } else { - cx.emit(AppEvent::SelectNode(selected.copied())); - } - } - } - - _ => {} - }); - } - - fn draw(&self, cx: &mut DrawContext, canvas: &mut vizia::view::Canvas) { - if let Some(app_data) = cx.data::() { - let bounds = cx.bounds(); - - let mut path = vg::Path::new(); - path.rect(bounds.x, bounds.y, bounds.w, bounds.h); - let background_color = cx.background_color(); - canvas.fill_path(&path, &vg::Paint::color(background_color.into())); - - draw_node( - &app_data.root_node, - &app_data.selected_nodes, - &app_data.world.tree, - &app_data.world.cache, - &app_data.world.store, - (bounds.x + 50.0, bounds.y + 50.0), - canvas, - ) - - // let root_node = app_data.root_node; - - // app_data.world.set_width(root_node, morph::Units::Pixels(bounds.w)); - // app_data.world.set_height(root_node, morph::Units::Pixels(bounds.h)); - - // layout(&root_node, None, bounds.w, bounds.h, &mut app_data.world.cache, &app_data.world.tree, &app_data.world.store); - } - } -} - -fn select_node<'a, N: Node>( - node: &'a N, - tree: &'a N::Tree, - cache: &impl Cache, - parent_posx: f32, - parent_posy: f32, - mousex: f32, - mousey: f32, -) -> Option<&'a N> { - let posx = parent_posx + cache.posx(node); - let posy = parent_posy + cache.posy(node); - let width = cache.width(node); - let height = cache.height(node); - - let mut selected_child = None; - let children = node.children(tree).collect::>(); - for child in children.into_iter().rev() { - selected_child = select_node(child, tree, cache, posx, posy, mousex, mousey); - if selected_child.is_some() { - break; - } - } - - if selected_child.is_none() { - if mousex >= posx && mousex < posx + width && mousey >= posy && mousey < posy + height { - Some(node) - } else { - None - } - } else { - selected_child - } -} - -fn draw_node>( - node: &N, - selected_nodes: &Option>, - tree: &N::Tree, - cache: &impl Cache, - store: &Store, - parent_pos: (f32, f32), - canvas: &mut Canvas, -) { - let posx = cache.posx(node); - let posy = cache.posy(node); - let width = cache.width(node); - let height = cache.height(node); - - let red = store.red.get(node.key()).unwrap_or(&0u8); - let green = store.green.get(node.key()).unwrap_or(&0u8); - let blue = store.blue.get(node.key()).unwrap_or(&0u8); - - let mut path = vg::Path::new(); - path.rect(parent_pos.0 + posx, parent_pos.1 + posy, width, height); - let paint = vg::Paint::color(vg::Color::rgb(*red, *green, *blue)); - canvas.fill_path(&path, &paint); - - if let Some(selected_nodes) = selected_nodes { - for selected_node in selected_nodes { - if node.key() == selected_node.key() { - let mut selection_paint = vg::Paint::color(vg::Color::rgb(72, 113, 174)); - selection_paint.set_line_width(4.0); - canvas.stroke_path(&path, &selection_paint); - } - } - } - - // if let Some(text) = store.text.get(&node.key()) { - // let mut paint = vg::Paint::color(vg::Color::black()); - // paint.set_font_size(48.0); - // paint.set_text_align(vg::Align::Left); - // paint.set_text_baseline(vg::Baseline::Top); - // paint.set_font(&vec![font]); - - // let font_metrics = canvas.measure_font(&paint).expect("Error measuring font"); - - // let mut y = 0.0; - // if let Ok(text_lines) = canvas.break_text_vec(width.ceil(), text, &paint) { - // //println!("font height: {}", font_metrics.height()); - // for line in text_lines.into_iter() { - // let _ = canvas.fill_text(parent_posx + posx, parent_posy + posy + y, &text[line], &paint); - // // println!("{} {}", y, font_metrics.height()); - // y += font_metrics.height(); - // } - // } - // } else { - // let mut paint = vg::Paint::color(vg::Color::black()); - // paint.set_font_size(48.0); - // paint.set_text_align(vg::Align::Center); - // paint.set_text_baseline(vg::Baseline::Middle); - // paint.set_font(&vec![font]); - - // let _ = canvas.fill_text( - // parent_posx + posx + width / 2.0, - // parent_posy + posy + height / 2.0, - // &node.key().0.to_string(), - // &paint, - // ); - // } - - for child in node.children(tree) { - draw_node(child, selected_nodes, tree, cache, store, (posx + parent_pos.0, posy + parent_pos.1), canvas); - } -} diff --git a/playground/src/main.rs b/playground/src/main.rs deleted file mode 100644 index 5dd2831a..00000000 --- a/playground/src/main.rs +++ /dev/null @@ -1,731 +0,0 @@ -use morphorm as morph; -use morphorm::*; -use morphorm_ecs as ecs; -use slotmap::SecondaryMap; -use vizia::prelude::*; - -mod tree_panel; -use tree_panel::*; - -mod canvas; -use canvas::*; - -mod properties; -use properties::*; - -pub enum AppEvent { - Relayout, - SetCanvasSize(f32, f32), - - AddChildNode, - AddSiblingNode, - DeleteNodes, - SelectNode(Option), - MultiSelectNode(ecs::Entity), - - SetLeft(morph::Units), - SetWidth(morph::Units), - SetRight(morph::Units), - - AlignLeft, - AlignCenter, - AlignRight, - FillWidth, - - SetTop(morph::Units), - SetHeight(morph::Units), - SetBottom(morph::Units), - - AlignTop, - AlignMiddle, - AlignBottom, - FillHeight, - - SetLayoutType(usize), - SetPositionType(usize), - - SetChildLeft(morph::Units), - SetColBetween(morph::Units), - SetChildRight(morph::Units), - - AlignChildLeft, - AlignChildCenter, - AlignChildRight, - - SetChildTop(morph::Units), - SetRowBetween(morph::Units), - SetChildBottom(morph::Units), - - AlignChildTop, - AlignChildMiddle, - AlignChildBottom, - - SetMinLeft(morph::Units), - SetMinWidth(morph::Units), - SetMinRight(morph::Units), - SetMaxLeft(morph::Units), - SetMaxWidth(morph::Units), - SetMaxRight(morph::Units), -} - -#[derive(Lens)] -pub struct AppData { - canvas_width: f32, - canvas_height: f32, - - tree_changed: usize, - - world: ecs::World, - root_node: ecs::Entity, - selected_nodes: Option>, - hovered_node: Option, - - collapsed: SecondaryMap, - - left: morph::Units, - width: morph::Units, - right: morph::Units, - - top: morph::Units, - height: morph::Units, - bottom: morph::Units, - - selected_layout_type: usize, - layout_type_list: Vec<&'static str>, - - position_type_list: Vec<&'static str>, - selected_position_type: usize, - - child_left: morph::Units, - col_between: morph::Units, - child_right: morph::Units, - - child_top: morph::Units, - row_between: morph::Units, - child_bottom: morph::Units, - - min_left: morph::Units, - min_width: morph::Units, - min_right: morph::Units, - max_left: morph::Units, - max_width: morph::Units, - max_right: morph::Units, -} - -impl Default for AppData { - fn default() -> Self { - AppData::new() - } -} - -impl AppData { - pub fn new() -> Self { - let mut world = ecs::World::default(); - let root_node = world.add(None); - world.set_width(root_node, morph::Units::Pixels(600.0)); - world.set_height(root_node, morph::Units::Pixels(600.0)); - world.set_child_left(root_node, morph::Units::Stretch(1.0)); - world.set_child_right(root_node, morph::Units::Stretch(1.0)); - world.set_child_top(root_node, morph::Units::Stretch(1.0)); - world.set_child_bottom(root_node, morph::Units::Stretch(1.0)); - world.set_col_between(root_node, morph::Units::Stretch(1.0)); - world.set_row_between(root_node, morph::Units::Stretch(1.0)); - - root_node.layout(&mut world.cache, &world.tree, &world.store, &mut ()); - - Self { - canvas_width: 600.0, - canvas_height: 600.0, - - tree_changed: 0, - - world, - root_node, - selected_nodes: Some(vec![root_node]), - hovered_node: None, - - collapsed: SecondaryMap::new(), - - left: morph::Units::Auto, - width: morph::Units::Pixels(600.0), - right: morph::Units::Auto, - - top: morph::Units::Auto, - height: morph::Units::Pixels(600.0), - bottom: morph::Units::Auto, - - selected_layout_type: 0, - layout_type_list: vec!["Row", "Column"], - - position_type_list: vec!["Parent Directed", "Self Directed"], - selected_position_type: 0, - - child_left: morph::Units::Stretch(1.0), - col_between: morph::Units::Stretch(1.0), - child_right: morph::Units::Stretch(1.0), - - child_top: morph::Units::Stretch(1.0), - row_between: morph::Units::Stretch(1.0), - child_bottom: morph::Units::Stretch(1.0), - - min_left: morph::Units::Auto, - min_width: morph::Units::Auto, - min_right: morph::Units::Auto, - max_left: morph::Units::Auto, - max_width: morph::Units::Auto, - max_right: morph::Units::Auto, - } - } - - fn sync(&mut self, node: ecs::Entity) { - self.left = self.world.store.left.get(node).copied().unwrap_or_default(); - self.right = self.world.store.right.get(node).copied().unwrap_or_default(); - self.top = self.world.store.top.get(node).copied().unwrap_or_default(); - self.bottom = self.world.store.bottom.get(node).copied().unwrap_or_default(); - self.width = self.world.store.width.get(node).copied().unwrap_or_default(); - self.height = self.world.store.height.get(node).copied().unwrap_or_default(); - - self.child_left = self.world.store.child_left.get(node).copied().unwrap_or_default(); - self.child_right = self.world.store.child_right.get(node).copied().unwrap_or_default(); - self.child_top = self.world.store.child_top.get(node).copied().unwrap_or_default(); - self.child_bottom = self.world.store.child_bottom.get(node).copied().unwrap_or_default(); - self.col_between = self.world.store.col_between.get(node).copied().unwrap_or_default(); - self.row_between = self.world.store.row_between.get(node).copied().unwrap_or_default(); - - self.selected_layout_type = match self.world.store.layout_type.get(node).copied().unwrap_or_default() { - morph::LayoutType::Column => 0, - morph::LayoutType::Row => 1, - }; - self.selected_position_type = match self.world.store.position_type.get(node).copied().unwrap_or_default() { - morph::PositionType::ParentDirected => 0, - morph::PositionType::SelfDirected => 1, - }; - - self.min_left = self.world.store.min_left.get(node).copied().unwrap_or_default(); - self.min_width = self.world.store.min_width.get(node).copied().unwrap_or_default(); - self.min_right = self.world.store.min_right.get(node).copied().unwrap_or_default(); - self.max_left = self.world.store.max_left.get(node).copied().unwrap_or_default(); - self.max_width = self.world.store.max_width.get(node).copied().unwrap_or_default(); - self.max_right = self.world.store.max_right.get(node).copied().unwrap_or_default(); - } -} - -impl Model for AppData { - fn event(&mut self, cx: &mut EventContext, event: &mut Event) { - event.map(|app_event, _| match app_event { - AppEvent::Relayout => { - self.root_node.layout(&mut self.world.cache, &self.world.tree, &self.world.store, &mut ()); - cx.needs_redraw(); - } - - AppEvent::SetCanvasSize(width, height) => { - self.canvas_width = *width; - self.canvas_height = *height; - self.world.set_width(self.root_node, morph::Units::Pixels(*width)); - self.world.set_height(self.root_node, morph::Units::Pixels(*height)); - cx.emit(AppEvent::Relayout); - } - - AppEvent::AddChildNode => { - let node = if let Some(selected) = self.selected_nodes.as_ref().and_then(|nodes| nodes.last()) { - self.world.add(Some(*selected)) - } else { - self.world.add(Some(self.root_node)) - }; - - self.world.set_width(node, morph::Units::Pixels(100.0)); - self.world.set_height(node, morph::Units::Pixels(100.0)); - self.world.set_left(node, morph::Units::Auto); - self.world.set_right(node, morph::Units::Auto); - self.world.set_top(node, morph::Units::Auto); - self.world.set_bottom(node, morph::Units::Auto); - self.world.set_child_left(node, morph::Units::Stretch(1.0)); - self.world.set_child_right(node, morph::Units::Stretch(1.0)); - self.world.set_child_top(node, morph::Units::Stretch(1.0)); - self.world.set_child_bottom(node, morph::Units::Stretch(1.0)); - self.world.set_col_between(node, morph::Units::Stretch(1.0)); - self.world.set_row_between(node, morph::Units::Stretch(1.0)); - - self.tree_changed += 1; - - cx.emit(AppEvent::SelectNode(Some(node))); - cx.emit(AppEvent::Relayout); - } - - AppEvent::AddSiblingNode => { - let node = if let Some(parent) = self - .selected_nodes - .as_ref() - .and_then(|nodes| nodes.last()) - .and_then(|selected| self.world.tree.get_parent(selected)) - { - self.world.add(Some(*parent)) - } else { - self.world.add(Some(self.root_node)) - }; - - self.world.set_width(node, morph::Units::Pixels(100.0)); - self.world.set_height(node, morph::Units::Pixels(100.0)); - self.world.set_left(node, morph::Units::Auto); - self.world.set_right(node, morph::Units::Auto); - self.world.set_top(node, morph::Units::Auto); - self.world.set_bottom(node, morph::Units::Auto); - self.world.set_child_left(node, morph::Units::Stretch(1.0)); - self.world.set_child_right(node, morph::Units::Stretch(1.0)); - self.world.set_child_top(node, morph::Units::Stretch(1.0)); - self.world.set_child_bottom(node, morph::Units::Stretch(1.0)); - self.world.set_col_between(node, morph::Units::Stretch(1.0)); - self.world.set_row_between(node, morph::Units::Stretch(1.0)); - - self.tree_changed += 1; - - cx.emit(AppEvent::SelectNode(Some(node))); - cx.emit(AppEvent::Relayout); - } - - AppEvent::DeleteNodes => { - if let Some(selected_nodes) = &self.selected_nodes { - for node in selected_nodes { - if *node != self.root_node { - self.world.remove(*node); - } - } - - self.tree_changed += 1; - - cx.emit(AppEvent::Relayout); - } - } - - AppEvent::SelectNode(selected) => { - if let Some(node) = selected { - self.selected_nodes = Some(vec![*node]); - - self.sync(*node); - } else { - self.selected_nodes = None; - } - - cx.needs_redraw(); - } - - AppEvent::MultiSelectNode(node) => { - if let Some(nodes) = &mut self.selected_nodes { - if let Some((index, _)) = nodes.iter().enumerate().find(|(_, &n)| n == *node) { - nodes.remove(index); - } else { - nodes.push(*node); - } - } else { - self.selected_nodes = Some(vec![*node]); - } - - self.sync(*node); - - cx.needs_redraw(); - } - - AppEvent::SetLeft(value) => { - if let Some(nodes) = &self.selected_nodes { - for selected in nodes { - self.world.set_left(*selected, *value); - self.left = *value; - } - cx.emit(AppEvent::Relayout); - } - } - - AppEvent::SetRight(value) => { - if let Some(nodes) = &self.selected_nodes { - for selected in nodes { - self.world.set_right(*selected, *value); - self.right = *value; - } - cx.emit(AppEvent::Relayout); - } - } - - AppEvent::SetTop(value) => { - if let Some(nodes) = &self.selected_nodes { - for selected in nodes { - self.world.set_top(*selected, *value); - self.top = *value; - } - cx.emit(AppEvent::Relayout); - } - } - - AppEvent::SetBottom(value) => { - if let Some(nodes) = &self.selected_nodes { - for selected in nodes { - self.world.set_bottom(*selected, *value); - self.bottom = *value; - } - cx.emit(AppEvent::Relayout); - } - } - - AppEvent::SetWidth(value) => { - if let Some(nodes) = &self.selected_nodes { - for selected in nodes { - self.world.set_width(*selected, *value); - self.width = *value; - } - cx.emit(AppEvent::Relayout); - } - } - - AppEvent::SetHeight(value) => { - if let Some(nodes) = &self.selected_nodes { - for selected in nodes { - self.world.set_height(*selected, *value); - self.height = *value; - } - cx.emit(AppEvent::Relayout); - } - } - - AppEvent::SetLayoutType(selected_layout_type) => { - if let Some(nodes) = &self.selected_nodes { - for selected in nodes { - let layout_type = match *selected_layout_type { - 0 => morph::LayoutType::Row, - _ => morph::LayoutType::Column, - }; - self.world.set_layout_type(*selected, layout_type); - self.selected_layout_type = *selected_layout_type; - } - cx.emit(AppEvent::Relayout); - } - } - - AppEvent::SetPositionType(selected_position_type) => { - if let Some(nodes) = &self.selected_nodes { - for selected in nodes { - let position_type = match *selected_position_type { - 0 => morph::PositionType::ParentDirected, - _ => morph::PositionType::SelfDirected, - }; - self.world.set_position_type(*selected, position_type); - self.selected_position_type = *selected_position_type; - } - cx.emit(AppEvent::Relayout); - } - } - - AppEvent::SetChildLeft(value) => { - if let Some(nodes) = &self.selected_nodes { - for selected in nodes { - self.world.set_child_left(*selected, *value); - self.child_left = *value; - } - cx.emit(AppEvent::Relayout); - } - } - - AppEvent::SetChildRight(value) => { - if let Some(nodes) = &self.selected_nodes { - for selected in nodes { - self.world.set_child_right(*selected, *value); - self.child_right = *value; - } - cx.emit(AppEvent::Relayout); - } - } - - AppEvent::SetChildTop(value) => { - if let Some(nodes) = &self.selected_nodes { - for selected in nodes { - self.world.set_child_top(*selected, *value); - self.child_top = *value; - } - cx.emit(AppEvent::Relayout); - } - } - - AppEvent::SetChildBottom(value) => { - if let Some(nodes) = &self.selected_nodes { - for selected in nodes { - self.world.set_child_bottom(*selected, *value); - self.child_bottom = *value; - } - cx.emit(AppEvent::Relayout); - } - } - - AppEvent::SetColBetween(value) => { - if let Some(nodes) = &self.selected_nodes { - for selected in nodes { - self.world.set_col_between(*selected, *value); - self.col_between = *value; - } - cx.emit(AppEvent::Relayout); - } - } - - AppEvent::SetRowBetween(value) => { - if let Some(nodes) = &self.selected_nodes { - for selected in nodes { - self.world.set_row_between(*selected, *value); - self.row_between = *value; - } - cx.emit(AppEvent::Relayout); - } - } - - AppEvent::AlignLeft => { - if let Some(nodes) = &self.selected_nodes { - for selected in nodes { - self.world.set_left(*selected, morph::Units::Pixels(0.0)); - self.world.set_right(*selected, morph::Units::Stretch(1.0)); - self.left = morph::Units::Pixels(0.0); - self.right = morph::Units::Stretch(1.0); - } - cx.emit(AppEvent::Relayout); - } - } - - AppEvent::AlignRight => { - if let Some(nodes) = &self.selected_nodes { - for selected in nodes { - self.world.set_right(*selected, morph::Units::Pixels(0.0)); - self.world.set_left(*selected, morph::Units::Stretch(1.0)); - self.right = morph::Units::Pixels(0.0); - self.left = morph::Units::Stretch(1.0); - } - cx.emit(AppEvent::Relayout); - } - } - - AppEvent::AlignCenter => { - if let Some(nodes) = &self.selected_nodes { - for selected in nodes { - self.world.set_left(*selected, morph::Units::Stretch(1.0)); - self.world.set_right(*selected, morph::Units::Stretch(1.0)); - self.left = morph::Units::Stretch(1.0); - self.right = morph::Units::Stretch(1.0); - } - cx.emit(AppEvent::Relayout); - } - } - - AppEvent::FillWidth => { - if let Some(nodes) = &self.selected_nodes { - for selected in nodes { - self.world.set_right(*selected, morph::Units::Pixels(0.0)); - self.world.set_left(*selected, morph::Units::Pixels(0.0)); - self.world.set_width(*selected, morph::Units::Stretch(1.0)); - self.right = morph::Units::Pixels(0.0); - self.left = morph::Units::Pixels(0.0); - self.width = morph::Units::Stretch(1.0); - } - cx.emit(AppEvent::Relayout); - } - } - - AppEvent::AlignTop => { - if let Some(nodes) = &self.selected_nodes { - for selected in nodes { - self.world.set_top(*selected, morph::Units::Pixels(0.0)); - self.world.set_bottom(*selected, morph::Units::Stretch(1.0)); - self.top = morph::Units::Pixels(0.0); - self.bottom = morph::Units::Stretch(1.0); - } - cx.emit(AppEvent::Relayout); - } - } - - AppEvent::AlignBottom => { - if let Some(nodes) = &self.selected_nodes { - for selected in nodes { - self.world.set_bottom(*selected, morph::Units::Pixels(0.0)); - self.world.set_top(*selected, morph::Units::Stretch(1.0)); - self.bottom = morph::Units::Pixels(0.0); - self.top = morph::Units::Stretch(1.0); - } - cx.emit(AppEvent::Relayout); - } - } - - AppEvent::AlignMiddle => { - if let Some(nodes) = &self.selected_nodes { - for selected in nodes { - self.world.set_top(*selected, morph::Units::Stretch(1.0)); - self.world.set_bottom(*selected, morph::Units::Stretch(1.0)); - self.top = morph::Units::Stretch(1.0); - self.bottom = morph::Units::Stretch(1.0); - } - cx.emit(AppEvent::Relayout); - } - } - - AppEvent::FillHeight => { - if let Some(nodes) = &self.selected_nodes { - for selected in nodes { - self.world.set_bottom(*selected, morph::Units::Auto); - self.world.set_top(*selected, morph::Units::Auto); - self.world.set_height(*selected, morph::Units::Stretch(1.0)); - self.bottom = morph::Units::Auto; - self.top = morph::Units::Auto; - self.height = morph::Units::Stretch(1.0); - } - cx.emit(AppEvent::Relayout); - } - } - - AppEvent::AlignChildLeft => { - if let Some(nodes) = &self.selected_nodes { - for selected in nodes { - self.world.set_child_left(*selected, morph::Units::Pixels(0.0)); - self.world.set_child_right(*selected, morph::Units::Stretch(1.0)); - self.child_left = morph::Units::Auto; - self.child_right = morph::Units::Stretch(1.0); - } - cx.emit(AppEvent::Relayout); - } - } - - AppEvent::AlignChildRight => { - if let Some(nodes) = &self.selected_nodes { - for selected in nodes { - self.world.set_child_right(*selected, morph::Units::Pixels(0.0)); - self.world.set_child_left(*selected, morph::Units::Stretch(1.0)); - self.child_right = morph::Units::Auto; - self.child_left = morph::Units::Stretch(1.0); - } - cx.emit(AppEvent::Relayout); - } - } - - AppEvent::AlignChildCenter => { - if let Some(nodes) = &self.selected_nodes { - for selected in nodes { - self.world.set_child_left(*selected, morph::Units::Stretch(1.0)); - self.world.set_child_right(*selected, morph::Units::Stretch(1.0)); - self.child_left = morph::Units::Stretch(1.0); - self.child_right = morph::Units::Stretch(1.0); - } - cx.emit(AppEvent::Relayout); - } - } - - AppEvent::AlignChildTop => { - if let Some(nodes) = &self.selected_nodes { - for selected in nodes { - self.world.set_child_top(*selected, morph::Units::Pixels(0.0)); - self.world.set_child_bottom(*selected, morph::Units::Stretch(1.0)); - self.child_top = morph::Units::Auto; - self.child_bottom = morph::Units::Stretch(1.0); - } - cx.emit(AppEvent::Relayout); - } - } - - AppEvent::AlignChildBottom => { - if let Some(nodes) = &self.selected_nodes { - for selected in nodes { - self.world.set_child_bottom(*selected, morph::Units::Pixels(0.0)); - self.world.set_child_top(*selected, morph::Units::Stretch(1.0)); - self.child_bottom = morph::Units::Auto; - self.child_top = morph::Units::Stretch(1.0); - } - cx.emit(AppEvent::Relayout); - } - } - - AppEvent::AlignChildMiddle => { - if let Some(nodes) = &self.selected_nodes { - for selected in nodes { - self.world.set_child_top(*selected, morph::Units::Stretch(1.0)); - self.world.set_child_bottom(*selected, morph::Units::Stretch(1.0)); - self.child_top = morph::Units::Stretch(1.0); - self.child_bottom = morph::Units::Stretch(1.0); - } - cx.emit(AppEvent::Relayout); - } - } - - AppEvent::SetMinLeft(value) => { - if let Some(nodes) = &self.selected_nodes { - for selected in nodes { - self.world.set_min_left(*selected, *value); - self.min_left = *value; - } - cx.emit(AppEvent::Relayout); - } - } - - AppEvent::SetMinWidth(value) => { - if let Some(nodes) = &self.selected_nodes { - for selected in nodes { - self.world.set_min_width(*selected, *value); - self.min_width = *value; - } - cx.emit(AppEvent::Relayout); - } - } - - AppEvent::SetMinRight(value) => { - if let Some(nodes) = &self.selected_nodes { - for selected in nodes { - self.world.set_min_right(*selected, *value); - self.min_right = *value; - } - cx.emit(AppEvent::Relayout); - } - } - - AppEvent::SetMaxLeft(value) => { - if let Some(nodes) = &self.selected_nodes { - for selected in nodes { - self.world.set_max_left(*selected, *value); - self.max_left = *value; - } - cx.emit(AppEvent::Relayout); - } - } - - AppEvent::SetMaxWidth(value) => { - if let Some(nodes) = &self.selected_nodes { - for selected in nodes { - self.world.set_max_width(*selected, *value); - self.max_width = *value; - } - cx.emit(AppEvent::Relayout); - } - } - - AppEvent::SetMaxRight(value) => { - if let Some(nodes) = &self.selected_nodes { - for selected in nodes { - self.world.set_max_right(*selected, *value); - self.max_right = *value; - } - cx.emit(AppEvent::Relayout); - } - } - }); - } -} - -fn main() { - Application::new(|cx| { - cx.add_stylesheet(include_style!("src/theme.css")).expect("Failed to find stylesheet"); - - // cx.emit(EnvironmentEvent::SetThemeMode(ThemeMode::DarkMode)); - - AppData::new().build(cx); - HStack::new(cx, |cx| { - // Treeview - TreePanel::new(cx).width(Pixels(250.0)); - // Canvas - CanvasView::new(cx).background_color(Color::rgb(29, 29, 29)); - // Properties - PropertiesPanel::new(cx).width(Pixels(300.0)); - }); - }) - .title("Morphorm Playground") - .inner_size((1000, 600)) - .ignore_default_theme() - .run(); -} diff --git a/playground/src/properties.rs b/playground/src/properties.rs deleted file mode 100644 index d1eb3e35..00000000 --- a/playground/src/properties.rs +++ /dev/null @@ -1,414 +0,0 @@ -use vizia::icons::{ - ICON_ARROW_AUTOFIT_HEIGHT, ICON_ARROW_AUTOFIT_WIDTH, ICON_ARROW_BAR_DOWN, ICON_ARROW_BAR_LEFT, - ICON_ARROW_BAR_RIGHT, ICON_ARROW_BAR_UP, ICON_BOX_ALIGN_BOTTOM, ICON_BOX_ALIGN_BOTTOM_LEFT, - ICON_BOX_ALIGN_BOTTOM_RIGHT, ICON_BOX_ALIGN_LEFT, ICON_BOX_ALIGN_RIGHT, ICON_BOX_ALIGN_TOP, - ICON_BOX_ALIGN_TOP_LEFT, ICON_BOX_ALIGN_TOP_RIGHT, ICON_BOX_MARGIN, ICON_LAYOUT_ALIGN_BOTTOM, - ICON_LAYOUT_ALIGN_LEFT, ICON_LAYOUT_ALIGN_RIGHT, ICON_LAYOUT_ALIGN_TOP, ICON_LAYOUT_DISTRIBUTE_HORIZONTAL, - ICON_LAYOUT_DISTRIBUTE_VERTICAL, -}; -use vizia::prelude::*; - -use morphorm as morph; - -use crate::{AppData, AppEvent}; - -pub struct PropertiesPanel {} - -impl PropertiesPanel { - pub fn new(cx: &mut Context) -> Handle { - Self {}.build(cx, |cx| { - ScrollView::new(cx, 0.0, 0.0, false, true, |cx| { - VStack::new(cx, |cx| { - VStack::new(cx, |cx| { - Label::new(cx, "Space and Size").class("panel-title"); - - HStack::new(cx, |cx| { - Label::new(cx, "Position Type"); - PickList::new(cx, AppData::position_type_list, AppData::selected_position_type, true) - .on_select(|cx, index| cx.emit(AppEvent::SetPositionType(index))) - .width(Stretch(1.0)); - }) - .class("row"); - - HStack::new(cx, |cx| { - // VStack::new(cx, |cx| { - unit_box(cx, ICON_ARROW_BAR_RIGHT, AppData::left, AppEvent::SetLeft); - // }).height(Auto); - - // VStack::new(cx, |cx| { - unit_box(cx, ICON_ARROW_AUTOFIT_WIDTH, AppData::width, AppEvent::SetWidth); - // }).height(Auto); - - // VStack::new(cx, |cx| { - unit_box(cx, ICON_ARROW_BAR_LEFT, AppData::right, AppEvent::SetRight); - // }).height(Auto); - }) - .class("row"); - - HStack::new(cx, |cx| { - // VStack::new(cx, |cx| { - unit_box(cx, ICON_ARROW_BAR_DOWN, AppData::top, AppEvent::SetTop); - // }).height(Auto); - - // VStack::new(cx, |cx| { - unit_box(cx, ICON_ARROW_AUTOFIT_HEIGHT, AppData::height, AppEvent::SetHeight); - // }).height(Auto); - - // VStack::new(cx, |cx| { - unit_box(cx, ICON_ARROW_BAR_UP, AppData::bottom, AppEvent::SetBottom); - // }).height(Auto); - }) - .col_between(Pixels(10.0)) - .height(Auto) - .class("row"); - }) - .class("panel"); - - Element::new(cx).class("divider"); - - // VStack::new(cx, |cx| { - // Label::new(cx, "Space and Size Constraints").class("panel-title"); - - // HStack::new(cx, |cx| { - // VStack::new(cx, |cx| { - // unit_box(cx, "min-left", AppData::min_left, AppEvent::SetMinLeft); - // }); - - // VStack::new(cx, |cx| { - // unit_box(cx, "min-width", AppData::min_width, AppEvent::SetMinWidth); - // }); - - // VStack::new(cx, |cx| { - // unit_box(cx, "min-right", AppData::min_right, AppEvent::SetMinRight); - // }); - // }) - // .class("row"); - - // HStack::new(cx, |cx| { - // VStack::new(cx, |cx| { - // unit_box(cx, "max-left", AppData::max_left, AppEvent::SetMaxLeft); - // }); - - // VStack::new(cx, |cx| { - // unit_box(cx, "max-width", AppData::max_width, AppEvent::SetMaxWidth); - // }); - - // VStack::new(cx, |cx| { - // unit_box(cx, "max-right", AppData::max_right, AppEvent::SetMaxRight); - // }); - // }) - // .col_between(Pixels(10.0)) - // .height(Auto) - // .class("row"); - // }) - // .class("panel"); - - // VStack::new(cx, |cx| { - // Label::new(cx, "Alignment").class("panel-title"); - // HStack::new(cx, |cx| { - // Button::new( - // cx, - // |cx| { - // cx.emit(AppEvent::AlignTop); - // cx.emit(AppEvent::AlignLeft); - // }, - // |cx| Label::new(cx, ""), - // ); - - // Button::new( - // cx, - // |cx| { - // cx.emit(AppEvent::AlignTop); - // cx.emit(AppEvent::AlignCenter); - // }, - // |cx| Label::new(cx, ""), - // ); - - // Button::new( - // cx, - // |cx| { - // cx.emit(AppEvent::AlignTop); - // cx.emit(AppEvent::AlignRight); - // }, - // |cx| Label::new(cx, ""), - // ); - // }) - // .left(Stretch(1.0)) - // .right(Stretch(1.0)); - - // HStack::new(cx, |cx| { - // Button::new( - // cx, - // |cx| { - // cx.emit(AppEvent::AlignMiddle); - // cx.emit(AppEvent::AlignLeft); - // }, - // |cx| Label::new(cx, ""), - // ); - - // Button::new( - // cx, - // |cx| { - // cx.emit(AppEvent::AlignMiddle); - // cx.emit(AppEvent::AlignCenter); - // }, - // |cx| Label::new(cx, ""), - // ); - - // Button::new( - // cx, - // |cx| { - // cx.emit(AppEvent::AlignMiddle); - // cx.emit(AppEvent::AlignRight); - // }, - // |cx| Label::new(cx, ""), - // ); - // }) - // .left(Stretch(1.0)) - // .right(Stretch(1.0)); - - // HStack::new(cx, |cx| { - // Button::new( - // cx, - // |cx| { - // cx.emit(AppEvent::AlignBottom); - // cx.emit(AppEvent::AlignLeft); - // }, - // |cx| Label::new(cx, ""), - // ); - - // Button::new( - // cx, - // |cx| { - // cx.emit(AppEvent::AlignBottom); - // cx.emit(AppEvent::AlignCenter); - // }, - // |cx| Label::new(cx, ""), - // ); - - // Button::new( - // cx, - // |cx| { - // cx.emit(AppEvent::AlignBottom); - // cx.emit(AppEvent::AlignRight); - // }, - // |cx| Label::new(cx, ""), - // ); - // }) - // .left(Stretch(1.0)) - // .right(Stretch(1.0)); - // }) - // .class("panel") - // .class("align"); - - VStack::new(cx, |cx| { - Label::new(cx, "Child Layout").class("panel-title"); - HStack::new(cx, |cx| { - Label::new(cx, "Layout Type").width(Auto); - PickList::new(cx, AppData::layout_type_list, AppData::selected_layout_type, true) - .on_select(|cx, index| cx.emit(AppEvent::SetLayoutType(index))) - .width(Stretch(1.0)); - }) - .col_between(Pixels(10.0)) - .height(Auto) - .class("row"); - - HStack::new(cx, |cx| { - unit_box(cx, ICON_LAYOUT_ALIGN_LEFT, AppData::child_left, AppEvent::SetChildLeft); - - unit_box( - cx, - ICON_LAYOUT_DISTRIBUTE_VERTICAL, - AppData::col_between, - AppEvent::SetColBetween, - ); - - unit_box(cx, ICON_LAYOUT_ALIGN_RIGHT, AppData::child_right, AppEvent::SetChildRight); - }) - .col_between(Pixels(10.0)) - .height(Auto) - .class("row"); - - HStack::new(cx, |cx| { - unit_box(cx, ICON_LAYOUT_ALIGN_TOP, AppData::child_top, AppEvent::SetChildTop); - - unit_box( - cx, - ICON_LAYOUT_DISTRIBUTE_HORIZONTAL, - AppData::row_between, - AppEvent::SetRowBetween, - ); - - unit_box(cx, ICON_LAYOUT_ALIGN_BOTTOM, AppData::child_bottom, AppEvent::SetChildBottom); - }) - .col_between(Pixels(10.0)) - .height(Auto) - .class("row"); - }) - .class("panel"); - - Element::new(cx).class("divider"); - - VStack::new(cx, |cx| { - Label::new(cx, "Child Alignment").class("panel-title"); - HStack::new(cx, |cx| { - Button::new( - cx, - |cx| { - cx.emit(AppEvent::AlignChildTop); - cx.emit(AppEvent::AlignChildLeft); - }, - |cx| Icon::new(cx, ICON_BOX_ALIGN_TOP_LEFT), - ); - - Button::new( - cx, - |cx| { - cx.emit(AppEvent::AlignChildTop); - cx.emit(AppEvent::AlignChildCenter); - }, - |cx| Icon::new(cx, ICON_BOX_ALIGN_TOP), - ); - - Button::new( - cx, - |cx| { - cx.emit(AppEvent::AlignChildTop); - cx.emit(AppEvent::AlignChildRight); - }, - |cx| Icon::new(cx, ICON_BOX_ALIGN_TOP_RIGHT), - ); - }) - .height(Auto) - .child_left(Stretch(1.0)) - .child_right(Stretch(1.0)); - - HStack::new(cx, |cx| { - Button::new( - cx, - |cx| { - cx.emit(AppEvent::AlignChildMiddle); - cx.emit(AppEvent::AlignChildLeft); - }, - |cx| Icon::new(cx, ICON_BOX_ALIGN_LEFT), - ); - - Button::new( - cx, - |cx| { - cx.emit(AppEvent::AlignChildMiddle); - cx.emit(AppEvent::AlignChildCenter); - }, - |cx| Icon::new(cx, ICON_BOX_MARGIN), - ); - - Button::new( - cx, - |cx| { - cx.emit(AppEvent::AlignChildMiddle); - cx.emit(AppEvent::AlignChildRight); - }, - |cx| Icon::new(cx, ICON_BOX_ALIGN_RIGHT), - ); - }) - .height(Auto) - .child_left(Stretch(1.0)) - .child_right(Stretch(1.0)); - - HStack::new(cx, |cx| { - Button::new( - cx, - |cx| { - cx.emit(AppEvent::AlignChildBottom); - cx.emit(AppEvent::AlignChildLeft); - }, - |cx| Icon::new(cx, ICON_BOX_ALIGN_BOTTOM_LEFT), - ); - - Button::new( - cx, - |cx| { - cx.emit(AppEvent::AlignChildBottom); - cx.emit(AppEvent::AlignChildCenter); - }, - |cx| Icon::new(cx, ICON_BOX_ALIGN_BOTTOM), - ); - - Button::new( - cx, - |cx| { - cx.emit(AppEvent::AlignChildBottom); - cx.emit(AppEvent::AlignChildRight); - }, - |cx| Icon::new(cx, ICON_BOX_ALIGN_BOTTOM_RIGHT), - ); - }) - .height(Auto) - .child_left(Stretch(1.0)) - .child_right(Stretch(1.0)); - }) - .class("panel") - .class("align"); - - Element::new(cx).class("divider"); - }) - .width(Stretch(1.0)) - .height(Auto) - .row_between(Pixels(1.0)) - .child_left(Pixels(10.0)) - .child_right(Pixels(10.0)); - }) - .size(Stretch(1.0)); - }) - } -} - -impl View for PropertiesPanel { - fn element(&self) -> Option<&'static str> { - Some("properties") - } -} - -fn unit_box( - cx: &mut Context, - label: &str, - lens: impl Lens, - event: impl 'static + Fn(morph::Units) -> AppEvent + Send + Sync, -) { - // Label::new(cx, label).text_wrap(false); - HStack::new(cx, |cx| { - Label::new(cx, label).text_wrap(false).class("icons"); - Textbox::new(cx, lens.map(|left| print_units(*left))).on_submit(move |cx, txt, _| { - if let Some(val) = text_to_units(txt.as_ref()) { - cx.emit(event(val)); - } - }); - }) - .class("unit_box"); -} - -pub fn text_to_units(text: &str) -> Option { - match text { - "auto" => Some(morph::Units::Auto), - t => { - if let Some(tt) = t.strip_suffix("px") { - tt.parse::().ok().map(morph::Units::Pixels) - } else if let Some(tt) = t.strip_suffix('%') { - tt.parse::().ok().map(morph::Units::Percentage) - } else if let Some(tt) = t.strip_suffix('s') { - tt.parse::().ok().map(morph::Units::Stretch) - } else { - t.parse::().ok().map(morph::Units::Pixels) - } - } - } -} - -pub fn print_units(units: morph::Units) -> String { - match units { - morph::Units::Pixels(val) => format!("{val}px"), - morph::Units::Percentage(val) => format!("{val}%"), - morph::Units::Stretch(val) => format!("{val}s"), - morph::Units::Auto => String::from("auto"), - } -} diff --git a/playground/src/theme.css b/playground/src/theme.css deleted file mode 100644 index 026eb63b..00000000 --- a/playground/src/theme.css +++ /dev/null @@ -1,217 +0,0 @@ -/* * { - border-width: 1px; - border-color: red; -} */ - -:root { - background-color: #1d1d1d; - color: #e6e6e6; - font-size: 12.0; -} - -.icons { - font-family: "tabler-icons"; - font-size: 16.0; -} - -treepanel { - background-color: #3e3e3e; - -} - -treepanel .action_bar { - child-space: 8px; - col-between: 8px; - height: auto; -} - -.tree_item { - height: 30px; - width: 1s; - child-top: 1s; - child-bottom: 1s; - border-width: 1px; - col-between: 2px; -} - -.tree_item label.icons { - width: 22px; - height: 22px; - top: 1s; - bottom: 1s; - child-space: 1s; -} - -.tree_item:over { - border-color: #1c8fbc55; -} - -.tree_item:checked { - background-color: #1c8fbc55; - border-color: #1c8fbc55; -} - -.treeview { - left: 0px; - right: 0px; -} - -.add_button { - width: 28px; - height: 28px; - border-radius: 2px; - /* background-color: #1c8fbc; */ - -} - -.add_button label { - width: 1s; - height: 1s; - child-space: 1s; - font-size: 20.0; -} - -.add_button.danger { - left: 1s; - /* background-color: #bc1c1c; */ -} - -properties { - background-color: #1d1d1d; - row-between: 1px; - background-color: #3e3e3e; -} - -.divider { - background-color: #1d1d1d; - width: 1s; - height: 1px; - left: 0px; - right: 0px; -} - -.panel { - width: 1s; - height: auto; - child-space: 8px; - row-between: 8px; - background-color: #3e3e3e; -} - -dropdown { - width: 1s; - height: 22px; - background-color: #2b2b2b; - border-radius: 2px; -} -/* -dropdown .header { - width: 1s; - height: 1s; - background-color: #2b2b2b; -} */ - -dropdown > popup { - background-color: #2b2b2b; - outer-shadow: 0 3 5 #00000055; - border-radius: 2px; - child-top: 4px; - child-bottom: 4px; -} - -.unit_box { - width: 1s; - height: 22px; - background-color: #2b2b2b; - border-radius: 2px; - child-top: 1s; - child-bottom: 1s; - child-left: 5px; -} - -.unit_box label { - height: 1s; - width: 22px; - child-top: 1s; - child-bottom: 1s; - /* background-color: #acacac; */ -} - -textbox { - width: 1s; - height: 22px; - child-top: 1s; - child-bottom: 1s; -} - -textbox:checked .textbox_content { - caret-color: #f0f0f0; - selection-color: #4871ae88; -} - - -.panel textbox .textbox_content { - child-left: 1s; - child-right: 8px; -} - -.panel label { - width: auto; - height: 22px; - child-space: 1s; -} - -.panel label.panel-title { - color: #acacac; - font-weight: bold; - font-size: 14.0; - child-left: 0px; -} - -.panel .row { - height: auto; - col-between: 8px; -} - -button { - width: 1s; - border-radius: 2px; - background-color: #565656; -} - -.panel.align { - row-between: 4px; -} - -.panel.align hstack { - col-between: 4px; -} - -.panel.align button { - width: 22px; - height: 22px; -} - -.panel.align button label { - width: 22px; - height: 22px; - child-space: 1s; - font-size: 18.0; - color: #e6e6e655; - transition: color 0.1 0.0; -} - -.panel.align button:hover label { - color: #e6e6e6; - transition: color 0.1 0.0; -} - -scrollview > scrollbar.vertical { - top: 0px; - height: 1s; - bottom: 0px; - - left: 1s; - width: 8px; - right: 0px; -} \ No newline at end of file diff --git a/playground/src/tree_panel.rs b/playground/src/tree_panel.rs deleted file mode 100644 index a9e09053..00000000 --- a/playground/src/tree_panel.rs +++ /dev/null @@ -1,97 +0,0 @@ -use morphorm::Node; -use vizia::icons::{ICON_CHEVRON_DOWN, ICON_LAYOUT_LIST, ICON_ROW_INSERT_BOTTOM, ICON_SQUARE_PLUS, ICON_TRASH}; -use vizia::prelude::*; - -use crate::{AppData, AppEvent}; - -pub struct TreePanel {} - -impl TreePanel { - pub fn new(cx: &mut Context) -> Handle { - Self {}.build(cx, |cx| { - // Toolbar - HStack::new(cx, |cx| { - Button::new( - cx, - |cx| cx.emit(AppEvent::AddChildNode), - |cx| Label::new(cx, ICON_SQUARE_PLUS).class("icons"), - ) - .class("add_button"); - Button::new( - cx, - |cx| cx.emit(AppEvent::AddSiblingNode), - |cx| Label::new(cx, ICON_ROW_INSERT_BOTTOM).class("icons"), - ) - .class("add_button"); - Button::new(cx, |cx| cx.emit(AppEvent::DeleteNodes), |cx| Label::new(cx, ICON_TRASH).class("icons")) - .class("add_button") - .class("danger"); - }) - .class("action_bar"); - Element::new(cx).class("divider"); - // Treeview - VStack::new(cx, |cx| { - Binding::new(cx, AppData::tree_changed, |cx, _| { - let children = if let Some(data) = cx.data::() { - data.root_node.children(&data.world.tree).copied().collect::>() - } else { - panic!(); - }; - for child in children.iter() { - let subchildren = if let Some(data) = cx.data::() { - child.children(&data.world.tree).copied().collect::>() - } else { - vec![] - }; - build_tree(cx, *child, subchildren, 0); - } - }); - }) - .class("treeview"); - }) - } -} - -impl View for TreePanel { - fn element(&self) -> Option<&'static str> { - Some("treepanel") - } -} - -fn build_tree(cx: &mut Context, node: morphorm_ecs::Entity, children: Vec, level: usize) { - HStack::new(cx, |cx| { - Label::new(cx, if children.is_empty() { "" } else { ICON_CHEVRON_DOWN }).class("icons").width(Pixels(12.0)); - Label::new(cx, ICON_LAYOUT_LIST).class("icons"); - Label::new(cx, &format!("Entity {}", node.0)).width(Stretch(1.0)).child_left(Pixels(4.0)).on_press(move |ex| { - if ex.modifiers().contains(Modifiers::CTRL) { - ex.emit(AppEvent::MultiSelectNode(node)); - } else { - ex.emit(AppEvent::SelectNode(Some(node))); - } - }); - }) - .child_left(Pixels(14.0 * level as f32 + 4.0)) - .class("tree_item") - .on_press(move |ex| { - if ex.modifiers().contains(Modifiers::CTRL) { - ex.emit(AppEvent::MultiSelectNode(node)); - } else { - ex.emit(AppEvent::SelectNode(Some(node))); - } - }) - .checked(AppData::selected_nodes.map(move |selected| { - if let Some(selected_nodes) = selected { - selected_nodes.iter().any(|s| *s == node) - } else { - false - } - })); - for child in children.iter() { - let subchildren = if let Some(data) = cx.data::() { - child.children(&data.world.tree).copied().collect::>() - } else { - vec![] - }; - build_tree(cx, *child, subchildren, level + 1); - } -} diff --git a/src/layout.rs b/src/layout.rs index 23d584f8..9d5e7c98 100644 --- a/src/layout.rs +++ b/src/layout.rs @@ -1,7 +1,6 @@ use smallvec::SmallVec; -use crate::{Cache, LayoutType, Node, NodeExt, PositionType, Size, Units}; -use crate::{CacheExt, Units::*}; +use crate::{Cache, CacheExt, LayoutType, Node, NodeExt, PositionType, Size, Units::*}; const DEFAULT_MIN: f32 = -f32::MAX; const DEFAULT_MAX: f32 = f32::MAX; @@ -69,8 +68,8 @@ struct ChildNode<'a, N: Node> { /// /// * `node` - Root node to start layout from. /// * `parent_layout_type` - The [`LayoutType`] of the parent of the `node`. -/// * `parent_main` - The size of the parent of the `node` on its main axis. If the `node` has no parent then pass the size of the node. -/// * `cross_size` - The size of the `node` along its cross axis. +/// * `parent_main` - The size of the parent of the `node` on its main axis or the main-size of the node if the node is stretch (determined by parent). +/// * `parent_cross` - The size of the parent of the `node` on its cross axis or the cross-size of the node if the node is stretch (determined by parent). /// * `cache` - A mutable reference to the [`Cache`]. /// * `tree` - A mutable reference to the [`Tree`](crate::Node::Tree). /// * `store` - A mutable reference to the [`Store`](crate::Node::Store). @@ -86,7 +85,7 @@ pub(crate) fn layout( node: &N, parent_layout_type: LayoutType, parent_main: f32, - cross_size: f32, + parent_cross: f32, cache: &mut C, tree: &::Tree, store: &::Store, @@ -103,12 +102,29 @@ where let main = node.main(store, parent_layout_type); let cross = node.cross(store, parent_layout_type); - let min_main = node.min_main(store, parent_layout_type).to_px(parent_main, DEFAULT_MIN); - let max_main = node.max_main(store, parent_layout_type).to_px(parent_main, DEFAULT_MAX); + let mut min_main = if main.is_stretch() { + DEFAULT_MIN + } else { + node.min_main(store, parent_layout_type).to_px(parent_main, DEFAULT_MIN) + }; - // TODO: Need parent_cross to compute this correctly - let min_cross = node.min_cross(store, parent_layout_type).to_px(cross_size, DEFAULT_MIN); - let max_cross = node.max_cross(store, parent_layout_type).to_px(cross_size, DEFAULT_MAX); + let max_main = if main.is_stretch() { + DEFAULT_MAX + } else { + node.max_main(store, parent_layout_type).to_px(parent_main, DEFAULT_MAX) + }; + + let mut min_cross = if cross.is_stretch() { + DEFAULT_MIN + } else { + node.min_cross(store, parent_layout_type).to_px(parent_cross, DEFAULT_MIN) + }; + + let max_cross = if cross.is_stretch() { + DEFAULT_MAX + } else { + node.max_cross(store, parent_layout_type).to_px(parent_cross, DEFAULT_MAX) + }; // Compute main-axis size. let mut computed_main = match main { @@ -118,8 +134,13 @@ where Auto => 0.0, }; - // Cross size is determined by the parent. - let mut computed_cross = cross_size; + // Compute cross-axis size. + let mut computed_cross = match cross { + Pixels(val) => val, + Percentage(val) => (parent_cross * (val / 100.0)).round(), + Stretch(_) => parent_cross, + Auto => 0.0, + }; let border_main_before = node.border_main_before(store, parent_layout_type).to_px(computed_main, DEFAULT_BORDER_WIDTH); @@ -130,10 +151,8 @@ where let border_cross_after = node.border_cross_after(store, parent_layout_type).to_px(computed_cross, DEFAULT_BORDER_WIDTH); - let node_children = node.children(tree).filter(|child| child.visible(store)); - // Get the total number of children of the node. - let num_children = node_children.count(); + let num_children = node.children(tree).filter(|child| child.visible(store)).count(); // Get the total number of parent-directed children of the node. let num_parent_directed_children = node @@ -142,28 +161,45 @@ where .filter(|child| child.visible(store)) .count(); + // Sum of all child nodes on the main-axis. + let mut main_sum = 0.0; + + // Maximum of all child nodes on the cross-axis. + let mut cross_max = 0.0f32; + // Apply content sizing. if (main.is_auto() || cross.is_auto()) && num_parent_directed_children == 0 { - let parent_main = if main.is_auto() { None } else { Some(computed_main) }; - let parent_cross = if cross.is_auto() { None } else { Some(computed_cross) }; - if let Some(content_size) = node.content_sizing(store, sublayout, parent_layout_type, parent_main, parent_cross) - { + let p_main = if main.is_auto() { None } else { Some(computed_main) }; + let p_cross = if cross.is_auto() { None } else { Some(computed_cross) }; + + if let Some(content_size) = node.content_sizing(store, sublayout, parent_layout_type, p_main, p_cross) { computed_main = content_size.0; computed_cross = content_size.1; } } - // Apply main-axis size constraints for pixels and percentage. - if !main.is_stretch() { - computed_main = computed_main.min(max_main).max(min_main); + if (node.min_main(store, parent_layout_type).is_auto() || node.min_cross(store, parent_layout_type).is_auto()) + && num_parent_directed_children == 0 + { + let p_main = if node.min_main(store, parent_layout_type).is_auto() { None } else { Some(computed_main) }; + let p_cross = if node.min_cross(store, parent_layout_type).is_auto() { None } else { Some(computed_cross) }; + + if let Some(content_size) = node.content_sizing(store, sublayout, parent_layout_type, p_main, p_cross) { + min_main = content_size.0; + min_cross = content_size.1; + } } - // TODO: Figure out how to constrain content size on cross axis. + // Apply main-axis size constraints for pixels and percentage. + // if !main.is_stretch() { + computed_main = computed_main.max(min_main).min(max_main); + computed_cross = computed_cross.max(min_cross).min(max_cross); + // } // Return early if there's no children to layout. - if num_children == 0 { - return Size { main: computed_main, cross: computed_cross }; - } + // if num_children == 0 { + // return Size { main: computed_main, cross: computed_cross }; + // } // Determine the parent_main/cross size to pass to the children based on the layout type of the parent and the node. // i.e. if the parent layout type and the node layout type are different, swap the main and the cross axes. @@ -176,12 +212,6 @@ where // Sum of all space and size flex factors on the main-axis of the node. let mut main_flex_sum = 0.0; - // Sum of all child nodes on the main-axis. - let mut main_sum = 0.0; - - // Maximum of all child nodes on the cross-axis. - let mut cross_max = 0.0f32; - // List of child nodes for the current node. let mut children = SmallVec::<[ChildNode; 32]>::with_capacity(num_children); @@ -228,9 +258,6 @@ where let child_min_cross_before = child.min_cross_before(store, layout_type); let child_max_cross_before = child.max_cross_before(store, layout_type); - let child_min_cross = child.min_cross(store, layout_type); - let child_max_cross = child.max_cross(store, layout_type); - let child_min_cross_after = child.min_cross_after(store, layout_type); let child_max_cross_after = child.max_cross_after(store, layout_type); @@ -244,27 +271,27 @@ where let child_max_main = child.max_main(store, layout_type); // Apply parent child_space overrides to auto child space. - if child_main_before == Units::Auto && first == Some(index) { + if child_main_before.is_auto() && first == Some(index) { child_main_before = node_child_main_before; } - if child_main_after == Units::Auto { + if child_main_after.is_auto() { if last == Some(index) { child_main_after = node_child_main_after; } else if let Some((_, next_node)) = node_children.peek() { // Only apply main between if both adjacent children have auto space between let next_main_before = next_node.main_before(store, layout_type); - if next_main_before == Units::Auto { + if next_main_before.is_auto() { child_main_after = node_child_main_between; } } } - if child_cross_before == Units::Auto { + if child_cross_before.is_auto() { child_cross_before = node_child_cross_before; } - if child_cross_after == Units::Auto { + if child_cross_after.is_auto() { child_cross_after = node_child_cross_after; } @@ -306,13 +333,21 @@ where let computed_child_cross_before = child_cross_before.to_px_clamped(parent_cross, 0.0, child_min_cross_before, child_max_cross_before); - // Compute fixed-size child_cross. - let mut computed_child_cross = child_cross.to_px_clamped(parent_cross, 0.0, child_min_cross, child_max_cross); - // Compute fixed-size child cross_after. let computed_child_cross_after = child_cross_after.to_px_clamped(parent_cross, 0.0, child_min_cross_after, child_max_cross_after); + let mut computed_child_cross = child_cross.to_px(parent_cross, 0.0); + + if child.min_cross(store, layout_type).is_auto() { + let p_cross = if child.min_cross(store, layout_type).is_auto() { None } else { Some(parent_cross) }; + + if let Some(content_size) = child.content_sizing(store, sublayout, layout_type, p_cross, p_cross) { + // min_main = content_size.0; + computed_child_cross = content_size.1; + } + } + // Compute fixed-size child main_before. let computed_child_main_before = child_main_before.to_px_clamped(parent_main, 0.0, child_min_main_before, child_max_main_before); @@ -322,10 +357,11 @@ where child_main_after.to_px_clamped(parent_main, 0.0, child_min_main_after, child_max_main_after); let mut computed_child_main = 0.0; - // Compute fixed-size child main. + // let mut computed_child_cross = 0.0; + + // Compute fixed-size child main and cross. if !child_main.is_stretch() && !child_cross.is_stretch() { - let child_size = - layout(child, layout_type, parent_main, computed_child_cross, cache, tree, store, sublayout); + let child_size = layout(child, layout_type, parent_main, parent_cross, cache, tree, store, sublayout); computed_child_main = child_size.main; computed_child_cross = child_size.cross; @@ -345,11 +381,31 @@ where }); } - // Determine cross-size of auto node from children. - if num_parent_directed_children != 0 && node.cross(store, layout_type) == Auto { - parent_cross = (cross_max + border_cross_before + border_cross_after).min(max_cross).max(min_cross); + // Determine auto main and cross size from space and size of children. + if num_parent_directed_children != 0 { + if main.is_auto() || node.min_main(store, parent_layout_type).is_auto() { + if parent_layout_type == layout_type { + min_main = main_sum + border_main_before + border_main_after; + parent_main = parent_main.max(min_main).min(max_main); + } else { + min_main = cross_max + border_main_before + border_main_after; + parent_cross = parent_cross.max(min_main).min(max_main); + } + } + if cross.is_auto() || node.min_cross(store, parent_layout_type).is_auto() { + if parent_layout_type == layout_type { + min_cross = cross_max + border_cross_before + border_cross_after; + parent_cross = parent_cross.max(min_cross).min(max_cross); + } else { + min_cross = main_sum + border_cross_before + border_cross_after; + parent_main = parent_main.max(min_cross).min(max_cross); + } + } } + computed_main = computed_main.max(min_main).min(max_main); + computed_cross = computed_cross.max(min_cross).min(max_cross); + // Compute flexible space and size on the cross-axis for parent-directed children. for (index, child) in children .iter_mut() @@ -362,11 +418,11 @@ where let mut child_cross_after = child.node.cross_after(store, layout_type); // Apply child_space overrides. - if child_cross_before == Units::Auto { + if child_cross_before.is_auto() { child_cross_before = node_child_cross_before; } - if child_cross_after == Units::Auto { + if child_cross_after.is_auto() { child_cross_after = node_child_cross_after; } @@ -496,11 +552,31 @@ where cross_max = cross_max.max(child.cross_before + child.cross + child.cross_after); } - // Determine main-size of auto node from children. - if num_parent_directed_children != 0 && node.main(store, layout_type) == Auto { - parent_main = (parent_main.max(main_sum) + border_main_before + border_main_after).min(max_main).max(min_main); + // Determine auto main and cross size from space and size of children. + if num_parent_directed_children != 0 { + if main.is_auto() || node.min_main(store, parent_layout_type).is_auto() { + if parent_layout_type == layout_type { + min_main = main_sum + border_main_before + border_main_after; + parent_main = parent_main.max(min_main).min(max_main); + } else { + min_main = cross_max + border_main_before + border_main_after; + parent_cross = parent_cross.max(min_main).min(max_main); + } + } + if cross.is_auto() || node.min_cross(store, parent_layout_type).is_auto() { + if parent_layout_type == layout_type { + min_cross = cross_max + border_cross_before + border_cross_after; + parent_cross = parent_cross.max(min_cross).min(max_cross); + } else { + min_cross = main_sum + border_cross_before + border_cross_after; + parent_main = parent_main.max(min_cross).min(max_cross); + } + } } + computed_main = computed_main.max(min_main).min(max_main); + computed_cross = computed_cross.max(min_cross).min(max_cross); + // Compute flexible space and size on the main axis for parent-directed children. if !main_axis.is_empty() { loop { @@ -517,6 +593,25 @@ where for item in main_axis.iter_mut().filter(|item| !item.frozen) { let actual_main = (item.factor * free_main_space / main_flex_sum).round(); + let child = &mut children[item.index]; + if item.item_type == ItemType::Size { + let child_size = layout( + child.node, + layout_type, + actual_main, + if child.node.cross(store, layout_type).is_stretch() { child.cross } else { parent_cross }, + cache, + tree, + store, + sublayout, + ); + child.cross = child_size.cross; + cross_max = cross_max.max(child.cross_before + child.cross + child.cross_after); + if child.node.min_main(store, layout_type).is_auto() { + item.min = child_size.main; + } + } + let clamped = actual_main.min(item.max).max(item.min); item.violation = clamped - actual_main; total_violation += item.violation; @@ -540,19 +635,7 @@ where match item.item_type { ItemType::Size => { - let child_size = layout( - child.node, - layout_type, - item.computed, - child.cross, - cache, - tree, - store, - sublayout, - ); - child.main = child_size.main; - child.cross = child_size.cross; - cross_max = cross_max.max(child.cross_before + child.cross + child.cross_after); + child.main = item.computed; } ItemType::Before => { @@ -568,123 +651,31 @@ where } } - // Compute stretch cross_before and stretch cross_after for auto cross children. - // TODO: I think this only needs to be done for parent-directed children... - for (index, child) in children.iter_mut().filter(|child| child.node.cross(store, layout_type).is_auto()).enumerate() - { - let mut child_cross_before = child.node.cross_before(store, layout_type); - let mut child_cross_after = child.node.cross_after(store, layout_type); - - // Apply child_space overrides. - if child_cross_before == Units::Auto { - child_cross_before = node_child_cross_before; - } - - if child_cross_after == Units::Auto { - child_cross_after = node_child_cross_after; - } - - let mut cross_flex_sum = 0.0; - - // Collect stretch cross items. - let mut cross_axis = SmallVec::<[StretchItem; 3]>::new(); - if let Stretch(factor) = child_cross_before { - let child_min_cross_before = - child.node.min_cross_before(store, layout_type).to_px(parent_cross, DEFAULT_MIN); - let child_max_cross_before = - child.node.max_cross_before(store, layout_type).to_px(parent_cross, DEFAULT_MAX); - - cross_flex_sum += factor; - - child.cross_before = 0.0; - - cross_axis.push(StretchItem::new( - index, - factor, - ItemType::Before, - child_min_cross_before, - child_max_cross_before, - )); - } - - if let Stretch(factor) = child_cross_after { - let child_min_cross_after = child.node.min_cross_after(store, layout_type).to_px(parent_cross, DEFAULT_MIN); - let child_max_cross_after = child.node.max_cross_after(store, layout_type).to_px(parent_cross, DEFAULT_MAX); - - cross_flex_sum += factor; - - child.cross_after = 0.0; - - cross_axis.push(StretchItem::new( - index, - factor, - ItemType::After, - child_min_cross_after, - child_max_cross_after, - )); - } - - let child_position_type = child.node.position_type(store).unwrap_or_default(); - - loop { - // If all stretch items are frozen, exit the loop. - if cross_axis.iter().all(|item| item.frozen) { - break; - } - - // Compute free space in the cross axis. - let child_cross_free_space = parent_cross - - border_cross_before - - border_cross_after - - child.cross_before - - child.cross - - child.cross_after; - - // Total size violation in the cross axis. - let mut total_violation = 0.0; - - for item in cross_axis.iter_mut().filter(|item| !item.frozen) { - let actual_cross = (item.factor * child_cross_free_space / cross_flex_sum).round(); - - let clamped = actual_cross.min(item.max).max(item.min); - item.violation = clamped - actual_cross; - total_violation += item.violation; - - item.computed = clamped; - } - - for item in cross_axis.iter_mut().filter(|item| !item.frozen) { - // Freeze over-stretched items. - item.frozen = match total_violation { - v if v > 0.0 => item.violation > 0.0, - v if v < 0.0 => item.violation < 0.0, - _ => true, - }; - - // If the item is frozen, adjust the used_space and sum of cross stretch factors. - if item.frozen { - cross_flex_sum -= item.factor; - - match item.item_type { - ItemType::Before => { - child.cross_before = item.computed; - } - - ItemType::After => { - child.cross_after = item.computed; - } - - _ => {} - } - } + // Determine auto main and cross size from space and size of children. + if num_parent_directed_children != 0 { + if main.is_auto() || node.min_main(store, parent_layout_type).is_auto() { + if parent_layout_type == layout_type { + min_main = main_sum + border_main_before + border_main_after; + parent_main = parent_main.max(min_main).min(max_main); + } else { + min_main = cross_max + border_main_before + border_main_after; + parent_cross = parent_cross.max(min_main).min(max_main); } } - - if child_position_type == PositionType::ParentDirected { - cross_max = cross_max.max(child.cross_before + child.cross + child.cross_after); + if cross.is_auto() || node.min_cross(store, parent_layout_type).is_auto() { + if parent_layout_type == layout_type { + min_cross = cross_max + border_cross_before + border_cross_after; + parent_cross = parent_cross.max(min_cross).min(max_cross); + } else { + min_cross = main_sum + border_cross_before + border_cross_after; + parent_main = parent_main.max(min_cross).min(max_cross); + } } } + computed_main = computed_main.max(min_main).min(max_main); + computed_cross = computed_cross.max(min_cross).min(max_cross); + let node_children = node .children(tree) .filter(|child| child.position_type(store).unwrap_or_default() == PositionType::SelfDirected) @@ -705,9 +696,6 @@ where let child_min_cross_before = child.min_cross_before(store, layout_type); let child_max_cross_before = child.max_cross_before(store, layout_type); - let child_min_cross = child.min_cross(store, layout_type); - let child_max_cross = child.max_cross(store, layout_type); - let child_min_cross_after = child.min_cross_after(store, layout_type); let child_max_cross_after = child.max_cross_after(store, layout_type); @@ -717,23 +705,20 @@ where let child_min_main_after = child.min_main_after(store, layout_type); let child_max_main_after = child.max_main_after(store, layout_type); - // let child_min_main = child.min_main(store, layout_type); - // let child_max_main = child.max_main(store, layout_type); - // Apply parent child_space overrides to auto child space. - if child_main_before == Units::Auto { + if child_main_before.is_auto() { child_main_before = node_child_main_before; } - if child_main_after == Units::Auto { + if child_main_after.is_auto() { child_main_after = node_child_main_after; } - if child_cross_before == Units::Auto { + if child_cross_before.is_auto() { child_cross_before = node_child_cross_before; } - if child_cross_after == Units::Auto { + if child_cross_after.is_auto() { child_cross_after = node_child_cross_after; } @@ -741,9 +726,6 @@ where let computed_child_cross_before = child_cross_before.to_px_clamped(parent_cross, 0.0, child_min_cross_before, child_max_cross_before); - // Compute fixed-size child_cross. - let mut computed_child_cross = child_cross.to_px_clamped(parent_cross, 0.0, child_min_cross, child_max_cross); - // Compute fixed-size child cross_after. let computed_child_cross_after = child_cross_after.to_px_clamped(parent_cross, 0.0, child_min_cross_after, child_max_cross_after); @@ -757,11 +739,11 @@ where child_main_after.to_px_clamped(parent_main, 0.0, child_min_main_after, child_max_main_after); let mut computed_child_main = 0.0; + let mut computed_child_cross = 0.0; // Compute fixed-size child main. if !child_main.is_stretch() && !child_cross.is_stretch() { - let child_size = - layout(child, layout_type, parent_main, computed_child_cross, cache, tree, store, sublayout); + let child_size = layout(child, layout_type, parent_main, parent_cross, cache, tree, store, sublayout); computed_child_main = child_size.main; computed_child_cross = child_size.cross; @@ -789,11 +771,11 @@ where let mut child_cross_after = child.node.cross_after(store, layout_type); // Apply child_space overrides. - if child_cross_before == Units::Auto { + if child_cross_before.is_auto() { child_cross_before = node_child_cross_before; } - if child_cross_after == Units::Auto { + if child_cross_after.is_auto() { child_cross_after = node_child_cross_after; } @@ -870,6 +852,15 @@ where for item in cross_axis.iter_mut().filter(|item| !item.frozen) { let actual_cross = (item.factor * child_cross_free_space / cross_flex_sum).round(); + if item.item_type == ItemType::Size && !child.node.main(store, layout_type).is_stretch() { + let child_size = + layout(child.node, layout_type, parent_main, actual_cross, cache, tree, store, sublayout); + if child.node.min_cross(store, layout_type).is_auto() { + item.min = child_size.cross; + } + child.main = child_size.main; + } + let clamped = actual_cross.min(item.max).max(item.min); item.violation = clamped - actual_cross; total_violation += item.violation; @@ -892,24 +883,6 @@ where match item.item_type { ItemType::Size => { child.cross = item.computed; - if !child.node.main(store, layout_type).is_stretch() { - let child_size = layout( - child.node, - layout_type, - parent_main, - item.computed, - cache, - tree, - store, - sublayout, - ); - child.main = child_size.main; - child.cross = child_size.cross; - - if child_position_type == PositionType::ParentDirected { - main_sum += child.main; - } - } } ItemType::Before => { @@ -940,11 +913,11 @@ where let mut child_main_after = child.node.main_after(store, layout_type); // Apply child_space overrides. - if child_main_before == Units::Auto { + if child_main_before.is_auto() { child_main_before = node_child_main_before; } - if child_main_after == Units::Auto { + if child_main_after.is_auto() { child_main_after = node_child_main_after; } @@ -1009,6 +982,23 @@ where for item in main_axis.iter_mut().filter(|item| !item.frozen) { let actual_main = (item.factor * child_main_free_space / child_main_flex_sum).round(); + if item.item_type == ItemType::Size { + let child_size = layout( + child.node, + layout_type, + actual_main, + if child.node.cross(store, layout_type).is_stretch() { child.cross } else { parent_cross }, + cache, + tree, + store, + sublayout, + ); + child.cross = child_size.cross; + if child.node.min_main(store, layout_type).is_auto() { + item.min = child_size.main; + } + } + let clamped = actual_main.min(item.max).max(item.min); item.violation = clamped - actual_main; total_violation += item.violation; @@ -1041,9 +1031,152 @@ where } } } + } + + // Determine auto main and cross size from space and size of children. + if num_parent_directed_children != 0 { + if main.is_auto() || node.min_main(store, parent_layout_type).is_auto() { + if parent_layout_type == layout_type { + min_main = main_sum + border_main_before + border_main_after; + // parent_main = parent_main.max(min_main).min(max_main); + } else { + min_main = cross_max + border_main_before + border_main_after; + parent_cross = parent_cross.max(min_main).min(max_main); + } + } + if cross.is_auto() || node.min_cross(store, parent_layout_type).is_auto() { + if parent_layout_type == layout_type { + min_cross = cross_max + border_cross_before + border_cross_after; + parent_cross = parent_cross.max(min_cross).min(max_cross); + } else { + min_cross = main_sum + border_cross_before + border_cross_after; + // parent_main = parent_main.max(min_cross).min(max_cross); + } + } + } + + computed_main = computed_main.max(min_main).min(max_main); + computed_cross = computed_cross.max(min_cross).min(max_cross); + + // Compute stretch cross_before and stretch cross_after for auto cross children. + // TODO: I think this only needs to be done for parent-directed children... + for (index, child) in children + .iter_mut() + // .filter(|child| { + // child.node.cross(store, layout_type).is_auto() || child.node.min_cross(store, layout_type).is_auto() + // }) + .enumerate() + { + let mut child_cross_before = child.node.cross_before(store, layout_type); + let mut child_cross_after = child.node.cross_after(store, layout_type); + + // Apply child_space overrides. + if child_cross_before.is_auto() { + child_cross_before = node_child_cross_before; + } + + if child_cross_after.is_auto() { + child_cross_after = node_child_cross_after; + } + + let mut cross_flex_sum = 0.0; + + // Collect stretch cross items. + let mut cross_axis = SmallVec::<[StretchItem; 3]>::new(); + if let Stretch(factor) = child_cross_before { + let child_min_cross_before = + child.node.min_cross_before(store, layout_type).to_px(parent_cross, DEFAULT_MIN); + let child_max_cross_before = + child.node.max_cross_before(store, layout_type).to_px(parent_cross, DEFAULT_MAX); + + cross_flex_sum += factor; + + child.cross_before = 0.0; + + cross_axis.push(StretchItem::new( + index, + factor, + ItemType::Before, + child_min_cross_before, + child_max_cross_before, + )); + } + + if let Stretch(factor) = child_cross_after { + let child_min_cross_after = child.node.min_cross_after(store, layout_type).to_px(parent_cross, DEFAULT_MIN); + let child_max_cross_after = child.node.max_cross_after(store, layout_type).to_px(parent_cross, DEFAULT_MAX); + + cross_flex_sum += factor; + + child.cross_after = 0.0; + + cross_axis.push(StretchItem::new( + index, + factor, + ItemType::After, + child_min_cross_after, + child_max_cross_after, + )); + } + + let child_position_type = child.node.position_type(store).unwrap_or_default(); + + loop { + // If all stretch items are frozen, exit the loop. + if cross_axis.iter().all(|item| item.frozen) { + break; + } + + // Compute free space in the cross axis. + let child_cross_free_space = parent_cross + - border_cross_before + - border_cross_after + - child.cross_before + - child.cross + - child.cross_after; + + // Total size violation in the cross axis. + let mut total_violation = 0.0; + + for item in cross_axis.iter_mut().filter(|item| !item.frozen) { + let actual_cross = (item.factor * child_cross_free_space / cross_flex_sum).round(); + + let clamped = actual_cross.min(item.max).max(item.min); + item.violation = clamped - actual_cross; + total_violation += item.violation; + + item.computed = clamped; + } + + for item in cross_axis.iter_mut().filter(|item| !item.frozen) { + // Freeze over-stretched items. + item.frozen = match total_violation { + v if v > 0.0 => item.violation > 0.0, + v if v < 0.0 => item.violation < 0.0, + _ => true, + }; + + // If the item is frozen, adjust the used_space and sum of cross stretch factors. + if item.frozen { + cross_flex_sum -= item.factor; + + match item.item_type { + ItemType::Before => { + child.cross_before = item.computed; + } + + ItemType::After => { + child.cross_after = item.computed; + } + + _ => {} + } + } + } + } - if let Stretch(_) = child_main { - layout(child.node, layout_type, child.main, child.cross, cache, tree, store, sublayout); + if child_position_type == PositionType::ParentDirected { + cross_max = cross_max.max(child.cross_before + child.cross + child.cross_after); } } @@ -1078,24 +1211,6 @@ where }; } - // Determine auto main and cross size from space and size of children. - if num_parent_directed_children != 0 { - main_sum += border_main_before + border_main_after; - cross_max += border_cross_before + border_cross_after; - - if parent_layout_type != layout_type { - std::mem::swap(&mut main_sum, &mut cross_max) - }; - - if main == Auto { - computed_main = main_sum.min(max_main).max(min_main); - } - - if cross == Auto { - computed_cross = cross_max.min(max_cross).max(min_cross); - } - } - // Return the computed size, propagating it back up the tree. Size { main: computed_main, cross: computed_cross } } diff --git a/src/node.rs b/src/node.rs index 9484655a..16a140c6 100644 --- a/src/node.rs +++ b/src/node.rs @@ -266,6 +266,7 @@ pub(crate) trait NodeExt: Node { } // Currently unused until wrapping is implemented + #[allow(dead_code)] fn cross_between(&self, store: &Self::Store, parent_layout_type: LayoutType) -> Units { parent_layout_type.select_unwrap(store, |store| self.row_between(store), |store| self.col_between(store)) } diff --git a/tests/size_constraints.rs b/tests/size_constraints.rs index ed0bb13b..55b0a132 100644 --- a/tests/size_constraints.rs +++ b/tests/size_constraints.rs @@ -193,3 +193,215 @@ fn min_width_percentage_width_auto() { assert_eq!(world.cache.bounds(node), Some(&Rect { posx: 0.0, posy: 0.0, width: 600.0, height: 100.0 })); assert_eq!(world.cache.bounds(node2), Some(&Rect { posx: 0.0, posy: 0.0, width: 200.0, height: 100.0 })); } + +#[test] +fn min_width_auto() { + let mut world = World::default(); + + let root = world.add(None); + world.set_width(root, Units::Pixels(600.0)); + world.set_height(root, Units::Pixels(600.0)); + world.set_child_space(root, Units::Stretch(1.0)); + + let node = world.add(Some(root)); + world.set_width(node, Units::Stretch(1.0)); + world.set_height(node, Units::Stretch(1.0)); + world.set_min_width(node, Units::Auto); + + let node2 = world.add(Some(node)); + world.set_width(node2, Units::Pixels(300.0)); + world.set_height(node2, Units::Pixels(300.0)); + root.layout(&mut world.cache, &world.tree, &world.store, &mut ()); + + assert_eq!(world.cache.bounds(node), Some(&Rect { posx: 150.0, posy: 200.0, width: 300.0, height: 200.0 })); + assert_eq!(world.cache.bounds(node2), Some(&Rect { posx: 0.0, posy: 0.0, width: 300.0, height: 300.0 })); +} + +#[test] +fn min_height_auto() { + let mut world = World::default(); + + let root = world.add(None); + world.set_width(root, Units::Pixels(600.0)); + world.set_height(root, Units::Pixels(600.0)); + world.set_child_space(root, Units::Stretch(1.0)); + + let node = world.add(Some(root)); + world.set_width(node, Units::Stretch(1.0)); + world.set_height(node, Units::Stretch(1.0)); + world.set_min_height(node, Units::Auto); + + let node2 = world.add(Some(node)); + world.set_width(node2, Units::Pixels(300.0)); + world.set_height(node2, Units::Pixels(300.0)); + root.layout(&mut world.cache, &world.tree, &world.store, &mut ()); + + assert_eq!(world.cache.bounds(node), Some(&Rect { posx: 200.0, posy: 150.0, width: 200.0, height: 300.0 })); + assert_eq!(world.cache.bounds(node2), Some(&Rect { posx: 0.0, posy: 0.0, width: 300.0, height: 300.0 })); +} + +#[test] +fn min_size_auto() { + let mut world = World::default(); + + let root = world.add(None); + world.set_width(root, Units::Pixels(600.0)); + world.set_height(root, Units::Pixels(600.0)); + world.set_child_space(root, Units::Stretch(1.0)); + + let node = world.add(Some(root)); + world.set_width(node, Units::Stretch(1.0)); + world.set_height(node, Units::Stretch(1.0)); + world.set_min_width(node, Units::Auto); + world.set_min_height(node, Units::Auto); + + let node2 = world.add(Some(node)); + world.set_width(node2, Units::Pixels(300.0)); + world.set_height(node2, Units::Pixels(300.0)); + root.layout(&mut world.cache, &world.tree, &world.store, &mut ()); + + assert_eq!(world.cache.bounds(node), Some(&Rect { posx: 150.0, posy: 150.0, width: 300.0, height: 300.0 })); + assert_eq!(world.cache.bounds(node2), Some(&Rect { posx: 0.0, posy: 0.0, width: 300.0, height: 300.0 })); +} + +#[test] +fn min_width_auto_self_directed() { + let mut world = World::default(); + let root = world.add(None); + world.set_width(root, Units::Pixels(600.0)); + world.set_height(root, Units::Pixels(600.0)); + world.set_child_space(root, Units::Stretch(1.0)); + let node = world.add(Some(root)); + world.set_width(node, Units::Stretch(1.0)); + world.set_height(node, Units::Stretch(1.0)); + world.set_min_width(node, Units::Auto); + world.set_position_type(node, PositionType::SelfDirected); + let node2 = world.add(Some(node)); + world.set_width(node2, Units::Pixels(300.0)); + world.set_height(node2, Units::Pixels(300.0)); + root.layout(&mut world.cache, &world.tree, &world.store, &mut ()); + assert_eq!(world.cache.bounds(node), Some(&Rect { posx: 150.0, posy: 200.0, width: 300.0, height: 200.0 })); + assert_eq!(world.cache.bounds(node2), Some(&Rect { posx: 0.0, posy: 0.0, width: 300.0, height: 300.0 })); +} + +#[test] +fn min_height_auto_self_directed() { + let mut world = World::default(); + + let root = world.add(None); + world.set_width(root, Units::Pixels(600.0)); + world.set_height(root, Units::Pixels(600.0)); + world.set_child_space(root, Units::Stretch(1.0)); + + let node = world.add(Some(root)); + world.set_width(node, Units::Stretch(1.0)); + world.set_height(node, Units::Stretch(1.0)); + world.set_min_height(node, Units::Auto); + world.set_position_type(node, PositionType::SelfDirected); + + let node2 = world.add(Some(node)); + world.set_width(node2, Units::Pixels(300.0)); + world.set_height(node2, Units::Pixels(300.0)); + root.layout(&mut world.cache, &world.tree, &world.store, &mut ()); + + assert_eq!(world.cache.bounds(node), Some(&Rect { posx: 200.0, posy: 150.0, width: 200.0, height: 300.0 })); + assert_eq!(world.cache.bounds(node2), Some(&Rect { posx: 0.0, posy: 0.0, width: 300.0, height: 300.0 })); +} + +#[test] +fn min_size_auto_self_directed() { + let mut world = World::default(); + + let root = world.add(None); + world.set_width(root, Units::Pixels(600.0)); + world.set_height(root, Units::Pixels(600.0)); + world.set_child_space(root, Units::Stretch(1.0)); + + let node = world.add(Some(root)); + world.set_width(node, Units::Stretch(1.0)); + world.set_height(node, Units::Stretch(1.0)); + world.set_min_width(node, Units::Auto); + world.set_min_height(node, Units::Auto); + world.set_position_type(node, PositionType::SelfDirected); + + let node2 = world.add(Some(node)); + world.set_width(node2, Units::Pixels(300.0)); + world.set_height(node2, Units::Pixels(300.0)); + root.layout(&mut world.cache, &world.tree, &world.store, &mut ()); + + assert_eq!(world.cache.bounds(node), Some(&Rect { posx: 150.0, posy: 150.0, width: 300.0, height: 300.0 })); + assert_eq!(world.cache.bounds(node2), Some(&Rect { posx: 0.0, posy: 0.0, width: 300.0, height: 300.0 })); +} + +#[test] +fn min_width_auto_child_self_directed() { + let mut world = World::default(); + + let root = world.add(None); + world.set_width(root, Units::Pixels(600.0)); + world.set_height(root, Units::Pixels(600.0)); + world.set_child_space(root, Units::Stretch(1.0)); + + let node = world.add(Some(root)); + world.set_width(node, Units::Stretch(1.0)); + world.set_height(node, Units::Stretch(1.0)); + world.set_min_width(node, Units::Auto); + + let node2 = world.add(Some(node)); + world.set_width(node2, Units::Pixels(300.0)); + world.set_height(node2, Units::Pixels(300.0)); + world.set_position_type(node2, PositionType::SelfDirected); + root.layout(&mut world.cache, &world.tree, &world.store, &mut ()); + + assert_eq!(world.cache.bounds(node), Some(&Rect { posx: 200.0, posy: 200.0, width: 200.0, height: 200.0 })); + assert_eq!(world.cache.bounds(node2), Some(&Rect { posx: 0.0, posy: 0.0, width: 300.0, height: 300.0 })); +} + +#[test] +fn min_height_auto_child_self_directed() { + let mut world = World::default(); + + let root = world.add(None); + world.set_width(root, Units::Pixels(600.0)); + world.set_height(root, Units::Pixels(600.0)); + world.set_child_space(root, Units::Stretch(1.0)); + + let node = world.add(Some(root)); + world.set_width(node, Units::Stretch(1.0)); + world.set_height(node, Units::Stretch(1.0)); + world.set_min_height(node, Units::Auto); + + let node2 = world.add(Some(node)); + world.set_width(node2, Units::Pixels(300.0)); + world.set_height(node2, Units::Pixels(300.0)); + world.set_position_type(node2, PositionType::SelfDirected); + root.layout(&mut world.cache, &world.tree, &world.store, &mut ()); + + assert_eq!(world.cache.bounds(node), Some(&Rect { posx: 200.0, posy: 200.0, width: 200.0, height: 200.0 })); + assert_eq!(world.cache.bounds(node2), Some(&Rect { posx: 0.0, posy: 0.0, width: 300.0, height: 300.0 })); +} + +#[test] +fn min_size_auto_child_self_directed() { + let mut world = World::default(); + + let root = world.add(None); + world.set_width(root, Units::Pixels(600.0)); + world.set_height(root, Units::Pixels(600.0)); + world.set_child_space(root, Units::Stretch(1.0)); + + let node = world.add(Some(root)); + world.set_width(node, Units::Stretch(1.0)); + world.set_height(node, Units::Stretch(1.0)); + world.set_min_width(node, Units::Auto); + world.set_min_height(node, Units::Auto); + + let node2 = world.add(Some(node)); + world.set_width(node2, Units::Pixels(300.0)); + world.set_height(node2, Units::Pixels(300.0)); + world.set_position_type(node2, PositionType::SelfDirected); + root.layout(&mut world.cache, &world.tree, &world.store, &mut ()); + + assert_eq!(world.cache.bounds(node), Some(&Rect { posx: 200.0, posy: 200.0, width: 200.0, height: 200.0 })); + assert_eq!(world.cache.bounds(node2), Some(&Rect { posx: 0.0, posy: 0.0, width: 300.0, height: 300.0 })); +}