frus_widgets/
aspectratio.rs1use frus_core::{Rect, Scene};
4use frus_layout::{Dimension, Style};
5
6use crate::interaction::Status;
7use crate::theme::Theme;
8use crate::widget::Widget;
9
10pub struct AspectRatio<Msg = crate::callback::Callback> {
21 ratio: f32,
22 children: Vec<Box<dyn Widget<Msg>>>,
23}
24
25impl<Msg> AspectRatio<Msg> {
26 pub fn new(ratio: f32) -> Self {
28 Self {
29 ratio: ratio.max(f32::MIN_POSITIVE),
30 children: Vec::new(),
31 }
32 }
33
34 pub fn child(mut self, child: impl Widget<Msg> + 'static) -> Self {
36 self.children.clear();
37 self.children.push(Box::new(child));
38 self
39 }
40}
41
42impl<Msg: Clone> Widget<Msg> for AspectRatio<Msg> {
43 fn style(&self) -> Style {
44 Style {
48 width: Dimension::Percent(1.0),
49 aspect_ratio: Some(self.ratio),
50 ..Default::default()
51 }
52 }
53
54 fn children(&self) -> &[Box<dyn Widget<Msg>>] {
55 &self.children
56 }
57
58 fn paint(&self, _bounds: Rect, _status: Status, _theme: &Theme, _scene: &mut Scene) {
59 }
61
62 fn on_click(&self) -> Option<Msg> {
63 None
64 }
65}
66
67#[cfg(test)]
68mod tests {
69 use super::*;
70 use crate::Container;
71 use frus_core::{Color, Primitive, Size};
72
73 #[test]
77 fn derives_free_dimension_from_ratio() {
78 let red = Color::rgb(1.0, 0.0, 0.0);
79 let root = crate::Flex::<()>::column()
80 .width(100.0)
81 .child(AspectRatio::new(2.0).child(Container::new().flex(1.0).color(red)));
82 let rt = crate::runtime::Runtime::default();
83 let theme = crate::Theme::dark();
84 let ui = crate::ui::build_ui(&root, Size::new(100.0, 200.0), &rt, &theme);
85 let rect = ui
86 .scene()
87 .primitives()
88 .iter()
89 .find_map(|p| match p {
90 Primitive::Rect { rect, color, .. } if color.r > 0.5 => Some(*rect),
91 _ => None,
92 })
93 .expect("the child's red background");
94 assert!(
95 (rect.width - 100.0).abs() < 0.5 && (rect.height - 50.0).abs() < 0.5,
96 "ratio 2.0 → 100×50: {rect:?}"
97 );
98 }
99
100 #[test]
103 fn ratio_below_one_is_taller_than_wide() {
104 let red = Color::rgb(1.0, 0.0, 0.0);
105 let root = crate::Flex::<()>::column()
106 .width(100.0)
107 .child(AspectRatio::new(0.5).child(Container::new().flex(1.0).color(red)));
108 let rt = crate::runtime::Runtime::default();
109 let theme = crate::Theme::dark();
110 let ui = crate::ui::build_ui(&root, Size::new(100.0, 400.0), &rt, &theme);
111 let rect = ui
112 .scene()
113 .primitives()
114 .iter()
115 .find_map(|p| match p {
116 Primitive::Rect { rect, color, .. } if color.r > 0.5 => Some(*rect),
117 _ => None,
118 })
119 .expect("the child's red background");
120 assert!(
121 (rect.width - 100.0).abs() < 0.5 && (rect.height - 200.0).abs() < 0.5,
122 "ratio 0.5 → 100×200: {rect:?}"
123 );
124 }
125}