From 1341c29dd15a0ef7c47dae5a75fd47415b0c8612 Mon Sep 17 00:00:00 2001 From: Michael Mikovsky <77305074+Astatin3@users.noreply.github.com> Date: Wed, 29 Oct 2025 14:18:23 -0600 Subject: [PATCH] Work on constraint layout --- src/app/mod.rs | 4 +- src/lib.rs | 4 +- src/render/buffer.rs | 38 ++-- src/render/mod.rs | 6 +- src/render/renderer.rs | 24 +-- src/views/color_rect_view.rs | 57 +++++- src/views/constraint_layout.rs | 331 +++++++++++++++++++++++++++++++++ src/views/mod.rs | 66 ++++++- src/views/text_view.rs | 41 ++-- src/views/vertical_layout.rs | 18 +- 10 files changed, 530 insertions(+), 59 deletions(-) create mode 100644 src/views/constraint_layout.rs diff --git a/src/app/mod.rs b/src/app/mod.rs index 2a6683f..29aa1b1 100644 --- a/src/app/mod.rs +++ b/src/app/mod.rs @@ -1,5 +1,7 @@ mod cursors; +use std::time::Instant; + pub use cursors::{Cursor, set_cursor}; use wasm_bindgen::prelude::wasm_bindgen; @@ -68,7 +70,7 @@ impl App { // App events impl App { - pub fn resize(&mut self, width: u32, height: u32) { + pub fn resize(&mut self, width: usize, height: usize) { self.renderer.resize(width, height); self.draw(); } diff --git a/src/lib.rs b/src/lib.rs index 53e1c82..b28e9cf 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -28,7 +28,7 @@ macro_rules! log { } #[wasm_bindgen] -pub fn init(canvas: &web_sys::HtmlCanvasElement, width: u32, height: u32) -> App { +pub fn init(canvas: &web_sys::HtmlCanvasElement, width: usize, height: usize) -> App { log!("WASM Successfully initialized!"); let mut renderer = Renderer::new(canvas, width, height); @@ -38,7 +38,7 @@ pub fn init(canvas: &web_sys::HtmlCanvasElement, width: u32, height: u32) -> App } #[wasm_bindgen] -pub fn resize(app: &mut App, width: u32, height: u32) { +pub fn resize(app: &mut App, width: usize, height: usize) { app.resize(width, height); } diff --git a/src/render/buffer.rs b/src/render/buffer.rs index 49155ed..a20b76d 100644 --- a/src/render/buffer.rs +++ b/src/render/buffer.rs @@ -1,5 +1,5 @@ // use crate::console_log; -use crate::{log, render::rand}; +use crate::render::rand; // macro_rules! log { // ($($t:tt)*) => (console_log(&format_args!($($t)*).to_string())) @@ -7,14 +7,14 @@ use crate::{log, render::rand}; pub struct ImgBuffer { pub data: Vec, - width: u32, - height: u32, + width: usize, + height: usize, } impl ImgBuffer { - pub fn new(width: u32, height: u32) -> Self { + pub fn new(width: usize, height: usize) -> Self { ImgBuffer { - data: vec![127; (width * height * 4) as usize], + data: vec![127; width * height * 4], width, height, } @@ -30,29 +30,35 @@ impl ImgBuffer { } pub fn overlay_bitmap(&mut self, other: &Bitmap, xoffset: usize, yoffset: usize) { + let length = self.data.len(); for y in 0..other.height { for x in 0..other.width { - let offset = (y + yoffset) * self.width as usize + (x + xoffset); + // Calculate the offset for the current pixel in the buffer + // This uses bitshift instead of multiplication x4 for performance + let offset = ((y + yoffset) * self.width + (x + xoffset)) << 2; + if offset >= length { + return; + } let color = other.data[y * other.width + x]; - self.data[offset * 4] = color; - self.data[offset * 4 + 1] = color; - self.data[offset * 4 + 2] = color; - self.data[offset * 4 + 3] = 255; + self.data[offset] = color; + self.data[offset + 1] = color; + self.data[offset + 2] = color; + self.data[offset + 3] = 255; } } } - pub fn resize(&mut self, width: u32, height: u32) { - self.data = vec![0; (width * height * 4) as usize]; + pub fn resize(&mut self, width: usize, height: usize) { + self.data = vec![0; width * height * 4]; self.width = width; self.height = height; } - pub fn width(&self) -> u32 { + pub fn width(&self) -> usize { self.width } - pub fn height(&self) -> u32 { + pub fn height(&self) -> usize { self.height } @@ -62,8 +68,8 @@ impl ImgBuffer { } impl ImgBuffer { - pub fn set_pixel(&mut self, x: u32, y: u32, color: (u8, u8, u8)) { - let index = ((y * self.width + x) * 4) as usize; + pub fn set_pixel(&mut self, x: usize, y: usize, color: (u8, u8, u8)) { + let index = (y * self.width + x) * 4; self.data[index] = color.0; self.data[index + 1] = color.1; self.data[index + 2] = color.2; diff --git a/src/render/mod.rs b/src/render/mod.rs index 79b966a..429f48b 100644 --- a/src/render/mod.rs +++ b/src/render/mod.rs @@ -6,13 +6,13 @@ pub use renderer::Renderer; pub const RESOLUTION: u32 = 2000; -pub fn calc_resolution(width: u32, height: u32) -> (u32, u32, f32, f32) { +pub fn calc_resolution(width: usize, height: usize) -> (usize, usize, f32, f32) { let aspect = width as f32 / height as f32; let (distortion_x, distortion_y) = (aspect, 1. / aspect); - let new_width = (distortion_x * RESOLUTION as f32).round() as u32; - let new_height = (distortion_y * RESOLUTION as f32).round() as u32; + let new_width = (distortion_x * RESOLUTION as f32).round() as usize; + let new_height = (distortion_y * RESOLUTION as f32).round() as usize; (new_width, new_height, distortion_x, distortion_y) } diff --git a/src/render/renderer.rs b/src/render/renderer.rs index 4de4013..c4c8882 100644 --- a/src/render/renderer.rs +++ b/src/render/renderer.rs @@ -15,10 +15,10 @@ pub struct Renderer { pub(crate) img: ImgBuffer, pub(crate) rand: Rnd, - pub canvas_width: u32, - pub canvas_height: u32, - pub actual_width: u32, - pub actual_height: u32, + pub canvas_width: usize, + pub canvas_height: usize, + pub actual_width: usize, + pub actual_height: usize, pub distortion_x: f32, pub distortion_y: f32, @@ -27,7 +27,7 @@ pub struct Renderer { } impl Renderer { - pub fn new(canvas: &web_sys::HtmlCanvasElement, width: u32, height: u32) -> Self { + pub fn new(canvas: &web_sys::HtmlCanvasElement, width: usize, height: usize) -> Self { let (cwidth, cheight, dist_x, dist_y) = calc_resolution(width, height); // let ctx = canvas.get_context("2d").unwrap().unwrap(); @@ -55,7 +55,7 @@ impl Renderer { } } - pub fn resize(&mut self, width: u32, height: u32) { + pub fn resize(&mut self, width: usize, height: usize) { let (cwidth, cheight, dist_x, dist_y) = calc_resolution(width, height); self.canvas_width = cwidth; @@ -69,15 +69,15 @@ impl Renderer { self.ratio_y = cheight as f32 / height as f32; self.img.resize(cwidth, cheight); - self.ctx.canvas().unwrap().set_width(cwidth); - self.ctx.canvas().unwrap().set_height(cheight); + self.ctx.canvas().unwrap().set_width(cwidth as u32); + self.ctx.canvas().unwrap().set_height(cheight as u32); } pub fn update(&mut self) { let data = ImageData::new_with_u8_clamped_array_and_sh( Clamped(&self.img.data), - self.img.width(), - self.img.height(), + self.img.width() as u32, + self.img.height() as u32, ) .unwrap(); self.ctx.put_image_data(&data, 0., 0.).unwrap(); @@ -128,7 +128,7 @@ impl Renderer { for x in leftx as i32..rightx as i32 { for y in topy as i32..bottomy as i32 { - self.img.set_pixel(x as u32, y as u32, color); + self.img.set_pixel(x as usize, y as usize, color); } } } @@ -163,7 +163,7 @@ impl Renderer { for x in leftx as i32..rightx as i32 { for y in topy as i32..bottomy as i32 { if self.screen_dist_sq(x as f32, y as f32, cx, cy) <= r2 { - self.img.set_pixel(x as u32, y as u32, color); + self.img.set_pixel(x as usize, y as usize, color); } } } diff --git a/src/views/color_rect_view.rs b/src/views/color_rect_view.rs index 2160b22..0f798b4 100644 --- a/src/views/color_rect_view.rs +++ b/src/views/color_rect_view.rs @@ -1,15 +1,29 @@ use crate::{ render::Renderer, - views::{Bounds, View}, + views::{ + Bounds, LayoutView, View, + constraint_layout::{Constraint, ConstraintView}, + }, }; pub struct ColorRectView { color: (u8, u8, u8), + bounds: (Option, Option), + constraints: ( + Option, + Option, + Option, + Option, + ), } impl ColorRectView { pub fn new(r: u8, g: u8, b: u8) -> Self { - Self { color: (r, g, b) } + Self { + color: (r, g, b), + bounds: (None, None), + constraints: (None, None, None, None), + } } } @@ -18,7 +32,44 @@ impl View for ColorRectView { renderer.rect_xywh(x as i32, y as i32, w as i32, h as i32, self.color); // log!("Draw"); } +} + +impl LayoutView for ColorRectView { + fn set_bounds(&mut self, width: Option, height: Option) { + self.bounds = (width, height); + } fn bounds(&self, _ph: f32, _pw: f32) -> (Bounds, Bounds) { - (Bounds::MatchParent, Bounds::Pixels(200.)) + ( + *self.bounds.0.as_ref().unwrap_or(&Bounds::Pixels(100.)), + self.bounds.1.unwrap_or(Bounds::Pixels(100.)), + ) + } +} + +impl ConstraintView for ColorRectView { + fn set_constraints( + &mut self, + top: Option, + left: Option, + right: Option, + bottom: Option, + ) { + self.constraints = (top, left, right, bottom) + } + + fn get_constraints( + &self, + ) -> ( + &Option, + &Option, + &Option, + &Option, + ) { + ( + &self.constraints.0, + &self.constraints.1, + &self.constraints.2, + &self.constraints.3, + ) } } diff --git a/src/views/constraint_layout.rs b/src/views/constraint_layout.rs new file mode 100644 index 0000000..3ba7f69 --- /dev/null +++ b/src/views/constraint_layout.rs @@ -0,0 +1,331 @@ +use std::collections::{HashMap, HashSet}; + +use crate::{ + log, + render::Renderer, + views::{LayoutView, View}, +}; + +pub enum Constraint { + Left(f32, usize), + Right(f32, usize), + Top(f32, usize), + Bottom(f32, usize), + + LeftParent(f32), + RightParent(f32), + TopParent(f32), + BottomParent(f32), +} + +pub trait ConstraintView: LayoutView { + fn set_constraints( + &mut self, + top: Option, + left: Option, + right: Option, + bottom: Option, + ); + fn get_constraints( + &self, + ) -> ( + &Option, + &Option, + &Option, + &Option, + ); +} + +pub struct ConstraintLayout { + pub children: Vec>, + child_positions: Option>, +} + +struct Rect { + x: f32, + y: f32, + width: f32, + height: f32, +} + +impl ConstraintLayout { + pub fn new(children: Vec>) -> Self { + for child in &children { + let (top, left, right, bottom) = child.get_constraints(); + + assert!( + top.is_some() || bottom.is_some(), + "Vertically underconstrained" + ); + assert!( + left.is_some() || right.is_some(), + "Horizontally underconstrained" + ); + } + + let this = ConstraintLayout { + children, + child_positions: None, + }; + + this.test(); + + this + } + pub fn test(&self) { + let dependencies = self + .children + .iter() + .map(|c| { + let constraints = c.get_constraints(); + [constraints.0, constraints.1, constraints.2, constraints.3] + }) + .collect::>(); + + let dac = topological_sort(&dependencies); + + for i in 0..dac.len() { + log!("{:?}", dac[i]) + } + } +} + +impl View for ConstraintLayout { + fn draw(&mut self, renderer: &mut Renderer, x: f32, y: f32, self_w: f32, self_h: f32) { + let self_top = y; + let self_left = x; + let self_right = x + self_w; + let self_bottom = y + self_h; + + let mut calculated_positions: HashMap = HashMap::new(); + + let dependencies = self + .children + .iter() + .map(|c| { + let constraints = c.get_constraints(); + [constraints.0, constraints.1, constraints.2, constraints.3] + }) + .collect::>(); + + let sorted = topological_sort(&dependencies); + + for idx in sorted { + let child = &mut self.children[idx]; + + let (c_top, c_left, c_right, c_bottom) = child.get_constraints(); + let (c_width, c_height) = child.bounds(self_w, self_h); + + let c_width = match c_width { + crate::views::Bounds::MatchParent => self_w, + crate::views::Bounds::Pixels(x) => x, + }; + + let c_height = match c_height { + crate::views::Bounds::MatchParent => self_h, + crate::views::Bounds::Pixels(x) => x, + }; + + let (y, height) = match (c_top, c_bottom) { + (None, None) => unreachable!("Vertically unconstrained"), + (Some(Constraint::TopParent(top_margin)), None) => { + (self_top + top_margin, c_height) + } + (Some(Constraint::BottomParent(top_margin)), None) => { + (self_top + top_margin + c_height, c_height) + } + (None, Some(Constraint::TopParent(top_margin))) => { + (self_top - top_margin, c_height) + } + (None, Some(Constraint::BottomParent(bottom_margin))) => { + (self_h - (bottom_margin + c_height), c_height) + } + (Some(Constraint::TopParent(_)), Some(Constraint::TopParent(_))) => unreachable!(), + (Some(Constraint::BottomParent(_)), Some(Constraint::BottomParent(_))) => { + unreachable!() + } + (Some(Constraint::BottomParent(_)), Some(Constraint::TopParent(_))) => { + unreachable!() + } + (Some(Constraint::TopParent(_)), Some(Constraint::BottomParent(top_margin))) => { + (self_top + top_margin + c_height, c_height) + } + _ => todo!(), + }; + + assert!(height >= 0.); + + let x_align = |constraint: &Option| match constraint { + Some(Constraint::LeftParent(margin)) => Some(self_left + margin), + Some(Constraint::RightParent(margin)) => Some(self_right - margin), + Some(Constraint::Left(margin, id)) => { + let other = calculated_positions.get(&id).unwrap(); + Some(other.x - margin - c_width) + } + + Some(Constraint::Right(margin, id)) => { + let other = calculated_positions.get(&id).unwrap(); + Some(other.x + other.width + margin) + } + None => None, + _ => unreachable!(), + }; + + let x = match (x_align(c_left), x_align(c_right)) { + (Some(left), Some(right)) => (right + left - c_width) / 2., + (Some(left), None) => left, + (None, Some(right)) => right - c_width, + (None, None) => unreachable!("Horizontally unconstrained"), + }; + + // let (x, width) = match (c_left, c_right) { + // (None, None) => unreachable!("Vertically unconstrained"), + // (Some(Constraint::LeftParent(left_margin)), None) => { + // (self_top + left_margin, width) + // } + // (Some(Constraint::RightParent(right_margin)), None) => { + // (self_top - right_margin + width, width) + // } + // (None, Some(Constraint::LeftParent(left_margin))) => { + // (self_top - left_margin, width) + // } + // (None, Some(Constraint::RightParent(right_margin))) => { + // (self_w - (right_margin + width), width) + // // (self_top + top_margin + height, height) + // } + // (Some(Constraint::Left(left_margin, n)), None) => { + // let other = calculated_positions.get(n).unwrap(); + // (other.x - (width + left_margin), width) + // } + // (Some(Constraint::Right(right_margin, idx)), None) => { + // (self_top - right_margin + width, width) + // } + // (None, Some(Constraint::Left(left_margin, idx))) => (self_top - left_margin, width), + // (None, Some(Constraint::Right(right_margin, idx))) => { + // (self_w - (right_margin + width), width) + // // (self_top + top_margin + height, height) + // } + // (Some(Constraint::LeftParent(_)), Some(Constraint::LeftParent(_))) => { + // unreachable!() + // } + // (Some(Constraint::RightParent(_)), Some(Constraint::RightParent(_))) => { + // unreachable!() + // } + // (Some(Constraint::RightParent(_)), Some(Constraint::LeftParent(_))) => { + // unreachable!() + // } + // (Some(Constraint::TopParent(_)), Some(Constraint::BottomParent(top_margin))) => { + // (self_top + top_margin + width, width) + // } + // _ => todo!(), + // }; + + assert!(c_width >= 0.); + + calculated_positions.insert( + idx, + Rect { + x: x, + y: y, + width: c_width, + height: height, + }, + ); + child.draw(renderer, x, y, c_width, height); + + // match c_top { + // None => {} + // Some(Constraint::TopParent(top_margin)) => { + + // // let child_top = self_top + top_margin; + // // let child_left = self_left; + // // let child_right = self_right; + // // let child_bottom = child_top + child.get_height(); + + // // child.draw(renderer, child_left, child_top, child_right - child_left, child_bottom - child_top); + // } + // _ => unreachable!(), + // } + + // match + + // let child_top = child_top.unwrap_or(self_top); + // let child_left = child_left.unwrap_or(self_left); + // let child_right = child_right.unwrap_or(self_right); + // let child_bottom = child_bottom.unwrap_or(self_bottom); + } + } +} + +#[derive(Clone, Copy, PartialEq)] +enum VisitState { + Unvisited, + Visiting, + Visited, +} + +/// Performs a topological sort on objects with constraints. +/// Returns indices in topologically sorted order (dependencies before dependents). +/// Panics if a cycle is detected. +pub fn topological_sort(objects: &Vec<[&Option; 4]>) -> Vec { + let n = objects.len(); + let mut states = vec![VisitState::Unvisited; n]; + let mut result = Vec::with_capacity(n); + + for i in 0..n { + if states[i] == VisitState::Unvisited { + dfs(i, objects, &mut states, &mut result); + } + } + + result +} + +fn dfs( + node: usize, + objects: &Vec<[&Option; 4]>, + states: &mut Vec, + result: &mut Vec, +) { + if states[node] == VisitState::Visiting { + panic!("Cycle detected in constraint graph at node {}", node); + } + + if states[node] == VisitState::Visited { + return; + } + + states[node] = VisitState::Visiting; + + // Visit all dependencies + for constraint in &objects[node] { + if let Some(c) = constraint { + if let Some(dep) = get_dependency(c) { + if dep >= objects.len() { + panic!("Invalid dependency index {} in node {}", dep, node); + } + dfs(dep, objects, states, result); + } + } + } + + states[node] = VisitState::Visited; + result.push(node); +} + +/// Extracts the dependency index from a constraint, if it has one. +fn get_dependency(constraint: &Constraint) -> Option { + match constraint { + Constraint::Left(_, idx) => Some(*idx), + Constraint::Right(_, idx) => Some(*idx), + Constraint::Top(_, idx) => Some(*idx), + Constraint::Bottom(_, idx) => Some(*idx), + _ => None, // Parent constraints have no dependencies + } +} + +// impl LayoutView for ConstraintLayout { +// fn set_bounds(&mut self, width: Option, height: Option) { +// // Implement bounds setting logic here +// } +// } diff --git a/src/views/mod.rs b/src/views/mod.rs index fdd06c2..811a76a 100644 --- a/src/views/mod.rs +++ b/src/views/mod.rs @@ -1,6 +1,10 @@ -use crate::render::Renderer; +use crate::{ + render::Renderer, + views::constraint_layout::{Constraint, ConstraintLayout, ConstraintView}, +}; mod color_rect_view; +mod constraint_layout; mod text_view; mod vertical_layout; @@ -10,22 +14,72 @@ use vertical_layout::VerticalLayout; pub trait View { fn draw(&mut self, renderer: &mut Renderer, x: f32, y: f32, w: f32, h: f32); - fn bounds(&self, pw: f32, ph: f32) -> (Bounds, Bounds); } +#[derive(Debug, Clone, Copy, PartialEq)] pub enum Bounds { MatchParent, Pixels(f32), } +pub trait LayoutView: View { + fn set_bounds(&mut self, width: Option, height: Option); + fn bounds(&self, pw: f32, ph: f32) -> (Bounds, Bounds); +} + // pub trait ViewPosition: Drawable { // // fn draw(&self) // } pub fn default_view() -> Box { - Box::new(VerticalLayout::new(vec![ - Box::new(ColorRectView::new(12, 34, 56)), - Box::new(TextView::new("Testing!\n12345".to_string())), - Box::new(ColorRectView::new(20, 60, 80)), + let mut a = ColorRectView::new(127, 0, 0); + + let temp_margin = 10.0; + + a.set_constraints( + Some(Constraint::TopParent(temp_margin)), + None, + Some(Constraint::Left(temp_margin, 2)), + Some(Constraint::BottomParent(temp_margin)), + ); + + let mut b = ColorRectView::new(0, 127, 0); + + b.set_constraints( + Some(Constraint::TopParent(temp_margin)), + None, + Some(Constraint::RightParent(temp_margin)), + None, + ); + + let mut c = ColorRectView::new(0, 0, 127); + + c.set_constraints( + None, + Some(Constraint::LeftParent(temp_margin)), + Some(Constraint::RightParent(temp_margin)), + Some(Constraint::BottomParent(temp_margin)), + ); + + let mut d = ColorRectView::new(127, 127, 0); + + d.set_constraints( + None, + Some(Constraint::LeftParent(temp_margin)), + None, + Some(Constraint::BottomParent(temp_margin)), + ); + + Box::new(ConstraintLayout::new(vec![ + Box::new(a), + Box::new(b), + Box::new(c), + Box::new(d), ])) + + // Box::new(VerticalLayout::new(vec![ + // Box::new(ColorRectView::new(12, 34, 56)), + // Box::new(TextView::new("Testing!\n12345".to_string())), + // Box::new(ColorRectView::new(20, 60, 80)), + // ])) } diff --git a/src/views/text_view.rs b/src/views/text_view.rs index 6660512..15eb1f1 100644 --- a/src/views/text_view.rs +++ b/src/views/text_view.rs @@ -2,9 +2,8 @@ use fontdue::layout::{CoordinateSystem, Layout, LayoutSettings, TextStyle}; use crate::{ fonts::{FONTS, FontHandle}, - log, render::{Renderer, buffer::Bitmap}, - views::{Bounds, View}, + views::{Bounds, LayoutView, View}, }; pub struct TextView { @@ -12,6 +11,7 @@ pub struct TextView { text: String, scale: f32, font: FontHandle, + bounds: (Option, Option), } impl TextView { @@ -22,12 +22,22 @@ impl TextView { ..LayoutSettings::default() }); - Self { + let mut s = Self { layout, text, scale: 20., font: FontHandle::AtiksonHyperlegibleRegular, - } + bounds: (None, None), + }; + + let font = (s.font as usize).clone(); + + s.layout.clear(); + + s.layout + .append(&FONTS, &TextStyle::new(&s.text, s.scale, font)); + + s } } @@ -46,13 +56,6 @@ impl View for TextView { let (x, y) = renderer.undistort(x as f32, y as f32); - let font = (self.font as usize).clone(); - - self.layout.clear(); - - self.layout - .append(&FONTS, &TextStyle::new(&self.text, self.scale, font)); - let (mut width, mut height): (usize, usize) = (0, 0); for glyph in self.layout.glyphs() { width = width.max(glyph.x as usize + glyph.width); @@ -79,7 +82,21 @@ impl View for TextView { // new_bitmap } +} + +impl LayoutView for TextView { + fn set_bounds(&mut self, width: Option, height: Option) { + self.bounds = (width, height); + } fn bounds(&self, _ph: f32, _pw: f32) -> (Bounds, Bounds) { - (Bounds::MatchParent, Bounds::Pixels(200.)) + // let (x, y) = renderer.undistort(x as f32, y as f32); + + let (mut width, mut height): (usize, usize) = (0, 0); + for glyph in self.layout.glyphs() { + width = width.max(glyph.x as usize + glyph.width); + height = height.max(glyph.y as usize + glyph.height); + } + + (Bounds::Pixels(width as f32), Bounds::Pixels(height as f32)) } } diff --git a/src/views/vertical_layout.rs b/src/views/vertical_layout.rs index 8b667db..21bdd00 100644 --- a/src/views/vertical_layout.rs +++ b/src/views/vertical_layout.rs @@ -1,12 +1,16 @@ -use crate::views::{Bounds, View}; +use crate::views::{Bounds, LayoutView, View}; pub struct VerticalLayout { - pub views: Vec>, + pub views: Vec>, + pub bounds: (Option, Option), } impl VerticalLayout { - pub fn new(views: Vec>) -> Self { - Self { views } + pub fn new(views: Vec>) -> Self { + Self { + views, + bounds: (None, None), + } } } @@ -34,6 +38,12 @@ impl View for VerticalLayout { } } } +} + +impl LayoutView for VerticalLayout { + fn set_bounds(&mut self, width: Option, height: Option) { + self.bounds = (width, height); + } fn bounds(&self, pw: f32, ph: f32) -> (Bounds, Bounds) { let (mut maxx, mut totaly): (f32, f32) = (0., 0.);