diff --git a/CMakeLists.txt b/CMakeLists.txt index f52796c1..90e5f9ba 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -65,7 +65,6 @@ option(BRISK_LTO ON "Use Link Time Optimization in Release builds") option(BRISK_BROTLI "Enable Brotli compression" ON) cmake_dependent_option(BRISK_WEBGPU "Enable WebGPU" OFF "WIN32" ON) cmake_dependent_option(BRISK_D3D11 "Enable D3D11 backend (Windows only)" ON "WIN32" OFF) -option(BRISK_ICU "Link to ICU by default for full Unicode support" ON) option(BRISK_LOG_TO_STDERR "Write log output to stderr (Windows-specific)" OFF) option(BRISK_INTERACTIVE_TESTS "Enable interactive tests" OFF) option(BRISK_RTTI "Enable RTTI for Brisk builds" ON) @@ -219,13 +218,12 @@ if (BRISK_TESTS) ${TESTS_GRAPHICS} LIBRARIES brisk-graphics - brisk-i18n-icu ${TESTS_GRAPHICS_LIBS} DEFINITIONS ${TESTS_GRAPHICS_DEFS}) - add_autotests(window SOURCES ${TESTS_WINDOW} LIBRARIES brisk-window brisk-i18n-icu) - add_autotests(gui SOURCES ${TESTS_GUI} LIBRARIES brisk-gui brisk-i18n-icu) - add_autotests(widgets SOURCES ${TESTS_WIDGETS} LIBRARIES brisk-widgets brisk-i18n-icu) + add_autotests(window SOURCES ${TESTS_WINDOW} LIBRARIES brisk-window) + add_autotests(gui SOURCES ${TESTS_GUI} LIBRARIES brisk-gui) + add_autotests(widgets SOURCES ${TESTS_WIDGETS} LIBRARIES brisk-widgets) if (BRISK_OVERRIDE_D3D_WARP) place_warp_dll(graphics_tests) @@ -279,8 +277,6 @@ install( brisk-gui brisk-widgets brisk-executable - brisk-i18n - brisk-i18n-icu ${EXTRA_INSTALL_TARGETS} EXPORT BriskTargets ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}$<$:${BRISK_DBG_SUFFIX}> diff --git a/acquire-deps.cmake b/acquire-deps.cmake index 7690becd..4f62ce28 100644 --- a/acquire-deps.cmake +++ b/acquire-deps.cmake @@ -53,8 +53,8 @@ if (NOT EXISTS ${DEST_FILE}) message(FATAL_ERROR "vcpkg not found. Please ensure vcpkg is installed and accessible in your PATH.") endif () - execute_process(COMMAND ${VCPKG_EXECUTABLE} install --x-install-root ${ROOT}/vcpkg_installed --x-feature=icu - ${EXTRA_VCPKG_ARGS} WORKING_DIRECTORY ${ROOT} COMMAND_ERROR_IS_FATAL ANY) + execute_process(COMMAND ${VCPKG_EXECUTABLE} install --x-install-root ${ROOT}/vcpkg_installed + ${EXTRA_VCPKG_ARGS} WORKING_DIRECTORY ${ROOT} COMMAND_ERROR_IS_FATAL ANY) endif () file(REMOVE_RECURSE ${ROOT}/vcpkg_exported) diff --git a/cmake/config.cmake.in b/cmake/config.cmake.in index 1e19b877..7775eb15 100644 --- a/cmake/config.cmake.in +++ b/cmake/config.cmake.in @@ -15,8 +15,6 @@ add_library(brisk-gui ALIAS Brisk::Gui) add_library(brisk-widgets ALIAS Brisk::Widgets) add_library(brisk-network ALIAS Brisk::Network) add_library(brisk-executable ALIAS Brisk::Executable) -add_library(brisk-i18n-icu ALIAS Brisk::I18n-Icu) - if (TARGET Brisk::brisk-renderer-webgpu) add_library(brisk-renderer-webgpu ALIAS Brisk::brisk-renderer-webgpu) endif () @@ -40,12 +38,6 @@ set(BRISK_BROTLI @BRISK_BROTLI@) set(BRISK_WEBGPU @BRISK_WEBGPU@) set(BRISK_D3D11 @BRISK_D3D11@) -if (BRISK_ICU) - target_link_libraries(Brisk::Graphics INTERFACE Brisk::I18n-Icu) -else () - target_link_libraries(Brisk::Graphics INTERFACE Brisk::I18n) -endif () - set(_DEP_PUBLIC INTERFACE) set(_DEP_PRIVATE INTERFACE) set(_FIND_PACKAGE find_dependency) diff --git a/examples/calc/math.hpp b/examples/calc/math.hpp index c4f96906..63af40d1 100644 --- a/examples/calc/math.hpp +++ b/examples/calc/math.hpp @@ -295,7 +295,7 @@ struct Calculator { std::optional> exponentiationOperation; Number currentOperand; - BindingRegistration registration{ this, uiScheduler }; + BindingRegistration registration{ this, mainScheduler }; std::string output() const { if (editable) diff --git a/examples/crud/main.cpp b/examples/crud/main.cpp index 3138100e..24a067c0 100644 --- a/examples/crud/main.cpp +++ b/examples/crud/main.cpp @@ -39,7 +39,7 @@ class Model { std::vector m_list{ { "Hans", "Emil" }, { "Max", "Mustermann" }, { "Roman", "Tisch" } }; }; -class ViewModel : public BindableObject { +class ViewModel : public BindableObject { public: ViewModel() { filter(m_prefix); @@ -215,7 +215,5 @@ using namespace Brisk; int briskMain() { GuiApplication application; - return application.run([]() { - return rcnew View(rcnew ViewModel()); - }); + return application.run(rcnew View(rcnew ViewModel())); } diff --git a/examples/dashboard/src/ViewModel/ViewModel.hpp b/examples/dashboard/src/ViewModel/ViewModel.hpp index d3169a39..e0183970 100644 --- a/examples/dashboard/src/ViewModel/ViewModel.hpp +++ b/examples/dashboard/src/ViewModel/ViewModel.hpp @@ -28,7 +28,7 @@ namespace App { using namespace Brisk; -class DataSourceViewModel : public BindableObject { +class DataSourceViewModel : public BindableObject { public: DataSourceViewModel(Rc model, Value updateTrigger); diff --git a/examples/own-window-gui/own-window-gui.cpp b/examples/own-window-gui/own-window-gui.cpp index 4c8ddf18..90554737 100644 --- a/examples/own-window-gui/own-window-gui.cpp +++ b/examples/own-window-gui/own-window-gui.cpp @@ -95,7 +95,7 @@ int main() { // Convert mouse coordinates to framebuffer pixels event.point = PointF(xpos, ypos) * PointF(SizeF(win->framebufferSize)) / PointF(SizeF(win->windowSize)); - win->input.addEvent(std::move(event)); + std::ignore = win->input.processEvent(std::move(event)); }); // Set callback for mouse button presses and releases @@ -122,9 +122,9 @@ int main() { event.downPoint = std::nullopt; // Not implemented in this example event.mods = KeyModifiers::None; // Not implemented in this example if (action == GLFW_PRESS) { - win->input.addEvent(EventMouseButtonPressed{ event }); + std::ignore = win->input.processEvent(EventMouseButtonPressed{ event }); } else { - win->input.addEvent(EventMouseButtonReleased{ event }); + std::ignore = win->input.processEvent(EventMouseButtonReleased{ event }); } }); @@ -174,7 +174,6 @@ int main() { // Process Brisk events mainScheduler->process(); - uiScheduler->process(); for (int i = 0; i < numWindows; ++i) { // Update pixel ratio for rendering (ratio between framebuffer pixels and GUI pixels) diff --git a/examples/showcase/src/Binding.hpp b/examples/showcase/src/Binding.hpp index 21bb0bd9..0537cb07 100644 --- a/examples/showcase/src/Binding.hpp +++ b/examples/showcase/src/Binding.hpp @@ -24,7 +24,7 @@ namespace Brisk { -class ShowcaseBinding : public BindableObject { +class ShowcaseBinding : public BindableObject { public: ShowcaseBinding(); Rc build(Rc notifications, Value globalEnabled); diff --git a/examples/showcase/src/Buttons.hpp b/examples/showcase/src/Buttons.hpp index 764ed12c..43aa5958 100644 --- a/examples/showcase/src/Buttons.hpp +++ b/examples/showcase/src/Buttons.hpp @@ -24,7 +24,7 @@ namespace Brisk { -class ShowcaseButtons : public BindableObject { +class ShowcaseButtons : public BindableObject { public: ShowcaseButtons() {} diff --git a/examples/showcase/src/Dialogs.hpp b/examples/showcase/src/Dialogs.hpp index d0347c9a..98e91481 100644 --- a/examples/showcase/src/Dialogs.hpp +++ b/examples/showcase/src/Dialogs.hpp @@ -24,7 +24,7 @@ namespace Brisk { -class ShowcaseDialogs : public BindableObject { +class ShowcaseDialogs : public BindableObject { public: Rc build(Rc notifications, Value globalEnabled); diff --git a/examples/showcase/src/Dropdowns.hpp b/examples/showcase/src/Dropdowns.hpp index 62741858..7b6cbc27 100644 --- a/examples/showcase/src/Dropdowns.hpp +++ b/examples/showcase/src/Dropdowns.hpp @@ -24,7 +24,7 @@ namespace Brisk { -class ShowcaseDropdowns : public BindableObject { +class ShowcaseDropdowns : public BindableObject { public: Rc build(Rc notifications, Value globalEnabled); diff --git a/examples/showcase/src/Editors.hpp b/examples/showcase/src/Editors.hpp index ca7a35e9..421c9e2e 100644 --- a/examples/showcase/src/Editors.hpp +++ b/examples/showcase/src/Editors.hpp @@ -25,7 +25,7 @@ namespace Brisk { -class ShowcaseEditors : public BindableObject { +class ShowcaseEditors : public BindableObject { public: Rc build(Rc notifications, Value globalEnabled); diff --git a/examples/showcase/src/Layout.hpp b/examples/showcase/src/Layout.hpp index c4a89b11..564eba79 100644 --- a/examples/showcase/src/Layout.hpp +++ b/examples/showcase/src/Layout.hpp @@ -24,7 +24,7 @@ namespace Brisk { -class ShowcaseLayout : public BindableObject { +class ShowcaseLayout : public BindableObject { public: Rc build(Rc notifications, Value globalEnabled); diff --git a/examples/showcase/src/Messenger.hpp b/examples/showcase/src/Messenger.hpp index 2611b36a..b33d4cb7 100644 --- a/examples/showcase/src/Messenger.hpp +++ b/examples/showcase/src/Messenger.hpp @@ -24,7 +24,7 @@ namespace Brisk { -class ShowcaseMessenger : public BindableObject { +class ShowcaseMessenger : public BindableObject { public: ShowcaseMessenger(); diff --git a/examples/showcase/src/Typography.hpp b/examples/showcase/src/Typography.hpp index 687dfd84..43e585e0 100644 --- a/examples/showcase/src/Typography.hpp +++ b/examples/showcase/src/Typography.hpp @@ -24,7 +24,7 @@ namespace Brisk { -class ShowcaseTypography : public BindableObject { +class ShowcaseTypography : public BindableObject { public: Rc build(Rc notifications, Value globalEnabled); diff --git a/examples/showcase/src/Visual.hpp b/examples/showcase/src/Visual.hpp index a552a4ae..2435db38 100644 --- a/examples/showcase/src/Visual.hpp +++ b/examples/showcase/src/Visual.hpp @@ -24,7 +24,7 @@ namespace Brisk { -class ShowcaseVisual : public BindableObject { +class ShowcaseVisual : public BindableObject { public: ShowcaseVisual(); diff --git a/examples/splash-screen/splash-screen.cpp b/examples/splash-screen/splash-screen.cpp index cfd8f5a3..53d65547 100644 --- a/examples/splash-screen/splash-screen.cpp +++ b/examples/splash-screen/splash-screen.cpp @@ -69,7 +69,7 @@ class SplashScreen final : public Component { window->setRectangle(windowRect); // Applies the centered position and size. } - std::atomic_int m_progress{ 0 }; // Tracks loading progress (0-100), starting at 0. + int m_progress{ 0 }; // Tracks loading progress (0-100), starting at 0. }; // Defines the main application component with a simple UI. @@ -131,7 +131,9 @@ int briskMain() { // Simulates a 2-second app loading process, updating the progress bar dynamically. auto splashTime = std::chrono::steady_clock::now() + std::chrono::milliseconds(2000); while (std::chrono::steady_clock::now() < splashTime) { - application.cycle(false); // Processes events without blocking during the loading simulation. + application.cycle( + WindowApplication::ProcessEventsMode::CheckOnly); // Processes events without blocking during the + // loading simulation. // Updates the progress bar based on remaining time, increasing from 0 to 100. splash->m_progress = 100 - std::chrono::duration_cast( splashTime - std::chrono::steady_clock::now()) diff --git a/include/brisk/core/Binding.hpp b/include/brisk/core/Binding.hpp index ae5eeed7..386226d5 100644 --- a/include/brisk/core/Binding.hpp +++ b/include/brisk/core/Binding.hpp @@ -1057,6 +1057,10 @@ class Bindings { */ void registerRegion(BindingAddress region, Rc queue); + void registerRegion(BindingAddress region) { + registerRegion(region, mainScheduler); + } + /** * @brief Unregisters a previously registered region. * @@ -1464,6 +1468,9 @@ struct BindingRegistration { bindings->registerRegion(toBindingAddress(thiz), std::move(queue)); } + template + BindingRegistration(const T* thiz) : BindingRegistration(thiz, mainScheduler) {} + /** * @brief Destructor that unregisters the binding region. */ @@ -2070,7 +2077,7 @@ concept PointerToScheduler = requires(T p) { { *p } -> std::convertible_to>; }; -template *>(nullptr)> +template class BindableObject : public Object, public std::enable_shared_from_this> { private: diff --git a/include/brisk/core/Io.hpp b/include/brisk/core/Io.hpp index 2d326952..98bcd5d9 100644 --- a/include/brisk/core/Io.hpp +++ b/include/brisk/core/Io.hpp @@ -496,9 +496,6 @@ fs::path uniqueFileName(std::string_view base, std::string_view numbered, int i * character from the set of lowercase letters and digits, and `*` * with 16 random characters from the same set. * - * The function is thread-safe, using a mutex to ensure that the random - * number generator is accessed in a synchronized manner. - * * @param pattern A string representing the desired pattern for the * temporary file name. The pattern can include `?` * and `*` as placeholders for random characters. diff --git a/include/brisk/core/Threading.hpp b/include/brisk/core/Threading.hpp index 0e3b0db1..413b4d9d 100644 --- a/include/brisk/core/Threading.hpp +++ b/include/brisk/core/Threading.hpp @@ -31,6 +31,24 @@ namespace Brisk { +class Thread { +public: + Thread(); + + ~Thread(); + + std::thread::id get_id() const noexcept; + +protected: + bool isTerminated() const noexcept; + + virtual void threadBody(); + +private: + std::thread m_thread; + std::atomic m_terminate{ false }; +}; + /** * @enum ExecuteImmediately * @brief Defines when and how a scheduled function is dispatched in a task queue. @@ -250,6 +268,13 @@ class TaskQueue : public Scheduler { ~TaskQueue(); + /** + * @brief Set the Thread Id object + * + * @param threadId + */ + void setThreadId(std::thread::id threadId) noexcept; + /** * @brief Dispatches a function for execution. * @@ -336,7 +361,7 @@ class TaskQueue : public Scheduler { /// @brief Represents the task queue and scheduler for the main thread. extern Rc mainScheduler; -extern Rc uiScheduler; +extern Rc noScheduler; template T waitFuture(VoidFunc waitFunc, std::future future, int intervalMS) { diff --git a/include/brisk/core/Utilities.hpp b/include/brisk/core/Utilities.hpp index 90af7b50..656ae20d 100644 --- a/include/brisk/core/Utilities.hpp +++ b/include/brisk/core/Utilities.hpp @@ -31,6 +31,7 @@ #include #include "internal/Optional.hpp" #include "internal/Typename.hpp" +#include #include namespace Brisk { @@ -722,7 +723,7 @@ inline void removeValueByKey(KeyValueOrderedList& list, const K& key) { * @return std::optional The found value, or std::nullopt if not found. */ template -inline std::optional keyToValue(const std::vector& list, K(V::* field), const K& fieldValue) { +inline std::optional keyToValue(const std::vector& list, K(V::*field), const K& fieldValue) { for (size_t i = 0; i < list.size(); ++i) { if (list[i].*field == fieldValue) return list[i]; @@ -744,7 +745,7 @@ inline std::optional keyToValue(const std::vector& list, K(V::* field), co * @return std::optional The index of the found key, or std::nullopt if not found. */ template -inline std::optional findKey(const std::vector& list, K(V::* field), const K& fieldValue) { +inline std::optional findKey(const std::vector& list, K(V::*field), const K& fieldValue) { for (size_t i = 0; i < list.size(); ++i) { if (list[i].*field == fieldValue) return i; @@ -1010,8 +1011,8 @@ struct AutoSingleton { }; template -std::optional> transformOptional(const std::optional& opt, - Func&& func) { +constexpr std::optional> transformOptional(const std::optional& opt, + Func&& func) { if (opt.has_value()) { return std::optional>{ std::forward(func)(*opt) }; } @@ -1019,7 +1020,8 @@ std::optional> transformOptional(const std: } template -std::optional> transformOptional(std::optional&& opt, Func&& func) { +constexpr std::optional> transformOptional(std::optional&& opt, + Func&& func) { if (opt.has_value()) { return std::optional>{ std::forward(func)(std::move(*opt)) }; } @@ -1027,7 +1029,7 @@ std::optional> transformOptional(std::optional -std::underlying_type_t to_underlying(Enum e) +constexpr std::underlying_type_t to_underlying(Enum e) requires std::is_enum_v { return static_cast>(e); @@ -1048,7 +1050,7 @@ struct optional_value_type { }; template -std::optional wrapOptional(std::optional&& value) { +constexpr std::optional wrapOptional(std::optional&& value) { return std::move(value); } @@ -1093,6 +1095,163 @@ FakeOptional wrapOptional(const T& value) template struct TypeList {}; +/** + * @brief RAII wrapper that manages the lifetime of a shared singleton instance. + * + * SharedSingleton ensures that a single instance of type T exists as long as at least one + * SharedSingleton object is alive. The singleton is created when the first SharedSingleton + * is constructed and destroyed when the last one is destroyed. + * + * This class is designed to be embedded as a member variable in other classes to automatically + * manage singleton dependencies based on the containing class's lifetime. + * + * Thread-safe: All operations are protected by an internal mutex. + * + * @tparam T The type of the singleton instance. Must be default constructible. + * @tparam Tag Optional tag type to create distinct singleton instances for the same T. + * + * @code + * class MyService { + * SharedSingleton m_dbPool; // DB exists while any MyService exists + * public: + * void doWork() { + * m_dbPool->execute("SELECT ..."); + * } + * }; + * @endcode + */ +template +class SharedSingleton { + static_assert(std::is_default_constructible_v, "T must be default constructible"); + +private: + inline static std::mutex m_mutex; + inline static T* m_instance = nullptr; + inline static uint32_t m_refCount = 0; + + static T* acquire() noexcept(std::is_nothrow_default_constructible_v) { + std::lock_guard lock(m_mutex); + if (m_refCount++ == 0) { + BRISK_ASSERT(!m_instance); + m_instance = new T(); + } + return m_instance; + } + + static void release() noexcept { + std::lock_guard lock(m_mutex); + BRISK_ASSERT(m_refCount > 0); + if (--m_refCount == 0) { + BRISK_ASSERT(m_instance); + delete m_instance; + m_instance = nullptr; + } + } + +public: + /** + * @brief Constructs a SharedSingleton and increments the reference count. + * + * If this is the first SharedSingleton for type T, creates the singleton instance. + * + * @throws Any exception thrown by T's default constructor (if not noexcept). + */ + SharedSingleton() noexcept(std::is_nothrow_default_constructible_v) { + acquire(); + } + + /** + * @brief Destroys the SharedSingleton and decrements the reference count. + * + * If this is the last SharedSingleton for type T, destroys the singleton instance. + */ + ~SharedSingleton() { + release(); + } + + /** + * @brief Copy constructor. Increments the reference count. + * + * Creates a new SharedSingleton that shares ownership of the same singleton instance. + * + * @param other The SharedSingleton to copy from. + * @throws Any exception thrown by T's default constructor (if not noexcept and singleton + * needs to be created). + */ + SharedSingleton(const SharedSingleton&) noexcept(std::is_nothrow_default_constructible_v) { + acquire(); + } + + /** + * @brief Copy assignment operator. + * + * No-op since all SharedSingleton instances for the same T already manage the same singleton. + * + * @param other The SharedSingleton to assign from. + * @return Reference to this object. + */ + SharedSingleton& operator=(const SharedSingleton&) noexcept { + // No-op: both objects already manage the same singleton + return *this; + } + + /** + * @brief Move constructor. Increments the reference count. + * + * Note: This does not transfer ownership but creates a new reference to the shared singleton, + * similar to the copy constructor. + * + * @param other The SharedSingleton to move from. + * @throws Any exception thrown by T's default constructor (if not noexcept and singleton + * needs to be created). + */ + SharedSingleton(SharedSingleton&&) noexcept(std::is_nothrow_default_constructible_v) { + acquire(); + } + + /** + * @brief Move assignment operator. + * + * No-op since all SharedSingleton instances for the same T already manage the same singleton. + * + * @param other The SharedSingleton to assign from. + * @return Reference to this object. + */ + SharedSingleton& operator=(SharedSingleton&&) noexcept { + // No-op: both objects already manage the same singleton + return *this; + } + + /** + * @brief Arrow operator for accessing members of the singleton instance. + * + * @return Pointer to the singleton instance. + */ + T* operator->() const noexcept { + BRISK_ASSERT(m_instance != nullptr); + return m_instance; + } + + /** + * @brief Dereference operator for accessing the singleton instance. + * + * @return Reference to the singleton instance. + */ + T& operator*() const noexcept { + BRISK_ASSERT(m_instance != nullptr); + return *m_instance; + } + + /** + * @brief Gets a pointer to the singleton instance. + * + * @return Pointer to the singleton instance. + */ + T* get() const noexcept { + return m_instance; + } +}; + } // namespace Brisk template diff --git a/include/brisk/graphics/Fonts.hpp b/include/brisk/graphics/Fonts.hpp index 04e19c51..a41b7b8f 100644 --- a/include/brisk/graphics/Fonts.hpp +++ b/include/brisk/graphics/Fonts.hpp @@ -30,7 +30,6 @@ #include "Image.hpp" #include #include "internal/Sprites.hpp" -#include "I18n.hpp" #include #include #include @@ -39,9 +38,15 @@ namespace Brisk { -class EUnicode : public ELogic { -public: - using ELogic::ELogic; +enum class TextDirection : uint8_t { + LTR, + RTL, +}; + +template <> +inline constexpr std::initializer_list> defaultNames{ + { "LTR", TextDirection::LTR }, + { "RTL", TextDirection::RTL }, }; class EFreeType : public ELogic { @@ -758,23 +763,4 @@ class FontManager final { extern std::optional fonts; -inline std::vector textBreakPositions(std::u32string_view text, TextBreakMode mode) { - std::vector result(1, 0); - Rc iter = Internal::textBreakIterator(text, mode); - while (auto p = iter->next()) { - result.push_back(*p); - } - return result; -} - -/** - * @brief Indicates whether the ICU library is available for full Unicode support. - * - * When `icuAvailable` is `true`, the font functions will have full Unicode support - * for Bidirectional (BiDi) text processing and grapheme/line - * breaking functionality (textBreakPositions). - * - */ -extern bool icuAvailable; - } // namespace Brisk diff --git a/include/brisk/graphics/Gradients.hpp b/include/brisk/graphics/Gradients.hpp index dd3c62f2..53cd0828 100644 --- a/include/brisk/graphics/Gradients.hpp +++ b/include/brisk/graphics/Gradients.hpp @@ -46,7 +46,7 @@ struct ColorStop { enum class GradientType : uint8_t { Linear, ///< A linear gradient. Radial, ///< A radial gradient. - Angular, ///< An angular (conic) gradient. + Angular, ///< An angular (conic) gradient. Reflected, ///< A reflected gradient. }; diff --git a/include/brisk/graphics/I18n.hpp b/include/brisk/graphics/I18n.hpp deleted file mode 100644 index c7a1708f..00000000 --- a/include/brisk/graphics/I18n.hpp +++ /dev/null @@ -1,82 +0,0 @@ -/* - * Brisk - * - * Cross-platform application framework - * -------------------------------------------------------------- - * - * Copyright (C) 2025 Brisk Developers - * - * This file is part of the Brisk library. - * - * Brisk is dual-licensed under the GNU General Public License, version 2 (GPL-2.0+), - * and a commercial license. You may use, modify, and distribute this software under - * the terms of the GPL-2.0+ license if you comply with its conditions. - * - * You should have received a copy of the GNU General Public License along with this program. - * If not, see . - * - * If you do not wish to be bound by the GPL-2.0+ license, you must purchase a commercial - * license. For commercial licensing options, please visit: https://brisklib.com - */ \ -#pragma once - -#include -#include -#include -#include -#include -#include -#include - -namespace Brisk { - -enum class TextBreakMode { - Grapheme, - Word, - Line, -}; - -constexpr auto operator+(TextBreakMode value) noexcept { - return static_cast>(value); -} - -enum class TextDirection : uint8_t { - LTR, - RTL, -}; - -template <> -inline constexpr std::initializer_list> defaultNames{ - { "LTR", TextDirection::LTR }, - { "RTL", TextDirection::RTL }, -}; - -namespace Internal { - -class TextBreakIterator { -public: - virtual ~TextBreakIterator() {} - - virtual std::optional next() = 0; -}; - -Rc textBreakIterator(std::u32string_view text, TextBreakMode mode); - -class BidiTextIterator { -public: - virtual ~BidiTextIterator() {} - - struct TextFragment { - Range codepointRange; - uint32_t visualOrder; - TextDirection direction; - }; - - virtual std::optional next() = 0; -}; - -Rc bidiTextIterator(std::u32string_view text, TextDirection defaultDirection); - -} // namespace Internal - -} // namespace Brisk diff --git a/include/brisk/graphics/NativeWindowHandle.hpp b/include/brisk/graphics/NativeWindowHandle.hpp index 4d50e7e1..3d54f4c9 100644 --- a/include/brisk/graphics/NativeWindowHandle.hpp +++ b/include/brisk/graphics/NativeWindowHandle.hpp @@ -41,7 +41,9 @@ namespace Brisk { struct NativeWindowHandle { - void* ptr = nullptr; + void* ptr = nullptr; + + explicit NativeWindowHandle(void* ptr) noexcept : ptr(ptr) {} NativeWindowHandle() noexcept = default; @@ -61,10 +63,30 @@ struct NativeWindowHandle { #ifdef BRISK_APPLE #if defined(__OBJC__) NSWindow* nsWindow() const noexcept { - return (__bridge NSWindow*)ptr; + if (ptr == nullptr) + return nullptr; + if (isNsWindow()) + return (__bridge NSWindow*)ptr; + return [(__bridge NSView*)ptr window]; + } + + NSView* nsView() const noexcept { + if (ptr == nullptr) + return nullptr; + return (__bridge NSView*)ptr; + } + + bool isNsWindow() const noexcept { + return ptr != nullptr && [((__bridge id)ptr) isKindOfClass:[NSWindow class]]; + } + + bool isNsView() const noexcept { + return ptr != nullptr && [((__bridge id)ptr) isKindOfClass:[NSView class]]; } explicit NativeWindowHandle(NSWindow* nsWindow) noexcept : ptr((__bridge void*)nsWindow) {} + + explicit NativeWindowHandle(NSView* nsView) noexcept : ptr((__bridge void*)nsView) {} #endif #endif diff --git a/include/brisk/graphics/RenderState.hpp b/include/brisk/graphics/RenderState.hpp index 9a3ecab7..014a70f0 100644 --- a/include/brisk/graphics/RenderState.hpp +++ b/include/brisk/graphics/RenderState.hpp @@ -22,8 +22,10 @@ #include "Matrix.hpp" #include "Gradients.hpp" +#include #include #include +#include #include #include #include @@ -31,6 +33,12 @@ namespace Brisk { +extern Rc renderScheduler; + +extern bool separateRenderThread; + +void ensureOnRenderThread(); + namespace Internal { constexpr inline uint32_t max2DTextureSize = 8192; constexpr inline float textRectPadding = 4 / 6.f; // 0.667f; diff --git a/include/brisk/graphics/Renderer.hpp b/include/brisk/graphics/Renderer.hpp index ab1132e1..1f8004be 100644 --- a/include/brisk/graphics/Renderer.hpp +++ b/include/brisk/graphics/Renderer.hpp @@ -315,7 +315,7 @@ class RenderPipeline final : public RenderContext { std::vector m_data; ///< Buffer for associated rendering data. std::vector> m_textures; ///< List of textures used in rendering. int m_numBatches = 0; ///< Number of rendering batches. - Rectangle m_globalScissor; ///< The current clipping rectangle. + Rectangle m_globalScissor; ///< The current clipping rectangle. }; /** diff --git a/include/brisk/gui/Component.hpp b/include/brisk/gui/Component.hpp index e5eb7c25..de763e60 100644 --- a/include/brisk/gui/Component.hpp +++ b/include/brisk/gui/Component.hpp @@ -33,7 +33,7 @@ namespace Brisk { * in the application. It manages the lifecycle of the component, its event * handling, and its associated window. */ -class Component : public BindableObject { +class Component : public BindableObject { public: ~Component() override {} @@ -60,6 +60,8 @@ class Component : public BindableObject { */ Rc makeWindow(); + Rc newWindow(); + /** * @brief Closes the associated window. * @@ -81,7 +83,7 @@ class Component : public BindableObject { virtual Rc build(); /** - * @brief This method is called on the main thread and is expected to return + * @brief This method is expected to return * the window object that the component will use. * * @return Rc A reference-counted pointer to the GuiWindow. @@ -89,7 +91,7 @@ class Component : public BindableObject { virtual Rc createWindow(); /** - * @brief Handles any unhandled events. + * @brief Handles event not handled by widget tree. * * If an event is not handled by the widget tree, this function will be called. * It can be overridden to provide custom handling for specific events. @@ -130,19 +132,17 @@ class Component : public BindableObject { }; /** - * @brief Creates a component on the UI thread and assigns it to the provided reference. + * @brief Creates a component and assigns it to the provided reference. * @tparam ComponentClass The type of the component, must derive from Component. * @param component Reference to an Rc where the created component will be stored. */ template ComponentClass> void createComponent(Rc& component) { - uiScheduler->dispatchAndWait([&]() { - component = rcnew ComponentClass(); - }); + component = rcnew ComponentClass(); } /** - * @brief Creates a component on the UI thread and returns it. + * @brief Creates a component and returns it. * @tparam ComponentClass The type of the component, must derive from Component. * @return An Rc containing the created component. */ diff --git a/include/brisk/gui/Event.hpp b/include/brisk/gui/Event.hpp index 92ea9e05..fe69bcc8 100644 --- a/include/brisk/gui/Event.hpp +++ b/include/brisk/gui/Event.hpp @@ -44,11 +44,6 @@ enum class MouseInteraction : uint8_t { class Widget; -/** - * @brief Global atomic event counter. - */ -extern std::atomic_uint32_t eventCookie; - /** * @brief Enum representing drag event subtypes. */ @@ -67,11 +62,9 @@ enum class DropEventSubtype { }; /** - * @brief Base struct for events, holding a unique event cookie. + * @brief Base struct for events */ -struct EventBase { - uint32_t cookie = ++eventCookie; ///< Unique identifier for the event. -}; +struct EventBase {}; /** * @brief Base struct for input events, derived from EventBase. @@ -357,11 +350,6 @@ struct Event : public EventVariant { template std::optional as() const; - /** - * @brief Returns the event's unique cookie. - */ - uint32_t cookie() const; - /** * @brief Stops the event from propagating. */ @@ -465,10 +453,7 @@ struct InputQueue { int focusCaptureLevel = 0; int maxFocusCaptureLevel = 0; - std::deque events; - std::vector injectedEvents; - function unhandledEvent; - bool passThroughFlag = false; + bool passThroughFlag = false; std::weak_ptr passedThroughBy; std::optional lastMouseEvent; std::optional lastInputEvent; @@ -494,6 +479,7 @@ struct InputQueue { * @param ptr The widget to auto-focus. */ void setAutoFocus(std::weak_ptr ptr); + void updateAutoFocus(); /** * Increases the focus capture level. @@ -509,7 +495,7 @@ struct InputQueue { * Checks if any widget currently has focus. * @return True if a widget has focus, false otherwise. */ - bool hasFocus(); + [[nodiscard]] bool hasFocus(); /** * Handles mouse leave events. @@ -539,7 +525,7 @@ struct InputQueue { * Checks if an object is currently being dragged. * @return True if dragging, false otherwise. */ - bool isDragging() const; + [[nodiscard]] bool isDragging() const; /** * Allows the current drag to be dropped. @@ -568,14 +554,14 @@ struct InputQueue { * @param widget The widget to check. * @return The mouse position relative to the widget, if applicable. */ - std::optional mousePosFor(Widget* widget) const; + [[nodiscard]] std::optional mousePosFor(Widget* widget) const; /** * Returns the mouse position relative to the widget's client area, if within its bounds. * @param widget The widget to check. * @return The mouse position relative to the widget's client area, if applicable. */ - std::optional mousePosForClient(Widget* widget) const; + [[nodiscard]] std::optional mousePosForClient(Widget* widget) const; /** * Gets the widget at the specified point, starting from a specified offset. @@ -584,8 +570,8 @@ struct InputQueue { * @param respect_anywhere Whether to respect the "anywhere" flag. * @return A tuple containing the widget at the point and the corresponding index. */ - std::tuple, int> getAt(Point pt, int offset = -1, - bool respect_anywhere = true) const; + [[nodiscard]] std::tuple, int> getAt(Point pt, int offset = -1, + bool respect_anywhere = true) const; /** * Calls the provided function for each widget at the current mouse position, optionally bubbling. @@ -594,11 +580,12 @@ struct InputQueue { * @param useMouseCapture Whether to use mouse capture information. * @return True if the function succeeds, false otherwise. */ - bool mouseAtBubble(function_ref fn, bool bubble = true, bool useMouseCapture = true) const; + [[nodiscard]] bool mouseAtBubble(function_ref fn, bool bubble = true, + bool useMouseCapture = true) const; template - std::optional getAtMouse(function_ref(Widget*)> fn, bool bubble = true, - bool useMouseCapture = true) const { + [[nodiscard]] std::optional getAtMouse(function_ref(Widget*)> fn, bool bubble = true, + bool useMouseCapture = true) const { std::optional value; if (mouseAtBubble( [&](Widget* w) BRISK_INLINE_LAMBDA -> bool { @@ -617,13 +604,13 @@ struct InputQueue { * Gets the hint of the widget under the mouse, if any. * @return The hint of the widget, if applicable. */ - std::optional getHintAtMouse() const; + [[nodiscard]] std::optional getHintAtMouse() const; /** * Gets the cursor type for the widget under the mouse, if any. * @return The cursor type, if applicable. */ - std::optional getCursorAtMouse() const; + [[nodiscard]] std::optional getCursorAtMouse() const; /** * Sets the focus to the specified widget. @@ -673,45 +660,41 @@ struct InputQueue { void handleFocusEvents(Event& e); /** - * Injects an event into the queue to be processed at the next frame. - * @param event The event to inject. - */ - void injectEvent(Event event); - - /** - * Adds an event to the queue to be processed in the next call to processEvent. - * @param event The event to add. + * @brief Clean up the input queue state. + * + * Should be called after frame processing to remove invalid references. */ - void addEvent(Event event); + void cleanup(); - /** - * Processes all events in the queue. - */ - void processEvents(); + /// Returns true if the event was handled, false otherwise. + [[nodiscard]] bool processEvent(Event e); /** * Processes a key event. * @param e The key event to process. + * @return True if the event was handled, false otherwise. */ - void processKeyEvent(Event e); + [[nodiscard]] bool processKeyEvent(Event e); /** * Processes a mouse event. * @param e The mouse event to process. + * @return True if the event was handled, false otherwise. */ - void processMouseEvent(Event e); + [[nodiscard]] bool processMouseEvent(Event e); /** - * Processes a drag event. - * @param e The drag event to process. + * @brief Processes mouse event and generate all needed drag&drop events + * @return True if drag&drop events were generated, false otherwise. */ - void processDragEvent(Event e); + [[nodiscard]] bool handleDragAndDrop(Event e); /** * Processes a targeted event. * @param e The targeted event to process. + * @return True if the event was handled, false otherwise. */ - void processTargetedEvent(Event e); + [[nodiscard]] bool processTargetedEvent(Event e); /** * Constructor for the InputQueue class. diff --git a/include/brisk/gui/Gui.hpp b/include/brisk/gui/Gui.hpp index 417b9b4c..c4898445 100644 --- a/include/brisk/gui/Gui.hpp +++ b/include/brisk/gui/Gui.hpp @@ -65,9 +65,9 @@ void boxPainter(Canvas& canvas, const Widget& widget, RectangleF rect); void boxPainter(Canvas& canvas, const Widget& widget); namespace Internal { -extern std::atomic_bool debugRelayoutAndRegenerate; -extern std::atomic_bool debugBoundaries; -extern std::atomic_bool debugDirtyRect; +extern bool debugRelayoutAndRegenerate; +extern bool debugBoundaries; +extern bool debugDirtyRect; } // namespace Internal class Stylesheet; @@ -389,7 +389,7 @@ struct IsConstexprCompatible> { template struct GuiProp { - ValueType(WidgetClass::* field); + ValueType(WidgetClass::*field); std::conditional_t::value, ValueType, ValueType (*)()> initialValue; PropFlags flags; const char* name = nullptr; @@ -466,12 +466,12 @@ struct GuiProp { }; template -GuiProp(ValueType(WidgetClass::*), std::type_identity_t, PropFlags, const char* = nullptr) - -> GuiProp; +GuiProp(ValueType(WidgetClass::*), std::type_identity_t, PropFlags, + const char* = nullptr) -> GuiProp; template struct GuiProp> { - Animated(WidgetClass::* field); + Animated(WidgetClass::*field); ValueType initialValue; PropFlags flags; const char* name = nullptr; @@ -623,17 +623,13 @@ struct GuiPropCompound { }; } // namespace Internal -class WIDGET Widget : public BindableObject { +class WIDGET Widget : public BindableObject { BRISK_DYNAMIC_CLASS_ROOT(Widget) public: - using Ptr = std::shared_ptr; - using WidgetPtrs = std::vector; - using WidgetIterator = typename WidgetPtrs::iterator; - using WidgetConstIterator = typename WidgetPtrs::const_iterator; - - static Rc dispatcher() { - return uiScheduler; - } + using Ptr = std::shared_ptr; + using WidgetPtrs = std::vector; + using WidgetIterator = typename WidgetPtrs::iterator; + using WidgetConstIterator = typename WidgetPtrs::const_iterator; Widget& operator=(const Widget&) = delete; Widget& operator=(Widget&&) = delete; diff --git a/include/brisk/gui/GuiApplication.hpp b/include/brisk/gui/GuiApplication.hpp index b36942f0..e56cd18a 100644 --- a/include/brisk/gui/GuiApplication.hpp +++ b/include/brisk/gui/GuiApplication.hpp @@ -37,12 +37,6 @@ class GuiApplication : public WindowApplication { [[nodiscard]] int run(Rc mainComponent); - template Fn> - [[nodiscard]] int run(Fn&& fn) { - Rc component = uiScheduler->dispatchAndWait(std::move(fn)); - return run(std::move(component)); - } - void modalRun(Rc modalComponent); void addWindow(Rc component, bool makeVisible = true); diff --git a/include/brisk/gui/GuiWindow.hpp b/include/brisk/gui/GuiWindow.hpp index b4f8a020..cf8a59dc 100644 --- a/include/brisk/gui/GuiWindow.hpp +++ b/include/brisk/gui/GuiWindow.hpp @@ -44,11 +44,11 @@ class GuiWindow : public Window { void setId(std::string id); bool handleKeyEvent(KeyCode key, int scancode, KeyAction action, KeyModifiers mods); bool handleCharEvent(char32_t character); - bool handleEvent(function fn); WidgetTree& tree(); explicit GuiWindow(Rc component); ~GuiWindow(); + bool processEvent(Event&&); protected: Rc m_component; @@ -68,12 +68,12 @@ class GuiWindow : public Window { void beforeFrame() override; void beforeOpeningWindow() override; - void onKeyEvent(KeyCode key, int scancode, KeyAction action, KeyModifiers mods) override; - void onCharEvent(char32_t character) override; - void onMouseEvent(MouseButton button, MouseAction action, KeyModifiers mods, PointF point, + bool onKeyEvent(KeyCode key, int scancode, KeyAction action, KeyModifiers mods) override; + bool onCharEvent(char32_t character) override; + bool onMouseEvent(MouseButton button, MouseAction action, KeyModifiers mods, PointF point, int conseqClicks) override; - void onMouseMove(PointF point) override; - void onWheelEvent(float x, float y) override; + bool onMouseMove(PointF point) override; + bool onWheelEvent(float x, float y) override; void onMouseEnter() override; void onMouseLeave() override; void onNonClientClicked() override; @@ -84,8 +84,6 @@ class GuiWindow : public Window { private: std::string m_id; - bool m_frameSkipTestState = false; - std::vector m_unhandledEvents; Rectangle m_savedPaintRect{}; void updateWindowLimits(); @@ -104,6 +102,4 @@ class GuiWindow : public Window { BRISK_PROPERTIES_END }; -; - } // namespace Brisk diff --git a/include/brisk/gui/WidgetTree.hpp b/include/brisk/gui/WidgetTree.hpp index f93e75f8..aeab20e5 100644 --- a/include/brisk/gui/WidgetTree.hpp +++ b/include/brisk/gui/WidgetTree.hpp @@ -83,6 +83,10 @@ class WidgetTree { void disableTransitions(); void disableRealtimeMode(); + void applyStyleChanges(); + void processAnimations(); + void updateLayoutAndGeometry(); + Callbacks onAttached; Callbacks onDetached; @@ -105,9 +109,7 @@ class WidgetTree { void groupsBeforePaint(); void groupsAfterFrame(); void groupsBeforeLayout(); - void applyStyleChanges(); - void processEventsAndAnimations(); - void updateLayoutAndGeometry(); + void updateVisibility(); std::shared_ptr m_root; std::vector> m_animationQueue; std::vector> m_rebuildQueue; @@ -126,7 +128,6 @@ class WidgetTree { bool m_painting = false; bool m_savedDebugBoundaries = false; bool m_realtime = true; - bool m_layoutIsActual = false; InputQueue* m_inputQueue = nullptr; }; } // namespace Brisk diff --git a/include/brisk/gui/internal/Animation.hpp b/include/brisk/gui/internal/Animation.hpp index 83e5106e..3605163f 100644 --- a/include/brisk/gui/internal/Animation.hpp +++ b/include/brisk/gui/internal/Animation.hpp @@ -159,9 +159,6 @@ extern double animationSpeed; * * The PropertyAnimations class provides an interface for animating properties over time * using customizable transition parameters and easing functions. - * - * Thread Safety: - * - This class is not thread-safe. All methods should be called from the UI thread. */ class PropertyAnimations { public: diff --git a/include/brisk/widgets/Knob.hpp b/include/brisk/widgets/Knob.hpp index a4ce6155..733f4cc5 100644 --- a/include/brisk/widgets/Knob.hpp +++ b/include/brisk/widgets/Knob.hpp @@ -43,8 +43,8 @@ class WIDGET Knob : public ValueWidget { explicit Knob(Construction construction, ArgumentsView args); private: - mutable bool m_dragActive = false; - mutable float m_savedValue = NAN; + mutable bool m_dragActive = false; + mutable double m_savedValue = 0; }; void knobPainter(Canvas& canvas, const Widget& widget); diff --git a/include/brisk/widgets/Slider.hpp b/include/brisk/widgets/Slider.hpp index bb1575b9..794b4a80 100644 --- a/include/brisk/widgets/Slider.hpp +++ b/include/brisk/widgets/Slider.hpp @@ -48,10 +48,10 @@ class WIDGET Slider : public ValueWidget { private: bool m_drag = false; - float m_savedValue = NAN; + double m_savedValue = 0; constexpr static int trackThickness = 4; constexpr static int thumbRadius = 5; - float m_distance = NAN; + double m_distance = 0; }; void sliderPainter(Canvas& canvas, const Widget& widget); diff --git a/include/brisk/window/Window.hpp b/include/brisk/window/Window.hpp index 873b7200..3d795782 100644 --- a/include/brisk/window/Window.hpp +++ b/include/brisk/window/Window.hpp @@ -84,11 +84,11 @@ struct DisplaySyncPoint { Clock::duration frameDuration{ 0 }; }; -extern std::atomic_bool debugShowRenderTimeline; +extern bool debugShowRenderTimeline; struct FrameTimePredictor; -/// Current window instance. Available in UI thread. Set in uiThreadBody +/// Current window instance. Set in uiThreadBody extern Window* currentWindow; Rc currentWindowPtr(); @@ -352,9 +352,20 @@ class Window : public BindableObject, public NativeWindo PlatformWindow* platformWindow(); - void disableKeyHandling(); + void setKeyHandling(bool keyHandling); NativeWindowHandle getHandle() const final; + void setParent(NativeWindowHandle parent); + NativeWindowHandle parent() const; + + void setVSync(bool vSync); + bool vSync() const noexcept; + + /** + * @brief Check if the window is a top-level (not child) window + */ + bool isTopLevel() const noexcept; + void setOwner(Rc window); void enterModal(); void exitModal(); @@ -380,22 +391,19 @@ class Window : public BindableObject, public NativeWindo virtual void attachedToApplication(); bool m_attached = false; -private: - void mustBeUiThread() const; - protected: // Properties and dimensions - WindowStyle m_style = WindowStyle::Normal; /// UI-thread - std::string m_title; /// UI-thread - Size m_minimumSize{ -1, -1 }; /// UI-thread - Size m_maximumSize{ -1, -1 }; /// UI-thread - Size m_windowSize{ 640, 480 }; /// UI-thread - Size m_framebufferSize{ 0, 0 }; /// UI-thread - Point m_position{ -1, -1 }; /// UI-thread + WindowStyle m_style = WindowStyle::Normal; + std::string m_title; + Size m_minimumSize{ -1, -1 }; + Size m_maximumSize{ -1, -1 }; + Size m_windowSize{ 640, 480 }; + Size m_framebufferSize{ 0, 0 }; + Point m_position{ -1, -1 }; Cursor m_cursor = Cursor::Arrow; - void* m_parent = nullptr; - bool m_visible{ true }; /// Desired value. Will be applied to OS window when open - std::atomic_bool m_closing{ false }; /// If true, application will remove this window from windows list + NativeWindowHandle m_parent; + bool m_visible{ true }; /// Desired value. Will be applied to OS window when open + bool m_closing{ false }; /// If true, application will remove this window from windows list // call to change visibility void setVisible(bool newVisible); @@ -409,14 +417,14 @@ class Window : public BindableObject, public NativeWindo bool m_doubleClicked = false; bool m_keyHandling = true; - void keyEvent(KeyCode key, int scancode, KeyAction action, KeyModifiers mods); - void charEvent(char32_t character); - void mouseEvent(MouseButton button, MouseAction action, KeyModifiers mods, PointF point); - void mouseMove(PointF point); - void wheelEvent(float x, float y); + [[nodiscard]] bool keyEvent(KeyCode key, int scancode, KeyAction action, KeyModifiers mods); + [[nodiscard]] bool charEvent(char32_t character); + [[nodiscard]] bool mouseEvent(MouseButton button, MouseAction action, KeyModifiers mods, PointF point); + [[nodiscard]] bool mouseMove(PointF point); + [[nodiscard]] bool wheelEvent(float x, float y); + [[nodiscard]] bool filesDropped(std::vector files); void mouseEnter(); void mouseLeave(); - void filesDropped(std::vector files); void windowStateChanged(bool isIconified, bool isMaximized); void focusChange(bool gained); void visibilityChanged(bool newVisible); @@ -424,15 +432,15 @@ class Window : public BindableObject, public NativeWindo void windowResized(Size windowSize, Size framebufferSize); void windowMoved(Point position); void windowNonClientClicked(); - virtual void onKeyEvent(KeyCode key, int scancode, KeyAction action, KeyModifiers mods); - virtual void onCharEvent(char32_t character); - virtual void onMouseEvent(MouseButton button, MouseAction action, KeyModifiers mods, PointF point, - int conseqClicks); - virtual void onMouseMove(PointF point); - virtual void onWheelEvent(float x, float y); + [[nodiscard]] virtual bool onKeyEvent(KeyCode key, int scancode, KeyAction action, KeyModifiers mods); + [[nodiscard]] virtual bool onCharEvent(char32_t character); + [[nodiscard]] virtual bool onMouseEvent(MouseButton button, MouseAction action, KeyModifiers mods, + PointF point, int conseqClicks); + [[nodiscard]] virtual bool onMouseMove(PointF point); + [[nodiscard]] virtual bool onWheelEvent(float x, float y); + [[nodiscard]] virtual bool onFilesDropped(std::vector files); virtual void onMouseEnter(); virtual void onMouseLeave(); - virtual void onFilesDropped(std::vector files); virtual void onWindowStateChanged(bool isIconified, bool isMaximized); virtual void onFocusChange(bool gained); virtual void onVisibilityChanged(bool newVisible); @@ -449,14 +457,14 @@ class Window : public BindableObject, public NativeWindo Rc m_bufferedFrameTarget; std::chrono::microseconds m_lastFrameRenderTime{ 0 }; Internal::DisplaySyncPoint m_syncPoint; - std::atomic_llong m_frameNumber{ 0 }; + int64_t m_frameNumber{ 0 }; std::optional m_nextFrameTime; std::unique_ptr m_frameTimePredictor; - std::mutex m_mutex; VisualSettings m_renderSettings{}; - std::atomic_bool m_rendering{ false }; /// true if rendering is active - std::atomic_bool m_bufferedRendering{ Internal::bufferedRendering }; - std::atomic_bool m_forceRenderEveryFrame{ Internal::forceRenderEveryFrame }; + bool m_rendering{ false }; /// true if rendering is active + bool m_bufferedRendering{ Internal::bufferedRendering }; + bool m_forceRenderEveryFrame{ Internal::forceRenderEveryFrame }; + bool m_vSync{ true }; RenderStat m_renderStat; Rc m_renderDevice; Rc renderDevice(); @@ -486,7 +494,7 @@ class Window : public BindableObject, public NativeWindo * physical pixels. * */ - std::atomic m_contentScale{ 1.f }; + float m_contentScale{ 1.f }; /** * @brief Additional scaling factor applied to the UI and canvas. @@ -495,9 +503,9 @@ class Window : public BindableObject, public NativeWindo * * Default value is 1.0 (no additional scaling). */ - std::atomic m_canvasScale{ 1.f }; + float m_canvasScale{ 1.f }; - // m_pixelRatio = m_contentScale * m_canvasScale, UI thread + // m_pixelRatio = m_contentScale * m_canvasScale float m_pixelRatio = 1.f; int m_syncInterval{ 1 }; @@ -523,4 +531,17 @@ struct ModalMode { Rc owner; }; +struct CurrentWindowScope { + CurrentWindowScope(Window* window) { + previousWindow = Internal::currentWindow; + Internal::currentWindow = window; + } + + ~CurrentWindowScope() { + Internal::currentWindow = previousWindow; + } + + Window* previousWindow = nullptr; +}; + } // namespace Brisk diff --git a/include/brisk/window/WindowApplication.hpp b/include/brisk/window/WindowApplication.hpp index 42d0a191..5688cae7 100644 --- a/include/brisk/window/WindowApplication.hpp +++ b/include/brisk/window/WindowApplication.hpp @@ -41,14 +41,10 @@ namespace Internal { extern Window* currentWindow; } // namespace Internal -/** - * @brief Controls whether the application should process UI and render in a separate threads. Default true - */ -extern bool separateUiThread; - enum class QuitCondition { FirstWindowClosed, AllWindowsClosed, + Never, PlatformDependant, // Never on macOS, AllWindowsClosed on others }; @@ -106,7 +102,6 @@ class WindowApplication : public SerializableInterface { /** * @brief Checks if the specific window is registered to the WindowApplication - * @remark Safe to call from main or UI thread * @param window */ bool hasWindow(const Rc& window); @@ -119,10 +114,9 @@ class WindowApplication : public SerializableInterface { bool isActive() const; /** - * @brief Returns a copy of the windows list. - * @remark Safe to call from main or UI thread + * @brief Returns the windows list. */ - std::vector> windows() const; + const std::vector>& windows() const; /** * @brief Returns true if @c quit has called @@ -132,14 +126,10 @@ class WindowApplication : public SerializableInterface { // Internal methods WindowApplication(); ~WindowApplication(); - void mustBeUiThread(); double doubleClickTime() const; double doubleClickDistance() const; - Rc afterRenderQueue; - Rc onApplicationClose = rcnew TaskQueue(); - VoidFunc idleFunc(); + Callbacks<> onApplicationClose; void systemModal(function body); - void updateAndWait(); /** * @brief Start the main loop @@ -153,12 +143,20 @@ class WindowApplication : public SerializableInterface { */ void stop(); + // Enum with three options: check messages, check messages and wait for them, don't check messages + // Rephrase to get correct wording + enum class ProcessEventsMode { + CheckAndWait, + CheckOnly, + DontCheck, + }; + /** * @brief Run one cycle of the main loop - * @param wait Wait for OS events + * @param mode Mode of processing events * @remark This function is internal. Use only if you know what you do */ - void cycle(bool wait); + void cycle(ProcessEventsMode mode); QuitCondition quitCondition() const noexcept; void setQuitCondition(QuitCondition value); @@ -173,38 +171,24 @@ class WindowApplication : public SerializableInterface { void closeWindows(); void removeClosed(); - struct { - std::vector> m_windows; - } m_mainData; - - struct { - std::vector> m_windows; - } m_uiData; - - std::atomic_bool m_active{ false }; - void windowsChanged(); - double m_doubleClickTime = 0.5; - double m_doubleClickDistance = 3.0; - constexpr static int32_t noExitCode = INT32_MIN; - std::atomic_int32_t m_exitCode{ noExitCode }; - const bool m_separateUiThread; - std::thread m_uiThread; - std::atomic_bool m_uiThreadTerminate{ false }; - std::atomic_bool m_uiThreadTerminated{ false }; - std::atomic m_quitCondition{ QuitCondition::AllWindowsClosed }; - std::binary_semaphore m_uiThreadStarted{ 0 }; + std::vector> m_windows; + + bool m_active = false; + double m_doubleClickTime = 0.5; + double m_doubleClickDistance = 3.0; + std::optional m_exitCode; + QuitCondition m_quitCondition = QuitCondition::AllWindowsClosed; void renderWindows(); - void uiThreadBody(); private: - std::atomic m_discreteGpu = false; - std::atomic m_syncInterval = 1; - std::atomic m_uiScale = 1; - std::atomic m_blueLightFilter = 0; - std::atomic m_globalGamma = 1; - std::atomic m_subPixelText = true; - - BindingRegistration m_registration{ this, nullptr }; + bool m_discreteGpu = false; + int m_syncInterval = 1; + float m_uiScale = 1; + float m_blueLightFilter = 0; + float m_globalGamma = 1; + bool m_subPixelText = true; + + BindingRegistration m_registration{ this }; public: static const auto& properties() noexcept { diff --git a/include/brisk/window/internal/Windows.ComCtl6.manifest b/include/brisk/window/internal/Windows.ComCtl6.manifest new file mode 100644 index 00000000..e0a09774 --- /dev/null +++ b/include/brisk/window/internal/Windows.ComCtl6.manifest @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/resources/icu/icudt74l.dat b/resources/icu/icudt74l.dat deleted file mode 100644 index 73de76be..00000000 Binary files a/resources/icu/icudt74l.dat and /dev/null differ diff --git a/src/core/Binding.cpp b/src/core/Binding.cpp index ba062078..4d03a79d 100644 --- a/src/core/Binding.cpp +++ b/src/core/Binding.cpp @@ -105,7 +105,6 @@ void Bindings::unregisterRegion(const uint8_t* rangeBegin) { BRISK_ASSERT(false); // Assert if the region is not found } removeIndirectDependencies(it->second.get()); - const BindingAddress range = it->second->region; m_regions.erase(it); } diff --git a/src/core/Initialization.cpp b/src/core/Initialization.cpp index 32be826a..4c97deb9 100644 --- a/src/core/Initialization.cpp +++ b/src/core/Initialization.cpp @@ -33,10 +33,8 @@ void initializeCommon(InitializationFlags flags) { if (flags && InitializationFlags::Threading) { mainScheduler = rcnew TaskQueue(); - uiScheduler = rcnew TaskQueue(); } else { mainScheduler = nullptr; - uiScheduler = nullptr; } if (flags && InitializationFlags::Settings) { settings = new Settings{}; @@ -57,7 +55,6 @@ void finalizeCommon() { } mainScheduler = nullptr; - uiScheduler = nullptr; } } diff --git a/src/core/Threading.cpp b/src/core/Threading.cpp index 73710738..19de60ae 100644 --- a/src/core/Threading.cpp +++ b/src/core/Threading.cpp @@ -158,6 +158,31 @@ void Scheduler::waitForCompletion() { } Rc mainScheduler; -Rc uiScheduler; +Rc noScheduler; // always nullptr +void TaskQueue::setThreadId(std::thread::id threadId) noexcept { + m_threadId = threadId; +} + +Thread::Thread() { + m_thread = std::thread(&Thread::threadBody, this); +} + +Thread::~Thread() { + // stop thread and wait + m_terminate.store(true, std::memory_order_release); + if (m_thread.joinable()) { + m_thread.join(); + } +} + +std::thread::id Thread::get_id() const noexcept { + return m_thread.get_id(); +} + +bool Thread::isTerminated() const noexcept { + return m_terminate.load(std::memory_order_acquire); +} + +void Thread::threadBody() {} } // namespace Brisk diff --git a/src/core/Time.cpp b/src/core/Time.cpp index 3ec66c6f..6a2daa95 100644 --- a/src/core/Time.cpp +++ b/src/core/Time.cpp @@ -46,13 +46,13 @@ void SingleTimerThread::run() { } } -PeriodicTimer::PeriodicTimer(bool startNow) : time(NAN) { +PeriodicTimer::PeriodicTimer(bool startNow) : time(-1e10) { if (startNow) start(); } void PeriodicTimer::stop() { - time = NAN; + time = -1e10; } void PeriodicTimer::start() { @@ -60,7 +60,7 @@ void PeriodicTimer::start() { } bool PeriodicTimer::active() const { - return !std::isnan(time); + return time > -1e10; } bool PeriodicTimer::elapsed(double period) { diff --git a/src/graphics/CMakeLists.txt b/src/graphics/CMakeLists.txt index 1eadd13e..4670e151 100644 --- a/src/graphics/CMakeLists.txt +++ b/src/graphics/CMakeLists.txt @@ -101,24 +101,6 @@ endif () target_compile_definitions(brisk-graphics PRIVATE V_NAMESPACE=Brisk) -add_library(brisk-i18n STATIC ${PROJECT_SOURCE_DIR}/src/graphics/I18n_fallback.cpp) -target_link_libraries(brisk-i18n PUBLIC brisk-core) -add_library(Brisk::I18n ALIAS brisk-i18n) -set_property(TARGET brisk-i18n PROPERTY EXPORT_NAME I18n) - -set(ICU_DT icudt74l.dat) - -add_library(brisk-i18n-icu STATIC ${PROJECT_SOURCE_DIR}/src/graphics/I18n.cpp) -add_library(Brisk::I18n-Icu ALIAS brisk-i18n-icu) -set_property(TARGET brisk-i18n-icu PROPERTY EXPORT_NAME I18n-Icu) -target_link_libraries(brisk-i18n-icu PUBLIC brisk-core) - -if (BRISK_ICU) - target_link_libraries(brisk-graphics INTERFACE $) -else () - target_link_libraries(brisk-graphics INTERFACE $) -endif () - if (BRISK_WEBGPU) add_subdirectory(WebGpuRenderer) endif () diff --git a/src/graphics/Canvas.cpp b/src/graphics/Canvas.cpp index ef3bcb6e..55ce6500 100644 --- a/src/graphics/Canvas.cpp +++ b/src/graphics/Canvas.cpp @@ -269,17 +269,6 @@ void Canvas::drawPreparedPathCmd(const PreparedPath& path, const PaintAndTransfo } } -static float roundRadius(JoinStyle joinStyle, float radius) { - return std::max(radius, joinStyle == JoinStyle::Miter ? 0.f : 0.5f); -} - -static Rectangle transformedClipRect(const Matrix& matrix, RectangleF clipRect) { - return horizontalMax(clipRect.p1.v) <= float(INT32_MIN) && - horizontalMin(clipRect.p2.v) >= float(INT32_MAX) - ? noClipRect - : Rectangle(matrix.transform(clipRect).roundOutward()); -} - template struct CopyOrRef { CopyOrRef(T&& copy) : copy(std::move(copy)) {} diff --git a/src/graphics/D3d11Renderer/ImageBackend.cpp b/src/graphics/D3d11Renderer/ImageBackend.cpp index 07ce6d3b..cd2a9f28 100644 --- a/src/graphics/D3d11Renderer/ImageBackend.cpp +++ b/src/graphics/D3d11Renderer/ImageBackend.cpp @@ -25,6 +25,7 @@ namespace Brisk { ImageBackendD3d11* getOrCreateBackend(Rc device, Rc image, bool uploadImage, bool renderTarget) { + ensureOnRenderThread(); if (!image) return nullptr; Internal::ImageBackend* imageBackend = Internal::getBackend(image); @@ -42,6 +43,7 @@ ImageBackendD3d11* getOrCreateBackend(Rc device, Rc im ImageBackendD3d11::ImageBackendD3d11(Rc device, Image* image, bool uploadImage, bool renderTarget) : m_device(std::move(device)), m_image(image) { + ensureOnRenderThread(); D3D11_TEXTURE2D_DESC tex = texDesc( dxFormatTypeless(m_image->pixelType(), m_image->pixelFormat()), image->size(), 1, D3D11_USAGE_DEFAULT, renderTarget ? D3D11_BIND_RENDER_TARGET | D3D11_BIND_SHADER_RESOURCE : D3D11_BIND_SHADER_RESOURCE); @@ -67,12 +69,14 @@ ImageBackendD3d11::ImageBackendD3d11(Rc device, Image* image, } void ImageBackendD3d11::begin(AccessMode mode, Rectangle rect) { + ensureOnRenderThread(); if (mode != AccessMode::W) { readFromGpu(m_image->data().subrect(rect), rect.p1); } } void ImageBackendD3d11::end(AccessMode mode, Rectangle rect) { + ensureOnRenderThread(); if (mode != AccessMode::R) { writeToGpu(m_image->data().subrect(rect), rect.p1); } @@ -83,6 +87,7 @@ void ImageBackendD3d11::invalidate() { } void ImageBackendD3d11::readFromGpu(const ImageData& data, Point origin) { + ensureOnRenderThread(); D3D11_TEXTURE2D_DESC texDesc; m_texture.Get()->GetDesc(&texDesc); texDesc.Width = data.size.width; @@ -116,6 +121,7 @@ void ImageBackendD3d11::readFromGpu(const ImageData& data, Point o } void ImageBackendD3d11::writeToGpu(const ImageData& data, Point origin) { + ensureOnRenderThread(); D3D11_BOX box; box.left = origin.x; box.top = origin.y; diff --git a/src/graphics/D3d11Renderer/ImageBackend.hpp b/src/graphics/D3d11Renderer/ImageBackend.hpp index 3c38b14b..ae4a74bc 100644 --- a/src/graphics/D3d11Renderer/ImageBackend.hpp +++ b/src/graphics/D3d11Renderer/ImageBackend.hpp @@ -50,7 +50,7 @@ class ImageBackendD3d11 final : public Internal::ImageBackend { private: friend class ImageRenderTargetD3d11; friend class RenderEncoderD3d11; - Rc m_device; + const Rc m_device; ComPtr m_texture; ComPtr m_srv; diff --git a/src/graphics/D3d11Renderer/ImageRenderTarget.cpp b/src/graphics/D3d11Renderer/ImageRenderTarget.cpp index ee2cd3d8..c9fbdb0a 100644 --- a/src/graphics/D3d11Renderer/ImageRenderTarget.cpp +++ b/src/graphics/D3d11Renderer/ImageRenderTarget.cpp @@ -27,6 +27,7 @@ ImageRenderTargetD3d11::ImageRenderTargetD3d11(Rc device, Siz DepthStencilType depthStencil, int samples) : m_device(std::move(device)), m_frameSize(frameSize), m_type(type), m_depthStencilType(depthStencil), m_samples(samples) { + ensureOnRenderThread(); if (!updateImage()) { return; @@ -36,6 +37,7 @@ ImageRenderTargetD3d11::ImageRenderTargetD3d11(Rc device, Siz ImageRenderTargetD3d11::~ImageRenderTargetD3d11() = default; bool ImageRenderTargetD3d11::updateImage() { + ensureOnRenderThread(); m_image = rcnew Image(m_frameSize, imageFormat(m_type, backBufferFormat)); ImageBackendD3d11* backend = getOrCreateBackend(m_device, m_image, false, true); m_backBuffer.colorBuffer = backend->m_texture; @@ -50,6 +52,7 @@ Size ImageRenderTargetD3d11::size() const { } void ImageRenderTargetD3d11::setSize(Size newSize) { + ensureOnRenderThread(); if (newSize != m_frameSize) { m_frameSize = newSize; if (!updateImage()) { diff --git a/src/graphics/D3d11Renderer/RenderDevice.cpp b/src/graphics/D3d11Renderer/RenderDevice.cpp index fa1a7ca8..7e0efcc5 100644 --- a/src/graphics/D3d11Renderer/RenderDevice.cpp +++ b/src/graphics/D3d11Renderer/RenderDevice.cpp @@ -39,6 +39,7 @@ static D3D_FEATURE_LEVEL featureLevels[] = { }; bool RenderDeviceD3d11::createDevice(UINT flags) { + ensureOnRenderThread(); HRESULT hr = CreateDXGIFactory2(0, IID_PPV_ARGS(m_factory.ReleaseAndGetAddressOf())); if (!SUCCEEDED(hr)) @@ -123,9 +124,12 @@ bool RenderDeviceD3d11::createDevice(UINT flags) { } RenderDeviceD3d11::RenderDeviceD3d11(RendererDeviceSelection deviceSelection, NativeDisplayHandle display) - : m_deviceSelection(deviceSelection), m_display(display) {} + : m_deviceSelection(deviceSelection), m_display(display) { + ensureOnRenderThread(); +} status RenderDeviceD3d11::init() { + ensureOnRenderThread(); #ifndef NDEBUG if (!createDevice(D3D11_CREATE_DEVICE_DEBUG)) { if (!createDevice(0)) { @@ -199,6 +203,7 @@ status RenderDeviceD3d11::init() { } void RenderDeviceD3d11::createSamplers() { + ensureOnRenderThread(); D3D11_SAMPLER_DESC samplerDesc{}; // zero-initialize samplerDesc.Filter = D3D11_FILTER_MIN_MAG_MIP_POINT; samplerDesc.AddressU = D3D11_TEXTURE_ADDRESS_CLAMP; @@ -215,11 +220,12 @@ void RenderDeviceD3d11::createSamplers() { samplerDesc.AddressU = D3D11_TEXTURE_ADDRESS_WRAP; samplerDesc.AddressV = D3D11_TEXTURE_ADDRESS_WRAP; samplerDesc.AddressW = D3D11_TEXTURE_ADDRESS_WRAP; - hr = m_device->CreateSamplerState(&samplerDesc, m_sampler.ReleaseAndGetAddressOf()); + hr = m_device->CreateSamplerState(&samplerDesc, m_sampler.ReleaseAndGetAddressOf()); CHECK_HRESULT(hr, return); } void RenderDeviceD3d11::createBlendState() { + ensureOnRenderThread(); D3D11_BLEND_DESC blendDesc{}; // zero-initialize blendDesc.RenderTarget[0].BlendEnable = TRUE; blendDesc.RenderTarget[0].SrcBlend = D3D11_BLEND_ONE; @@ -234,6 +240,7 @@ void RenderDeviceD3d11::createBlendState() { } void RenderDeviceD3d11::createRasterizerState() { + ensureOnRenderThread(); D3D11_RASTERIZER_DESC rasterDesc{}; // zero-initialize rasterDesc.FillMode = D3D11_FILL_SOLID; rasterDesc.CullMode = D3D11_CULL_NONE; @@ -244,6 +251,7 @@ void RenderDeviceD3d11::createRasterizerState() { } void RenderDeviceD3d11::createPerFrameConstantBuffer() { + ensureOnRenderThread(); D3D11_BUFFER_DESC bufDesc{}; // zero-initialize bufDesc.ByteWidth = sizeof(ConstantPerFrame); bufDesc.Usage = D3D11_USAGE_DYNAMIC; @@ -266,6 +274,7 @@ static std::string_view findVendor(uint16_t vendor) { } RenderDeviceInfo RenderDeviceD3d11::info() const { + ensureOnRenderThread(); DXGI_ADAPTER_DESC desc; m_adapter->GetDesc(&desc); RenderDeviceInfo info; @@ -283,11 +292,13 @@ RenderDeviceInfo RenderDeviceD3d11::info() const { Rc RenderDeviceD3d11::createWindowTarget(const NativeWindow* window, PixelType type, DepthStencilType depthStencil, int samples) { + ensureOnRenderThread(); return rcnew WindowRenderTargetD3d11(shared_from_this(), window, type, depthStencil, samples); } Rc RenderDeviceD3d11::createImageTarget(Size frameSize, PixelType type, DepthStencilType depthStencil, int samples) { + ensureOnRenderThread(); if (frameSize.longestSide() >= 16384) { throwException(EImageError("Requested image render target size is too large: {}", frameSize)); } @@ -295,6 +306,7 @@ Rc RenderDeviceD3d11::createImageTarget(Size frameSize, Pixel } Rc RenderDeviceD3d11::createEncoder() { + ensureOnRenderThread(); return rcnew RenderEncoderD3d11(shared_from_this()); } @@ -302,6 +314,7 @@ RenderDeviceD3d11::~RenderDeviceD3d11() = default; bool RenderDeviceD3d11::updateBackBuffer(BackBufferD3d11& buffer, PixelType type, DepthStencilType depthType, int samples) { + ensureOnRenderThread(); D3D11_RENDER_TARGET_VIEW_DESC rtvDesc{}; // zero-initialize rtvDesc.ViewDimension = samples > 1 ? D3D11_RTV_DIMENSION_TEXTURE2DMS : D3D11_RTV_DIMENSION_TEXTURE2D; rtvDesc.Format = linearColor ? dxFormat(type, backBufferFormat) : dxFormatNoSrgb(type, backBufferFormat); @@ -347,6 +360,7 @@ void RenderDeviceD3d11::decrementWindowTargets() { } void RenderDeviceD3d11::createImageBackend(Rc image) { + ensureOnRenderThread(); BRISK_ASSERT(image); if (dxFormat(image->pixelType(), image->pixelFormat()) == DXGI_FORMAT_UNKNOWN) { throwException(EImageError("Direct3D11 backend does not support the image type or format: {}, {}. " diff --git a/src/graphics/D3d11Renderer/RenderEncoder.cpp b/src/graphics/D3d11Renderer/RenderEncoder.cpp index 2c721d38..bb5c618b 100644 --- a/src/graphics/D3d11Renderer/RenderEncoder.cpp +++ b/src/graphics/D3d11Renderer/RenderEncoder.cpp @@ -111,7 +111,6 @@ void RenderEncoderD3d11::batch(std::span commands, std::span< m_frameTiming[m_frameTimingIndex].begin(m_device->m_device.Get(), m_device->m_context.Get()); } const BackBufferD3d11& backBuf = getBackBuffer(m_currentTarget.get()); - Size size = static_cast(commands.front().sourceImage)->m_image->size(); context->OMSetRenderTargets(0, nullptr, nullptr); context->CopyResource(backBuf.colorBuffer.Get(), static_cast(commands.front().sourceImage)->m_texture.Get()); diff --git a/src/graphics/D3d11Renderer/Renderer.cpp b/src/graphics/D3d11Renderer/Renderer.cpp index 57f72656..259dda0a 100644 --- a/src/graphics/D3d11Renderer/Renderer.cpp +++ b/src/graphics/D3d11Renderer/Renderer.cpp @@ -25,6 +25,7 @@ namespace Brisk { expected, RenderDeviceError> createRenderDeviceD3d11(RendererDeviceSelection deviceSelection, NativeDisplayHandle display) { + ensureOnRenderThread(); Rc device(new RenderDeviceD3d11(deviceSelection, display)); auto status = device->init(); if (!status) diff --git a/src/graphics/D3d11Renderer/WindowRenderTarget.cpp b/src/graphics/D3d11Renderer/WindowRenderTarget.cpp index 879ba803..ea7f2db2 100644 --- a/src/graphics/D3d11Renderer/WindowRenderTarget.cpp +++ b/src/graphics/D3d11Renderer/WindowRenderTarget.cpp @@ -28,6 +28,7 @@ WindowRenderTargetD3d11::WindowRenderTargetD3d11(Rc device, c PixelType type, DepthStencilType depthStencil, int samples) : m_device(std::move(device)), m_window(window), m_type(type), m_depthStencilFmt(depthStencil), m_samples(samples) { + ensureOnRenderThread(); m_device->incrementWindowTargets(); @@ -88,20 +89,24 @@ WindowRenderTargetD3d11::WindowRenderTargetD3d11(Rc device, c } WindowRenderTargetD3d11::~WindowRenderTargetD3d11() { + ensureOnRenderThread(); m_device->decrementWindowTargets(); } void WindowRenderTargetD3d11::setVSyncInterval(int interval) { + ensureOnRenderThread(); if (interval != m_vsyncInterval) { m_vsyncInterval = interval; } } void WindowRenderTargetD3d11::present() { + ensureOnRenderThread(); m_swapChain->Present(m_vsyncInterval, 0); } void WindowRenderTargetD3d11::createBackBuffer(Size size) { + ensureOnRenderThread(); if (size.longestSide() >= 16384) { throwException(EImageError("Requested window framebuffer size is too large: {}", size)); } @@ -114,6 +119,7 @@ void WindowRenderTargetD3d11::createBackBuffer(Size size) { } void WindowRenderTargetD3d11::resizeBackbuffer(Size size) { + ensureOnRenderThread(); if (size != m_size) { m_device->m_context->OMSetRenderTargets(0, nullptr, nullptr); diff --git a/src/graphics/Deps.cmake b/src/graphics/Deps.cmake index 2fac8510..de1b05f7 100644 --- a/src/graphics/Deps.cmake +++ b/src/graphics/Deps.cmake @@ -6,14 +6,6 @@ if ("${_BRISK_GRAPHICS}" STREQUAL "") set(_BRISK_GRAPHICS brisk-graphics) endif () -get_property( - _BRISK_I18N_ICU - TARGET brisk-i18n-icu - PROPERTY ALIASED_TARGET) -if ("${_BRISK_I18N_ICU}" STREQUAL "") - set(_BRISK_I18N_ICU brisk-i18n-icu) -endif () - if (BRISK_WEBGPU) get_property( _BRISK_RENDERER_WEBGPU @@ -80,14 +72,6 @@ endif () target_link_libraries(${_BRISK_GRAPHICS} ${_DEP_PRIVATE} SheenBidi::SheenBidi) # /sheenbidi -# >icu -find_package( - ICU - COMPONENTS uc - REQUIRED) -target_link_libraries(${_BRISK_I18N_ICU} ${_DEP_PRIVATE} ICU::uc) -# /icu - if (BRISK_WEBGPU) find_package(Dawn CONFIG REQUIRED) target_link_libraries(${_BRISK_RENDERER_WEBGPU} ${_DEP_PRIVATE} dawn::webgpu_dawn) @@ -104,11 +88,6 @@ if (BRISK_WEBGPU) endif () -brisk_target_link_resource( - ${_BRISK_I18N_ICU} PRIVATE "internal/icudt.dat" - INPUT ${BRISK_RESOURCES_DIR}/icu/${ICU_DT} - BROTLI) - if (BRISK_D3D11) if (_EXPORT_MODE) set(SHADER_DIR ${BRISK_RESOURCES_DIR}/shaders) diff --git a/src/graphics/Fonts.cpp b/src/graphics/Fonts.cpp index 72761864..56a16c2a 100644 --- a/src/graphics/Fonts.cpp +++ b/src/graphics/Fonts.cpp @@ -644,8 +644,6 @@ void TextLayout::selectionRectsByCharacter(Range selection, onRect); } -void uncompressICUData(); - static std::string_view freeTypeError(FT_Error err) { #undef FTERRORS_H_ #define FT_ERRORDEF(e, v, s) \ diff --git a/src/graphics/I18n.cpp b/src/graphics/I18n.cpp deleted file mode 100644 index 3c4a3263..00000000 --- a/src/graphics/I18n.cpp +++ /dev/null @@ -1,298 +0,0 @@ -/* - * Brisk - * - * Cross-platform application framework - * -------------------------------------------------------------- - * - * Copyright (C) 2025 Brisk Developers - * - * This file is part of the Brisk library. - * - * Brisk is dual-licensed under the GNU General Public License, version 2 (GPL-2.0+), - * and a commercial license. You may use, modify, and distribute this software under - * the terms of the GPL-2.0+ license if you comply with its conditions. - * - * You should have received a copy of the GNU General Public License along with this program. - * If not, see . - * - * If you do not wish to be bound by the GPL-2.0+ license, you must purchase a commercial - * license. For commercial licensing options, please visit: https://brisklib.com - */ - -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include - -#include "unicode/utypes.h" -#include "unicode/udata.h" - -typedef struct alignas(16) { - uint16_t headerSize; - uint8_t magic1, magic2; - UDataInfo info; - char padding[8]; - uint32_t count, reserved; - /* - const struct { - const char *const name; - const void *const data; - } toc[1]; - */ - uint64_t fakeNameAndData[2]; /* TODO: Change this header type from */ - /* pointerTOC to OffsetTOC. */ -} ICU_Data_Header; - -extern "C" U_EXPORT const ICU_Data_Header U_ICUDATA_ENTRY_POINT alignas(16) = { - 32, /* headerSize */ - 0xda, /* magic1, (see struct MappedData in udata.c) */ - 0x27, /* magic2 */ - { - /*UDataInfo */ - sizeof(UDataInfo), /* size */ - 0, /* reserved */ - -#if U_IS_BIG_ENDIAN - 1, -#else - 0, -#endif - - U_CHARSET_FAMILY, - sizeof(char16_t), - 0, /* reserved */ - { 0x54, 0x6f, 0x43, 0x50 }, /* data format identifier: "ToCP" */ - { 1, 0, 0, 0 }, /* format version major, minor, milli, micro */ - { 0, 0, 0, 0 } /* dataVersion */ - }, - { 's', 't', 'u', 'b', 'd', 'a', 't', 'a' }, /* Padding[8] */ - 0, /* count */ - 0, /* Reserved */ - { - /* TOC structure */ - 0, 0 /* name and data entries. Count says there are none, */ - /* but put one in just in case. */ - } -}; - -namespace Brisk { - -bool icuAvailable = true; - -// Uncompress and initialize ICU data. -static void uncompressIcuData() { - static bool icuDataInit = false; - if (icuDataInit) - return; - - // Unpack the ICU data. - static const Bytes icudt = Resources::load("internal/icudt.dat"); - - UErrorCode uerr = U_ZERO_ERROR; - udata_setCommonData(icudt.data(), &uerr); - if (uerr != UErrorCode::U_ZERO_ERROR) { - // Throw an exception if there was an error, including the error name. - throwException(EUnicode("ICU setCommonData Error: {}", u_errorName(uerr))); - } - - icuDataInit = true; - - uerr = U_ZERO_ERROR; - u_init(&uerr); - - if (uerr != UErrorCode::U_ZERO_ERROR) { - // Throw an exception if there was an error, including the error name. - throwException(EUnicode("ICU Init Error: {}", u_errorName(uerr))); - } -} - -struct UBiDiDeleter { - void operator()(UBiDi* ptr) { - ubidi_close(ptr); - } -}; - -[[noreturn]] static void handleIcuErr(UErrorCode err) { - throwException(EUnicode("ICU Error: {}", safeCharPtr(u_errorName(err)))); -} - -struct LockableICUBreakIterator { - std::mutex mutex; - std::unique_ptr iterator; -}; - -static LockableICUBreakIterator cachedBreakIterators[3]; - -static std::unique_ptr createICUBreakIterator(TextBreakMode mode) { - uncompressIcuData(); - UErrorCode uerr = U_ZERO_ERROR; - std::unique_ptr iter; - switch (mode) { - case TextBreakMode::Grapheme: - iter.reset(icu::BreakIterator::createCharacterInstance(icu::Locale::getDefault(), uerr)); - break; - case TextBreakMode::Word: - iter.reset(icu::BreakIterator::createWordInstance(icu::Locale::getDefault(), uerr)); - break; - case TextBreakMode::Line: - iter.reset(icu::BreakIterator::createLineInstance(icu::Locale::getDefault(), uerr)); - break; - default: - BRISK_UNREACHABLE(); - } - if (U_FAILURE(uerr)) { - handleIcuErr(uerr); - } else { - return iter; - } -} - -struct LockedICUBreakIterator { - LockableICUBreakIterator* locked; - icu::BreakIterator* iterator; - - icu::BreakIterator* operator->() const { - return iterator; - } - - LockedICUBreakIterator(const LockedICUBreakIterator&) noexcept = delete; - LockedICUBreakIterator(LockedICUBreakIterator&&) noexcept = delete; - LockedICUBreakIterator& operator=(const LockedICUBreakIterator&) noexcept = delete; - LockedICUBreakIterator& operator=(LockedICUBreakIterator&&) noexcept = delete; - - LockedICUBreakIterator(TextBreakMode mode) { - if (cachedBreakIterators[+mode].mutex.try_lock()) { - if (!cachedBreakIterators[+mode].iterator) { - cachedBreakIterators[+mode].iterator = createICUBreakIterator(mode); - } - locked = &cachedBreakIterators[+mode]; - iterator = cachedBreakIterators[+mode].iterator.get(); - } else { - locked = nullptr; - iterator = createICUBreakIterator(mode).release(); - } - } - - ~LockedICUBreakIterator() { - if (locked) { - locked->mutex.unlock(); - } else { - delete iterator; - } - } -}; - -LockedICUBreakIterator acquireICUBreakIterator(TextBreakMode mode) { - return LockedICUBreakIterator(mode); -} - -static TextDirection toDir(UBiDiDirection direction) { - return direction == UBIDI_LTR ? TextDirection::LTR : TextDirection::RTL; -} - -static TextDirection toDir(UBiDiLevel level) { - return (level & 1) ? TextDirection::RTL : TextDirection::LTR; -} - -#define HANDLE_UERROR(...) \ - if (U_FAILURE(uerr)) { \ - handleIcuErr(uerr); \ - return __VA_ARGS__; \ - } - -namespace Internal { - -namespace { - -class TextBreakIteratorIcu final : public TextBreakIterator { -public: - LockedICUBreakIterator icu; - icu::UnicodeString ustr; - size_t codepoints = 0; - int32_t oldp = 0; - - TextBreakIteratorIcu(std::u32string_view text, TextBreakMode mode) : icu(mode) { - uncompressIcuData(); - std::u16string u16 = utf32ToUtf16(text); - ustr.setTo(u16.data(), u16.size()); - icu->setText(ustr); - } - - ~TextBreakIteratorIcu() = default; - - std::optional next() { - int32_t p = icu->next(); - if (p == icu::BreakIterator::DONE) { - return std::nullopt; - } - codepoints += - utf16Codepoints(std::u16string_view{ ustr.getBuffer() + oldp, static_cast(p - oldp) }); - oldp = p; - return codepoints; - } -}; - -class BidiTextIteratorIcu final : public BidiTextIterator { -public: - std::unique_ptr bidi; - std::u16string u16; - int32_t codepoints = 0; - int32_t u16chars = 0; - - BidiTextIteratorIcu(std::u32string_view text, TextDirection defaultDirection) { - uncompressIcuData(); - UErrorCode uerr = U_ZERO_ERROR; - bidi.reset(ubidi_openSized(0, 0, &uerr)); - HANDLE_UERROR(); - u16 = utf32ToUtf16(text); - ubidi_setPara(bidi.get(), u16.data(), u16.size(), - defaultDirection == TextDirection::LTR ? UBIDI_DEFAULT_LTR : UBIDI_DEFAULT_RTL, nullptr, - &uerr); - HANDLE_UERROR(); - } - - ~BidiTextIteratorIcu() = default; - - bool isMixed() const { - return ubidi_getDirection(bidi.get()) == UBIDI_MIXED; - } - - std::optional next() { - if (u16chars == u16.size()) - return std::nullopt; - UErrorCode uerr = U_ZERO_ERROR; - TextFragment r; - int32_t u16length; - UBiDiLevel level; - ubidi_getLogicalRun(bidi.get(), u16chars, &u16length, &level); - u16length -= u16chars; - r.direction = toDir(level); - r.codepointRange.min = codepoints; - r.codepointRange.max = - codepoints + utf16Codepoints(std::u16string_view(u16).substr(u16chars, u16length)); - codepoints = r.codepointRange.max; - r.visualOrder = ubidi_getVisualIndex(bidi.get(), u16chars, &uerr); - HANDLE_UERROR(std::nullopt) - u16chars += u16length; - return r; - } -}; -} // namespace - -Rc textBreakIterator(std::u32string_view text, TextBreakMode mode) { - return rcnew TextBreakIteratorIcu(text, mode); -} - -Rc bidiTextIterator(std::u32string_view text, TextDirection defaultDirection) { - return rcnew BidiTextIteratorIcu(text, defaultDirection); -} -} // namespace Internal -} // namespace Brisk diff --git a/src/graphics/I18n_fallback.cpp b/src/graphics/I18n_fallback.cpp deleted file mode 100644 index 1157643c..00000000 --- a/src/graphics/I18n_fallback.cpp +++ /dev/null @@ -1,108 +0,0 @@ -/* - * Brisk - * - * Cross-platform application framework - * -------------------------------------------------------------- - * - * Copyright (C) 2025 Brisk Developers - * - * This file is part of the Brisk library. - * - * Brisk is dual-licensed under the GNU General Public License, version 2 (GPL-2.0+), - * and a commercial license. You may use, modify, and distribute this software under - * the terms of the GPL-2.0+ license if you comply with its conditions. - * - * You should have received a copy of the GNU General Public License along with this program. - * If not, see . - * - * If you do not wish to be bound by the GPL-2.0+ license, you must purchase a commercial - * license. For commercial licensing options, please visit: https://brisklib.com - */ - -#include -#include -#include - -namespace Brisk { - -bool icuAvailable = false; - -namespace Internal { - -namespace { - -bool isCategoryWithin(char32_t codepoint, int32_t categoryFirst, int32_t categoryLast) { - const int32_t category = utf8proc_category(codepoint); - return category >= categoryFirst && category <= categoryLast; -} - -bool isSplit(char32_t previous, char32_t current, TextBreakMode mode) { - switch (mode) { - case TextBreakMode::Grapheme: - return utf8proc_grapheme_break(previous, current); - case TextBreakMode::Word: - return isCategoryWithin(previous, UTF8PROC_CATEGORY_LU, UTF8PROC_CATEGORY_LO) != - isCategoryWithin(current, UTF8PROC_CATEGORY_LU, UTF8PROC_CATEGORY_LO); - case TextBreakMode::Line: - return isCategoryWithin(previous, UTF8PROC_CATEGORY_ZS, UTF8PROC_CATEGORY_ZP) && - !isCategoryWithin(current, UTF8PROC_CATEGORY_ZS, UTF8PROC_CATEGORY_ZP); - default: - BRISK_UNREACHABLE(); - } -} - -class TextBreakIteratorSimple final : public TextBreakIterator { -public: - TextBreakMode mode; - std::u32string text; - size_t pos = 1; - - TextBreakIteratorSimple(std::u32string_view text, TextBreakMode mode) : mode(mode), text(text) {} - - ~TextBreakIteratorSimple() = default; - - std::optional next() { - if (text.empty()) - return std::nullopt; - - for (; pos <= text.size(); ++pos) { - if (pos == text.size() || isSplit(text[pos - 1], text[pos], mode)) { - return static_cast(pos++); - } - } - return std::nullopt; - } -}; - -class BidiTextIteratorSimple final : public BidiTextIterator { -public: - std::optional fragment; - - BidiTextIteratorSimple(std::u32string_view text, TextDirection defaultDirection) { - fragment = TextFragment{ - Range{ 0u, uint32_t(text.size()) }, - 0, - defaultDirection, - }; - } - - ~BidiTextIteratorSimple() = default; - - std::optional next() { - std::optional result; - std::swap(result, fragment); - return result; - } -}; -} // namespace - -Rc textBreakIterator(std::u32string_view text, TextBreakMode mode) { - return rcnew TextBreakIteratorSimple(text, mode); -} - -Rc bidiTextIterator(std::u32string_view text, TextDirection defaultDirection) { - return rcnew BidiTextIteratorSimple(text, defaultDirection); -} - -} // namespace Internal -} // namespace Brisk diff --git a/src/graphics/RenderState.cpp b/src/graphics/RenderState.cpp index 93b61c6d..e9a4da1c 100644 --- a/src/graphics/RenderState.cpp +++ b/src/graphics/RenderState.cpp @@ -23,6 +23,10 @@ namespace Brisk { +Rc renderScheduler; + +bool separateRenderThread = false; + bool RenderState::operator==(const RenderState& state) const { return memcmp(this, &state, sizeof(RenderState)) == 0; } @@ -34,8 +38,8 @@ bool RenderState::compare(const RenderState& second) const { } void RenderState::premultiply() { - fillColor1 = fillColor1.premultiply(); - fillColor2 = fillColor2.premultiply(); + fillColor1 = fillColor1.premultiply(); + fillColor2 = fillColor2.premultiply(); } RenderStateEx::RenderStateEx(ShaderType shader, RenderStateExArgs args) { @@ -48,4 +52,10 @@ RenderStateEx::RenderStateEx(ShaderType shader, int instances, RenderStateExArgs this->shader = shader; args.apply(this); } + +void ensureOnRenderThread() { + if (renderScheduler != nullptr) { + BRISK_ASSERT(std::this_thread::get_id() == renderScheduler->getThreadId()); + } +} } // namespace Brisk diff --git a/src/graphics/Renderer.cpp b/src/graphics/Renderer.cpp index 13aa9f87..2019c537 100644 --- a/src/graphics/Renderer.cpp +++ b/src/graphics/Renderer.cpp @@ -89,12 +89,14 @@ void freeRenderDevice() { RenderPipeline::RenderPipeline(Rc encoder, Rc target, std::optional clear, Rectangle clipRect) : m_encoder(std::move(encoder)), m_resources(m_encoder->device()->resources()) { + ensureOnRenderThread(); m_limits = m_encoder->device()->limits(); m_encoder->begin(std::move(target), clear); m_globalScissor = clipRect; } bool RenderPipeline::flush() { + ensureOnRenderThread(); if (m_commands.empty()) return false; BRISK_ASSERT(m_resources.currentCommand > m_resources.firstCommand); @@ -114,6 +116,7 @@ bool RenderPipeline::flush() { } void RenderPipeline::command(RenderStateEx&& cmd, std::span data) { + ensureOnRenderThread(); cmd.scissor = cmd.scissor.intersection(m_globalScissor); if (cmd.scissor.empty()) return; @@ -189,6 +192,7 @@ void RenderPipeline::command(RenderStateEx&& cmd, std::span data } RenderPipeline::~RenderPipeline() { + ensureOnRenderThread(); if (std::uncaught_exceptions() == 0) { flush(); m_encoder->end(); @@ -199,20 +203,24 @@ RenderPipeline::~RenderPipeline() { } int RenderPipeline::numBatches() const { + ensureOnRenderThread(); return m_numBatches; } void RenderPipeline::setGlobalScissor(Rectangle clipRect) { + ensureOnRenderThread(); m_globalScissor = clipRect; } void RenderPipeline::blit(Rc image) { + ensureOnRenderThread(); RenderStateEx style(ShaderType::Blit, nullptr); style.sourceImageHandle = std::move(image); command(std::move(style), {}); } Rectangle RenderPipeline::globalScissor() const { + ensureOnRenderThread(); return m_globalScissor; } diff --git a/src/graphics/WebGpuRenderer/ImageBackend.cpp b/src/graphics/WebGpuRenderer/ImageBackend.cpp index 472a90b6..667b57cb 100644 --- a/src/graphics/WebGpuRenderer/ImageBackend.cpp +++ b/src/graphics/WebGpuRenderer/ImageBackend.cpp @@ -26,6 +26,7 @@ namespace Brisk { ImageBackendWebGpu* getOrCreateBackend(Rc device, Rc image, bool uploadImage, bool renderTarget) { + ensureOnRenderThread(); if (!image) return nullptr; Internal::ImageBackend* imageBackend = Internal::getBackend(image); @@ -41,6 +42,7 @@ ImageBackendWebGpu* getOrCreateBackend(Rc device, Rc ImageBackendWebGpu::ImageBackendWebGpu(Rc device, Image* image, bool uploadImage, bool renderTarget) : m_device(std::move(device)), m_image(image) { + ensureOnRenderThread(); Size size = image->size(); wgpu::TextureUsage usage = wgpu::TextureUsage::TextureBinding | wgpu::TextureUsage::CopyDst; @@ -62,22 +64,26 @@ ImageBackendWebGpu::ImageBackendWebGpu(Rc device, Image* ima } void ImageBackendWebGpu::begin(AccessMode mode, Rectangle rect) { + ensureOnRenderThread(); if (mode != AccessMode::W) { readFromGpu(m_image->data().subrect(rect), rect.p1); } } void ImageBackendWebGpu::end(AccessMode mode, Rectangle rect) { + ensureOnRenderThread(); if (mode != AccessMode::R) { writeToGpu(m_image->data().subrect(rect), rect.p1); } } void ImageBackendWebGpu::invalidate() { + ensureOnRenderThread(); m_invalidated = true; } void ImageBackendWebGpu::readFromGpu(const ImageData& data, Point origin) { + ensureOnRenderThread(); constexpr int wgpuBufferAlignment = 256; wgpu::BufferDescriptor bufDesc{}; bufDesc.usage = wgpu::BufferUsage::CopyDst | wgpu::BufferUsage::MapRead; @@ -135,6 +141,7 @@ void ImageBackendWebGpu::readFromGpu(const ImageData& data, Point } void ImageBackendWebGpu::writeToGpu(const ImageData& data, Point origin) { + ensureOnRenderThread(); wgpu::TexelCopyTextureInfo destination; destination.texture = m_texture; destination.origin.x = origin.x; diff --git a/src/graphics/WebGpuRenderer/ImageRenderTarget.cpp b/src/graphics/WebGpuRenderer/ImageRenderTarget.cpp index 4d454dd0..b66a5934 100644 --- a/src/graphics/WebGpuRenderer/ImageRenderTarget.cpp +++ b/src/graphics/WebGpuRenderer/ImageRenderTarget.cpp @@ -29,6 +29,7 @@ ImageRenderTargetWebGpu::ImageRenderTargetWebGpu(Rc device, PixelType type, DepthStencilType depthStencil, int samples) : m_device(std::move(device)), m_frameSize(frameSize), m_type(type), m_depthStencilType(depthStencil), m_samples(samples) { + ensureOnRenderThread(); if (!updateImage()) { return; @@ -38,6 +39,7 @@ ImageRenderTargetWebGpu::ImageRenderTargetWebGpu(Rc device, ImageRenderTargetWebGpu::~ImageRenderTargetWebGpu() = default; bool ImageRenderTargetWebGpu::updateImage() { + ensureOnRenderThread(); m_image = rcnew Image(m_frameSize, imageFormat(m_type, format)); ImageBackendWebGpu* backend = getOrCreateBackend(m_device, m_image, false, true); m_backBuffer.color = backend->m_texture; @@ -48,10 +50,12 @@ bool ImageRenderTargetWebGpu::updateImage() { } Size ImageRenderTargetWebGpu::size() const { + ensureOnRenderThread(); return m_frameSize; } void ImageRenderTargetWebGpu::setSize(Size newSize) { + ensureOnRenderThread(); m_frameSize = newSize; if (!updateImage()) { return; @@ -59,6 +63,7 @@ void ImageRenderTargetWebGpu::setSize(Size newSize) { } Rc ImageRenderTargetWebGpu::image(bool reset) const { + ensureOnRenderThread(); Rc image = m_image; if (reset) { const_cast(this)->updateImage(); diff --git a/src/graphics/WebGpuRenderer/RenderDevice.cpp b/src/graphics/WebGpuRenderer/RenderDevice.cpp index 020b4395..7229c060 100644 --- a/src/graphics/WebGpuRenderer/RenderDevice.cpp +++ b/src/graphics/WebGpuRenderer/RenderDevice.cpp @@ -65,6 +65,7 @@ static void storeCached(const void* key, size_t keySize, const void* value, size } bool RenderDeviceWebGpu::createDevice() { + ensureOnRenderThread(); const char* instanceToggles[] = { "allow_unsafe_apis", @@ -207,9 +208,12 @@ bool RenderDeviceWebGpu::createDevice() { } RenderDeviceWebGpu::RenderDeviceWebGpu(RendererDeviceSelection deviceSelection, NativeDisplayHandle display) - : m_deviceSelection(deviceSelection), m_display(display) {} + : m_deviceSelection(deviceSelection), m_display(display) { + ensureOnRenderThread(); +} status RenderDeviceWebGpu::init() { + ensureOnRenderThread(); if (!createDevice()) { return unexpected(RenderDeviceError::Unsupported); } @@ -341,6 +345,7 @@ status RenderDeviceWebGpu::init() { wgpu::RenderPipeline RenderDeviceWebGpu::createPipeline(wgpu::TextureFormat renderFormat, bool dualSourceBlending) { + ensureOnRenderThread(); if (auto it = m_pipelineCache.find(std::make_tuple(renderFormat, dualSourceBlending)); it != m_pipelineCache.end()) { return it->second; @@ -376,6 +381,7 @@ wgpu::RenderPipeline RenderDeviceWebGpu::createPipeline(wgpu::TextureFormat rend } void RenderDeviceWebGpu::createSamplers() { + ensureOnRenderThread(); { wgpu::TextureDescriptor desc{ .label = "DummyTexture", @@ -411,6 +417,7 @@ static const std::string_view wgpuBackends[] = { }; RenderDeviceInfo RenderDeviceWebGpu::info() const { + ensureOnRenderThread(); wgpu::AdapterInfo props; m_adapter.GetInfo(&props); RenderDeviceInfo info; @@ -423,11 +430,13 @@ RenderDeviceInfo RenderDeviceWebGpu::info() const { Rc RenderDeviceWebGpu::createWindowTarget(const NativeWindow* window, PixelType type, DepthStencilType depthStencil, int samples) { + ensureOnRenderThread(); return rcnew WindowRenderTargetWebGpu(shared_from_this(), window, type, depthStencil, samples); } Rc RenderDeviceWebGpu::createImageTarget(Size frameSize, PixelType type, DepthStencilType depthStencil, int samples) { + ensureOnRenderThread(); if (frameSize.longestSide() >= 16384) { throwException(EImageError("Requested image render target size is too large: {}", frameSize)); } @@ -435,10 +444,12 @@ Rc RenderDeviceWebGpu::createImageTarget(Size frameSize, Pixe } Rc RenderDeviceWebGpu::createEncoder() { + ensureOnRenderThread(); return rcnew RenderEncoderWebGpu(shared_from_this()); } RenderDeviceWebGpu::~RenderDeviceWebGpu() { + ensureOnRenderThread(); m_resources.reset(); m_pipelineCache = {}; m_device = nullptr; @@ -463,11 +474,13 @@ RenderDeviceWebGpu::~RenderDeviceWebGpu() { bool RenderDeviceWebGpu::updateBackBuffer(BackBufferWebGpu& buffer, PixelType type, DepthStencilType depthType, int samples) { + ensureOnRenderThread(); buffer.colorView = buffer.color.CreateView(); return true; } void RenderDeviceWebGpu::wait() { + ensureOnRenderThread(); wgpu::FutureWaitInfo future; future.future = m_device.GetQueue().OnSubmittedWorkDone(wgpu::CallbackMode::AllowProcessEvents, [](wgpu::QueueWorkDoneStatus status) {}); @@ -477,6 +490,7 @@ void RenderDeviceWebGpu::wait() { } void RenderDeviceWebGpu::createImageBackend(Rc image) { + ensureOnRenderThread(); BRISK_ASSERT(image); if (wgFormat(image->pixelType(), image->pixelFormat()) == wgpu::TextureFormat::Undefined) { throwException(EImageError("WebGPU backend does not support the image type or format: {}, {}. " diff --git a/src/graphics/WebGpuRenderer/RenderEncoder.cpp b/src/graphics/WebGpuRenderer/RenderEncoder.cpp index 73b8afb9..f7ecb7be 100644 --- a/src/graphics/WebGpuRenderer/RenderEncoder.cpp +++ b/src/graphics/WebGpuRenderer/RenderEncoder.cpp @@ -34,10 +34,12 @@ VisualSettings RenderEncoderWebGpu::visualSettings() const { } void RenderEncoderWebGpu::setVisualSettings(const VisualSettings& visualSettings) { + ensureOnRenderThread(); m_visualSettings = visualSettings; } void RenderEncoderWebGpu::begin(Rc target, std::optional clear) { + ensureOnRenderThread(); BRISK_ASSERT(static_cast(m_currentTarget == nullptr)); BRISK_ASSERT(static_cast(!m_queue.Get())); m_currentTarget = std::move(target); @@ -75,6 +77,7 @@ void RenderEncoderWebGpu::begin(Rc target, std::optional c } void RenderEncoderWebGpu::end() { + ensureOnRenderThread(); BRISK_ASSERT(m_currentTarget); BRISK_ASSERT(m_queue.Get()); m_queue = nullptr; @@ -82,6 +85,7 @@ void RenderEncoderWebGpu::end() { } void RenderEncoderWebGpu::batch(std::span commands, std::span data) { + ensureOnRenderThread(); BRISK_ASSERT(m_currentTarget); BRISK_ASSERT(m_queue.Get()); @@ -161,6 +165,7 @@ void RenderEncoderWebGpu::batch(std::span commands, std::span wgpu::BindGroup RenderEncoderWebGpu::createBindGroup(ImageBackendWebGpu* sourceImage, ImageBackendWebGpu* backImage) { + ensureOnRenderThread(); std::array entries = { wgpu::BindGroupEntry{ @@ -210,10 +215,12 @@ wgpu::BindGroup RenderEncoderWebGpu::createBindGroup(ImageBackendWebGpu* sourceI } void RenderEncoderWebGpu::wait() { + ensureOnRenderThread(); m_device->wait(); } void RenderEncoderWebGpu::updatePerFrameConstantBuffer(const ConstantPerFrame& constants) { + ensureOnRenderThread(); if (!m_perFrameConstantBuffer) { wgpu::BufferDescriptor desc{ .label = "PerFrameConstantBuffer", @@ -227,6 +234,7 @@ void RenderEncoderWebGpu::updatePerFrameConstantBuffer(const ConstantPerFrame& c } void RenderEncoderWebGpu::updateConstantBuffer(std::span data) { + ensureOnRenderThread(); if (!m_constantBuffer || m_constantBuffer.GetSize() != data.size_bytes()) { wgpu::BufferDescriptor desc{ .label = "ConstantBuffer", @@ -241,6 +249,7 @@ void RenderEncoderWebGpu::updateConstantBuffer(std::span data // Update the data buffer and possibly recreate it. void RenderEncoderWebGpu::updateDataBuffer(std::span data) { + ensureOnRenderThread(); size_t alignedDataSize = std::max(data.size_bytes(), size_t(16)); if (!m_dataBuffer || alignedDataSize > m_dataBuffer.GetSize()) { wgpu::BufferDescriptor desc{ @@ -256,6 +265,7 @@ void RenderEncoderWebGpu::updateDataBuffer(std::span data) { } void RenderEncoderWebGpu::updateAtlasTexture() { + ensureOnRenderThread(); SpriteAtlas* atlas = m_device->m_resources.spriteAtlas.get(); Size newSize(Internal::max2DTextureSize, atlas->data().size() / Internal::max2DTextureSize); @@ -285,6 +295,7 @@ void RenderEncoderWebGpu::updateAtlasTexture() { } void RenderEncoderWebGpu::updateGradientTexture() { + ensureOnRenderThread(); GradientAtlas* gradAtlas = m_device->m_resources.gradientAtlas.get(); Size newSize(sizeof(GradientData) / sizeof(Simd), gradAtlas->data().size()); if (!m_gradientTexture || (m_gradient_generation <<= gradAtlas->changed)) { @@ -314,13 +325,17 @@ void RenderEncoderWebGpu::updateGradientTexture() { } } -RenderEncoderWebGpu::RenderEncoderWebGpu(Rc device) : m_device(std::move(device)) {} +RenderEncoderWebGpu::RenderEncoderWebGpu(Rc device) : m_device(std::move(device)) { + ensureOnRenderThread(); +} RenderEncoderWebGpu::~RenderEncoderWebGpu() { + ensureOnRenderThread(); m_device->m_instance.ProcessEvents(); } size_t RenderEncoderWebGpu::findFrameTimingSlot() { + ensureOnRenderThread(); for (size_t i = 0; i < m_frameTiming.size(); ++i) { if (!m_frameTiming[i].pending) { m_frameTiming[i].pending = true; @@ -334,6 +349,7 @@ size_t RenderEncoderWebGpu::findFrameTimingSlot() { } void RenderEncoderWebGpu::beginFrame(uint64_t frameId) { + ensureOnRenderThread(); if (m_device->m_timestampQuerySupported) { m_frameId = frameId; m_timestampIndex = 0; @@ -342,6 +358,7 @@ void RenderEncoderWebGpu::beginFrame(uint64_t frameId) { } void RenderEncoderWebGpu::endFrame(DurationCallback callback) { + ensureOnRenderThread(); if (m_device->m_timestampQuerySupported && m_timestampIndex > 0) { BRISK_ASSERT(m_frameTimingIndex < m_frameTiming.size()); FrameTiming& timing = m_frameTiming[m_frameTimingIndex]; @@ -396,10 +413,12 @@ void RenderEncoderWebGpu::endFrame(DurationCallback callback) { } Rc RenderEncoderWebGpu::currentTarget() const { + ensureOnRenderThread(); return m_currentTarget; } RenderEncoderWebGpu::FrameTiming::FrameTiming(wgpu::Device& device) { + ensureOnRenderThread(); wgpu::QuerySetDescriptor querySetDesc{}; querySetDesc.type = wgpu::QueryType::Timestamp; querySetDesc.count = RenderEncoderWebGpu::maxTimestamps; @@ -421,6 +440,7 @@ RenderEncoderWebGpu::FrameTiming::FrameTiming(wgpu::Device& device) { } const BackBufferWebGpu& getBackBuffer(RenderTarget* target) { + ensureOnRenderThread(); switch (target->type()) { case RenderTargetType::Window: return static_cast(target)->getBackBuffer(); diff --git a/src/graphics/WebGpuRenderer/Renderer.cpp b/src/graphics/WebGpuRenderer/Renderer.cpp index c94b77c0..9cc19af8 100644 --- a/src/graphics/WebGpuRenderer/Renderer.cpp +++ b/src/graphics/WebGpuRenderer/Renderer.cpp @@ -25,6 +25,7 @@ namespace Brisk { expected, RenderDeviceError> createRenderDeviceWebGpu( RendererDeviceSelection deviceSelection, NativeDisplayHandle display) { + ensureOnRenderThread(); Rc device(new RenderDeviceWebGpu(deviceSelection, display)); auto status = device->init(); if (!status) diff --git a/src/graphics/WebGpuRenderer/WindowRenderTarget.cpp b/src/graphics/WebGpuRenderer/WindowRenderTarget.cpp index d7180866..1d465743 100644 --- a/src/graphics/WebGpuRenderer/WindowRenderTarget.cpp +++ b/src/graphics/WebGpuRenderer/WindowRenderTarget.cpp @@ -26,6 +26,7 @@ WindowRenderTargetWebGpu::WindowRenderTargetWebGpu(Rc device PixelType type, DepthStencilType depthStencil, int samples) : m_device(std::move(device)), m_window(window), m_type(type), m_depthStencilFmt(depthStencil), m_samples(samples) { + ensureOnRenderThread(); createSurface(window); @@ -34,6 +35,7 @@ WindowRenderTargetWebGpu::WindowRenderTargetWebGpu(Rc device } void WindowRenderTargetWebGpu::setVSyncInterval(int interval) { + ensureOnRenderThread(); if (interval != m_vsyncInterval) { m_vsyncInterval = interval; recreateSwapChain(); @@ -41,11 +43,13 @@ void WindowRenderTargetWebGpu::setVSyncInterval(int interval) { } void WindowRenderTargetWebGpu::present() { + ensureOnRenderThread(); m_surface.Present(); m_device->m_instance.ProcessEvents(); } void WindowRenderTargetWebGpu::recreateSwapChain() { + ensureOnRenderThread(); m_backBuffer = {}; wgpu::SurfaceConfiguration swapChainDesc{ .device = m_device->m_device, @@ -60,6 +64,7 @@ void WindowRenderTargetWebGpu::recreateSwapChain() { } void WindowRenderTargetWebGpu::resizeBackbuffer(Size size) { + ensureOnRenderThread(); if (size.longestSide() >= 16384) { throwException(EImageError("Requested window framebuffer size is too large: {}", size)); } @@ -70,14 +75,17 @@ void WindowRenderTargetWebGpu::resizeBackbuffer(Size size) { } Size WindowRenderTargetWebGpu::size() const { + ensureOnRenderThread(); return m_window->framebufferSize(); } int WindowRenderTargetWebGpu::vsyncInterval() const { + ensureOnRenderThread(); return m_vsyncInterval; } const BackBufferWebGpu& WindowRenderTargetWebGpu::getBackBuffer() const { + ensureOnRenderThread(); wgpu::SurfaceTexture surfaceTexture; m_surface.GetCurrentTexture(&surfaceTexture); m_backBuffer.color = surfaceTexture.texture; diff --git a/src/graphics/WebGpuRenderer/WindowRenderTarget_Darwin.mm b/src/graphics/WebGpuRenderer/WindowRenderTarget_Darwin.mm index 2dae6828..2212bf94 100644 --- a/src/graphics/WebGpuRenderer/WindowRenderTarget_Darwin.mm +++ b/src/graphics/WebGpuRenderer/WindowRenderTarget_Darwin.mm @@ -27,10 +27,18 @@ namespace Brisk { void WindowRenderTargetWebGpu::createSurface(const NativeWindow* window) { + ensureOnRenderThread(); @autoreleasepool { NativeWindowHandle handle = window->getHandle(); - NSWindow* nsWindow = handle.nsWindow(); - NSView* view = [nsWindow contentView]; + NSWindow* nsWindow; + NSView* view; + if (handle.isNsWindow()) { + nsWindow = handle.nsWindow(); + view = [nsWindow contentView]; + } else { + nsWindow = handle.nsWindow(); + view = handle.nsView(); + } [view setWantsLayer:YES]; [view setLayer:[CAMetalLayer layer]]; diff --git a/src/graphics/WebGpuRenderer/WindowRenderTarget_Linux.cpp b/src/graphics/WebGpuRenderer/WindowRenderTarget_Linux.cpp index 8ba80b4c..5e15335a 100644 --- a/src/graphics/WebGpuRenderer/WindowRenderTarget_Linux.cpp +++ b/src/graphics/WebGpuRenderer/WindowRenderTarget_Linux.cpp @@ -31,6 +31,7 @@ namespace Brisk { void WindowRenderTargetWebGpu::createSurface(const NativeWindow* window) { + ensureOnRenderThread(); NativeWindowHandle handle = window->getHandle(); wgpu::SurfaceDescriptor surfaceDesc; diff --git a/src/graphics/WebGpuRenderer/WindowRenderTarget_Windows.cpp b/src/graphics/WebGpuRenderer/WindowRenderTarget_Windows.cpp index 5047834d..70fd54aa 100644 --- a/src/graphics/WebGpuRenderer/WindowRenderTarget_Windows.cpp +++ b/src/graphics/WebGpuRenderer/WindowRenderTarget_Windows.cpp @@ -25,6 +25,7 @@ namespace Brisk { void WindowRenderTargetWebGpu::createSurface(const NativeWindow* window) { + ensureOnRenderThread(); NativeWindowHandle handle = window->getHandle(); wgpu::SurfaceDescriptor surfaceDesc; diff --git a/src/graphics/blaze/BumpAllocator.cpp b/src/graphics/blaze/BumpAllocator.cpp index fe0b5969..861fc5c9 100644 --- a/src/graphics/blaze/BumpAllocator.cpp +++ b/src/graphics/blaze/BumpAllocator.cpp @@ -1,12 +1,10 @@ #include "BumpAllocator.h" - namespace Blaze { static constexpr int kMinimumMasterBlockSize = 1024 * 128; - /** * Returns block allocation size aligned to 32 kilobyte boundary. */ @@ -18,16 +16,14 @@ static int RoundUpBlockSize(const int size) { return m & ~32767; } - BumpAllocator::~BumpAllocator() { FreeBlockChain(mMasterActiveList); FreeBlockChain(mMasterFreeList); } - -void BumpAllocator::FreeBlockChain(Block *block) { +void BumpAllocator::FreeBlockChain(Block* block) { while (block != nullptr) { - Block *next = block->Next; + Block* next = block->Next; free(block->Bytes); free(block); @@ -36,26 +32,25 @@ void BumpAllocator::FreeBlockChain(Block *block) { } } - -void *BumpAllocator::MallocFromNewBlock(const int size) { +void* BumpAllocator::MallocFromNewBlock(const int size) { BLAZE_ASSERT(size > 0); - Block **ptr = &mMasterFreeList; + Block** ptr = &mMasterFreeList; while ((*ptr) != nullptr) { - Block *b = (*ptr); + Block* b = (*ptr); BLAZE_ASSERT(b->Position == 0); if (b->BlockSize >= size) { - (*ptr) = b->Next; + (*ptr) = b->Next; // Block is large enough. Remove from free list and insert to // active block list. - void *p = b->Bytes; + void* p = b->Bytes; - b->Position = RoundUpAllocationSizeForNextAllocation(size); - b->Next = mMasterActiveList; + b->Position = RoundUpAllocationSizeForNextAllocation(size); + b->Next = mMasterActiveList; mMasterActiveList = b; @@ -66,40 +61,39 @@ void *BumpAllocator::MallocFromNewBlock(const int size) { } // A new block is needed. - Block *block = reinterpret_cast(malloc(sizeof(Block))); + Block* block = reinterpret_cast(malloc(sizeof(Block))); block->BlockSize = Max(kMinimumMasterBlockSize, RoundUpBlockSize(size)); - block->Bytes = reinterpret_cast(malloc(block->BlockSize)); + block->Bytes = reinterpret_cast(malloc(block->BlockSize)); BLAZE_ASSERT(block->Bytes != nullptr); // Assign position to allocation size because we will return base pointer // later without adjusting current position. - block->Position = RoundUpAllocationSizeForNextAllocation(size); + block->Position = RoundUpAllocationSizeForNextAllocation(size); // Insert to main list. - block->Next = mMasterActiveList; + block->Next = mMasterActiveList; mMasterActiveList = block; return block->Bytes; } - void BumpAllocator::Free() { - Block *b = mMasterActiveList; + Block* b = mMasterActiveList; while (b != nullptr) { - Block *next = b->Next; + Block* next = b->Next; - b->Next = mMasterFreeList; - b->Position = 0; + b->Next = mMasterFreeList; + b->Position = 0; mMasterFreeList = b; - b = next; + b = next; } mMasterActiveList = nullptr; diff --git a/src/graphics/blaze/CurveUtils.cpp b/src/graphics/blaze/CurveUtils.cpp index 9399b60d..1ab87f8d 100644 --- a/src/graphics/blaze/CurveUtils.cpp +++ b/src/graphics/blaze/CurveUtils.cpp @@ -1,10 +1,9 @@ #include "CurveUtils.h" - namespace Blaze { -static int AcceptRoot(Float *t, const Float root) { +static int AcceptRoot(Float* t, const Float root) { if (root < -BLAZE_EPSILON) { return 0; } else if (root > (1.0 + BLAZE_EPSILON)) { @@ -16,9 +15,7 @@ static int AcceptRoot(Float *t, const Float root) { return 1; } - -int FindQuadraticRoots( - const Float a, const Float b, const Float c, Float roots[2]) { +int FindQuadraticRoots(const Float a, const Float b, const Float c, Float roots[2]) { BLAZE_ASSERT(roots != nullptr); const Float delta = b * b - 4.0 * a * c; @@ -28,8 +25,8 @@ int FindQuadraticRoots( } if (delta > 0.0) { - const Float d = Sqrt(delta); - const Float q = -0.5 * (b + (b < 0.0 ? -d : d)); + const Float d = Sqrt(delta); + const Float q = -0.5 * (b + (b < 0.0 ? -d : d)); const Float rv0 = q / a; const Float rv1 = c / q; @@ -59,8 +56,7 @@ int FindQuadraticRoots( return 0; } - -static int AcceptRootWithin(Float *t, const Float root) { +static int AcceptRootWithin(Float* t, const Float root) { if (root <= BLAZE_EPSILON) { return 0; } else if (root >= (1.0 - BLAZE_EPSILON)) { @@ -72,9 +68,7 @@ static int AcceptRootWithin(Float *t, const Float root) { return 1; } - -static int FindQuadraticRootsWithin( - const Float a, const Float b, const Float c, Float roots[2]) { +static int FindQuadraticRootsWithin(const Float a, const Float b, const Float c, Float roots[2]) { BLAZE_ASSERT(roots != nullptr); const Float delta = b * b - 4.0 * a * c; @@ -84,8 +78,8 @@ static int FindQuadraticRootsWithin( } if (delta > 0.0) { - const Float d = Sqrt(delta); - const Float q = -0.5 * (b + (b < 0.0 ? -d : d)); + const Float d = Sqrt(delta); + const Float q = -0.5 * (b + (b < 0.0 ? -d : d)); const Float rv0 = q / a; const Float rv1 = c / q; @@ -115,11 +109,9 @@ static int FindQuadraticRootsWithin( return 0; } - -bool FindQuadraticExtrema( - const Float a, const Float b, const Float c, Float &t) { +bool FindQuadraticExtrema(const Float a, const Float b, const Float c, Float& t) { const Float aMinusB = a - b; - const Float d = aMinusB - b + c; + const Float d = aMinusB - b + c; if (aMinusB == 0 || d == 0) { return false; @@ -138,9 +130,7 @@ bool FindQuadraticExtrema( return true; } - -int FindCubicExtrema( - const Float a, const Float b, const Float c, const Float d, Float t[2]) { +int FindCubicExtrema(const Float a, const Float b, const Float c, const Float d, Float t[2]) { const Float A = d - a + 3.0 * (b - c); const Float B = 2.0 * (a - b - b + c); const Float C = b - a; @@ -148,7 +138,6 @@ int FindCubicExtrema( return FindQuadraticRootsWithin(A, B, C, t); } - int CutCubicAtYExtrema(const FloatPoint src[4], FloatPoint dst[10]) { BLAZE_ASSERT(src != nullptr); BLAZE_ASSERT(dst != nullptr); @@ -168,8 +157,8 @@ int CutCubicAtYExtrema(const FloatPoint src[4], FloatPoint dst[10]) { // Make sure curve tangents at extrema are horizontal. const Float y = dst[3].Y; - dst[2].Y = y; - dst[4].Y = y; + dst[2].Y = y; + dst[4].Y = y; return 2; } @@ -188,9 +177,9 @@ int CutCubicAtYExtrema(const FloatPoint src[4], FloatPoint dst[10]) { CutCubicAt(src, tmp, t[0]); - dst[0] = tmp[0]; - dst[1] = tmp[1]; - dst[2] = tmp[2]; + dst[0] = tmp[0]; + dst[1] = tmp[1]; + dst[2] = tmp[2]; const Float d = 1.0 - t[0]; @@ -206,10 +195,10 @@ int CutCubicAtYExtrema(const FloatPoint src[4], FloatPoint dst[10]) { const Float y0 = dst[3].Y; const Float y1 = dst[6].Y; - dst[2].Y = y0; - dst[4].Y = y0; - dst[5].Y = y1; - dst[7].Y = y1; + dst[2].Y = y0; + dst[4].Y = y0; + dst[5].Y = y1; + dst[7].Y = y1; return 3; } @@ -224,7 +213,6 @@ int CutCubicAtYExtrema(const FloatPoint src[4], FloatPoint dst[10]) { return 1; } - int CutCubicAtXExtrema(const FloatPoint src[4], FloatPoint dst[10]) { BLAZE_ASSERT(src != nullptr); BLAZE_ASSERT(dst != nullptr); @@ -244,8 +232,8 @@ int CutCubicAtXExtrema(const FloatPoint src[4], FloatPoint dst[10]) { // Make sure curve tangents at extrema are horizontal. const Float x = dst[3].X; - dst[2].X = x; - dst[4].X = x; + dst[2].X = x; + dst[4].X = x; return 2; } @@ -264,9 +252,9 @@ int CutCubicAtXExtrema(const FloatPoint src[4], FloatPoint dst[10]) { CutCubicAt(src, tmp, t[0]); - dst[0] = tmp[0]; - dst[1] = tmp[1]; - dst[2] = tmp[2]; + dst[0] = tmp[0]; + dst[1] = tmp[1]; + dst[2] = tmp[2]; const Float d = 1.0 - t[0]; @@ -282,10 +270,10 @@ int CutCubicAtXExtrema(const FloatPoint src[4], FloatPoint dst[10]) { const Float x0 = dst[3].X; const Float x1 = dst[6].X; - dst[2].X = x0; - dst[4].X = x0; - dst[5].X = x1; - dst[7].X = x1; + dst[2].X = x0; + dst[4].X = x0; + dst[5].X = x1; + dst[7].X = x1; return 3; } @@ -300,10 +288,9 @@ int CutCubicAtXExtrema(const FloatPoint src[4], FloatPoint dst[10]) { return 1; } - static bool IsQuadraticMonotonic(const Float a, const Float b, const Float c) { const Float ab = a - b; - Float bc = b - c; + Float bc = b - c; if (ab < 0) { bc = -bc; @@ -312,7 +299,6 @@ static bool IsQuadraticMonotonic(const Float a, const Float b, const Float c) { return ab != 0 && bc >= 0; } - int CutQuadraticAtYExtrema(const FloatPoint src[3], FloatPoint dst[5]) { const Float a = src[0].Y; const Float b = src[1].Y; @@ -333,8 +319,8 @@ int CutQuadraticAtYExtrema(const FloatPoint src[3], FloatPoint dst[5]) { const Float y = dst[2].Y; - dst[1].Y = y; - dst[3].Y = y; + dst[1].Y = y; + dst[3].Y = y; return 2; } @@ -348,7 +334,6 @@ int CutQuadraticAtYExtrema(const FloatPoint src[3], FloatPoint dst[5]) { return 1; } - int CutQuadraticAtXExtrema(const FloatPoint src[3], FloatPoint dst[5]) { const Float a = src[0].X; const Float b = src[1].X; @@ -369,8 +354,8 @@ int CutQuadraticAtXExtrema(const FloatPoint src[3], FloatPoint dst[5]) { const Float x = dst[2].X; - dst[1].X = x; - dst[3].X = x; + dst[1].X = x; + dst[3].X = x; return 2; } diff --git a/src/graphics/blaze/LineBlockAllocator.cpp b/src/graphics/blaze/LineBlockAllocator.cpp index d8c4664c..70fc16c1 100644 --- a/src/graphics/blaze/LineBlockAllocator.cpp +++ b/src/graphics/blaze/LineBlockAllocator.cpp @@ -1,14 +1,13 @@ #include "LineBlockAllocator.h" - namespace Blaze { LineBlockAllocator::~LineBlockAllocator() { - Arena *p = mAllArenas; + Arena* p = mAllArenas; while (p != nullptr) { - Arena *next = p->Links.NextAll; + Arena* next = p->Links.NextAll; free(p); @@ -16,47 +15,44 @@ LineBlockAllocator::~LineBlockAllocator() { } } - void LineBlockAllocator::Clear() { - Arena *l = nullptr; + Arena* l = nullptr; - Arena *p = mAllArenas; + Arena* p = mAllArenas; while (p != nullptr) { - Arena *next = p->Links.NextAll; + Arena* next = p->Links.NextAll; p->Links.NextFree = l; - l = p; + l = p; - p = next; + p = next; } - mCurrent = nullptr; - mEnd = nullptr; + mCurrent = nullptr; + mEnd = nullptr; mFreeArenas = l; } - void LineBlockAllocator::NewArena() { - Arena *p = mFreeArenas; + Arena* p = mFreeArenas; if (p != nullptr) { mFreeArenas = p->Links.NextFree; } else { - p = static_cast(malloc(sizeof(Arena))); + p = static_cast(malloc(sizeof(Arena))); p->Links.NextAll = mAllArenas; - mAllArenas = p; + mAllArenas = p; } p->Links.NextFree = nullptr; - mCurrent = p->Memory + sizeof(Arena::Links); - mEnd = p->Memory + Arena::Size - - Max3(sizeof(LineArrayX32Y16Block), sizeof(LineArrayX16Y16Block), - sizeof(LineArrayTiledBlock)); + mCurrent = p->Memory + sizeof(Arena::Links); + mEnd = p->Memory + Arena::Size - + Max3(sizeof(LineArrayX32Y16Block), sizeof(LineArrayX16Y16Block), sizeof(LineArrayTiledBlock)); } } // namespace Blaze \ No newline at end of file diff --git a/src/graphics/blaze/Matrix.cpp b/src/graphics/blaze/Matrix.cpp index dce0024f..909c2c43 100644 --- a/src/graphics/blaze/Matrix.cpp +++ b/src/graphics/blaze/Matrix.cpp @@ -5,29 +5,21 @@ namespace Blaze { const Matrix Matrix::Identity; +Matrix::Matrix(const Matrix& matrix1, const Matrix& matrix2) { + m[0][0] = matrix2.m[0][0] * matrix1.m[0][0] + matrix2.m[0][1] * matrix1.m[1][0]; -Matrix::Matrix(const Matrix &matrix1, const Matrix &matrix2) { - m[0][0] = - matrix2.m[0][0] * matrix1.m[0][0] + matrix2.m[0][1] * matrix1.m[1][0]; + m[0][1] = matrix2.m[0][0] * matrix1.m[0][1] + matrix2.m[0][1] * matrix1.m[1][1]; - m[0][1] = - matrix2.m[0][0] * matrix1.m[0][1] + matrix2.m[0][1] * matrix1.m[1][1]; + m[1][0] = matrix2.m[1][0] * matrix1.m[0][0] + matrix2.m[1][1] * matrix1.m[1][0]; - m[1][0] = - matrix2.m[1][0] * matrix1.m[0][0] + matrix2.m[1][1] * matrix1.m[1][0]; + m[1][1] = matrix2.m[1][0] * matrix1.m[0][1] + matrix2.m[1][1] * matrix1.m[1][1]; - m[1][1] = - matrix2.m[1][0] * matrix1.m[0][1] + matrix2.m[1][1] * matrix1.m[1][1]; + m[2][0] = matrix2.m[2][0] * matrix1.m[0][0] + matrix2.m[2][1] * matrix1.m[1][0] + matrix1.m[2][0]; - m[2][0] = matrix2.m[2][0] * matrix1.m[0][0] + - matrix2.m[2][1] * matrix1.m[1][0] + matrix1.m[2][0]; - - m[2][1] = matrix2.m[2][0] * matrix1.m[0][1] + - matrix2.m[2][1] * matrix1.m[1][1] + matrix1.m[2][1]; + m[2][1] = matrix2.m[2][0] * matrix1.m[0][1] + matrix2.m[2][1] * matrix1.m[1][1] + matrix1.m[2][1]; } - -Matrix::Matrix(const FloatPoint &translation) { +Matrix::Matrix(const FloatPoint& translation) { m[0][0] = 1; m[0][1] = 0; m[1][0] = 0; @@ -36,7 +28,6 @@ Matrix::Matrix(const FloatPoint &translation) { m[2][1] = translation.Y; } - Matrix Matrix::CreateRotation(const Float degrees) { if (FuzzyIsZero(degrees)) { return Matrix::Identity; @@ -55,26 +46,23 @@ Matrix Matrix::CreateRotation(const Float degrees) { // Arbitrary rotation. const Float radians = Deg2Rad(degrees); - c = Cos(radians); - s = Sin(radians); + c = Cos(radians); + s = Sin(radians); } return Matrix(c, s, -s, c, 0, 0); } - -Matrix Matrix::Lerp( - const Matrix &matrix1, const Matrix &matrix2, const Float t) { +Matrix Matrix::Lerp(const Matrix& matrix1, const Matrix& matrix2, const Float t) { return Matrix(matrix1.m[0][0] + (matrix2.m[0][0] - matrix1.m[0][0]) * t, - matrix1.m[0][1] + (matrix2.m[0][1] - matrix1.m[0][1]) * t, - matrix1.m[1][0] + (matrix2.m[1][0] - matrix1.m[1][0]) * t, - matrix1.m[1][1] + (matrix2.m[1][1] - matrix1.m[1][1]) * t, - matrix1.m[2][0] + (matrix2.m[2][0] - matrix1.m[2][0]) * t, - matrix1.m[2][1] + (matrix2.m[2][1] - matrix1.m[2][1]) * t); + matrix1.m[0][1] + (matrix2.m[0][1] - matrix1.m[0][1]) * t, + matrix1.m[1][0] + (matrix2.m[1][0] - matrix1.m[1][0]) * t, + matrix1.m[1][1] + (matrix2.m[1][1] - matrix1.m[1][1]) * t, + matrix1.m[2][0] + (matrix2.m[2][0] - matrix1.m[2][0]) * t, + matrix1.m[2][1] + (matrix2.m[2][1] - matrix1.m[2][1]) * t); } - -bool Matrix::Invert(Matrix &result) const { +bool Matrix::Invert(Matrix& result) const { const Float det = GetDeterminant(); if (FuzzyIsZero(det)) { @@ -82,14 +70,13 @@ bool Matrix::Invert(Matrix &result) const { return false; } - result = Matrix(m[1][1] / det, -m[0][1] / det, -m[1][0] / det, - m[0][0] / det, (m[1][0] * m[2][1] - m[1][1] * m[2][0]) / det, - (m[0][1] * m[2][0] - m[0][0] * m[2][1]) / det); + result = + Matrix(m[1][1] / det, -m[0][1] / det, -m[1][0] / det, m[0][0] / det, + (m[1][0] * m[2][1] - m[1][1] * m[2][0]) / det, (m[0][1] * m[2][0] - m[0][0] * m[2][1]) / det); return true; } - Matrix Matrix::Inverse() const { const Float det = GetDeterminant(); @@ -98,115 +85,90 @@ Matrix Matrix::Inverse() const { } return Matrix(m[1][1] / det, -m[0][1] / det, -m[1][0] / det, m[0][0] / det, - (m[1][0] * m[2][1] - m[1][1] * m[2][0]) / det, - (m[0][1] * m[2][0] - m[0][0] * m[2][1]) / det); + (m[1][0] * m[2][1] - m[1][1] * m[2][0]) / det, + (m[0][1] * m[2][0] - m[0][0] * m[2][1]) / det); } - -FloatRect Matrix::Map(const FloatRect &rect) const { - const FloatPoint topLeft = Map(rect.MinX, rect.MinY); - const FloatPoint topRight = Map(rect.MaxX, rect.MinY); - const FloatPoint bottomLeft = Map(rect.MinX, rect.MaxY); +FloatRect Matrix::Map(const FloatRect& rect) const { + const FloatPoint topLeft = Map(rect.MinX, rect.MinY); + const FloatPoint topRight = Map(rect.MaxX, rect.MinY); + const FloatPoint bottomLeft = Map(rect.MinX, rect.MaxY); const FloatPoint bottomRight = Map(rect.MaxX, rect.MaxY); - const Float minX = - Min(topLeft.X, Min(topRight.X, Min(bottomLeft.X, bottomRight.X))); - const Float maxX = - Max(topLeft.X, Max(topRight.X, Max(bottomLeft.X, bottomRight.X))); - const Float minY = - Min(topLeft.Y, Min(topRight.Y, Min(bottomLeft.Y, bottomRight.Y))); - const Float maxY = - Max(topLeft.Y, Max(topRight.Y, Max(bottomLeft.Y, bottomRight.Y))); + const Float minX = Min(topLeft.X, Min(topRight.X, Min(bottomLeft.X, bottomRight.X))); + const Float maxX = Max(topLeft.X, Max(topRight.X, Max(bottomLeft.X, bottomRight.X))); + const Float minY = Min(topLeft.Y, Min(topRight.Y, Min(bottomLeft.Y, bottomRight.Y))); + const Float maxY = Max(topLeft.Y, Max(topRight.Y, Max(bottomLeft.Y, bottomRight.Y))); return FloatRect(minX, minY, maxX - minX, maxY - minY); } - -IntRect Matrix::MapBoundingRect(const IntRect &rect) const { +IntRect Matrix::MapBoundingRect(const IntRect& rect) const { const FloatRect r = Map(rect); return r.ToExpandedIntRect(); } - bool Matrix::IsIdentity() const { // Look at diagonal elements first to return if scale is not 1. - return m[0][0] == 1 && m[1][1] == 1 && m[0][1] == 0 && m[1][0] == 0 && - m[2][0] == 0 && m[2][1] == 0; + return m[0][0] == 1 && m[1][1] == 1 && m[0][1] == 0 && m[1][0] == 0 && m[2][0] == 0 && m[2][1] == 0; } - -bool Matrix::IsEqual(const Matrix &matrix) const { - return FuzzyIsEqual(m[0][0], matrix.m[0][0]) && - FuzzyIsEqual(m[0][1], matrix.m[0][1]) && - FuzzyIsEqual(m[1][0], matrix.m[1][0]) && - FuzzyIsEqual(m[1][1], matrix.m[1][1]) && - FuzzyIsEqual(m[2][0], matrix.m[2][0]) && - FuzzyIsEqual(m[2][1], matrix.m[2][1]); +bool Matrix::IsEqual(const Matrix& matrix) const { + return FuzzyIsEqual(m[0][0], matrix.m[0][0]) && FuzzyIsEqual(m[0][1], matrix.m[0][1]) && + FuzzyIsEqual(m[1][0], matrix.m[1][0]) && FuzzyIsEqual(m[1][1], matrix.m[1][1]) && + FuzzyIsEqual(m[2][0], matrix.m[2][0]) && FuzzyIsEqual(m[2][1], matrix.m[2][1]); } - -void Matrix::PreTranslate(const FloatPoint &translation) { +void Matrix::PreTranslate(const FloatPoint& translation) { PreMultiply(Matrix(translation)); } - -void Matrix::PostTranslate(const FloatPoint &translation) { +void Matrix::PostTranslate(const FloatPoint& translation) { PostMultiply(Matrix(translation)); } - void Matrix::PreTranslate(const Float x, const Float y) { PreTranslate(FloatPoint{ x, y }); } - void Matrix::PostTranslate(const Float x, const Float y) { PostTranslate(FloatPoint{ x, y }); } - -void Matrix::PreScale(const FloatPoint &scale) { +void Matrix::PreScale(const FloatPoint& scale) { PreMultiply(CreateScale(scale)); } - -void Matrix::PostScale(const FloatPoint &scale) { +void Matrix::PostScale(const FloatPoint& scale) { PostMultiply(CreateScale(scale)); } - void Matrix::PreScale(const Float x, const Float y) { PreScale(FloatPoint{ x, y }); } - void Matrix::PostScale(const Float x, const Float y) { PostScale(FloatPoint{ x, y }); } - void Matrix::PreScale(const Float scale) { PreScale(FloatPoint{ scale, scale }); } - void Matrix::PostScale(const Float scale) { PostScale(FloatPoint{ scale, scale }); } - void Matrix::PreRotate(const Float degrees) { PreMultiply(CreateRotation(degrees)); } - void Matrix::PostRotate(const Float degrees) { PostMultiply(CreateRotation(degrees)); } - -void Matrix::PostMultiply(const Matrix &matrix) { +void Matrix::PostMultiply(const Matrix& matrix) { const Float m00 = m[0][0]; const Float m01 = m[0][1]; const Float m10 = m[1][0]; @@ -214,16 +176,15 @@ void Matrix::PostMultiply(const Matrix &matrix) { const Float m20 = m[2][0]; const Float m21 = m[2][1]; - m[0][0] = matrix.m[0][0] * m00 + matrix.m[0][1] * m10; - m[0][1] = matrix.m[0][0] * m01 + matrix.m[0][1] * m11; - m[1][0] = matrix.m[1][0] * m00 + matrix.m[1][1] * m10; - m[1][1] = matrix.m[1][0] * m01 + matrix.m[1][1] * m11; - m[2][0] = matrix.m[2][0] * m00 + matrix.m[2][1] * m10 + m20; - m[2][1] = matrix.m[2][0] * m01 + matrix.m[2][1] * m11 + m21; + m[0][0] = matrix.m[0][0] * m00 + matrix.m[0][1] * m10; + m[0][1] = matrix.m[0][0] * m01 + matrix.m[0][1] * m11; + m[1][0] = matrix.m[1][0] * m00 + matrix.m[1][1] * m10; + m[1][1] = matrix.m[1][0] * m01 + matrix.m[1][1] * m11; + m[2][0] = matrix.m[2][0] * m00 + matrix.m[2][1] * m10 + m20; + m[2][1] = matrix.m[2][0] * m01 + matrix.m[2][1] * m11 + m21; } - -void Matrix::PreMultiply(const Matrix &matrix) { +void Matrix::PreMultiply(const Matrix& matrix) { const Float m00 = m[0][0]; const Float m01 = m[0][1]; const Float m10 = m[1][0]; @@ -231,33 +192,31 @@ void Matrix::PreMultiply(const Matrix &matrix) { const Float m20 = m[2][0]; const Float m21 = m[2][1]; - m[0][0] = m00 * matrix.m[0][0] + m01 * matrix.m[1][0]; - m[0][1] = m00 * matrix.m[0][1] + m01 * matrix.m[1][1]; - m[1][0] = m10 * matrix.m[0][0] + m11 * matrix.m[1][0]; - m[1][1] = m10 * matrix.m[0][1] + m11 * matrix.m[1][1]; - m[2][0] = m20 * matrix.m[0][0] + m21 * matrix.m[1][0] + matrix.m[2][0]; - m[2][1] = m20 * matrix.m[0][1] + m21 * matrix.m[1][1] + matrix.m[2][1]; + m[0][0] = m00 * matrix.m[0][0] + m01 * matrix.m[1][0]; + m[0][1] = m00 * matrix.m[0][1] + m01 * matrix.m[1][1]; + m[1][0] = m10 * matrix.m[0][0] + m11 * matrix.m[1][0]; + m[1][1] = m10 * matrix.m[0][1] + m11 * matrix.m[1][1]; + m[2][0] = m20 * matrix.m[0][0] + m21 * matrix.m[1][0] + matrix.m[2][0]; + m[2][1] = m20 * matrix.m[0][1] + m21 * matrix.m[1][1] + matrix.m[2][1]; } - MatrixComplexity Matrix::DetermineComplexity() const { - const bool m00 = FuzzyNotEqual(m[0][0], Float(1.0)); - const bool m01 = FuzzyNotZero(m[0][1]); - const bool m10 = FuzzyNotZero(m[1][0]); - const bool m11 = FuzzyNotEqual(m[1][1], Float(1.0)); - const bool m20 = FuzzyNotZero(m[2][0]); - const bool m21 = FuzzyNotZero(m[2][1]); + const bool m00 = FuzzyNotEqual(m[0][0], Float(1.0)); + const bool m01 = FuzzyNotZero(m[0][1]); + const bool m10 = FuzzyNotZero(m[1][0]); + const bool m11 = FuzzyNotEqual(m[1][1], Float(1.0)); + const bool m20 = FuzzyNotZero(m[2][0]); + const bool m21 = FuzzyNotZero(m[2][1]); - const bool translation = m20 | m21; - const bool scale = m00 | m11; - const bool complex = m01 | m10; + const bool translation = m20 | m21; + const bool scale = m00 | m11; + const bool complex = m01 | m10; static constexpr int TranslationBit = 2; - static constexpr int ScaleBit = 1; - static constexpr int ComplexBit = 0; + static constexpr int ScaleBit = 1; + static constexpr int ComplexBit = 0; - const int mask = (int(translation) << 2) | (int(scale) << 1) | - (int(complex) << ComplexBit); + const int mask = (int(translation) << 2) | (int(scale) << 1) | (int(complex) << ComplexBit); switch (mask) { case 0: diff --git a/src/graphics/blaze/Threads.cpp b/src/graphics/blaze/Threads.cpp index aa378558..452399d5 100644 --- a/src/graphics/blaze/Threads.cpp +++ b/src/graphics/blaze/Threads.cpp @@ -17,7 +17,7 @@ int Threads::GetHardwareThreadCount() { #endif } -void Threads::Run(const int count, Function *loopBody) { +void Threads::Run(const int count, Function* loopBody) { #ifdef MULTITHREAD BLAZE_ASSERT(loopBody != nullptr); @@ -29,14 +29,14 @@ void Threads::Run(const int count, Function *loopBody) { return; } - mTaskData->Cursor = 0; - mTaskData->Count = count; - mTaskData->Fn = loopBody; + mTaskData->Cursor = 0; + mTaskData->Count = count; + mTaskData->Fn = loopBody; - const int threadCount = Min(mThreadCount, count); + const int threadCount = Min(mThreadCount, count); mTaskData->RequiredWorkerCount = threadCount; - mTaskData->FinalizedWorkers = 0; + mTaskData->FinalizedWorkers = 0; { std::unique_lock lock(mTaskData->FinalizationMutex); @@ -50,11 +50,11 @@ void Threads::Run(const int count, Function *loopBody) { } // Cleanup. - mTaskData->Cursor = 0; - mTaskData->Count = 0; - mTaskData->Fn = nullptr; + mTaskData->Cursor = 0; + mTaskData->Count = 0; + mTaskData->Fn = nullptr; mTaskData->RequiredWorkerCount = 0; - mTaskData->FinalizedWorkers = 0; + mTaskData->FinalizedWorkers = 0; #endif } @@ -72,7 +72,7 @@ void Threads::RunThreads() { return; } - mTaskData = new TaskList(); + mTaskData = new TaskList(); mThreadCount = Min(GetHardwareThreadCount(), 128); mThreadData.resize(mThreadCount); @@ -81,18 +81,18 @@ void Threads::RunThreads() { } for (int i = 0; i < mThreadCount; i++) { - ThreadData *d = mThreadData[i]; - d->Thread = std::thread(&Threads::Worker, d); + ThreadData* d = mThreadData[i]; + d->Thread = std::thread(&Threads::Worker, d); d->Thread.detach(); // same as pthread_create + no join } #endif } -void Threads::Worker(ThreadData *d) { +void Threads::Worker(ThreadData* d) { #ifdef MULTITHREAD BLAZE_ASSERT(d != nullptr); - TaskList *items = d->Tasks; + TaskList* items = d->Tasks; for (;;) { { diff --git a/src/graphics/vector/Raster.hpp b/src/graphics/vector/Raster.hpp index 2f75e1c7..951bf5cd 100644 --- a/src/graphics/vector/Raster.hpp +++ b/src/graphics/vector/Raster.hpp @@ -27,6 +27,4 @@ #include "../Mask.hpp" #include "Rle.hpp" -namespace Brisk { - -} // namespace Brisk +namespace Brisk {} // namespace Brisk diff --git a/src/gui/Component.cpp b/src/gui/Component.cpp index 05e59395..abdd245a 100644 --- a/src/gui/Component.cpp +++ b/src/gui/Component.cpp @@ -45,6 +45,12 @@ Rc Component::makeWindow() { return window; } +Rc Component::newWindow() { + Rc window = createWindow(); + configureWindow(window); + return window; +} + Rc Component::createWindow() { return rcnew GuiWindow{ shared_from_this() }; } @@ -65,9 +71,7 @@ void Component::beforeFrame() {} void Component::closeWindow() { if (auto win = m_window.lock()) { - uiScheduler->dispatch([win]() { - win->close(); - }); + win->close(); } } @@ -90,6 +94,8 @@ void Component::handleDebugKeystrokes(Event& event) { if (auto t = window() ? window()->target() : nullptr) t->setVSyncInterval(1 - t->vsyncInterval()); } else if (event.keyPressed(KeyCode::F5)) { + if (!window() || !window()->tree().root()) + return; tree().root()->dump(); } else if (event.keyPressed(KeyCode::F6)) { Internal::debugDirtyRect = !Internal::debugDirtyRect; diff --git a/src/gui/Event.cpp b/src/gui/Event.cpp index 53674e6b..e1a43f49 100644 --- a/src/gui/Event.cpp +++ b/src/gui/Event.cpp @@ -23,8 +23,6 @@ namespace Brisk { -std::atomic_uint32_t eventCookie{ 0 }; - const char* const eventTypeNames[+EventType::Count] = { "Undefined", "MouseMoved", @@ -171,17 +169,9 @@ void InputQueue::beginDrag(std::shared_ptr dragSource, std::shared_ptrdraggingOnSource = true; } -void InputQueue::addEvent(Event event) { - events.push_back(std::move(event)); -} - -void InputQueue::injectEvent(Event event) { - injectedEvents.push_back(std::move(event)); -} - void InputQueue::setFocus(std::shared_ptr focus, bool keyboard) { if (auto previous = focused.lock()) { - addEvent(EventBlurred{ { {}, std::move(previous) } }); + std::ignore = processEvent(EventBlurred{ { {}, std::move(previous) } }); } if (focus && !focus->m_tabStop) { // If the widget doesn't accept focus, find the first descendant that does @@ -194,7 +184,7 @@ void InputQueue::setFocus(std::shared_ptr focus, bool keyboard) { focused = focus; if (focus) { - addEvent(EventFocused{ { {}, std::move(focus) }, keyboard }); + std::ignore = processEvent(EventFocused{ { {}, std::move(focus) }, keyboard }); } } @@ -284,7 +274,7 @@ void InputQueue::handleFocusEvents(Event& e) { } } -void InputQueue::processKeyEvent(Event e) { +bool InputQueue::processKeyEvent(Event e) { if (isDragging() && e.keyPressed(KeyCode::Escape)) { cancelDragging(); } @@ -302,9 +292,7 @@ void InputQueue::processKeyEvent(Event e) { handleFocusEvents(e); - if (e && unhandledEvent) { - unhandledEvent(e); - } + return !e; } std::optional InputQueue::getHintAtMouse() const { @@ -375,21 +363,22 @@ void InputQueue::cancelDragging() { dropAllowed = false; } -void InputQueue::processDragEvent(Event e) { +bool InputQueue::handleDragAndDrop(Event e) { // Process mouse event and generate all needed drag&drop events + std::shared_ptr source = dragSource.lock(); + if (!source) + return false; + std::optional base = e.as(); std::shared_ptr previousTarget = dragTarget.lock(); - std::shared_ptr source = dragSource.lock(); - if (!source) - return; - auto lookupResult = getAt(base->point, 0, false); - auto [target, index] = lookupResult; + auto lookupResult = getAt(base->point, 0, false); + auto [target, index] = lookupResult; - std::shared_ptr object = dragObject; + std::shared_ptr object = dragObject; if (!object) - return; + return false; EventDragNDrop dragBase{ *base, base->point, base->downPoint, object, source, target }; if (e.type() == EventType::MouseMoved) { @@ -429,14 +418,15 @@ void InputQueue::processDragEvent(Event e) { } draggingOnSource = source->rect().contains(base->point); + return true; } -void InputQueue::processMouseEvent(Event e) { +bool InputQueue::processMouseEvent(Event e) { std::optional base = e.as(); BRISK_ASSERT(!!base); if (isDragging()) { - processDragEvent(e); + std::ignore = handleDragAndDrop(e); } auto [target, index] = getAt(base->point); @@ -462,20 +452,17 @@ void InputQueue::processMouseEvent(Event e) { resetFocus(); e.stopPropagation(); } - if (e && unhandledEvent) { - unhandledEvent(e); - } + processMouseState(); + return !e; } -void InputQueue::processTargetedEvent(Event e) { +bool InputQueue::processTargetedEvent(Event e) { std::optional targeted = e.as(); BRISK_ASSERT(!!targeted); if (Rc target = targeted->target.lock()) { target->processEvent(e); } - if (e && unhandledEvent) { - unhandledEvent(e); - } + return !e; } template @@ -485,7 +472,7 @@ static bool isVisible(const std::weak_ptr& w) { } template -static void cleanup(std::vector>& vec) { +static void cleanupVector(std::vector>& vec) { vec.erase(std::remove_if(vec.begin(), vec.end(), [](const std::weak_ptr& w) BRISK_INLINE_LAMBDA { return !isVisible(w); @@ -493,70 +480,58 @@ static void cleanup(std::vector>& vec) { vec.end()); } -void InputQueue::processEvents() { +bool InputQueue::processEvent(Event e) { + switch (e.type()) { + case EventType::CharacterTyped: + case EventType::KeyPressed: + case EventType::KeyReleased: + setLastInputEvent(*e.as()); + return processKeyEvent(std::move(e)); + case EventType::MouseButtonPressed: + case EventType::MouseButtonReleased: + case EventType::MouseMoved: + case EventType::MouseEntered: + case EventType::MouseExited: + case EventType::MouseYWheel: + case EventType::MouseDoubleClicked: + case EventType::MouseTripleClicked: + setLastMouseEvent(*e.as()); + setLastInputEvent(*e.as()); + return processMouseEvent(std::move(e)); + case EventType::SourceDragging: + case EventType::SourceDropped: + case EventType::TargetDragging: + case EventType::TargetDropped: + // Drag and Drop events are routed directly to the appropriate objects; they do not go through the + // event queue. + BRISK_ASSERT(false); + return false; + case EventType::Focused: + case EventType::Blurred: + return processTargetedEvent(std::move(e)); + default: + return false; + } +} + +void InputQueue::updateAutoFocus() { if (isVisible(autoFocus) && !isVisible(focused)) { auto w = autoFocus.lock(); if (!w->m_autofocusReceived) { w->m_autofocusReceived = true; setFocus(w, false); + autoFocus.reset(); } } - cleanup(tabList); - cleanup(capturingKeys); - cleanup(capturingMouse); +} + +void InputQueue::cleanup() { + cleanupVector(tabList); + cleanupVector(capturingKeys); + cleanupVector(capturingMouse); if (!isVisible(focused)) { focused.reset(); } - - if (events.empty() && injectedEvents.empty()) - return; - - while (!events.empty()) { - Event e = std::move(events.front()); - events.pop_front(); - switch (e.type()) { - case EventType::CharacterTyped: - case EventType::KeyPressed: - case EventType::KeyReleased: - setLastInputEvent(*e.as()); - processKeyEvent(std::move(e)); - break; - case EventType::MouseButtonPressed: - case EventType::MouseButtonReleased: - case EventType::MouseMoved: - case EventType::MouseEntered: - case EventType::MouseExited: - case EventType::MouseYWheel: - case EventType::MouseDoubleClicked: - case EventType::MouseTripleClicked: - setLastMouseEvent(*e.as()); - setLastInputEvent(*e.as()); - processMouseEvent(std::move(e)); - break; - case EventType::SourceDragging: - case EventType::SourceDropped: - case EventType::TargetDragging: - case EventType::TargetDropped: - // Drag and Drop events are routed directly to the appropriate objects; they do not go through the - // event queue. - BRISK_ASSERT(false); - break; - case EventType::Focused: - case EventType::Blurred: - processTargetedEvent(std::move(e)); - break; - default: - break; - } - } - - processMouseState(); - - if (!injectedEvents.empty()) { - events.insert(events.end(), std::make_move_iterator(injectedEvents.begin()), - std::make_move_iterator(injectedEvents.end())); - injectedEvents.clear(); - } } void InputQueue::processMouseState(const std::shared_ptr& target) { @@ -764,10 +739,6 @@ void Event::stopPropagation() { *this = std::monostate{}; } -uint32_t Event::cookie() const { - return as()->cookie; -} - bool Event::shouldBubble() const { return type() < EventType::Focused; } @@ -796,13 +767,14 @@ bool InputQueue::isDragging() const { void InputQueue::setAutoFocus(std::weak_ptr ptr) { autoFocus = std::move(ptr); + updateAutoFocus(); } bool InputQueue::hasFocus() { return static_cast(focused.lock()); } -InputQueue::InputQueue() : registration{ this, uiScheduler } {} +InputQueue::InputQueue() : registration{ this } {} void InputQueue::passThrough() { passThroughFlag = true; diff --git a/src/gui/Gui.cpp b/src/gui/Gui.cpp index 40f5b22b..d59c155e 100644 --- a/src/gui/Gui.cpp +++ b/src/gui/Gui.cpp @@ -488,9 +488,9 @@ int shufflePalette(int x) { } namespace Internal { -std::atomic_bool debugRelayoutAndRegenerate{ false }; -std::atomic_bool debugBoundaries{ false }; -std::atomic_bool debugDirtyRect{ false }; +bool debugRelayoutAndRegenerate{ false }; +bool debugBoundaries{ false }; +bool debugDirtyRect{ false }; } // namespace Internal void Widget::requestUpdateLayout() { @@ -1421,7 +1421,7 @@ void Widget::bubbleEvent(Event& event, WidgetState enable, WidgetState disable, } void Widget::processTreeVisibility(bool isVisible) { - isVisible = isVisible && m_visible; + isVisible = isVisible && m_visible && !m_hidden; processVisibility(isVisible); for (const Rc& w : *this) { w->processTreeVisibility(isVisible); @@ -1440,6 +1440,8 @@ void Widget::updateGeometry(HitTestMap::State& state) { else if (m_mouseInteraction == MouseInteraction::Disable) state.mouseTransparent = true; + processVisibility(state.visible); + if (auto inputQueue = this->inputQueue()) { if (m_focusCapture && state.visible) { inputQueue->enterFocusCapture(); @@ -1483,8 +1485,10 @@ void Widget::updateGeometry(HitTestMap::State& state) { inputQueue->leaveFocusCapture(); } - if (m_autofocus && m_isVisible) { - inputQueue->setAutoFocus(self); + if (m_autofocus) { + if (m_isVisible) { + inputQueue->setAutoFocus(self); + } } } state = std::move(saved_state); diff --git a/src/gui/GuiWindow.cpp b/src/gui/GuiWindow.cpp index ab8c2662..7f19f284 100644 --- a/src/gui/GuiWindow.cpp +++ b/src/gui/GuiWindow.cpp @@ -27,15 +27,26 @@ namespace Brisk { -void GuiWindow::onKeyEvent(KeyCode key, int scancode, KeyAction action, KeyModifiers mods) { +bool GuiWindow::processEvent(Event&& e) { + if (m_inputQueue.processEvent(e)) + return true; + + if (m_component) + m_component->unhandledEvent(e); + if (e) + this->unhandledEvent(e); + return !e; +} + +bool GuiWindow::onKeyEvent(KeyCode key, int scancode, KeyAction action, KeyModifiers mods) { if (action != KeyAction::Release) - m_inputQueue.addEvent(EventKeyPressed{ { { {}, mods }, key }, action == KeyAction::Repeat }); + return processEvent(EventKeyPressed{ { { {}, mods }, key }, action == KeyAction::Repeat }); else - m_inputQueue.addEvent(EventKeyReleased{ { { {}, mods }, key } }); + return processEvent(EventKeyReleased{ { { {}, mods }, key } }); } -void GuiWindow::onCharEvent(char32_t character) { - m_inputQueue.addEvent(EventCharacterTyped{ { {}, m_mods }, character }); +bool GuiWindow::onCharEvent(char32_t character) { + return processEvent(EventCharacterTyped{ { {}, m_mods }, character }); } void GuiWindow::onMouseEnter() { @@ -54,75 +65,39 @@ void GuiWindow::onFocusChange(bool gained) { m_inputQueue.finishMenu(); } -bool GuiWindow::handleEvent(function fn) { - // The function fn creates an Event and pushes it to the m_inputQueue. - // This method ensures that the event is processed by the event handlers - // and doesn't end up in m_unhandledEvents. - bool result = false; - std::atomic_bool finished{ false }; - - uiScheduler->dispatchAndWait([fn = std::move(fn), this, &result, &finished] { - fn(); - uint32_t cookie = m_inputQueue.events.back().cookie(); - BRISK_LOG_DEBUG("wait cookie={:08X}", cookie); - - windowApplication->afterRenderQueue->dispatch( - [this, &result, cookie, &finished] { - BRISK_LOG_DEBUG("m_unhandledEvents.size={}", m_unhandledEvents.size()); - BRISK_LOG_DEBUG("unhandled cookies = {}", join(map(m_unhandledEvents, - [](uint32_t c) { - return fmt::format("{:08}", c); - }), - ",")); - - auto it = std::find(m_unhandledEvents.begin(), m_unhandledEvents.end(), cookie); - // Event is considered handled if not found in m_unhandledEvents. - result = it == m_unhandledEvents.end(); - finished.store(true, std::memory_order::release); - }, - ExecuteImmediately::Never); // Ensure the lambda is executed at the right time. - }); - - // waitUsingFunc(nullptr, finished, nullptr, 0.2); - - return result; -} - bool GuiWindow::handleKeyEvent(KeyCode key, int scancode, KeyAction action, KeyModifiers mods) { - return handleEvent([=, this] { - m_mods = mods; - keyEvent(key, scancode, action, mods); - }); + return keyEvent(key, scancode, action, mods); } bool GuiWindow::handleCharEvent(char32_t character) { - return handleEvent([=, this] { - charEvent(character); - }); + return charEvent(character); } -void GuiWindow::onMouseEvent(MouseButton button, MouseAction action, KeyModifiers mods, PointF point, +bool GuiWindow::onMouseEvent(MouseButton button, MouseAction action, KeyModifiers mods, PointF point, int conseqClicks) { if (action == MouseAction::Press) { - m_inputQueue.addEvent(EventMouseButtonPressed{ { { { {}, mods }, point, m_downPoint }, button } }); + bool handled = + processEvent(EventMouseButtonPressed{ { { { {}, mods }, point, m_downPoint }, button } }); if (conseqClicks == 3) - m_inputQueue.addEvent(EventMouseTripleClicked{ { { {}, mods }, point, m_downPoint } }); + processEvent(EventMouseTripleClicked{ { { {}, mods }, point, m_downPoint } }); else if (conseqClicks == 2) - m_inputQueue.addEvent(EventMouseDoubleClicked{ { { {}, mods }, point, m_downPoint } }); + processEvent(EventMouseDoubleClicked{ { { {}, mods }, point, m_downPoint } }); + return handled; } else { - m_inputQueue.addEvent(EventMouseButtonReleased{ { { { {}, mods }, point, m_downPoint }, button } }); + return processEvent(EventMouseButtonReleased{ { { { {}, mods }, point, m_downPoint }, button } }); } } -void GuiWindow::onMouseMove(PointF point) { - m_inputQueue.addEvent(EventMouseMoved{ { { {}, m_mods }, point, m_downPoint } }); +bool GuiWindow::onMouseMove(PointF point) { + return processEvent(EventMouseMoved{ { { {}, m_mods }, point, m_downPoint } }); } -void GuiWindow::onWheelEvent(float x, float y) { +bool GuiWindow::onWheelEvent(float x, float y) { if (y) - m_inputQueue.addEvent(EventMouseYWheel{ { { {}, m_mods }, m_mousePoint, m_downPoint }, y }); + return processEvent(EventMouseYWheel{ { { {}, m_mods }, m_mousePoint, m_downPoint }, y }); if (x) - m_inputQueue.addEvent(EventMouseXWheel{ { { {}, m_mods }, m_mousePoint, m_downPoint }, x }); + return processEvent(EventMouseXWheel{ { { {}, m_mods }, m_mousePoint, m_downPoint }, x }); + return false; } void GuiWindow::attachedToApplication() { @@ -136,16 +111,6 @@ void GuiWindow::attachedToApplication() { GuiWindow::GuiWindow(Rc component) : Window(), m_component(std::move(component)) { registerBuiltinFonts(); - m_inputQueue.unhandledEvent = [this](Event& event) BRISK_INLINE_LAMBDA { - if (m_component) - m_component->unhandledEvent(event); - if (!event) - return; - this->unhandledEvent(event); - if (!event) - return; - m_unhandledEvents.push_back(event.cookie()); - }; BRISK_LOG_INFO("Done creating GuiWindow"); } @@ -179,7 +144,6 @@ void GuiWindow::paintImmediate(RenderContext& context) { } bool GuiWindow::update() { - m_unhandledEvents.clear(); m_tree.setViewportRectangle(getFramebufferBounds()); if (!m_tree.root()) { rebuild(); @@ -222,7 +186,7 @@ void GuiWindow::updateWindowLimits() { Size resolution = display ? display->workarea().size() : Size{ 4096, 2048 }; newWindowSize = min(newWindowSize, resolution); - if (newWindowSize != windowSize) { + if (newWindowSize != windowSize && isTopLevel()) { if (m_windowFit == WindowFit::MinimumSize) { setMinimumSize(newWindowSize); } else { @@ -270,9 +234,7 @@ void GuiWindow::afterDraw(Canvas& canvas) {} void GuiWindow::beforeDraw(Canvas& canvas) {} void GuiWindow::beforeOpeningWindow() { - uiScheduler->dispatchAndWait([this]() { - updateWindowLimits(); - }); + updateWindowLimits(); } WidgetTree& GuiWindow::tree() { diff --git a/src/gui/WidgetTree.cpp b/src/gui/WidgetTree.cpp index fcb543d9..b73d63bb 100644 --- a/src/gui/WidgetTree.cpp +++ b/src/gui/WidgetTree.cpp @@ -38,13 +38,17 @@ void WidgetTree::setRoot(std::shared_ptr root) { if (m_root) { m_root->setTree(this); } - m_layoutIsActual = false; + if (m_inputQueue) { + m_inputQueue->reset(); + } + updateLayoutAndGeometry(); } } void WidgetTree::rescale() { if (m_root) { m_root->resolveAndInherit(); + updateLayoutAndGeometry(); } } @@ -141,26 +145,25 @@ void WidgetTree::update() { groupsBeforeFrame(); - if (!m_layoutIsActual) { - processEventsAndAnimations(); - applyStyleChanges(); - updateLayoutAndGeometry(); - m_layoutIsActual = true; - } - - processEventsAndAnimations(); - + processAnimations(); applyStyleChanges(); updateLayoutAndGeometry(); - m_layoutIsActual = true; + + if (m_inputQueue) + m_inputQueue->cleanup(); } -void WidgetTree::processEventsAndAnimations() { - if (m_layoutIsActual) { - if (m_inputQueue) - m_inputQueue->processEvents(); +void WidgetTree::updateVisibility() { + if (m_updateVisibilityRequested) { + m_root->processTreeVisibility(true); + m_updateVisibilityRequested = false; + if (m_inputQueue) { + m_inputQueue->updateAutoFocus(); + } } +} +void WidgetTree::processAnimations() { processAnimation(); processRebuild(); @@ -171,11 +174,7 @@ void WidgetTree::processEventsAndAnimations() { m_root->refreshTree(); m_refreshTime = frameStartTime; } - - if (m_updateVisibilityRequested) { - m_root->processTreeVisibility(true); - m_updateVisibilityRequested = false; - } + updateVisibility(); } void WidgetTree::applyStyleChanges() { @@ -193,7 +192,8 @@ void WidgetTree::updateLayoutAndGeometry() { m_inputQueue->reset(); HitTestMap::State state; m_root->updateGeometry(state); - m_updateGeometryRequested = false; + m_updateGeometryRequested = false; + m_updateVisibilityRequested = false; if (m_inputQueue) { m_inputQueue->processMouseState(); } diff --git a/src/widgets/Button.cpp b/src/widgets/Button.cpp index 46ed5e6e..8a10ad14 100644 --- a/src/widgets/Button.cpp +++ b/src/widgets/Button.cpp @@ -56,13 +56,13 @@ void Button::onEvent(Event& event) { } m_repeatState = std::nullopt; event.stopPropagation(); - } else if ((m_keyEvents && ButtonKeyEvents::AcceptsEnter) && event.keyPressed(KeyCode::Enter) || - (m_keyEvents && ButtonKeyEvents::AcceptsSpace) && event.keyPressed(KeyCode::Space)) { + } else if (((m_keyEvents && ButtonKeyEvents::AcceptsEnter) && event.keyPressed(KeyCode::Enter)) || + ((m_keyEvents && ButtonKeyEvents::AcceptsSpace) && event.keyPressed(KeyCode::Space))) { toggleState(WidgetState::Pressed, true); doClick(); event.stopPropagation(); - } else if ((m_keyEvents && ButtonKeyEvents::AcceptsEnter) && event.keyReleased(KeyCode::Enter) || - (m_keyEvents && ButtonKeyEvents::AcceptsSpace) && event.keyReleased(KeyCode::Space)) { + } else if (((m_keyEvents && ButtonKeyEvents::AcceptsEnter) && event.keyReleased(KeyCode::Enter)) || + ((m_keyEvents && ButtonKeyEvents::AcceptsSpace) && event.keyReleased(KeyCode::Space))) { toggleState(WidgetState::Pressed, false); event.stopPropagation(); } diff --git a/src/widgets/Knob.cpp b/src/widgets/Knob.cpp index eddc3372..f60a2316 100644 --- a/src/widgets/Knob.cpp +++ b/src/widgets/Knob.cpp @@ -28,7 +28,7 @@ void Knob::onEvent(Event& event) { Widget::onEvent(event); if (float delta = event.wheelScrolled(m_wheelModifiers)) { - m_savedValue = std::clamp(static_cast(normalizedValue) + delta / 24.f, 0.f, 1.f); + m_savedValue = std::clamp(static_cast(normalizedValue) + delta / 24., 0., 1.); normalizedValue = m_savedValue; event.stopPropagation(); return; @@ -42,8 +42,8 @@ void Knob::onEvent(Event& event) { event.stopPropagation(); return; case DragEvent::Dragging: { - const float unit_distance = mods && KeyModifiers::Shift ? 1500_dp : 150.0_dp; - normalizedValue = std::clamp((offset.x - offset.y) / unit_distance + m_savedValue, 0.f, 1.f); + const double unit_distance = mods && KeyModifiers::Shift ? 1500_dp : 150.0_dp; + normalizedValue = std::clamp((offset.x - offset.y) / unit_distance + m_savedValue, 0., 1.); startModifying(); event.stopPropagation(); return; @@ -59,27 +59,27 @@ void Knob::onEvent(Event& event) { if (auto e = event.as()) { switch (e->key) { case KeyCode::Up: - value = std::min(static_cast(normalizedValue) + 0.01f, 1.f); + value = std::min(static_cast(normalizedValue) + 0.01, 1.); event.stopPropagation(); break; case KeyCode::Down: - value = std::min(static_cast(normalizedValue) - 0.01f, 1.f); + value = std::min(static_cast(normalizedValue) - 0.01, 1.); event.stopPropagation(); break; case KeyCode::PageUp: - value = std::min(static_cast(normalizedValue) + 0.1f, 1.f); + value = std::min(static_cast(normalizedValue) + 0.1, 1.); event.stopPropagation(); break; case KeyCode::PageDown: - value = std::max(static_cast(normalizedValue) - 0.1f, 0.f); + value = std::max(static_cast(normalizedValue) - 0.1, 0.); event.stopPropagation(); break; case KeyCode::Home: - value = 0.f; + value = 0.; event.stopPropagation(); break; case KeyCode::End: - value = 1.f; + value = 1.; event.stopPropagation(); break; default: diff --git a/src/widgets/Menu.cpp b/src/widgets/Menu.cpp index fb5bafa1..3e3d46a0 100644 --- a/src/widgets/Menu.cpp +++ b/src/widgets/Menu.cpp @@ -59,7 +59,7 @@ void Menu::append(Rc widget) { if (Item* it = dynamicCast(widget.get())) { it->focusOnHover = true; Base::append(std::move(widget)); - } else if (Spacer* it = dynamicCast(widget.get())) { + } else if (isOf(widget.get())) { Base::append(std::move(widget)); } else { Base::append(rcnew Item{ std::move(widget), focusOnHover = true }); diff --git a/src/widgets/Slider.cpp b/src/widgets/Slider.cpp index 6d60354d..754ed428 100644 --- a/src/widgets/Slider.cpp +++ b/src/widgets/Slider.cpp @@ -65,7 +65,7 @@ void Slider::onEvent(Event& event) { Base::onEvent(event); if (float delta = event.wheelScrolled(m_rect, m_wheelModifiers)) { - float val = std::clamp(static_cast(normalizedValue) + delta / 24.f, 0.f, 1.f); + double val = std::clamp(static_cast(normalizedValue) + delta / 24., 0., 1.); normalizedValue = val; event.stopPropagation(); } else { @@ -84,12 +84,12 @@ void Slider::onEvent(Event& event) { event.stopPropagation(); break; case DragEvent::Dragging: - float newValue; + double newValue; if (horizontal) newValue = (offset.x) / m_distance + m_savedValue; else newValue = (-offset.y) / m_distance + m_savedValue; - normalizedValue = std::clamp(newValue, 0.f, 1.f); + normalizedValue = std::clamp(newValue, 0., 1.); startModifying(); if (m_hintFormatter) m_hint = m_hintFormatter(m_value); diff --git a/src/widgets/TextEditor.cpp b/src/widgets/TextEditor.cpp index b71166af..31b04022 100644 --- a/src/widgets/TextEditor.cpp +++ b/src/widgets/TextEditor.cpp @@ -380,7 +380,6 @@ void TextEditor::onEvent(Event& event) { if (isDisabled()) return; - const Rectangle textRect = m_clientRect; std::u32string text; if (event.doubleClicked()) { selectWordAtCursor(); diff --git a/src/widgets/Widgets_test.cpp b/src/widgets/Widgets_test.cpp index a70c2831..091e8641 100644 --- a/src/widgets/Widgets_test.cpp +++ b/src/widgets/Widgets_test.cpp @@ -69,9 +69,6 @@ static void widgetTest(const std::string& name, Rc widget, std::initiali Size size = defaultSize, float pixelRatio = defaultPixelRatio, ColorW winColor = 0x131419_rgb) { InputQueue input; - for (const Event& e : events) { - input.addEvent(e); - } WidgetTree tree(&input); tree.disableTransitions(); Brisk::pixelRatio() = pixelRatio; @@ -81,9 +78,15 @@ static void widgetTest(const std::string& name, Rc widget, std::initiali std::move(widget), }); tree.rescale(); + for (const Event& e : events) { + bool handled = input.processEvent(e); + if (e.type() != EventType::MouseMoved) { + REQUIRE(handled); // Ensure non-move events are handled + } + } + tree.update(); renderTest(name, tree.viewportRectangle().size(), [&](RenderContext& context) { Canvas canvas(context); - tree.update(); tree.paint(canvas, Palette::black, true); }); } @@ -447,22 +450,22 @@ TEST_CASE("Button states animation") { rcnew Row{ rcnew Button{ storeWidget(&btn), "Button"_Text } }, }); - animation.input.addEvent(mouseMove(btn->rect().center())); + std::ignore = animation.input.processEvent(mouseMove(btn->rect().center())); dynamicPointerCast(btn->widgets().front())->text = "Hover"; animation.frames(1250ms); - animation.input.addEvent(mousePress(btn->rect().center())); + REQUIRE(animation.input.processEvent(mousePress(btn->rect().center()))); dynamicPointerCast(btn->widgets().front())->text = "Pressed"; animation.frames(1250ms); - animation.input.addEvent(mouseRelease(btn->rect().center())); + REQUIRE(animation.input.processEvent(mouseRelease(btn->rect().center()))); dynamicPointerCast(btn->widgets().front())->text = "Hover"; animation.frames(1250ms); - animation.input.addEvent(mouseMove({})); + std::ignore = animation.input.processEvent(mouseMove({})); dynamicPointerCast(btn->widgets().front())->text = "Normal"; animation.frames(1250ms); @@ -551,21 +554,21 @@ TEST_CASE("TextEditor animation") { std::mt19937 rnd(123); std::uniform_int_distribution<> uniform(75, 150); for (char32_t ch : text) { - animation.input.addEvent(EventCharacterTyped{ .character = ch }); + REQUIRE(animation.input.processEvent(EventCharacterTyped{ .character = ch })); int32_t delay = uniform(rnd); animation.frames(delay * 1ms); } animation.frames(800ms); for (char32_t ch : text) { - animation.input.addEvent(EventKeyPressed{ EventKey{ {}, KeyCode::Left } }); + REQUIRE(animation.input.processEvent(EventKeyPressed{ EventKey{ {}, KeyCode::Left } })); animation.frames(40ms); } animation.frames(600ms); - animation.input.addEvent( - EventKeyPressed{ EventKey{ EventInput{ {}, KeyModifiers::ControlOrCommand }, KeyCode::A } }); + REQUIRE(animation.input.processEvent( + EventKeyPressed{ EventKey{ EventInput{ {}, KeyModifiers::ControlOrCommand }, KeyCode::A } })); animation.frames(600ms); - animation.input.addEvent(EventKeyPressed{ EventKey{ {}, KeyCode::Del } }); + REQUIRE(animation.input.processEvent(EventKeyPressed{ EventKey{ {}, KeyCode::Del } })); animation.frames(600ms); animation.save("animation/texteditor.webp"); diff --git a/src/window/OsDialogs_Windows.cpp b/src/window/OsDialogs_Windows.cpp index e09fbd9e..9bd79d40 100644 --- a/src/window/OsDialogs_Windows.cpp +++ b/src/window/OsDialogs_Windows.cpp @@ -267,7 +267,7 @@ static DialogResult showChildDialog(NativeWindow* window, DialogButtons buttons, if (buttons && DialogButtons::Retry) btns |= TDCBF_RETRY_BUTTON; int btn = 0; - if (!SUCCEEDED(TaskDialog(handleFromWindow(window), GetModuleHandleW(nullptr), utf8ToWcs(title).c_str(), + if (!SUCCEEDED(TaskDialog(handleFromWindow(window), NULL, utf8ToWcs(title).c_str(), utf8ToWcs(message).c_str(), nullptr, btns, icon, &btn))) { BRISK_LOG_ERROR("TaskDialog() failed"); return DialogResult::Cancel; diff --git a/src/window/PlatformWindow.cpp b/src/window/PlatformWindow.cpp index cf4ae98a..2edecf27 100644 --- a/src/window/PlatformWindow.cpp +++ b/src/window/PlatformWindow.cpp @@ -33,9 +33,7 @@ void PlatformWindow::updateSize() { if (m_iconified) return; - uiScheduler->dispatch([window = m_window, size = m_windowSize, framebufferSize = m_framebufferSize] { - window->windowResized(size, framebufferSize); - }); + m_window->windowResized(m_windowSize, m_framebufferSize); } namespace Internal { @@ -99,27 +97,26 @@ bool PlatformCursors::isSystem(Cursor cursor) { } } // namespace Internal -void PlatformWindow::charEvent(char32_t codepoint, bool nonClient) { +bool PlatformWindow::charEvent(char32_t codepoint, bool nonClient) { if (codepoint < 32 || (codepoint > 126 && codepoint < 160)) - return; + return false; if (!nonClient) { - uiScheduler->dispatch([window = m_window, codepoint] { - window->charEvent(static_cast(codepoint)); - }); + return m_window->charEvent(static_cast(codepoint)); } + return false; } void PlatformWindow::releaseButtonsAndKeys() { for (int kc = 0; kc <= +KeyCode::Last; ++kc) { if (m_keyState[kc]) { - keyEvent(static_cast(kc), keyCodeToScanCode(KeyCode(kc)), KeyAction::Release, - KeyModifiers::None); + std::ignore = keyEvent(static_cast(kc), keyCodeToScanCode(KeyCode(kc)), + KeyAction::Release, KeyModifiers::None); } } for (int mb = 0; mb <= +MouseButton::Last; ++mb) { if (m_mouseState[mb]) { - mouseEvent(static_cast(mb), MouseAction::Release, KeyModifiers::None, - PointF(-1, -1)); + std::ignore = mouseEvent(static_cast(mb), MouseAction::Release, KeyModifiers::None, + PointF(-1, -1)); } } } @@ -129,29 +126,29 @@ void PlatformWindow::focusChange(bool gained) { releaseButtonsAndKeys(); } - uiScheduler->dispatch([window = m_window, gained] { - window->focusChange(gained); - }); + m_window->focusChange(gained); } void PlatformWindow::closeAttempt() { m_shouldClose = true; - uiScheduler->dispatch([window = m_window] { - window->closeAttempt(); - }); + m_window->closeAttempt(); +} + +void PlatformWindow::requestRedraw() { + m_window->doPaint(); } -void PlatformWindow::keyEvent(KeyCode key, int scancode, KeyAction action, KeyModifiers mods) { +bool PlatformWindow::keyEvent(KeyCode key, int scancode, KeyAction action, KeyModifiers mods) { if (m_windowStyle && WindowStyle::Disabled) - return; + return false; if (key < KeyCode(0) || key > KeyCode::Last) // NOLINT(clang-analyzer-optin.core.EnumCastOutOfRange) - return; + return false; bool repeated = false; if (action == KeyAction::Release && !m_keyState[+key]) - return; + return false; if (action == KeyAction::Press && m_keyState[+key]) repeated = true; @@ -160,51 +157,42 @@ void PlatformWindow::keyEvent(KeyCode key, int scancode, KeyAction action, KeyMo if (repeated) action = KeyAction::Repeat; - uiScheduler->dispatch([window = m_window, key, scancode, action, mods] { - window->keyEvent(static_cast(key), scancode, static_cast(action), - static_cast(mods)); - }); + return m_window->keyEvent(static_cast(key), scancode, static_cast(action), + static_cast(mods)); } -void PlatformWindow::mouseEvent(MouseButton button, MouseAction action, KeyModifiers mods, PointF pos, +bool PlatformWindow::mouseEvent(MouseButton button, MouseAction action, KeyModifiers mods, PointF pos, Window::Unit unit) { if (m_windowStyle && WindowStyle::Disabled) - return; + return false; if (button < MouseButton(0) || button > MouseButton::Last) - return; + return false; if (action == MouseAction::Release && !m_mouseState[+button]) - return; + return false; if (action == MouseAction::Press && m_mouseState[+button]) - return; + return false; m_mouseState[+button] = action == MouseAction::Press; - uiScheduler->dispatch([window = m_window, button, action, mods, pos, unit] { - window->mouseEvent(button, action, mods, window->convertUnit(Window::Unit::Framebuffer, pos, unit)); - }); + return m_window->mouseEvent(button, action, mods, + m_window->convertUnit(Window::Unit::Framebuffer, pos, unit)); } void PlatformWindow::mouseEnterOrLeave(bool enter) { - uiScheduler->dispatch([window = m_window, enter] { - if (enter) - window->mouseEnter(); - else - window->mouseLeave(); - }); + if (enter) + m_window->mouseEnter(); + else + m_window->mouseLeave(); } -void PlatformWindow::mouseMove(PointF pos, Window::Unit unit) { - uiScheduler->dispatch([window = m_window, pos, unit] { - window->mouseMove(window->convertUnit(Window::Unit::Framebuffer, pos, unit)); - }); +bool PlatformWindow::mouseMove(PointF pos, Window::Unit unit) { + return m_window->mouseMove(m_window->convertUnit(Window::Unit::Framebuffer, pos, unit)); } -void PlatformWindow::wheelEvent(float x, float y) { - uiScheduler->dispatch([window = m_window, x, y] { - window->wheelEvent(x, y); - }); +bool PlatformWindow::wheelEvent(float x, float y) { + return m_window->wheelEvent(x, y); } void PlatformWindow::windowStateEvent(WindowState state) {} @@ -219,9 +207,7 @@ void PlatformWindow::windowMoved(Point position) { if (!isVisible()) { return; } - uiScheduler->dispatch([window = m_window, position] { - window->windowMoved(position); - }); + m_window->windowMoved(position); } void PlatformWindow::windowNonClientClicked() { @@ -229,9 +215,7 @@ void PlatformWindow::windowNonClientClicked() { if (!isVisible()) { return; } - uiScheduler->dispatch([window = m_window] { - window->windowNonClientClicked(); - }); + m_window->windowNonClientClicked(); } void PlatformWindow::contentScaleChanged(float xscale, float yscale) { @@ -242,17 +226,13 @@ void PlatformWindow::contentScaleChanged(float xscale, float yscale) { m_window->recomputeScales(); } -void PlatformWindow::filesDropped(std::vector files) { +bool PlatformWindow::filesDropped(std::vector files) { mustBeMainThread(); - uiScheduler->dispatch([window = m_window, files = std::move(files)] { - window->filesDropped(std::move(files)); - }); + return m_window->filesDropped(std::move(files)); } void PlatformWindow::windowStateChanged(bool isIconified, bool isMaximized) { - uiScheduler->dispatch([window = m_window, isIconified, isMaximized] { - window->windowStateChanged(isIconified, isMaximized); - }); + m_window->windowStateChanged(isIconified, isMaximized); } } // namespace Brisk diff --git a/src/window/PlatformWindow.hpp b/src/window/PlatformWindow.hpp index 6e1f09d0..7b41a9db 100644 --- a/src/window/PlatformWindow.hpp +++ b/src/window/PlatformWindow.hpp @@ -108,7 +108,7 @@ class PlatformWindow { Bytes placement() const; void setPlacement(BytesView data); - explicit PlatformWindow(Window* window, Size windowSize, Point position, WindowStyle style); + explicit PlatformWindow(Window* window, Size windowSize, Point position, WindowStyle style, NativeWindowHandle parent); bool createWindow(); void setTitle(std::string_view title); @@ -130,27 +130,28 @@ class PlatformWindow { bool isIconified() const; bool isMaximized() const; void updateVisibility(); + void requestRedraw(); void setWindowIcon(); bool cursorInContentArea() const; void updateCursorImage(); bool isVisible() const; - void charEvent(char32_t codepoint, bool nonClient); - void mouseEvent(MouseButton button, MouseAction action, KeyModifiers mods, PointF pos, - Window::Unit unit = Window::Unit::Screen); + [[nodiscard]] bool charEvent(char32_t codepoint, bool nonClient); + [[nodiscard]] bool mouseEvent(MouseButton button, MouseAction action, KeyModifiers mods, PointF pos, + Window::Unit unit = Window::Unit::Screen); void focusChange(bool gained); void closeAttempt(); - void keyEvent(KeyCode key, int scancode, KeyAction action, KeyModifiers mods); + [[nodiscard]] bool keyEvent(KeyCode key, int scancode, KeyAction action, KeyModifiers mods); void mouseEnterOrLeave(bool enter); - void mouseMove(PointF pos, Window::Unit unit = Window::Unit::Screen); - void wheelEvent(float x, float y); + [[nodiscard]] bool mouseMove(PointF pos, Window::Unit unit = Window::Unit::Screen); + [[nodiscard]] bool wheelEvent(float x, float y); void windowStateEvent(WindowState state); void windowResized(Size windowSize, Size framebufferSize); void windowMoved(Point position); void windowNonClientClicked(); void contentScaleChanged(float xscale, float yscale); - void filesDropped(std::vector files); + [[nodiscard]] bool filesDropped(std::vector files); void windowStateChanged(bool isIconified, bool isMaximized); long long windowProc(MsgParams params); diff --git a/src/window/PlatformWindow_Darwin.mm b/src/window/PlatformWindow_Darwin.mm index bbc5c1de..82d34219 100644 --- a/src/window/PlatformWindow_Darwin.mm +++ b/src/window/PlatformWindow_Darwin.mm @@ -127,9 +127,10 @@ - (void)applicationDidFinishLaunching:(NSNotification*)notification { namespace Brisk { struct PlatformWindowData { - NSWindow* window = nil; - NSView* view = nil; - id delegate = nil; + __strong id parent = nil; + NSWindow* window = nil; + NSView* view = nil; + __strong id delegate = nil; float scale{ -1 }; bool occluded = false; NSPoint cascadePoint{ 0, 0 }; @@ -273,9 +274,9 @@ static KeyModifiers getKeyMods(NSUInteger flags) { static const NSRange kEmptyRange = { NSNotFound, 0 }; static struct { - id helper; - id delegate; - id keyUpMonitor; + __strong id helper; + __strong id delegate; + __strong id keyUpMonitor; } staticData; /*static*/ void PlatformWindow::initialize() { @@ -376,59 +377,88 @@ static KeyModifiers getKeyMods(NSUInteger flags) { void PlatformWindow::updateVisibility() { @autoreleasepool { - bool visible = m_window->m_visible; - if (visible) { - [m_data->window orderFront:nil]; - focus(); + if (m_data->window == nil) { + // Child window + [m_data->view setHidden:(m_window->m_visible ? YES : NO)]; } else { - [m_data->window orderOut:nil]; + bool visible = m_window->m_visible; + if (visible) { + [m_data->window orderFront:nil]; + focus(); + } else { + [m_data->window orderOut:nil]; + } } } } void PlatformWindow::iconify() { @autoreleasepool { - [m_data->window miniaturize:nil]; + if (m_data->window == nil) { + // do nothing for child windows + } else { + [m_data->window miniaturize:nil]; + } } // autoreleasepool } void PlatformWindow::restore() { @autoreleasepool { - if ([m_data->window isMiniaturized]) - [m_data->window deminiaturize:nil]; - else if ([m_data->window isZoomed]) - [m_data->window zoom:nil]; + if (m_data->window == nil) { + // do nothing for child windows + } else { + if ([m_data->window isMiniaturized]) + [m_data->window deminiaturize:nil]; + else if ([m_data->window isZoomed]) + [m_data->window zoom:nil]; + } } // autoreleasepool } void PlatformWindow::maximize() { @autoreleasepool { - if (![m_data->window isZoomed]) - [m_data->window zoom:nil]; + if (m_data->window == nil) { + // do nothing for child windows + } else { + if (![m_data->window isZoomed]) + [m_data->window zoom:nil]; + } } // autoreleasepool } bool PlatformWindow::isFocused() const { @autoreleasepool { - return [m_data->window isKeyWindow]; + if (m_data->window == nil) + return [[m_data->view window] isKeyWindow]; + else + return [m_data->window isKeyWindow]; } // autoreleasepool } bool PlatformWindow::isIconified() const { @autoreleasepool { - return [m_data->window isMiniaturized]; + if (m_data->window == nil) + return false; + else + return [m_data->window isMiniaturized]; } // autoreleasepool } bool PlatformWindow::isMaximized() const { @autoreleasepool { - return [m_data->window isZoomed]; + if (m_data->window == nil) + return false; + else + return [m_data->window isZoomed]; } // autoreleasepool } bool PlatformWindow::isVisible() const { @autoreleasepool { - return [m_data->window isVisible]; + if (m_data->window == nil) + return [m_data->view isHidden] == NO; + else + return [m_data->window isVisible]; } // autoreleasepool } @@ -439,15 +469,21 @@ static KeyModifiers getKeyMods(NSUInteger flags) { // being activated, but should probably not be done every time any // window is shown [NSApp activateIgnoringOtherApps:YES]; - [m_data->window makeKeyAndOrderFront:nil]; + if (m_data->window == nil) { + [[m_data->view window] makeKeyAndOrderFront:nil]; + } else { + [m_data->window makeKeyAndOrderFront:nil]; + } } // autoreleasepool } void PlatformWindow::setOwner(Rc window) { - // + BRISK_LOG_WARN("macOS doesn't implement owner windows"); } void PlatformWindow::setTitle(std::string_view title) { + if (m_data->window == nil) + return; [m_data->window setTitle:toNSString(title)]; } @@ -460,27 +496,35 @@ static KeyModifiers getKeyMods(NSUInteger flags) { } NativeWindowHandle PlatformWindow::getHandle() const { - return NativeWindowHandle(m_data->window); + if (m_data->window) + return NativeWindowHandle(m_data->window); + else + return NativeWindowHandle(m_data->view); } PlatformWindow::~PlatformWindow() { mustBeMainThread(); - [m_data->window orderOut:nil]; - [m_data->window setDelegate:nil]; + if (m_data->window != nil) { + [m_data->window orderOut:nil]; + [m_data->window setDelegate:nil]; + } m_data->delegate = nil; m_data->view = nil; m_data->window = nil; + m_data->parent = nil; // HACK: Allow Cocoa to catch up before returning pollEvents(); } -PlatformWindow::PlatformWindow(Window* window, Size windowSize, Point position, WindowStyle style) +PlatformWindow::PlatformWindow(Window* window, Size windowSize, Point position, WindowStyle style, + NativeWindowHandle parent) : m_data(new PlatformWindowData{}), m_window(window), m_windowStyle(style), m_windowSize(windowSize), m_position(position) { mustBeMainThread(); + m_data->parent = (__bridge id)parent.ptr; BRISK_ASSERT(m_window); bool created = createWindow(); @@ -613,6 +657,8 @@ - (void)dealloc { } - (BOOL)isOpaque { + if (window->m_data->window == nil) + return NO; return [window->m_data->window isOpaque]; } @@ -629,7 +675,7 @@ - (BOOL)wantsUpdateLayer { } - (void)updateLayer { - // Repaint + window->requestRedraw(); } - (void)cursorUpdate:(NSEvent*)event { @@ -648,8 +694,8 @@ - (PointF)eventToPos:(NSEvent*)event { } - (void)mouseDown:(NSEvent*)event { - window->mouseEvent(MouseButton::Left, MouseAction::Press, getKeyMods(event.modifierFlags), - [self eventToPos:event]); + std::ignore = window->mouseEvent(MouseButton::Left, MouseAction::Press, getKeyMods(event.modifierFlags), + [self eventToPos:event]); } - (void)mouseDragged:(NSEvent*)event { @@ -657,17 +703,17 @@ - (void)mouseDragged:(NSEvent*)event { } - (void)mouseUp:(NSEvent*)event { - window->mouseEvent(MouseButton::Left, MouseAction::Release, getKeyMods(event.modifierFlags), - [self eventToPos:event]); + std::ignore = window->mouseEvent(MouseButton::Left, MouseAction::Release, getKeyMods(event.modifierFlags), + [self eventToPos:event]); } - (void)mouseMoved:(NSEvent*)event { - window->mouseMove([self eventToPos:event]); + std::ignore = window->mouseMove([self eventToPos:event]); } - (void)rightMouseDown:(NSEvent*)event { - window->mouseEvent(MouseButton::Right, MouseAction::Press, getKeyMods(event.modifierFlags), - [self eventToPos:event]); + std::ignore = window->mouseEvent(MouseButton::Right, MouseAction::Press, getKeyMods(event.modifierFlags), + [self eventToPos:event]); } - (void)rightMouseDragged:(NSEvent*)event { @@ -675,13 +721,13 @@ - (void)rightMouseDragged:(NSEvent*)event { } - (void)rightMouseUp:(NSEvent*)event { - window->mouseEvent(MouseButton::Right, MouseAction::Release, getKeyMods(event.modifierFlags), - [self eventToPos:event]); + std::ignore = window->mouseEvent(MouseButton::Right, MouseAction::Release, + getKeyMods(event.modifierFlags), [self eventToPos:event]); } - (void)otherMouseDown:(NSEvent*)event { - window->mouseEvent(MouseButton((int)[event buttonNumber]), MouseAction::Press, - getKeyMods(event.modifierFlags), [self eventToPos:event]); + std::ignore = window->mouseEvent(MouseButton((int)[event buttonNumber]), MouseAction::Press, + getKeyMods(event.modifierFlags), [self eventToPos:event]); } - (void)otherMouseDragged:(NSEvent*)event { @@ -689,8 +735,8 @@ - (void)otherMouseDragged:(NSEvent*)event { } - (void)otherMouseUp:(NSEvent*)event { - window->mouseEvent(MouseButton((int)[event buttonNumber]), MouseAction::Release, - getKeyMods(event.modifierFlags), [self eventToPos:event]); + std::ignore = window->mouseEvent(MouseButton((int)[event buttonNumber]), MouseAction::Release, + getKeyMods(event.modifierFlags), [self eventToPos:event]); } - (void)mouseExited:(NSEvent*)event { @@ -747,7 +793,7 @@ - (void)updateTrackingAreas { - (void)keyDown:(NSEvent*)event { const KeyCode key = scanCodeToKeyCode(event.keyCode); - window->keyEvent(key, [event keyCode], KeyAction::Press, getKeyMods(event.modifierFlags)); + std::ignore = window->keyEvent(key, [event keyCode], KeyAction::Press, getKeyMods(event.modifierFlags)); [self interpretKeyEvents:@[ event ]]; } @@ -790,12 +836,12 @@ - (void)flagsChanged:(NSEvent*)event { action = KeyAction::Release; } - window->keyEvent(key, [event keyCode], action, getKeyMods(event.modifierFlags)); + std::ignore = window->keyEvent(key, [event keyCode], action, getKeyMods(event.modifierFlags)); } - (void)keyUp:(NSEvent*)event { const KeyCode key = scanCodeToKeyCode(event.keyCode); - window->keyEvent(key, [event keyCode], KeyAction::Release, getKeyMods(event.modifierFlags)); + std::ignore = window->keyEvent(key, [event keyCode], KeyAction::Release, getKeyMods(event.modifierFlags)); } - (void)scrollWheel:(NSEvent*)event { @@ -808,7 +854,7 @@ - (void)scrollWheel:(NSEvent*)event { } if (fabs(deltaX) > 0.0 || fabs(deltaY) > 0.0) { - window->wheelEvent(deltaX, deltaY); + std::ignore = window->wheelEvent(deltaX, deltaY); } } @@ -822,7 +868,7 @@ - (BOOL)performDragOperation:(id)sender { const NSRect contentRect = [window->m_data->view frame]; // NOTE: The returned location uses base 0,1 not 0,0 const NSPoint pos = [sender draggingLocation]; - window->mouseMove(PointF(pos.x, contentRect.size.height - pos.y) * window->m_scale); + std::ignore = window->mouseMove(PointF(pos.x, contentRect.size.height - pos.y) * window->m_scale); NSPasteboard* pasteboard = [sender draggingPasteboard]; NSDictionary* options = @{ NSPasteboardURLReadingFileURLsOnlyKey : @YES }; @@ -833,7 +879,7 @@ - (BOOL)performDragOperation:(id)sender { for (NSUInteger i = 0; i < count; i++) paths.push_back([urls[i] fileSystemRepresentation]); - window->filesDropped(std::move(paths)); + std::ignore = window->filesDropped(std::move(paths)); } return YES; @@ -911,7 +957,7 @@ - (void)insertText:(id)string replacementRange:(NSRange)replacementRange { if (codepoint >= 0xf700 && codepoint <= 0xf7ff) continue; - window->charEvent(codepoint, false); + std::ignore = window->charEvent(codepoint, false); } } } @@ -940,7 +986,7 @@ - (BOOL)canBecomeMainWindow { // Transforms a y-coordinate between the CG display and NS screen spaces // -static float transformYCocoa(float y) { +static CGFloat transformYCocoa(CGFloat y) { return CGDisplayBounds(CGMainDisplayID()).size.height - y - 1; } @@ -949,11 +995,7 @@ static float transformYCocoa(float y) { bool PlatformWindow::createWindow() { Size size = max(m_windowSize, Size{ 1, 1 }); Point initialPos = m_position; - - m_data->delegate = [[BriskWindowDelegate alloc] initWithWindow:this]; - if (m_data->delegate == nil) { - return false; - } + bool topLevel = m_data->parent == nil; NSRect contentRect; @@ -965,60 +1007,81 @@ static float transformYCocoa(float y) { contentRect = NSMakeRect(xpos, ypos, size.width, size.height); } - NSUInteger styleMask = NSWindowStyleMaskMiniaturizable; + if (topLevel) { + m_data->delegate = [[BriskWindowDelegate alloc] initWithWindow:this]; + if (m_data->delegate == nil) { + return false; + } - if (m_windowStyle && WindowStyle::Undecorated) { - styleMask |= NSWindowStyleMaskBorderless; - } else { - styleMask |= (NSWindowStyleMaskTitled | NSWindowStyleMaskClosable); + NSUInteger styleMask = NSWindowStyleMaskMiniaturizable; - if (m_windowStyle && WindowStyle::Resizable) - styleMask |= NSWindowStyleMaskResizable; - } + if (m_windowStyle && WindowStyle::Undecorated) { + styleMask |= NSWindowStyleMaskBorderless; + } else { + styleMask |= (NSWindowStyleMaskTitled | NSWindowStyleMaskClosable); - m_data->window = [[BriskWindow alloc] initWithContentRect:contentRect - styleMask:styleMask - backing:NSBackingStoreBuffered - defer:NO]; + if (m_windowStyle && WindowStyle::Resizable) + styleMask |= NSWindowStyleMaskResizable; + } - if (m_data->window == nil) { - BRISK_SOFT_ASSERT_MSG("Cocoa: Failed to create window", false); - return false; - } + m_data->window = [[BriskWindow alloc] initWithContentRect:contentRect + styleMask:styleMask + backing:NSBackingStoreBuffered + defer:NO]; - if (initialPos.x == dontCare || initialPos.y == dontCare) { - [m_data->window center]; - m_data->cascadePoint = [m_data->window cascadeTopLeftFromPoint:m_data->cascadePoint]; - } + if (m_data->window == nil) { + BRISK_SOFT_ASSERT_MSG("Cocoa: Failed to create window", false); + return false; + } - if (m_windowStyle && WindowStyle::Resizable) { - const NSWindowCollectionBehavior behavior = - NSWindowCollectionBehaviorFullScreenPrimary | NSWindowCollectionBehaviorManaged; - [m_data->window setCollectionBehavior:behavior]; - } else { - const NSWindowCollectionBehavior behavior = NSWindowCollectionBehaviorFullScreenNone; - [m_data->window setCollectionBehavior:behavior]; - } + if (initialPos.x == dontCare || initialPos.y == dontCare) { + [m_data->window center]; + m_data->cascadePoint = [m_data->window cascadeTopLeftFromPoint:m_data->cascadePoint]; + } + + if (m_windowStyle && WindowStyle::Resizable) { + const NSWindowCollectionBehavior behavior = + NSWindowCollectionBehaviorFullScreenPrimary | NSWindowCollectionBehaviorManaged; + [m_data->window setCollectionBehavior:behavior]; + } else { + const NSWindowCollectionBehavior behavior = NSWindowCollectionBehaviorFullScreenNone; + [m_data->window setCollectionBehavior:behavior]; + } - if (m_windowStyle && WindowStyle::TopMost) - [m_data->window setLevel:NSFloatingWindowLevel]; + if (m_windowStyle && WindowStyle::TopMost) + [m_data->window setLevel:NSFloatingWindowLevel]; - // if (wndconfig->maximized) - // [m_data->window zoom:nil]; + // if (wndconfig->maximized) + // [m_data->window zoom:nil]; - // if (strlen(wndconfig->ns.frameName)) - // [m_data->window setFrameAutosaveName:@(wndconfig->ns.frameName)]; + // if (strlen(wndconfig->ns.frameName)) + // [m_data->window setFrameAutosaveName:@(wndconfig->ns.frameName)]; + } m_data->view = [[BriskView alloc] initWithWindow:this]; - [m_data->window setContentView:m_data->view]; - [m_data->window makeFirstResponder:m_data->view]; - [m_data->window setTitle:toNSString(m_window->m_title)]; - [m_data->window setDelegate:m_data->delegate]; - [m_data->window setAcceptsMouseMovedEvents:YES]; - [m_data->window setRestorable:NO]; + if (topLevel) { + [m_data->window setContentView:m_data->view]; + [m_data->window makeFirstResponder:m_data->view]; + [m_data->window setTitle:toNSString(m_window->m_title)]; + [m_data->window setDelegate:m_data->delegate]; + [m_data->window setAcceptsMouseMovedEvents:YES]; + [m_data->window setRestorable:NO]; - [m_data->window setTabbingMode:NSWindowTabbingModeDisallowed]; + [m_data->window setTabbingMode:NSWindowTabbingModeDisallowed]; + } else { + // Set rect for view to contentRect + [m_data->view setFrame:contentRect]; + if ([m_data->parent isKindOfClass:[NSWindow class]]) { + NSWindow* window = (NSWindow*)m_data->parent; + [window setContentView:m_data->view]; + [window makeFirstResponder:m_data->view]; + [window setAcceptsMouseMovedEvents:YES]; + } else { // NSView + NSView* view = (NSView*)m_data->parent; + [view addSubview:m_data->view]; + } + } NSRect frame = m_data->view.frame; m_windowSize = fromNSSize(frame.size); @@ -1032,6 +1095,8 @@ static float transformYCocoa(float y) { } void PlatformWindow::setSizeLimits(Size minSize, Size maxSize) { + if (m_data->window == nil) + return; @autoreleasepool { if (minSize.width == dontCare || minSize.height == dontCare) [m_data->window setContentMinSize:NSMakeSize(0, 0)]; @@ -1047,6 +1112,8 @@ static float transformYCocoa(float y) { } void PlatformWindow::setStyle(WindowStyle windowStyle) { + if (m_data->window == nil) + return; m_windowStyle = windowStyle; @autoreleasepool { @@ -1091,16 +1158,46 @@ static float transformYCocoa(float y) { void PlatformWindow::setSize(Size size) { @autoreleasepool { - NSRect contentRect = [m_data->window contentRectForFrameRect:[m_data->window frame]]; - contentRect.origin.y += contentRect.size.height - size.height; - contentRect.size = NSMakeSize(size.width, size.height); - [m_data->window setFrame:[m_data->window frameRectForContentRect:contentRect] display:YES]; + if (m_data->window != nil) { + NSRect contentRect = [m_data->window contentRectForFrameRect:[m_data->window frame]]; + + contentRect.origin.y += contentRect.size.height - size.height; + contentRect.size = NSMakeSize(size.width, size.height); + + [m_data->window setFrame:[m_data->window frameRectForContentRect:contentRect] display:YES]; + } else { + NSView* v = m_data->view; + const BOOL needsFlip = ![v isFlipped]; + + NSRect frame = [v frame]; + const CGFloat oldHeight = frame.size.height; + + frame.size = NSMakeSize(size.width, size.height); + + if (needsFlip) { + NSView* parent = [v superview]; + const CGFloat parentHeight = parent ? parent.bounds.size.height : 0; + + frame.origin.y += (oldHeight - size.height); + if (parent) + frame.origin.y = MIN(frame.origin.y, parentHeight - size.height); + } + + [v setFrame:frame]; + } } } bool PlatformWindow::cursorInContentArea() const { - const NSPoint pos = [m_data->window mouseLocationOutsideOfEventStream]; - return [m_data->view mouse:pos inRect:[m_data->view frame]]; + @autoreleasepool { + if (m_data->window != nil) { + const NSPoint pos = [m_data->window mouseLocationOutsideOfEventStream]; + return [m_data->view mouse:pos inRect:[m_data->view frame]]; + } else { + const NSPoint pos = [[m_data->view window] mouseLocationOutsideOfEventStream]; + return [m_data->view mouse:pos inRect:[m_data->view frame]]; + } + } } void PlatformWindow::updateCursorImage() { @@ -1122,12 +1219,33 @@ static float transformYCocoa(float y) { void PlatformWindow::setPosition(Point point) { @autoreleasepool { - const NSRect contentRect = [m_data->view frame]; - const NSRect dummyRect = - NSMakeRect(point.x, transformYCocoa(point.y + contentRect.size.height - 1), 0, 0); - const NSRect frameRect = [m_data->window frameRectForContentRect:dummyRect]; - [m_data->window setFrameOrigin:frameRect.origin]; - } // autoreleasepool + if (m_data->window != nil) { + const NSRect contentRect = [m_data->view frame]; + const CGFloat flippedY = transformYCocoa(point.y + contentRect.size.height - 1); + + const NSRect dummyRect = NSMakeRect(point.x, flippedY, 0, 0); + + const NSRect frameRect = [m_data->window frameRectForContentRect:dummyRect]; + + [m_data->window setFrameOrigin:frameRect.origin]; + } else { + NSView* v = m_data->view; + const BOOL needsFlip = ![v isFlipped]; + + NSRect frame = [v frame]; + + if (needsFlip) { + NSView* parent = [v superview]; + const CGFloat parentHeight = parent ? parent.bounds.size.height : 0; + + frame.origin = NSMakePoint(point.x, parentHeight - point.y - frame.size.height); + } else { + frame.origin = NSMakePoint(point.x, point.y); + } + + [v setFrame:frame]; + } + } } HiDPIMode hiDPIMode() { diff --git a/src/window/PlatformWindow_Linux.cpp b/src/window/PlatformWindow_Linux.cpp index 90f9a8c5..c189f012 100644 --- a/src/window/PlatformWindow_Linux.cpp +++ b/src/window/PlatformWindow_Linux.cpp @@ -124,7 +124,9 @@ bool PlatformWindow::createWindow() { }); glfwSetWindowIconifyCallback(m_data->win, nullptr); glfwSetWindowMaximizeCallback(m_data->win, nullptr); - glfwSetWindowRefreshCallback(m_data->win, nullptr); + glfwSetWindowRefreshCallback(m_data->win, [](GLFWwindow* gw) { + reinterpret_cast(glfwGetWindowUserPointer(gw))->requestRedraw(); + }); glfwSetWindowContentScaleCallback(m_data->win, [](GLFWwindow* gw, float scalex, float scaley) { auto* window = reinterpret_cast(glfwGetWindowUserPointer(gw)); window->m_scale = std::max(scalex, scaley); @@ -146,33 +148,37 @@ bool PlatformWindow::createWindow() { window->windowResized(window->m_windowSize, window->m_framebufferSize); }); glfwSetKeyCallback(m_data->win, [](GLFWwindow* gw, int key, int scancode, int action, int mods) { - reinterpret_cast(glfwGetWindowUserPointer(gw)) - ->keyEvent(static_cast(key), scancode, static_cast(action), - static_cast(mods)); + std::ignore = reinterpret_cast(glfwGetWindowUserPointer(gw)) + ->keyEvent(static_cast(key), scancode, static_cast(action), + static_cast(mods)); }); glfwSetCharCallback(m_data->win, [](GLFWwindow* gw, unsigned int codepoint) { - reinterpret_cast(glfwGetWindowUserPointer(gw))->charEvent(codepoint, false); + std::ignore = + reinterpret_cast(glfwGetWindowUserPointer(gw))->charEvent(codepoint, false); }); glfwSetCursorPosCallback(m_data->win, [](GLFWwindow* gw, double xpos, double ypos) { PointOf cur{ xpos, ypos }; - reinterpret_cast(glfwGetWindowUserPointer(gw))->mouseMove(cur, Window::Unit::Screen); + std::ignore = reinterpret_cast(glfwGetWindowUserPointer(gw)) + ->mouseMove(cur, Window::Unit::Screen); }); glfwSetMouseButtonCallback(m_data->win, [](GLFWwindow* gw, int button, int action, int mods) { PointOf cur; glfwGetCursorPos(gw, &cur.x, &cur.y); - reinterpret_cast(glfwGetWindowUserPointer(gw)) - ->mouseEvent(static_cast(button), static_cast(action), - static_cast(mods), cur, Window::Unit::Screen); + std::ignore = reinterpret_cast(glfwGetWindowUserPointer(gw)) + ->mouseEvent(static_cast(button), static_cast(action), + static_cast(mods), cur, Window::Unit::Screen); }); glfwSetCursorEnterCallback(m_data->win, [](GLFWwindow* gw, int entered) { reinterpret_cast(glfwGetWindowUserPointer(gw))->mouseEnterOrLeave(entered); }); glfwSetScrollCallback(m_data->win, [](GLFWwindow* gw, double xoffset, double yoffset) { - reinterpret_cast(glfwGetWindowUserPointer(gw))->wheelEvent(xoffset, yoffset); + std::ignore = + reinterpret_cast(glfwGetWindowUserPointer(gw))->wheelEvent(xoffset, yoffset); }); glfwSetDropCallback(m_data->win, [](GLFWwindow* gw, int path_count, const char* paths[]) { std::vector files(paths, paths + path_count); - reinterpret_cast(glfwGetWindowUserPointer(gw))->filesDropped(std::move(files)); + std::ignore = + reinterpret_cast(glfwGetWindowUserPointer(gw))->filesDropped(std::move(files)); }); glfwGetWindowPos(m_data->win, &m_position.x, &m_position.y); @@ -189,9 +195,12 @@ PlatformWindow::~PlatformWindow() { m_data->win = nullptr; } -PlatformWindow::PlatformWindow(Window* window, Size windowSize, Point position, WindowStyle style) +PlatformWindow::PlatformWindow(Window* window, Size windowSize, Point position, WindowStyle style, + NativeWindowHandle parent) : m_data(new PlatformWindowData{}), m_window(window), m_windowStyle(style), m_windowSize(windowSize), m_position(position) { + + BRISK_ASSERT_MSG("Setting parent window is not supported on Linux due to limitations of GLFW", !parent); mustBeMainThread(); BRISK_ASSERT(m_window); diff --git a/src/window/PlatformWindow_Windows.cpp b/src/window/PlatformWindow_Windows.cpp index ca33665b..25ee0e79 100644 --- a/src/window/PlatformWindow_Windows.cpp +++ b/src/window/PlatformWindow_Windows.cpp @@ -42,7 +42,10 @@ namespace Brisk { -static const wchar_t* propKey = L"CC"; +constexpr UINT resizeMoveTimerId = 0x1234; +constexpr uint32_t resizeMoveTimerInterval = 16; // ~60 FPS + +static const wchar_t* propKey = L"CC"; static struct { ATOM helperWindowClass = {}; @@ -61,6 +64,7 @@ static LRESULT CALLBACK helperWindowProc(HWND hWnd, UINT uMsg, WPARAM wParam, LP struct PlatformWindowData { HWND hWnd{}; + HWND hParent{}; WCHAR highSurrogate{}; Point mousePos{ -1, -1 }; bool cursorTracked = false; @@ -95,7 +99,10 @@ static KeyModifiers getKeyMods() { return mods; } -static DWORD getWindowStyle(WindowStyle style) { +static DWORD getWindowStyle(WindowStyle style, bool child) { + if (child) { + return WS_CHILD | WS_CLIPSIBLINGS | WS_CLIPCHILDREN; + } DWORD result = WS_CLIPSIBLINGS | WS_CLIPCHILDREN | WS_SYSMENU | WS_MINIMIZEBOX; if (style && WindowStyle::Undecorated) result |= WS_POPUP; @@ -107,7 +114,10 @@ static DWORD getWindowStyle(WindowStyle style) { return result; } -static DWORD getWindowExStyle(WindowStyle style) { +static DWORD getWindowExStyle(WindowStyle style, bool child) { + if (child) { + return 0; + } DWORD result = 0; if (style && WindowStyle::TopMost) @@ -164,7 +174,7 @@ long long PlatformWindow::windowProc(MsgParams params) { } m_data->highSurrogate = 0; - charEvent(static_cast(codepoint), uMsg == WM_SYSCHAR); + return charEvent(static_cast(codepoint), uMsg == WM_SYSCHAR) ? 0 : 1; } return 0; @@ -178,8 +188,7 @@ long long PlatformWindow::windowProc(MsgParams params) { return TRUE; } - charEvent(static_cast(wParam), false); - return 0; + return charEvent(static_cast(wParam), false) ? 0 : 1; } case WM_KEYDOWN: @@ -247,17 +256,17 @@ long long PlatformWindow::windowProc(MsgParams params) { // HACK: Release both Shift keys on Shift up event, as when both // are pressed the first release does not emit any event // NOTE: The other half of this is in pollEvents - keyEvent(KeyCode::LeftShift, scancode, action, mods); - keyEvent(KeyCode::RightShift, scancode, action, mods); + std::ignore = keyEvent(KeyCode::LeftShift, scancode, action, mods); + std::ignore = keyEvent(KeyCode::RightShift, scancode, action, mods); + return 0; } else if (wParam == VK_SNAPSHOT) { // HACK: Key down is not reported for the Print Screen key - keyEvent(key, scancode, KeyAction::Press, mods); - keyEvent(key, scancode, KeyAction::Release, mods); + std::ignore = keyEvent(key, scancode, KeyAction::Press, mods); + std::ignore = keyEvent(key, scancode, KeyAction::Release, mods); + return 0; } else { - keyEvent(key, scancode, action, mods); + return keyEvent(key, scancode, action, mods) ? 0 : 1; } - - break; } case WM_LBUTTONDOWN: case WM_RBUTTONDOWN: @@ -290,7 +299,7 @@ long long PlatformWindow::windowProc(MsgParams params) { if (!m_mouseState.any()) SetCapture(m_data->hWnd); - mouseEvent(button, action, getKeyMods(), m_data->mousePos); + bool handled = mouseEvent(button, action, getKeyMods(), m_data->mousePos); if (!m_mouseState.any()) ReleaseCapture(); @@ -298,7 +307,7 @@ long long PlatformWindow::windowProc(MsgParams params) { if (uMsg == WM_XBUTTONDOWN || uMsg == WM_XBUTTONUP) return TRUE; - return 0; + return handled ? 0 : 1; } case WM_MOUSEMOVE: { @@ -317,9 +326,7 @@ long long PlatformWindow::windowProc(MsgParams params) { m_data->mousePos = { x, y }; - mouseMove(m_data->mousePos); - - return 0; + return mouseMove(m_data->mousePos) ? 0 : 1; } case WM_MOUSELEAVE: { m_data->cursorTracked = false; @@ -327,12 +334,28 @@ long long PlatformWindow::windowProc(MsgParams params) { return 0; } case WM_MOUSEWHEEL: { - wheelEvent(0.f, (SHORT)HIWORD(wParam) / (float)WHEEL_DELTA); - return 0; + return wheelEvent(0.f, (SHORT)HIWORD(wParam) / (float)WHEEL_DELTA) ? 0 : 1; } case WM_MOUSEHWHEEL: { // NOTE: The X-axis is inverted for consistency with macOS and X11 - wheelEvent(-((SHORT)HIWORD(wParam) / (float)WHEEL_DELTA), 0.f); + return wheelEvent(-((SHORT)HIWORD(wParam) / (float)WHEEL_DELTA), 0.f) ? 0 : 1; + } + + // Start timer when entering resize/move/menu modal loop + case WM_ENTERSIZEMOVE: + case WM_ENTERMENULOOP: { + SetTimer(m_data->hWnd, resizeMoveTimerId, resizeMoveTimerInterval, NULL); + break; + } + case WM_EXITSIZEMOVE: + case WM_EXITMENULOOP: { + KillTimer(m_data->hWnd, resizeMoveTimerId); + break; + } + case WM_TIMER: { + if (wParam == resizeMoveTimerId) { + requestRedraw(); + } return 0; } @@ -365,10 +388,13 @@ long long PlatformWindow::windowProc(MsgParams params) { } case WM_GETMINMAXINFO: { + if (m_data->hParent != nullptr) { + break; + } RECT frame = { 0 }; MINMAXINFO* mmi = (MINMAXINFO*)lParam; - const DWORD style = getWindowStyle(m_windowStyle); - const DWORD exStyle = getWindowExStyle(m_windowStyle); + const DWORD style = getWindowStyle(m_windowStyle, false); + const DWORD exStyle = getWindowExStyle(m_windowStyle, false); const Size scaledMinSize = m_minSize; const Size scaledMaxSize = m_maxSize; @@ -403,6 +429,7 @@ long long PlatformWindow::windowProc(MsgParams params) { return 0; } case WM_PAINT: { + requestRedraw(); break; } case WM_ERASEBKGND: { @@ -431,6 +458,9 @@ long long PlatformWindow::windowProc(MsgParams params) { } case WM_DPICHANGED: { + if (m_data->hParent != nullptr) { + break; + } const float xscale = HIWORD(wParam) / (float)USER_DEFAULT_SCREEN_DPI; const float yscale = LOWORD(wParam) / (float)USER_DEFAULT_SCREEN_DPI; @@ -469,7 +499,7 @@ long long PlatformWindow::windowProc(MsgParams params) { DragQueryPoint(drop, &pt); m_data->mousePos = { pt.x, pt.y }; - mouseMove(m_data->mousePos); + std::ignore = mouseMove(m_data->mousePos); for (int i = 0; i < count; i++) { std::wstring ws; @@ -479,10 +509,10 @@ long long PlatformWindow::windowProc(MsgParams params) { paths.push_back(wcsToUtf8(ws)); } - filesDropped(std::move(paths)); + bool handled = filesDropped(std::move(paths)); DragFinish(drop); - return 0; + return handled ? 0 : 1; } } return DefWindowProcW(m_data->hWnd, uMsg, wParam, lParam); @@ -616,13 +646,15 @@ bool PlatformWindow::createWindow() { RECT rect = { 0, 0, size.width, size.height }; - DWORD style = getWindowStyle(m_windowStyle); - DWORD exStyle = getWindowExStyle(m_windowStyle); + DWORD style = getWindowStyle(m_windowStyle, m_data->hParent != nullptr); + DWORD exStyle = getWindowExStyle(m_windowStyle, m_data->hParent != nullptr); - if (isOsWindows10(Windows10Version::AnniversaryUpdate)) { - AdjustWindowRectExForDpi(&rect, style, FALSE, exStyle, primaryDpi.x); - } else { - AdjustWindowRectEx(&rect, style, FALSE, exStyle); + if (m_data->hParent == nullptr) { + if (isOsWindows10(Windows10Version::AnniversaryUpdate)) { + AdjustWindowRectExForDpi(&rect, style, FALSE, exStyle, primaryDpi.x); + } else { + AdjustWindowRectEx(&rect, style, FALSE, exStyle); + } } std::wstring wideTitle = utf8ToWcs(m_window->m_title); @@ -630,8 +662,8 @@ bool PlatformWindow::createWindow() { initialPos.x == dontCare ? CW_USEDEFAULT : initialPos.x, initialPos.y == dontCare ? CW_USEDEFAULT : initialPos.y, rect.right - rect.left, rect.bottom - rect.top, - nullptr, // No parent window - nullptr, // No window menu + m_data->hParent, // No parent window + nullptr, // No window menu winInstance, (LPVOID)this); SetPropW(m_data->hWnd, propKey, this); @@ -651,25 +683,27 @@ bool PlatformWindow::createWindow() { float newScale = dpi.longestSide() / static_cast(USER_DEFAULT_SCREEN_DPI); if (newScale != m_scale) { m_scale = newScale; - // Adjust window rect to account for DPI scaling of the window frame and - // (if enabled) DPI scaling of the content area - // This cannot be done until we know what monitor the window was placed on - // Only update the restored window rect as the window may be maximized - rect = { 0, 0, size.width, size.height }; + if (m_data->hParent == nullptr) { + // Adjust window rect to account for DPI scaling of the window frame and + // (if enabled) DPI scaling of the content area + // This cannot be done until we know what monitor the window was placed on + // Only update the restored window rect as the window may be maximized + rect = { 0, 0, size.width, size.height }; - if (isOsWindows10(Windows10Version::AnniversaryUpdate)) { - AdjustWindowRectExForDpi(&rect, style, FALSE, exStyle, GetDpiForWindow(m_data->hWnd)); - } else { - AdjustWindowRectEx(&rect, style, FALSE, exStyle); - } + if (isOsWindows10(Windows10Version::AnniversaryUpdate)) { + AdjustWindowRectExForDpi(&rect, style, FALSE, exStyle, GetDpiForWindow(m_data->hWnd)); + } else { + AdjustWindowRectEx(&rect, style, FALSE, exStyle); + } - WINDOWPLACEMENT wp = { .length = sizeof(wp) }; - GetWindowPlacement(m_data->hWnd, &wp); - OffsetRect(&rect, wp.rcNormalPosition.left - rect.left, wp.rcNormalPosition.top - rect.top); + WINDOWPLACEMENT wp = { .length = sizeof(wp) }; + GetWindowPlacement(m_data->hWnd, &wp); + OffsetRect(&rect, wp.rcNormalPosition.left - rect.left, wp.rcNormalPosition.top - rect.top); - wp.rcNormalPosition = rect; - wp.showCmd = SW_HIDE; - SetWindowPlacement(m_data->hWnd, &wp); + wp.rcNormalPosition = rect; + wp.showCmd = SW_HIDE; + SetWindowPlacement(m_data->hWnd, &wp); + } } DragAcceptFiles(m_data->hWnd, TRUE); @@ -689,9 +723,11 @@ PlatformWindow::~PlatformWindow() { DestroyWindow(m_data->hWnd); } -PlatformWindow::PlatformWindow(Window* window, Size windowSize, Point position, WindowStyle style) +PlatformWindow::PlatformWindow(Window* window, Size windowSize, Point position, WindowStyle style, + NativeWindowHandle parent) : m_data(new PlatformWindowData{}), m_window(window), m_windowStyle(style), m_windowSize(windowSize), m_position(position) { + m_data->hParent = parent.hWnd(); mustBeMainThread(); BRISK_ASSERT(m_window); @@ -710,27 +746,36 @@ void PlatformWindow::setTitle(std::string_view title) { } void PlatformWindow::setSize(Size size) { - RECT rect = { 0, 0, size.x, size.y }; - - if (isOsWindows10(Windows10Version::AnniversaryUpdate)) { - AdjustWindowRectExForDpi(&rect, getWindowStyle(m_windowStyle), FALSE, getWindowExStyle(m_windowStyle), - GetDpiForWindow(m_data->hWnd)); + if (m_data->hParent == nullptr) { + RECT rect = { 0, 0, size.x, size.y }; + if (isOsWindows10(Windows10Version::AnniversaryUpdate)) { + AdjustWindowRectExForDpi(&rect, getWindowStyle(m_windowStyle, m_data->hParent != 0), FALSE, + getWindowExStyle(m_windowStyle, m_data->hParent != 0), + GetDpiForWindow(m_data->hWnd)); + } else { + AdjustWindowRectEx(&rect, getWindowStyle(m_windowStyle, m_data->hParent != 0), FALSE, + getWindowExStyle(m_windowStyle, m_data->hParent != 0)); + } + SetWindowPos(m_data->hWnd, HWND_TOP, 0, 0, rect.right - rect.left, rect.bottom - rect.top, + SWP_NOACTIVATE | SWP_NOMOVE | SWP_NOOWNERZORDER | SWP_NOZORDER); } else { - AdjustWindowRectEx(&rect, getWindowStyle(m_windowStyle), FALSE, getWindowExStyle(m_windowStyle)); + SetWindowPos(m_data->hWnd, HWND_TOP, m_position.x, m_position.y, size.width, size.height, + SWP_NOACTIVATE | SWP_NOOWNERZORDER | SWP_NOZORDER); } - - SetWindowPos(m_data->hWnd, HWND_TOP, 0, 0, rect.right - rect.left, rect.bottom - rect.top, - SWP_NOACTIVATE | SWP_NOOWNERZORDER | SWP_NOMOVE | SWP_NOZORDER); } void PlatformWindow::setPosition(Point point) { RECT rect = { point.x, point.y, point.x, point.y }; - if (isOsWindows10(Windows10Version::AnniversaryUpdate)) { - AdjustWindowRectExForDpi(&rect, getWindowStyle(m_windowStyle), FALSE, getWindowExStyle(m_windowStyle), - GetDpiForWindow(m_data->hWnd)); - } else { - AdjustWindowRectEx(&rect, getWindowStyle(m_windowStyle), FALSE, getWindowExStyle(m_windowStyle)); + if (m_data->hParent == nullptr) { + if (isOsWindows10(Windows10Version::AnniversaryUpdate)) { + AdjustWindowRectExForDpi(&rect, getWindowStyle(m_windowStyle, m_data->hParent != 0), FALSE, + getWindowExStyle(m_windowStyle, m_data->hParent != 0), + GetDpiForWindow(m_data->hWnd)); + } else { + AdjustWindowRectEx(&rect, getWindowStyle(m_windowStyle, m_data->hParent != 0), FALSE, + getWindowExStyle(m_windowStyle, m_data->hParent != 0)); + } } SetWindowPos(m_data->hWnd, nullptr, rect.left, rect.top, 0, 0, @@ -760,27 +805,27 @@ void PlatformWindow::setStyle(WindowStyle windowStyle) { RECT rect; DWORD style = GetWindowLongW(m_data->hWnd, GWL_STYLE); style &= ~(WS_OVERLAPPEDWINDOW | WS_POPUP); - style |= getWindowStyle(m_windowStyle); - - GetClientRect(m_data->hWnd, &rect); + style |= getWindowStyle(m_windowStyle, m_data->hParent != 0); - if (isOsWindows10(Windows10Version::AnniversaryUpdate)) { - AdjustWindowRectExForDpi(&rect, style, FALSE, getWindowExStyle(m_windowStyle), - GetDpiForWindow(m_data->hWnd)); - } else { - AdjustWindowRectEx(&rect, style, FALSE, getWindowExStyle(m_windowStyle)); - } - - ClientToScreen(m_data->hWnd, (POINT*)&rect.left); - ClientToScreen(m_data->hWnd, (POINT*)&rect.right); SetWindowLongW(m_data->hWnd, GWL_STYLE, style); - SetWindowPos(m_data->hWnd, HWND_TOP, rect.left, rect.top, rect.right - rect.left, rect.bottom - rect.top, - SWP_FRAMECHANGED | SWP_NOACTIVATE | SWP_NOZORDER); + if (m_data->hParent == nullptr) { + GetClientRect(m_data->hWnd, &rect); - // TopMost - const HWND after = m_windowStyle && WindowStyle::TopMost ? HWND_TOPMOST : HWND_NOTOPMOST; - SetWindowPos(m_data->hWnd, after, 0, 0, 0, 0, SWP_NOACTIVATE | SWP_NOMOVE | SWP_NOSIZE); + if (isOsWindows10(Windows10Version::AnniversaryUpdate)) { + AdjustWindowRectExForDpi(&rect, style, FALSE, getWindowExStyle(m_windowStyle, false), + GetDpiForWindow(m_data->hWnd)); + } else { + AdjustWindowRectEx(&rect, style, FALSE, getWindowExStyle(m_windowStyle, false)); + } + ClientToScreen(m_data->hWnd, (POINT*)&rect.left); + ClientToScreen(m_data->hWnd, (POINT*)&rect.right); + SetWindowPos(m_data->hWnd, HWND_TOP, rect.left, rect.top, rect.right - rect.left, + rect.bottom - rect.top, SWP_FRAMECHANGED | SWP_NOACTIVATE | SWP_NOZORDER); + // TopMost + const HWND after = m_windowStyle && WindowStyle::TopMost ? HWND_TOPMOST : HWND_NOTOPMOST; + SetWindowPos(m_data->hWnd, after, 0, 0, 0, 0, SWP_NOACTIVATE | SWP_NOMOVE | SWP_NOSIZE); + } // Disabled EnableWindow(m_data->hWnd, m_windowStyle && WindowStyle::Disabled ? FALSE : TRUE); } @@ -1028,7 +1073,7 @@ void PlatformWindow::updateVisibility() { if (!window->m_keyState[+key]) continue; - window->keyEvent(key, scancode, KeyAction::Release, getKeyMods()); + std::ignore = window->keyEvent(key, scancode, KeyAction::Release, getKeyMods()); } } } diff --git a/src/window/Window.cpp b/src/window/Window.cpp index 3d47efd5..6601777b 100644 --- a/src/window/Window.cpp +++ b/src/window/Window.cpp @@ -51,9 +51,9 @@ static BindingRegistration frameStartTime_reg{ &frameStartTime, nullptr }; namespace Internal { constinit bool bufferedRendering = true; -constinit bool forceRenderEveryFrame = false; +constinit bool forceRenderEveryFrame = true; // false; -std::atomic_bool debugShowRenderTimeline{ false }; +bool debugShowRenderTimeline{ false }; Window* currentWindow = nullptr; Rc currentWindowPtr() { @@ -62,97 +62,76 @@ Rc currentWindowPtr() { } // namespace Internal void Window::iconify() { - mustBeUiThread(); + mustBeMainThread(); if (!m_platformWindow) return; - mainScheduler->dispatch([this] { - m_platformWindow->iconify(); - }); + m_platformWindow->iconify(); } void Window::maximize() { - mustBeUiThread(); + mustBeMainThread(); if (!m_platformWindow) return; - mainScheduler->dispatch([this] { - m_platformWindow->maximize(); - }); + m_platformWindow->maximize(); } void Window::restore() { - mustBeUiThread(); + mustBeMainThread(); if (!m_platformWindow) return; - mainScheduler->dispatch([this] { - m_platformWindow->restore(); - }); + m_platformWindow->restore(); } void Window::focus() { - mustBeUiThread(); + mustBeMainThread(); if (!m_platformWindow) return; - mainScheduler->dispatch([this] { - m_platformWindow->focus(); - }); -} - -void Window::mustBeUiThread() const { - if (m_attached && windowApplication) - windowApplication->mustBeUiThread(); + m_platformWindow->focus(); } void Window::setVisible(bool newVisible) { - mustBeUiThread(); + mustBeMainThread(); // Do not compare with current value of m_visible to allow setting the same value m_visible = newVisible; if (!m_platformWindow) { return; } - mainScheduler->dispatch([this]() { - m_platformWindow->updateVisibility(); - }); + m_platformWindow->updateVisibility(); } bool Window::isFocused() const { - mustBeUiThread(); + mustBeMainThread(); if (!m_platformWindow) return false; - return mainScheduler->dispatchAndWait([this] { - return m_platformWindow->isFocused(); - }); + return m_platformWindow->isFocused(); } bool Window::isIconified() const { - mustBeUiThread(); + mustBeMainThread(); if (!m_platformWindow) return false; - return mainScheduler->dispatchAndWait([this] { - return m_platformWindow->isIconified(); - }); + return m_platformWindow->isIconified(); } bool Window::isMaximized() const { - mustBeUiThread(); + mustBeMainThread(); if (!m_platformWindow) return false; - return mainScheduler->dispatchAndWait([=, this] { - return m_platformWindow->isMaximized(); - }); + return m_platformWindow->isMaximized(); } bool Window::isVisible() const { - mustBeUiThread(); + mustBeMainThread(); return m_visible; } Size Window::getSize() const { - mustBeUiThread(); + mustBeMainThread(); return m_windowSize; } Size Window::getFramebufferSize() const { - mustBeUiThread(); + mustBeMainThread(); if (!m_platformWindow) return {}; return m_framebufferSize; @@ -164,42 +143,36 @@ Size Window::framebufferSize() const { } std::string Window::getTitle() const { - mustBeUiThread(); + mustBeMainThread(); return m_title; } void Window::setTitle(std::string title) { - mustBeUiThread(); + mustBeMainThread(); if (title != m_title) { m_title = std::move(title); - mainScheduler->dispatch([=, this] { - if (m_platformWindow) - m_platformWindow->setTitle(m_title); - }); + if (m_platformWindow) + m_platformWindow->setTitle(m_title); } } void Window::setRectangle(Rectangle rect) { - mustBeUiThread(); + mustBeMainThread(); // Do not compare with current values of m_desired* to allow setting the same value m_position = rect.p1; m_windowSize = rect.size(); - mainScheduler->dispatch([=, this] { - if (m_platformWindow) { - m_platformWindow->setPosition(m_position); - m_platformWindow->setSize(m_windowSize); - } - }); + if (m_platformWindow) { + m_platformWindow->setPosition(m_position); + m_platformWindow->setSize(m_windowSize); + } } void Window::setPosition(Point pos) { - mustBeUiThread(); + mustBeMainThread(); // Do not compare with current values of m_desired* to allow setting the same value m_position = pos; - mainScheduler->dispatch([=, this] { - if (m_platformWindow) - m_platformWindow->setPosition(m_position); - }); + if (m_platformWindow) + m_platformWindow->setPosition(m_position); } void Window::setMinimumSize(Size size) { @@ -211,55 +184,45 @@ void Window::setMaximumSize(Size size) { } void Window::setMinimumMaximumSizes(Size minSize, Size maxSize) { - mustBeUiThread(); + mustBeMainThread(); // Do not compare with current values of m_maximumSize to allow setting the same value m_minimumSize = minSize; m_maximumSize = maxSize; - mainScheduler->dispatch([=, this] { - if (m_platformWindow) - m_platformWindow->setSizeLimits(m_minimumSize, m_maximumSize); - }); + if (m_platformWindow) + m_platformWindow->setSizeLimits(m_minimumSize, m_maximumSize); } void Window::setSize(Size size) { - mustBeUiThread(); + mustBeMainThread(); if (size != m_windowSize) { m_windowSize = size; - mainScheduler->dispatch([this, size = m_windowSize] { - if (m_platformWindow) - m_platformWindow->setSize(size); - }); + if (m_platformWindow) + m_platformWindow->setSize(size); } } void Window::setStyle(WindowStyle style) { - mustBeUiThread(); + mustBeMainThread(); if (style != m_style) { m_style = style; - mainScheduler->dispatch([style = m_style, this] { - if (m_platformWindow) - m_platformWindow->setStyle(style); - }); + if (m_platformWindow) + m_platformWindow->setStyle(style); } } void Window::recomputeScales() { mustBeMainThread(); - const float pixelRatio = - m_contentScale.load(std::memory_order_relaxed) * m_canvasScale.load(std::memory_order_relaxed); - BRISK_LOG_INFO("Pixel Scales content ({}) * canvas ({}) = {}", - m_contentScale.load(std::memory_order_relaxed), - m_canvasScale.load(std::memory_order_relaxed), pixelRatio); - uiScheduler->dispatch([this, pixelRatio] { - if (pixelRatio != m_pixelRatio) { - m_pixelRatio = pixelRatio; - Brisk::pixelRatio() = pixelRatio; - pixelRatioChanged(); - } - }); + const float pixelRatio = m_contentScale * m_canvasScale; + BRISK_LOG_INFO("Pixel Scales content ({}) * canvas ({}) = {}", m_contentScale, m_canvasScale, pixelRatio); + if (pixelRatio != m_pixelRatio) { + m_pixelRatio = pixelRatio; + Brisk::pixelRatio() = pixelRatio; + pixelRatioChanged(); + } } void Window::visibilityChanged(bool newIsVisible) { + CurrentWindowScope scope(this); onVisibilityChanged(newIsVisible); } @@ -270,7 +233,7 @@ void Window::attachedToApplication() { bindings->connect(Value{ &m_syncInterval, [this]() { if (m_target) - m_target->setVSyncInterval(m_syncInterval); + m_target->setVSyncInterval(m_vSync ? m_syncInterval : 0); } }, Value{ &windowApplication->syncInterval }); } @@ -319,10 +282,9 @@ void Window::paintStat(Canvas& canvas, Rectangle rect) { #endif , m_framebufferSize.width, m_framebufferSize.height, pixelRatio(), info.api, info.device, - m_bufferedRendering.load(std::memory_order_relaxed), - m_forceRenderEveryFrame.load(std::memory_order_relaxed)); + m_bufferedRendering, m_forceRenderEveryFrame); - uint64_t frameNumber = m_frameNumber.load(std::memory_order_relaxed); + uint64_t frameNumber = m_frameNumber; using namespace std::chrono_literals; @@ -389,9 +351,9 @@ void Window::paintDebug(RenderContext& context) { } void Window::doPaint() { - mustBeUiThread(); + mustBeMainThread(); + CurrentWindowScope scope(this); BRISK_ASSERT(m_encoder); - PerformanceDuration start_time = perfNow(); ObjCPool pool; high_res_clock::time_point renderStart; @@ -411,7 +373,7 @@ void Window::doPaint() { beforeFrame(); - uint64_t frameNumber = m_frameNumber.load(std::memory_order_relaxed); + uint64_t frameNumber = m_frameNumber; m_renderStat.beginFrame(frameNumber); @@ -495,34 +457,30 @@ void Window::doPaint() { void Window::beforeDestroying() {} Window::~Window() { - mainScheduler->dispatchAndWait([this]() { - closeWindow(); - }); + closeWindow(); } WindowStyle Window::getStyle() const { - mustBeUiThread(); + mustBeMainThread(); return m_style; } PointOf Window::getMousePosition() const { - mustBeUiThread(); + mustBeMainThread(); return m_mousePoint; } PointOf Window::getPosition() const { - mustBeUiThread(); + mustBeMainThread(); return m_position; } void Window::setCursor(Cursor cursor) { - mustBeUiThread(); + mustBeMainThread(); if (cursor != m_cursor) { m_cursor = cursor; - mainScheduler->dispatch([=, this] { - if (m_platformWindow) - m_platformWindow->setCursor(m_cursor); - }); + if (m_platformWindow) + m_platformWindow->setCursor(m_cursor); } } @@ -539,17 +497,17 @@ float Window::contentScale() const noexcept { } Rectangle Window::getBounds() const { - mustBeUiThread(); + mustBeMainThread(); return Rectangle{ Point(0, 0), m_windowSize }; } Rectangle Window::getRectangle() const { - mustBeUiThread(); + mustBeMainThread(); return Rectangle{ m_position, m_windowSize }; } Rectangle Window::getFramebufferBounds() const { - mustBeUiThread(); + mustBeMainThread(); return Rectangle{ Point(0, 0), m_framebufferSize }; } @@ -562,14 +520,10 @@ void Window::hide() { } void Window::close() { - mustBeUiThread(); + mustBeMainThread(); hide(); + closeWindow(); // destroys m_platformWindow m_closing = true; // forces application to remove this window from the windows list - auto wk = weak_from_this(); - mainScheduler->dispatch([wk]() { - if (auto lk = std::static_pointer_cast(wk.lock())) - lk->closeWindow(); // destroys m_platformWindow - }); } NativeWindowHandle Window::getHandle() const { @@ -583,7 +537,7 @@ void Window::initializeRenderer() { return; m_encoder = renderDevice()->createEncoder(); m_target = renderDevice()->createWindowTarget(this); - m_target->setVSyncInterval(m_syncInterval); + m_target->setVSyncInterval(m_vSync ? m_syncInterval : 0); } void Window::finalizeRenderer() { @@ -594,27 +548,23 @@ void Window::finalizeRenderer() { } Bytes Window::windowPlacement() const { - mustBeUiThread(); + mustBeMainThread(); if (!m_platformWindow) { return {}; } - return uiScheduler->dispatchAndWait([this]() -> Bytes { - return m_platformWindow->placement(); - }); + return m_platformWindow->placement(); } void Window::setWindowPlacement(BytesView data) { - mustBeUiThread(); + mustBeMainThread(); if (!m_platformWindow) { return; } - uiScheduler->dispatchAndWait([this, data]() { // must wait, otherwise dangling reference - m_platformWindow->setPlacement(data); - }); + m_platformWindow->setPlacement(data); } -void Window::disableKeyHandling() { - m_keyHandling = false; +void Window::setKeyHandling(bool keyHandling) { + m_keyHandling = keyHandling; } void Window::captureFrame(function)> callback) { @@ -633,7 +583,7 @@ void Window::openWindow() { mustBeMainThread(); if (m_platformWindow) return; - m_platformWindow.reset(new PlatformWindow(this, m_windowSize, m_position, m_style)); + m_platformWindow.reset(new PlatformWindow(this, m_windowSize, m_position, m_style, m_parent)); recomputeScales(); initializeRenderer(); m_rendering = true; @@ -648,26 +598,28 @@ void Window::closeWindow() { mustBeMainThread(); if (!m_platformWindow) return; - windowApplication->updateAndWait(); m_rendering = false; finalizeRenderer(); m_platformWindow.reset(); } -void Window::keyEvent(KeyCode key, int scancode, KeyAction action, KeyModifiers mods) { +bool Window::keyEvent(KeyCode key, int scancode, KeyAction action, KeyModifiers mods) { + CurrentWindowScope scope(this); if (!m_keyHandling) - return; + return false; m_mods = mods; - onKeyEvent(key, scancode, action, mods); + return onKeyEvent(key, scancode, action, mods); } -void Window::charEvent(char32_t character) { +bool Window::charEvent(char32_t character) { + CurrentWindowScope scope(this); if (!m_keyHandling) - return; - onCharEvent(character); + return false; + return onCharEvent(character); } -void Window::mouseEvent(MouseButton button, MouseAction action, KeyModifiers mods, PointF point) { +bool Window::mouseEvent(MouseButton button, MouseAction action, KeyModifiers mods, PointF point) { + CurrentWindowScope scope(this); m_mods = mods; m_mousePoint = point; @@ -684,55 +636,74 @@ void Window::mouseEvent(MouseButton button, MouseAction action, KeyModifiers mod triClick = dblClick && m_doubleClicked; m_doubleClicked = dblClick; } - onMouseEvent(button, action, mods, point, triClick ? 3 : dblClick ? 2 : 1); + bool handled = onMouseEvent(button, action, mods, point, triClick ? 3 : dblClick ? 2 : 1); if (button == MouseButton::Left && action == MouseAction::Release) { m_downPoint = std::nullopt; } + return handled; } -void Window::mouseMove(PointF point) { +bool Window::mouseMove(PointF point) { + CurrentWindowScope scope(this); m_mousePoint = point; - onMouseMove(point); + return onMouseMove(point); } -void Window::wheelEvent(float x, float y) { - onWheelEvent(x, y); +bool Window::wheelEvent(float x, float y) { + CurrentWindowScope scope(this); + return onWheelEvent(x, y); } void Window::mouseEnter() { + CurrentWindowScope scope(this); onMouseEnter(); } void Window::mouseLeave() { + CurrentWindowScope scope(this); onMouseLeave(); } -void Window::filesDropped(std::vector files) { - onFilesDropped(files); +bool Window::filesDropped(std::vector files) { + CurrentWindowScope scope(this); + return onFilesDropped(files); } void Window::focusChange(bool newIsFocused) { + CurrentWindowScope scope(this); onFocusChange(newIsFocused); } void Window::pixelRatioChanged() {} -void Window::onKeyEvent(KeyCode key, int scancode, KeyAction action, KeyModifiers mods) {} +bool Window::onKeyEvent(KeyCode key, int scancode, KeyAction action, KeyModifiers mods) { + return false; +} -void Window::onCharEvent(char32_t character) {} +bool Window::onCharEvent(char32_t character) { + return false; +} -void Window::onMouseEvent(MouseButton button, MouseAction action, KeyModifiers mods, PointF point, - int conseqClicks) {} +bool Window::onMouseEvent(MouseButton button, MouseAction action, KeyModifiers mods, PointF point, + int conseqClicks) { + return false; +} -void Window::onMouseMove(PointF point) {} +bool Window::onMouseMove(PointF point) { + return false; +} -void Window::onWheelEvent(float x, float y) {} +bool Window::onWheelEvent(float x, float y) { + return false; +} void Window::onMouseEnter() {} void Window::onMouseLeave() {} -void Window::onFilesDropped(std::vector files) {} +bool Window::onFilesDropped(std::vector files) { + return false; +} void Window::onFocusChange(bool gained) {} @@ -753,6 +724,7 @@ void Window::paint(RenderContext& context, bool fullRepaint) {} void Window::beforeFrame() {} void Window::closeAttempt() { + CurrentWindowScope scope(this); switch (shouldClose()) { case CloseAction::Close: return close(); @@ -764,6 +736,7 @@ void Window::closeAttempt() { } void Window::windowResized(Size windowSize, Size framebufferSize) { + CurrentWindowScope scope(this); if (windowSize != m_windowSize || framebufferSize != m_framebufferSize) { m_windowSize = windowSize; m_framebufferSize = framebufferSize; @@ -772,6 +745,7 @@ void Window::windowResized(Size windowSize, Size framebufferSize) { } void Window::windowMoved(Point position) { + CurrentWindowScope scope(this); if (position != m_position) { m_position = position; onWindowMoved(m_position); @@ -779,6 +753,7 @@ void Window::windowMoved(Point position) { } void Window::windowNonClientClicked() { + CurrentWindowScope scope(this); onNonClientClicked(); } @@ -786,9 +761,7 @@ void Window::setOwner(Rc window) { m_owner = window; if (!m_platformWindow) return; - mainScheduler->dispatchAndWait([this, window]() { - m_platformWindow->setOwner(window); - }); + m_platformWindow->setOwner(window); } void Window::enterModal() { @@ -796,15 +769,13 @@ void Window::enterModal() { } void Window::exitModal() { - mustBeUiThread(); + mustBeMainThread(); if (m_style && WindowStyle::Disabled) { m_style &= ~WindowStyle::Disabled; - mainScheduler->dispatch([style = m_style, this] { - if (m_platformWindow) { - m_platformWindow->setStyle(style); - m_platformWindow->focus(); - } - }); + if (m_platformWindow) { + m_platformWindow->setStyle(m_style); + m_platformWindow->focus(); + } } } @@ -824,6 +795,7 @@ PlatformWindow* Window::platformWindow() { } void Window::windowStateChanged(bool isIconified, bool isMaximized) { + CurrentWindowScope scope(this); onWindowStateChanged(isIconified, isMaximized); } @@ -849,6 +821,17 @@ bool Window::forceRenderEveryFrame() const noexcept { return m_forceRenderEveryFrame; } +void Window::setVSync(bool vSync) { + m_vSync = vSync; + if (m_target) { + m_target->setVSyncInterval(m_vSync ? m_syncInterval : 0); + } +} + +bool Window::vSync() const noexcept { + return m_vSync; +} + Rc Window::display() const { NativeWindowHandle handle = getHandle(); if (!handle) @@ -974,4 +957,20 @@ const FrameStat& RenderStat::back() const noexcept { return m_frames[m_lastFrame % capacity]; } +NativeWindowHandle Window::parent() const { + return m_parent; +} + +bool Window::isTopLevel() const noexcept { + return !m_parent; +} + +void Window::setParent(NativeWindowHandle parent) { + m_parent = parent; + if (m_platformWindow) { + BRISK_LOG_WARN("The window's parent has changed. The new parent will not be applied until the window " + "is reopened"); + } +} + } // namespace Brisk diff --git a/src/window/WindowApplication.cpp b/src/window/WindowApplication.cpp index f15f9619..5e0a8436 100644 --- a/src/window/WindowApplication.cpp +++ b/src/window/WindowApplication.cpp @@ -32,23 +32,18 @@ namespace Brisk { -bool isStandaloneApp = false; -bool separateUiThread = true; +bool isStandaloneApp = false; Nullable windowApplication; void WindowApplication::quit(int exitCode) { + mustBeMainThread(); m_exitCode = exitCode; - if (Internal::wakeUpMainThread) { - Internal::wakeUpMainThread(); - } } -std::vector> WindowApplication::windows() const { - if (isMainThread()) - return m_mainData.m_windows; - else - return m_uiData.m_windows; +const std::vector>& WindowApplication::windows() const { + mustBeMainThread(); + return m_windows; } void WindowApplication::serialize(const Serialization& serialization) { @@ -60,7 +55,8 @@ void WindowApplication::serialize(const Serialization& serialization) { serialization(Value{ &subPixelText }, "subPixelText"); } -WindowApplication::WindowApplication() : m_separateUiThread(separateUiThread) { +WindowApplication::WindowApplication() { + initializeCommon(); BRISK_ASSERT(windowApplication.get() == nullptr); windowApplication = this; mustBeMainThread(); @@ -78,14 +74,6 @@ WindowApplication::WindowApplication() : m_separateUiThread(separateUiThread) { PlatformWindow::initialize(); - if (m_separateUiThread) { - m_uiThread = std::thread(&WindowApplication::uiThreadBody, this); - m_uiThreadStarted.acquire(); - } else { - uiScheduler = rcnew TaskQueue(); - afterRenderQueue = rcnew TaskQueue(); - } - if (settings) { Json data = settings->data("/display"); deserializeFrom(data); @@ -102,24 +90,13 @@ WindowApplication::~WindowApplication() { settings->setData("/display", data); } - if (m_separateUiThread) { - m_uiThreadTerminate = true; - while (!m_uiThreadTerminated) { - mainScheduler->process(); - std::this_thread::yield(); - } - m_uiThread.join(); - m_uiThread = {}; - } - - m_mainData.m_windows.clear(); + m_windows.clear(); PlatformWindow::finalize(); - onApplicationClose->process(); - m_mainData = {}; - m_uiData = {}; + onApplicationClose(); windowApplication = nullptr; + finalizeCommon(); } void WindowApplication::processEvents(bool wait) { @@ -131,41 +108,21 @@ void WindowApplication::processEvents(bool wait) { PlatformWindow::pollEvents(); } -constexpr static int maximumFPS = 120; +constexpr static int maximumFPS = 180; void WindowApplication::renderWindows() { - uiScheduler->process(); + mustBeMainThread(); using std::chrono::steady_clock; steady_clock::time_point stopTime = steady_clock::now() + std::chrono::milliseconds(1000 / maximumFPS); - std::vector> windows = this->windows(); + std::vector> windows = m_windows; for (Rc w : windows) { if (w->m_rendering) { - Window* curWindow = w.get(); - std::swap(Internal::currentWindow, curWindow); - Internal::currentWindow = w.get(); - SCOPE_EXIT { - std::swap(Internal::currentWindow, curWindow); - }; w->doPaint(); } } - afterRenderQueue->process(); std::this_thread::sleep_until(stopTime); } -void WindowApplication::uiThreadBody() { - uiScheduler = rcnew TaskQueue(); - afterRenderQueue = rcnew TaskQueue(); - setThreadName("UIThread"); - m_uiThreadStarted.release(); - while (!m_uiThreadTerminate) { - renderWindows(); - } - m_uiThreadTerminated = true; - uiScheduler = nullptr; - afterRenderQueue = nullptr; -} - void WindowApplication::start() { mustBeMainThread(); if (m_active) { @@ -176,46 +133,38 @@ void WindowApplication::start() { openWindows(); } -void WindowApplication::updateAndWait() { - mustBeMainThread(); - removeClosed(); - if (!afterRenderQueue->isOnThread()) - afterRenderQueue->waitForCompletion(); -} - void WindowApplication::removeClosed() { mustBeMainThread(); - bool changed = false; - QuitCondition cond = m_quitCondition.load(std::memory_order::relaxed); - for (int i = m_mainData.m_windows.size() - 1; i >= 0; --i) { - if (m_mainData.m_windows[i]->m_closing) { - m_mainData.m_windows.erase(m_mainData.m_windows.begin() + i); - changed = true; - if (i == 0 && cond == QuitCondition::FirstWindowClosed) { + for (int i = m_windows.size() - 1; i >= 0; --i) { + if (m_windows[i]->m_closing) { + auto w = m_windows[i]; + m_windows.erase(m_windows.begin() + i); + if (i == 0 && m_quitCondition == QuitCondition::FirstWindowClosed) { quit(); + } else { + w->m_closing = false; // Reset flag in case window is reused } } } - if (m_mainData.m_windows.empty() && (cond == QuitCondition::AllWindowsClosed + if (m_windows.empty() && (m_quitCondition == QuitCondition::AllWindowsClosed #if !defined BRISK_MACOS - || cond == QuitCondition::PlatformDependant + || m_quitCondition == QuitCondition::PlatformDependant #endif - )) { + )) { quit(); } - if (changed) - windowsChanged(); } -void WindowApplication::cycle(bool wait) { +void WindowApplication::cycle(ProcessEventsMode mode) { mustBeMainThread(); removeClosed(); - processEvents(wait); + if (mode != ProcessEventsMode::DontCheck) { + processEvents(mode == ProcessEventsMode::CheckAndWait); + } { mainScheduler->process(); processTimers(); - if (!m_separateUiThread) - renderWindows(); + renderWindows(); } } @@ -235,12 +184,12 @@ int WindowApplication::run() { start(); while (!hasQuit()) { - cycle(true); + cycle(separateRenderThread ? ProcessEventsMode::CheckAndWait : ProcessEventsMode::CheckOnly); } stop(); - return m_exitCode; + return m_exitCode.value_or(0); } int WindowApplication::run(Rc mainWindow) { @@ -249,27 +198,12 @@ int WindowApplication::run(Rc mainWindow) { } bool WindowApplication::hasWindow(const Rc& window) { - return mainScheduler->dispatchAndWait([&]() { - return std::find(m_mainData.m_windows.begin(), m_mainData.m_windows.end(), window) != - m_mainData.m_windows.end(); - }); -} - -VoidFunc WindowApplication::idleFunc() { - VoidFunc func; - if (m_separateUiThread && uiScheduler->isOnThread()) - func = [this]() { - renderWindows(); - }; - return func; + return std::find(m_windows.begin(), m_windows.end(), window) != m_windows.end(); } void WindowApplication::systemModal(function body) { ModalMode modal; - - waitFuture(idleFunc(), mainScheduler->dispatch([&]() { - body(modal.owner.get()); - })); + body(modal.owner.get()); } void WindowApplication::modalRun(Rc modalWindow) { @@ -281,34 +215,23 @@ void WindowApplication::modalRun(Rc modalWindow) { BRISK_ASSERT(m_active); - waitFuture(idleFunc(), mainScheduler->dispatch([this, modalWindow]() { - modalWindow->openWindow(); - while (!hasQuit() && hasWindow(modalWindow)) { - cycle(true); - } - })); + modalWindow->openWindow(); + while (!hasQuit() && hasWindow(modalWindow)) { + cycle(ProcessEventsMode::CheckAndWait); + } } void WindowApplication::addWindow(Rc window, bool makeVisible) { - waitFuture(idleFunc(), mainScheduler->dispatch([this, window = std::move(window), makeVisible]() { - m_mainData.m_windows.push_back(window); - window->attachedToApplication(); - windowsChanged(); + m_windows.push_back(window); + window->attachedToApplication(); - if (makeVisible && m_active) { - window->openWindow(); - } - })); -} - -void WindowApplication::mustBeUiThread() { - if (m_uiThread.get_id() != std::thread::id{}) { - BRISK_ASSERT(std::this_thread::get_id() == m_uiThread.get_id()); + if (makeVisible && m_active) { + window->openWindow(); } } bool WindowApplication::hasQuit() const { - return m_exitCode.load() != noExitCode; + return m_exitCode.has_value(); } double WindowApplication::doubleClickDistance() const { @@ -321,14 +244,14 @@ double WindowApplication::doubleClickTime() const { void WindowApplication::openWindows() { mustBeMainThread(); - for (Rc w : m_mainData.m_windows) { + for (Rc w : m_windows) { w->openWindow(); } } void WindowApplication::closeWindows() { mustBeMainThread(); - for (Rc w : m_mainData.m_windows) { + for (Rc w : m_windows) { w->closeWindow(); } } @@ -344,11 +267,4 @@ void WindowApplication::setQuitCondition(QuitCondition value) { QuitCondition WindowApplication::quitCondition() const noexcept { return m_quitCondition; } - -void WindowApplication::windowsChanged() { - auto windows = m_mainData.m_windows; - uiScheduler->dispatch([this, windows = std::move(windows)]() { - m_uiData.m_windows = std::move(windows); - }); -} } // namespace Brisk diff --git a/upload-deps.cmake b/upload-deps.cmake index f7b52b07..8f5d5e16 100644 --- a/upload-deps.cmake +++ b/upload-deps.cmake @@ -65,8 +65,7 @@ else () libpng16-debug.cmake libpng16-release.cmake zstdTargets-debug.cmake - zstdTargets-release.cmake - icu-config) + zstdTargets-release.cmake) execute_process( COMMAND diff --git a/vcpkg.json b/vcpkg.json index 0a3e72f1..5f1523c5 100644 --- a/vcpkg.json +++ b/vcpkg.json @@ -95,12 +95,6 @@ "wayland" ] }, - { - "name": "icu", - "features": [ - "tools" - ] - }, { "name": "pegtl" },