frus_widgets/
rotatedbox.rs1use frus_core::{Rect, Scene};
6use frus_layout::Style;
7
8use crate::interaction::Status;
9use crate::theme::Theme;
10use crate::widget::Widget;
11
12pub struct RotatedBox<Msg = crate::callback::Callback> {
21 quarter_turns: i32,
22 children: Vec<Box<dyn Widget<Msg>>>,
23}
24
25impl<Msg> RotatedBox<Msg> {
26 pub fn new(quarter_turns: i32) -> Self {
28 Self {
29 quarter_turns,
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 RotatedBox<Msg> {
43 fn style(&self) -> Style {
44 Style::default()
47 }
48
49 fn children(&self) -> &[Box<dyn Widget<Msg>>] {
50 &self.children
51 }
52
53 fn paint(&self, _bounds: Rect, _status: Status, _theme: &Theme, _scene: &mut Scene) {
54 }
56
57 fn on_click(&self) -> Option<Msg> {
58 None
59 }
60
61 fn rotated_quarter_turns(&self) -> Option<i32> {
62 Some(self.quarter_turns)
63 }
64}
65
66#[cfg(test)]
67mod tests {
68 use super::*;
69 use crate::{Container, Flex};
70 use frus_core::{Color, Primitive, Size};
71
72 #[test]
75 fn odd_quarter_turn_swaps_the_layout_box() {
76 let green = Color::rgb(0.0, 1.0, 0.0);
77 let root = Flex::<()>::column()
78 .child(
79 RotatedBox::new(1).child(
80 Container::new()
81 .width(80.0)
82 .height(20.0)
83 .color(Color::rgb(0.3, 0.3, 0.3)),
84 ),
85 )
86 .child(Container::new().width(40.0).height(30.0).color(green));
87 let rt = crate::runtime::Runtime::default();
88 let theme = crate::Theme::dark();
89 let ui = crate::ui::build_ui(&root, Size::new(200.0, 400.0), &rt, &theme);
90 let sibling_y = ui
91 .scene()
92 .primitives()
93 .iter()
94 .find_map(|p| match p {
95 Primitive::Rect { rect, color, .. } if color.g > 0.5 && color.r < 0.5 => {
96 Some(rect.y)
97 }
98 _ => None,
99 })
100 .expect("the green sibling");
101 assert!(
102 (sibling_y - 80.0).abs() < 0.5,
103 "rotated box = 20×80 → the sibling follows at y=80: {sibling_y}"
104 );
105 }
106
107 #[test]
109 fn even_quarter_turn_keeps_the_box() {
110 let green = Color::rgb(0.0, 1.0, 0.0);
111 let root = Flex::<()>::column()
112 .child(
113 RotatedBox::new(2).child(
114 Container::new()
115 .width(80.0)
116 .height(20.0)
117 .color(Color::rgb(0.3, 0.3, 0.3)),
118 ),
119 )
120 .child(Container::new().width(40.0).height(30.0).color(green));
121 let rt = crate::runtime::Runtime::default();
122 let theme = crate::Theme::dark();
123 let ui = crate::ui::build_ui(&root, Size::new(200.0, 400.0), &rt, &theme);
124 let sibling_y = ui
125 .scene()
126 .primitives()
127 .iter()
128 .find_map(|p| match p {
129 Primitive::Rect { rect, color, .. } if color.g > 0.5 && color.r < 0.5 => {
130 Some(rect.y)
131 }
132 _ => None,
133 })
134 .expect("the green sibling");
135 assert!(
136 (sibling_y - 20.0).abs() < 0.5,
137 "box unchanged 80×20 → sibling at y=20: {sibling_y}"
138 );
139 }
140
141 #[test]
143 fn emits_a_rotated_layer() {
144 let root = RotatedBox::<()>::new(1).child(
145 Container::new()
146 .width(80.0)
147 .height(20.0)
148 .color(Color::rgb(0.3, 0.3, 0.3)),
149 );
150 let rt = crate::runtime::Runtime::default();
151 let theme = crate::Theme::dark();
152 let ui = crate::ui::build_ui(&root, Size::new(200.0, 400.0), &rt, &theme);
153 let m = ui
154 .scene()
155 .primitives()
156 .iter()
157 .find_map(|p| match p {
158 Primitive::Layer {
159 transform: Some(t), ..
160 } => Some(t.affine),
161 _ => None,
162 })
163 .expect("a rotated layer");
164 assert!(
166 m.m[0].abs() < 1e-3 && (m.m[1] - 1.0).abs() < 1e-3,
167 "rotation +90° : {:?}",
168 m.m
169 );
170 }
171}