aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorYuqian Yang <crupest@crupest.life>2025-11-16 14:09:50 +0800
committerYuqian Yang <crupest@crupest.life>2025-11-16 14:09:50 +0800
commit07d662aafc25b145eb20e4123ebc82b5736cdeb7 (patch)
tree0b94baddd3235623beac4997ac09a503a00645e7
parentfaf73c4af74bdae1abf394a33b573149b98ec2b1 (diff)
downloadcru-07d662aafc25b145eb20e4123ebc82b5736cdeb7.tar.gz
cru-07d662aafc25b145eb20e4123ebc82b5736cdeb7.tar.bz2
cru-07d662aafc25b145eb20e4123ebc82b5736cdeb7.zip
New timer impl on Windows. Add Event test. Fix delete later.
-rw-r--r--include/cru/platform/gui/win/Base.h2
-rw-r--r--include/cru/platform/gui/win/GodWindow.h34
-rw-r--r--include/cru/platform/gui/win/UiApplication.h7
-rw-r--r--include/cru/ui/DeleteLater.h3
-rw-r--r--include/cru/ui/controls/TextHostControlService.h5
-rw-r--r--include/cru/ui/events/UiEvents.h16
-rw-r--r--include/cru/ui/host/WindowHost.h1
-rw-r--r--src/platform/gui/win/CMakeLists.txt2
-rw-r--r--src/platform/gui/win/Clipboard.cpp8
-rw-r--r--src/platform/gui/win/GodWindow.cpp61
-rw-r--r--src/platform/gui/win/TimerManager.cpp95
-rw-r--r--src/platform/gui/win/TimerManager.h53
-rw-r--r--src/platform/gui/win/UiApplication.cpp47
-rw-r--r--src/platform/gui/win/Window.cpp3
-rw-r--r--src/ui/DeleteLater.cpp7
-rw-r--r--src/ui/controls/Control.cpp5
-rw-r--r--src/ui/controls/TextHostControlService.cpp6
-rw-r--r--test/base/CMakeLists.txt1
-rw-r--r--test/base/EventTest.cpp39
-rw-r--r--test/ui/CMakeLists.txt6
-rw-r--r--test/ui/DeleteLaterTest.cpp17
21 files changed, 120 insertions, 298 deletions
diff --git a/include/cru/platform/gui/win/Base.h b/include/cru/platform/gui/win/Base.h
index 9c253950..1ef20ffd 100644
--- a/include/cru/platform/gui/win/Base.h
+++ b/include/cru/platform/gui/win/Base.h
@@ -17,8 +17,6 @@ namespace cru::platform::gui::win {
using platform::win::HResultError;
using platform::win::Win32Error;
-class GodWindow;
-class TimerManager;
class WinCursor;
class WinCursorManager;
class WindowClass;
diff --git a/include/cru/platform/gui/win/GodWindow.h b/include/cru/platform/gui/win/GodWindow.h
deleted file mode 100644
index 22068ff5..00000000
--- a/include/cru/platform/gui/win/GodWindow.h
+++ /dev/null
@@ -1,34 +0,0 @@
-#pragma once
-#include "Base.h"
-#include "WindowNativeMessageEventArgs.h"
-
-#include <cru/base/Event.h>
-
-#include <memory>
-
-namespace cru::platform::gui::win {
-class CRU_WIN_GUI_API GodWindow : public Object {
- CRU_DEFINE_CLASS_LOG_TAG("GodWindow")
-
- public:
- explicit GodWindow(WinUiApplication* application);
- ~GodWindow() override;
-
- HWND GetHandle() const { return hwnd_; }
-
- 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::gui::win
diff --git a/include/cru/platform/gui/win/UiApplication.h b/include/cru/platform/gui/win/UiApplication.h
index 9b1d011b..15c23148 100644
--- a/include/cru/platform/gui/win/UiApplication.h
+++ b/include/cru/platform/gui/win/UiApplication.h
@@ -2,6 +2,7 @@
#include "Base.h"
#include <cru/platform/gui/UiApplication.h>
+#include <cru/base/Timer.h>
#include <memory>
@@ -59,8 +60,6 @@ class CRU_WIN_GUI_API WinUiApplication : public WinNativeResource,
HINSTANCE GetInstanceHandle() const { return instance_handle_; }
- GodWindow* GetGodWindow() const { return god_window_.get(); }
- TimerManager* GetTimerManager() const { return timer_manager_.get(); }
WindowManager* GetWindowManager() const { return window_manager_.get(); }
private:
@@ -71,8 +70,8 @@ class CRU_WIN_GUI_API WinUiApplication : public WinNativeResource,
std::unique_ptr<cru::platform::graphics::direct2d::DirectGraphicsFactory>
graph_factory_;
- std::unique_ptr<GodWindow> god_window_;
- std::unique_ptr<TimerManager> timer_manager_;
+ TimerRegistry<std::function<void()>> timers_;
+
std::unique_ptr<WindowManager> window_manager_;
std::unique_ptr<WinCursorManager> cursor_manager_;
diff --git a/include/cru/ui/DeleteLater.h b/include/cru/ui/DeleteLater.h
index d04d1814..ddbf2ce1 100644
--- a/include/cru/ui/DeleteLater.h
+++ b/include/cru/ui/DeleteLater.h
@@ -7,6 +7,7 @@
namespace cru::ui {
class CRU_UI_API DeleteLaterImpl {
+ CRU_DEFINE_CLASS_LOG_TAG("cru::ui::DeleteLaterImpl")
public:
DeleteLaterImpl();
virtual ~DeleteLaterImpl();
@@ -36,7 +37,7 @@ DeleteLaterPtr<T> ToDeleteLaterPtr(std::unique_ptr<T>&& p) {
template <typename T, typename... Args>
DeleteLaterPtr<T> MakeDeleteLater(Args&&... args) {
- return DeleteLaterPtr<T>(std::forward<Args>(args)...);
+ return DeleteLaterPtr<T>(new T(std::forward<Args>(args)...));
}
} // namespace cru::ui
diff --git a/include/cru/ui/controls/TextHostControlService.h b/include/cru/ui/controls/TextHostControlService.h
index d4a1218f..e1586e66 100644
--- a/include/cru/ui/controls/TextHostControlService.h
+++ b/include/cru/ui/controls/TextHostControlService.h
@@ -2,7 +2,6 @@
#include "../render/TextRenderObject.h"
#include "cru/platform/gui/InputMethod.h"
#include "cru/platform/gui/UiApplication.h"
-#include "cru/ui/DeleteLater.h"
#include "cru/ui/controls/Control.h"
#include "cru/ui/helper/ShortcutHub.h"
@@ -71,7 +70,7 @@ class TextControlMovePattern {
};
class CRU_UI_API TextHostControlService : public Object {
- CRU_DEFINE_CLASS_LOG_TAG("TextControlService")
+ CRU_DEFINE_CLASS_LOG_TAG("cru::ui::controls::TextControlService")
public:
TextHostControlService(Control* control);
@@ -207,6 +206,6 @@ class CRU_UI_API TextHostControlService : public Object {
bool mouse_move_selecting_ = false;
bool context_menu_dirty_ = true;
- DeleteLaterPtr<components::PopupMenu> context_menu_;
+ std::unique_ptr<components::PopupMenu> context_menu_;
};
} // namespace cru::ui::controls
diff --git a/include/cru/ui/events/UiEvents.h b/include/cru/ui/events/UiEvents.h
index 301ec694..a74c9d7d 100644
--- a/include/cru/ui/events/UiEvents.h
+++ b/include/cru/ui/events/UiEvents.h
@@ -1,10 +1,10 @@
#pragma once
-#include "FocusChangeEventArgs.h"
-#include "KeyEventArgs.h"
-#include "MouseButtonEventArgs.h"
-#include "MouseEventArgs.h"
-#include "MouseWheelEventArgs.h"
-#include "PaintEventArgs.h"
-#include "RoutedEvent.h"
-#include "UiEventArgs.h"
+#include "FocusChangeEventArgs.h" // IWYU pragma: export
+#include "KeyEventArgs.h" // IWYU pragma: export
+#include "MouseButtonEventArgs.h" // IWYU pragma: export
+#include "MouseEventArgs.h" // IWYU pragma: export
+#include "MouseWheelEventArgs.h" // IWYU pragma: export
+#include "PaintEventArgs.h" // IWYU pragma: export
+#include "RoutedEvent.h" // IWYU pragma: export
+#include "UiEventArgs.h" // IWYU pragma: export
diff --git a/include/cru/ui/host/WindowHost.h b/include/cru/ui/host/WindowHost.h
index 889665d5..58fd120d 100644
--- a/include/cru/ui/host/WindowHost.h
+++ b/include/cru/ui/host/WindowHost.h
@@ -9,7 +9,6 @@
#include <functional>
#include <memory>
-#include <optional>
namespace cru::ui::host {
class LayoutPaintCycler;
diff --git a/src/platform/gui/win/CMakeLists.txt b/src/platform/gui/win/CMakeLists.txt
index d5ac6851..8c0dd2a4 100644
--- a/src/platform/gui/win/CMakeLists.txt
+++ b/src/platform/gui/win/CMakeLists.txt
@@ -2,10 +2,8 @@ add_library(CruPlatformGuiWin
Base.cpp
Clipboard.cpp
Cursor.cpp
- GodWindow.cpp
InputMethod.cpp
Keyboard.cpp
- TimerManager.cpp
UiApplication.cpp
Window.cpp
WindowClass.cpp
diff --git a/src/platform/gui/win/Clipboard.cpp b/src/platform/gui/win/Clipboard.cpp
index f5d2513a..7062f160 100644
--- a/src/platform/gui/win/Clipboard.cpp
+++ b/src/platform/gui/win/Clipboard.cpp
@@ -1,7 +1,6 @@
#include "cru/platform/gui/win/Clipboard.h"
#include "cru/base/StringUtil.h"
#include "cru/base/log/Logger.h"
-#include "cru/platform/gui/win/GodWindow.h"
#include "cru/platform/gui/win/UiApplication.h"
namespace cru::platform::gui::win {
@@ -11,9 +10,7 @@ WinClipboard::WinClipboard(WinUiApplication* application)
WinClipboard::~WinClipboard() {}
std::string WinClipboard::GetText() {
- auto god_window = application_->GetGodWindow();
-
- if (!::OpenClipboard(god_window->GetHandle())) {
+ if (!::OpenClipboard(nullptr)) {
CRU_LOG_TAG_WARN("Failed to open clipboard.");
return {};
}
@@ -47,9 +44,8 @@ std::string WinClipboard::GetText() {
void WinClipboard::SetText(std::string utf8_text) {
auto text = string::ToUtf16(utf8_text);
- auto god_window = application_->GetGodWindow();
- if (!::OpenClipboard(god_window->GetHandle())) {
+ if (!::OpenClipboard(nullptr)) {
CRU_LOG_TAG_WARN("Failed to open clipboard.");
return;
}
diff --git a/src/platform/gui/win/GodWindow.cpp b/src/platform/gui/win/GodWindow.cpp
deleted file mode 100644
index 7cdf229c..00000000
--- a/src/platform/gui/win/GodWindow.cpp
+++ /dev/null
@@ -1,61 +0,0 @@
-#include "cru/platform/gui/win/GodWindow.h"
-#include "cru/base/log/Logger.h"
-#include "cru/platform/gui/win/UiApplication.h"
-#include "cru/platform/gui/win/WindowClass.h"
-
-namespace cru::platform::gui::win {
-constexpr auto god_window_class_name = L"GodWindowClass";
-
-LRESULT CALLBACK GodWndProc(HWND hWnd, UINT uMsg, WPARAM wParam,
- LPARAM lParam) {
- const auto app = WinUiApplication::GetInstance();
-
- if (app) {
- LRESULT result;
- auto god_window = app->GetGodWindow();
- if (god_window != nullptr) {
- const auto handled = god_window->HandleGodWindowMessage(
- hWnd, uMsg, wParam, lParam, &result);
- if (handled) return result;
- }
- }
- return DefWindowProcW(hWnd, uMsg, wParam, lParam);
-}
-
-GodWindow::GodWindow(WinUiApplication* application) {
- application_ = application;
-
- const auto h_instance = application->GetInstanceHandle();
-
- god_window_class_ = std::make_unique<WindowClass>(god_window_class_name,
- GodWndProc, h_instance);
-
- hwnd_ = CreateWindowEx(0, god_window_class_name, L"", 0, CW_USEDEFAULT,
- CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,
- HWND_MESSAGE, nullptr, h_instance, nullptr);
-
- if (hwnd_ == nullptr)
- throw Win32Error(::GetLastError(), "Failed to create god window.");
-}
-
-GodWindow::~GodWindow() {
- if (!::DestroyWindow(hwnd_)) {
- // Although this could be "safely" ignore.
- CRU_LOG_TAG_WARN("Failed to destroy god window.");
- }
-}
-
-bool GodWindow::HandleGodWindowMessage(HWND hwnd, UINT msg, WPARAM w_param,
- LPARAM l_param, LRESULT* result) {
- WindowNativeMessageEventArgs args(
- WindowNativeMessage{hwnd, msg, w_param, l_param});
- message_event_.Raise(args);
-
- if (args.IsHandled()) {
- *result = args.GetResult();
- return true;
- }
-
- return false;
-}
-} // namespace cru::platform::gui::win
diff --git a/src/platform/gui/win/TimerManager.cpp b/src/platform/gui/win/TimerManager.cpp
deleted file mode 100644
index 794e714c..00000000
--- a/src/platform/gui/win/TimerManager.cpp
+++ /dev/null
@@ -1,95 +0,0 @@
-#include "TimerManager.h"
-
-#include <functional>
-
-namespace cru::platform::gui::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,
- static_cast<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 = static_cast<UINT_PTR>(info->id);
- ::SetTimer(god_window_->GetHandle(), info->native_timer_id, info->period,
- nullptr);
-}
-
-void TimerManager::KillNativeTimer(TimerInfo* info) {
- if (info->native_timer_id == 0) return;
- ::KillTimer(god_window_->GetHandle(), info->native_timer_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::gui::win
diff --git a/src/platform/gui/win/TimerManager.h b/src/platform/gui/win/TimerManager.h
deleted file mode 100644
index b35ef1fc..00000000
--- a/src/platform/gui/win/TimerManager.h
+++ /dev/null
@@ -1,53 +0,0 @@
-#pragma once
-#include "cru/base/Base.h"
-#include "cru/base/Event.h"
-#include "cru/platform/gui/win/GodWindow.h"
-#include "cru/platform/gui/win/WindowNativeMessageEventArgs.h"
-
-#include <functional>
-#include <unordered_map>
-
-namespace cru::platform::gui::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);
- ~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_;
-
- EventHandlerRevokerListGuard event_guard_;
-
- long long next_id_ = 1;
- std::unordered_map<long long, TimerInfo> info_map_;
-};
-} // namespace cru::platform::gui::win
diff --git a/src/platform/gui/win/UiApplication.cpp b/src/platform/gui/win/UiApplication.cpp
index 0de79675..12a74bc6 100644
--- a/src/platform/gui/win/UiApplication.cpp
+++ b/src/platform/gui/win/UiApplication.cpp
@@ -1,14 +1,14 @@
#include "cru/platform/gui/win/UiApplication.h"
-#include "TimerManager.h"
#include "WindowManager.h"
#include "cru/platform/graphics/direct2d/Factory.h"
#include "cru/platform/gui/win/Base.h"
#include "cru/platform/gui/win/Clipboard.h"
#include "cru/platform/gui/win/Cursor.h"
-#include "cru/platform/gui/win/GodWindow.h"
#include "cru/platform/gui/win/Window.h"
+#include <chrono>
+
namespace cru::platform::gui {
std::unique_ptr<IUiApplication> CreateUiApplication() {
return std::make_unique<win::WinUiApplication>();
@@ -30,8 +30,6 @@ WinUiApplication::WinUiApplication() {
graph_factory_ = std::make_unique<
cru::platform::graphics::direct2d::DirectGraphicsFactory>();
- god_window_ = std::make_unique<GodWindow>(this);
- timer_manager_ = std::make_unique<TimerManager>(god_window_.get());
window_manager_ = std::make_unique<WindowManager>(this);
cursor_manager_ = std::make_unique<WinCursorManager>();
clipboard_ = std::make_unique<WinClipboard>(this);
@@ -41,9 +39,28 @@ WinUiApplication::~WinUiApplication() { instance = nullptr; }
int WinUiApplication::Run() {
MSG msg;
- while (GetMessageW(&msg, nullptr, 0, 0)) {
- TranslateMessage(&msg);
- DispatchMessageW(&msg);
+ bool exit = false;
+
+ while (!exit) {
+ if (auto result = timers_.Update(std::chrono::steady_clock::now())) {
+ result->data();
+ continue;
+ }
+
+ auto timeout = timers_.NextTimeout(std::chrono::steady_clock::now());
+
+ ::MsgWaitForMultipleObjects(
+ 0, nullptr, FALSE, timeout ? timeout->count() : INFINITE, QS_ALLINPUT);
+
+ while (PeekMessageW(&msg, nullptr, 0, 0, PM_REMOVE)) {
+ TranslateMessage(&msg);
+ DispatchMessageW(&msg);
+
+ if (msg.message == WM_QUIT) {
+ exit = true;
+ break;
+ }
+ }
}
for (const auto& handler : quit_handlers_) handler();
@@ -60,27 +77,21 @@ void WinUiApplication::AddOnQuitHandler(std::function<void()> handler) {
}
long long WinUiApplication::SetImmediate(std::function<void()> action) {
- return this->timer_manager_->SetTimer(TimerType::Immediate, 0,
- std::move(action));
+ return timers_.Add(std::move(action), std::chrono::milliseconds::zero(),
+ false);
}
long long WinUiApplication::SetTimeout(std::chrono::milliseconds milliseconds,
std::function<void()> action) {
- return this->timer_manager_->SetTimer(TimerType::Timeout,
- static_cast<int>(milliseconds.count()),
- std::move(action));
+ return timers_.Add(std::move(action), milliseconds, false);
}
long long WinUiApplication::SetInterval(std::chrono::milliseconds milliseconds,
std::function<void()> action) {
- return this->timer_manager_->SetTimer(TimerType::Interval,
- static_cast<int>(milliseconds.count()),
- std::move(action));
+ return timers_.Add(std::move(action), milliseconds, true);
}
-void WinUiApplication::CancelTimer(long long id) {
- timer_manager_->CancelTimer(id);
-}
+void WinUiApplication::CancelTimer(long long id) { timers_.Remove(id); }
std::vector<INativeWindow*> WinUiApplication::GetAllWindow() {
const auto&& windows = window_manager_->GetAllWindows();
diff --git a/src/platform/gui/win/Window.cpp b/src/platform/gui/win/Window.cpp
index 2acd4c75..af308768 100644
--- a/src/platform/gui/win/Window.cpp
+++ b/src/platform/gui/win/Window.cpp
@@ -68,7 +68,7 @@ Rect CalcClientRectFromWindow(const Rect& rect, WindowStyleFlag style_flag,
WinNativeWindow::WinNativeWindow(WinUiApplication* application)
: application_(application) {
- Expects(application); // application can't be null.
+ input_method_context_ = std::make_unique<WinInputMethodContext>(this);
}
WinNativeWindow::~WinNativeWindow() { Close(); }
@@ -490,7 +490,6 @@ void WinNativeWindow::RecreateWindow() {
application_->GetDirectFactory(), hwnd_);
window_render_target_->SetDpi(dpi_, dpi_);
- input_method_context_ = std::make_unique<WinInputMethodContext>(this);
input_method_context_->DisableIME();
}
diff --git a/src/ui/DeleteLater.cpp b/src/ui/DeleteLater.cpp
index 499b9b34..27ec2155 100644
--- a/src/ui/DeleteLater.cpp
+++ b/src/ui/DeleteLater.cpp
@@ -1,16 +1,21 @@
#include "cru/ui/DeleteLater.h"
#include "Helper.h"
+#include "cru/base/log/Logger.h"
#include "cru/platform/gui/UiApplication.h"
namespace cru::ui {
DeleteLaterImpl::DeleteLaterImpl() : delete_later_scheduled_(false) {}
-DeleteLaterImpl::~DeleteLaterImpl() {}
+DeleteLaterImpl::~DeleteLaterImpl() {
+ CRU_LOG_TAG_DEBUG("Delete later object being deleted {}.",
+ static_cast<void*>(this));
+}
void DeleteLaterImpl::DeleteLater() {
if (!delete_later_scheduled_) {
+ CRU_LOG_TAG_DEBUG("Schedule delete later {}.", static_cast<void*>(this));
GetUiApplication()->SetImmediate([this] { delete this; });
delete_later_scheduled_ = true;
}
diff --git a/src/ui/controls/Control.cpp b/src/ui/controls/Control.cpp
index 3218f185..14397fa7 100644
--- a/src/ui/controls/Control.cpp
+++ b/src/ui/controls/Control.cpp
@@ -1,10 +1,8 @@
#include "cru/ui/controls/Control.h"
-#include "cru/base/log/Logger.h"
#include "cru/platform/gui/Cursor.h"
#include "cru/platform/gui/UiApplication.h"
#include "cru/ui/host/WindowHost.h"
-#include "cru/ui/render/RenderObject.h"
#include "cru/ui/style/StyleRuleSet.h"
namespace cru::ui::controls {
@@ -30,8 +28,7 @@ Control::Control() {
Control::~Control() {
if (host::WindowHost::IsInEventHandling()) {
- CRU_LOG_TAG_ERROR(
- "Control destroyed during event handling. Please use DeleteLater.");
+ std::terminate();
}
in_destruction_ = true;
diff --git a/src/ui/controls/TextHostControlService.cpp b/src/ui/controls/TextHostControlService.cpp
index 235e6e6a..f581d3c9 100644
--- a/src/ui/controls/TextHostControlService.cpp
+++ b/src/ui/controls/TextHostControlService.cpp
@@ -12,7 +12,6 @@
#include "cru/platform/gui/Window.h"
#include "cru/ui/Base.h"
#include "cru/ui/DebugFlags.h"
-#include "cru/ui/DeleteLater.h"
#include "cru/ui/components/Menu.h"
#include "cru/ui/helper/ShortcutHub.h"
#include "cru/ui/host/WindowHost.h"
@@ -149,7 +148,8 @@ std::vector<TextControlMovePattern> TextControlMovePattern::kDefaultPatterns = {
TextHostControlService::TextHostControlService(Control* control)
: control_(control),
- text_host_control_(dynamic_cast<ITextHostControl*>(control)) {
+ text_host_control_(dynamic_cast<ITextHostControl*>(control)),
+ context_menu_(new components::PopupMenu()) {
SetUpShortcuts();
SetupOneHandler(&Control::MouseMoveEvent,
@@ -699,7 +699,7 @@ void TextHostControlService::SetUpShortcuts() {
void TextHostControlService::OpenContextMenu(const Point& position,
ContextMenuItem items) {
- context_menu_ = MakeDeleteLater<components::PopupMenu>();
+ CRU_LOG_TAG_DEBUG("Open context menu.");
auto menu = context_menu_->GetMenu();
if (items & ContextMenuItem::kSelectAll) {
menu->AddTextItem("Select All", [this] { this->SelectAll(); });
diff --git a/test/base/CMakeLists.txt b/test/base/CMakeLists.txt
index 731f268c..52f61a8c 100644
--- a/test/base/CMakeLists.txt
+++ b/test/base/CMakeLists.txt
@@ -1,4 +1,5 @@
add_executable(CruBaseTest
+ EventTest.cpp
PropertyTreeTest.cpp
SelfResolvableTest.cpp
StringUtilTest.cpp
diff --git a/test/base/EventTest.cpp b/test/base/EventTest.cpp
new file mode 100644
index 00000000..9ebf8b28
--- /dev/null
+++ b/test/base/EventTest.cpp
@@ -0,0 +1,39 @@
+#include "cru/base/Event.h"
+
+#include <catch2/catch_test_macros.hpp>
+
+TEST_CASE("Event should work.", "[event]") {
+ using cru::Event;
+
+ Event<int&> event;
+
+ int count = 0;
+ bool triggered = false;
+
+ auto revoker = event.AddHandler([](int& count) { count++; });
+ event.AddHandler([](int& count) { count++; });
+ event.AddSpyOnlyHandler([&triggered] { triggered = true; });
+
+ event.Raise(count);
+ REQUIRE(count == 2);
+ REQUIRE(triggered);
+
+ revoker();
+
+ event.Raise(count);
+ REQUIRE(count == 3);
+}
+
+TEST_CASE("Event destroy during raising.", "[event]") {
+ using cru::Event;
+
+ Event<int&>* event = new Event<int&>();
+
+ event->AddSpyOnlyHandler([event] { delete event; });
+ auto revoker = event->AddHandler([](int& count) { count++; });
+
+ int count = 0;
+ event->Raise(count);
+ REQUIRE(count == 1);
+ revoker();
+}
diff --git a/test/ui/CMakeLists.txt b/test/ui/CMakeLists.txt
index e69de29b..66a7cd27 100644
--- a/test/ui/CMakeLists.txt
+++ b/test/ui/CMakeLists.txt
@@ -0,0 +1,6 @@
+add_executable(CruUiTest
+ DeleteLaterTest.cpp
+)
+target_link_libraries(CruUiTest PRIVATE CruUi CruTestBase)
+
+cru_catch_discover_tests(CruUiTest)
diff --git a/test/ui/DeleteLaterTest.cpp b/test/ui/DeleteLaterTest.cpp
new file mode 100644
index 00000000..c9d600de
--- /dev/null
+++ b/test/ui/DeleteLaterTest.cpp
@@ -0,0 +1,17 @@
+#include "cru/ui/DeleteLater.h"
+
+#include <catch2/catch_test_macros.hpp>
+
+struct MockDeleteLater {
+ bool triggered = false;
+
+ void DeleteLater() { triggered = true; }
+};
+
+TEST_CASE("DeleteLaterPtr should work.", "[delete-later]") {
+ auto ptr = cru::ui::MakeDeleteLater<MockDeleteLater>();
+ auto p = ptr.get();
+ ptr.reset();
+ REQUIRE(p->triggered);
+ delete p;
+}