use cvkg_core::{LayoutCache, LayoutView, Rect, Size, SizeProposal, Alignment, Distribution};
pub struct HStack {
spacing: f32,
alignment: Alignment,
distribution: Distribution,
children: Vec<Box<dyn LayoutView>>,
}
impl HStack {
pub fn new(spacing: f32, alignment: Alignment, distribution: Distribution) -> Self {
Self {
spacing,
alignment,
distribution,
children: Vec::new(),
}
}
pub fn add_view<V: LayoutView + 'static>(&mut self, view: V) {
self.children.push(Box::new(view));
}
pub fn compute_layout(
spacing: f32,
alignment: Alignment,
distribution: Distribution,
bounds: Rect,
subviews: &[&dyn LayoutView],
cache: &mut LayoutCache,
) -> Vec<Rect> {
let n = subviews.len();
if n == 0 { return Vec::new(); }
let mut rects = vec![Rect::zero(); n];
let mut child_sizes = Vec::with_capacity(n);
let mut total_fixed_width = 0.0;
let mut total_flex_weight = 0.0;
let mut flex_indices = Vec::new();
for (i, child) in subviews.iter().enumerate() {
let weight = child.flex_weight();
if weight > 0.0 {
total_flex_weight += weight;
flex_indices.push(i);
child_sizes.push(Size::ZERO); } else {
let desired = child.size_that_fits(
SizeProposal::new(Some(bounds.width), Some(bounds.height)),
&[],
cache
);
child_sizes.push(desired);
total_fixed_width += desired.width;
}
}
let total_spacing = spacing * (n - 1) as f32;
let available_for_flex = (bounds.width - total_fixed_width - total_spacing).max(0.0);
for &idx in &flex_indices {
let weight = subviews[idx].flex_weight();
let flex_width = (weight / total_flex_weight) * available_for_flex;
let desired = subviews[idx].size_that_fits(
SizeProposal::new(Some(flex_width), Some(bounds.height)),
&[],
cache
);
child_sizes[idx] = Size {
width: flex_width,
height: desired.height,
};
}
let content_width = if total_flex_weight > 0.0 {
bounds.width - total_spacing
} else {
total_fixed_width
} + total_spacing;
let (mut x, actual_spacing) = match distribution {
Distribution::Leading | Distribution::Fill if total_flex_weight > 0.0 => (bounds.x, spacing),
Distribution::Leading | Distribution::Fill => (bounds.x, spacing),
Distribution::Trailing => (bounds.x + bounds.width - content_width, spacing),
Distribution::Center => (bounds.x + (bounds.width - content_width) / 2.0, spacing),
Distribution::SpaceBetween => {
let s = if n > 1 { (bounds.width - (total_fixed_width + available_for_flex)) / (n - 1) as f32 } else { 0.0 };
(bounds.x, s)
}
_ => (bounds.x, spacing), };
for i in 0..n {
let size = child_sizes[i];
let y = match alignment {
Alignment::Top => bounds.y,
Alignment::Bottom => bounds.y + bounds.height - size.height,
_ => bounds.y + (bounds.height - size.height) / 2.0,
};
rects[i] = Rect {
x,
y,
width: size.width,
height: size.height,
};
x += size.width + actual_spacing;
}
rects
}
}
impl LayoutView for HStack {
fn size_that_fits(
&self,
proposal: SizeProposal,
subviews: &[&dyn LayoutView],
cache: &mut LayoutCache,
) -> Size {
let mut width = 0.0f32;
let mut height = 0.0f32;
for (i, child) in subviews.iter().enumerate() {
let child_size = child.size_that_fits(proposal, &[], cache);
width += child_size.width;
height = height.max(child_size.height);
if i < subviews.len() - 1 {
width += self.spacing;
}
}
Size {
width: proposal.width.unwrap_or(width),
height: proposal.height.unwrap_or(height),
}
}
fn place_subviews(
&self,
bounds: Rect,
subviews: &mut [&mut dyn LayoutView],
cache: &mut LayoutCache,
) {
let views: Vec<&dyn LayoutView> = subviews.iter().map(|v| &**v as &dyn LayoutView).collect();
let rects = Self::compute_layout(
self.spacing,
self.alignment,
self.distribution,
bounds,
&views,
cache,
);
for (child, rect) in subviews.iter_mut().zip(rects) {
child.place_subviews(rect, &mut [], cache);
}
}
}
pub struct VStack {
spacing: f32,
alignment: Alignment,
distribution: Distribution,
children: Vec<Box<dyn LayoutView>>,
}
impl VStack {
pub fn new(spacing: f32, alignment: Alignment, distribution: Distribution) -> Self {
Self {
spacing,
alignment,
distribution,
children: Vec::new(),
}
}
pub fn add_view<V: LayoutView + 'static>(&mut self, view: V) {
self.children.push(Box::new(view));
}
pub fn compute_layout(
spacing: f32,
alignment: Alignment,
distribution: Distribution,
bounds: Rect,
subviews: &[&dyn LayoutView],
cache: &mut LayoutCache,
) -> Vec<Rect> {
let n = subviews.len();
if n == 0 { return Vec::new(); }
let mut rects = vec![Rect::zero(); n];
let mut child_sizes = Vec::with_capacity(n);
let mut total_fixed_height = 0.0;
let mut total_flex_weight = 0.0;
let mut flex_indices = Vec::new();
for (i, child) in subviews.iter().enumerate() {
let weight = child.flex_weight();
if weight > 0.0 {
total_flex_weight += weight;
flex_indices.push(i);
child_sizes.push(Size::ZERO); } else {
let desired = child.size_that_fits(
SizeProposal::new(Some(bounds.width), Some(bounds.height)),
&[],
cache
);
child_sizes.push(desired);
total_fixed_height += desired.height;
}
}
let total_spacing = spacing * (n - 1) as f32;
let available_for_flex = (bounds.height - total_fixed_height - total_spacing).max(0.0);
for &idx in &flex_indices {
let weight = subviews[idx].flex_weight();
let flex_height = (weight / total_flex_weight) * available_for_flex;
let desired = subviews[idx].size_that_fits(
SizeProposal::new(Some(bounds.width), Some(flex_height)),
&[],
cache
);
child_sizes[idx] = Size {
width: desired.width,
height: flex_height,
};
}
let content_height = if total_flex_weight > 0.0 {
bounds.height - total_spacing
} else {
total_fixed_height
} + total_spacing;
let (mut y, actual_spacing) = match distribution {
Distribution::Leading | Distribution::Fill if total_flex_weight > 0.0 => (bounds.y, spacing),
Distribution::Leading | Distribution::Fill => (bounds.y, spacing),
Distribution::Trailing => (bounds.y + bounds.height - content_height, spacing),
Distribution::Center => (bounds.y + (bounds.height - content_height) / 2.0, spacing),
Distribution::SpaceBetween => {
let s = if n > 1 { (bounds.height - (total_fixed_height + available_for_flex)) / (n - 1) as f32 } else { 0.0 };
(bounds.y, s)
}
_ => (bounds.y, spacing),
};
for i in 0..n {
let size = child_sizes[i];
let x = match alignment {
Alignment::Leading => bounds.x,
Alignment::Trailing => bounds.x + bounds.width - size.width,
_ => bounds.x + (bounds.width - size.width) / 2.0,
};
rects[i] = Rect {
x,
y,
width: size.width,
height: size.height,
};
y += size.height + actual_spacing;
}
rects
}
}
impl LayoutView for VStack {
fn size_that_fits(
&self,
proposal: SizeProposal,
subviews: &[&dyn LayoutView],
cache: &mut LayoutCache,
) -> Size {
let mut width = 0.0f32;
let mut height = 0.0f32;
for (i, child) in subviews.iter().enumerate() {
let child_size = child.size_that_fits(proposal, &[], cache);
width = width.max(child_size.width);
height += child_size.height;
if i < subviews.len() - 1 {
height += self.spacing;
}
}
Size {
width: proposal.width.unwrap_or(width),
height: proposal.height.unwrap_or(height),
}
}
fn place_subviews(
&self,
bounds: Rect,
subviews: &mut [&mut dyn LayoutView],
cache: &mut LayoutCache,
) {
let views: Vec<&dyn LayoutView> = subviews.iter().map(|v| &**v as &dyn LayoutView).collect();
let rects = Self::compute_layout(
self.spacing,
self.alignment,
self.distribution,
bounds,
&views,
cache,
);
for (child, rect) in subviews.iter_mut().zip(rects) {
child.place_subviews(rect, &mut [], cache);
}
}
}
pub struct ZStack {
children: Vec<Box<dyn LayoutView>>,
}
impl ZStack {
pub fn new() -> Self {
Self {
children: Vec::new(),
}
}
pub fn add_view<V: LayoutView + 'static>(&mut self, view: V) {
self.children.push(Box::new(view));
}
}
impl LayoutView for ZStack {
fn size_that_fits(
&self,
proposal: SizeProposal,
subviews: &[&dyn LayoutView],
cache: &mut LayoutCache,
) -> Size {
let mut width = 0.0f32;
let mut height = 0.0f32;
for child in subviews.iter() {
let child_size = child.size_that_fits(proposal, &[], cache);
width = width.max(child_size.width);
height = height.max(child_size.height);
}
Size { width, height }
}
fn place_subviews(
&self,
bounds: Rect,
subviews: &mut [&mut dyn LayoutView],
cache: &mut LayoutCache,
) {
for child in subviews.iter_mut() {
child.place_subviews(bounds, &mut [], cache);
}
}
}
pub struct Spacer;
impl LayoutView for Spacer {
fn size_that_fits(
&self,
proposal: SizeProposal,
_subviews: &[&dyn LayoutView],
_cache: &mut LayoutCache,
) -> Size {
Size {
width: proposal.width.unwrap_or(0.0),
height: proposal.height.unwrap_or(0.0),
}
}
fn place_subviews(
&self,
_bounds: Rect,
_subviews: &mut [&mut dyn LayoutView],
_cache: &mut LayoutCache,
) {
}
}
pub struct Flex {
pub orientation: cvkg_core::Orientation,
pub spacing: f32,
}
impl Flex {
pub fn new(orientation: cvkg_core::Orientation, spacing: f32) -> Self {
Self {
orientation,
spacing,
}
}
}
impl LayoutView for Flex {
fn size_that_fits(
&self,
proposal: SizeProposal,
_subviews: &[&dyn LayoutView],
_cache: &mut LayoutCache,
) -> Size {
Size {
width: proposal.width.unwrap_or(100.0),
height: proposal.height.unwrap_or(100.0),
}
}
fn place_subviews(
&self,
bounds: Rect,
subviews: &mut [&mut dyn LayoutView],
cache: &mut LayoutCache,
) {
if subviews.is_empty() {
return;
}
let n = subviews.len() as f32;
match self.orientation {
cvkg_core::Orientation::Horizontal => {
let total_spacing = self.spacing * (n - 1.0);
let item_width = (bounds.width - total_spacing) / n;
for (i, child) in subviews.iter_mut().enumerate() {
let child_rect = Rect {
x: bounds.x + i as f32 * (item_width + self.spacing),
y: bounds.y,
width: item_width,
height: bounds.height,
};
child.place_subviews(child_rect, &mut [], cache);
}
}
cvkg_core::Orientation::Vertical => {
let total_spacing = self.spacing * (n - 1.0);
let item_height = (bounds.height - total_spacing) / n;
for (i, child) in subviews.iter_mut().enumerate() {
let child_rect = Rect {
x: bounds.x,
y: bounds.y + i as f32 * (item_height + self.spacing),
width: bounds.width,
height: item_height,
};
child.place_subviews(child_rect, &mut [], cache);
}
}
}
}
}
pub struct Grid {
pub rows: usize,
pub cols: usize,
pub spacing: f32,
}
impl Grid {
pub fn new(rows: usize, cols: usize, spacing: f32) -> Self {
Self { rows, cols, spacing }
}
pub fn compute_layout(
rows: usize,
cols: usize,
spacing: f32,
bounds: Rect,
subviews: &[&dyn LayoutView],
_cache: &mut LayoutCache,
) -> Vec<Rect> {
if subviews.is_empty() || rows == 0 || cols == 0 {
return Vec::new();
}
let mut rects = Vec::with_capacity(subviews.len());
let item_width = (bounds.width - (cols - 1) as f32 * spacing) / cols as f32;
let item_height = (bounds.height - (rows - 1) as f32 * spacing) / rows as f32;
for (i, _) in subviews.iter().enumerate() {
let r = i / cols;
let c = i % cols;
if r >= rows { break; }
rects.push(Rect {
x: bounds.x + c as f32 * (item_width + spacing),
y: bounds.y + r as f32 * (item_height + spacing),
width: item_width,
height: item_height,
});
}
rects
}
}
impl LayoutView for Grid {
fn size_that_fits(
&self,
proposal: SizeProposal,
_subviews: &[&dyn LayoutView],
_cache: &mut LayoutCache,
) -> Size {
Size {
width: proposal.width.unwrap_or(200.0),
height: proposal.height.unwrap_or(200.0),
}
}
fn place_subviews(
&self,
bounds: Rect,
subviews: &mut [&mut dyn LayoutView],
cache: &mut LayoutCache,
) {
let views: Vec<&dyn LayoutView> = subviews.iter().map(|v| &**v as &dyn LayoutView).collect();
let rects = Self::compute_layout(self.rows, self.cols, self.spacing, bounds, &views, cache);
for (child, rect) in subviews.iter_mut().zip(rects) {
child.place_subviews(rect, &mut [], cache);
}
}
}