diff options
-rw-r--r-- | include/cru/common/Event.hpp | 29 | ||||
-rw-r--r-- | include/cru/platform/native/UiApplication.hpp | 10 | ||||
-rw-r--r-- | include/cru/win/native/GodWindow.hpp | 9 | ||||
-rw-r--r-- | include/cru/win/native/UiApplication.hpp | 2 | ||||
-rw-r--r-- | src/ui/UiHost.cpp | 2 | ||||
-rw-r--r-- | src/win/native/CMakeLists.txt | 5 | ||||
-rw-r--r-- | src/win/native/GodWindow.cpp | 33 | ||||
-rw-r--r-- | src/win/native/GodWindowMessage.hpp | 6 | ||||
-rw-r--r-- | src/win/native/Timer.cpp | 28 | ||||
-rw-r--r-- | src/win/native/Timer.hpp | 34 | ||||
-rw-r--r-- | src/win/native/TimerManager.cpp | 100 | ||||
-rw-r--r-- | src/win/native/TimerManager.hpp | 61 | ||||
-rw-r--r-- | src/win/native/UiApplication.cpp | 28 |
13 files changed, 225 insertions, 122 deletions
diff --git a/include/cru/common/Event.hpp b/include/cru/common/Event.hpp index 377ca7f3..6417bc78 100644 --- a/include/cru/common/Event.hpp +++ b/include/cru/common/Event.hpp @@ -5,6 +5,7 @@ #include <algorithm> #include <functional> +#include <initializer_list> #include <memory> #include <utility> #include <vector> @@ -183,6 +184,7 @@ struct EventRevokerDestroyer { }; } // namespace details +// A guard class for event revoker. Automatically revoke it when destroyed. class EventRevokerGuard { public: EventRevokerGuard() = default; @@ -201,7 +203,7 @@ class EventRevokerGuard { return *revoker_; } - void Release() { revoker_.release(); } + EventRevoker Release() { return std::move(*revoker_.release()); } void Reset(EventRevoker&& revoker) { revoker_.reset(new EventRevoker(std::move(revoker))); @@ -209,5 +211,28 @@ class EventRevokerGuard { private: std::unique_ptr<EventRevoker, details::EventRevokerDestroyer> revoker_; -}; // namespace cru +}; + +class EventRevokerListGuard { + public: + EventRevokerListGuard() = default; + EventRevokerListGuard(const EventRevokerListGuard& other) = delete; + EventRevokerListGuard(EventRevokerListGuard&& other) = default; + EventRevokerListGuard& operator=(const EventRevokerListGuard& other) = delete; + EventRevokerListGuard& operator=(EventRevokerListGuard&& other) = default; + ~EventRevokerListGuard() = default; + + public: + void Add(EventRevoker&& revoker) { + event_revoker_guard_list_.push_back(EventRevokerGuard(std::move(revoker))); + } + + EventRevokerListGuard& operator+=(EventRevoker&& revoker) { + this->Add(std::move(revoker)); + return *this; + } + + private: + std::vector<EventRevokerGuard> event_revoker_guard_list_; +}; } // namespace cru diff --git a/include/cru/platform/native/UiApplication.hpp b/include/cru/platform/native/UiApplication.hpp index 1aa4df57..135e95c3 100644 --- a/include/cru/platform/native/UiApplication.hpp +++ b/include/cru/platform/native/UiApplication.hpp @@ -31,16 +31,16 @@ struct IUiApplication : public virtual INativeResource { virtual void AddOnQuitHandler(std::function<void()> handler) = 0; - virtual void InvokeLater(std::function<void()> action) = 0; - // Timer id should always be positive and never the same. So it's ok to use - // negative value to represent no timer. + // Timer id should always be positive (not 0) and never the same. So it's ok + // to use negative value (or 0) to represent no timer. + virtual long long SetImmediate(std::function<void()> action) = 0; virtual long long SetTimeout(std::chrono::milliseconds milliseconds, std::function<void()> action) = 0; virtual long long SetInterval(std::chrono::milliseconds milliseconds, std::function<void()> action) = 0; // Implementation should guarantee calls on timer id already canceled have no - // effects and do not crash. Also canceling negative id should always result - // in no-op. + // effects and do not crash. Also canceling negative id or 0 should always + // result in no-op. virtual void CancelTimer(long long id) = 0; virtual std::vector<INativeWindow*> GetAllWindow() = 0; diff --git a/include/cru/win/native/GodWindow.hpp b/include/cru/win/native/GodWindow.hpp index 8b20e01f..93d1acad 100644 --- a/include/cru/win/native/GodWindow.hpp +++ b/include/cru/win/native/GodWindow.hpp @@ -1,6 +1,9 @@ #pragma once #include "Base.hpp" +#include "WindowNativeMessageEventArgs.hpp" +#include "cru/common/Event.hpp" + #include <memory> namespace cru::platform::native::win { @@ -20,10 +23,16 @@ class GodWindow : public Object { bool HandleGodWindowMessage(HWND hwnd, UINT msg, WPARAM w_param, LPARAM l_param, LRESULT* result); + IEvent<WindowNativeMessageEventArgs&>* MessageEvent() { + return &message_event_; + } + private: WinUiApplication* application_; std::unique_ptr<WindowClass> god_window_class_; HWND hwnd_; + + Event<WindowNativeMessageEventArgs&> message_event_; }; } // namespace cru::platform::native::win diff --git a/include/cru/win/native/UiApplication.hpp b/include/cru/win/native/UiApplication.hpp index cbc08af7..328a6b84 100644 --- a/include/cru/win/native/UiApplication.hpp +++ b/include/cru/win/native/UiApplication.hpp @@ -32,7 +32,7 @@ class WinUiApplication : public WinNativeResource, void AddOnQuitHandler(std::function<void()> handler) override; - void InvokeLater(std::function<void()> action) override; + long long SetImmediate(std::function<void()> action) override; long long SetTimeout(std::chrono::milliseconds milliseconds, std::function<void()> action) override; long long SetInterval(std::chrono::milliseconds milliseconds, diff --git a/src/ui/UiHost.cpp b/src/ui/UiHost.cpp index 5451ebce..0fdf2f53 100644 --- a/src/ui/UiHost.cpp +++ b/src/ui/UiHost.cpp @@ -156,7 +156,7 @@ void UiHost::InvalidatePaint() { void UiHost::InvalidateLayout() { log::TagDebug(log_tag, u"A relayout is requested."); if (!need_layout_) { - platform::native::IUiApplication::GetInstance()->InvokeLater( + platform::native::IUiApplication::GetInstance()->SetImmediate( [resolver = this->CreateResolver()] { if (const auto host = resolver.Resolve()) { host->Relayout(); diff --git a/src/win/native/CMakeLists.txt b/src/win/native/CMakeLists.txt index f1b167d2..0e8ae44f 100644 --- a/src/win/native/CMakeLists.txt +++ b/src/win/native/CMakeLists.txt @@ -2,8 +2,7 @@ set(CRU_WIN_NATIVE_INCLUDE_DIR ${CRU_INCLUDE_DIR}/cru/win/native) add_library(cru_win_native STATIC DpiUtil.hpp - GodWindowMessage.hpp - Timer.hpp + TimerManager.hpp WindowD2DPainter.hpp WindowManager.hpp @@ -11,7 +10,7 @@ add_library(cru_win_native STATIC GodWindow.cpp InputMethod.cpp Keyboard.cpp - Timer.cpp + TimerManager.cpp UiApplication.cpp Window.cpp WindowClass.cpp diff --git a/src/win/native/GodWindow.cpp b/src/win/native/GodWindow.cpp index b1e7275e..203542f7 100644 --- a/src/win/native/GodWindow.cpp +++ b/src/win/native/GodWindow.cpp @@ -1,7 +1,5 @@ #include "cru/win/native/GodWindow.hpp" -#include "GodWindowMessage.hpp" -#include "Timer.hpp" #include "cru/common/Logger.hpp" #include "cru/win/native/Exception.hpp" #include "cru/win/native/UiApplication.hpp" @@ -51,32 +49,15 @@ GodWindow::~GodWindow() { bool GodWindow::HandleGodWindowMessage(HWND hwnd, UINT msg, WPARAM w_param, LPARAM l_param, LRESULT* result) { - CRU_UNUSED(hwnd) - CRU_UNUSED(l_param) + WindowNativeMessageEventArgs args( + WindowNativeMessage{hwnd, msg, w_param, l_param}); + message_event_.Raise(args); - switch (msg) { - case invoke_later_message_id: { - const auto p_action = reinterpret_cast<std::function<void()>*>(w_param); - (*p_action)(); - delete p_action; - *result = 0; - return true; - } - case WM_TIMER: { - const auto id = static_cast<UINT_PTR>(w_param); - const auto action = application_->GetTimerManager()->GetAction(id); - if (action.has_value()) { - (action.value().second)(); - if (!action.value().first) - application_->GetTimerManager()->KillTimer(id); - result = 0; - return true; - } - break; - } - default: - return false; + if (args.IsHandled()) { + *result = args.GetResult(); + return true; } + return false; } } // namespace cru::platform::native::win diff --git a/src/win/native/GodWindowMessage.hpp b/src/win/native/GodWindowMessage.hpp deleted file mode 100644 index 9063cb4d..00000000 --- a/src/win/native/GodWindowMessage.hpp +++ /dev/null @@ -1,6 +0,0 @@ -#pragma once -#include "cru/win/WinPreConfig.hpp" - -namespace cru::platform::native::win { -constexpr int invoke_later_message_id = WM_USER + 2000; -} diff --git a/src/win/native/Timer.cpp b/src/win/native/Timer.cpp deleted file mode 100644 index 662067fb..00000000 --- a/src/win/native/Timer.cpp +++ /dev/null @@ -1,28 +0,0 @@ -#include "Timer.hpp" - -namespace cru::platform::native::win { -TimerManager::TimerManager(GodWindow* god_window) { god_window_ = god_window; } - -UINT_PTR TimerManager::CreateTimer(const UINT milliseconds, const bool loop, - TimerAction action) { - const auto id = current_count_++; - ::SetTimer(god_window_->GetHandle(), id, milliseconds, nullptr); - map_.emplace(id, std::make_pair(loop, std::move(action))); - return id; -} - -void TimerManager::KillTimer(const UINT_PTR id) { - const auto find_result = map_.find(id); - if (find_result != map_.cend()) { - ::KillTimer(god_window_->GetHandle(), id); - map_.erase(find_result); - } -} - -std::optional<std::pair<bool, TimerAction>> TimerManager::GetAction( - const UINT_PTR id) { - const auto find_result = map_.find(id); - if (find_result == map_.cend()) return std::nullopt; - return find_result->second; -} -} // namespace cru::platform::native::win diff --git a/src/win/native/Timer.hpp b/src/win/native/Timer.hpp deleted file mode 100644 index 95f186a1..00000000 --- a/src/win/native/Timer.hpp +++ /dev/null @@ -1,34 +0,0 @@ -#pragma once -#include "cru/win/WinPreConfig.hpp" - -#include "cru/common/Base.hpp" -#include "cru/win/native/GodWindow.hpp" - -#include <chrono> -#include <functional> -#include <map> -#include <optional> - -namespace cru::platform::native::win { -using TimerAction = std::function<void()>; - -class TimerManager : public Object { - public: - TimerManager(GodWindow* god_window); - - CRU_DELETE_COPY(TimerManager) - CRU_DELETE_MOVE(TimerManager) - - ~TimerManager() override = default; - - UINT_PTR CreateTimer(UINT milliseconds, bool loop, TimerAction action); - void KillTimer(UINT_PTR id); - std::optional<std::pair<bool, TimerAction>> GetAction(UINT_PTR id); - - private: - GodWindow* god_window_; - - std::map<UINT_PTR, std::pair<bool, TimerAction>> map_{}; - UINT_PTR current_count_ = 0; -}; -} // namespace cru::platform::native::win diff --git a/src/win/native/TimerManager.cpp b/src/win/native/TimerManager.cpp new file mode 100644 index 00000000..da129b52 --- /dev/null +++ b/src/win/native/TimerManager.cpp @@ -0,0 +1,100 @@ +#include "TimerManager.hpp" + +#include "cru/win/native/Base.hpp" +#include "cru/win/native/Exception.hpp" +#include "gsl/gsl_util" + +#include <functional> +#include <type_traits> + +namespace cru::platform::native::win { +constexpr int kSetImmediateWindowMessageId = WM_USER + 2000; + +TimerManager::TimerManager(GodWindow* god_window) { + god_window_ = god_window; + event_guard_ += god_window->MessageEvent()->AddHandler(std::bind( + &TimerManager::HandleGodWindowMessage, this, std::placeholders::_1)); +} + +long long TimerManager::SetTimer(TimerType type, int period, + std::function<void()> action) { + auto id = next_id_++; + TimerInfo timer_info{id, type, type == TimerType::Immediate ? 0 : period, + std::move(action)}; + if (type == TimerType::Immediate) { + if (!::PostMessageW(god_window_->GetHandle(), kSetImmediateWindowMessageId, + gsl::narrow<UINT_PTR>(id), 0)) { + throw Win32Error( + ::GetLastError(), + "Failed to post window message to god window for set immediate."); + } + } else { + CreateNativeTimer(&timer_info); + } + + info_map_.emplace(id, std::move(timer_info)); + return id; +} + +void TimerManager::CancelTimer(long long id) { + if (id <= 0) return; + auto find_result = this->info_map_.find(id); + if (find_result != info_map_.cend()) { + auto& info = find_result->second; + KillNativeTimer(&info); + this->info_map_.erase(find_result); + } +} + +void TimerManager::CreateNativeTimer(TimerInfo* info) { + info->native_timer_id = gsl::narrow<UINT_PTR>(info->id); + ::SetTimer(god_window_->GetHandle(), info->native_timer_id, info->period, + nullptr); +} + +void TimerManager::KillNativeTimer(TimerInfo* info) { + if (info->id == 0) return; + ::KillTimer(god_window_->GetHandle(), info->id); + info->native_timer_id = 0; +} + +void TimerManager::HandleGodWindowMessage(WindowNativeMessageEventArgs& args) { + const auto& message = args.GetWindowMessage(); + + switch (message.msg) { + case kSetImmediateWindowMessageId: { + auto find_result = + this->info_map_.find(static_cast<long long>(message.w_param)); + if (find_result != info_map_.cend()) { + auto& info = find_result->second; + info.action(); + info_map_.erase(find_result); + } + args.SetResult(0); + args.SetHandled(true); + return; + } + case WM_TIMER: { + auto find_result = + this->info_map_.find(static_cast<long long>(message.w_param)); + if (find_result != info_map_.cend()) { + auto& info = find_result->second; + if (info.type == TimerType::Interval) { + info.action(); + args.SetResult(0); + args.SetHandled(true); + } else if (info.type == TimerType::Timeout) { + info.action(); + KillNativeTimer(&info); + info_map_.erase(find_result); + args.SetResult(0); + args.SetHandled(true); + } + } + return; + } + default: + return; + } +} +} // namespace cru::platform::native::win diff --git a/src/win/native/TimerManager.hpp b/src/win/native/TimerManager.hpp new file mode 100644 index 00000000..f2731f60 --- /dev/null +++ b/src/win/native/TimerManager.hpp @@ -0,0 +1,61 @@ +#pragma once +#include "cru/common/Event.hpp" +#include "cru/win/WinPreConfig.hpp" + +#include "cru/common/Base.hpp" +#include "cru/win/native/GodWindow.hpp" +#include "cru/win/native/WindowNativeMessageEventArgs.hpp" + +#include <chrono> +#include <functional> +#include <optional> +#include <unordered_map> + +namespace cru::platform::native::win { +enum class TimerType { Immediate, Timeout, Interval }; + +struct TimerInfo { + TimerInfo(long long id, TimerType type, int period, + std::function<void()> action, UINT_PTR native_timer_id = 0) + : id(id), + type(type), + period(period), + action(std::move(action)), + native_timer_id(native_timer_id) {} + + long long id; + TimerType type; + int period; // in milliseconds + std::function<void()> action; + UINT_PTR native_timer_id; +}; + +class TimerManager : public Object { + public: + TimerManager(GodWindow* god_window); + + CRU_DELETE_COPY(TimerManager) + CRU_DELETE_MOVE(TimerManager) + + ~TimerManager() override = default; + + // Period is in milliseconds. When type is immediate, it is not checked and + // used. + long long SetTimer(TimerType type, int period, std::function<void()> action); + void CancelTimer(long long id); + + private: + void HandleGodWindowMessage(WindowNativeMessageEventArgs& args); + + void CreateNativeTimer(TimerInfo* info); + void KillNativeTimer(TimerInfo* info); + + private: + GodWindow* god_window_; + + EventRevokerListGuard event_guard_; + + long long next_id_ = 1; + std::unordered_map<long long, TimerInfo> info_map_; +}; +} // namespace cru::platform::native::win diff --git a/src/win/native/UiApplication.cpp b/src/win/native/UiApplication.cpp index dbf52e05..a806db88 100644 --- a/src/win/native/UiApplication.cpp +++ b/src/win/native/UiApplication.cpp @@ -1,6 +1,8 @@ #include "cru/win/native/UiApplication.hpp" #include "../DebugLogger.hpp" +#include "TimerManager.hpp" +#include "WindowManager.hpp" #include "cru/common/Logger.hpp" #include "cru/platform/Check.hpp" #include "cru/win/graph/direct/Factory.hpp" @@ -9,9 +11,6 @@ #include "cru/win/native/GodWindow.hpp" #include "cru/win/native/InputMethod.hpp" #include "cru/win/native/Window.hpp" -#include "GodWindowMessage.hpp" -#include "Timer.hpp" -#include "WindowManager.hpp" namespace cru::platform::native { std::unique_ptr<IUiApplication> CreateUiApplication() { @@ -64,30 +63,27 @@ void WinUiApplication::AddOnQuitHandler(std::function<void()> handler) { quit_handlers_.push_back(std::move(handler)); } -void WinUiApplication::InvokeLater(std::function<void()> action) { - // copy the action to a safe place - auto p_action_copy = new std::function<void()>(std::move(action)); - - if (::PostMessageW(GetGodWindow()->GetHandle(), invoke_later_message_id, - reinterpret_cast<WPARAM>(p_action_copy), 0) == 0) - throw Win32Error(::GetLastError(), "InvokeLater failed to post message."); +long long WinUiApplication::SetImmediate(std::function<void()> action) { + return this->timer_manager_->SetTimer(TimerType::Immediate, 0, + std::move(action)); } long long WinUiApplication::SetTimeout(std::chrono::milliseconds milliseconds, std::function<void()> action) { - return gsl::narrow<long long>(timer_manager_->CreateTimer( - static_cast<UINT>(milliseconds.count()), false, std::move(action))); + return this->timer_manager_->SetTimer(TimerType::Timeout, + gsl::narrow<int>(milliseconds.count()), + std::move(action)); } long long WinUiApplication::SetInterval(std::chrono::milliseconds milliseconds, std::function<void()> action) { - return gsl::narrow<long long>(timer_manager_->CreateTimer( - static_cast<UINT>(milliseconds.count()), true, std::move(action))); + return this->timer_manager_->SetTimer(TimerType::Interval, + gsl::narrow<int>(milliseconds.count()), + std::move(action)); } void WinUiApplication::CancelTimer(long long id) { - if (id < 0) return; - timer_manager_->KillTimer(static_cast<UINT_PTR>(id)); + timer_manager_->CancelTimer(id); } std::vector<INativeWindow*> WinUiApplication::GetAllWindow() { |