From 4bb59b7c5fe3ba93146c9d8b8d5dbf88207a68b6 Mon Sep 17 00:00:00 2001 From: griffi-gh Date: Mon, 25 Mar 2024 14:08:04 +0100 Subject: [PATCH] wip `Remaing` size --- hui-examples/examples/vscode_layout.rs | 41 +++++++---- hui/src/element.rs | 7 +- hui/src/element/builtin/container.rs | 92 ++++++++++++++++++++----- hui/src/element/builtin/fill_rect.rs | 19 ++--- hui/src/element/builtin/image.rs | 4 ++ hui/src/element/builtin/interactable.rs | 4 ++ hui/src/element/builtin/text.rs | 19 ++--- hui/src/frame/point.rs | 6 +- hui/src/instance.rs | 1 + hui/src/layout.rs | 26 ++++++- hui/src/macros.rs | 14 ++++ 11 files changed, 171 insertions(+), 62 deletions(-) diff --git a/hui-examples/examples/vscode_layout.rs b/hui-examples/examples/vscode_layout.rs index 3d09eec..7620c98 100644 --- a/hui-examples/examples/vscode_layout.rs +++ b/hui-examples/examples/vscode_layout.rs @@ -4,6 +4,7 @@ use hui::{ color, size, draw::{ImageHandle, TextureFormat}, layout::{Alignment, Direction}, + rect::Sides, element::{ container::Container, fill_rect::FillRect, @@ -38,7 +39,12 @@ ui_main!( .with_size(size!(100%, auto)) .with_direction(Direction::Horizontal) .with_align((Alignment::Begin, Alignment::Center)) - .with_padding(8.) + .with_padding(Sides { + left: 5., + right: 0., + top: 5., + bottom: 5., + }) .with_gap(15.) .with_background(color::rgb_hex(0x3d3c3e)) .with_wrap(true) //XXX: not authentic but great for demostration @@ -51,6 +57,16 @@ ui_main!( .with_text_size(15) .add_child(ui); } + Container::default() + //HACK: due to a bug in the layout system, 100%= doesn't work as expected + .with_size(size!(94%=, 100%)) + .with_align((Alignment::End, Alignment::Center)) + .with_children(|ui| { + Text::new("- ×") + .with_text_size(32) + .add_child(ui); + }) + .add_child(ui); }) .add_child(ui); FillRect::default() @@ -58,9 +74,10 @@ ui_main!( .with_frame(color::rgb_hex(0x2d2d30)) .add_child(ui); Container::default() - .with_size(size!(100%, 100%)) + .with_size(size!(100%, 100%=)) .with_direction(Direction::Horizontal) .with_children(|ui| { + // Sidebar: Container::default() .with_size(size!(54, 100%)) .with_background(color::rgb_hex(0x343334)) @@ -69,6 +86,8 @@ ui_main!( .with_size(size!(1, 100%)) .with_frame(color::rgb_hex(0x2d2d30)) .add_child(ui); + + // Explorer pane: Container::default() .with_size(size!(200, 100%)) .with_padding((15., 8.)) @@ -78,20 +97,16 @@ ui_main!( .add_child(ui); }) .add_child(ui); + + // "Code" pane Container::default() - .with_size(size!(100%, 100%)) + .with_size(size!(100%=, 100%)) .with_background(color::rgb_hex(0x1f1e1f)) .add_child(ui); }) .add_child(ui); - }) - .add_root(ui, size); - //Bottom bar (yeah, it's basically fake/overlay) - Container::default() - .with_size(size!(100%)) - .with_align((Alignment::Begin, Alignment::End)) - .with_children(|ui| { + //Status bar Container::default() .with_size(size!(100%, auto)) .with_background(color::rgb_hex(0x0079cc)) @@ -111,8 +126,8 @@ ui_main!( .with_text_size(15) .add_child(ui); }) - .add_child(ui); - }) - .add_root(ui, size); + .add_child(ui); + }) + .add_root(ui, size); } ); diff --git a/hui/src/element.rs b/hui/src/element.rs index e245610..0fe4689 100644 --- a/hui/src/element.rs +++ b/hui/src/element.rs @@ -4,7 +4,7 @@ use std::any::Any; use crate::{ draw::{atlas::ImageCtx, UiDrawCommandList}, input::InputCtx, - layout::LayoutInfo, + layout::{LayoutInfo, Size2d}, measure::Response, signal::SignalStore, state::StateRepo, @@ -45,6 +45,11 @@ pub trait UiElement { /// For example, "button" or "progress_bar" fn name(&self) -> &'static str; + /// Get the requested UiElement size + /// + /// You should implement this function whenever possible, otherwise some features may not work at all, such as the `Remaining` size + fn size(&self) -> Option { None } + /// Get the unique id used for internal state management\ /// This value must be unique for each instance of the element /// diff --git a/hui/src/element/builtin/container.rs b/hui/src/element/builtin/container.rs index 466bc93..403e9dc 100644 --- a/hui/src/element/builtin/container.rs +++ b/hui/src/element/builtin/container.rs @@ -5,7 +5,7 @@ use glam::{Vec2, vec2}; use crate::{ element::{ElementList, MeasureContext, ProcessContext, UiElement}, frame::{Frame, FrameRect}, - layout::{Alignment, Alignment2d, Direction, LayoutInfo, Size, Size2d, WrapBehavior}, + layout::{compute_size, Alignment, Alignment2d, Direction, LayoutInfo, Size, Size2d, WrapBehavior}, measure::{Hints, Response}, rect::Sides, }; @@ -19,6 +19,7 @@ use crate::{ struct CudLine { start_idx: usize, content_size: Vec2, + remaining_space: f32, } struct ContainerUserData { @@ -90,19 +91,24 @@ impl Default for Container { impl Container { pub fn measure_max_inner_size(&self, layout: &LayoutInfo) -> Vec2 { - let outer_size_x = match self.size.width { - Size::Auto => layout.max_size.x, - Size::Relative(p) => layout.max_size.x * p, - Size::Absolute(p) => p, - }; - let outer_size_y = match self.size.height { - Size::Auto => layout.max_size.y, - Size::Relative(p) => layout.max_size.y * p, - Size::Absolute(p) => p, - }; + // let outer_size_x = match self.size.width { + // Size::Auto => layout.max_size.x, + // Size::Relative(p) => layout.max_size.x * p, + // Size::Absolute(p) => p, + // Size::Remaining(p) => match layout.direction { + // Direction::Horizontal => layout.remaining_space.unwrap_or(layout.max_size.x) * p, + // Direction::Vertical => layout.max_size.x, + // } + // }; + // let outer_size_y = match self.size.height { + // Size::Auto => layout.max_size.y, + // Size::Relative(p) => layout.max_size.y * p, + // Size::Absolute(p) => p, + // }; + let outer_size = compute_size(layout, self.size, layout.max_size); vec2( - outer_size_x - (self.padding.left + self.padding.right), - outer_size_y - (self.padding.top + self.padding.bottom), + outer_size.x - (self.padding.left + self.padding.right), + outer_size.y - (self.padding.top + self.padding.bottom), ) } } @@ -112,6 +118,10 @@ impl UiElement for Container { "container" } + fn size(&self) -> Option { + Some(self.size) + } + fn measure(&self, ctx: MeasureContext) -> Response { // XXX: If both axes are NOT set to auto, we should be able quickly return the size // ... but we can't, because we need to measure the children to get the inner_content_size and user_data values @@ -126,11 +136,13 @@ impl UiElement for Container { Size::Auto => ctx.layout.max_size.x, Size::Relative(p) => ctx.layout.max_size.x * p, Size::Absolute(p) => p, + Size::Remaining(p) => ctx.layout.remaining_space.unwrap_or(ctx.layout.max_size.x) * p, }, Direction::Vertical => match self.size.height { Size::Auto => ctx.layout.max_size.y, Size::Relative(p) => ctx.layout.max_size.y * p, Size::Absolute(p) => p, + Size::Remaining(p) => ctx.layout.remaining_space.unwrap_or(ctx.layout.max_size.y) * p, } }; @@ -156,10 +168,25 @@ impl UiElement for Container { CudLine { start_idx: 0, content_size: Vec2::ZERO, + remaining_space: 0., } ]; + //set to true if in the current line there is an element with Remaining size (line will have to be wrapped) + // let mut has_remaining = false; + for (idx, element) in self.children.0.iter().enumerate() { + if let Some(esize) = element.size() { + let pri_size = match self.direction { + Direction::Horizontal => esize.width, + Direction::Vertical => esize.height, + }; + if matches!(pri_size, Size::Remaining(_)) { + //XXX: kinda a hack? + continue; + } + } + let measure = element.measure(MeasureContext{ state: ctx.state, layout: &LayoutInfo { @@ -172,6 +199,7 @@ impl UiElement for Container { //TODO: subtract size already taken by previous children max_size: self.measure_max_inner_size(ctx.layout), direction: self.direction, + remaining_space: None, }, text_measure: ctx.text_measure, current_font: ctx.current_font, @@ -193,12 +221,20 @@ impl UiElement for Container { line_size -= leftover_gap; //update the previous line metadata - lines.last_mut().unwrap().content_size = line_size; + { + let last_line = lines.last_mut().unwrap(); + last_line.content_size = line_size; + last_line.remaining_space = max_line_pri - match self.direction { + Direction::Horizontal => line_size.x, + Direction::Vertical => line_size.y, + }; + } //push the line metadata lines.push(CudLine { start_idx: idx, content_size: Vec2::ZERO, + remaining_space: 0., }); //Update the total size accordingly @@ -249,7 +285,14 @@ impl UiElement for Container { line_size -= leftover_gap; //Update the content size of the last line - lines.last_mut().unwrap().content_size = line_size; + { + let cur_line = lines.last_mut().unwrap(); + cur_line.content_size = line_size; + cur_line.remaining_space = max_line_pri - match self.direction { + Direction::Horizontal => line_size.x, + Direction::Vertical => line_size.y, + }; + } //Update the total size according to the size of the last line match self.direction { @@ -275,17 +318,27 @@ impl UiElement for Container { self.padding.top + self.padding.bottom, ); + let computed_size = compute_size(ctx.layout, self.size, total_size); match self.size.width { Size::Auto => (), - Size::Relative(percentage) => total_size.x = ctx.layout.max_size.x * percentage, - Size::Absolute(pixels) => total_size.x = pixels, + _ => total_size.x = computed_size.x, } match self.size.height { Size::Auto => (), - Size::Relative(percentage) => total_size.y = ctx.layout.max_size.y * percentage, - Size::Absolute(pixels) => total_size.y = pixels, + _ => total_size.y = computed_size.y, } + // match self.size.width { + // Size::Auto => (), + // Size::Relative(percentage) => total_size.x = ctx.layout.max_size.x * percentage, + // Size::Absolute(pixels) => total_size.x = pixels, + // } + // match self.size.height { + // Size::Auto => (), + // Size::Relative(percentage) => total_size.y = ctx.layout.max_size.y * percentage, + // Size::Absolute(pixels) => total_size.y = pixels, + // } + Response { size: total_size, hints: Hints { @@ -382,6 +435,7 @@ impl UiElement for Container { position: local_position, max_size: self.measure_max_inner_size(ctx.layout), direction: self.direction, + remaining_space: Some(cur_line.remaining_space), }; //measure diff --git a/hui/src/element/builtin/fill_rect.rs b/hui/src/element/builtin/fill_rect.rs index 824da7c..affcfec 100644 --- a/hui/src/element/builtin/fill_rect.rs +++ b/hui/src/element/builtin/fill_rect.rs @@ -6,7 +6,7 @@ use crate::{ draw::{RoundedCorners, UiDrawCommand}, element::{MeasureContext, ProcessContext, UiElement}, frame::{Frame, FrameRect}, - layout::{Size, Size2d}, + layout::{compute_size, Size, Size2d}, measure::Response, size }; @@ -45,20 +45,13 @@ impl UiElement for FillRect { "fill_rect" } + fn size(&self) -> Option { + Some(self.size) + } + fn measure(&self, ctx: MeasureContext) -> Response { Response { - size: vec2( - match self.size.width { - Size::Auto => ctx.layout.max_size.x, - Size::Relative(percentage) => ctx.layout.max_size.x * percentage, - Size::Absolute(pixels) => pixels, - }, - match self.size.height { - Size::Auto => ctx.layout.max_size.y, - Size::Relative(percentage) => ctx.layout.max_size.y * percentage, - Size::Absolute(pixels) => pixels, - }, - ), + size: compute_size(ctx.layout, self.size, ctx.layout.max_size), ..Default::default() } } diff --git a/hui/src/element/builtin/image.rs b/hui/src/element/builtin/image.rs index 1b71579..87c1a53 100644 --- a/hui/src/element/builtin/image.rs +++ b/hui/src/element/builtin/image.rs @@ -54,6 +54,10 @@ impl UiElement for Image { "image" } + fn size(&self) -> Option { + Some(self.size) + } + fn measure(&self, ctx: MeasureContext) -> Response { let dim = ctx.images.get_size(self.image).expect("invalid image handle"); let pre_size = compute_size(ctx.layout, self.size, dim.as_vec2()); diff --git a/hui/src/element/builtin/interactable.rs b/hui/src/element/builtin/interactable.rs index c15de60..ff4183e 100644 --- a/hui/src/element/builtin/interactable.rs +++ b/hui/src/element/builtin/interactable.rs @@ -48,6 +48,10 @@ impl UiElement for Interactable { "interactable" } + fn size(&self) -> Option { + self.element.size() + } + fn measure(&self, ctx: MeasureContext) -> crate::measure::Response { self.element.measure(ctx) } diff --git a/hui/src/element/builtin/text.rs b/hui/src/element/builtin/text.rs index fdc6734..48784cd 100644 --- a/hui/src/element/builtin/text.rs +++ b/hui/src/element/builtin/text.rs @@ -6,7 +6,7 @@ use glam::{vec2, Vec4}; use crate::{ draw::UiDrawCommand, element::{MeasureContext, ProcessContext, UiElement}, - layout::{Size, Size2d}, + layout::{compute_size, Size, Size2d}, measure::Response, text::FontHandle, }; @@ -74,6 +74,10 @@ impl UiElement for Text { "text" } + fn size(&self) -> Option { + Some(self.size) + } + fn measure(&self, ctx: MeasureContext) -> Response { let mut size = (0., 0.); if matches!(self.size.width, Size::Auto) || matches!(self.size.height, Size::Auto) { @@ -83,18 +87,7 @@ impl UiElement for Text { size.1 = res.height; } Response { - size: vec2( - match self.size.width { - Size::Auto => size.0, - Size::Relative(percentage) => ctx.layout.max_size.x * percentage, - Size::Absolute(pixels) => pixels, - }, - match self.size.height { - Size::Auto => size.1, - Size::Relative(percentage) => ctx.layout.max_size.y * percentage, - Size::Absolute(pixels) => pixels, - }, - ), + size: compute_size(ctx.layout, self.size, size.into()), ..Default::default() } } diff --git a/hui/src/frame/point.rs b/hui/src/frame/point.rs index dd5b0ca..5d0660e 100644 --- a/hui/src/frame/point.rs +++ b/hui/src/frame/point.rs @@ -24,11 +24,13 @@ impl From for FramePoint { impl From for FramePoint { /// Convert a `Size` into a `FramePoint` /// - /// This function behaves just as you would expect, but `Auto` is always treated as `BEGIN` + /// This function behaves just as you would expect, but: + /// - `Auto` is always treated as `BEGIN` + /// - `Remaining` is treated as `Relative` fn from(size: Size) -> Self { match size { Size::Auto => Self::BEGIN, - Size::Relative(value) => Self::relative(value), + Size::Relative(value) | Size::Remaining(value) => Self::relative(value), Size::Absolute(value) => Self::absolute(value), } } diff --git a/hui/src/instance.rs b/hui/src/instance.rs index c488844..ebfae21 100644 --- a/hui/src/instance.rs +++ b/hui/src/instance.rs @@ -157,6 +157,7 @@ impl UiInstance { position: Vec2::ZERO, max_size, direction: Direction::Vertical, + remaining_space: None, }; let measure = element.measure(MeasureContext { state: &self.stateful_state, diff --git a/hui/src/layout.rs b/hui/src/layout.rs index 12e8a13..046214e 100644 --- a/hui/src/layout.rs +++ b/hui/src/layout.rs @@ -134,7 +134,16 @@ pub enum Size { /// Expected range: `0.0..=1.0` Relative(f32), - //TODO Remaining(f32) + /// Size as a ratio of remaining space after all other elements have been laid out + /// + /// Expected range: `0.0..=1.0` + /// + /// - This feature is experimental and may not work as expected;\ + /// Current `Container` implementation: + /// - Assumes that he line is fully filled if any element uses `Remaining` size, even if sum of remaining sizes is less than 1.0 + /// - Does not support `Remaining` size in the secondary axis, it will be treated as `Relative` + /// - In cases where it's not applicable or not supported, it's defined to behave as `Relative` + Remaining(f32), } impl From for Size { @@ -197,6 +206,13 @@ pub struct LayoutInfo { /// Current direction of the layout\ /// (Usually matches direction of the parent container) pub direction: Direction, + + /// Remaining space in the primary axis\ + /// + /// This value is only available during the layout step and is only likely to be present if the element uses `Size::Remaining` + /// + /// (Make sure that LayoutInfo::direction is set to the correct direction!) + pub remaining_space: Option, } /// Helper function to calculate the size of an element based on its layout and size information\ @@ -206,11 +222,19 @@ pub fn compute_size(layout: &LayoutInfo, size: Size2d, comfy_size: Vec2) -> Vec2 Size::Auto => comfy_size.x, Size::Relative(fraction) => layout.max_size.x * fraction, Size::Absolute(size) => size, + Size::Remaining(fraction) => match layout.direction { + Direction::Horizontal => layout.remaining_space.unwrap_or(layout.max_size.x) * fraction, + Direction::Vertical => layout.max_size.x * fraction, + } }; let height = match size.height { Size::Auto => comfy_size.y, Size::Relative(fraction) => layout.max_size.y * fraction, Size::Absolute(size) => size, + Size::Remaining(fraction) => match layout.direction { + Direction::Horizontal => layout.max_size.y * fraction, + Direction::Vertical => layout.remaining_space.unwrap_or(layout.max_size.y) * fraction, + } }; vec2(width, height) } diff --git a/hui/src/macros.rs b/hui/src/macros.rs index 90b86ff..9386032 100644 --- a/hui/src/macros.rs +++ b/hui/src/macros.rs @@ -6,6 +6,8 @@ /// - `x` - `Size::Absolute(x)` /// - `x%` - `Size::Relative(x / 100.)` *(literal only)* /// - `x/` - `Size::Relative(x)` +/// - `x%=` - `Size::Remaining(x / 100.)` *(literal only)* +/// - `x/=` - `Size::Remaining(x)` /// /// ...where `x` is a literal, identifier or an expression wrapped in parentheses /// @@ -32,6 +34,12 @@ macro_rules! size { ($x:literal /) => { $crate::layout::Size::Relative($x as f32) }; + ($x:literal %=) => { + $crate::layout::Size::Remaining($x as f32 / 100.) + }; + ($x:literal /=) => { + $crate::layout::Size::Remaining($x as f32) + }; ($x:ident) => { $crate::layout::Size::Absolute($x as f32) @@ -39,6 +47,9 @@ macro_rules! size { ($x:ident /) => { $crate::layout::Size::Relative($x as f32) }; + ($x:ident /=) => { + $crate::layout::Size::Remaining($x as f32) + }; (($x:expr)) => { $crate::layout::Size::Absolute(($x) as f32) @@ -46,6 +57,9 @@ macro_rules! size { (($x:expr) /) => { $crate::layout::Size::Relative(($x) as f32) }; + (($x:expr) /=) => { + $crate::layout::Size::Remaining(($x) as f32) + }; ($x:tt , $y:tt $($ys:tt)?) => { $crate::layout::Size2d {