use teksilo_canvas::{Rect, Size, SizeProposal};
use teksilo_core::widget::{LayoutContext, PaintContext, Widget};
#[derive(Debug)]
pub struct Spacer {
min_length: f32,
}
impl Spacer {
pub fn new() -> Self {
Self { min_length: 0.0 }
}
pub fn min_length(mut self, min: f32) -> Self {
self.min_length = min;
self
}
}
impl Default for Spacer {
fn default() -> Self {
Self::new()
}
}
impl Widget for Spacer {
fn layout_response(
&self,
_proposal: SizeProposal,
ctx: &LayoutContext,
) -> teksilo_core::widget::LayoutResponse {
use teksilo_core::widget::StackAxis;
let size = match ctx.stack_main_axis() {
Some(StackAxis::Horizontal) => Size::new(self.min_length, 0.0),
Some(StackAxis::Vertical) => Size::new(0.0, self.min_length),
None => Size::new(self.min_length, self.min_length),
};
teksilo_core::widget::LayoutResponse::flexible(size, 1.0)
}
fn paint(&self, _bounds: Rect, _canvas: &mut teksilo_canvas::Canvas, _ctx: &PaintContext) {
}
}
#[cfg(test)]
mod tests {
use super::*;
use teksilo_core::widget_tree::WidgetTree;
use crate::primitives::hstack::HStack;
use crate::primitives::vstack::VStack;
#[derive(Debug)]
struct FixedLeaf(f32, f32);
impl Widget for FixedLeaf {
fn layout_response(
&self,
_proposal: SizeProposal,
_ctx: &LayoutContext,
) -> teksilo_core::widget::LayoutResponse {
Size::new(self.0, self.1).into()
}
}
#[test]
fn spacer_pushes_to_trailing_in_hstack() {
let mut tree = WidgetTree::new();
let spacer = tree.add(Spacer::new());
let btn = tree.add(FixedLeaf(60.0, 30.0));
let _stack = tree.add(HStack::new().add_child(spacer).add_child(btn));
tree.layout(SizeProposal::exact(300.0, 50.0));
assert!((tree.bounds(btn).x - 240.0).abs() < 0.01);
}
#[test]
fn two_spacers_center_child_in_hstack() {
let mut tree = WidgetTree::new();
let s1 = tree.add(Spacer::new());
let label = tree.add(FixedLeaf(60.0, 30.0));
let s2 = tree.add(Spacer::new());
let _stack = tree.add(HStack::new().add_child(s1).add_child(label).add_child(s2));
tree.layout(SizeProposal::exact(300.0, 50.0));
assert!((tree.bounds(label).x - 120.0).abs() < 0.01);
}
#[test]
fn spacer_pushes_to_bottom_in_vstack() {
let mut tree = WidgetTree::new();
let spacer = tree.add(Spacer::new());
let btn = tree.add(FixedLeaf(60.0, 30.0));
let _stack = tree.add(VStack::new().add_child(spacer).add_child(btn));
tree.layout(SizeProposal::exact(200.0, 300.0));
assert!((tree.bounds(btn).y - 270.0).abs() < 0.01);
}
#[test]
fn spacer_with_min_length() {
let mut tree = WidgetTree::new();
let btn1 = tree.add(FixedLeaf(60.0, 30.0));
let spacer = tree.add(Spacer::new().min_length(20.0));
let btn2 = tree.add(FixedLeaf(60.0, 30.0));
let _stack = tree.add(
HStack::new()
.add_child(btn1)
.add_child(spacer)
.add_child(btn2),
);
tree.layout(SizeProposal::exact(300.0, 50.0));
assert!((tree.bounds(btn2).x - 240.0).abs() < 0.01);
}
#[test]
fn min_length_does_not_inflate_cross_axis() {
let mut tree = WidgetTree::new();
let btn = tree.add(FixedLeaf(60.0, 30.0));
let spacer = tree.add(Spacer::new().min_length(80.0));
let stack = tree.add(HStack::new().add_child(btn).add_child(spacer));
tree.layout(SizeProposal {
width: Some(400.0),
height: None,
});
assert!(
(tree.bounds(stack).height - 30.0).abs() < 0.01,
"HStack height should follow content (30), not the spacer min_length (80); got {}",
tree.bounds(stack).height
);
}
#[test]
fn min_length_is_honoured_on_the_main_axis() {
let mut tree = WidgetTree::new();
let btn1 = tree.add(FixedLeaf(60.0, 30.0));
let spacer = tree.add(Spacer::new().min_length(40.0));
let btn2 = tree.add(FixedLeaf(60.0, 30.0));
let stack = tree.add(
HStack::new()
.add_child(btn1)
.add_child(spacer)
.add_child(btn2),
);
tree.layout(SizeProposal::exact(160.0, 50.0));
assert!((tree.bounds(btn2).x - 100.0).abs() < 0.01);
let _ = stack;
}
#[test]
fn flex_factor_is_one() {
let theme = teksilo_core::presets::intui::light();
let ctx = LayoutContext::for_testing(&theme);
let r = Spacer::new().layout_response(SizeProposal::unspecified(), &ctx);
assert_eq!(r.flex, 1.0);
}
}