frus_widgets/
fractional.rs1use frus_core::{Curve, Rect, Scene};
5use frus_layout::{Dimension, Style};
6
7use crate::interaction::Status;
8use crate::theme::Theme;
9use crate::widget::Widget;
10
11pub struct FractionallySizedBox<Msg = crate::callback::Callback> {
23 width_factor: Option<f32>,
24 height_factor: Option<f32>,
25 animated: Option<(f32, Curve)>,
26 children: Vec<Box<dyn Widget<Msg>>>,
27}
28
29impl<Msg> FractionallySizedBox<Msg> {
30 pub fn new() -> Self {
33 Self {
34 width_factor: None,
35 height_factor: None,
36 animated: None,
37 children: Vec::new(),
38 }
39 }
40
41 pub fn width_factor(mut self, factor: f32) -> Self {
43 self.width_factor = Some(factor.max(0.0));
44 self
45 }
46
47 pub fn height_factor(mut self, factor: f32) -> Self {
49 self.height_factor = Some(factor.max(0.0));
50 self
51 }
52
53 pub fn animated(mut self, duration: f32, curve: Curve) -> Self {
60 self.animated = Some((duration, curve));
61 self
62 }
63
64 pub fn child(mut self, child: impl Widget<Msg> + 'static) -> Self {
66 self.children.clear();
67 self.children.push(Box::new(child));
68 self
69 }
70}
71
72impl<Msg> Default for FractionallySizedBox<Msg> {
73 fn default() -> Self {
74 Self::new()
75 }
76}
77
78impl<Msg: Clone> Widget<Msg> for FractionallySizedBox<Msg> {
79 fn style(&self) -> Style {
80 let dim = |factor: Option<f32>| match factor {
83 Some(f) => Dimension::Percent(f),
84 None => Dimension::Auto,
85 };
86 Style {
87 width: dim(self.width_factor),
88 height: dim(self.height_factor),
89 ..Default::default()
90 }
91 }
92
93 fn children(&self) -> &[Box<dyn Widget<Msg>>] {
94 &self.children
95 }
96
97 fn paint(&self, _bounds: Rect, _status: Status, _theme: &Theme, _scene: &mut Scene) {
98 }
100
101 fn on_click(&self) -> Option<Msg> {
102 None
103 }
104
105 fn anim_fractions(&self) -> Option<(Option<f32>, Option<f32>)> {
106 self.animated
107 .as_ref()
108 .map(|_| (self.width_factor, self.height_factor))
109 }
110
111 fn anim_duration(&self) -> f32 {
112 self.animated
113 .as_ref()
114 .map_or(crate::runtime::ANIM_DURATION, |(d, _)| *d)
115 }
116
117 fn anim_curve(&self) -> Curve {
118 self.animated
119 .as_ref()
120 .map_or(Curve::Linear, |(_, c)| c.clone())
121 }
122
123 fn debug_name(&self) -> &'static str {
124 "FractionallySizedBox"
125 }
126}
127
128#[cfg(test)]
129mod tests {
130 use super::*;
131 use crate::Container;
132 use frus_core::{Color, Primitive, Size};
133
134 #[test]
137 fn width_factor_takes_a_fraction_of_the_parent() {
138 let red = Color::rgb(1.0, 0.0, 0.0);
139 let root = crate::Flex::<()>::column().width(100.0).child(
140 FractionallySizedBox::new()
141 .width_factor(0.5)
142 .child(Container::new().flex(1.0).height(20.0).color(red)),
143 );
144 let rt = crate::runtime::Runtime::default();
145 let theme = crate::Theme::dark();
146 let ui = crate::ui::build_ui(&root, Size::new(100.0, 200.0), &rt, &theme);
147 let rect = ui
148 .scene()
149 .primitives()
150 .iter()
151 .find_map(|p| match p {
152 Primitive::Rect { rect, color, .. } if color.r > 0.5 => Some(*rect),
153 _ => None,
154 })
155 .expect("the child's red background");
156 assert!((rect.width - 50.0).abs() < 0.5, "half the width: {rect:?}");
157 }
158
159 #[test]
162 fn height_factor_takes_a_fraction_of_the_parent() {
163 let red = Color::rgb(1.0, 0.0, 0.0);
164 let root = crate::Flex::<()>::column()
165 .width(100.0)
166 .height(200.0)
167 .child(
168 FractionallySizedBox::new()
169 .height_factor(0.25)
170 .child(Container::new().flex(1.0).color(red)),
171 );
172 let rt = crate::runtime::Runtime::default();
173 let theme = crate::Theme::dark();
174 let ui = crate::ui::build_ui(&root, Size::new(100.0, 200.0), &rt, &theme);
175 let rect = ui
176 .scene()
177 .primitives()
178 .iter()
179 .find_map(|p| match p {
180 Primitive::Rect { rect, color, .. } if color.r > 0.5 => Some(*rect),
181 _ => None,
182 })
183 .expect("the child's red background");
184 assert!(
185 (rect.height - 50.0).abs() < 0.5,
186 "a quarter of the height: {rect:?}"
187 );
188 }
189}