mirror of
				https://github.com/slint-ui/slint.git
				synced 2025-11-04 13:39:03 +00:00 
			
		
		
		
	Base the commercial license on the Royalty-free license adding clauses pertaining to the fees.
		
			
				
	
	
		
			364 lines
		
	
	
	
		
			10 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			364 lines
		
	
	
	
		
			10 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
// Copyright © SixtyFPS GmbH <info@slint.dev>
 | 
						|
// SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0
 | 
						|
 | 
						|
// cSpell: ignore singleshot
 | 
						|
 | 
						|
#define CATCH_CONFIG_MAIN
 | 
						|
#include "catch2/catch.hpp"
 | 
						|
 | 
						|
#include <slint-platform.h>
 | 
						|
#include <thread>
 | 
						|
#include <deque>
 | 
						|
#include <memory>
 | 
						|
#include <mutex>
 | 
						|
#include <chrono>
 | 
						|
#include <optional>
 | 
						|
 | 
						|
struct TestPlatform : slint::platform::Platform
 | 
						|
{
 | 
						|
    std::mutex the_mutex;
 | 
						|
    std::deque<slint::platform::Platform::Task> queue;
 | 
						|
    bool quit = false;
 | 
						|
    std::condition_variable cv;
 | 
						|
    std::chrono::time_point<std::chrono::steady_clock> start = std::chrono::steady_clock::now();
 | 
						|
 | 
						|
    /// Returns a new WindowAdapter
 | 
						|
    virtual std::unique_ptr<slint::platform::WindowAdapter> create_window_adapter() override
 | 
						|
    {
 | 
						|
#ifdef SLINT_FEATURE_RENDERER_SOFTWARE
 | 
						|
        struct TestWindowAdapter : slint::platform::WindowAdapter
 | 
						|
        {
 | 
						|
            slint::platform::SoftwareRenderer r { {} };
 | 
						|
            slint::PhysicalSize size() override { return slint::PhysicalSize({}); }
 | 
						|
            slint::platform::AbstractRenderer &renderer() override { return r; }
 | 
						|
        };
 | 
						|
        return std::make_unique<TestWindowAdapter>();
 | 
						|
#else
 | 
						|
        assert(!"creating window in this test");
 | 
						|
        return nullptr;
 | 
						|
#endif
 | 
						|
    };
 | 
						|
 | 
						|
    /// Spins an event loop and renders the visible windows.
 | 
						|
    virtual void run_event_loop() override
 | 
						|
    {
 | 
						|
        quit = false;
 | 
						|
        while (true) {
 | 
						|
            slint::platform::update_timers_and_animations();
 | 
						|
            std::optional<slint::platform::Platform::Task> event;
 | 
						|
            {
 | 
						|
                std::unique_lock lock(the_mutex);
 | 
						|
                if (queue.empty()) {
 | 
						|
                    if (quit) {
 | 
						|
                        quit = false;
 | 
						|
                        break;
 | 
						|
                    }
 | 
						|
                    if (auto duration = slint::platform::duration_until_next_timer_update()) {
 | 
						|
                        cv.wait_for(lock, *duration);
 | 
						|
                    } else {
 | 
						|
                        cv.wait(lock);
 | 
						|
                    }
 | 
						|
                    continue;
 | 
						|
                } else {
 | 
						|
                    event = std::move(queue.front());
 | 
						|
                    queue.pop_front();
 | 
						|
                }
 | 
						|
            }
 | 
						|
            if (event) {
 | 
						|
                std::move(*event).run();
 | 
						|
                event.reset();
 | 
						|
            }
 | 
						|
        }
 | 
						|
    }
 | 
						|
 | 
						|
    virtual void quit_event_loop() override
 | 
						|
    {
 | 
						|
        const std::unique_lock lock(the_mutex);
 | 
						|
        quit = true;
 | 
						|
        cv.notify_all();
 | 
						|
    }
 | 
						|
 | 
						|
    virtual void run_in_event_loop(slint::platform::Platform::Task event) override
 | 
						|
    {
 | 
						|
        const std::unique_lock lock(the_mutex);
 | 
						|
        queue.push_back(std::move(event));
 | 
						|
        cv.notify_all();
 | 
						|
    }
 | 
						|
 | 
						|
#ifdef SLINT_FEATURE_FREESTANDING
 | 
						|
    virtual std::chrono::milliseconds duration_since_start() override
 | 
						|
    {
 | 
						|
        return std::chrono::duration_cast<std::chrono::milliseconds>(
 | 
						|
                std::chrono::steady_clock::now() - start);
 | 
						|
    }
 | 
						|
#endif
 | 
						|
};
 | 
						|
 | 
						|
