1 | // Copyright © SixtyFPS GmbH <info@slint.dev> |
2 | // SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-1.1 OR LicenseRef-Slint-commercial |
3 | |
4 | use slint::platform::software_renderer::{ |
5 | MinimalSoftwareWindow, PremultipliedRgbaColor, SoftwareRenderer, TargetPixel, |
6 | }; |
7 | use slint::platform::{PlatformError, WindowAdapter}; |
8 | use slint::{Model, PhysicalPosition, PhysicalSize}; |
9 | use std::rc::Rc; |
10 | |
11 | thread_local! { |
12 | static WINDOW: Rc<MinimalSoftwareWindow> = |
13 | MinimalSoftwareWindow::new(slint::platform::software_renderer::RepaintBufferType::ReusedBuffer); |
14 | |
15 | } |
16 | |
17 | struct TestPlatform; |
18 | impl slint::platform::Platform for TestPlatform { |
19 | fn create_window_adapter(&self) -> Result<Rc<dyn WindowAdapter>, PlatformError> { |
20 | Ok(WINDOW.with(|x: &Rc| x.clone())) |
21 | } |
22 | } |
23 | |
24 | #[derive (Clone, Copy, Default)] |
25 | struct TestPixel(bool); |
26 | |
27 | impl TargetPixel for TestPixel { |
28 | fn blend(&mut self, _color: PremultipliedRgbaColor) { |
29 | *self = Self(true); |
30 | } |
31 | |
32 | fn from_rgb(_red: u8, _green: u8, _blue: u8) -> Self { |
33 | Self(true) |
34 | } |
35 | } |
36 | |
37 | #[track_caller ] |
38 | fn do_test_render_region(renderer: &SoftwareRenderer, x: i32, y: i32, x2: i32, y2: i32) { |
39 | let mut buffer: Vec = vec![TestPixel(false); 500 * 500]; |
40 | let r: PhysicalRegion = renderer.render(buffer:buffer.as_mut_slice(), pixel_stride:500); |
41 | assert_eq!(r.bounding_box_size(), PhysicalSize { width: (x2 - x) as _, height: (y2 - y) as _ }); |
42 | assert_eq!(r.bounding_box_origin(), PhysicalPosition { x, y }); |
43 | |
44 | for py: usize in 0..500 { |
45 | for px: usize in 0..500 { |
46 | assert_eq!( |
47 | buffer[py * 500 + px].0, |
48 | (x..x2).contains(&(px as i32)) && (y..y2).contains(&(py as i32)), |
49 | "unexpected value at {px}, {py}" |
50 | ) |
51 | } |
52 | } |
53 | } |
54 | |
55 | #[test ] |
56 | fn simple() { |
57 | slint::slint! { |
58 | export component Ui inherits Window { |
59 | in property <color> c: yellow; |
60 | background: black; |
61 | Rectangle { |
62 | x: 1phx; |
63 | y: 80phx; |
64 | width: 15phx; |
65 | height: 17phx; |
66 | background: red; |
67 | } |
68 | Rectangle { |
69 | x: 10phx; |
70 | y: 19phx; |
71 | Rectangle { |
72 | x: 5phx; |
73 | y: 80phx; |
74 | width: 12phx; |
75 | height: 13phx; |
76 | background: c; |
77 | } |
78 | Rectangle { |
79 | x: 50phx; |
80 | y: 8phx; |
81 | width: 15phx; |
82 | height: 17phx; |
83 | background: c; |
84 | } |
85 | } |
86 | } |
87 | } |
88 | |
89 | slint::platform::set_platform(Box::new(TestPlatform)).ok(); |
90 | let ui = Ui::new().unwrap(); |
91 | let window = WINDOW.with(|x| x.clone()); |
92 | window.set_size(slint::PhysicalSize::new(180, 260)); |
93 | ui.show().unwrap(); |
94 | assert!(window.draw_if_needed(|renderer| { |
95 | do_test_render_region(renderer, 0, 0, 180, 260); |
96 | })); |
97 | assert!(!window.draw_if_needed(|_| { unreachable!() })); |
98 | ui.set_c(slint::Color::from_rgb_u8(45, 12, 13)); |
99 | assert!(window.draw_if_needed(|renderer| { |
100 | do_test_render_region(renderer, 10 + 5, 19 + 8, 10 + 50 + 15, 19 + 80 + 13); |
101 | })); |
102 | ui.set_c(slint::Color::from_rgb_u8(45, 12, 13)); |
103 | assert!(!window.draw_if_needed(|_| { unreachable!() })); |
104 | } |
105 | |
106 | #[test ] |
107 | fn visibility() { |
108 | slint::slint! { |
109 | export component Ui inherits Window { |
110 | in property <bool> c : true; |
111 | background: black; |
112 | Rectangle { |
113 | x: 10phx; |
114 | y: 19phx; |
115 | Rectangle { |
116 | x: 5phx; |
117 | y: 80phx; |
118 | width: 12phx; |
119 | height: 13phx; |
120 | background: red; |
121 | visible: c; |
122 | } |
123 | Rectangle { |
124 | x: 50phx; |
125 | y: 8phx; |
126 | width: 15phx; |
127 | height: 17phx; |
128 | background: gray; |
129 | visible: !c; |
130 | } |
131 | } |
132 | } |
133 | } |
134 | |
135 | slint::platform::set_platform(Box::new(TestPlatform)).ok(); |
136 | let ui = Ui::new().unwrap(); |
137 | let window = WINDOW.with(|x| x.clone()); |
138 | window.set_size(slint::PhysicalSize::new(180, 260)); |
139 | ui.show().unwrap(); |
140 | assert!(window.draw_if_needed(|renderer| { |
141 | do_test_render_region(renderer, 0, 0, 180, 260); |
142 | })); |
143 | assert!(!window.draw_if_needed(|_| { unreachable!() })); |
144 | ui.set_c(false); |
145 | assert!(window.draw_if_needed(|renderer| { |
146 | do_test_render_region(renderer, 10 + 5, 19 + 8, 10 + 50 + 15, 19 + 80 + 13); |
147 | })); |
148 | assert!(!window.draw_if_needed(|_| { unreachable!() })); |
149 | ui.set_c(true); |
150 | assert!(window.draw_if_needed(|renderer| { |
151 | do_test_render_region(renderer, 10 + 5, 19 + 8, 10 + 50 + 15, 19 + 80 + 13); |
152 | })); |
153 | assert!(!window.draw_if_needed(|_| { unreachable!() })); |
154 | } |
155 | |
156 | #[test ] |
157 | fn if_condition() { |
158 | slint::slint! { |
159 | export component Ui inherits Window { |
160 | in property <bool> c : true; |
161 | background: black; |
162 | if c: Rectangle { |
163 | x: 45px; |
164 | y: 45px; |
165 | background: pink; |
166 | width: 32px; |
167 | height: 3px; |
168 | } |
169 | } |
170 | } |
171 | |
172 | slint::platform::set_platform(Box::new(TestPlatform)).ok(); |
173 | let ui = Ui::new().unwrap(); |
174 | let window = WINDOW.with(|x| x.clone()); |
175 | window.set_size(slint::PhysicalSize::new(180, 260)); |
176 | ui.show().unwrap(); |
177 | assert!(window.draw_if_needed(|renderer| { |
178 | do_test_render_region(renderer, 0, 0, 180, 260); |
179 | })); |
180 | assert!(!window.draw_if_needed(|_| { unreachable!() })); |
181 | ui.set_c(false); |
182 | assert!(window.draw_if_needed(|renderer| { |
183 | // Currently we redraw when a condition becomes false because we don't track the position otherwise |
184 | do_test_render_region(renderer, 0, 0, 180, 260); |
185 | })); |
186 | assert!(!window.draw_if_needed(|_| { unreachable!() })); |
187 | ui.set_c(true); |
188 | assert!(window.draw_if_needed(|renderer| { |
189 | do_test_render_region(renderer, 45, 45, 45 + 32, 45 + 3); |
190 | })); |
191 | assert!(!window.draw_if_needed(|_| { unreachable!() })); |
192 | } |
193 | |
194 | #[test ] |
195 | fn list_view() { |
196 | slint::slint! { |
197 | // We can't rely on the style as they are all different so implement our own in very basic terms |
198 | component ListView inherits Flickable { |
199 | out property <length> visible-width <=> self.width; |
200 | out property <length> visible-height <=> self.height; |
201 | @children |
202 | } |
203 | export component Ui inherits Window { |
204 | width: 300px; |
205 | height: 300px; |
206 | in property <[int]> model; |
207 | ListView { |
208 | x: 20px; y: 10px; width: 100px; height: 90px; |
209 | for x in model: Rectangle { |
210 | background: x == 1 ? red : blue; |
211 | height: 10px; |
212 | width: 25px; |
213 | } |
214 | |
215 | } |
216 | } |
217 | } |
218 | |
219 | slint::platform::set_platform(Box::new(TestPlatform)).ok(); |
220 | let ui = Ui::new().unwrap(); |
221 | let window = WINDOW.with(|x| x.clone()); |
222 | window.set_size(slint::PhysicalSize::new(300, 300)); |
223 | ui.show().unwrap(); |
224 | assert!(window.draw_if_needed(|renderer| { |
225 | do_test_render_region(renderer, 0, 0, 300, 300); |
226 | })); |
227 | assert!(!window.draw_if_needed(|_| { unreachable!() })); |
228 | let model = std::rc::Rc::new(slint::VecModel::from(vec![0])); |
229 | ui.set_model(model.clone().into()); |
230 | |
231 | const LV_X: i32 = 20; |
232 | const LV_Y: i32 = 10; |
233 | |
234 | assert!(window.draw_if_needed(|renderer| { |
235 | do_test_render_region(renderer, LV_X, LV_Y, LV_X + 25, LV_Y + 10); |
236 | })); |
237 | assert!(!window.draw_if_needed(|_| { unreachable!() })); |
238 | |
239 | model.insert(0, 1); |
240 | assert!(window.draw_if_needed(|renderer| { |
241 | do_test_render_region(renderer, LV_X, LV_Y, LV_X + 25, LV_Y + 20); |
242 | })); |
243 | assert!(!window.draw_if_needed(|_| { unreachable!() })); |
244 | model.set_row_data(1, 1); |
245 | assert!(window.draw_if_needed(|renderer| { |
246 | do_test_render_region(renderer, LV_X, LV_Y + 10, LV_X + 25, LV_Y + 20); |
247 | })); |
248 | assert!(!window.draw_if_needed(|_| { unreachable!() })); |
249 | model.set_vec(vec![0, 0]); |
250 | assert!(window.draw_if_needed(|renderer| { |
251 | // Currently, when ItemTree are removed, we redraw the whole window. |
252 | do_test_render_region(renderer, 0, 0, 300, 300); |
253 | })); |
254 | assert!(!window.draw_if_needed(|_| { unreachable!() })); |
255 | model.remove(1); |
256 | assert!(window.draw_if_needed(|renderer| { |
257 | // Currently, when ItemTree are removed, we redraw the whole window. |
258 | do_test_render_region(renderer, 0, 0, 300, 300); |
259 | })); |
260 | assert!(!window.draw_if_needed(|_| { unreachable!() })); |
261 | } |
262 | |