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
4use slint::platform::software_renderer::{
5 MinimalSoftwareWindow, PremultipliedRgbaColor, SoftwareRenderer, TargetPixel,
6};
7use slint::platform::{PlatformError, WindowAdapter};
8use slint::{Model, PhysicalPosition, PhysicalSize};
9use std::rc::Rc;
10
11thread_local! {
12 static WINDOW: Rc<MinimalSoftwareWindow> =
13 MinimalSoftwareWindow::new(slint::platform::software_renderer::RepaintBufferType::ReusedBuffer);
14
15}
16
17struct TestPlatform;
18impl 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)]
25struct TestPixel(bool);
26
27impl 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]
38fn 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]
56fn 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]
107fn 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]
157fn 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]
195fn 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