use teksilo_canvas::{Rect, Size, SizeProposal};
use teksilo_core::widget::{LayoutContext, LayoutResponse, PendingChild, Widget, WidgetPlacement};
use teksilo_core::widget_id::WidgetId;
#[derive(Debug)]
pub struct Shrinkable {
child_id: Option<WidgetId>,
pending_child: Option<PendingChild>,
shrink: f32,
min_width: f32,
min_height: f32,
}
impl Shrinkable {
pub fn new() -> Self {
Self {
child_id: None,
pending_child: None,
shrink: 1.0,
min_width: 0.0,
min_height: 0.0,
}
}
pub fn shrink(mut self, weight: f32) -> Self {
self.shrink = weight.max(0.0);
self
}
pub fn min_width(mut self, min: f32) -> Self {
self.min_width = min.max(0.0);
self
}
pub fn min_height(mut self, min: f32) -> Self {
self.min_height = min.max(0.0);
self
}
pub fn child(mut self, widget: impl Widget + 'static) -> Self {
self.pending_child = Some(PendingChild::Deferred(Box::new(widget)));
self
}
pub fn child_id(mut self, id: WidgetId) -> Self {
self.pending_child = Some(PendingChild::Id(id));
self
}
}
impl Default for Shrinkable {
fn default() -> Self {
Self::new()
}
}
impl Widget for Shrinkable {
fn build(&mut self, ctx: &mut teksilo_core::build_context::BuildContext) -> Vec<WidgetId> {
if let Some(pending) = self.pending_child.take() {
let id = match pending {
PendingChild::Id(id) => id,
PendingChild::Deferred(w) => ctx.add_boxed(w),
};
self.child_id = Some(id);
}
self.child_id.into_iter().collect()
}
fn layout_response(&self, proposal: SizeProposal, ctx: &LayoutContext) -> LayoutResponse {
let Some(child) = self.child_id else {
return proposal.resolve(0.0, 0.0).into();
};
let r = ctx
.child_layout_response(child, proposal)
.unwrap_or(LayoutResponse::ZERO);
let min = Size::new(
self.min_width.min(r.size.width),
self.min_height.min(r.size.height),
);
LayoutResponse::flexible(r.size, r.flex)
.with_shrink(self.shrink)
.with_min(min)
}
fn place_children(
&self,
bounds: Rect,
_proposal: SizeProposal,
children: &mut [WidgetPlacement],
_ctx: &LayoutContext,
) {
for child in children.iter_mut() {
child.origin = bounds.origin();
child.size = bounds.size();
}
}
fn children(&self) -> Vec<WidgetId> {
self.child_id.into_iter().collect()
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::primitives::hstack::HStack;
use teksilo_core::widget_tree::WidgetTree;
#[derive(Debug)]
struct FixedLeaf(f32, f32);
impl Widget for FixedLeaf {
fn layout_response(&self, _p: SizeProposal, _ctx: &LayoutContext) -> LayoutResponse {
Size::new(self.0, self.1).into()
}
}
#[test]
fn shrinkable_wrapper_compresses_its_child_down_to_floor() {
let mut tree = WidgetTree::new();
let big = tree.add(FixedLeaf(200.0, 20.0));
let wrapped = tree.add(Shrinkable::new().min_width(50.0).child_id(big));
let rigid = tree.add(FixedLeaf(60.0, 20.0));
let _stack = tree.add(HStack::new().add_child(wrapped).add_child(rigid));
tree.layout(SizeProposal::exact(100.0, 40.0));
assert!(
(tree.bounds(wrapped).width - 50.0).abs() < 0.01,
"wrapped width = {}",
tree.bounds(wrapped).width
);
assert!(
(tree.bounds(rigid).width - 60.0).abs() < 0.01,
"rigid width = {}",
tree.bounds(rigid).width
);
assert!((tree.bounds(big).width - 50.0).abs() < 0.01);
}
#[test]
fn shrinkable_does_not_shrink_when_there_is_room() {
let mut tree = WidgetTree::new();
let child = tree.add(FixedLeaf(80.0, 20.0));
let wrapped = tree.add(Shrinkable::new().min_width(20.0).child_id(child));
let _stack = tree.add(HStack::new().add_child(wrapped));
tree.layout(SizeProposal::exact(300.0, 40.0));
assert!((tree.bounds(wrapped).width - 80.0).abs() < 0.01);
}
}