bool init_platform = (slint::platform::set_platform(std::make_unique<TestPlatform>()), true);
 | 
						|
 | 
						|
TEST_CASE("C++ Singleshot Timers")
 | 
						|
{
 | 
						|
    using namespace slint;
 | 
						|
    int called = 0;
 | 
						|
    Timer testTimer(std::chrono::milliseconds(16), [&]() {
 | 
						|
        slint::quit_event_loop();
 | 
						|
        called += 10;
 | 
						|
    });
 | 
						|
    REQUIRE(called == 0);
 | 
						|
    slint::run_event_loop();
 | 
						|
    REQUIRE(called == 10);
 | 
						|
}
 | 
						|
 | 
						|
TEST_CASE("C++ Repeated Timer")
 | 
						|
{
 | 
						|
    int timer_triggered = 0;
 | 
						|
    slint::Timer timer;
 | 
						|
 | 
						|
    timer.start(slint::TimerMode::Repeated, std::chrono::milliseconds(3),
 | 
						|
                [&]() { timer_triggered++; });
 | 
						|
 | 
						|
    REQUIRE(timer_triggered == 0);
 | 
						|
 | 
						|
    bool timer_was_running = false;
 | 
						|
 | 
						|
    slint::Timer::single_shot(std::chrono::milliseconds(100), [&]() {
 | 
						|
        timer_was_running = timer.running();
 | 
						|
        slint::quit_event_loop();
 | 
						|
    });
 | 
						|
 | 
						|
    slint::run_event_loop();
 | 
						|
 | 
						|
    REQUIRE(timer_triggered > 1);
 | 
						|
    REQUIRE(timer_was_running);
 | 
						|
}
 | 
						|
 | 
						|
TEST_CASE("C++ Restart Singleshot Timer")
 | 
						|
{
 | 
						|
    int timer_triggered = 0;
 | 
						|
    slint::Timer timer;
 | 
						|
 | 
						|
    timer.start(slint::TimerMode::SingleShot, std::chrono::milliseconds(3),
 | 
						|
                [&]() { timer_triggered++; });
 | 
						|
    REQUIRE(timer.running());
 | 
						|
 | 
						|
    REQUIRE(timer_triggered == 0);
 | 
						|
 | 
						|
    bool timer_was_running = true;
 | 
						|
 | 
						|
    slint::Timer::single_shot(std::chrono::milliseconds(50), [&]() {
 | 
						|
        timer_was_running = timer.running();
 | 
						|
        slint::quit_event_loop();
 | 
						|
    });
 | 
						|
 | 
						|
    slint::run_event_loop();
 | 
						|
 | 
						|
    REQUIRE(timer_triggered == 1);
 | 
						|
    REQUIRE(!timer_was_running); // Timer is already stopped at this point
 | 
						|
 | 
						|
    timer_was_running = true;
 | 
						|
    timer_triggered = 0;
 | 
						|
    timer.restart();
 | 
						|
    REQUIRE(timer.running());
 | 
						|
    slint::Timer::single_shot(std::chrono::milliseconds(50), [&]() {
 | 
						|
        timer_was_running = timer.running();
 | 
						|
        slint::quit_event_loop();
 | 
						|
    });
 | 
						|
 | 
						|
    slint::run_event_loop();
 | 
						|
 | 
						|
    REQUIRE(timer_triggered == 1);
 | 
						|
    REQUIRE(!timer_was_running);
 | 
						|
}
 | 
						|
 | 
						|
