use cvkg_core::{Alignment, Distribution, LayoutCache, LayoutView, Rect, Size, SizeProposal};
pub use cvkg_core::layout::EdgeInsets;
pub struct HStack {
spacing: f32,
alignment: Alignment,
distribution: Distribution,
}
impl HStack {
pub fn new(spacing: f32, alignment: Alignment, distribution: Distribution) -> Self {
Self {
spacing,
alignment,
distribution,
}
}
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,
}
impl VStack {
pub fn new(spacing: f32, alignment: Alignment, distribution: Distribution) -> Self {
Self {
spacing,
alignment,
distribution,
}
}
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 {}
impl Default for ZStack {
fn default() -> Self {
Self::new()
}
}
impl ZStack {
pub fn new() -> Self {
Self {}
}
}
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);
}
}
}
}
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum GridTrack {
Fixed(f32),
Flex(f32),
Auto,
MinMax(f32, f32),
}
pub struct Grid {
pub columns: Vec<GridTrack>,
pub rows: Vec<GridTrack>,
pub column_gap: f32,
pub row_gap: f32,
}
impl Grid {
pub fn new(
columns: Vec<GridTrack>,
rows: Vec<GridTrack>,
column_gap: f32,
row_gap: f32,
) -> Self {
Self {
columns,
rows,
column_gap,
row_gap,
}
}
pub fn compute_layout_rects(
&self,
bounds: Rect,
subviews: &[&dyn LayoutView],
placements: &[Option<cvkg_core::GridPlacement>],
cache: &mut LayoutCache,
) -> Vec<Rect> {
let col_count = self.columns.len();
let row_count = self.rows.len();
if col_count == 0 || row_count == 0 || subviews.is_empty() {
return vec![Rect::zero(); subviews.len()];
}
let mut rects = vec![Rect::zero(); subviews.len()];
let mut resolved_placements = Vec::with_capacity(subviews.len());
let mut occupied = vec![vec![false; col_count]; row_count];
for &opt_placement in placements.iter() {
let placement = if let Some(p) = opt_placement {
let resolved_col = if p.column < 0 {
(col_count as i32 + p.column).max(0) as usize
} else {
p.column as usize
};
let resolved_row = if p.row < 0 {
(row_count as i32 + p.row).max(0) as usize
} else {
p.row as usize
};
cvkg_core::GridPlacement {
column: resolved_col as i32,
column_span: p.column_span.max(1),
row: resolved_row as i32,
row_span: p.row_span.max(1),
}
} else {
let mut found_col = 0;
let mut found_row = 0;
let mut found = false;
for (r, row) in occupied.iter().enumerate() {
for (c, cell) in row.iter().enumerate() {
if !*cell {
found_col = c;
found_row = r;
found = true;
break;
}
}
if found {
break;
}
}
cvkg_core::GridPlacement {
column: found_col as i32,
column_span: 1,
row: found_row as i32,
row_span: 1,
}
};
let start_r = (placement.row as usize).min(row_count - 1);
let end_r = (start_r + placement.row_span as usize).min(row_count);
let start_c = (placement.column as usize).min(col_count - 1);
let end_c = (start_c + placement.column_span as usize).min(col_count);
for row in occupied.iter_mut().take(end_r).skip(start_r) {
for cell in row.iter_mut().take(end_c).skip(start_c) {
*cell = true;
}
}
resolved_placements.push(placement);
}
let mut col_content_widths = vec![0.0f32; col_count];
let mut row_content_heights = vec![0.0f32; row_count];
for (i, &placement) in resolved_placements.iter().enumerate() {
let child = subviews[i];
let size = child.size_that_fits(
SizeProposal::new(Some(bounds.width), Some(bounds.height)),
&[],
cache,
);
if placement.column_span == 1 {
let c = placement.column as usize;
if c < col_count {
col_content_widths[c] = col_content_widths[c].max(size.width);
}
}
if placement.row_span == 1 {
let r = placement.row as usize;
if r < row_count {
row_content_heights[r] = row_content_heights[r].max(size.height);
}
}
}
let total_col_gaps = self.column_gap * (col_count - 1) as f32;
let mut remaining_width = (bounds.width - total_col_gaps).max(0.0);
let mut total_flex_weight_col = 0.0f32;
let mut col_widths = vec![0.0f32; col_count];
for (c, track) in self.columns.iter().enumerate() {
match track {
GridTrack::Fixed(w) => {
col_widths[c] = *w;
remaining_width = (remaining_width - *w).max(0.0);
}
GridTrack::Auto => {
let w = col_content_widths[c];
col_widths[c] = w;
remaining_width = (remaining_width - w).max(0.0);
}
GridTrack::MinMax(min, max) => {
let w = col_content_widths[c].clamp(*min, *max);
col_widths[c] = w;
remaining_width = (remaining_width - w).max(0.0);
}
GridTrack::Flex(weight) => {
total_flex_weight_col += *weight;
}
}
}
if total_flex_weight_col > 0.0 {
for (c, track) in self.columns.iter().enumerate() {
if let GridTrack::Flex(weight) = track {
col_widths[c] = (*weight / total_flex_weight_col) * remaining_width;
}
}
}
let total_row_gaps = self.row_gap * (row_count - 1) as f32;
let mut remaining_height = (bounds.height - total_row_gaps).max(0.0);
let mut total_flex_weight_row = 0.0f32;
let mut row_heights = vec![0.0f32; row_count];
for (r, track) in self.rows.iter().enumerate() {
match track {
GridTrack::Fixed(h) => {
row_heights[r] = *h;
remaining_height = (remaining_height - *h).max(0.0);
}
GridTrack::Auto => {
let h = row_content_heights[r];
row_heights[r] = h;
remaining_height = (remaining_height - h).max(0.0);
}
GridTrack::MinMax(min, max) => {
let h = row_content_heights[r].clamp(*min, *max);
row_heights[r] = h;
remaining_height = (remaining_height - h).max(0.0);
}
GridTrack::Flex(weight) => {
total_flex_weight_row += *weight;
}
}
}
if total_flex_weight_row > 0.0 {
for (r, track) in self.rows.iter().enumerate() {
if let GridTrack::Flex(weight) = track {
row_heights[r] = (*weight / total_flex_weight_row) * remaining_height;
}
}
}
let mut col_positions = vec![0.0f32; col_count + 1];
let mut x_acc = bounds.x;
for c in 0..col_count {
col_positions[c] = x_acc;
x_acc += col_widths[c] + self.column_gap;
}
col_positions[col_count] = x_acc - self.column_gap;
let mut row_positions = vec![0.0f32; row_count + 1];
let mut y_acc = bounds.y;
for r in 0..row_count {
row_positions[r] = y_acc;
y_acc += row_heights[r] + self.row_gap;
}
row_positions[row_count] = y_acc - self.row_gap;
for i in 0..subviews.len() {
let placement = resolved_placements[i];
let c_start = (placement.column as usize).min(col_count - 1);
let c_end = (c_start + placement.column_span as usize).min(col_count);
let r_start = (placement.row as usize).min(row_count - 1);
let r_end = (r_start + placement.row_span as usize).min(row_count);
let child_x = col_positions[c_start];
let child_y = row_positions[r_start];
let child_w = if c_end > c_start {
col_widths[c_start..c_end].iter().sum::<f32>()
+ self.column_gap * (c_end - c_start - 1) as f32
} else {
0.0
};
let child_h = if r_end > r_start {
row_heights[r_start..r_end].iter().sum::<f32>()
+ self.row_gap * (r_end - r_start - 1) as f32
} else {
0.0
};
rects[i] = Rect {
x: child_x,
y: child_y,
width: child_w,
height: child_h,
};
}
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 placements = vec![None; subviews.len()];
let rects = self.compute_layout_rects(bounds, &views, &placements, cache);
for (child, rect) in subviews.iter_mut().zip(rects) {
child.place_subviews(rect, &mut [], cache);
}
}
}
pub struct Padding {
pub insets: EdgeInsets,
}
impl Padding {
pub fn new(insets: EdgeInsets) -> Self {
Self { insets }
}
pub fn uniform(value: f32) -> Self {
Self {
insets: EdgeInsets::all(value),
}
}
pub fn symmetric(horizontal: f32, vertical: f32) -> Self {
Self {
insets: EdgeInsets {
top: vertical,
bottom: vertical,
leading: horizontal,
trailing: horizontal,
},
}
}
}
impl LayoutView for Padding {
fn size_that_fits(
&self,
proposal: SizeProposal,
subviews: &[&dyn LayoutView],
cache: &mut LayoutCache,
) -> Size {
let inner_proposal = SizeProposal::new(
proposal
.width
.map(|w| (w - self.insets.leading - self.insets.trailing).max(0.0)),
proposal
.height
.map(|h| (h - self.insets.top - self.insets.bottom).max(0.0)),
);
let child_size = if subviews.is_empty() {
Size::ZERO
} else {
subviews[0].size_that_fits(inner_proposal, &[], cache)
};
Size {
width: child_size.width + self.insets.leading + self.insets.trailing,
height: child_size.height + self.insets.top + self.insets.bottom,
}
}
fn place_subviews(
&self,
bounds: Rect,
subviews: &mut [&mut dyn LayoutView],
cache: &mut LayoutCache,
) {
let inner = Rect {
x: bounds.x + self.insets.leading,
y: bounds.y + self.insets.top,
width: (bounds.width - self.insets.leading - self.insets.trailing).max(0.0),
height: (bounds.height - self.insets.top - self.insets.bottom).max(0.0),
};
for child in subviews.iter_mut() {
child.place_subviews(inner, &mut [], cache);
}
}
}
pub struct SafeArea {
pub edges: SafeAreaEdges,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct SafeAreaEdges {
pub top: bool,
pub bottom: bool,
pub leading: bool,
pub trailing: bool,
}
impl Default for SafeAreaEdges {
fn default() -> Self {
Self {
top: true,
bottom: true,
leading: false,
trailing: false,
}
}
}
impl SafeArea {
pub fn all() -> Self {
Self {
edges: SafeAreaEdges {
top: true,
bottom: true,
leading: true,
trailing: true,
},
}
}
pub fn vertical() -> Self {
Self {
edges: SafeAreaEdges::default(),
}
}
fn insets(&self) -> EdgeInsets {
EdgeInsets {
top: if self.edges.top { 44.0 } else { 0.0 },
bottom: if self.edges.bottom { 34.0 } else { 0.0 },
leading: if self.edges.leading { 0.0 } else { 0.0 },
trailing: if self.edges.trailing { 0.0 } else { 0.0 },
}
}
}
impl LayoutView for SafeArea {
fn size_that_fits(
&self,
proposal: SizeProposal,
subviews: &[&dyn LayoutView],
cache: &mut LayoutCache,
) -> Size {
Padding::new(self.insets()).size_that_fits(proposal, subviews, cache)
}
fn place_subviews(
&self,
bounds: Rect,
subviews: &mut [&mut dyn LayoutView],
cache: &mut LayoutCache,
) {
Padding::new(self.insets()).place_subviews(bounds, subviews, cache);
}
}
pub struct AspectRatio {
pub ratio: f32,
}
impl AspectRatio {
pub fn new(ratio: f32) -> Self {
Self {
ratio: ratio.max(0.01),
}
}
pub fn square() -> Self {
Self::new(1.0)
}
pub fn widescreen() -> Self {
Self::new(16.0 / 9.0)
}
pub fn portrait() -> Self {
Self::new(9.0 / 16.0)
}
fn fitted_size(&self, proposal: SizeProposal) -> Size {
let max_w = proposal.width.unwrap_or(f32::MAX);
let max_h = proposal.height.unwrap_or(f32::MAX);
let w = max_w;
let h = w / self.ratio;
if h <= max_h {
return Size { width: w, height: h };
}
Size {
width: max_h * self.ratio,
height: max_h,
}
}
}
impl LayoutView for AspectRatio {
fn size_that_fits(
&self,
proposal: SizeProposal,
subviews: &[&dyn LayoutView],
cache: &mut LayoutCache,
) -> Size {
if subviews.is_empty() {
return self.fitted_size(proposal);
}
let child = subviews[0];
let child_size = child.size_that_fits(
SizeProposal::new(Some(f32::MAX), Some(f32::MAX)),
&[],
cache,
);
let intrinsic_ratio = child_size.width / child_size.height.max(0.01);
if (intrinsic_ratio - self.ratio).abs() < 0.01 {
return self.fitted_size(proposal);
}
let fit = self.fitted_size(proposal);
let child_w = fit.width.min(child_size.width);
let child_h = child_w / intrinsic_ratio;
let final_h = child_h.min(fit.height);
let final_w = final_h * intrinsic_ratio;
Size { width: final_w, height: final_h }
}
fn place_subviews(
&self,
bounds: Rect,
subviews: &mut [&mut dyn LayoutView],
cache: &mut LayoutCache,
) {
let fit = self.fitted_size(SizeProposal::new(Some(bounds.width), Some(bounds.height)));
let x = bounds.x + (bounds.width - fit.width) * 0.5;
let y = bounds.y + (bounds.height - fit.height) * 0.0;
let inner = Rect {
x,
y,
width: fit.width,
height: fit.height,
};
for child in subviews.iter_mut() {
child.place_subviews(inner, &mut [], cache);
}
}
}
#[cfg(test)]
mod tests {
use super::*;
struct MockView {
size: Size,
flex: f32,
}
impl LayoutView for MockView {
fn size_that_fits(
&self,
_p: SizeProposal,
_s: &[&dyn LayoutView],
_c: &mut LayoutCache,
) -> Size {
self.size
}
fn place_subviews(&self, _b: Rect, _s: &mut [&mut dyn LayoutView], _c: &mut LayoutCache) {}
fn flex_weight(&self) -> f32 {
self.flex
}
}
#[test]
fn test_hstack_basic() {
let v1 = MockView {
size: Size {
width: 50.0,
height: 50.0,
},
flex: 0.0,
};
let v2 = MockView {
size: Size {
width: 100.0,
height: 100.0,
},
flex: 0.0,
};
let views: Vec<&dyn LayoutView> = vec![&v1, &v2];
let mut cache = LayoutCache::new();
let bounds = Rect {
x: 0.0,
y: 0.0,
width: 300.0,
height: 200.0,
};
let rects = HStack::compute_layout(
10.0,
Alignment::Center,
Distribution::Leading,
bounds,
&views,
&mut cache,
);
assert_eq!(rects.len(), 2);
assert_eq!(
rects[0],
Rect {
x: 0.0,
y: 75.0,
width: 50.0,
height: 50.0
}
);
assert_eq!(
rects[1],
Rect {
x: 60.0,
y: 50.0,
width: 100.0,
height: 100.0
}
);
}
#[test]
fn test_vstack_flex() {
let v1 = MockView {
size: Size {
width: 100.0,
height: 50.0,
},
flex: 0.0,
};
let v2 = MockView {
size: Size {
width: 100.0,
height: 0.0,
},
flex: 1.0,
}; let views: Vec<&dyn LayoutView> = vec![&v1, &v2];
let mut cache = LayoutCache::new();
let bounds = Rect {
x: 0.0,
y: 0.0,
width: 200.0,
height: 160.0,
};
let rects = VStack::compute_layout(
10.0,
Alignment::Leading,
Distribution::Fill,
bounds,
&views,
&mut cache,
);
assert_eq!(rects.len(), 2);
assert_eq!(
rects[0],
Rect {
x: 0.0,
y: 0.0,
width: 100.0,
height: 50.0
}
);
assert_eq!(
rects[1],
Rect {
x: 0.0,
y: 60.0,
width: 100.0,
height: 100.0
}
); }
#[test]
fn test_grid_layout() {
let v1 = MockView {
size: Size::ZERO,
flex: 0.0,
};
let v2 = MockView {
size: Size::ZERO,
flex: 0.0,
};
let v3 = MockView {
size: Size::ZERO,
flex: 0.0,
};
let views: Vec<&dyn LayoutView> = vec![&v1, &v2, &v3];
let mut cache = LayoutCache::new();
let bounds = Rect {
x: 0.0,
y: 0.0,
width: 210.0,
height: 210.0,
};
let grid = Grid::new(
vec![GridTrack::Fixed(100.0), GridTrack::Fixed(100.0)],
vec![GridTrack::Fixed(100.0), GridTrack::Fixed(100.0)],
10.0,
10.0,
);
let placements = vec![
Some(cvkg_core::GridPlacement {
column: 0,
column_span: 1,
row: 0,
row_span: 1,
}),
Some(cvkg_core::GridPlacement {
column: 1,
column_span: 1,
row: 0,
row_span: 1,
}),
Some(cvkg_core::GridPlacement {
column: 0,
column_span: 1,
row: 1,
row_span: 1,
}),
];
let rects = grid.compute_layout_rects(bounds, &views, &placements, &mut cache);
assert_eq!(rects.len(), 3);
assert_eq!(
rects[0],
Rect {
x: 0.0,
y: 0.0,
width: 100.0,
height: 100.0
}
);
assert_eq!(
rects[1],
Rect {
x: 110.0,
y: 0.0,
width: 100.0,
height: 100.0
}
);
assert_eq!(
rects[2],
Rect {
x: 0.0,
y: 110.0,
width: 100.0,
height: 100.0
}
);
}
}