frus_widgets/
fittedbox.rs1use frus_core::{BoxFit, Rect, Scene};
6use frus_layout::{Dimension, Style};
7
8use crate::interaction::Status;
9use crate::theme::Theme;
10use crate::widget::Widget;
11
12pub struct FittedBox<Msg = crate::callback::Callback> {
25 fit: BoxFit,
26 width: Dimension,
27 height: Dimension,
28 flex_grow: f32,
29 children: Vec<Box<dyn Widget<Msg>>>,
30}
31
32impl<Msg> FittedBox<Msg> {
33 pub fn new(fit: BoxFit) -> Self {
35 Self {
36 fit,
37 width: Dimension::Auto,
38 height: Dimension::Auto,
39 flex_grow: 0.0,
40 children: Vec::new(),
41 }
42 }
43
44 pub fn width(mut self, width: f32) -> Self {
46 self.width = Dimension::Length(width);
47 self
48 }
49
50 pub fn height(mut self, height: f32) -> Self {
52 self.height = Dimension::Length(height);
53 self
54 }
55
56 pub fn flex(mut self, grow: f32) -> Self {
58 self.flex_grow = grow;
59 self
60 }
61
62 pub fn child(mut self, child: impl Widget<Msg> + 'static) -> Self {
64 self.children.clear();
65 self.children.push(Box::new(child));
66 self
67 }
68}
69
70impl<Msg: Clone> Widget<Msg> for FittedBox<Msg> {
71 fn style(&self) -> Style {
72 Style {
73 width: self.width,
74 height: self.height,
75 flex_grow: self.flex_grow,
76 ..Default::default()
77 }
78 }
79
80 fn children(&self) -> &[Box<dyn Widget<Msg>>] {
81 &self.children
82 }
83
84 fn paint(&self, _bounds: Rect, _status: Status, _theme: &Theme, _scene: &mut Scene) {
85 }
87
88 fn on_click(&self) -> Option<Msg> {
89 None
90 }
91
92 fn fitted(&self) -> Option<BoxFit> {
93 Some(self.fit)
94 }
95}
96
97#[cfg(test)]
98mod tests {
99 use super::*;
100 use crate::{Container, Flex};
101 use frus_core::{Color, Primitive, Size};
102
103 #[test]
106 fn fill_scales_child_to_the_box() {
107 let root = Flex::<()>::column().width(200.0).child(
108 FittedBox::new(BoxFit::Fill)
109 .width(200.0)
110 .height(100.0)
111 .child(
112 Container::new()
113 .width(40.0)
114 .height(20.0)
115 .color(Color::rgb(0.3, 0.3, 0.3)),
116 ),
117 );
118 let rt = crate::runtime::Runtime::default();
119 let theme = crate::Theme::dark();
120 let ui = crate::ui::build_ui(&root, Size::new(200.0, 400.0), &rt, &theme);
121 let m = ui
122 .scene()
123 .primitives()
124 .iter()
125 .find_map(|p| match p {
126 Primitive::Layer {
127 transform: Some(t), ..
128 } => Some(t.affine),
129 _ => None,
130 })
131 .expect("a fitted layer");
132 assert!(
133 (m.m[0] - 5.0).abs() < 1e-2 && (m.m[3] - 5.0).abs() < 1e-2,
134 "Fill scale 5×5: {:?}",
135 m.m
136 );
137 }
138
139 #[test]
142 fn contain_preserves_aspect() {
143 let root = Flex::<()>::column().width(200.0).child(
144 FittedBox::new(BoxFit::Contain)
145 .width(200.0)
146 .height(100.0)
147 .child(
148 Container::new()
149 .width(40.0)
150 .height(40.0)
151 .color(Color::rgb(0.3, 0.3, 0.3)),
152 ),
153 );
154 let rt = crate::runtime::Runtime::default();
155 let theme = crate::Theme::dark();
156 let ui = crate::ui::build_ui(&root, Size::new(200.0, 400.0), &rt, &theme);
157 let m = ui
158 .scene()
159 .primitives()
160 .iter()
161 .find_map(|p| match p {
162 Primitive::Layer {
163 transform: Some(t), ..
164 } => Some(t.affine),
165 _ => None,
166 })
167 .expect("a fitted layer");
168 assert!(
170 (m.m[0] - 2.5).abs() < 1e-2 && (m.m[3] - 2.5).abs() < 1e-2,
171 "Contain scale 2.5×2.5: {:?}",
172 m.m
173 );
174 }
175}