TEST_CASE("C++ Restart Repeated Timer")
 | 
						|
{
 | 
						|
    int timer_triggered = 0;
 | 
						|
    slint::Timer timer;
 | 
						|
 | 
						|
    timer.start(slint::TimerMode::Repeated, std::chrono::milliseconds(3),
 | 
						|
                [&]() { timer_triggered++; });
 | 
						|
 | 
						|
    REQUIRE(timer_triggered == 0);
 | 
						|
 | 
						|
    bool timer_was_running = false;
 | 
						|
 | 
						|
    slint::Timer::single_shot(std::chrono::milliseconds(50), [&]() {
 | 
						|
        timer_was_running = timer.running();
 | 
						|
        slint::quit_event_loop();
 | 
						|
    });
 | 
						|
 | 
						|
    slint::run_event_loop();
 | 
						|
 | 
						|
    REQUIRE(timer_triggered > 1);
 | 
						|
    REQUIRE(timer_was_running);
 | 
						|
 | 
						|
    timer_was_running = false;
 | 
						|
    timer_triggered = 0;
 | 
						|
    timer.stop();
 | 
						|
    slint::Timer::single_shot(std::chrono::milliseconds(50), [&]() {
 | 
						|
        timer_was_running = timer.running();
 | 
						|
        slint::quit_event_loop();
 | 
						|
    });
 | 
						|
 | 
						|
    slint::run_event_loop();
 | 
						|
 | 
						|
    REQUIRE(timer_triggered == 0);
 | 
						|
    REQUIRE(!timer_was_running);
 | 
						|
 | 
						|
    timer_was_running = false;
 | 
						|
    timer_triggered = 0;
 | 
						|
 | 
						|
    timer.restart();
 | 
						|
 | 
						|
    slint::Timer::single_shot(std::chrono::milliseconds(50), [&]() {
 | 
						|
        timer_was_running = timer.running();
 | 
						|
        slint::quit_event_loop();
 | 
						|
    });
 | 
						|
 | 
						|
    slint::run_event_loop();
 | 
						|
 | 
						|
    REQUIRE(timer_triggered > 1);
 | 
						|
    REQUIRE(timer_was_running);
 | 
						|
}
 | 
						|
 | 
						|
TEST_CASE("Quit from event")
 | 
						|
{
 | 
						|
    int called = 0;
 | 
						|
    slint::invoke_from_event_loop([&] {
 | 
						|
        slint::quit_event_loop();
 | 
						|
        called += 10;
 | 
						|
    });
 | 
						|
    REQUIRE(called == 0);
 | 
						|
    slint::run_event_loop();
 | 
						|
    REQUIRE(called == 10);
 | 
						|
}
 | 
						|
 | 
						|
TEST_CASE("Event from thread")
 | 
						|
{
 | 
						|
    std::atomic<int> called = 0;
 | 
						|
    auto t = std::thread([&] {
 | 
						|
        called += 10;
 | 
						|
        slint::invoke_from_event_loop([&] {
 | 
						|
            called += 100;
 | 
						|
            slint::quit_event_loop();
 | 
						|
        });
 | 
						|
    });
 | 
						|
 | 
						|
    slint::run_event_loop();
 | 
						|
    REQUIRE(called == 110);
 | 
						|
    t.join();
 | 
						|
}
 | 
						|
 | 
						|
TEST_CASE("Blocking Event from thread")
 | 
						|
{
 | 
						|
    std::atomic<int> called = 0;
 | 
						|
    auto t = std::thread([&] {
 | 
						|
        // test returning a, unique_ptr because it is movable-only
 | 
						|
        std::unique_ptr foo =
 | 
						|
                slint::blocking_invoke_from_event_loop([&] { return std::make_unique<int>(42); });
 | 
						|
        called = *foo;
 | 
						|
        int xxx = 123;
 | 
						|
        slint::blocking_invoke_from_event_loop([&] {
 | 
						|
            slint::quit_event_loop();
 | 
						|
            xxx = 888999;
 | 
						|
        });
 | 
						|
        REQUIRE(xxx == 888999);
 | 
						|
    });
 | 
						|
 | 
						|
    slint::run_event_loop();
 | 
						|
    REQUIRE(called == 42);
 | 
						|
    t.join();
 | 
						|
}
 | 
						|
 | 
						|
#if defined(SLINT_FEATURE_INTERPRETER) && defined(SLINT_FEATURE_RENDERER_SOFTWARE)
 | 
						|
 | 
						|
#    include <slint-interpreter.h>
 | 
						|
 | 
						|
