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// cSpell: ignore singleshot
5
6#define CATCH_CONFIG_MAIN
7#include "catch2/catch.hpp"
8
9#include <slint-platform.h>
10#include <thread>
11#include <deque>
12#include <memory>
13#include <mutex>
14#include <chrono>
15#include <optional>
16
17struct TestPlatform : slint::platform::Platform
18{
19 std::mutex the_mutex;
20 std::deque<slint::platform::Platform::Task> queue;
21 bool quit = false;
22 std::condition_variable cv;
23 std::chrono::time_point<std::chrono::steady_clock> start = std::chrono::steady_clock::now();
24
25 /// Returns a new WindowAdapter
26 virtual std::unique_ptr<slint::platform::WindowAdapter> create_window_adapter() override
27 {
28#ifdef SLINT_FEATURE_RENDERER_SOFTWARE
29 struct TestWindowAdapter : slint::platform::WindowAdapter
30 {
31 slint::platform::SoftwareRenderer r { {} };
32 slint::PhysicalSize size() override { return slint::PhysicalSize({}); }
33 slint::platform::AbstractRenderer &renderer() override { return r; }
34 };
35 return std::make_unique<TestWindowAdapter>();
36#else
37 assert(!"creating window in this test");
38 return nullptr;
39#endif
40 };
41
42 /// Spins an event loop and renders the visible windows.
43 virtual void run_event_loop() override
44 {
45 quit = false;
46 while (true) {
47 slint::platform::update_timers_and_animations();
48 std::optional<slint::platform::Platform::Task> event;
49 {
50 std::unique_lock lock(the_mutex);
51 if (queue.empty()) {
52 if (quit) {
53 quit = false;
54 break;
55 }
56 if (auto duration = slint::platform::duration_until_next_timer_update()) {
57 cv.wait_for(lock&: lock, rtime: *duration);
58 } else {
59 cv.wait(lock&: lock);
60 }
61 continue;
62 } else {
63 event = std::move(queue.front());
64 queue.pop_front();
65 }
66 }
67 if (event) {
68 std::move(*event).run();
69 event.reset();
70 }
71 }
72 }
73
74 virtual void quit_event_loop() override
75 {
76 const std::unique_lock lock(the_mutex);
77 quit = true;
78 cv.notify_all();
79 }
80
81 virtual void run_in_event_loop(slint::platform::Platform::Task event) override
82 {
83 const std::unique_lock lock(the_mutex);
84 queue.push_back(x: std::move(event));
85 cv.notify_all();
86 }
87
88#ifdef SLINT_FEATURE_FREESTANDING
89 virtual std::chrono::milliseconds duration_since_start() override
90 {
91 return std::chrono::duration_cast<std::chrono::milliseconds>(
92 std::chrono::steady_clock::now() - start);
93 }
94#endif
95};
96
97bool init_platform = (slint::platform::set_platform(std::make_unique<TestPlatform>()), true);
98
99TEST_CASE("C++ Singleshot Timers")
100{
101 using namespace slint;
102 int called = 0;
103 Timer testTimer(std::chrono::milliseconds(16), [&]() {
104 slint::quit_event_loop();
105 called += 10;
106 });
107 REQUIRE(called == 0);
108 slint::run_event_loop();
109 REQUIRE(called == 10);
110}
111
112TEST_CASE("C++ Repeated Timer")
113{
114 int timer_triggered = 0;
115 slint::Timer timer;
116
117 timer.start(slint::TimerMode::Repeated, std::chrono::milliseconds(3),
118 [&]() { timer_triggered++; });
119
120 REQUIRE(timer_triggered == 0);
121
122 bool timer_was_running = false;
123
124 slint::Timer::single_shot(std::chrono::milliseconds(100), [&]() {
125 timer_was_running = timer.running();
126 slint::quit_event_loop();
127 });
128
129 slint::run_event_loop();
130
131 REQUIRE(timer_triggered > 1);
132 REQUIRE(timer_was_running);
133}
134
135TEST_CASE("C++ Restart Singleshot Timer")
136{
137 int timer_triggered = 0;
138 slint::Timer timer;
139
140 timer.start(slint::TimerMode::SingleShot, std::chrono::milliseconds(3),
141 [&]() { timer_triggered++; });
142 REQUIRE(timer.running());
143
144 REQUIRE(timer_triggered == 0);
145
146 bool timer_was_running = true;
147
148 slint::Timer::single_shot(std::chrono::milliseconds(50), [&]() {
149 timer_was_running = timer.running();
150 slint::quit_event_loop();
151 });
152
153 slint::run_event_loop();
154
155 REQUIRE(timer_triggered == 1);
156 REQUIRE(!timer_was_running); // Timer is already stopped at this point
157
158 timer_was_running = true;
159 timer_triggered = 0;
160 timer.restart();
161 REQUIRE(timer.running());
162 slint::Timer::single_shot(std::chrono::milliseconds(50), [&]() {
163 timer_was_running = timer.running();
164 slint::quit_event_loop();
165 });
166
167 slint::run_event_loop();
168
169 REQUIRE(timer_triggered == 1);
170 REQUIRE(!timer_was_running);
171}
172
173TEST_CASE("C++ Restart Repeated Timer")
174{
175 int timer_triggered = 0;
176 slint::Timer timer;
177
178 timer.start(slint::TimerMode::Repeated, std::chrono::milliseconds(3),
179 [&]() { timer_triggered++; });
180
181 REQUIRE(timer_triggered == 0);
182
183 bool timer_was_running = false;
184
185 slint::Timer::single_shot(std::chrono::milliseconds(50), [&]() {
186 timer_was_running = timer.running();
187 slint::quit_event_loop();
188 });
189
190 slint::run_event_loop();
191
192 REQUIRE(timer_triggered > 1);
193 REQUIRE(timer_was_running);
194
195 timer_was_running = false;
196 timer_triggered = 0;
197 timer.stop();
198 slint::Timer::single_shot(std::chrono::milliseconds(50), [&]() {
199 timer_was_running = timer.running();
200 slint::quit_event_loop();
201 });
202
203 slint::run_event_loop();
204
205 REQUIRE(timer_triggered == 0);
206 REQUIRE(!timer_was_running);
207
208 timer_was_running = false;
209 timer_triggered = 0;
210
211 timer.restart();
212
213 slint::Timer::single_shot(std::chrono::milliseconds(50), [&]() {
214 timer_was_running = timer.running();
215 slint::quit_event_loop();
216 });
217
218 slint::run_event_loop();
219
220 REQUIRE(timer_triggered > 1);
221 REQUIRE(timer_was_running);
222}
223
224TEST_CASE("Quit from event")
225{
226 int called = 0;
227 slint::invoke_from_event_loop([&] {
228 slint::quit_event_loop();
229 called += 10;
230 });
231 REQUIRE(called == 0);
232 slint::run_event_loop();
233 REQUIRE(called == 10);
234}
235
236TEST_CASE("Event from thread")
237{
238 std::atomic<int> called = 0;
239 auto t = std::thread([&] {
240 called += 10;
241 slint::invoke_from_event_loop([&] {
242 called += 100;
243 slint::quit_event_loop();
244 });
245 });
246
247 slint::run_event_loop();
248 REQUIRE(called == 110);
249 t.join();
250}
251
252TEST_CASE("Blocking Event from thread")
253{
254 std::atomic<int> called = 0;
255 auto t = std::thread([&] {
256 // test returning a, unique_ptr because it is movable-only
257 std::unique_ptr foo =
258 slint::blocking_invoke_from_event_loop([&] { return std::make_unique<int>(42); });
259 called = *foo;
260 int xxx = 123;
261 slint::blocking_invoke_from_event_loop([&] {
262 slint::quit_event_loop();
263 xxx = 888999;
264 });
265 REQUIRE(xxx == 888999);
266 });
267
268 slint::run_event_loop();
269 REQUIRE(called == 42);
270 t.join();
271}
272
273#if defined(SLINT_FEATURE_INTERPRETER) && defined(SLINT_FEATURE_RENDERER_SOFTWARE)
274
275# include <slint-interpreter.h>
276
277TEST_CASE("Quit on last window closed")
278{
279 using namespace slint::interpreter;
280 using namespace slint;
281
282 int ok = 0;
283
284 ComponentCompiler compiler;
285 auto comp_def = compiler.build_from_source("export component App inherits Window { }", "");
286 REQUIRE(comp_def.has_value());
287 auto instance = comp_def->create();
288 instance->hide(); // hide before show should mess the counter
289 REQUIRE(instance->window().is_visible() == false);
290 instance->show();
291 REQUIRE(instance->window().is_visible() == true);
292
293 slint::Timer::single_shot(std::chrono::milliseconds(10), [&]() {
294 REQUIRE(instance->window().is_visible() == true);
295 instance->hide();
296 REQUIRE(instance->window().is_visible() == false);
297 ok = 1;
298 slint::Timer::single_shot(std::chrono::milliseconds(0), [&]() {
299 // event loop should be stopped
300 ok = -1;
301 });
302 });
303 slint::run_event_loop();
304 REQUIRE(ok == 1);
305 REQUIRE(instance->window().is_visible() == false);
306
307 ok = 0;
308 slint::Timer::single_shot(std::chrono::milliseconds(5), [&]() {
309 REQUIRE(ok == -1); // the event we started previously should have been ran first
310 ok = 1;
311 REQUIRE(instance->window().is_visible() == false);
312 instance->show();
313 instance->show(); // two show shouldn't make the loop alive
314 slint::Timer::single_shot(std::chrono::milliseconds(0), [&]() {
315 REQUIRE(instance->window().is_visible() == true);
316 instance->hide();
317 ok = 2;
318 slint::Timer::single_shot(std::chrono::milliseconds(0), [&]() {
319 // event loop should be stopped
320 ok = -2;
321 });
322 });
323 });
324 slint::run_event_loop();
325 REQUIRE(ok == 2);
326
327 ok = 0;
328 auto instance2 = comp_def->create();
329 instance2->show();
330 slint::Timer::single_shot(std::chrono::milliseconds(5), [&]() {
331 REQUIRE(ok == -2); // the event we started previously should have been ran first
332 instance->show();
333 instance2->hide();
334 slint::Timer::single_shot(std::chrono::milliseconds(0), [&]() {
335 instance2->show();
336 instance->hide();
337 slint::Timer::single_shot(std::chrono::milliseconds(0), [&]() {
338 instance2->hide();
339 ok = 3;
340 });
341 });
342 });
343 slint::run_event_loop();
344 REQUIRE(ok == 3);
345 ok = 0;
346 slint::Timer::single_shot(std::chrono::milliseconds(0), [&]() {
347 REQUIRE(ok == 0);
348 instance->show();
349 slint::Timer::single_shot(std::chrono::milliseconds(0), [&]() {
350 instance2->hide();
351 instance->hide();
352 slint::Timer::single_shot(std::chrono::milliseconds(0), [&]() {
353 instance2->show();
354 slint::quit_event_loop();
355 ok = 4;
356 });
357 });
358 });
359 slint::run_event_loop(slint::EventLoopMode::RunUntilQuit);
360
361 REQUIRE(ok == 4);
362}
363
364#endif
365

source code of slint/api/cpp/tests/platform_eventloop.cpp