// Integration coverage for `Stack` alignment / margin / translation and for // the way `Spacer` distributes leftover space inside a `Column`. use ltk::core::Canvas; use ltk::{ stack, spacer, HAlign, VAlign, Rect }; fn make_canvas() -> Canvas { Canvas::new( 800, 600 ) } fn rect( x: f32, y: f32, w: f32, h: f32 ) -> Rect { Rect { x, y, width: w, height: h } } // ── Stack: alignment ────────────────────────────────────────────────────────── #[ test ] fn stack_fill_child_takes_full_rect() { let canvas = make_canvas(); let s = stack::<()>().push( spacer().width( 100.0 ).height( 100.0 ) ); let layout = s.layout( rect( 0.0, 0.0, 400.0, 300.0 ), &canvas ); assert_eq!( layout.len(), 1 ); let ( child_rect, _ ) = layout[ 0 ]; // Fill ignores the spacer's preferred size and stretches across the // entire stack rect. assert_eq!( child_rect, rect( 0.0, 0.0, 400.0, 300.0 ) ); } #[ test ] fn stack_center_centers_child_inside_rect() { let canvas = make_canvas(); let child = spacer().width( 100.0 ).height( 50.0 ); let s = stack::<()>().push_aligned( child, HAlign::Center, VAlign::Center ); let layout = s.layout( rect( 0.0, 0.0, 400.0, 300.0 ), &canvas ); let ( child_rect, _ ) = layout[ 0 ]; assert_eq!( child_rect.x, 150.0 ); assert_eq!( child_rect.y, 125.0 ); assert_eq!( child_rect.width, 100.0 ); assert_eq!( child_rect.height, 50.0 ); } #[ test ] fn stack_end_bottom_anchors_to_bottom_right() { let canvas = make_canvas(); let child = spacer().width( 80.0 ).height( 40.0 ); let s = stack::<()>().push_aligned( child, HAlign::End, VAlign::Bottom ); let layout = s.layout( rect( 0.0, 0.0, 400.0, 300.0 ), &canvas ); let ( child_rect, _ ) = layout[ 0 ]; assert_eq!( child_rect.x, 320.0 ); assert_eq!( child_rect.y, 260.0 ); } #[ test ] fn stack_start_top_anchors_to_top_left() { let canvas = make_canvas(); let child = spacer().width( 40.0 ).height( 30.0 ); let s = stack::<()>().push_aligned( child, HAlign::Start, VAlign::Top ); let layout = s.layout( rect( 100.0, 50.0, 400.0, 300.0 ), &canvas ); let ( child_rect, _ ) = layout[ 0 ]; assert_eq!( child_rect.x, 100.0 ); assert_eq!( child_rect.y, 50.0 ); } // ── Stack: margin ───────────────────────────────────────────────────────────── #[ test ] fn stack_margin_insets_fill_child_on_all_sides() { let canvas = make_canvas(); let child = spacer().width( 50.0 ).height( 50.0 ); let s = stack::<()>() .push_aligned_margin( child, HAlign::Fill, VAlign::Fill, 12.0 ); let layout = s.layout( rect( 0.0, 0.0, 400.0, 300.0 ), &canvas ); let ( child_rect, _ ) = layout[ 0 ]; // Fill + margin = original rect inset on every side. assert_eq!( child_rect, rect( 12.0, 12.0, 376.0, 276.0 ) ); } #[ test ] fn stack_margin_pushes_end_anchored_child_inward() { let canvas = make_canvas(); let child = spacer().width( 50.0 ).height( 50.0 ); let s = stack::<()>() .push_aligned_margin( child, HAlign::End, VAlign::Top, 8.0 ); let layout = s.layout( rect( 0.0, 0.0, 400.0, 300.0 ), &canvas ); let ( child_rect, _ ) = layout[ 0 ]; // 400 - 50 - 8 = 342 from left edge; y starts at 8. assert_eq!( child_rect.x, 342.0 ); assert_eq!( child_rect.y, 8.0 ); } #[ test ] fn stack_margin_larger_than_half_rect_clamps_inner_to_zero() { let canvas = make_canvas(); let child = spacer().width( 10.0 ).height( 10.0 ); let s = stack::<()>() .push_aligned_margin( child, HAlign::Fill, VAlign::Fill, 200.0 ); // Stack rect is 100×100; margin 200 → inner is clamped to (0, 0). let layout = s.layout( rect( 0.0, 0.0, 100.0, 100.0 ), &canvas ); let ( child_rect, _ ) = layout[ 0 ]; assert_eq!( child_rect.width, 0.0 ); assert_eq!( child_rect.height, 0.0 ); } // ── Stack: translation ──────────────────────────────────────────────────────── #[ test ] fn stack_translation_offsets_child_after_alignment() { let canvas = make_canvas(); let child = spacer().width( 40.0 ).height( 30.0 ); let s = stack::<()>() .push_translated( child, HAlign::Start, VAlign::Top, 5.0, 7.0 ); let layout = s.layout( rect( 0.0, 0.0, 400.0, 300.0 ), &canvas ); let ( child_rect, _ ) = layout[ 0 ]; assert_eq!( child_rect.x, 5.0 ); assert_eq!( child_rect.y, 7.0 ); } // ── Stack: order ───────────────────────────────────────────────────────────── #[ test ] fn stack_layout_preserves_child_order() { let canvas = make_canvas(); let s = stack::<()>() .push( spacer().width( 10.0 ).height( 10.0 ) ) .push( spacer().width( 20.0 ).height( 20.0 ) ) .push( spacer().width( 30.0 ).height( 30.0 ) ); let layout = s.layout( rect( 0.0, 0.0, 400.0, 300.0 ), &canvas ); assert_eq!( layout.len(), 3 ); assert_eq!( layout[ 0 ].1, 0 ); assert_eq!( layout[ 1 ].1, 1 ); assert_eq!( layout[ 2 ].1, 2 ); } // ── Stack: preferred_size ───────────────────────────────────────────────────── #[ test ] fn stack_preferred_size_returns_max_child_height() { let canvas = make_canvas(); let s = stack::<()>() .push( spacer().width( 40.0 ).height( 30.0 ) ) .push( spacer().width( 60.0 ).height( 80.0 ) ); let ( w, h ) = s.preferred_size( 400.0, &canvas ); // Stack always reports `max_width` for width, and the tallest child for // height — Stack is meant for overlaying, not flow. assert_eq!( w, 400.0 ); assert_eq!( h, 80.0 ); } #[ test ] fn stack_empty_preferred_size_height_is_zero() { let canvas = make_canvas(); let s: ltk::Stack<()> = stack(); let ( _, h ) = s.preferred_size( 400.0, &canvas ); assert_eq!( h, 0.0 ); } // ── Spacer: builder semantics ───────────────────────────────────────────────── // Builder-field storage (weight / fixed dimensions) is covered by internal // unit tests in `src/layout/spacer.rs`; here we exercise only the public // observable — `preferred_size` — which reflects those fields. #[ test ] fn spacer_default_has_no_fixed_size() { let canvas = make_canvas(); assert_eq!( spacer().preferred_size( &canvas ), ( 0.0, 0.0 ) ); } #[ test ] fn spacer_height_pins_vertical_axis_only() { let canvas = make_canvas(); assert_eq!( spacer().height( 24.0 ).preferred_size( &canvas ), ( 0.0, 24.0 ) ); } #[ test ] fn spacer_width_pins_horizontal_axis_only() { let canvas = make_canvas(); assert_eq!( spacer().width( 12.0 ).preferred_size( &canvas ), ( 12.0, 0.0 ) ); } // ── Spacer in Column: leftover distribution ─────────────────────────────────── // These use `column().layout(...)` directly (Column re-exports its `layout()` // method as `pub`). Fixed-height children consume known space; flexible spacers // must absorb the rest. #[ test ] fn flexible_spacer_in_column_consumes_leftover_height() { use ltk::column; let canvas = make_canvas(); let col = column::<()>() .padding( 0.0 ) .spacing( 0.0 ) .push( spacer().width( 50.0 ).height( 100.0 ) ) .push( spacer() ) // flexible .push( spacer().width( 50.0 ).height( 100.0 ) ); let layout = col.layout( rect( 0.0, 0.0, 200.0, 500.0 ), &canvas ); assert_eq!( layout.len(), 3 ); // Two fixed children of 100 each + one spacer absorbing the leftover 300. assert!( ( layout[ 0 ].0.height - 100.0 ).abs() < 1e-3 ); assert!( ( layout[ 1 ].0.height - 300.0 ).abs() < 1e-3 ); assert!( ( layout[ 2 ].0.height - 100.0 ).abs() < 1e-3 ); } #[ test ] fn weighted_spacers_split_leftover_proportionally() { use ltk::column; let canvas = make_canvas(); let col = column::<()>() .padding( 0.0 ) .spacing( 0.0 ) .push( spacer().weight( 1 ) ) .push( spacer().weight( 3 ) ); let layout = col.layout( rect( 0.0, 0.0, 200.0, 400.0 ), &canvas ); // Total weight 4; leftover 400; weight 1 → 100, weight 3 → 300. assert!( ( layout[ 0 ].0.height - 100.0 ).abs() < 1e-3 ); assert!( ( layout[ 1 ].0.height - 300.0 ).abs() < 1e-3 ); } #[ test ] fn fixed_height_spacer_does_not_consume_leftover() { use ltk::column; let canvas = make_canvas(); let col = column::<()>() .padding( 0.0 ) .spacing( 0.0 ) .push( spacer().height( 50.0 ) ) // fixed — keeps its 50 px .push( spacer() ); // flexible — absorbs the rest let layout = col.layout( rect( 0.0, 0.0, 200.0, 500.0 ), &canvas ); assert!( ( layout[ 0 ].0.height - 50.0 ).abs() < 1e-3 ); assert!( ( layout[ 1 ].0.height - 450.0 ).abs() < 1e-3 ); }