TEST_CASE("Quit on last window closed")
 | 
						|
{
 | 
						|
    using namespace slint::interpreter;
 | 
						|
    using namespace slint;
 | 
						|
 | 
						|
    int ok = 0;
 | 
						|
 | 
						|
    ComponentCompiler compiler;
 | 
						|
    auto comp_def = compiler.build_from_source("export component App inherits Window { }", "");
 | 
						|
    REQUIRE(comp_def.has_value());
 | 
						|
    auto instance = comp_def->create();
 | 
						|
    instance->hide(); // hide before show should mess the counter
 | 
						|
    REQUIRE(instance->window().is_visible() == false);
 | 
						|
    instance->show();
 | 
						|
    REQUIRE(instance->window().is_visible() == true);
 | 
						|
 | 
						|
    slint::Timer::single_shot(std::chrono::milliseconds(10), [&]() {
 | 
						|
        REQUIRE(instance->window().is_visible() == true);
 | 
						|
        instance->hide();
 | 
						|
        REQUIRE(instance->window().is_visible() == false);
 | 
						|
        ok = 1;
 | 
						|
        slint::Timer::single_shot(std::chrono::milliseconds(0), [&]() {
 | 
						|
            // event loop should be stopped
 | 
						|
            ok = -1;
 | 
						|
        });
 | 
						|
    });
 | 
						|
    slint::run_event_loop();
 | 
						|
    REQUIRE(ok == 1);
 | 
						|
    REQUIRE(instance->window().is_visible() == false);
 | 
						|
 | 
						|
    ok = 0;
 | 
						|
    slint::Timer::single_shot(std::chrono::milliseconds(5), [&]() {
 | 
						|
        REQUIRE(ok == -1); // the event we started previously should have been ran first
 | 
						|
        ok = 1;
 | 
						|
        REQUIRE(instance->window().is_visible() == false);
 | 
						|
        instance->show();
 | 
						|
        instance->show(); // two show shouldn't make the loop alive
 | 
						|
        slint::Timer::single_shot(std::chrono::milliseconds(0), [&]() {
 | 
						|
            REQUIRE(instance->window().is_visible() == true);
 | 
						|
            instance->hide();
 | 
						|
            ok = 2;
 | 
						|
            slint::Timer::single_shot(std::chrono::milliseconds(0), [&]() {
 | 
						|
                // event loop should be stopped
 | 
						|
                ok = -2;
 | 
						|
            });
 | 
						|
        });
 | 
						|
    });
 | 
						|
    slint::run_event_loop();
 | 
						|
    REQUIRE(ok == 2);
 | 
						|
 | 
						|
    ok = 0;
 | 
						|
    auto instance2 = comp_def->create();
 | 
						|
    instance2->show();
 | 
						|
    slint::Timer::single_shot(std::chrono::milliseconds(5), [&]() {
 | 
						|
        REQUIRE(ok == -2); // the event we started previously should have been ran first
 | 
						|
        instance->show();
 | 
						|
        instance2->hide();
 | 
						|
        slint::Timer::single_shot(std::chrono::milliseconds(0), [&]() {
 | 
						|
            instance2->show();
 | 
						|
            instance->hide();
 | 
						|
            slint::Timer::single_shot(std::chrono::milliseconds(0), [&]() {
 | 
						|
                instance2->hide();
 | 
						|
                ok = 3;
 | 
						|
            });
 | 
						|
        });
 | 
						|
    });
 | 
						|
    slint::run_event_loop();
 | 
						|
    REQUIRE(ok == 3);
 | 
						|
    ok = 0;
 | 
						|
    slint::Timer::single_shot(std::chrono::milliseconds(0), [&]() {
 | 
						|
        REQUIRE(ok == 0);
 | 
						|
        instance->show();
 | 
						|
        slint::Timer::single_shot(std::chrono::milliseconds(0), [&]() {
 | 
						|
            instance2->hide();
 | 
						|
            instance->hide();
 | 
						|
            slint::Timer::single_shot(std::chrono::milliseconds(0), [&]() {
 | 
						|
                instance2->show();
 | 
						|
                slint::quit_event_loop();
 | 
						|
                ok = 4;
 | 
						|
            });
 | 
						|
        });
 | 
						|
    });
 | 
						|
    slint::run_event_loop(slint::EventLoopMode::RunUntilQuit);
 | 
						|
 | 
						|
    REQUIRE(ok == 4);
 | 
						|
}
 | 
						|
 | 
						|
#endif
 |