wip Remaing size

This commit is contained in:
griffi-gh 2024-03-25 14:08:04 +01:00
parent 6da1cc5d88
commit 3c6e6be754
11 changed files with 171 additions and 62 deletions

View file

@ -4,6 +4,7 @@ use hui::{
color, size, color, size,
draw::{ImageHandle, TextureFormat}, draw::{ImageHandle, TextureFormat},
layout::{Alignment, Direction}, layout::{Alignment, Direction},
rect::Sides,
element::{ element::{
container::Container, container::Container,
fill_rect::FillRect, fill_rect::FillRect,
@ -38,7 +39,12 @@ ui_main!(
.with_size(size!(100%, auto)) .with_size(size!(100%, auto))
.with_direction(Direction::Horizontal) .with_direction(Direction::Horizontal)
.with_align((Alignment::Begin, Alignment::Center)) .with_align((Alignment::Begin, Alignment::Center))
.with_padding(8.) .with_padding(Sides {
left: 5.,
right: 0.,
top: 5.,
bottom: 5.,
})
.with_gap(15.) .with_gap(15.)
.with_background(color::rgb_hex(0x3d3c3e)) .with_background(color::rgb_hex(0x3d3c3e))
.with_wrap(true) //XXX: not authentic but great for demostration .with_wrap(true) //XXX: not authentic but great for demostration
@ -51,6 +57,16 @@ ui_main!(
.with_text_size(15) .with_text_size(15)
.add_child(ui); .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); .add_child(ui);
FillRect::default() FillRect::default()
@ -58,9 +74,10 @@ ui_main!(
.with_frame(color::rgb_hex(0x2d2d30)) .with_frame(color::rgb_hex(0x2d2d30))
.add_child(ui); .add_child(ui);
Container::default() Container::default()
.with_size(size!(100%, 100%)) .with_size(size!(100%, 100%=))
.with_direction(Direction::Horizontal) .with_direction(Direction::Horizontal)
.with_children(|ui| { .with_children(|ui| {
// Sidebar:
Container::default() Container::default()
.with_size(size!(54, 100%)) .with_size(size!(54, 100%))
.with_background(color::rgb_hex(0x343334)) .with_background(color::rgb_hex(0x343334))
@ -69,6 +86,8 @@ ui_main!(
.with_size(size!(1, 100%)) .with_size(size!(1, 100%))
.with_frame(color::rgb_hex(0x2d2d30)) .with_frame(color::rgb_hex(0x2d2d30))
.add_child(ui); .add_child(ui);
// Explorer pane:
Container::default() Container::default()
.with_size(size!(200, 100%)) .with_size(size!(200, 100%))
.with_padding((15., 8.)) .with_padding((15., 8.))
@ -78,20 +97,16 @@ ui_main!(
.add_child(ui); .add_child(ui);
}) })
.add_child(ui); .add_child(ui);
// "Code" pane
Container::default() Container::default()
.with_size(size!(100%, 100%)) .with_size(size!(100%=, 100%))
.with_background(color::rgb_hex(0x1f1e1f)) .with_background(color::rgb_hex(0x1f1e1f))
.add_child(ui); .add_child(ui);
}) })
.add_child(ui); .add_child(ui);
})
.add_root(ui, size);
//Bottom bar (yeah, it's basically fake/overlay) //Status bar
Container::default()
.with_size(size!(100%))
.with_align((Alignment::Begin, Alignment::End))
.with_children(|ui| {
Container::default() Container::default()
.with_size(size!(100%, auto)) .with_size(size!(100%, auto))
.with_background(color::rgb_hex(0x0079cc)) .with_background(color::rgb_hex(0x0079cc))

View file

@ -4,7 +4,7 @@ use std::any::Any;
use crate::{ use crate::{
draw::{atlas::ImageCtx, UiDrawCommandList}, draw::{atlas::ImageCtx, UiDrawCommandList},
input::InputCtx, input::InputCtx,
layout::LayoutInfo, layout::{LayoutInfo, Size2d},
measure::Response, measure::Response,
signal::SignalStore, signal::SignalStore,
state::StateRepo, state::StateRepo,
@ -45,6 +45,11 @@ pub trait UiElement {
/// For example, "button" or "progress_bar" /// For example, "button" or "progress_bar"
fn name(&self) -> &'static str; 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<Size2d> { None }
/// Get the unique id used for internal state management\ /// Get the unique id used for internal state management\
/// This value must be unique for each instance of the element /// This value must be unique for each instance of the element
/// ///

View file

@ -5,7 +5,7 @@ use glam::{Vec2, vec2};
use crate::{ use crate::{
element::{ElementList, MeasureContext, ProcessContext, UiElement}, element::{ElementList, MeasureContext, ProcessContext, UiElement},
frame::{Frame, FrameRect}, frame::{Frame, FrameRect},
layout::{Alignment, Alignment2d, Direction, LayoutInfo, Size, Size2d, WrapBehavior}, layout::{compute_size, Alignment, Alignment2d, Direction, LayoutInfo, Size, Size2d, WrapBehavior},
measure::{Hints, Response}, measure::{Hints, Response},
rect::Sides, rect::Sides,
}; };
@ -19,6 +19,7 @@ use crate::{
struct CudLine { struct CudLine {
start_idx: usize, start_idx: usize,
content_size: Vec2, content_size: Vec2,
remaining_space: f32,
} }
struct ContainerUserData { struct ContainerUserData {
@ -90,19 +91,24 @@ impl Default for Container {
impl Container { impl Container {
pub fn measure_max_inner_size(&self, layout: &LayoutInfo) -> Vec2 { pub fn measure_max_inner_size(&self, layout: &LayoutInfo) -> Vec2 {
let outer_size_x = match self.size.width { // let outer_size_x = match self.size.width {
Size::Auto => layout.max_size.x, // Size::Auto => layout.max_size.x,
Size::Relative(p) => layout.max_size.x * p, // Size::Relative(p) => layout.max_size.x * p,
Size::Absolute(p) => p, // Size::Absolute(p) => p,
}; // Size::Remaining(p) => match layout.direction {
let outer_size_y = match self.size.height { // Direction::Horizontal => layout.remaining_space.unwrap_or(layout.max_size.x) * p,
Size::Auto => layout.max_size.y, // Direction::Vertical => layout.max_size.x,
Size::Relative(p) => layout.max_size.y * 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 = compute_size(layout, self.size, layout.max_size);
vec2( vec2(
outer_size_x - (self.padding.left + self.padding.right), outer_size.x - (self.padding.left + self.padding.right),
outer_size_y - (self.padding.top + self.padding.bottom), outer_size.y - (self.padding.top + self.padding.bottom),
) )
} }
} }
@ -112,6 +118,10 @@ impl UiElement for Container {
"container" "container"
} }
fn size(&self) -> Option<Size2d> {
Some(self.size)
}
fn measure(&self, ctx: MeasureContext) -> Response { fn measure(&self, ctx: MeasureContext) -> Response {
// XXX: If both axes are NOT set to auto, we should be able quickly return the size // 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 // ... 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::Auto => ctx.layout.max_size.x,
Size::Relative(p) => ctx.layout.max_size.x * p, Size::Relative(p) => ctx.layout.max_size.x * p,
Size::Absolute(p) => 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 { Direction::Vertical => match self.size.height {
Size::Auto => ctx.layout.max_size.y, Size::Auto => ctx.layout.max_size.y,
Size::Relative(p) => ctx.layout.max_size.y * p, Size::Relative(p) => ctx.layout.max_size.y * p,
Size::Absolute(p) => 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 { CudLine {
start_idx: 0, start_idx: 0,
content_size: Vec2::ZERO, 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() { 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{ let measure = element.measure(MeasureContext{
state: ctx.state, state: ctx.state,
layout: &LayoutInfo { layout: &LayoutInfo {
@ -172,6 +199,7 @@ impl UiElement for Container {
//TODO: subtract size already taken by previous children //TODO: subtract size already taken by previous children
max_size: self.measure_max_inner_size(ctx.layout), max_size: self.measure_max_inner_size(ctx.layout),
direction: self.direction, direction: self.direction,
remaining_space: None,
}, },
text_measure: ctx.text_measure, text_measure: ctx.text_measure,
current_font: ctx.current_font, current_font: ctx.current_font,
@ -193,12 +221,20 @@ impl UiElement for Container {
line_size -= leftover_gap; line_size -= leftover_gap;
//update the previous line metadata //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 //push the line metadata
lines.push(CudLine { lines.push(CudLine {
start_idx: idx, start_idx: idx,
content_size: Vec2::ZERO, content_size: Vec2::ZERO,
remaining_space: 0.,
}); });
//Update the total size accordingly //Update the total size accordingly
@ -249,7 +285,14 @@ impl UiElement for Container {
line_size -= leftover_gap; line_size -= leftover_gap;
//Update the content size of the last line //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 //Update the total size according to the size of the last line
match self.direction { match self.direction {
@ -275,17 +318,27 @@ impl UiElement for Container {
self.padding.top + self.padding.bottom, self.padding.top + self.padding.bottom,
); );
let computed_size = compute_size(ctx.layout, self.size, total_size);
match self.size.width { match self.size.width {
Size::Auto => (), Size::Auto => (),
Size::Relative(percentage) => total_size.x = ctx.layout.max_size.x * percentage, _ => total_size.x = computed_size.x,
Size::Absolute(pixels) => total_size.x = pixels,
} }
match self.size.height { match self.size.height {
Size::Auto => (), Size::Auto => (),
Size::Relative(percentage) => total_size.y = ctx.layout.max_size.y * percentage, _ => total_size.y = computed_size.y,
Size::Absolute(pixels) => total_size.y = pixels,
} }
// 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 { Response {
size: total_size, size: total_size,
hints: Hints { hints: Hints {
@ -382,6 +435,7 @@ impl UiElement for Container {
position: local_position, position: local_position,
max_size: self.measure_max_inner_size(ctx.layout), max_size: self.measure_max_inner_size(ctx.layout),
direction: self.direction, direction: self.direction,
remaining_space: Some(cur_line.remaining_space),
}; };
//measure //measure

View file

@ -6,7 +6,7 @@ use crate::{
draw::{RoundedCorners, UiDrawCommand}, draw::{RoundedCorners, UiDrawCommand},
element::{MeasureContext, ProcessContext, UiElement}, element::{MeasureContext, ProcessContext, UiElement},
frame::{Frame, FrameRect}, frame::{Frame, FrameRect},
layout::{Size, Size2d}, layout::{compute_size, Size, Size2d},
measure::Response, measure::Response,
size size
}; };
@ -45,20 +45,13 @@ impl UiElement for FillRect {
"fill_rect" "fill_rect"
} }
fn size(&self) -> Option<Size2d> {
Some(self.size)
}
fn measure(&self, ctx: MeasureContext) -> Response { fn measure(&self, ctx: MeasureContext) -> Response {
Response { Response {
size: vec2( size: compute_size(ctx.layout, self.size, ctx.layout.max_size),
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,
},
),
..Default::default() ..Default::default()
} }
} }

View file

@ -54,6 +54,10 @@ impl UiElement for Image {
"image" "image"
} }
fn size(&self) -> Option<Size2d> {
Some(self.size)
}
fn measure(&self, ctx: MeasureContext) -> Response { fn measure(&self, ctx: MeasureContext) -> Response {
let dim = ctx.images.get_size(self.image).expect("invalid image handle"); let dim = ctx.images.get_size(self.image).expect("invalid image handle");
let pre_size = compute_size(ctx.layout, self.size, dim.as_vec2()); let pre_size = compute_size(ctx.layout, self.size, dim.as_vec2());

View file

@ -48,6 +48,10 @@ impl UiElement for Interactable {
"interactable" "interactable"
} }
fn size(&self) -> Option<crate::layout::Size2d> {
self.element.size()
}
fn measure(&self, ctx: MeasureContext) -> crate::measure::Response { fn measure(&self, ctx: MeasureContext) -> crate::measure::Response {
self.element.measure(ctx) self.element.measure(ctx)
} }

View file

@ -6,7 +6,7 @@ use glam::{vec2, Vec4};
use crate::{ use crate::{
draw::UiDrawCommand, draw::UiDrawCommand,
element::{MeasureContext, ProcessContext, UiElement}, element::{MeasureContext, ProcessContext, UiElement},
layout::{Size, Size2d}, layout::{compute_size, Size, Size2d},
measure::Response, measure::Response,
text::FontHandle, text::FontHandle,
}; };
@ -74,6 +74,10 @@ impl UiElement for Text {
"text" "text"
} }
fn size(&self) -> Option<Size2d> {
Some(self.size)
}
fn measure(&self, ctx: MeasureContext) -> Response { fn measure(&self, ctx: MeasureContext) -> Response {
let mut size = (0., 0.); let mut size = (0., 0.);
if matches!(self.size.width, Size::Auto) || matches!(self.size.height, Size::Auto) { 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; size.1 = res.height;
} }
Response { Response {
size: vec2( size: compute_size(ctx.layout, self.size, size.into()),
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,
},
),
..Default::default() ..Default::default()
} }
} }

View file

@ -24,11 +24,13 @@ impl From<f32> for FramePoint {
impl From<Size> for FramePoint { impl From<Size> for FramePoint {
/// Convert a `Size` into a `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 { fn from(size: Size) -> Self {
match size { match size {
Size::Auto => Self::BEGIN, 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), Size::Absolute(value) => Self::absolute(value),
} }
} }

View file

@ -157,6 +157,7 @@ impl UiInstance {
position: Vec2::ZERO, position: Vec2::ZERO,
max_size, max_size,
direction: Direction::Vertical, direction: Direction::Vertical,
remaining_space: None,
}; };
let measure = element.measure(MeasureContext { let measure = element.measure(MeasureContext {
state: &self.stateful_state, state: &self.stateful_state,

View file

@ -134,7 +134,16 @@ pub enum Size {
/// Expected range: `0.0..=1.0` /// Expected range: `0.0..=1.0`
Relative(f32), 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<f32> for Size { impl From<f32> for Size {
@ -197,6 +206,13 @@ pub struct LayoutInfo {
/// Current direction of the layout\ /// Current direction of the layout\
/// (Usually matches direction of the parent container) /// (Usually matches direction of the parent container)
pub direction: Direction, 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<f32>,
} }
/// Helper function to calculate the size of an element based on its layout and size information\ /// 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::Auto => comfy_size.x,
Size::Relative(fraction) => layout.max_size.x * fraction, Size::Relative(fraction) => layout.max_size.x * fraction,
Size::Absolute(size) => size, 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 { let height = match size.height {
Size::Auto => comfy_size.y, Size::Auto => comfy_size.y,
Size::Relative(fraction) => layout.max_size.y * fraction, Size::Relative(fraction) => layout.max_size.y * fraction,
Size::Absolute(size) => size, 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) vec2(width, height)
} }

View file

@ -6,6 +6,8 @@
/// - `x` - `Size::Absolute(x)` /// - `x` - `Size::Absolute(x)`
/// - `x%` - `Size::Relative(x / 100.)` *(literal only)* /// - `x%` - `Size::Relative(x / 100.)` *(literal only)*
/// - `x/` - `Size::Relative(x)` /// - `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 /// ...where `x` is a literal, identifier or an expression wrapped in parentheses
/// ///
@ -32,6 +34,12 @@ macro_rules! size {
($x:literal /) => { ($x:literal /) => {
$crate::layout::Size::Relative($x as f32) $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) => { ($x:ident) => {
$crate::layout::Size::Absolute($x as f32) $crate::layout::Size::Absolute($x as f32)
@ -39,6 +47,9 @@ macro_rules! size {
($x:ident /) => { ($x:ident /) => {
$crate::layout::Size::Relative($x as f32) $crate::layout::Size::Relative($x as f32)
}; };
($x:ident /=) => {
$crate::layout::Size::Remaining($x as f32)
};
(($x:expr)) => { (($x:expr)) => {
$crate::layout::Size::Absolute(($x) as f32) $crate::layout::Size::Absolute(($x) as f32)
@ -46,6 +57,9 @@ macro_rules! size {
(($x:expr) /) => { (($x:expr) /) => {
$crate::layout::Size::Relative(($x) as f32) $crate::layout::Size::Relative(($x) as f32)
}; };
(($x:expr) /=) => {
$crate::layout::Size::Remaining(($x) as f32)
};
($x:tt , $y:tt $($ys:tt)?) => { ($x:tt , $y:tt $($ys:tt)?) => {
$crate::layout::Size2d { $crate::layout::Size2d {