aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
author杨宇千 <crupest@outlook.com>2019-03-28 20:39:36 +0800
committerGitHub <noreply@github.com>2019-03-28 20:39:36 +0800
commitc45a6e62298e972f5945f5f3461ed723aea80317 (patch)
treef46ef303ee87a8e3814ea8743bd7062d432bfee3 /src
parentb028e74a48de181ca078ad3bf4ababf4fa146cd3 (diff)
parent37216f211b0e22205a3a0d3373d985fc68aea59b (diff)
downloadcru-c45a6e62298e972f5945f5f3461ed723aea80317.tar.gz
cru-c45a6e62298e972f5945f5f3461ed723aea80317.tar.bz2
cru-c45a6e62298e972f5945f5f3461ed723aea80317.zip
Merge pull request #37 from crupest/render
Refactor.
Diffstat (limited to 'src')
-rw-r--r--src/CMakeLists.txt37
-rw-r--r--src/any_map.cpp33
-rw-r--r--src/any_map.hpp103
-rw-r--r--src/application.cpp195
-rw-r--r--src/application.hpp177
-rw-r--r--src/base.cpp20
-rw-r--r--src/base.hpp84
-rw-r--r--src/cru_debug.cpp14
-rw-r--r--src/cru_debug.hpp71
-rw-r--r--src/cru_event.hpp134
-rw-r--r--src/exception.cpp76
-rw-r--r--src/exception.hpp94
-rw-r--r--src/format.hpp110
-rw-r--r--src/graph/graph.cpp226
-rw-r--r--src/graph/graph.hpp180
-rw-r--r--src/graph/graph_manager.cpp79
-rw-r--r--src/graph/graph_manager.hpp60
-rw-r--r--src/graph/graph_util.hpp63
-rw-r--r--src/graph/window_render_target.cpp97
-rw-r--r--src/graph/window_render_target.hpp49
-rw-r--r--src/main.cpp244
-rw-r--r--src/math_util.hpp69
-rw-r--r--src/pre.hpp10
-rw-r--r--src/system_headers.hpp25
-rw-r--r--src/timer.cpp105
-rw-r--r--src/timer.hpp90
-rw-r--r--src/ui/animations/animation.cpp172
-rw-r--r--src/ui/animations/animation.hpp130
-rw-r--r--src/ui/border_property.cpp23
-rw-r--r--src/ui/border_property.hpp87
-rw-r--r--src/ui/content_control.cpp30
-rw-r--r--src/ui/content_control.hpp31
-rw-r--r--src/ui/control.cpp942
-rw-r--r--src/ui/control.hpp690
-rw-r--r--src/ui/controls/button.cpp44
-rw-r--r--src/ui/controls/button.hpp61
-rw-r--r--src/ui/controls/flex_layout.cpp21
-rw-r--r--src/ui/controls/flex_layout.hpp41
-rw-r--r--src/ui/controls/frame_layout.cpp70
-rw-r--r--src/ui/controls/frame_layout.hpp40
-rw-r--r--src/ui/controls/linear_layout.cpp151
-rw-r--r--src/ui/controls/linear_layout.hpp50
-rw-r--r--src/ui/controls/list_item.cpp63
-rw-r--r--src/ui/controls/list_item.hpp62
-rw-r--r--src/ui/controls/popup_menu.cpp58
-rw-r--r--src/ui/controls/popup_menu.hpp23
-rw-r--r--src/ui/controls/scroll_control.cpp384
-rw-r--r--src/ui/controls/scroll_control.hpp152
-rw-r--r--src/ui/controls/text_block.cpp30
-rw-r--r--src/ui/controls/text_block.hpp66
-rw-r--r--src/ui/controls/text_box.cpp198
-rw-r--r--src/ui/controls/text_box.hpp47
-rw-r--r--src/ui/controls/text_control.cpp250
-rw-r--r--src/ui/controls/text_control.hpp95
-rw-r--r--src/ui/controls/toggle_button.cpp117
-rw-r--r--src/ui/controls/toggle_button.hpp57
-rw-r--r--src/ui/convert_util.hpp21
-rw-r--r--src/ui/cursor.cpp32
-rw-r--r--src/ui/cursor.hpp43
-rw-r--r--src/ui/d2d_util.hpp82
-rw-r--r--src/ui/events/ui_event.cpp13
-rw-r--r--src/ui/events/ui_event.hpp474
-rw-r--r--src/ui/events/window_event.hpp42
-rw-r--r--src/ui/input_util.cpp20
-rw-r--r--src/ui/input_util.hpp10
-rw-r--r--src/ui/layout_base.cpp6
-rw-r--r--src/ui/layout_base.hpp102
-rw-r--r--src/ui/layout_control.cpp41
-rw-r--r--src/ui/layout_control.hpp33
-rw-r--r--src/ui/no_child_control.cpp5
-rw-r--r--src/ui/no_child_control.hpp26
-rw-r--r--src/ui/render/border_render_object.cpp234
-rw-r--r--src/ui/render/border_render_object.hpp92
-rw-r--r--src/ui/render/flex_layout_render_object.cpp237
-rw-r--r--src/ui/render/flex_layout_render_object.hpp59
-rw-r--r--src/ui/render/render_object.cpp115
-rw-r--r--src/ui/render/render_object.hpp97
-rw-r--r--src/ui/render/text_render_object.cpp148
-rw-r--r--src/ui/render/text_render_object.hpp69
-rw-r--r--src/ui/render/window_render_object.cpp46
-rw-r--r--src/ui/render/window_render_object.hpp40
-rw-r--r--src/ui/ui_base.cpp23
-rw-r--r--src/ui/ui_base.hpp454
-rw-r--r--src/ui/ui_manager.cpp157
-rw-r--r--src/ui/ui_manager.hpp155
-rw-r--r--src/ui/window.cpp1291
-rw-r--r--src/ui/window.hpp416
-rw-r--r--src/ui/window_class.cpp28
-rw-r--r--src/ui/window_class.hpp26
-rw-r--r--src/util/com_util.hpp22
-rw-r--r--src/util/format.hpp100
-rw-r--r--src/util/math_util.hpp51
-rw-r--r--src/util/string_util.cpp22
-rw-r--r--src/util/string_util.hpp8
94 files changed, 4253 insertions, 7217 deletions
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
new file mode 100644
index 00000000..b4097d63
--- /dev/null
+++ b/src/CMakeLists.txt
@@ -0,0 +1,37 @@
+add_library(cru_ui STATIC
+ application.cpp
+ cru_debug.cpp
+ exception.cpp
+ timer.cpp
+ graph/graph_manager.cpp
+ graph/window_render_target.cpp
+ ui/content_control.cpp
+ ui/control.cpp
+ ui/input_util.cpp
+ ui/layout_control.cpp
+ ui/no_child_control.cpp
+ ui/ui_manager.cpp
+ ui/window_class.cpp
+ ui/window.cpp
+ ui/animations/animation.cpp
+ ui/controls/button.cpp
+ ui/controls/flex_layout.cpp
+ ui/controls/text_block.cpp
+ ui/render/border_render_object.cpp
+ ui/render/flex_layout_render_object.cpp
+ ui/render/render_object.cpp
+ ui/render/text_render_object.cpp
+ ui/render/window_render_object.cpp
+ util/string_util.cpp)
+
+
+target_include_directories(cru_ui PUBLIC .)
+
+if(WIN32)
+target_link_libraries(cru_ui PRIVATE D3D11 D2d1 DWrite)
+target_compile_definitions(cru_ui PUBLIC UNICODE _UNICODE) # use unicode
+endif()
+
+add_executable(demo WIN32 main.cpp)
+
+target_link_libraries(demo PRIVATE cru_ui)
diff --git a/src/any_map.cpp b/src/any_map.cpp
deleted file mode 100644
index de13f85e..00000000
--- a/src/any_map.cpp
+++ /dev/null
@@ -1,33 +0,0 @@
-#include "any_map.hpp"
-
-namespace cru
-{
- AnyMap::ListenerToken AnyMap::RegisterValueChangeListener(const String& key, const Listener& listener)
- {
- const auto token = current_listener_token_++;
- map_[key].second.push_back(token);
- listeners_.emplace(token, listener);
- return token;
- }
-
- void AnyMap::UnregisterValueChangeListener(const ListenerToken token)
- {
- const auto find_result = listeners_.find(token);
- if (find_result != listeners_.cend())
- listeners_.erase(find_result);
- }
-
- void AnyMap::InvokeListeners(std::list<ListenerToken>& listener_list, const std::any& value)
- {
- auto i = listener_list.cbegin();
- while (i != listener_list.cend())
- {
- auto current_i = i++;
- const auto find_result = listeners_.find(*current_i);
- if (find_result != listeners_.cend())
- find_result->second(value);
- else
- listener_list.erase(current_i); // otherwise remove the invalid listener token.
- }
- }
-}
diff --git a/src/any_map.hpp b/src/any_map.hpp
deleted file mode 100644
index dfc54f3f..00000000
--- a/src/any_map.hpp
+++ /dev/null
@@ -1,103 +0,0 @@
-#pragma once
-
-// ReSharper disable once CppUnusedIncludeDirective
-#include "pre.hpp"
-
-#include <any>
-#include <unordered_map>
-#include <functional>
-#include <optional>
-#include <typeinfo>
-
-#include "base.hpp"
-#include "format.hpp"
-
-
-namespace cru
-{
- // A map with String as key and any type as value.
- // It also has notification when value with specified key changed.
- class AnyMap : public Object
- {
- public:
- using ListenerToken = long;
- using Listener = std::function<void(const std::any&)>;
-
- AnyMap() = default;
- AnyMap(const AnyMap& other) = delete;
- AnyMap(AnyMap&& other) = delete;
- AnyMap& operator=(const AnyMap& other) = delete;
- AnyMap& operator=(AnyMap&& other) = delete;
- ~AnyMap() override = default;
-
-
- // return the value if the value exists and the type of value is T.
- // return a null optional if value doesn't exists.
- // throw std::runtime_error if type is mismatch.
- template <typename T>
- std::optional<T> GetOptionalValue(const String& key) const
- {
- try
- {
- const auto find_result = map_.find(key);
- if (find_result != map_.cend())
- {
- const auto& value = find_result->second.first;
- if (value.has_value())
- return std::any_cast<T>(value);
- return std::nullopt;
- }
- return std::nullopt;
- }
- catch (const std::bad_any_cast&)
- {
- throw std::runtime_error(Format("Value of key \"{}\" in AnyMap is not of the type {}.", ToUtf8String(key), typeid(T).name()));
- }
- }
-
- // return the value if the value exists and the type of value is T.
- // throw if value doesn't exists. (different from "GetOptionalValue").
- // throw std::runtime_error if type is mismatch.
- template <typename T>
- T GetValue(const String& key) const
- {
- const auto optional_value = GetOptionalValue<T>(key);
- if (optional_value.has_value())
- return optional_value.value();
- else
- throw std::runtime_error(Format("Key \"{}\" does not exists in AnyMap.", ToUtf8String(key)));
- }
-
- // Set the value of key, and trigger all related listeners.
- template <typename T>
- void SetValue(const String& key, T&& value)
- {
- auto& p = map_[key];
- p.first = std::make_any<T>(std::forward<T>(value));
- InvokeListeners(p.second, p.first);
- }
-
- // Remove the value of the key.
- void ClearValue(const String& key)
- {
- auto& p = map_[key];
- p.first = std::any{};
- InvokeListeners(p.second, std::any{});
- }
-
- // Add a listener which is called when value of key is changed.
- // Return a token used to remove the listener.
- ListenerToken RegisterValueChangeListener(const String& key, const Listener& listener);
-
- // Remove a listener by token.
- void UnregisterValueChangeListener(ListenerToken token);
-
- private:
- void InvokeListeners(std::list<ListenerToken>& listener_list, const std::any& value);
-
- private:
- std::unordered_map<String, std::pair<std::any, std::list<ListenerToken>>> map_{};
- std::unordered_map<ListenerToken, Listener> listeners_{};
- ListenerToken current_listener_token_ = 0;
- };
-}
diff --git a/src/application.cpp b/src/application.cpp
index c3669f72..aafca6fe 100644
--- a/src/application.cpp
+++ b/src/application.cpp
@@ -1,136 +1,113 @@
#include "application.hpp"
+#include <VersionHelpers.h>
+
#include "exception.hpp"
#include "timer.hpp"
-#include "ui/window.hpp"
-#include "ui/cursor.hpp"
+#include "ui/window_class.hpp"
namespace cru {
- constexpr auto god_window_class_name = L"GodWindowClass";
- constexpr int invoke_later_message_id = WM_USER + 2000;
-
-
- LRESULT CALLBACK GodWndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
- {
- const auto app = Application::GetInstance();
-
- if (app)
- {
- const auto result = app->GetGodWindow()->HandleGodWindowMessage(hWnd, uMsg, wParam, lParam);
- if (result.has_value())
- return result.value();
- else
- return DefWindowProc(hWnd, uMsg, wParam, lParam);
- }
- else
- return DefWindowProc(hWnd, uMsg, wParam, lParam);
- }
-
- GodWindow::GodWindow(Application* application)
- {
- const auto h_instance = application->GetInstanceHandle();
+constexpr auto god_window_class_name = L"GodWindowClass";
+constexpr int invoke_later_message_id = WM_USER + 2000;
+
+LRESULT CALLBACK GodWndProc(HWND hWnd, UINT uMsg, WPARAM wParam,
+ LPARAM lParam) {
+ const auto app = Application::GetInstance();
+
+ if (app) {
+ const auto result =
+ app->GetGodWindow()->HandleGodWindowMessage(hWnd, uMsg, wParam, lParam);
+ if (result.has_value())
+ return result.value();
+ else
+ return DefWindowProc(hWnd, uMsg, wParam, lParam);
+ } else
+ return DefWindowProc(hWnd, uMsg, wParam, lParam);
+}
- god_window_class_ = std::make_unique<ui::WindowClass>(god_window_class_name, GodWndProc, h_instance);
+GodWindow::GodWindow(Application* application) {
+ const auto h_instance = application->GetInstanceHandle();
- hwnd_ = CreateWindowEx(0,
- god_window_class_name,
- L"", 0,
- CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,
- HWND_MESSAGE, nullptr, h_instance, nullptr
- );
+ god_window_class_ = std::make_unique<ui::WindowClass>(god_window_class_name,
+ GodWndProc, h_instance);
- if (hwnd_ == nullptr)
- throw std::runtime_error("Failed to create window.");
- }
+ hwnd_ = CreateWindowEx(0, god_window_class_name, L"", 0, CW_USEDEFAULT,
+ CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,
+ HWND_MESSAGE, nullptr, h_instance, nullptr);
- GodWindow::~GodWindow()
- {
- ::DestroyWindow(hwnd_);
- }
+ if (hwnd_ == nullptr) throw std::runtime_error("Failed to create window.");
+}
- std::optional<LRESULT> GodWindow::HandleGodWindowMessage(HWND hwnd, int msg, WPARAM w_param, LPARAM l_param)
- {
- switch (msg)
- {
- case invoke_later_message_id:
- {
- const auto p_action = reinterpret_cast<std::function<void()>*>(w_param);
- (*p_action)();
- delete p_action;
- return 0;
- }
- case WM_TIMER:
- {
- const auto id = static_cast<UINT_PTR>(w_param);
- const auto action = TimerManager::GetInstance()->GetAction(id);
- if (action.has_value())
- {
- (action.value().second)();
- if (!action.value().first)
- TimerManager::GetInstance()->KillTimer(id);
- return 0;
- }
- break;
- }
- default:
- return std::nullopt;
- }
- return std::nullopt;
+GodWindow::~GodWindow() { ::DestroyWindow(hwnd_); }
+
+std::optional<LRESULT> GodWindow::HandleGodWindowMessage(HWND hwnd, int msg,
+ WPARAM w_param,
+ LPARAM l_param) {
+ switch (msg) {
+ case invoke_later_message_id: {
+ const auto p_action = reinterpret_cast<std::function<void()>*>(w_param);
+ (*p_action)();
+ delete p_action;
+ return 0;
}
-
-
-
- Application* Application::instance_ = nullptr;
-
- Application * Application::GetInstance() {
- return instance_;
+ case WM_TIMER: {
+ const auto id = static_cast<UINT_PTR>(w_param);
+ const auto action = TimerManager::GetInstance()->GetAction(id);
+ if (action.has_value()) {
+ (action.value().second)();
+ if (!action.value().first) TimerManager::GetInstance()->KillTimer(id);
+ return 0;
+ }
+ break;
}
+ default:
+ return std::nullopt;
+ }
+ return std::nullopt;
+}
- Application::Application(HINSTANCE h_instance)
- : h_instance_(h_instance) {
+Application* Application::instance_ = nullptr;
- if (instance_)
- throw std::runtime_error("A application instance already exists.");
+Application* Application::GetInstance() { return instance_; }
- instance_ = this;
+Application::Application(HINSTANCE h_instance) : h_instance_(h_instance) {
+ if (instance_)
+ throw std::runtime_error("A application instance already exists.");
- if (!::IsWindows8OrGreater())
- throw std::runtime_error("Must run on Windows 8 or later.");
+ instance_ = this;
- god_window_ = std::make_unique<GodWindow>(this);
+ if (!::IsWindows8OrGreater())
+ throw std::runtime_error("Must run on Windows 8 or later.");
- ui::cursors::LoadSystemCursors();
- }
+ god_window_ = std::make_unique<GodWindow>(this);
+}
- Application::~Application()
- {
- for (auto i = singleton_list_.crbegin(); i != singleton_list_.crend(); ++i)
- delete *i;
- instance_ = nullptr;
- }
+Application::~Application() {
+ for (auto i = singleton_list_.crbegin(); i != singleton_list_.crend(); ++i)
+ delete *i;
+ instance_ = nullptr;
+}
- int Application::Run()
- {
- MSG msg;
+int Application::Run() {
+ MSG msg;
- while (GetMessage(&msg, nullptr, 0, 0))
- {
- TranslateMessage(&msg);
- DispatchMessage(&msg);
- }
+ while (GetMessage(&msg, nullptr, 0, 0)) {
+ TranslateMessage(&msg);
+ DispatchMessage(&msg);
+ }
- return static_cast<int>(msg.wParam);
- }
+ return static_cast<int>(msg.wParam);
+}
- void Application::Quit(const int quit_code) {
- ::PostQuitMessage(quit_code);
- }
+void Application::Quit(const int quit_code) { ::PostQuitMessage(quit_code); }
- void InvokeLater(const std::function<void()>& action) {
- //copy the action to a safe place
- auto p_action_copy = new std::function<void()>(action);
+void InvokeLater(const std::function<void()>& action) {
+ // copy the action to a safe place
+ auto p_action_copy = new std::function<void()>(action);
- if (PostMessageW(Application::GetInstance()->GetGodWindow()->GetHandle(), invoke_later_message_id, reinterpret_cast<WPARAM>(p_action_copy), 0) == 0)
- throw Win32Error(::GetLastError(), "InvokeLater failed to post message.");
- }
+ if (PostMessageW(Application::GetInstance()->GetGodWindow()->GetHandle(),
+ invoke_later_message_id,
+ reinterpret_cast<WPARAM>(p_action_copy), 0) == 0)
+ throw Win32Error(::GetLastError(), "InvokeLater failed to post message.");
}
+} // namespace cru
diff --git a/src/application.hpp b/src/application.hpp
index a8d59cc8..f5f69ea4 100644
--- a/src/application.hpp
+++ b/src/application.hpp
@@ -1,14 +1,12 @@
#pragma once
-
-// ReSharper disable once CppUnusedIncludeDirective
#include "pre.hpp"
-#include "system_headers.hpp"
+#include <Windows.h>
+#include <functional>
#include <memory>
#include <optional>
-#include <functional>
-#include <typeindex>
#include <type_traits>
+#include <typeindex>
#include "base.hpp"
@@ -16,103 +14,92 @@
#include <unordered_set>
#endif
-namespace cru
-{
- class Application;
-
- namespace ui
- {
- class WindowClass;
- }
-
-
- class GodWindow : public Object
- {
- public:
- explicit GodWindow(Application* application);
- GodWindow(const GodWindow& other) = delete;
- GodWindow(GodWindow&& other) = delete;
- GodWindow& operator=(const GodWindow& other) = delete;
- GodWindow& operator=(GodWindow&& other) = delete;
- ~GodWindow() override;
-
- HWND GetHandle() const
- {
- return hwnd_;
- }
-
- std::optional<LRESULT> HandleGodWindowMessage(HWND hwnd, int msg, WPARAM w_param, LPARAM l_param);
-
- private:
- std::unique_ptr<ui::WindowClass> god_window_class_;
- HWND hwnd_;
- };
-
- class Application : public Object
- {
- public:
- static Application* GetInstance();
- private:
- static Application* instance_;
-
- public:
- explicit Application(HINSTANCE h_instance);
- Application(const Application&) = delete;
- Application(Application&&) = delete;
- Application& operator = (const Application&) = delete;
- Application& operator = (Application&&) = delete;
- ~Application() override;
-
- public:
- int Run();
- void Quit(int quit_code);
-
-
- HINSTANCE GetInstanceHandle() const
- {
- return h_instance_;
- }
-
- GodWindow* GetGodWindow() const
- {
- return god_window_.get();
- }
-
- // Resolve a singleton.
- // All singletons will be delete in reverse order of resolve.
- template<typename T, typename = std::enable_if_t<std::is_base_of_v<Object, T>>>
- T* ResolveSingleton(const std::function<T*(Application*)>& creator)
- {
- const auto& index = std::type_index{typeid(T)};
- const auto find_result = singleton_map_.find(index);
- if (find_result != singleton_map_.cend())
- return static_cast<T*>(find_result->second);
+namespace cru {
+class Application;
+
+namespace ui {
+class WindowClass;
+}
+
+class GodWindow : public Object {
+ public:
+ explicit GodWindow(Application* application);
+ GodWindow(const GodWindow& other) = delete;
+ GodWindow(GodWindow&& other) = delete;
+ GodWindow& operator=(const GodWindow& other) = delete;
+ GodWindow& operator=(GodWindow&& other) = delete;
+ ~GodWindow() override;
+
+ HWND GetHandle() const { return hwnd_; }
+
+ std::optional<LRESULT> HandleGodWindowMessage(HWND hwnd, int msg,
+ WPARAM w_param, LPARAM l_param);
+
+ private:
+ std::unique_ptr<ui::WindowClass> god_window_class_;
+ HWND hwnd_;
+};
+
+class Application : public Object {
+ public:
+ static Application* GetInstance();
+
+ private:
+ static Application* instance_;
+
+ public:
+ explicit Application(HINSTANCE h_instance);
+ Application(const Application&) = delete;
+ Application(Application&&) = delete;
+ Application& operator=(const Application&) = delete;
+ Application& operator=(Application&&) = delete;
+ ~Application() override;
+
+ public:
+ int Run();
+ void Quit(int quit_code);
+
+ HINSTANCE GetInstanceHandle() const { return h_instance_; }
+
+ GodWindow* GetGodWindow() const { return god_window_.get(); }
+
+ // Resolve a singleton.
+ // All singletons will be delete in reverse order of resolve.
+ template <typename T,
+ typename = std::enable_if_t<std::is_base_of_v<Object, T>>>
+ T* ResolveSingleton(const std::function<T*(Application*)>& creator) {
+ const auto& index = std::type_index{typeid(T)};
+ const auto find_result = singleton_map_.find(index);
+ if (find_result != singleton_map_.cend())
+ return static_cast<T*>(find_result->second);
#ifdef CRU_DEBUG
- const auto type_find_result = singleton_type_set_.find(index);
- if (type_find_result != singleton_type_set_.cend())
- throw std::logic_error("The singleton of that type is being constructed. This may cause a dead recursion.");
- singleton_type_set_.insert(index);
+ const auto type_find_result = singleton_type_set_.find(index);
+ if (type_find_result != singleton_type_set_.cend())
+ throw std::logic_error(
+ "The singleton of that type is being constructed. This may cause a "
+ "dead recursion.");
+ singleton_type_set_.insert(index);
#endif
- auto singleton = creator(this);
- singleton_map_.emplace(index, static_cast<Object*>(singleton));
- singleton_list_.push_back(singleton);
- return singleton;
- }
+ auto singleton = creator(this);
+ singleton_map_.emplace(index, static_cast<Object*>(singleton));
+ singleton_list_.push_back(singleton);
+ return singleton;
+ }
- private:
- HINSTANCE h_instance_;
+ private:
+ HINSTANCE h_instance_;
- std::unique_ptr<GodWindow> god_window_;
+ std::unique_ptr<GodWindow> god_window_;
- std::unordered_map<std::type_index, Object*> singleton_map_;
- std::list<Object*> singleton_list_; // used for reverse destroy.
+ std::unordered_map<std::type_index, Object*> singleton_map_;
+ std::list<Object*> singleton_list_; // used for reverse destroy.
#ifdef CRU_DEBUG
- std::unordered_set<std::type_index> singleton_type_set_; // used for dead recursion.
+ std::unordered_set<std::type_index>
+ singleton_type_set_; // used for detecting dead recursion.
#endif
- };
+};
-
- void InvokeLater(const std::function<void()>& action);
-}
+void InvokeLater(const std::function<void()>& action);
+} // namespace cru
diff --git a/src/base.cpp b/src/base.cpp
deleted file mode 100644
index a2d20fc4..00000000
--- a/src/base.cpp
+++ /dev/null
@@ -1,20 +0,0 @@
-#include "base.hpp"
-
-#include "system_headers.hpp"
-#include "exception.hpp"
-
-namespace cru
-{
- MultiByteString ToUtf8String(const StringView& string)
- {
- if (string.empty())
- return MultiByteString();
-
- const auto length = ::WideCharToMultiByte(CP_UTF8, 0, string.data(), -1, nullptr, 0, nullptr, nullptr);
- MultiByteString result;
- result.reserve(length);
- if (::WideCharToMultiByte(CP_UTF8, 0, string.data(), -1, result.data(), static_cast<int>(result.capacity()), nullptr, nullptr) == 0)
- throw Win32Error(::GetLastError(), "Failed to convert wide string to UTF-8.");
- return result;
- }
-}
diff --git a/src/base.hpp b/src/base.hpp
index 64ce7f6e..e3dfc1ee 100644
--- a/src/base.hpp
+++ b/src/base.hpp
@@ -1,59 +1,39 @@
#pragma once
-
-// ReSharper disable once CppUnusedIncludeDirective
#include "pre.hpp"
-#include <string>
+#include <cassert>
+#include <chrono>
#include <stdexcept>
+#include <string>
#include <string_view>
-#include <chrono>
-namespace cru
-{
- template<typename T> struct type_tag {};
-
- //typedefs
- using String = std::wstring;
- using MultiByteString = std::string;
-
- using StringView = std::wstring_view;
- using MultiByteStringView = std::string_view;
-
- using FloatSecond = std::chrono::duration<double, std::chrono::seconds::period>;
-
- enum class FlowControl
- {
- Continue,
- Break
- };
-
-
- class Object
- {
- public:
- Object() = default;
- Object(const Object&) = default;
- Object& operator = (const Object&) = default;
- Object(Object&&) = default;
- Object& operator = (Object&&) = default;
- virtual ~Object() = default;
- };
-
- struct Interface
- {
- virtual ~Interface() = default;
- };
-
- [[noreturn]] inline void UnreachableCode()
- {
- throw std::logic_error("Unreachable code.");
- }
-
- MultiByteString ToUtf8String(const StringView& string);
-
- inline void Require(const bool condition, const MultiByteStringView& error_message)
- {
- if (!condition)
- throw std::invalid_argument(error_message.data());
- }
+namespace cru {
+// typedefs
+using String = std::wstring;
+using MultiByteString = std::string;
+
+using StringView = std::wstring_view;
+using MultiByteStringView = std::string_view;
+
+using FloatSecond = std::chrono::duration<double, std::chrono::seconds::period>;
+
+enum class FlowControl { Continue, Break };
+
+class Object {
+ public:
+ Object() = default;
+ Object(const Object&) = default;
+ Object& operator=(const Object&) = default;
+ Object(Object&&) = default;
+ Object& operator=(Object&&) = default;
+ virtual ~Object() = default;
+};
+
+struct Interface {
+ virtual ~Interface() = default;
+};
+
+[[noreturn]] inline void UnreachableCode() {
+ throw std::logic_error("Unreachable code.");
}
+} // namespace cru
diff --git a/src/cru_debug.cpp b/src/cru_debug.cpp
index 9c61d052..81945227 100644
--- a/src/cru_debug.cpp
+++ b/src/cru_debug.cpp
@@ -1,11 +1,11 @@
#include "cru_debug.hpp"
-#include "system_headers.hpp"
+#include <Windows.h>
-namespace cru::debug
-{
- void DebugMessage(const StringView& message)
- {
- ::OutputDebugStringW(message.data());
- }
+namespace cru::debug {
+#ifdef CRU_DEBUG
+void DebugMessage(const StringView& message) {
+ ::OutputDebugStringW(message.data());
}
+#endif
+} // namespace cru::debug
diff --git a/src/cru_debug.hpp b/src/cru_debug.hpp
index 17cc7b53..58431d56 100644
--- a/src/cru_debug.hpp
+++ b/src/cru_debug.hpp
@@ -1,47 +1,50 @@
#pragma once
-
-// ReSharper disable once CppUnusedIncludeDirective
#include "pre.hpp"
#include <functional>
#include "base.hpp"
-#include "format.hpp"
+#include "util/format.hpp"
-namespace cru::debug
-{
- void DebugMessage(const StringView& message);
+namespace cru::debug {
+#ifdef CRU_DEBUG
+void DebugMessage(const StringView& message);
+#else
+inline void DebugMessage(const StringView& message) {}
+#endif
#ifdef CRU_DEBUG
- inline void DebugTime(const std::function<void()>& action, const StringView& hint_message)
- {
- const auto before = std::chrono::steady_clock::now();
- action();
- const auto after = std::chrono::steady_clock::now();
- const auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(after - before);
- DebugMessage(Format(L"{}: {}ms.\n", hint_message, duration.count()));
- }
+inline void DebugTime(const std::function<void()>& action,
+ const StringView& hint_message) {
+ const auto before = std::chrono::steady_clock::now();
+ action();
+ const auto after = std::chrono::steady_clock::now();
+ const auto duration =
+ std::chrono::duration_cast<std::chrono::milliseconds>(after - before);
+ DebugMessage(util::Format(L"{}: {}ms.\n", hint_message, duration.count()));
+}
- template<typename TReturn>
- TReturn DebugTime(const std::function<TReturn()>& action, const StringView& hint_message)
- {
- const auto before = std::chrono::steady_clock::now();
- auto&& result = action();
- const auto after = std::chrono::steady_clock::now();
- const auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(after - before);
- DebugMessage(Format(L"{}: {}ms.\n", hint_message, duration.count()));
- return std::move(result);
- }
+template <typename TReturn>
+TReturn DebugTime(const std::function<TReturn()>& action,
+ const StringView& hint_message) {
+ const auto before = std::chrono::steady_clock::now();
+ auto&& result = action();
+ const auto after = std::chrono::steady_clock::now();
+ const auto duration =
+ std::chrono::duration_cast<std::chrono::milliseconds>(after - before);
+ DebugMessage(util::Format(L"{}: {}ms.\n", hint_message, duration.count()));
+ return std::move(result);
+}
#else
- inline void DebugTime(const std::function<void()>& action, const StringView& hint_message)
- {
- action();
- }
+inline void DebugTime(const std::function<void()>& action,
+ const StringView& hint_message) {
+ action();
+}
- template<typename TReturn>
- TReturn DebugTime(const std::function<TReturn()>& action, const StringView& hint_message)
- {
- return action();
- }
-#endif
+template <typename TReturn>
+TReturn DebugTime(const std::function<TReturn()>& action,
+ const StringView& hint_message) {
+ return action();
}
+#endif
+} // namespace cru::debug
diff --git a/src/cru_event.hpp b/src/cru_event.hpp
index fa41d646..a669695f 100644
--- a/src/cru_event.hpp
+++ b/src/cru_event.hpp
@@ -1,84 +1,68 @@
#pragma once
-
-// ReSharper disable once CppUnusedIncludeDirective
#include "pre.hpp"
-#include <type_traits>
#include <functional>
#include <map>
+#include <type_traits>
#include "base.hpp"
namespace cru {
- //Base class of all event args.
- class BasicEventArgs : public Object
- {
- public:
- explicit BasicEventArgs(Object* sender)
- : sender_(sender)
- {
-
- }
- BasicEventArgs(const BasicEventArgs& other) = default;
- BasicEventArgs(BasicEventArgs&& other) = default;
- BasicEventArgs& operator=(const BasicEventArgs& other) = default;
- BasicEventArgs& operator=(BasicEventArgs&& other) = default;
- ~BasicEventArgs() override = default;
-
- //Get the sender of the event.
- Object* GetSender() const
- {
- return sender_;
- }
-
- private:
- Object* sender_;
- };
-
-
- //A non-copyable non-movable Event class.
- //It stores a list of event handlers.
- //TArgsType must be subclass of BasicEventArgs.
- template<typename TArgsType>
- class Event
- {
- public:
- static_assert(std::is_base_of_v<BasicEventArgs, TArgsType>,
- "TArgsType must be subclass of BasicEventArgs.");
-
-
- using ArgsType = TArgsType;
- using EventHandler = std::function<void(ArgsType&)>;
- using EventHandlerToken = long;
-
- Event() = default;
- Event(const Event&) = delete;
- Event& operator = (const Event&) = delete;
- Event(Event&&) = delete;
- Event& operator = (Event&&) = delete;
- ~Event() = default;
-
- EventHandlerToken AddHandler(const EventHandler& handler)
- {
- const auto token = current_token_++;
- handlers_.emplace(token, handler);
- return token;
- }
-
- void RemoveHandler(const EventHandlerToken token) {
- auto find_result = handlers_.find(token);
- if (find_result != handlers_.cend())
- handlers_.erase(find_result);
- }
-
- void Raise(ArgsType& args) {
- for (const auto& handler : handlers_)
- (handler.second)(args);
- }
-
- private:
- std::map<EventHandlerToken, EventHandler> handlers_;
-
- EventHandlerToken current_token_ = 0;
- };
-}
+// Base class of all event args.
+class BasicEventArgs : public Object {
+ public:
+ explicit BasicEventArgs(Object* sender) : sender_(sender) {}
+ BasicEventArgs(const BasicEventArgs& other) = default;
+ BasicEventArgs(BasicEventArgs&& other) = default;
+ BasicEventArgs& operator=(const BasicEventArgs& other) = default;
+ BasicEventArgs& operator=(BasicEventArgs&& other) = default;
+ ~BasicEventArgs() override = default;
+
+ // Get the sender of the event.
+ Object* GetSender() const { return sender_; }
+
+ private:
+ Object* sender_;
+};
+
+// A non-copyable non-movable Event class.
+// It stores a list of event handlers.
+// TArgsType must be subclass of BasicEventArgs.
+template <typename TArgsType>
+class Event {
+ public:
+ static_assert(std::is_base_of_v<BasicEventArgs, TArgsType>,
+ "TArgsType must be subclass of BasicEventArgs.");
+
+ using ArgsType = TArgsType;
+ using EventHandler = std::function<void(ArgsType&)>;
+ using EventHandlerToken = long;
+
+ Event() = default;
+ Event(const Event&) = delete;
+ Event& operator=(const Event&) = delete;
+ Event(Event&&) = delete;
+ Event& operator=(Event&&) = delete;
+ ~Event() = default;
+
+ EventHandlerToken AddHandler(const EventHandler& handler) {
+ const auto token = current_token_++;
+ handlers_.emplace(token, handler);
+ return token;
+ }
+
+ void RemoveHandler(const EventHandlerToken token) {
+ auto find_result = handlers_.find(token);
+ if (find_result != handlers_.cend()) handlers_.erase(find_result);
+ }
+
+ void Raise(ArgsType& args) {
+ for (const auto& handler : handlers_) (handler.second)(args);
+ }
+
+ private:
+ std::map<EventHandlerToken, EventHandler> handlers_;
+
+ EventHandlerToken current_token_ = 0;
+};
+} // namespace cru
diff --git a/src/exception.cpp b/src/exception.cpp
index cb1ca2c7..dbc98453 100644
--- a/src/exception.cpp
+++ b/src/exception.cpp
@@ -1,40 +1,42 @@
#include "exception.hpp"
-#include "format.hpp"
-
-namespace cru
-{
- inline std::string HResultMakeMessage(HRESULT h_result, std::optional<MultiByteStringView> message)
- {
- char buffer[10];
- sprintf_s(buffer, "%#08x", h_result);
-
- if (message.has_value())
- return Format("An HResultError is thrown. HRESULT: {}.\nAdditional message: {}\n", buffer, message.value());
- else
- return Format("An HResultError is thrown. HRESULT: {}.\n", buffer);
- }
-
- HResultError::HResultError(HRESULT h_result, std::optional<MultiByteStringView> additional_message)
- : runtime_error(HResultMakeMessage(h_result, std::nullopt)), h_result_(h_result)
- {
-
- }
-
- inline std::string Win32MakeMessage(DWORD error_code, std::optional<MultiByteStringView> message)
- {
- char buffer[10];
- sprintf_s(buffer, "%#04x", error_code);
-
- if (message.has_value())
- return Format("Last error code: {}.\nAdditional message: {}\n", buffer, message.value());
- else
- return Format("Last error code: {}.\n", buffer);
- }
-
- Win32Error::Win32Error(DWORD error_code, std::optional<MultiByteStringView> additional_message)
- : runtime_error(Win32MakeMessage(error_code, std::nullopt)), error_code_(error_code)
- {
-
- }
+#include "util/format.hpp"
+
+namespace cru {
+using util::Format;
+
+inline std::string HResultMakeMessage(
+ HRESULT h_result, std::optional<MultiByteStringView> message) {
+ char buffer[10];
+ sprintf_s(buffer, "%#08x", h_result);
+
+ if (message.has_value())
+ return Format(
+ "An HResultError is thrown. HRESULT: {}.\nAdditional message: {}\n",
+ buffer, message.value());
+ else
+ return Format("An HResultError is thrown. HRESULT: {}.\n", buffer);
}
+
+HResultError::HResultError(
+ HRESULT h_result, std::optional<MultiByteStringView> additional_message)
+ : runtime_error(HResultMakeMessage(h_result, std::nullopt)),
+ h_result_(h_result) {}
+
+inline std::string Win32MakeMessage(
+ DWORD error_code, std::optional<MultiByteStringView> message) {
+ char buffer[10];
+ sprintf_s(buffer, "%#04x", error_code);
+
+ if (message.has_value())
+ return Format("Last error code: {}.\nAdditional message: {}\n", buffer,
+ message.value());
+ else
+ return Format("Last error code: {}.\n", buffer);
+}
+
+Win32Error::Win32Error(DWORD error_code,
+ std::optional<MultiByteStringView> additional_message)
+ : runtime_error(Win32MakeMessage(error_code, std::nullopt)),
+ error_code_(error_code) {}
+} // namespace cru
diff --git a/src/exception.hpp b/src/exception.hpp
index b8cef604..ade51d54 100644
--- a/src/exception.hpp
+++ b/src/exception.hpp
@@ -1,60 +1,52 @@
#pragma once
-
-// ReSharper disable once CppUnusedIncludeDirective
#include "pre.hpp"
-#include "system_headers.hpp"
+#include <Windows.h>
#include <optional>
#include "base.hpp"
-
namespace cru {
- class HResultError : public std::runtime_error
- {
- public:
- explicit HResultError(HRESULT h_result, std::optional<MultiByteStringView> additional_message = std::nullopt);
- HResultError(const HResultError& other) = default;
- HResultError(HResultError&& other) = default;
- HResultError& operator=(const HResultError& other) = default;
- HResultError& operator=(HResultError&& other) = default;
- ~HResultError() override = default;
-
- HRESULT GetHResult() const
- {
- return h_result_;
- }
-
- private:
- HRESULT h_result_;
- };
-
- inline void ThrowIfFailed(const HRESULT h_result) {
- if (FAILED(h_result))
- throw HResultError(h_result);
- }
-
- inline void ThrowIfFailed(const HRESULT h_result, const MultiByteStringView& message) {
- if (FAILED(h_result))
- throw HResultError(h_result, message);
- }
-
- class Win32Error : public std::runtime_error
- {
- public:
- explicit Win32Error(DWORD error_code, std::optional<MultiByteStringView> additional_message = std::nullopt);
- Win32Error(const Win32Error& other) = default;
- Win32Error(Win32Error&& other) = default;
- Win32Error& operator=(const Win32Error& other) = default;
- Win32Error& operator=(Win32Error&& other) = default;
- ~Win32Error() override = default;
-
- HRESULT GetErrorCode() const
- {
- return error_code_;
- }
-
- private:
- DWORD error_code_;
- };
+class HResultError : public std::runtime_error {
+ public:
+ explicit HResultError(
+ HRESULT h_result,
+ std::optional<MultiByteStringView> additional_message = std::nullopt);
+ HResultError(const HResultError& other) = default;
+ HResultError(HResultError&& other) = default;
+ HResultError& operator=(const HResultError& other) = default;
+ HResultError& operator=(HResultError&& other) = default;
+ ~HResultError() override = default;
+
+ HRESULT GetHResult() const { return h_result_; }
+
+ private:
+ HRESULT h_result_;
+};
+
+inline void ThrowIfFailed(const HRESULT h_result) {
+ if (FAILED(h_result)) throw HResultError(h_result);
}
+
+inline void ThrowIfFailed(const HRESULT h_result,
+ const MultiByteStringView& message) {
+ if (FAILED(h_result)) throw HResultError(h_result, message);
+}
+
+class Win32Error : public std::runtime_error {
+ public:
+ explicit Win32Error(
+ DWORD error_code,
+ std::optional<MultiByteStringView> additional_message = std::nullopt);
+ Win32Error(const Win32Error& other) = default;
+ Win32Error(Win32Error&& other) = default;
+ Win32Error& operator=(const Win32Error& other) = default;
+ Win32Error& operator=(Win32Error&& other) = default;
+ ~Win32Error() override = default;
+
+ HRESULT GetErrorCode() const { return error_code_; }
+
+ private:
+ DWORD error_code_;
+};
+} // namespace cru
diff --git a/src/format.hpp b/src/format.hpp
deleted file mode 100644
index efd25f89..00000000
--- a/src/format.hpp
+++ /dev/null
@@ -1,110 +0,0 @@
-#pragma once
-
-// ReSharper disable once CppUnusedIncludeDirective
-#include "pre.hpp"
-
-#include "base.hpp"
-
-namespace cru
-{
- namespace details
- {
- constexpr StringView PlaceHolder(type_tag<String>)
- {
- return StringView(L"{}");
- }
-
- constexpr MultiByteStringView PlaceHolder(type_tag<MultiByteString>)
- {
- return MultiByteStringView("{}");
- }
-
- template<typename TString>
- void FormatInternal(TString& string)
- {
- const auto find_result = string.find(PlaceHolder(type_tag<TString>{}));
- if (find_result != TString::npos)
- throw std::invalid_argument("There is more placeholders than args.");
- }
-
- template<typename TString, typename T, typename... TRest>
- void FormatInternal(TString& string, const T& arg, const TRest&... args)
- {
- const auto find_result = string.find(PlaceHolder(type_tag<TString>{}));
- if (find_result == TString::npos)
- throw std::invalid_argument("There is less placeholders than args.");
-
- string.replace(find_result, 2, FormatToString(arg, type_tag<TString>{}));
- FormatInternal<TString>(string, args...);
- }
- }
-
- template<typename... T>
- String Format(const StringView& format, const T&... args)
- {
- String result(format);
- details::FormatInternal<String>(result, args...);
- return result;
- }
-
- template<typename... T>
- MultiByteString Format(const MultiByteStringView& format, const T&... args)
- {
- MultiByteString result(format);
- details::FormatInternal<MultiByteString>(result, args...);
- return result;
- }
-
-#define CRU_FORMAT_NUMBER(type) \
- inline String FormatToString(const type number, type_tag<String>) \
- { \
- return std::to_wstring(number); \
- } \
- inline MultiByteString FormatToString(const type number, type_tag<MultiByteString>) \
- { \
- return std::to_string(number); \
- }
-
- CRU_FORMAT_NUMBER(int)
- CRU_FORMAT_NUMBER(short)
- CRU_FORMAT_NUMBER(long)
- CRU_FORMAT_NUMBER(long long)
- CRU_FORMAT_NUMBER(unsigned int)
- CRU_FORMAT_NUMBER(unsigned short)
- CRU_FORMAT_NUMBER(unsigned long)
- CRU_FORMAT_NUMBER(unsigned long long)
- CRU_FORMAT_NUMBER(float)
- CRU_FORMAT_NUMBER(double)
-
-#undef CRU_FORMAT_NUMBER
-
- inline StringView FormatToString(const String& string, type_tag<String>)
- {
- return string;
- }
-
- inline MultiByteString FormatToString(const MultiByteString& string, type_tag<MultiByteString>)
- {
- return string;
- }
-
- inline StringView FormatToString(const StringView& string, type_tag<String>)
- {
- return string;
- }
-
- inline MultiByteStringView FormatToString(const MultiByteStringView& string, type_tag<MultiByteString>)
- {
- return string;
- }
-
- inline StringView FormatToString(const wchar_t* string, type_tag<String>)
- {
- return StringView(string);
- }
-
- inline MultiByteStringView FormatToString(const char* string, type_tag<MultiByteString>)
- {
- return MultiByteString(string);
- }
-}
diff --git a/src/graph/graph.cpp b/src/graph/graph.cpp
deleted file mode 100644
index eef95c8c..00000000
--- a/src/graph/graph.cpp
+++ /dev/null
@@ -1,226 +0,0 @@
-#include "graph.hpp"
-
-#include "application.hpp"
-#include "exception.hpp"
-
-namespace cru::graph
-{
- using Microsoft::WRL::ComPtr;
-
- WindowRenderTarget::WindowRenderTarget(GraphManager* graph_manager, HWND hwnd)
- {
- this->graph_manager_ = graph_manager;
-
- const auto d3d11_device = graph_manager->GetD3D11Device();
- const auto dxgi_factory = graph_manager->GetDxgiFactory();
-
- // Allocate a descriptor.
- DXGI_SWAP_CHAIN_DESC1 swap_chain_desc = { 0 };
- swap_chain_desc.Width = 0; // use automatic sizing
- swap_chain_desc.Height = 0;
- swap_chain_desc.Format = DXGI_FORMAT_B8G8R8A8_UNORM; // this is the most common swapchain format
- swap_chain_desc.Stereo = false;
- swap_chain_desc.SampleDesc.Count = 1; // don't use multi-sampling
- swap_chain_desc.SampleDesc.Quality = 0;
- swap_chain_desc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
- swap_chain_desc.BufferCount = 2; // use double buffering to enable flip
- swap_chain_desc.Scaling = DXGI_SCALING_NONE;
- swap_chain_desc.SwapEffect = DXGI_SWAP_EFFECT_FLIP_SEQUENTIAL; // all apps must use this SwapEffect
- swap_chain_desc.Flags = 0;
-
-
-
- // Get the final swap chain for this window from the DXGI factory.
- ThrowIfFailed(
- dxgi_factory->CreateSwapChainForHwnd(
- d3d11_device.Get(),
- hwnd,
- &swap_chain_desc,
- nullptr,
- nullptr,
- &dxgi_swap_chain_
- )
- );
-
- CreateTargetBitmap();
- }
-
- WindowRenderTarget::~WindowRenderTarget()
- {
-
- }
-
- void WindowRenderTarget::ResizeBuffer(const int width, const int height)
- {
- const auto graph_manager = graph_manager_;
- const auto d2d1_device_context = graph_manager->GetD2D1DeviceContext();
-
- ComPtr<ID2D1Image> old_target;
- d2d1_device_context->GetTarget(&old_target);
- const auto target_this = old_target == this->target_bitmap_;
- if (target_this)
- d2d1_device_context->SetTarget(nullptr);
-
- old_target = nullptr;
- target_bitmap_ = nullptr;
-
- ThrowIfFailed(
- dxgi_swap_chain_->ResizeBuffers(0, width, height, DXGI_FORMAT_UNKNOWN, 0)
- );
-
- CreateTargetBitmap();
-
- if (target_this)
- d2d1_device_context->SetTarget(target_bitmap_.Get());
- }
-
- void WindowRenderTarget::SetAsTarget()
- {
- GetD2DDeviceContext()->SetTarget(target_bitmap_.Get());
- }
-
- void WindowRenderTarget::Present()
- {
- ThrowIfFailed(
- dxgi_swap_chain_->Present(1, 0)
- );
- }
-
- void WindowRenderTarget::CreateTargetBitmap()
- {
- // Direct2D needs the dxgi version of the backbuffer surface pointer.
- ComPtr<IDXGISurface> dxgiBackBuffer;
- ThrowIfFailed(
- dxgi_swap_chain_->GetBuffer(0, IID_PPV_ARGS(&dxgiBackBuffer))
- );
-
- const auto dpi = graph_manager_->GetDpi();
-
- auto bitmap_properties =
- D2D1::BitmapProperties1(
- D2D1_BITMAP_OPTIONS_TARGET | D2D1_BITMAP_OPTIONS_CANNOT_DRAW,
- D2D1::PixelFormat(DXGI_FORMAT_B8G8R8A8_UNORM, D2D1_ALPHA_MODE_IGNORE),
- dpi.x,
- dpi.y
- );
-
- // Get a D2D surface from the DXGI back buffer to use as the D2D render target.
- ThrowIfFailed(
- graph_manager_->GetD2D1DeviceContext()->CreateBitmapFromDxgiSurface(
- dxgiBackBuffer.Get(),
- &bitmap_properties,
- &target_bitmap_
- )
- );
- }
-
- GraphManager* GraphManager::GetInstance()
- {
- return Application::GetInstance()->ResolveSingleton<GraphManager>([](auto)
- {
- return new GraphManager{};
- });
- }
-
- GraphManager::GraphManager()
- {
- UINT creation_flags = D3D11_CREATE_DEVICE_BGRA_SUPPORT;
-
-#ifdef CRU_DEBUG
- creation_flags |= D3D11_CREATE_DEVICE_DEBUG;
-#endif
-
- const D3D_FEATURE_LEVEL feature_levels[] =
- {
- D3D_FEATURE_LEVEL_11_1,
- D3D_FEATURE_LEVEL_11_0,
- D3D_FEATURE_LEVEL_10_1,
- D3D_FEATURE_LEVEL_10_0,
- D3D_FEATURE_LEVEL_9_3,
- D3D_FEATURE_LEVEL_9_2,
- D3D_FEATURE_LEVEL_9_1
- };
-
-
- ThrowIfFailed(D3D11CreateDevice(
- nullptr,
- D3D_DRIVER_TYPE_HARDWARE,
- nullptr,
- creation_flags,
- feature_levels,
- ARRAYSIZE(feature_levels),
- D3D11_SDK_VERSION,
- &d3d11_device_,
- nullptr,
- &d3d11_device_context_
- ));
-
- Microsoft::WRL::ComPtr<IDXGIDevice> dxgi_device;
-
- ThrowIfFailed(d3d11_device_.As(&dxgi_device));
-
- ThrowIfFailed(D2D1CreateFactory(
- D2D1_FACTORY_TYPE_SINGLE_THREADED,
- __uuidof(ID2D1Factory1),
- &d2d1_factory_
- ));
-
- ThrowIfFailed(d2d1_factory_->CreateDevice(dxgi_device.Get(), &d2d1_device_));
-
- ThrowIfFailed(d2d1_device_->CreateDeviceContext(
- D2D1_DEVICE_CONTEXT_OPTIONS_NONE,
- &d2d1_device_context_
- ));
-
- // Identify the physical adapter (GPU or card) this device is runs on.
- ComPtr<IDXGIAdapter> dxgi_adapter;
- ThrowIfFailed(
- dxgi_device->GetAdapter(&dxgi_adapter)
- );
-
- // Get the factory object that created the DXGI device.
- ThrowIfFailed(
- dxgi_adapter->GetParent(IID_PPV_ARGS(&dxgi_factory_))
- );
-
-
- ThrowIfFailed(DWriteCreateFactory(
- DWRITE_FACTORY_TYPE_SHARED,
- __uuidof(IDWriteFactory),
- reinterpret_cast<IUnknown**>(dwrite_factory_.GetAddressOf())
- ));
-
- dwrite_factory_->GetSystemFontCollection(&dwrite_system_font_collection_);
- }
-
- GraphManager::~GraphManager()
- {
-
- }
-
- std::shared_ptr<WindowRenderTarget> GraphManager::CreateWindowRenderTarget(HWND hwnd)
- {
- return std::make_shared<WindowRenderTarget>(this, hwnd);
- }
-
- Dpi GraphManager::GetDpi() const
- {
- Dpi dpi;
- d2d1_factory_->GetDesktopDpi(&dpi.x, &dpi.y);
- return dpi;
- }
-
- void GraphManager::ReloadSystemMetrics()
- {
- ThrowIfFailed(
- d2d1_factory_->ReloadSystemMetrics()
- );
- }
-
- Microsoft::WRL::ComPtr<ID2D1SolidColorBrush> CreateSolidColorBrush(const D2D1_COLOR_F& color)
- {
- Microsoft::WRL::ComPtr<ID2D1SolidColorBrush> brush;
- ThrowIfFailed(GraphManager::GetInstance()->GetD2D1DeviceContext()->CreateSolidColorBrush(color, &brush));
- return brush;
- }
-}
diff --git a/src/graph/graph.hpp b/src/graph/graph.hpp
deleted file mode 100644
index 440b0594..00000000
--- a/src/graph/graph.hpp
+++ /dev/null
@@ -1,180 +0,0 @@
-#pragma once
-
-// ReSharper disable once CppUnusedIncludeDirective
-#include "pre.hpp"
-
-#include "system_headers.hpp"
-#include <memory>
-#include <functional>
-
-#include "base.hpp"
-
-
-namespace cru::graph
-{
- class GraphManager;
-
- //Represents a window render target.
- class WindowRenderTarget : public Object
- {
- public:
- WindowRenderTarget(GraphManager* graph_manager, HWND hwnd);
- WindowRenderTarget(const WindowRenderTarget& other) = delete;
- WindowRenderTarget(WindowRenderTarget&& other) = delete;
- WindowRenderTarget& operator=(const WindowRenderTarget& other) = delete;
- WindowRenderTarget& operator=(WindowRenderTarget&& other) = delete;
- ~WindowRenderTarget() override;
-
- public:
- //Get the graph manager that created the render target.
- GraphManager* GetGraphManager() const
- {
- return graph_manager_;
- }
-
- //Get the d2d device context.
- inline Microsoft::WRL::ComPtr<ID2D1DeviceContext> GetD2DDeviceContext() const;
-
- //Get the target bitmap which can be set as the ID2D1DeviceContext's target.
- Microsoft::WRL::ComPtr<ID2D1Bitmap1> GetTargetBitmap() const
- {
- return target_bitmap_;
- }
-
- //Resize the underlying buffer.
- void ResizeBuffer(int width, int height);
-
- //Set this render target as the d2d device context's target.
- void SetAsTarget();
-
- //Present the data of the underlying buffer to the window.
- void Present();
-
- private:
- void CreateTargetBitmap();
-
- private:
- GraphManager* graph_manager_;
- Microsoft::WRL::ComPtr<IDXGISwapChain1> dxgi_swap_chain_;
- Microsoft::WRL::ComPtr<ID2D1Bitmap1> target_bitmap_;
- };
-
- struct Dpi
- {
- float x;
- float y;
- };
-
- class GraphManager final : public Object
- {
- public:
- static GraphManager* GetInstance();
-
- private:
- GraphManager();
- public:
- GraphManager(const GraphManager& other) = delete;
- GraphManager(GraphManager&& other) = delete;
- GraphManager& operator=(const GraphManager& other) = delete;
- GraphManager& operator=(GraphManager&& other) = delete;
- ~GraphManager() override;
-
- public:
- Microsoft::WRL::ComPtr<ID2D1Factory1> GetD2D1Factory() const
- {
- return d2d1_factory_;
- }
-
- Microsoft::WRL::ComPtr<ID2D1DeviceContext> GetD2D1DeviceContext() const
- {
- return d2d1_device_context_;
- }
-
- Microsoft::WRL::ComPtr<ID3D11Device> GetD3D11Device() const
- {
- return d3d11_device_;
- }
-
- Microsoft::WRL::ComPtr<IDXGIFactory2> GetDxgiFactory() const
- {
- return dxgi_factory_;
- }
-
- Microsoft::WRL::ComPtr<IDWriteFactory> GetDWriteFactory() const
- {
- return dwrite_factory_;
- }
-
-
- //Create a window render target with the HWND.
- std::shared_ptr<WindowRenderTarget> CreateWindowRenderTarget(HWND hwnd);
-
- //Get the desktop dpi.
- Dpi GetDpi() const;
-
- //Reload system metrics including desktop dpi.
- void ReloadSystemMetrics();
-
- Microsoft::WRL::ComPtr<IDWriteFontCollection> GetSystemFontCollection() const
- {
- return dwrite_system_font_collection_.Get();
- }
-
- private:
- Microsoft::WRL::ComPtr<ID3D11Device> d3d11_device_;
- Microsoft::WRL::ComPtr<ID3D11DeviceContext> d3d11_device_context_;
- Microsoft::WRL::ComPtr<ID2D1Factory1> d2d1_factory_;
- Microsoft::WRL::ComPtr<ID2D1Device> d2d1_device_;
- Microsoft::WRL::ComPtr<ID2D1DeviceContext> d2d1_device_context_;
- Microsoft::WRL::ComPtr<IDXGIFactory2> dxgi_factory_;
-
- Microsoft::WRL::ComPtr<IDWriteFactory> dwrite_factory_;
- Microsoft::WRL::ComPtr<IDWriteFontCollection> dwrite_system_font_collection_;
- };
-
- inline int DipToPixelInternal(const float dip, const float dpi)
- {
- return static_cast<int>(dip * dpi / 96.0f);
- }
-
- inline int DipToPixelX(const float dip_x)
- {
- return DipToPixelInternal(dip_x, GraphManager::GetInstance()->GetDpi().x);
- }
-
- inline int DipToPixelY(const float dip_y)
- {
- return DipToPixelInternal(dip_y, GraphManager::GetInstance()->GetDpi().y);
- }
-
- inline float DipToPixelInternal(const int pixel, const float dpi)
- {
- return static_cast<float>(pixel) * 96.0f / dpi;
- }
-
- inline float PixelToDipX(const int pixel_x)
- {
- return DipToPixelInternal(pixel_x, GraphManager::GetInstance()->GetDpi().x);
- }
-
- inline float PixelToDipY(const int pixel_y)
- {
- return DipToPixelInternal(pixel_y, GraphManager::GetInstance()->GetDpi().y);
- }
-
- Microsoft::WRL::ComPtr<ID2D1DeviceContext> WindowRenderTarget::GetD2DDeviceContext() const
- {
- return graph_manager_->GetD2D1DeviceContext();
- }
-
- inline void WithTransform(ID2D1DeviceContext* device_context, const D2D1_MATRIX_3X2_F matrix, const std::function<void(ID2D1DeviceContext*)>& action)
- {
- D2D1_MATRIX_3X2_F old_transform;
- device_context->GetTransform(&old_transform);
- device_context->SetTransform(old_transform * matrix);
- action(device_context);
- device_context->SetTransform(old_transform);
- }
-
- Microsoft::WRL::ComPtr<ID2D1SolidColorBrush> CreateSolidColorBrush(const D2D1_COLOR_F& color);
-}
diff --git a/src/graph/graph_manager.cpp b/src/graph/graph_manager.cpp
new file mode 100644
index 00000000..ecc60915
--- /dev/null
+++ b/src/graph/graph_manager.cpp
@@ -0,0 +1,79 @@
+#include "graph_manager.hpp"
+
+#include <d2d1_2.h>
+#include <d3d11.h>
+#include <dwrite.h>
+#include <dxgi1_2.h>
+#include <wrl/client.h>
+
+#include "application.hpp"
+#include "exception.hpp"
+#include "util/com_util.hpp"
+#include "window_render_target.hpp"
+
+namespace cru::graph {
+
+GraphManager* GraphManager::GetInstance() {
+ return Application::GetInstance()->ResolveSingleton<GraphManager>(
+ [](auto) { return new GraphManager{}; });
+}
+
+GraphManager::GraphManager() {
+ UINT creation_flags = D3D11_CREATE_DEVICE_BGRA_SUPPORT;
+
+#ifdef CRU_DEBUG
+ creation_flags |= D3D11_CREATE_DEVICE_DEBUG;
+#endif
+
+ const D3D_FEATURE_LEVEL feature_levels[] = {
+ D3D_FEATURE_LEVEL_11_1, D3D_FEATURE_LEVEL_11_0, D3D_FEATURE_LEVEL_10_1,
+ D3D_FEATURE_LEVEL_10_0, D3D_FEATURE_LEVEL_9_3, D3D_FEATURE_LEVEL_9_2,
+ D3D_FEATURE_LEVEL_9_1};
+
+ Microsoft::WRL::ComPtr<ID3D11DeviceContext> d3d11_device_context;
+ ID3D11Device* d3d11_device;
+
+ ThrowIfFailed(D3D11CreateDevice(
+ nullptr, D3D_DRIVER_TYPE_HARDWARE, nullptr, creation_flags,
+ feature_levels, ARRAYSIZE(feature_levels), D3D11_SDK_VERSION,
+ &d3d11_device, nullptr, &d3d11_device_context));
+ this->d3d11_device_ = util::CreateComSharedPtr(d3d11_device);
+
+ Microsoft::WRL::ComPtr<IDXGIDevice> dxgi_device;
+ ThrowIfFailed(d3d11_device_->QueryInterface(dxgi_device.GetAddressOf()));
+
+ ID2D1Factory1* d2d1_factory;
+ ThrowIfFailed(D2D1CreateFactory(D2D1_FACTORY_TYPE_SINGLE_THREADED,
+ IID_PPV_ARGS(&d2d1_factory)));
+ this->d2d1_factory_ = util::CreateComSharedPtr(d2d1_factory);
+
+ Microsoft::WRL::ComPtr<ID2D1Device> d2d1_device;
+
+ ThrowIfFailed(d2d1_factory_->CreateDevice(dxgi_device.Get(), &d2d1_device));
+
+ ID2D1DeviceContext* d2d1_device_context;
+ ThrowIfFailed(d2d1_device->CreateDeviceContext(
+ D2D1_DEVICE_CONTEXT_OPTIONS_NONE, &d2d1_device_context));
+ this->d2d1_device_context_ = util::CreateComSharedPtr(d2d1_device_context);
+
+ // Identify the physical adapter (GPU or card) this device is runs on.
+ Microsoft::WRL::ComPtr<IDXGIAdapter> dxgi_adapter;
+ ThrowIfFailed(dxgi_device->GetAdapter(&dxgi_adapter));
+
+ IDXGIFactory2* dxgi_factory;
+ // Get the factory object that created the DXGI device.
+ ThrowIfFailed(dxgi_adapter->GetParent(IID_PPV_ARGS(&dxgi_factory)));
+ this->dxgi_factory_ = util::CreateComSharedPtr(dxgi_factory);
+
+ IDWriteFactory* dwrite_factory;
+ ThrowIfFailed(
+ DWriteCreateFactory(DWRITE_FACTORY_TYPE_SHARED, __uuidof(IDWriteFactory),
+ reinterpret_cast<IUnknown**>(&dwrite_factory)));
+ this->dwrite_factory_ = util::CreateComSharedPtr(dwrite_factory);
+
+ IDWriteFontCollection* font_collection;
+ ThrowIfFailed(dwrite_factory_->GetSystemFontCollection(&font_collection));
+ this->dwrite_system_font_collection_ =
+ util::CreateComSharedPtr(font_collection);
+}
+} // namespace cru::graph
diff --git a/src/graph/graph_manager.hpp b/src/graph/graph_manager.hpp
new file mode 100644
index 00000000..4a1e7153
--- /dev/null
+++ b/src/graph/graph_manager.hpp
@@ -0,0 +1,60 @@
+#pragma once
+#include "pre.hpp"
+
+#include <memory>
+
+#include "base.hpp"
+
+struct ID3D11Device;
+struct ID3D11DeviceContext;
+struct ID2D1Factory1;
+struct ID2D1DeviceContext;
+struct IDXGIFactory2;
+struct IDWriteFontCollection;
+struct IDWriteFactory;
+
+struct ID2D1RenderTarget;
+
+namespace cru::graph {
+class WindowRenderTarget;
+
+class GraphManager final : public Object {
+ public:
+ static GraphManager* GetInstance();
+
+ private:
+ GraphManager();
+
+ public:
+ GraphManager(const GraphManager& other) = delete;
+ GraphManager(GraphManager&& other) = delete;
+ GraphManager& operator=(const GraphManager& other) = delete;
+ GraphManager& operator=(GraphManager&& other) = delete;
+ ~GraphManager() override = default;
+
+ public:
+ ID2D1Factory1* GetD2D1Factory() const { return d2d1_factory_.get(); }
+
+ ID2D1DeviceContext* GetD2D1DeviceContext() const {
+ return d2d1_device_context_.get();
+ }
+
+ ID3D11Device* GetD3D11Device() const { return d3d11_device_.get(); }
+
+ IDXGIFactory2* GetDxgiFactory() const { return dxgi_factory_.get(); }
+
+ IDWriteFactory* GetDWriteFactory() const { return dwrite_factory_.get(); }
+
+ IDWriteFontCollection* GetSystemFontCollection() const {
+ return dwrite_system_font_collection_.get();
+ }
+
+ private:
+ std::shared_ptr<ID3D11Device> d3d11_device_;
+ std::shared_ptr<ID2D1Factory1> d2d1_factory_;
+ std::shared_ptr<ID2D1DeviceContext> d2d1_device_context_;
+ std::shared_ptr<IDXGIFactory2> dxgi_factory_;
+ std::shared_ptr<IDWriteFactory> dwrite_factory_;
+ std::shared_ptr<IDWriteFontCollection> dwrite_system_font_collection_;
+};
+} // namespace cru::graph
diff --git a/src/graph/graph_util.hpp b/src/graph/graph_util.hpp
new file mode 100644
index 00000000..2d5be5f3
--- /dev/null
+++ b/src/graph/graph_util.hpp
@@ -0,0 +1,63 @@
+#pragma once
+#include "pre.hpp"
+
+#include <d2d1_1.h>
+#include <functional>
+
+#include "exception.hpp"
+#include "graph_manager.hpp"
+
+namespace cru::graph {
+struct Dpi {
+ float x;
+ float y;
+};
+
+inline Dpi GetDpi() {
+ Dpi dpi;
+ GraphManager::GetInstance()->GetD2D1Factory()->GetDesktopDpi(&dpi.x, &dpi.y);
+ return dpi;
+}
+
+inline int DipToPixelInternal(const float dip, const float dpi) {
+ return static_cast<int>(dip * dpi / 96.0f);
+}
+
+inline int DipToPixelX(const float dip_x) {
+ return DipToPixelInternal(dip_x, GetDpi().x);
+}
+
+inline int DipToPixelY(const float dip_y) {
+ return DipToPixelInternal(dip_y, GetDpi().y);
+}
+
+inline float DipToPixelInternal(const int pixel, const float dpi) {
+ return static_cast<float>(pixel) * 96.0f / dpi;
+}
+
+inline float PixelToDipX(const int pixel_x) {
+ return DipToPixelInternal(pixel_x, GetDpi().x);
+}
+
+inline float PixelToDipY(const int pixel_y) {
+ return DipToPixelInternal(pixel_y, GetDpi().y);
+}
+
+inline void WithTransform(
+ ID2D1RenderTarget* render_target, const D2D1_MATRIX_3X2_F matrix,
+ const std::function<void(ID2D1RenderTarget*)>& action) {
+ D2D1_MATRIX_3X2_F old_transform;
+ render_target->GetTransform(&old_transform);
+ render_target->SetTransform(old_transform * matrix);
+ action(render_target);
+ render_target->SetTransform(old_transform);
+}
+
+inline ID2D1SolidColorBrush* CreateSolidColorBrush(const D2D1_COLOR_F& color) {
+ ID2D1SolidColorBrush* brush;
+ ThrowIfFailed(GraphManager::GetInstance()
+ ->GetD2D1DeviceContext()
+ ->CreateSolidColorBrush(color, &brush));
+ return brush;
+}
+} // namespace cru::graph
diff --git a/src/graph/window_render_target.cpp b/src/graph/window_render_target.cpp
new file mode 100644
index 00000000..a36e0faf
--- /dev/null
+++ b/src/graph/window_render_target.cpp
@@ -0,0 +1,97 @@
+#include "window_render_target.hpp"
+
+#include <d2d1_1.h>
+#include <d3d11.h>
+#include <dxgi1_2.h>
+#include <wrl/client.h>
+
+#include "exception.hpp"
+#include "graph_manager.hpp"
+#include "graph_util.hpp"
+#include "util/com_util.hpp"
+
+namespace cru::graph {
+WindowRenderTarget::WindowRenderTarget(GraphManager* graph_manager, HWND hwnd) {
+ this->graph_manager_ = graph_manager;
+
+ const auto d3d11_device = graph_manager->GetD3D11Device();
+ const auto dxgi_factory = graph_manager->GetDxgiFactory();
+
+ // Allocate a descriptor.
+ DXGI_SWAP_CHAIN_DESC1 swap_chain_desc = {0};
+ swap_chain_desc.Width = 0; // use automatic sizing
+ swap_chain_desc.Height = 0;
+ swap_chain_desc.Format =
+ DXGI_FORMAT_B8G8R8A8_UNORM; // this is the most common swapchain format
+ swap_chain_desc.Stereo = false;
+ swap_chain_desc.SampleDesc.Count = 1; // don't use multi-sampling
+ swap_chain_desc.SampleDesc.Quality = 0;
+ swap_chain_desc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
+ swap_chain_desc.BufferCount = 2; // use double buffering to enable flip
+ swap_chain_desc.Scaling = DXGI_SCALING_NONE;
+ swap_chain_desc.SwapEffect =
+ DXGI_SWAP_EFFECT_FLIP_SEQUENTIAL; // all apps must use this SwapEffect
+ swap_chain_desc.Flags = 0;
+
+ IDXGISwapChain1* dxgi_swap_chain;
+ // Get the final swap chain for this window from the DXGI factory.
+ ThrowIfFailed(
+ dxgi_factory->CreateSwapChainForHwnd(d3d11_device, hwnd, &swap_chain_desc,
+ nullptr, nullptr, &dxgi_swap_chain));
+ this->dxgi_swap_chain_ = util::CreateComSharedPtr(dxgi_swap_chain);
+
+ CreateTargetBitmap();
+}
+
+void WindowRenderTarget::ResizeBuffer(const int width, const int height) {
+ const auto graph_manager = graph_manager_;
+ const auto d2d1_device_context = graph_manager->GetD2D1DeviceContext();
+
+ ID2D1Image* old_target;
+ d2d1_device_context->GetTarget(&old_target);
+ const auto target_this = old_target == this->target_bitmap_.get();
+ if (target_this) d2d1_device_context->SetTarget(nullptr);
+
+ util::SafeRelease(old_target);
+ target_bitmap_.reset();
+
+ ThrowIfFailed(dxgi_swap_chain_->ResizeBuffers(0, width, height,
+ DXGI_FORMAT_UNKNOWN, 0));
+
+ CreateTargetBitmap();
+
+ if (target_this) d2d1_device_context->SetTarget(target_bitmap_.get());
+}
+
+void WindowRenderTarget::SetAsTarget() {
+ graph_manager_->GetD2D1DeviceContext()->SetTarget(target_bitmap_.get());
+}
+
+void WindowRenderTarget::Present() {
+ ThrowIfFailed(dxgi_swap_chain_->Present(1, 0));
+}
+
+void WindowRenderTarget::CreateTargetBitmap() {
+ assert(target_bitmap_ == nullptr); // target bitmap must not exist.
+
+ // Direct2D needs the dxgi version of the backbuffer surface pointer.
+ Microsoft::WRL::ComPtr<IDXGISurface> dxgi_back_buffer;
+ ThrowIfFailed(
+ dxgi_swap_chain_->GetBuffer(0, IID_PPV_ARGS(&dxgi_back_buffer)));
+
+ const auto dpi = GetDpi();
+
+ auto bitmap_properties = D2D1::BitmapProperties1(
+ D2D1_BITMAP_OPTIONS_TARGET | D2D1_BITMAP_OPTIONS_CANNOT_DRAW,
+ D2D1::PixelFormat(DXGI_FORMAT_B8G8R8A8_UNORM, D2D1_ALPHA_MODE_IGNORE),
+ dpi.x, dpi.y);
+
+ ID2D1Bitmap1* bitmap;
+ // Get a D2D surface from the DXGI back buffer to use as the D2D render
+ // target.
+ ThrowIfFailed(
+ graph_manager_->GetD2D1DeviceContext()->CreateBitmapFromDxgiSurface(
+ dxgi_back_buffer.Get(), &bitmap_properties, &bitmap));
+ this->target_bitmap_ = util::CreateComSharedPtr(bitmap);
+}
+} // namespace cru::graph
diff --git a/src/graph/window_render_target.hpp b/src/graph/window_render_target.hpp
new file mode 100644
index 00000000..9b93df19
--- /dev/null
+++ b/src/graph/window_render_target.hpp
@@ -0,0 +1,49 @@
+#pragma once
+#include "pre.hpp"
+
+#include <Windows.h>
+#include <memory>
+
+#include "base.hpp"
+
+struct IDXGISwapChain1;
+struct ID2D1Bitmap1;
+
+namespace cru::graph {
+class GraphManager;
+
+// Represents a window render target.
+class WindowRenderTarget : public Object {
+ public:
+ WindowRenderTarget(GraphManager* graph_manager, HWND hwnd);
+ WindowRenderTarget(const WindowRenderTarget& other) = delete;
+ WindowRenderTarget(WindowRenderTarget&& other) = delete;
+ WindowRenderTarget& operator=(const WindowRenderTarget& other) = delete;
+ WindowRenderTarget& operator=(WindowRenderTarget&& other) = delete;
+ ~WindowRenderTarget() override = default;
+
+ public:
+ // Get the graph manager that created the render target.
+ GraphManager* GetGraphManager() const { return graph_manager_; }
+
+ // Get the target bitmap which can be set as the ID2D1DeviceContext's target.
+ ID2D1Bitmap1* GetTargetBitmap() const { return target_bitmap_.get(); }
+
+ // Resize the underlying buffer.
+ void ResizeBuffer(int width, int height);
+
+ // Set this render target as the d2d device context's target.
+ void SetAsTarget();
+
+ // Present the data of the underlying buffer to the window.
+ void Present();
+
+ private:
+ void CreateTargetBitmap();
+
+ private:
+ GraphManager* graph_manager_;
+ std::shared_ptr<IDXGISwapChain1> dxgi_swap_chain_;
+ std::shared_ptr<ID2D1Bitmap1> target_bitmap_;
+};
+} // namespace cru::graph
diff --git a/src/main.cpp b/src/main.cpp
index 1773ce84..c35213f7 100644
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -1,242 +1,46 @@
#include "pre.hpp"
#include "application.hpp"
-#include "ui/window.hpp"
-#include "ui/controls/linear_layout.hpp"
-#include "ui/controls/text_block.hpp"
-#include "ui/controls/toggle_button.hpp"
#include "ui/controls/button.hpp"
-#include "ui/controls/text_box.hpp"
-#include "ui/controls/list_item.hpp"
-#include "ui/controls/popup_menu.hpp"
-#include "ui/controls/frame_layout.hpp"
-#include "ui/controls/scroll_control.hpp"
-#include "graph/graph.hpp"
+#include "ui/controls/flex_layout.hpp"
+#include "ui/controls/text_block.hpp"
+#include "ui/window.hpp"
+using cru::Application;
using cru::String;
using cru::StringView;
-using cru::Application;
using cru::ui::Rect;
-using cru::ui::Window;
-using cru::ui::Alignment;
-using cru::ui::LayoutSideParams;
using cru::ui::Thickness;
-using cru::ui::ControlList;
-using cru::ui::CreateWithLayout;
-using cru::ui::controls::LinearLayout;
-using cru::ui::controls::TextBlock;
-using cru::ui::controls::ToggleButton;
+using cru::ui::Window;
using cru::ui::controls::Button;
-using cru::ui::controls::TextBox;
-using cru::ui::controls::ListItem;
-using cru::ui::controls::FrameLayout;
-using cru::ui::controls::ScrollControl;
-
-int APIENTRY wWinMain(
- HINSTANCE hInstance,
- HINSTANCE hPrevInstance,
- LPWSTR lpCmdLine,
- int nCmdShow) {
+using cru::ui::controls::FlexLayout;
+using cru::ui::controls::TextBlock;
+int APIENTRY wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
+ LPWSTR lpCmdLine, int nCmdShow) {
#ifdef CRU_DEBUG
- _CrtSetDbgFlag ( _CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF );
+ _CrtSetDbgFlag(_CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF);
#endif
- Application application(hInstance);
-
- const auto window = Window::CreateOverlapped();
- /*
- window.native_message_event.AddHandler([](cru::ui::events::WindowNativeMessageEventArgs& args)
- {
- if (args.GetWindowMessage().msg == WM_PAINT)
- {
- OutputDebugStringW(L"Paint!\n");
- //args.SetResult(0);
- }
- });
- */
- /*
- // test1
- cru::ui::controls::TextBlock text_block;
- text_block.SetText(L"Hello world!");
- text_block.SetSize(cru::ui::Size(200, 30));
- window.AddChild(&text_block);
-
- std::array<D2D_COLOR_F, 4> colors =
- {
- D2D1::ColorF(D2D1::ColorF::Blue),
- D2D1::ColorF(D2D1::ColorF::Yellow),
- D2D1::ColorF(D2D1::ColorF::Green),
- D2D1::ColorF(D2D1::ColorF::Red)
- };
-
- std::random_device rd; // only used once to initialise (seed) engine
- std::mt19937 rng(rd()); // random-number engine used (Mersenne-Twister in this case)
- std::uniform_int_distribution<decltype(colors.size())> uni(0, colors.size() - 1); // guaranteed unbiased
-
-
- window.draw_event.AddHandler([&](cru::ui::events::DrawEventArgs& args) {
- auto device_context = args.GetDeviceContext();
-
- ID2D1SolidColorBrush* brush;
- device_context->CreateSolidColorBrush(colors[uni(rng)], &brush);
-
- device_context->FillRectangle(D2D1::RectF(100.0f, 100.0f, 300.0f, 200.0f), brush);
-
- brush->Release();
- });
-
- cru::SetTimeout(2.0, [&window]() {
- window.InvalidateDraw();
-
- auto task = cru::SetInterval(0.5, [&window]() {
- window.InvalidateDraw();
- });
-
- cru::SetTimeout(4, [task]() {
- task->Cancel();
- task->Cancel(); // test for idempotency.
- });
- });
- */
-
-
- //test 2
- const auto layout = CreateWithLayout<LinearLayout>(LayoutSideParams::Exactly(500), LayoutSideParams::Content());
-
- layout->mouse_click_event.bubble.AddHandler([layout](cru::ui::events::MouseButtonEventArgs& args)
- {
- if (args.GetSender() == args.GetOriginalSender())
- layout->AddChild(TextBlock::Create(L"Layout is clicked!"));
- });
-
- {
- const auto inner_layout = CreateWithLayout<LinearLayout>(LayoutSideParams::Content(Alignment::End), LayoutSideParams::Content(), LinearLayout::Orientation::Horizontal);
-
- inner_layout->AddChild(TextBlock::Create(L"Toggle debug border"));
-
- const auto l = FrameLayout::Create();
- l->GetLayoutParams()->padding.SetLeftRight(20.0f);
- const auto toggle_button = ToggleButton::Create();
-#ifdef CRU_DEBUG_LAYOUT
- toggle_button->toggle_event.AddHandler([&window](cru::ui::events::ToggleEventArgs& args)
- {
- window->SetDebugLayout(args.GetNewState());
- });
-#endif
- l->AddChild(toggle_button);
- inner_layout->AddChild(l);
- layout->AddChild(inner_layout);
- }
-
- {
- const auto button = Button::Create();
- button->GetLayoutParams()->padding = Thickness(20, 5);
- button->SetChild(TextBlock::Create(L"Show popup window parenting this."));
- button->mouse_click_event.bubble.AddHandler([window, button](auto)
- {
- std::vector<cru::ui::controls::MenuItemInfo> items;
- items.emplace_back(L"Hello world!", []{});
- items.emplace_back(L"Item 2", []{});
- items.emplace_back(L"Close parent window.", [window]{ window->Close(); });
-
- cru::ui::controls::CreatePopupMenu(window->PointToScreen(button->GetPositionAbsolute()), items, window)->Show();
- });
- layout->AddChild(button);
- }
-
- {
- const auto button = Button::Create();
- button->GetLayoutParams()->padding = Thickness(20, 5);
- button->SetChild(TextBlock::Create(L"Show popup window parenting null."));
- button->SetBackgroundBrush(cru::graph::CreateSolidColorBrush(D2D1::ColorF(D2D1::ColorF::Gold)));
- button->mouse_click_event.bubble.AddHandler([](auto)
- {
- auto popup = Window::CreatePopup(nullptr);
- popup->SetWindowRect(Rect(100, 100, 300, 300));
- popup->Show();
- });
- layout->AddChild(button);
- }
-
- {
- const auto button = Button::Create();
- button->GetLayoutParams()->padding = Thickness(20, 5);
- button->SetChild(TextBlock::Create(L"Show popup window with caption."));
- button->mouse_click_event.bubble.AddHandler([](auto)
- {
- auto popup = Window::CreatePopup(nullptr, true);
- popup->SetWindowRect(Rect(100, 100, 300, 300));
- popup->Show();
- });
- layout->AddChild(button);
- }
-
- {
- const auto text_block = CreateWithLayout<TextBlock>(LayoutSideParams::Exactly(200), LayoutSideParams::Exactly(80), L"Hello World!!!");
-
- text_block->mouse_click_event.bubble.AddHandler([layout](cru::ui::events::MouseButtonEventArgs& args)
- {
- layout->AddChild(TextBlock::Create(L"Hello world is clicked!"));
- });
-
- layout->AddChild(text_block);
- }
-
- {
- const auto text_box = TextBox::Create();
- text_box->GetLayoutParams()->width.min = 50.0f;
- text_box->GetLayoutParams()->width.max = 100.0f;
- text_box->char_event.tunnel.AddHandler([](cru::ui::events::CharEventArgs& args)
- {
- if (args.GetChar() == L'1')
- args.SetHandled();
- });
- layout->AddChild(text_box);
- }
-
- {
- const auto scroll_view = CreateWithLayout<ScrollControl>(LayoutSideParams::Stretch(), LayoutSideParams::Stretch());
-
- scroll_view->SetVerticalScrollBarVisibility(ScrollControl::ScrollBarVisibility::Always);
-
- const auto text_block = TextBlock::Create(
- L"Love myself I do. Not everything, but I love the good as well as the bad. I love my crazy lifestyle, and I love my hard discipline. I love my freedom of speech and the way my eyes get dark when I'm tired. I love that I have learned to trust people with my heart, even if it will get broken. I am proud of everything that I am and will become.");
- text_block->SetSelectable(true);
-
- scroll_view->SetChild(text_block);
- layout->AddChild(scroll_view);
- }
-
- layout->AddChild(CreateWithLayout<TextBlock>(LayoutSideParams::Content(Alignment::Start), LayoutSideParams::Content(), L"This is a little short sentence!!!"));
- layout->AddChild(CreateWithLayout<TextBlock>(LayoutSideParams::Content(Alignment::End), LayoutSideParams::Stretch(), L"By crupest!!!"));
-
+ Application application(hInstance);
- window->SetChild(layout);
+ const auto window = Window::CreateOverlapped();
- /*
- window.AddChild(
- CreateWithLayout<Border>(LayoutSideParams::Exactly(200), LayoutSideParams::Content(),
- std::initializer_list<cru::ui::Control*>{
- CreateWithLayout<TextBox>(LayoutSideParams::Stretch(), LayoutSideParams::Content())
- }
- ));
- */
+ const auto flex_layout = FlexLayout::Create();
- /* test 3
- const auto linear_layout = CreateWithLayout<LinearLayout>(Thickness(50, 50), Thickness(50, 50), LinearLayout::Orientation::Vertical, ControlList{
- Button::Create({
- TextBlock::Create(L"Button")
- }),
- CreateWithLayout<TextBox>(Thickness(30), Thickness(20))
- });
+ window->SetChild(flex_layout);
- linear_layout->SetBordered(true);
+ const auto button = Button::Create();
+ const auto text_block1 = TextBlock::Create();
+ text_block1->SetText(L"Hello World!");
+ button->SetChild(text_block1);
+ flex_layout->AddChild(button, 0);
- window.AddChild(linear_layout);
- */
+ const auto text_block2 = TextBlock::Create();
+ text_block2->SetText(L"Hello World!");
+ flex_layout->AddChild(text_block2, 1);
- window->Show();
+ window->Show();
- return application.Run();
+ return application.Run();
}
diff --git a/src/math_util.hpp b/src/math_util.hpp
deleted file mode 100644
index b9830d6b..00000000
--- a/src/math_util.hpp
+++ /dev/null
@@ -1,69 +0,0 @@
-#pragma once
-
-// ReSharper disable once CppUnusedIncludeDirective
-#include "pre.hpp"
-
-// ReSharper disable once CppUnusedIncludeDirective
-#include <type_traits>
-#include <optional>
-
-namespace cru
-{
- template <typename T, typename = std::enable_if_t<std::is_arithmetic_v<T>>>
- float Coerce(const T n, const std::optional<T> min, const std::optional<T> max)
- {
- if (min.has_value() && n < min.value())
- return min.value();
- if (max.has_value() && n > max.value())
- return max.value();
- return n;
- }
-
- template <typename T, typename = std::enable_if_t<std::is_arithmetic_v<T>>>
- float Coerce(const T n, const T min, const T max)
- {
- if (n < min)
- return min;
- if (n > max)
- return max;
- return n;
- }
-
- template <typename T, typename = std::enable_if_t<std::is_arithmetic_v<T>>>
- float Coerce(const T n, const std::nullopt_t, const std::optional<T> max)
- {
- if (max.has_value() && n > max.value())
- return max.value();
- return n;
- }
-
- template <typename T, typename = std::enable_if_t<std::is_arithmetic_v<T>>>
- float Coerce(const T n, const std::optional<T> min, const std::nullopt_t)
- {
- if (min.has_value() && n < min.value())
- return min.value();
- return n;
- }
-
- template <typename T, typename = std::enable_if_t<std::is_arithmetic_v<T>>>
- float Coerce(const T n, const std::nullopt_t, const T max)
- {
- if (n > max)
- return max;
- return n;
- }
-
- template <typename T, typename = std::enable_if_t<std::is_arithmetic_v<T>>>
- float Coerce(const T n, const T min, const std::nullopt_t)
- {
- if (n < min)
- return min;
- return n;
- }
-
- template <typename T, typename = std::enable_if_t<std::is_arithmetic_v<T>>>
- T AtLeast0(const T value)
- {
- return value < static_cast<T>(0) ? static_cast<T>(0) : value;
- }
-}
diff --git a/src/pre.hpp b/src/pre.hpp
index 03c51a94..eefc828d 100644
--- a/src/pre.hpp
+++ b/src/pre.hpp
@@ -4,15 +4,11 @@
#define CRU_DEBUG
#endif
-#ifdef CRU_DEBUG
-#define CRU_DEBUG_LAYOUT
-#endif
-
+#define NOMINMAX
+#define WIN32_LEAN_AND_MEAN
#ifdef CRU_DEBUG
-// ReSharper disable once IdentifierTypo
-// ReSharper disable once CppInconsistentNaming
#define _CRTDBG_MAP_ALLOC
-#include <cstdlib>
#include <crtdbg.h>
+#include <cstdlib>
#endif
diff --git a/src/system_headers.hpp b/src/system_headers.hpp
deleted file mode 100644
index eabc7c25..00000000
--- a/src/system_headers.hpp
+++ /dev/null
@@ -1,25 +0,0 @@
-#pragma once
-
-// ReSharper disable once CppUnusedIncludeDirective
-#include "pre.hpp"
-
-//include system headers
-
-#define NOMINMAX
-#define WIN32_LEAN_AND_MEAN
-#include <Windows.h>
-#include <windowsx.h>
-
-#pragma comment(lib, "D3D11.lib")
-#include <d3d11.h>
-
-#pragma comment(lib, "D2d1.lib")
-#include <d2d1_1.h>
-
-#pragma comment(lib, "DWrite.lib")
-#include <dwrite.h>
-
-#include <dxgi1_2.h>
-#include <wrl/client.h>
-
-#include <VersionHelpers.h>
diff --git a/src/timer.cpp b/src/timer.cpp
index c839a48d..40e32640 100644
--- a/src/timer.cpp
+++ b/src/timer.cpp
@@ -2,62 +2,51 @@
#include "application.hpp"
-namespace cru
-{
- TimerManager* TimerManager::GetInstance()
- {
- return Application::GetInstance()->ResolveSingleton<TimerManager>([](auto)
- {
- return new TimerManager{};
- });
- }
-
- UINT_PTR TimerManager::CreateTimer(const UINT milliseconds, const bool loop, const TimerAction& action)
- {
- const auto id = current_count_++;
- ::SetTimer(Application::GetInstance()->GetGodWindow()->GetHandle(), id, milliseconds, nullptr);
- map_.emplace(id, std::make_pair(loop, action));
- return id;
- }
-
- void TimerManager::KillTimer(const UINT_PTR id)
- {
- const auto find_result = map_.find(id);
- if (find_result != map_.cend())
- {
- ::KillTimer(Application::GetInstance()->GetGodWindow()->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;
- }
-
- TimerTask::TimerTask(const UINT_PTR id)
- : id_(id)
- {
-
- }
-
- void TimerTask::Cancel() const
- {
- TimerManager::GetInstance()->KillTimer(id_);
- }
-
- TimerTask SetTimeout(std::chrono::milliseconds milliseconds, const TimerAction& action)
- {
- const auto id = TimerManager::GetInstance()->CreateTimer(static_cast<UINT>(milliseconds.count()), false, action);
- return TimerTask(id);
- }
-
- TimerTask SetInterval(std::chrono::milliseconds milliseconds, const TimerAction& action)
- {
- const auto id = TimerManager::GetInstance()->CreateTimer(static_cast<UINT>(milliseconds.count()), true, action);
- return TimerTask(id);
- }
+namespace cru {
+TimerManager* TimerManager::GetInstance() {
+ return Application::GetInstance()->ResolveSingleton<TimerManager>(
+ [](auto) { return new TimerManager{}; });
}
+
+UINT_PTR TimerManager::CreateTimer(const UINT milliseconds, const bool loop,
+ const TimerAction& action) {
+ const auto id = current_count_++;
+ ::SetTimer(Application::GetInstance()->GetGodWindow()->GetHandle(), id,
+ milliseconds, nullptr);
+ map_.emplace(id, std::make_pair(loop, action));
+ return id;
+}
+
+void TimerManager::KillTimer(const UINT_PTR id) {
+ const auto find_result = map_.find(id);
+ if (find_result != map_.cend()) {
+ ::KillTimer(Application::GetInstance()->GetGodWindow()->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;
+}
+
+TimerTask::TimerTask(const UINT_PTR id) : id_(id) {}
+
+void TimerTask::Cancel() const { TimerManager::GetInstance()->KillTimer(id_); }
+
+TimerTask SetTimeout(std::chrono::milliseconds milliseconds,
+ const TimerAction& action) {
+ const auto id = TimerManager::GetInstance()->CreateTimer(
+ static_cast<UINT>(milliseconds.count()), false, action);
+ return TimerTask(id);
+}
+
+TimerTask SetInterval(std::chrono::milliseconds milliseconds,
+ const TimerAction& action) {
+ const auto id = TimerManager::GetInstance()->CreateTimer(
+ static_cast<UINT>(milliseconds.count()), true, action);
+ return TimerTask(id);
+}
+} // namespace cru
diff --git a/src/timer.hpp b/src/timer.hpp
index 5055a3d8..7199adc2 100644
--- a/src/timer.hpp
+++ b/src/timer.hpp
@@ -1,64 +1,64 @@
#pragma once
-
-// ReSharper disable once CppUnusedIncludeDirective
#include "pre.hpp"
-#include "system_headers.hpp"
-#include <map>
+#include <Windows.h>
#include <chrono>
#include <functional>
+#include <map>
#include <optional>
#include "base.hpp"
-namespace cru
-{
- using TimerAction = std::function<void()>;
+namespace cru {
+using TimerAction = std::function<void()>;
+
+class TimerManager : public Object {
+ public:
+ static TimerManager* GetInstance();
- class TimerManager : public Object
- {
- public:
- static TimerManager* GetInstance();
+ private:
+ TimerManager() = default;
- private:
- TimerManager() = default;
- public:
- TimerManager(const TimerManager& other) = delete;
- TimerManager(TimerManager&& other) = delete;
- TimerManager& operator=(const TimerManager& other) = delete;
- TimerManager& operator=(TimerManager&& other) = delete;
- ~TimerManager() override = default;
+ public:
+ TimerManager(const TimerManager& other) = delete;
+ TimerManager(TimerManager&& other) = delete;
+ TimerManager& operator=(const TimerManager& other) = delete;
+ TimerManager& operator=(TimerManager&& other) = delete;
+ ~TimerManager() override = default;
- UINT_PTR CreateTimer(UINT milliseconds, bool loop, const TimerAction& action);
- void KillTimer(UINT_PTR id);
- std::optional<std::pair<bool, TimerAction>> GetAction(UINT_PTR id);
+ UINT_PTR CreateTimer(UINT milliseconds, bool loop, const TimerAction& action);
+ void KillTimer(UINT_PTR id);
+ std::optional<std::pair<bool, TimerAction>> GetAction(UINT_PTR id);
- private:
- std::map<UINT_PTR, std::pair<bool, TimerAction>> map_{};
- UINT_PTR current_count_ = 0;
- };
+ private:
+ std::map<UINT_PTR, std::pair<bool, TimerAction>> map_{};
+ UINT_PTR current_count_ = 0;
+};
- class TimerTask
- {
- friend TimerTask SetTimeout(std::chrono::milliseconds milliseconds, const TimerAction& action);
- friend TimerTask SetInterval(std::chrono::milliseconds milliseconds, const TimerAction& action);
+class TimerTask {
+ friend TimerTask SetTimeout(std::chrono::milliseconds milliseconds,
+ const TimerAction& action);
+ friend TimerTask SetInterval(std::chrono::milliseconds milliseconds,
+ const TimerAction& action);
- private:
- explicit TimerTask(UINT_PTR id);
+ private:
+ explicit TimerTask(UINT_PTR id);
- public:
- TimerTask(const TimerTask& other) = default;
- TimerTask(TimerTask&& other) = default;
- TimerTask& operator=(const TimerTask& other) = default;
- TimerTask& operator=(TimerTask&& other) = default;
- ~TimerTask() = default;
+ public:
+ TimerTask(const TimerTask& other) = default;
+ TimerTask(TimerTask&& other) = default;
+ TimerTask& operator=(const TimerTask& other) = default;
+ TimerTask& operator=(TimerTask&& other) = default;
+ ~TimerTask() = default;
- void Cancel() const;
+ void Cancel() const;
- private:
- UINT_PTR id_;
- };
+ private:
+ UINT_PTR id_;
+};
- TimerTask SetTimeout(std::chrono::milliseconds milliseconds, const TimerAction& action);
- TimerTask SetInterval(std::chrono::milliseconds milliseconds, const TimerAction& action);
-}
+TimerTask SetTimeout(std::chrono::milliseconds milliseconds,
+ const TimerAction& action);
+TimerTask SetInterval(std::chrono::milliseconds milliseconds,
+ const TimerAction& action);
+} // namespace cru
diff --git a/src/ui/animations/animation.cpp b/src/ui/animations/animation.cpp
deleted file mode 100644
index b1f92a3e..00000000
--- a/src/ui/animations/animation.cpp
+++ /dev/null
@@ -1,172 +0,0 @@
-#include "animation.hpp"
-
-#include <utility>
-
-#include "application.hpp"
-
-namespace cru::ui::animations
-{
- namespace details
- {
- class AnimationDelegateImpl;
- constexpr double frame_rate = 60;
- constexpr AnimationTimeUnit frame_step_time = AnimationTimeUnit(1) / frame_rate;
-
-
- class AnimationDelegateImpl : public virtual IAnimationDelegate
- {
- public:
- explicit AnimationDelegateImpl(String tag)
- : tag_(std::move(tag))
- {
-
- }
- AnimationDelegateImpl(const AnimationDelegateImpl& other) = delete;
- AnimationDelegateImpl(AnimationDelegateImpl&& other) = delete;
- AnimationDelegateImpl& operator=(const AnimationDelegateImpl& other) = delete;
- AnimationDelegateImpl& operator=(AnimationDelegateImpl&& other) = delete;
- ~AnimationDelegateImpl() override = default;
-
- void Cancel() override
- {
- AnimationManager::GetInstance()->RemoveAnimation(tag_);
- }
-
- private:
- String tag_;
- };
-
-
- class Animation : public Object
- {
- public:
- Animation(AnimationInfo info, AnimationDelegatePtr delegate)
- : info_(std::move(info)), delegate_(std::move(delegate))
- {
-
- }
-
- Animation(const Animation& other) = delete;
- Animation(Animation&& other) = delete;
- Animation& operator=(const Animation& other) = delete;
- Animation& operator=(Animation&& other) = delete;
- ~Animation() override;
-
-
- // If finish or invalid, return false.
- bool Step(AnimationTimeUnit time);
-
- String GetTag() const
- {
- return info_.tag;
- }
-
- private:
- const AnimationInfo info_;
- const AnimationDelegatePtr delegate_;
-
- AnimationTimeUnit current_time_ = AnimationTimeUnit::zero();
- };
-
- AnimationManager* AnimationManager::GetInstance()
- {
- return Application::GetInstance()->ResolveSingleton<AnimationManager>([](auto)
- {
- return new AnimationManager{};
- });
- }
-
- AnimationManager::AnimationManager()
- {
-
- }
-
- AnimationManager::~AnimationManager()
- {
- KillTimer();
- }
-
- AnimationDelegatePtr AnimationManager::CreateAnimation(AnimationInfo info)
- {
- if (animations_.empty())
- SetTimer();
-
- const auto tag = info.tag;
- auto delegate = std::make_shared<AnimationDelegateImpl>(tag);
- animations_[tag] = std::make_unique<Animation>(std::move(info), delegate);
-
- return delegate;
- }
-
- void AnimationManager::RemoveAnimation(const String& tag)
- {
- const auto find_result = animations_.find(tag);
- if (find_result != animations_.cend())
- animations_.erase(find_result);
-
- if (animations_.empty())
- KillTimer();
- }
-
- void AnimationManager::SetTimer()
- {
- if (!timer_.has_value())
- timer_ = SetInterval(std::chrono::duration_cast<std::chrono::milliseconds>(frame_step_time), [this]()
- {
- auto i = animations_.cbegin();
- while (i != animations_.cend())
- {
- auto current_i = i++;
- if (current_i->second->Step(frame_step_time))
- animations_.erase(current_i);
- }
-
- if (animations_.empty())
- KillTimer();
- });
- }
-
- void AnimationManager::KillTimer()
- {
- if (timer_.has_value())
- {
- timer_.value().Cancel();
- timer_ = std::nullopt;
- }
- }
-
- Animation::~Animation()
- {
- if (current_time_ < info_.duration)
- for (const auto& handler : info_.cancel_handlers)
- handler();
- }
-
- bool Animation::Step(const AnimationTimeUnit time)
- {
- current_time_ += time;
- if (current_time_ > info_.duration)
- {
- for (const auto& handler : info_.step_handlers)
- handler(delegate_, 1);
- for (const auto& handler : info_.finish_handlers)
- handler();
- return true;
- }
- else
- {
- for (const auto& handler : info_.step_handlers)
- handler(delegate_, current_time_ / info_.duration);
- return false;
- }
- }
-
- }
-
- AnimationDelegatePtr AnimationBuilder::Start()
- {
- CheckValid();
- valid_ = false;
- return details::AnimationManager::GetInstance()->CreateAnimation(std::move(info_));
- }
-}
diff --git a/src/ui/animations/animation.hpp b/src/ui/animations/animation.hpp
deleted file mode 100644
index 2226f021..00000000
--- a/src/ui/animations/animation.hpp
+++ /dev/null
@@ -1,130 +0,0 @@
-#pragma once
-
-// ReSharper disable once CppUnusedIncludeDirective
-#include "pre.hpp"
-
-#include <unordered_map>
-
-#include "base.hpp"
-#include "timer.hpp"
-
-namespace cru::ui::animations
-{
- using AnimationTimeUnit = FloatSecond;
-
- struct IAnimationDelegate : virtual Interface
- {
- virtual void Cancel() = 0;
- };
-
- using AnimationDelegatePtr = std::shared_ptr<IAnimationDelegate>;
-
- using AnimationStepHandler = std::function<void(AnimationDelegatePtr, double)>;
- using AnimationStartHandler = std::function<void(AnimationDelegatePtr)>;
- using AnimationFinishHandler = std::function<void()>;
- using AnimationCancelHandler = std::function<void()>;
-
- namespace details
- {
- class Animation;
- using AnimationPtr = std::unique_ptr<Animation>;
-
- class AnimationInfo
- {
- public:
- AnimationInfo(String tag, const AnimationTimeUnit duration)
- : tag(std::move(tag)),
- duration(duration)
- {
-
- }
- AnimationInfo(const AnimationInfo& other) = default;
- AnimationInfo(AnimationInfo&& other) = default;
- AnimationInfo& operator=(const AnimationInfo& other) = default;
- AnimationInfo& operator=(AnimationInfo&& other) = default;
- ~AnimationInfo() = default;
-
- String tag;
- AnimationTimeUnit duration;
- std::vector<AnimationStepHandler> step_handlers{};
- std::vector<AnimationStartHandler> start_handlers{};
- std::vector<AnimationFinishHandler> finish_handlers{};
- std::vector<AnimationCancelHandler> cancel_handlers{};
- };
-
- class AnimationManager : public Object
- {
- public:
- static AnimationManager* GetInstance();
- private:
- AnimationManager();
- public:
- AnimationManager(const AnimationManager& other) = delete;
- AnimationManager(AnimationManager&& other) = delete;
- AnimationManager& operator=(const AnimationManager& other) = delete;
- AnimationManager& operator=(AnimationManager&& other) = delete;
- ~AnimationManager() override;
-
- AnimationDelegatePtr CreateAnimation(AnimationInfo info);
- void RemoveAnimation(const String& tag);
-
- private:
- void SetTimer();
- void KillTimer();
-
- private:
- std::unordered_map<String, AnimationPtr> animations_;
- std::optional<TimerTask> timer_;
- };
- }
-
- class AnimationBuilder : public Object
- {
- public:
- AnimationBuilder(String tag, const AnimationTimeUnit duration)
- : info_(std::move(tag), duration)
- {
-
- }
-
- AnimationBuilder& AddStepHandler(const AnimationStepHandler& handler)
- {
- CheckValid();
- info_.step_handlers.push_back(handler);
- return *this;
- }
-
- AnimationBuilder& AddStartHandler(const AnimationStartHandler& handler)
- {
- CheckValid();
- info_.start_handlers.push_back(handler);
- return *this;
- }
-
- AnimationBuilder& AddFinishHandler(const AnimationFinishHandler& handler)
- {
- CheckValid();
- info_.finish_handlers.push_back(handler);
- return *this;
- }
-
- AnimationBuilder& AddCancelHandler(const AnimationCancelHandler& handler)
- {
- CheckValid();
- info_.cancel_handlers.push_back(handler);
- return *this;
- }
-
- AnimationDelegatePtr Start();
-
- private:
- void CheckValid() const
- {
- if (!valid_)
- throw std::runtime_error("The animation builder is invalid.");
- }
-
- bool valid_ = true;
- details::AnimationInfo info_;
- };
-}
diff --git a/src/ui/border_property.cpp b/src/ui/border_property.cpp
deleted file mode 100644
index b79bb482..00000000
--- a/src/ui/border_property.cpp
+++ /dev/null
@@ -1,23 +0,0 @@
-#include "border_property.hpp"
-
-#include "ui_manager.hpp"
-
-namespace cru::ui
-{
- BorderProperty::BorderProperty(): BorderProperty(UiManager::GetInstance()->GetPredefineResources()->border_property_brush)
- {
-
- }
-
- BorderProperty::BorderProperty(Microsoft::WRL::ComPtr<ID2D1Brush> brush): brush_(std::move(brush))
- {
-
- }
-
- BorderProperty::BorderProperty(Microsoft::WRL::ComPtr<ID2D1Brush> brush, const float width, const float radius_x,
- const float radius_y, Microsoft::WRL::ComPtr<ID2D1StrokeStyle> stroke_style) :
- brush_(std::move(brush)), stroke_width_(width), radius_x_(radius_x), radius_y_(radius_y), stroke_style_(std::move(stroke_style))
- {
-
- }
-}
diff --git a/src/ui/border_property.hpp b/src/ui/border_property.hpp
deleted file mode 100644
index 4dee0e0f..00000000
--- a/src/ui/border_property.hpp
+++ /dev/null
@@ -1,87 +0,0 @@
-#pragma once
-
-// ReSharper disable once CppUnusedIncludeDirective
-#include "pre.hpp"
-
-#include "system_headers.hpp"
-
-#include "base.hpp"
-
-
-namespace cru::ui
-{
- class BorderProperty final
- {
- public:
- BorderProperty();
- explicit BorderProperty(Microsoft::WRL::ComPtr<ID2D1Brush> brush);
- BorderProperty(Microsoft::WRL::ComPtr<ID2D1Brush> brush, float width, float radius_x, float radius_y, Microsoft::WRL::ComPtr<ID2D1StrokeStyle> stroke_style = nullptr);
- BorderProperty(const BorderProperty& other) = default;
- BorderProperty(BorderProperty&& other) = default;
- BorderProperty& operator=(const BorderProperty& other) = default;
- BorderProperty& operator=(BorderProperty&& other) = default;
- ~BorderProperty() = default;
-
-
- Microsoft::WRL::ComPtr<ID2D1Brush> GetBrush() const
- {
- return brush_;
- }
-
- float GetStrokeWidth() const
- {
- return stroke_width_;
- }
-
- Microsoft::WRL::ComPtr<ID2D1StrokeStyle> GetStrokeStyle() const
- {
- return stroke_style_;
- }
-
- float GetRadiusX() const
- {
- return radius_x_;
- }
-
- float GetRadiusY() const
- {
- return radius_y_;
- }
-
- void SetBrush(Microsoft::WRL::ComPtr<ID2D1Brush> brush)
- {
- Require(brush == nullptr, "Brush of BorderProperty mustn't be null.");
- brush_ = std::move(brush);
- }
-
- void SetStrokeWidth(const float stroke_width)
- {
- Require(stroke_width >= 0.0f, "Stroke width must be no less than 0.");
- stroke_width_ = stroke_width;
- }
-
- void SetStrokeStyle(Microsoft::WRL::ComPtr<ID2D1StrokeStyle> stroke_style)
- {
- stroke_style_ = std::move(stroke_style);
- }
-
- void SetRadiusX(const float radius_x)
- {
- Require(radius_x >= 0.0f, "Radius-x must be no less than 0.");
- radius_x_ = radius_x;
- }
-
- void SetRadiusY(const float radius_y)
- {
- Require(radius_y >= 0.0f, "Radius-y must be no less than 0.");
- radius_y_ = radius_y;
- }
-
- private:
- Microsoft::WRL::ComPtr<ID2D1Brush> brush_;
- float stroke_width_ = 1.0f;
- float radius_x_ = 0.0f;
- float radius_y_ = 0.0f;
- Microsoft::WRL::ComPtr<ID2D1StrokeStyle> stroke_style_ = nullptr;
- };
-}
diff --git a/src/ui/content_control.cpp b/src/ui/content_control.cpp
new file mode 100644
index 00000000..d5abca1c
--- /dev/null
+++ b/src/ui/content_control.cpp
@@ -0,0 +1,30 @@
+#include "content_control.hpp"
+
+#include "window.hpp"
+
+namespace cru::ui {
+ContentControl::ContentControl()
+ : child_vector_{nullptr}, child_(child_vector_[0]) {}
+
+ContentControl::~ContentControl() { delete child_; }
+
+void ContentControl::SetChild(Control* child) {
+ assert(!dynamic_cast<Window*>(child)); // Can't add a window as child.
+ if (child == child_) return;
+
+ const auto window = GetWindow();
+ const auto old_child = child_;
+ child_ = child;
+ if (old_child) {
+ old_child->_SetParent(nullptr);
+ old_child->_SetDescendantWindow(nullptr);
+ }
+ if (child) {
+ child->_SetParent(this);
+ child->_SetDescendantWindow(window);
+ }
+ OnChildChanged(old_child, child);
+}
+
+void ContentControl::OnChildChanged(Control* old_child, Control* new_child) {}
+} // namespace cru::ui
diff --git a/src/ui/content_control.hpp b/src/ui/content_control.hpp
new file mode 100644
index 00000000..88e7f60f
--- /dev/null
+++ b/src/ui/content_control.hpp
@@ -0,0 +1,31 @@
+#pragma once
+#include "pre.hpp"
+
+#include "control.hpp"
+
+namespace cru::ui {
+class ContentControl : public Control {
+ protected:
+ ContentControl();
+
+ public:
+ ContentControl(const ContentControl& other) = delete;
+ ContentControl(ContentControl&& other) = delete;
+ ContentControl& operator=(const ContentControl& other) = delete;
+ ContentControl& operator=(ContentControl&& other) = delete;
+ ~ContentControl() override;
+
+ const std::vector<Control*>& GetChildren() const override final {
+ return child_vector_;
+ }
+ Control* GetChild() const { return child_; }
+ void SetChild(Control* child);
+
+ protected:
+ virtual void OnChildChanged(Control* old_child, Control* new_child);
+
+ private:
+ std::vector<Control*> child_vector_;
+ Control*& child_;
+};
+} // namespace cru::ui
diff --git a/src/ui/control.cpp b/src/ui/control.cpp
index 3987e818..318d591a 100644
--- a/src/ui/control.cpp
+++ b/src/ui/control.cpp
@@ -1,905 +1,67 @@
#include "control.hpp"
-#include <algorithm>
-#include <cassert>
-
#include "window.hpp"
-#include "application.hpp"
-#include "graph/graph.hpp"
-#include "exception.hpp"
-#include "cru_debug.hpp"
-#include "convert_util.hpp"
-#include "math_util.hpp"
-
-#ifdef CRU_DEBUG_LAYOUT
-#include "ui_manager.hpp"
-#endif
-
-namespace cru::ui
-{
- Control::Control()
- {
- mouse_leave_event.bubble.AddHandler([this](events::MouseEventArgs& args)
- {
- if (args.GetOriginalSender() != this)
- return;
- for (auto& is_mouse_click_valid : is_mouse_click_valid_map_)
- {
- if (is_mouse_click_valid.second)
- {
- is_mouse_click_valid.second = false;
- OnMouseClickEnd(is_mouse_click_valid.first);
- }
- }
- });
-
- mouse_down_event.bubble.AddHandler([this](events::MouseButtonEventArgs& args)
- {
- if (args.GetOriginalSender() != this)
- return;
-
- if (is_focus_on_pressed_ && args.GetSender() == args.GetOriginalSender())
- RequestFocus();
- const auto button = args.GetMouseButton();
- is_mouse_click_valid_map_[button] = true;
- OnMouseClickBegin(button);
- });
-
- mouse_up_event.bubble.AddHandler([this](events::MouseButtonEventArgs& args)
- {
- if (args.GetOriginalSender() != this)
- return;
-
- const auto button = args.GetMouseButton();
- if (is_mouse_click_valid_map_[button])
- {
- is_mouse_click_valid_map_[button] = false;
- OnMouseClickEnd(button);
- const auto point = args.GetPoint(GetWindow());
- InvokeLater([this, button, point]
- {
- DispatchEvent(this, &Control::mouse_click_event, nullptr, point, button);
- });
- }
- });
- }
-
-
- void Control::SetParent(Control* parent)
- {
- const auto old_parent = GetParent();
- parent_ = parent;
- const auto new_parent = GetParent();
- if (old_parent != new_parent)
- OnParentChanged(old_parent, new_parent);
- }
-
- void Control::SetInternalParent(Control* internal_parent)
- {
- const auto old_internal_parent = GetInternalParent();
- const auto old_parent = GetParent();
- internal_parent_ = internal_parent;
- const auto new_internal_parent = GetInternalParent();
- const auto new_parent = GetParent();
- if (old_parent != new_parent)
- OnParentChanged(old_parent, new_parent);
- if (old_internal_parent != new_internal_parent)
- OnInternalParentChanged(old_internal_parent, new_internal_parent);
- }
-
- void Control::SetDescendantWindow(Window* window)
- {
- if (window == nullptr && window_ == nullptr)
- return;
-
- //You can only attach or detach window.
- assert((window != nullptr && window_ == nullptr) || (window == nullptr && window_ != nullptr));
-
- if (window == nullptr)
- {
- const auto old = window_;
- TraverseDescendants([old](Control* control)
- {
- control->window_ = nullptr;
- control->OnDetachToWindow(old);
- });
- }
- else
- TraverseDescendants([window](Control* control)
- {
- control->window_ = window;
- control->OnAttachToWindow(window);
- });
- }
-
-
- void TraverseDescendantsInternal(Control* control, const std::function<void(Control*)>& predicate)
- {
- predicate(control);
- for (auto c: control->GetInternalChildren())
- TraverseDescendantsInternal(c, predicate);
- }
-
- void Control::TraverseDescendants(const std::function<void(Control*)>& predicate)
- {
- TraverseDescendantsInternal(this, predicate);
- }
-
- Point Control::GetOffset()
- {
- return rect_.GetLeftTop();
- }
-
- Size Control::GetSize()
- {
- return rect_.GetSize();
- }
-
- void Control::SetRect(const Rect& rect)
- {
- const auto old_rect = rect_;
- rect_ = rect;
-
- RegenerateGeometryInfo();
-
- OnRectChange(old_rect, rect);
-
- if (auto window = GetWindow())
- window->InvalidateDraw();
- }
-
- namespace
- {
-#ifdef CRU_DEBUG_LAYOUT
- Microsoft::WRL::ComPtr<ID2D1Geometry> CalculateSquareRingGeometry(const Rect& out, const Rect& in)
- {
- const auto d2d1_factory = graph::GraphManager::GetInstance()->GetD2D1Factory();
- Microsoft::WRL::ComPtr<ID2D1RectangleGeometry> out_geometry;
- ThrowIfFailed(d2d1_factory->CreateRectangleGeometry(Convert(out), &out_geometry));
- Microsoft::WRL::ComPtr<ID2D1RectangleGeometry> in_geometry;
- ThrowIfFailed(d2d1_factory->CreateRectangleGeometry(Convert(in), &in_geometry));
- Microsoft::WRL::ComPtr<ID2D1PathGeometry> result_geometry;
- ThrowIfFailed(d2d1_factory->CreatePathGeometry(&result_geometry));
- Microsoft::WRL::ComPtr<ID2D1GeometrySink> sink;
- ThrowIfFailed(result_geometry->Open(&sink));
- ThrowIfFailed(out_geometry->CombineWithGeometry(in_geometry.Get(), D2D1_COMBINE_MODE_EXCLUDE, D2D1::Matrix3x2F::Identity(), sink.Get()));
- ThrowIfFailed(sink->Close());
- return result_geometry;
- }
-#endif
- }
-
- Point Control::GetPositionAbsolute() const
- {
- return position_cache_.lefttop_position_absolute;
- }
-
- Point Control::ControlToWindow(const Point& point) const
- {
- return Point(point.x + position_cache_.lefttop_position_absolute.x,
- point.y + position_cache_.lefttop_position_absolute.y);
- }
-
- Point Control::WindowToControl(const Point & point) const
- {
- return Point(point.x - position_cache_.lefttop_position_absolute.x,
- point.y - position_cache_.lefttop_position_absolute.y);
- }
-
- void Control::RefreshDescendantPositionCache()
- {
- auto point = Point::Zero();
- auto parent = this;
- while ((parent = parent->GetParent()))
- {
- const auto p = parent->GetOffset();
- point.x += p.x;
- point.y += p.y;
- }
- RefreshControlPositionCacheInternal(this, point);
- }
-
- void Control::RefreshControlPositionCacheInternal(Control* control, const Point& parent_lefttop_absolute)
- {
- const auto position = control->GetOffset();
- const Point lefttop(
- parent_lefttop_absolute.x + position.x,
- parent_lefttop_absolute.y + position.y
- );
- control->position_cache_.lefttop_position_absolute = lefttop;
- for(auto c : control->GetInternalChildren())
- {
- RefreshControlPositionCacheInternal(c, lefttop);
- }
- }
-
- bool Control::IsPointInside(const Point & point)
- {
- const auto border_geometry = geometry_info_.border_geometry;
- if (border_geometry != nullptr)
- {
- if (IsBordered())
- {
- BOOL contains;
- border_geometry->FillContainsPoint(Convert(point), D2D1::Matrix3x2F::Identity(), &contains);
- if (!contains)
- border_geometry->StrokeContainsPoint(Convert(point), GetBorderProperty().GetStrokeWidth(), nullptr, D2D1::Matrix3x2F::Identity(), &contains);
- return contains != 0;
- }
- else
- {
- BOOL contains;
- border_geometry->FillContainsPoint(Convert(point), D2D1::Matrix3x2F::Identity(), &contains);
- return contains != 0;
- }
- }
- return false;
- }
-
- Control* Control::HitTest(const Point& point)
- {
- const auto point_inside = IsPointInside(point);
-
- if (IsClipContent())
- {
- if (!point_inside)
- return nullptr;
- if (geometry_info_.content_geometry != nullptr)
- {
- BOOL contains;
- ThrowIfFailed(geometry_info_.content_geometry->FillContainsPoint(Convert(point), D2D1::Matrix3x2F::Identity(), &contains));
- if (contains == 0)
- return this;
- }
- }
-
- const auto& children = GetInternalChildren();
-
- for (auto i = children.crbegin(); i != children.crend(); ++i)
- {
- const auto&& lefttop = (*i)->GetOffset();
- const auto&& coerced_point = Point(point.x - lefttop.x, point.y - lefttop.y);
- const auto child_hit_test_result = (*i)->HitTest(coerced_point);
- if (child_hit_test_result != nullptr)
- return child_hit_test_result;
- }
-
- return point_inside ? this : nullptr;
- }
-
- void Control::SetClipContent(const bool clip)
- {
- if (clip_content_ == clip)
- return;
-
- clip_content_ = clip;
- InvalidateDraw();
- }
-
- void Control::Draw(ID2D1DeviceContext* device_context)
- {
- D2D1::Matrix3x2F old_transform;
- device_context->GetTransform(&old_transform);
-
- const auto position = GetOffset();
- device_context->SetTransform(old_transform * D2D1::Matrix3x2F::Translation(position.x, position.y));
-
- OnDrawDecoration(device_context);
-
- const auto set_layer = geometry_info_.content_geometry != nullptr && IsClipContent();
- if (set_layer)
- device_context->PushLayer(D2D1::LayerParameters(D2D1::InfiniteRect(), geometry_info_.content_geometry.Get()), nullptr);
-
- OnDrawCore(device_context);
-
- for (auto child : GetInternalChildren())
- child->Draw(device_context);
-
- if (set_layer)
- device_context->PopLayer();
-
- device_context->SetTransform(old_transform);
- }
-
- void Control::InvalidateDraw()
- {
- if (window_ != nullptr)
- window_->InvalidateDraw();
- }
-
- bool Control::RequestFocus()
- {
- auto window = GetWindow();
- if (window == nullptr)
- return false;
-
- return window->RequestFocusFor(this);
- }
-
- bool Control::HasFocus()
- {
- auto window = GetWindow();
- if (window == nullptr)
- return false;
-
- return window->GetFocusControl() == this;
- }
-
- void Control::InvalidateLayout()
- {
- if (const auto window = GetWindow())
- window->WindowInvalidateLayout();
- }
-
- void Control::Measure(const Size& available_size, const AdditionalMeasureInfo& additional_info)
- {
- SetDesiredSize(OnMeasureCore(available_size, additional_info));
- }
-
- void Control::Layout(const Rect& rect, const AdditionalLayoutInfo& additional_info)
- {
- auto my_additional_info = additional_info;
- my_additional_info.total_offset.x += rect.left;
- my_additional_info.total_offset.y += rect.top;
- position_cache_.lefttop_position_absolute.x = my_additional_info.total_offset.x;
- position_cache_.lefttop_position_absolute.y = my_additional_info.total_offset.y;
-
- SetRect(rect);
- OnLayoutCore(Rect(Point::Zero(), rect.GetSize()), my_additional_info);
- }
-
- Size Control::GetDesiredSize() const
- {
- return desired_size_;
- }
-
- void Control::SetDesiredSize(const Size& desired_size)
- {
- desired_size_ = desired_size;
- }
-
- inline void Shrink(Rect& rect, const Thickness& thickness)
- {
- rect.left += thickness.left;
- rect.top += thickness.top;
- rect.width -= thickness.GetHorizontalTotal();
- rect.height -= thickness.GetVerticalTotal();
- }
-
- Rect Control::GetRect(const RectRange range)
- {
- if (GetSize() == Size::Zero())
- return Rect();
-
- const auto layout_params = GetLayoutParams();
-
- auto result = Rect(Point::Zero(), GetSize());
-
- if (range == RectRange::Margin)
- return result;
-
- Shrink(result, layout_params->margin);
-
- if (range == RectRange::FullBorder)
- return result;
-
- if (is_bordered_)
- Shrink(result, Thickness(GetBorderProperty().GetStrokeWidth() / 2.0f));
-
- if (range == RectRange::HalfBorder)
- return result;
-
- if (is_bordered_)
- Shrink(result, Thickness(GetBorderProperty().GetStrokeWidth() / 2.0f));
-
- if (range == RectRange::Padding)
- return result;
-
- Shrink(result, layout_params->padding);
-
- return result;
- }
-
- Point Control::TransformPoint(const Point& point, const RectRange from, const RectRange to)
- {
- const auto rect_from = GetRect(from);
- const auto rect_to = GetRect(to);
- auto p = point;
- p.x += rect_from.left;
- p.y += rect_from.top;
- p.x -= rect_to.left;
- p.y -= rect_to.top;
- return p;
- }
-
- void Control::UpdateBorder()
- {
- RegenerateGeometryInfo();
- InvalidateLayout();
- InvalidateDraw();
- }
-
- void Control::SetBordered(const bool bordered)
- {
- if (bordered != is_bordered_)
- {
- is_bordered_ = bordered;
- UpdateBorder();
- }
- }
-
- void Control::SetCursor(const Cursor::Ptr& cursor)
- {
- if (cursor != cursor_)
- {
- cursor_ = cursor;
- const auto window = GetWindow();
- if (window && window->GetMouseHoverControl() == this)
- window->UpdateCursor();
- }
- }
-
- void Control::OnParentChanged(Control* old_parent, Control* new_parent)
- {
-
- }
-
- void Control::OnInternalParentChanged(Control* old_internal_parent, Control* new_internal_parent)
- {
-
- }
-
- void Control::OnAttachToWindow(Window* window)
- {
- window_ = window;
- }
-
- void Control::OnDetachToWindow(Window * window)
- {
- window_ = nullptr;
- }
-
- void Control::OnDrawDecoration(ID2D1DeviceContext* device_context)
- {
-#ifdef CRU_DEBUG_LAYOUT
- if (GetWindow()->IsDebugLayout())
- {
- if (padding_geometry_ != nullptr)
- device_context->FillGeometry(padding_geometry_.Get(), UiManager::GetInstance()->GetPredefineResources()->debug_layout_padding_brush.Get());
- if (margin_geometry_ != nullptr)
- device_context->FillGeometry(margin_geometry_.Get(), UiManager::GetInstance()->GetPredefineResources()->debug_layout_margin_brush.Get());
- device_context->DrawRectangle(Convert(GetRect(RectRange::Margin)), UiManager::GetInstance()->GetPredefineResources()->debug_layout_out_border_brush.Get());
- }
-#endif
-
- if (is_bordered_ && geometry_info_.border_geometry != nullptr)
- device_context->DrawGeometry(
- geometry_info_.border_geometry.Get(),
- GetBorderProperty().GetBrush().Get(),
- GetBorderProperty().GetStrokeWidth(),
- GetBorderProperty().GetStrokeStyle().Get()
- );
- }
- void Control::OnDrawCore(ID2D1DeviceContext* device_context)
- {
- const auto ground_geometry = geometry_info_.padding_content_geometry;
- //draw background.
- if (ground_geometry != nullptr && background_brush_ != nullptr)
- device_context->FillGeometry(ground_geometry.Get(), background_brush_.Get());
- const auto padding_rect = GetRect(RectRange::Padding);
- graph::WithTransform(device_context, D2D1::Matrix3x2F::Translation(padding_rect.left, padding_rect.top),
- [this](ID2D1DeviceContext* device_context)
- {
- events::DrawEventArgs args(this, this, device_context);
- draw_background_event.Raise(args);
- });
-
-
- const auto rect = GetRect(RectRange::Content);
- graph::WithTransform(device_context, D2D1::Matrix3x2F::Translation(rect.left, rect.top),
- [this](ID2D1DeviceContext* device_context)
- {
- events::DrawEventArgs args(this, this, device_context);
- draw_content_event.Raise(args);
- });
-
-
- //draw foreground.
- if (ground_geometry != nullptr && foreground_brush_ != nullptr)
- device_context->FillGeometry(ground_geometry.Get(), foreground_brush_.Get());
- graph::WithTransform(device_context, D2D1::Matrix3x2F::Translation(padding_rect.left, padding_rect.top),
- [this](ID2D1DeviceContext* device_context)
- {
- events::DrawEventArgs args(this, this, device_context);
- draw_foreground_event.Raise(args);
- });
- }
-
- void Control::OnRectChange(const Rect& old_rect, const Rect& new_rect)
- {
-
- }
-
- void Control::RegenerateGeometryInfo()
- {
- if (IsBordered())
- {
- const auto bound_rect = GetRect(RectRange::HalfBorder);
- const auto bound_rounded_rect = D2D1::RoundedRect(Convert(bound_rect),
- GetBorderProperty().GetRadiusX(),
- GetBorderProperty().GetRadiusY());
-
- Microsoft::WRL::ComPtr<ID2D1RoundedRectangleGeometry> geometry;
- ThrowIfFailed(
- graph::GraphManager::GetInstance()->GetD2D1Factory()->CreateRoundedRectangleGeometry(bound_rounded_rect, &geometry)
- );
- geometry_info_.border_geometry = std::move(geometry);
-
- const auto padding_rect = GetRect(RectRange::Padding);
- const auto in_border_rounded_rect = D2D1::RoundedRect(Convert(padding_rect),
- GetBorderProperty().GetRadiusX() - GetBorderProperty().GetStrokeWidth() / 2.0f,
- GetBorderProperty().GetRadiusY() - GetBorderProperty().GetStrokeWidth() / 2.0f);
-
- Microsoft::WRL::ComPtr<ID2D1RoundedRectangleGeometry> geometry2;
- ThrowIfFailed(
- graph::GraphManager::GetInstance()->GetD2D1Factory()->CreateRoundedRectangleGeometry(in_border_rounded_rect, &geometry2)
- );
- geometry_info_.padding_content_geometry = geometry2;
-
-
- Microsoft::WRL::ComPtr<ID2D1RectangleGeometry> geometry3;
- ThrowIfFailed(
- graph::GraphManager::GetInstance()->GetD2D1Factory()->CreateRectangleGeometry(Convert(GetRect(RectRange::Content)), &geometry3)
- );
- Microsoft::WRL::ComPtr<ID2D1PathGeometry> geometry4;
- ThrowIfFailed(
- graph::GraphManager::GetInstance()->GetD2D1Factory()->CreatePathGeometry(&geometry4)
- );
- Microsoft::WRL::ComPtr<ID2D1GeometrySink> sink;
- geometry4->Open(&sink);
- ThrowIfFailed(
- geometry3->CombineWithGeometry(geometry2.Get(), D2D1_COMBINE_MODE_INTERSECT, D2D1::Matrix3x2F::Identity(), sink.Get())
- );
- sink->Close();
- geometry_info_.content_geometry = std::move(geometry4);
- }
- else
- {
- const auto bound_rect = GetRect(RectRange::Padding);
- Microsoft::WRL::ComPtr<ID2D1RectangleGeometry> geometry;
- ThrowIfFailed(
- graph::GraphManager::GetInstance()->GetD2D1Factory()->CreateRectangleGeometry(Convert(bound_rect), &geometry)
- );
- geometry_info_.border_geometry = geometry;
- geometry_info_.padding_content_geometry = std::move(geometry);
-
- Microsoft::WRL::ComPtr<ID2D1RectangleGeometry> geometry2;
- ThrowIfFailed(
- graph::GraphManager::GetInstance()->GetD2D1Factory()->CreateRectangleGeometry(Convert(GetRect(RectRange::Content)), &geometry2)
- );
- geometry_info_.content_geometry = std::move(geometry2);
- }
-
- //TODO: generate debug geometry
-#ifdef CRU_DEBUG_LAYOUT
- margin_geometry_ = CalculateSquareRingGeometry(GetRect(RectRange::Margin), GetRect(RectRange::FullBorder));
- padding_geometry_ = CalculateSquareRingGeometry(GetRect(RectRange::Padding), GetRect(RectRange::Content));
-#endif
- }
-
- void Control::OnMouseClickBegin(MouseButton button)
- {
- }
-
- void Control::OnMouseClickEnd(MouseButton button)
- {
- }
-
- inline Size ThicknessToSize(const Thickness& thickness)
- {
- return Size(thickness.left + thickness.right, thickness.top + thickness.bottom);
- }
-
- Size Control::OnMeasureCore(const Size& available_size, const AdditionalMeasureInfo& additional_info)
- {
- const auto layout_params = GetLayoutParams();
-
- if (!layout_params->Validate())
- throw std::runtime_error("LayoutParams is not valid. Please check it.");
-
- auto my_additional_info = additional_info;
-
- if (layout_params->width.mode == MeasureMode::Content)
- my_additional_info.horizontal_stretchable = false;
- else if (layout_params->width.mode == MeasureMode::Exactly)
- my_additional_info.horizontal_stretchable = true;
- // if stretch, then inherent parent's value
-
- if (layout_params->height.mode == MeasureMode::Content)
- my_additional_info.vertical_stretchable = false;
- else if (layout_params->height.mode == MeasureMode::Exactly)
- my_additional_info.vertical_stretchable = true;
- // if stretch, then inherent parent's value
-
-
- auto border_size = Size::Zero();
- if (is_bordered_)
- {
- const auto border_width = GetBorderProperty().GetStrokeWidth();
- border_size = Size(border_width * 2.0f, border_width * 2.0f);
- }
-
- // the total size of padding, border and margin
- const auto outer_size = ThicknessToSize(layout_params->padding) +
- ThicknessToSize(layout_params->margin) + border_size;
-
-
- auto&& get_content_measure_length = [](const LayoutSideParams& layout_length, const float available_length, const float outer_length) -> float
- {
- float length;
- if (layout_length.mode == MeasureMode::Exactly)
- length = layout_length.length;
- else if (available_length > outer_length)
- length = available_length - outer_length;
- else
- length = 0;
- return Coerce(length, layout_length.min, layout_length.max);
- };
-
- // if padding, margin and border exceeded, then content size is 0.
- const auto content_measure_size = Size(
- get_content_measure_length(layout_params->width, available_size.width, outer_size.width),
- get_content_measure_length(layout_params->height, available_size.height, outer_size.height)
- );
-
- const auto content_actual_size = OnMeasureContent(content_measure_size, my_additional_info);
-
-
-
- auto&& calculate_final_length = [](const bool stretch, const std::optional<float> min_length, const float measure_length, const float actual_length) -> float
- {
- // only use measure length when stretch and actual length is smaller than measure length, that is "stretch"
- if (stretch && actual_length < measure_length)
- return measure_length;
- return Coerce(actual_length, min_length, std::nullopt);
- };
-
- const auto final_size = Size(
- calculate_final_length(my_additional_info.horizontal_stretchable, layout_params->width.min, content_measure_size.width, content_actual_size.width),
- calculate_final_length(my_additional_info.vertical_stretchable, layout_params->height.min, content_measure_size.height, content_actual_size.height)
- ) + outer_size;
-
- return final_size;
- }
-
- void Control::OnLayoutCore(const Rect& rect, const AdditionalLayoutInfo& additional_info)
- {
- const auto layout_params = GetLayoutParams();
-
- auto border_width = 0.0f;
- if (is_bordered_)
- {
- border_width = GetBorderProperty().GetStrokeWidth();
- }
-
- const Rect content_rect(
- rect.left + layout_params->padding.left + layout_params->margin.right + border_width,
- rect.top + layout_params->padding.top + layout_params->margin.top + border_width,
- rect.width - layout_params->padding.GetHorizontalTotal() - layout_params->margin.GetHorizontalTotal() - border_width * 2.0f,
- rect.height - layout_params->padding.GetVerticalTotal() - layout_params->margin.GetVerticalTotal() - border_width * 2.0f
- );
-
- if (content_rect.width < 0.0)
- throw std::runtime_error(Format("Width to layout must sufficient. But in {}, width for content is {}.", ToUtf8String(GetControlType()), content_rect.width));
- if (content_rect.height < 0.0)
- throw std::runtime_error(Format("Height to layout must sufficient. But in {}, height for content is {}.", ToUtf8String(GetControlType()), content_rect.height));
-
- OnLayoutContent(content_rect, additional_info);
- }
-
- const std::vector<Control*> NoChildControl::empty_control_vector{};
-
- std::list<Control*> GetAncestorList(Control* control)
- {
- std::list<Control*> l;
- while (control != nullptr)
- {
- l.push_front(control);
- control = control->GetInternalParent();
- }
- return l;
- }
-
- void NoChildControl::OnLayoutContent(const Rect& rect, const AdditionalLayoutInfo& additional_info)
- {
-
- }
-
- SingleChildControl::SingleChildControl() : child_vector_{nullptr}, child_(child_vector_[0])
- {
-
- }
-
- SingleChildControl::~SingleChildControl()
- {
- delete child_;
- }
-
- void SingleChildControl::SetChild(Control* child)
- {
- if (child == child_)
- return;
-
- const auto window = GetWindow();
- const auto old_child = child_;
- child_ = child;
- if (old_child)
- {
- old_child->SetInternalParent(nullptr);
- old_child->SetDescendantWindow(nullptr);
- }
- if (child)
- {
- child->SetInternalParent(this);
- child->SetDescendantWindow(window);
- }
- OnChildChanged(old_child, child);
- }
-
- void SingleChildControl::OnChildChanged(Control* old_child, Control* new_child)
- {
-
- }
-
- Size SingleChildControl::OnMeasureContent(const Size& available_size, const AdditionalMeasureInfo& additional_info)
- {
- auto child_size = Size::Zero();
- if (child_)
- {
- child_->Measure(available_size, additional_info);
- child_size = child_->GetDesiredSize();
- }
-
- return child_size;
- }
-
- void SingleChildControl::OnLayoutContent(const Rect& rect, const AdditionalLayoutInfo& additional_info)
- {
- if (child_)
- {
- const auto layout_params = child_->GetLayoutParams();
- const auto size = child_->GetDesiredSize();
-
- auto&& calculate_anchor = [](const float anchor, const Alignment alignment, const float layout_length, const float control_length) -> float
- {
- switch (alignment)
- {
- case Alignment::Center:
- return anchor + (layout_length - control_length) / 2;
- case Alignment::Start:
- return anchor;
- case Alignment::End:
- return anchor + layout_length - control_length;
- default:
- UnreachableCode();
- }
- };
-
- child_->Layout(Rect(Point(
- calculate_anchor(rect.left, layout_params->width.alignment, rect.width, size.width),
- calculate_anchor(rect.top, layout_params->height.alignment, rect.height, size.height)
- ), size), additional_info);
- }
- }
-
- void AddChildCheck(Control* control)
- {
- if (control->GetInternalParent() != nullptr)
- throw std::invalid_argument("The control already has a parent.");
-
- if (dynamic_cast<Window*>(control))
- throw std::invalid_argument("Can't add a window as child.");
- }
-
- MultiChildControl::~MultiChildControl()
- {
- for (const auto child : children_)
- delete child;
- }
-
- void MultiChildControl::AddChild(Control* control)
- {
- AddChildCheck(control);
-
- children_.push_back(control);
-
- control->SetInternalParent(this);
- control->SetDescendantWindow(GetWindow());
-
- OnAddChild(control);
- }
-
- void MultiChildControl::AddChild(Control* control, const int position)
- {
- AddChildCheck(control);
-
- if (position < 0 || static_cast<decltype(children_.size())>(position) > this->children_.size())
- throw std::invalid_argument("The position is out of range.");
-
- children_.insert(this->children_.cbegin() + position, control);
-
- control->SetInternalParent(this);
- control->SetDescendantWindow(GetWindow());
-
- OnAddChild(control);
- }
-
- void MultiChildControl::RemoveChild(Control* child)
- {
- const auto i = std::find(this->children_.cbegin(), this->children_.cend(), child);
- if (i == this->children_.cend())
- throw std::invalid_argument("The argument child is not a child of this control.");
-
- children_.erase(i);
-
- child->SetInternalParent(nullptr);
- child->SetDescendantWindow(nullptr);
-
- OnRemoveChild(child);
- }
-
- void MultiChildControl::RemoveChild(const int position)
- {
- if (position < 0 || static_cast<decltype(this->children_.size())>(position) >= this->children_.size())
- throw std::invalid_argument("The position is out of range.");
-
- const auto i = children_.cbegin() + position;
- const auto child = *i;
+namespace cru::ui {
+void Control::_SetParent(Control* parent) {
+ const auto old_parent = GetParent();
+ parent_ = parent;
+ const auto new_parent = GetParent();
+ if (old_parent != new_parent) OnParentChanged(old_parent, new_parent);
+}
- children_.erase(i);
+void Control::_SetDescendantWindow(Window* window) {
+ if (window == nullptr && window_ == nullptr) return;
+
+ // You can only attach or detach window.
+ assert((window != nullptr && window_ == nullptr) ||
+ (window == nullptr && window_ != nullptr));
+
+ if (window == nullptr) {
+ const auto old = window_;
+ TraverseDescendants([old](Control* control) {
+ control->window_ = nullptr;
+ control->OnDetachToWindow(old);
+ });
+ } else
+ TraverseDescendants([window](Control* control) {
+ control->window_ = window;
+ control->OnAttachToWindow(window);
+ });
+}
- child->SetInternalParent(nullptr);
- child->SetDescendantWindow(nullptr);
+void Control::TraverseDescendants(
+ const std::function<void(Control*)>& predicate) {
+ _TraverseDescendants(this, predicate);
+}
- OnRemoveChild(child);
- }
+void Control::_TraverseDescendants(
+ Control* control, const std::function<void(Control*)>& predicate) {
+ predicate(control);
+ for (auto c : control->GetChildren())
+ _TraverseDescendants(c, predicate);
+}
+bool Control::RequestFocus() {
+ auto window = GetWindow();
+ if (window == nullptr) return false;
- void MultiChildControl::OnAddChild(Control* child)
- {
+ return window->RequestFocusFor(this);
+}
- }
+bool Control::HasFocus() {
+ auto window = GetWindow();
+ if (window == nullptr) return false;
- void MultiChildControl::OnRemoveChild(Control* child)
- {
+ return window->GetFocusControl() == this;
+}
- }
+void Control::OnParentChanged(Control* old_parent, Control* new_parent) {}
- Control* FindLowestCommonAncestor(Control * left, Control * right)
- {
- if (left == nullptr || right == nullptr)
- return nullptr;
+void Control::OnAttachToWindow(Window* window) {}
- auto&& left_list = GetAncestorList(left);
- auto&& right_list = GetAncestorList(right);
+void Control::OnDetachToWindow(Window* window) {}
- // the root is different
- if (left_list.front() != right_list.front())
- return nullptr;
+void Control::OnMouseClickBegin(MouseButton button) {}
- // find the last same control or the last control (one is ancestor of the other)
- auto left_i = left_list.cbegin();
- auto right_i = right_list.cbegin();
- while (true)
- {
- if (left_i == left_list.cend())
- return *(--left_i);
- if (right_i == right_list.cend())
- return *(--right_i);
- if (*left_i != *right_i)
- return *(--left_i);
- ++left_i;
- ++right_i;
- }
- }
-}
+void Control::OnMouseClickEnd(MouseButton button) {}
+} // namespace cru::ui
diff --git a/src/ui/control.hpp b/src/ui/control.hpp
index 6abcc365..2504aa62 100644
--- a/src/ui/control.hpp
+++ b/src/ui/control.hpp
@@ -1,570 +1,136 @@
#pragma once
-
-// ReSharper disable once CppUnusedIncludeDirective
#include "pre.hpp"
-#include "system_headers.hpp"
-#include <unordered_map>
-#include <any>
-#include <utility>
-
#include "base.hpp"
-#include "ui_base.hpp"
-#include "layout_base.hpp"
#include "events/ui_event.hpp"
-#include "border_property.hpp"
-#include "cursor.hpp"
-#include "any_map.hpp"
-
-namespace cru::ui
-{
- class Window;
-
-
- struct AdditionalMeasureInfo
- {
- bool horizontal_stretchable = true;
- bool vertical_stretchable = true;
- };
-
- struct AdditionalLayoutInfo
- {
- Point total_offset = Point::Zero();
- };
-
- //the position cache
- struct ControlPositionCache
- {
- //The lefttop relative to the ancestor.
- Point lefttop_position_absolute = Point::Zero();
- };
-
-
- class Control : public Object
- {
- friend class Window;
-
- protected:
- struct GeometryInfo
- {
- Microsoft::WRL::ComPtr<ID2D1Geometry> border_geometry = nullptr;
- Microsoft::WRL::ComPtr<ID2D1Geometry> padding_content_geometry = nullptr;
- Microsoft::WRL::ComPtr<ID2D1Geometry> content_geometry = nullptr;
- };
-
-
- protected:
- Control();
- public:
- Control(const Control& other) = delete;
- Control(Control&& other) = delete;
- Control& operator=(const Control& other) = delete;
- Control& operator=(Control&& other) = delete;
- ~Control() override = default;
-
- public:
-
- //*************** region: tree ***************
- virtual StringView GetControlType() const = 0;
-
- virtual const std::vector<Control*>& GetInternalChildren() const = 0;
-
- Control* GetParent() const
- {
- return parent_ == nullptr ? internal_parent_ : parent_;
- }
-
- Control* GetInternalParent() const
- {
- return internal_parent_;
- }
-
- //Get the window if attached, otherwise, return nullptr.
- Window* GetWindow() const
- {
- return window_;
- }
-
- void SetParent(Control* parent);
-
- void SetInternalParent(Control* internal_parent);
-
- void SetDescendantWindow(Window* window);
-
-
- //Traverse the tree rooted the control including itself.
- void TraverseDescendants(const std::function<void(Control*)>& predicate);
-
- //*************** region: position and size ***************
-
- //Get the lefttop relative to its parent.
- virtual Point GetOffset();
-
- //Get the actual size.
- virtual Size GetSize();
-
- // If offset changes, call RefreshDescendantPositionCache.
- virtual void SetRect(const Rect& rect);
-
- //Get lefttop relative to ancestor. This is only valid when
- //attached to window. Notice that the value is cached.
- //You can invalidate and recalculate it by calling "InvalidatePositionCache".
- Point GetPositionAbsolute() const;
-
- //Local point to absolute point.
- Point ControlToWindow(const Point& point) const;
-
- //Absolute point to local point.
- Point WindowToControl(const Point& point) const;
-
- void RefreshDescendantPositionCache();
-
- private:
- static void RefreshControlPositionCacheInternal(Control* control, const Point& parent_lefttop_absolute);
-
- public:
-
- // Default implement in Control is test point in border geometry's
- // fill and stroke with width of border.
- virtual bool IsPointInside(const Point& point);
-
- // Get the top control among all descendants (including self) in local coordinate.
- virtual Control* HitTest(const Point& point);
-
- //*************** region: graphic ***************
-
- bool IsClipContent() const
- {
- return clip_content_;
- }
-
- void SetClipContent(bool clip);
-
- //Draw this control and its child controls.
- void Draw(ID2D1DeviceContext* device_context);
-
- virtual void InvalidateDraw();
-
- Microsoft::WRL::ComPtr<ID2D1Brush> GetForegroundBrush() const
- {
- return foreground_brush_;
- }
-
- void SetForegroundBrush(Microsoft::WRL::ComPtr<ID2D1Brush> foreground_brush)
- {
- foreground_brush_ = std::move(foreground_brush);
- InvalidateDraw();
- }
-
- Microsoft::WRL::ComPtr<ID2D1Brush> GetBackgroundBrush() const
- {
- return background_brush_;
- }
-
- void SetBackgroundBrush(Microsoft::WRL::ComPtr<ID2D1Brush> background_brush)
- {
- background_brush_ = std::move(background_brush);
- InvalidateDraw();
- }
-
-
- //*************** region: focus ***************
-
- bool RequestFocus();
-
- bool HasFocus();
-
- bool IsFocusOnPressed() const
- {
- return is_focus_on_pressed_;
- }
-
- void SetFocusOnPressed(const bool value)
- {
- is_focus_on_pressed_ = value;
- }
-
- //*************** region: layout ***************
-
- void InvalidateLayout();
-
- void Measure(const Size& available_size, const AdditionalMeasureInfo& additional_info);
-
- void Layout(const Rect& rect, const AdditionalLayoutInfo& additional_info);
-
- Size GetDesiredSize() const;
-
- void SetDesiredSize(const Size& desired_size);
-
- BasicLayoutParams* GetLayoutParams()
- {
- return &layout_params_;
- }
-
- Rect GetRect(RectRange range);
-
- Point TransformPoint(const Point& point, RectRange from = RectRange::Margin, RectRange to = RectRange::Content);
-
- //*************** region: border ***************
-
- BorderProperty& GetBorderProperty()
- {
- return border_property_;
- }
-
- void UpdateBorder();
-
- bool IsBordered() const
- {
- return is_bordered_;
- }
-
- void SetBordered(bool bordered);
-
-
- //*************** region: additional properties ***************
- AnyMap* GetAdditionalPropertyMap()
- {
- return &additional_property_map_;
- }
-
-
- //*************** region: cursor ***************
- // If cursor is set to null, then it uses parent's cursor.
- // Window's cursor can't be null.
-
- Cursor::Ptr GetCursor() const
- {
- return cursor_;
- }
-
- void SetCursor(const Cursor::Ptr& cursor);
-
-
- //*************** region: events ***************
- //Raised when mouse enter the control.
- events::RoutedEvent<events::MouseEventArgs> mouse_enter_event;
- //Raised when mouse is leave the control.
- events::RoutedEvent<events::MouseEventArgs> mouse_leave_event;
- //Raised when mouse is move in the control.
- events::RoutedEvent<events::MouseEventArgs> mouse_move_event;
- //Raised when a mouse button is pressed in the control.
- events::RoutedEvent<events::MouseButtonEventArgs> mouse_down_event;
- //Raised when a mouse button is released in the control.
- events::RoutedEvent<events::MouseButtonEventArgs> mouse_up_event;
- //Raised when a mouse button is pressed in the control and released in the control with mouse not leaving it between two operations.
- events::RoutedEvent<events::MouseButtonEventArgs> mouse_click_event;
-
- events::RoutedEvent<events::MouseWheelEventArgs> mouse_wheel_event;
-
- events::RoutedEvent<events::KeyEventArgs> key_down_event;
- events::RoutedEvent<events::KeyEventArgs> key_up_event;
- events::RoutedEvent<events::CharEventArgs> char_event;
-
- events::RoutedEvent<events::FocusChangeEventArgs> get_focus_event;
- events::RoutedEvent<events::FocusChangeEventArgs> lose_focus_event;
-
- Event<events::DrawEventArgs> draw_content_event;
- Event<events::DrawEventArgs> draw_background_event;
- Event<events::DrawEventArgs> draw_foreground_event;
-
-
- //*************** region: tree event ***************
- protected:
- virtual void OnParentChanged(Control* old_parent, Control* new_parent);
-
- virtual void OnInternalParentChanged(Control* old_internal_parent, Control* new_internal_parent);
-
- //Invoked when the control is attached to a window. Overrides should invoke base.
- virtual void OnAttachToWindow(Window* window);
- //Invoked when the control is detached to a window. Overrides should invoke base.
- virtual void OnDetachToWindow(Window* window);
-
-
- //*************** region: graphic event ***************
- private:
- void OnDrawDecoration(ID2D1DeviceContext* device_context);
- void OnDrawCore(ID2D1DeviceContext* device_context);
-
-
- //*************** region: position and size event ***************
- protected:
- virtual void OnRectChange(const Rect& old_rect, const Rect& new_rect);
-
- void RegenerateGeometryInfo();
-
- const GeometryInfo& GetGeometryInfo() const
- {
- return geometry_info_;
- }
-
-
- //*************** region: mouse event ***************
- protected:
- virtual void OnMouseClickBegin(MouseButton button);
- virtual void OnMouseClickEnd(MouseButton button);
-
-
- //*************** region: layout ***************
- private:
- Size OnMeasureCore(const Size& available_size, const AdditionalMeasureInfo& additional_info);
- void OnLayoutCore(const Rect& rect, const AdditionalLayoutInfo& additional_info);
-
- protected:
- virtual Size OnMeasureContent(const Size& available_size, const AdditionalMeasureInfo& additional_info) = 0;
- virtual void OnLayoutContent(const Rect& rect, const AdditionalLayoutInfo& additional_info) = 0;
-
- private:
- Window * window_ = nullptr;
- Control* parent_ = nullptr; // when parent and internal parent are the same, parent_ is nullptr.
- Control * internal_parent_ = nullptr;
-
- Rect rect_{};
-
- ControlPositionCache position_cache_{};
-
- std::unordered_map<MouseButton, bool> is_mouse_click_valid_map_
- {
- { MouseButton::Left, true },
- { MouseButton::Middle, true },
- { MouseButton::Right, true }
- }; // used for clicking determination
-
- BasicLayoutParams layout_params_{};
- Size desired_size_ = Size::Zero();
-
- bool is_bordered_ = false;
- BorderProperty border_property_;
-
- GeometryInfo geometry_info_{};
-
- bool clip_content_ = false;
-
- Microsoft::WRL::ComPtr<ID2D1Brush> foreground_brush_ = nullptr;
- Microsoft::WRL::ComPtr<ID2D1Brush> background_brush_ = nullptr;
-
- AnyMap additional_property_map_{};
-
- bool is_focus_on_pressed_ = true;
-
-#ifdef CRU_DEBUG_LAYOUT
- Microsoft::WRL::ComPtr<ID2D1Geometry> margin_geometry_;
- Microsoft::WRL::ComPtr<ID2D1Geometry> padding_geometry_;
-#endif
-
- Cursor::Ptr cursor_{};
- };
-
-
-
- class NoChildControl : public Control
- {
- private:
- // used in GetInternalChildren.
- static const std::vector<Control*> empty_control_vector;
-
- protected:
- NoChildControl() = default;
- public:
- NoChildControl(const NoChildControl& other) = delete;
- NoChildControl(NoChildControl&& other) = delete;
- NoChildControl& operator=(const NoChildControl& other) = delete;
- NoChildControl& operator=(NoChildControl&& other) = delete;
- ~NoChildControl() override = default;
-
- const std::vector<Control*>& GetInternalChildren() const override final
- {
- return empty_control_vector;
- }
-
- protected:
- void OnLayoutContent(const Rect& rect, const AdditionalLayoutInfo& additional_info) override;
- };
-
-
- class SingleChildControl : public Control
- {
- protected:
- SingleChildControl();
- public:
- SingleChildControl(const SingleChildControl& other) = delete;
- SingleChildControl(SingleChildControl&& other) = delete;
- SingleChildControl& operator=(const SingleChildControl& other) = delete;
- SingleChildControl& operator=(SingleChildControl&& other) = delete;
- ~SingleChildControl() override;
-
- const std::vector<Control*>& GetInternalChildren() const override final
- {
- return child_vector_;
- }
-
- Control* GetChild() const
- {
- return child_;
- }
-
- void SetChild(Control* child);
-
- protected:
- // Override should call base.
- virtual void OnChildChanged(Control* old_child, Control* new_child);
-
- Size OnMeasureContent(const Size& available_size, const AdditionalMeasureInfo& additional_info) override;
- void OnLayoutContent(const Rect& rect, const AdditionalLayoutInfo& additional_info) override;
-
- private:
- std::vector<Control*> child_vector_;
- Control*& child_;
- };
-
-
- class MultiChildControl : public Control
- {
- protected:
- MultiChildControl() = default;
- public:
- MultiChildControl(const MultiChildControl& other) = delete;
- MultiChildControl(MultiChildControl&& other) = delete;
- MultiChildControl& operator=(const MultiChildControl& other) = delete;
- MultiChildControl& operator=(MultiChildControl&& other) = delete;
- ~MultiChildControl() override;
-
- const std::vector<Control*>& GetInternalChildren() const override final
- {
- return children_;
- }
-
- const std::vector<Control*>& GetChildren() const
- {
- return children_;
- }
-
- //Add a child at tail.
- void AddChild(Control* control);
-
- //Add a child before the position.
- void AddChild(Control* control, int position);
-
- //Remove a child.
- void RemoveChild(Control* child);
-
- //Remove a child at specified position.
- void RemoveChild(int position);
-
- protected:
- //Invoked when a child is added. Overrides should invoke base.
- virtual void OnAddChild(Control* child);
- //Invoked when a child is removed. Overrides should invoke base.
- virtual void OnRemoveChild(Control* child);
-
- private:
- std::vector<Control*> children_;
- };
-
-
-
- //*************** region: event dispatcher helper ***************
-
- // Dispatch the event.
- //
- // This will raise routed event of the control and its parent and parent's
- // parent ... (until "last_receiver" if it's not nullptr) with appropriate args.
- //
- // First tunnel from top to bottom possibly stopped by "handled" flag in EventArgs.
- // Second bubble from bottom to top possibly stopped by "handled" flag in EventArgs.
- // Last direct to each control.
- //
- // Args is of type "EventArgs". The first init argument is "sender", which is
- // automatically bound to each receiving control. The second init argument is
- // "original_sender", which is unchanged. And "args" will be perfectly forwarded
- // as the rest arguments.
- template<typename EventArgs, typename... Args>
- void DispatchEvent(Control* const original_sender, events::RoutedEvent<EventArgs> Control::* event_ptr, Control* const last_receiver, Args&&... args)
- {
- std::list<Control*> receive_list;
-
- auto parent = original_sender;
- while (parent != last_receiver)
- {
- receive_list.push_back(parent);
- parent = parent->GetInternalParent();
- }
-
- auto handled = false;
-
- //tunnel
- for (auto i = receive_list.crbegin(); i != receive_list.crend(); ++i)
- {
- EventArgs event_args(*i, original_sender, std::forward<Args>(args)...);
- (*i->*event_ptr).tunnel.Raise(event_args);
- if (event_args.IsHandled())
- {
- handled = true;
- break;
- }
- }
-
- //bubble
- if (!handled)
- {
- for (auto i : receive_list)
- {
- EventArgs event_args(i, original_sender, std::forward<Args>(args)...);
- (i->*event_ptr).bubble.Raise(event_args);
- if (event_args.IsHandled())
- break;
- }
- }
-
- //direct
- for (auto i : receive_list)
- {
- EventArgs event_args(i, original_sender, std::forward<Args>(args)...);
- (i->*event_ptr).direct.Raise(event_args);
- }
- }
-
-
- //*************** region: tree helper ***************
-
- // Find the lowest common ancestor.
- // Return nullptr if "left" and "right" are not in the same tree.
- Control* FindLowestCommonAncestor(Control* left, Control* right);
-
-
- //*************** region: create helper ***************
-
- template <typename TControl, typename... Args>
- TControl* CreateWithLayout(const LayoutSideParams& width, const LayoutSideParams& height, Args&&... args)
- {
- static_assert(std::is_base_of_v<Control, TControl>, "TControl is not a control class.");
- TControl* control = TControl::Create(std::forward<Args>(args)...);
- control->GetLayoutParams()->width = width;
- control->GetLayoutParams()->height = height;
- return control;
- }
-
- template <typename TControl, typename... Args>
- TControl* CreateWithLayout(const Thickness& padding, const Thickness& margin, Args&&... args)
- {
- static_assert(std::is_base_of_v<Control, TControl>, "TControl is not a control class.");
- TControl* control = TControl::Create(std::forward<Args>(args)...);
- control->GetLayoutParams()->padding = padding;
- control->GetLayoutParams()->margin = margin;
- return control;
- }
+#include "ui_base.hpp"
- template <typename TControl, typename... Args>
- TControl* CreateWithLayout(const LayoutSideParams& width, const LayoutSideParams& height, const Thickness& padding, const Thickness& margin, Args&&... args)
- {
- static_assert(std::is_base_of_v<Control, TControl>, "TControl is not a control class.");
- TControl* control = TControl::Create(std::forward<Args>(args)...);
- control->GetLayoutParams()->width = width;
- control->GetLayoutParams()->height = height;
- control->GetLayoutParams()->padding = padding;
- control->GetLayoutParams()->margin = margin;
- return control;
- }
+namespace cru::ui {
+class Window;
- using ControlList = std::initializer_list<Control*>;
+namespace render {
+class RenderObject;
}
+
+class Control : public Object {
+ friend class Window;
+
+ protected:
+ Control() = default;
+
+ public:
+ Control(const Control& other) = delete;
+ Control(Control&& other) = delete;
+ Control& operator=(const Control& other) = delete;
+ Control& operator=(Control&& other) = delete;
+ ~Control() override = default;
+
+ public:
+ virtual StringView GetControlType() const = 0;
+
+ //*************** region: tree ***************
+ public:
+ // Get the window if attached, otherwise, return nullptr.
+ Window* GetWindow() const { return window_; }
+
+ Control* GetParent() const { return parent_; }
+
+ virtual const std::vector<Control*>& GetChildren() const = 0;
+
+ // Traverse the tree rooted the control including itself.
+ void TraverseDescendants(const std::function<void(Control*)>& predicate);
+
+ void _SetParent(Control* parent);
+ void _SetDescendantWindow(Window* window);
+
+ private:
+ static void _TraverseDescendants(
+ Control* control, const std::function<void(Control*)>& predicate);
+
+ public:
+ virtual render::RenderObject* GetRenderObject() const = 0;
+
+ //*************** region: focus ***************
+ public:
+ bool RequestFocus();
+
+ bool HasFocus();
+
+ //*************** region: events ***************
+ public:
+ // Raised when mouse enter the control.
+ events::RoutedEvent<events::MouseEventArgs>* MouseEnterEvent() {
+ return &mouse_enter_event_;
+ }
+ // Raised when mouse is leave the control.
+ events::RoutedEvent<events::MouseEventArgs>* MouseLeaveEvent() {
+ return &mouse_leave_event_;
+ }
+ // Raised when mouse is move in the control.
+ events::RoutedEvent<events::MouseEventArgs>* MouseMoveEvent() {
+ return &mouse_move_event_;
+ }
+ // Raised when a mouse button is pressed in the control.
+ events::RoutedEvent<events::MouseButtonEventArgs>* MouseDownEvent() {
+ return &mouse_down_event_;
+ }
+ // Raised when a mouse button is released in the control.
+ events::RoutedEvent<events::MouseButtonEventArgs>* MouseUpEvent() {
+ return &mouse_up_event_;
+ }
+ // Raised when a mouse button is pressed in the control and released in the
+ // control with mouse not leaving it between two operations.
+ events::RoutedEvent<events::MouseButtonEventArgs>* MouseClickEvent() {
+ return &mouse_click_event_;
+ }
+ events::RoutedEvent<events::MouseWheelEventArgs>* MouseWheelEvent() {
+ return &mouse_wheel_event_;
+ }
+ events::RoutedEvent<events::KeyEventArgs>* KeyDownEvent() {
+ return &key_down_event_;
+ }
+ events::RoutedEvent<events::KeyEventArgs>* KeyUpEvent() {
+ return &key_up_event_;
+ }
+ events::RoutedEvent<events::CharEventArgs>* CharEvent() {
+ return &char_event_;
+ }
+ events::RoutedEvent<events::FocusChangeEventArgs>* GainFocusEvent() {
+ return &gain_focus_event_;
+ }
+ events::RoutedEvent<events::FocusChangeEventArgs>* LoseFocusEvent() {
+ return &lose_focus_event_;
+ }
+
+ private:
+ events::RoutedEvent<events::MouseEventArgs> mouse_enter_event_;
+ events::RoutedEvent<events::MouseEventArgs> mouse_leave_event_;
+ events::RoutedEvent<events::MouseEventArgs> mouse_move_event_;
+ events::RoutedEvent<events::MouseButtonEventArgs> mouse_down_event_;
+ events::RoutedEvent<events::MouseButtonEventArgs> mouse_up_event_;
+ events::RoutedEvent<events::MouseButtonEventArgs> mouse_click_event_;
+ events::RoutedEvent<events::MouseWheelEventArgs> mouse_wheel_event_;
+
+ events::RoutedEvent<events::KeyEventArgs> key_down_event_;
+ events::RoutedEvent<events::KeyEventArgs> key_up_event_;
+ events::RoutedEvent<events::CharEventArgs> char_event_;
+
+ events::RoutedEvent<events::FocusChangeEventArgs> gain_focus_event_;
+ events::RoutedEvent<events::FocusChangeEventArgs> lose_focus_event_;
+
+ //*************** region: tree ***************
+ protected:
+ virtual void OnParentChanged(Control* old_parent, Control* new_parent);
+ virtual void OnAttachToWindow(Window* window);
+ virtual void OnDetachToWindow(Window* window);
+
+ //*************** region: additional mouse event ***************
+ protected:
+ virtual void OnMouseClickBegin(MouseButton button);
+ virtual void OnMouseClickEnd(MouseButton button);
+
+ private:
+ Window* window_ = nullptr;
+ Control* parent_ = nullptr;
+};
+} // namespace cru::ui
diff --git a/src/ui/controls/button.cpp b/src/ui/controls/button.cpp
index d4537f54..a6578251 100644
--- a/src/ui/controls/button.cpp
+++ b/src/ui/controls/button.cpp
@@ -1,34 +1,24 @@
#include "button.hpp"
-#include "graph/graph.hpp"
+#include "ui/render/border_render_object.hpp"
#include "ui/ui_manager.hpp"
-namespace cru::ui::controls
-{
- Button::Button() :
- normal_border_{UiManager::GetInstance()->GetPredefineResources()->button_normal_border},
- pressed_border_{UiManager::GetInstance()->GetPredefineResources()->button_press_border}
- {
- SetBordered(true);
- GetBorderProperty() = normal_border_;
-
- SetCursor(cursors::hand);
- }
-
- StringView Button::GetControlType() const
- {
- return control_type;
- }
+namespace cru::ui::controls {
+Button::Button() {
+ const auto predefined_resource =
+ UiManager::GetInstance()->GetPredefineResources();
+ render_object_.reset(new render::BorderRenderObject(
+ predefined_resource->button_normal_border_brush));
+ render_object_->SetEnabled(true);
+ render_object_->SetBorderWidth(Thickness{3});
+ render_object_->SetCornerRadius(render::CornerRadius{Point{10, 5}});
+}
- void Button::OnMouseClickBegin(MouseButton button)
- {
- GetBorderProperty() = pressed_border_;
- UpdateBorder();
- }
+render::RenderObject* Button::GetRenderObject() const { return render_object_.get(); }
- void Button::OnMouseClickEnd(MouseButton button)
- {
- GetBorderProperty() = normal_border_;
- UpdateBorder();
- }
+void Button::OnChildChanged(Control* old_child, Control* new_child) {
+ if (old_child != nullptr) render_object_->RemoveChild(0);
+ if (new_child != nullptr)
+ render_object_->AddChild(new_child->GetRenderObject(), 0);
}
+} // namespace cru::ui::controls
diff --git a/src/ui/controls/button.hpp b/src/ui/controls/button.hpp
index 82694fe8..3f313dfc 100644
--- a/src/ui/controls/button.hpp
+++ b/src/ui/controls/button.hpp
@@ -1,44 +1,39 @@
#pragma once
-
-// ReSharper disable once CppUnusedIncludeDirective
#include "pre.hpp"
-#include <initializer_list>
+#include <memory>
-#include "ui/control.hpp"
+#include "ui/content_control.hpp"
-namespace cru::ui::controls
-{
- class Button : public SingleChildControl
- {
- public:
- static constexpr auto control_type = L"Button";
+namespace cru::ui::render {
+class BorderRenderObject;
+}
- static Button* Create(Control* child = nullptr)
- {
- const auto button = new Button();
- button->SetChild(child);
- return button;
- }
+namespace cru::ui::controls {
+class Button : public ContentControl {
+ public:
+ static constexpr auto control_type = L"Button";
- protected:
- Button();
+ static Button* Create() { return new Button(); }
- public:
- Button(const Button& other) = delete;
- Button(Button&& other) = delete;
- Button& operator=(const Button& other) = delete;
- Button& operator=(Button&& other) = delete;
- ~Button() override = default;
+ protected:
+ Button();
- StringView GetControlType() const override final;
+ public:
+ Button(const Button& other) = delete;
+ Button(Button&& other) = delete;
+ Button& operator=(const Button& other) = delete;
+ Button& operator=(Button&& other) = delete;
+ ~Button() override = default;
- protected:
- void OnMouseClickBegin(MouseButton button) override final;
- void OnMouseClickEnd(MouseButton button) override final;
+ StringView GetControlType() const override final { return control_type; }
- private:
- BorderProperty normal_border_;
- BorderProperty pressed_border_;
- };
-}
+ render::RenderObject* GetRenderObject() const override;
+
+ protected:
+ void OnChildChanged(Control* old_child, Control* new_child) override;
+
+ private:
+ std::shared_ptr<render::BorderRenderObject> render_object_{};
+};
+} // namespace cru::ui::controls
diff --git a/src/ui/controls/flex_layout.cpp b/src/ui/controls/flex_layout.cpp
new file mode 100644
index 00000000..bdbe2d73
--- /dev/null
+++ b/src/ui/controls/flex_layout.cpp
@@ -0,0 +1,21 @@
+#include "flex_layout.hpp"
+
+#include "ui/render/flex_layout_render_object.hpp"
+
+namespace cru::ui::controls {
+using render::FlexLayoutRenderObject;
+
+FlexLayout::FlexLayout() { render_object_.reset(new FlexLayoutRenderObject()); }
+
+render::RenderObject* FlexLayout::GetRenderObject() const {
+ return render_object_.get();
+}
+
+void FlexLayout::OnAddChild(Control* child, int position) {
+ render_object_->AddChild(child->GetRenderObject(), position);
+}
+
+void FlexLayout::OnRemoveChild(Control* child, int position) {
+ render_object_->RemoveChild(position);
+}
+} // namespace cru::ui::controls
diff --git a/src/ui/controls/flex_layout.hpp b/src/ui/controls/flex_layout.hpp
new file mode 100644
index 00000000..9ceef1f6
--- /dev/null
+++ b/src/ui/controls/flex_layout.hpp
@@ -0,0 +1,41 @@
+#pragma once
+#include "pre.hpp"
+
+#include <memory>
+
+#include "ui/layout_control.hpp"
+
+namespace cru::ui::render {
+class FlexLayoutRenderObject;
+}
+
+namespace cru::ui::controls {
+
+class FlexLayout : public LayoutControl {
+ public:
+ static constexpr auto control_type = L"FlexLayout";
+
+ static FlexLayout* Create() { return new FlexLayout(); }
+
+ protected:
+ FlexLayout();
+
+ public:
+ FlexLayout(const FlexLayout& other) = delete;
+ FlexLayout(FlexLayout&& other) = delete;
+ FlexLayout& operator=(const FlexLayout& other) = delete;
+ FlexLayout& operator=(FlexLayout&& other) = delete;
+ ~FlexLayout() override = default;
+
+ StringView GetControlType() const override final { return control_type; }
+
+ render::RenderObject* GetRenderObject() const override;
+
+ protected:
+ void OnAddChild(Control* child, int position) override;
+ void OnRemoveChild(Control* child, int position) override;
+
+ private:
+ std::shared_ptr<render::FlexLayoutRenderObject> render_object_;
+};
+} // namespace cru::ui::controls
diff --git a/src/ui/controls/frame_layout.cpp b/src/ui/controls/frame_layout.cpp
deleted file mode 100644
index d68bc338..00000000
--- a/src/ui/controls/frame_layout.cpp
+++ /dev/null
@@ -1,70 +0,0 @@
-#include "frame_layout.hpp"
-
-namespace cru::ui::controls
-{
- FrameLayout::FrameLayout() = default;
-
- FrameLayout::~FrameLayout() = default;
-
- StringView FrameLayout::GetControlType() const
- {
- return control_type;
- }
-
- Size FrameLayout::OnMeasureContent(const Size& available_size, const AdditionalMeasureInfo& additional_info)
- {
- auto max_child_size = Size::Zero();
- for (auto control: GetChildren())
- {
- control->Measure(available_size, additional_info);
- const auto&& size = control->GetDesiredSize();
- if (max_child_size.width < size.width)
- max_child_size.width = size.width;
- if (max_child_size.height < size.height)
- max_child_size.height = size.height;
- }
-
- // coerce size fro stretch.
- for (auto control: GetChildren())
- {
- auto size = control->GetDesiredSize();
- const auto layout_params = control->GetLayoutParams();
- if (layout_params->width.mode == MeasureMode::Stretch)
- size.width = max_child_size.width;
- if (layout_params->height.mode == MeasureMode::Stretch)
- size.height = max_child_size.height;
- control->SetDesiredSize(size);
- }
-
- return max_child_size;
- }
-
- void FrameLayout::OnLayoutContent(const Rect& rect, const AdditionalLayoutInfo& additional_info)
- {
- for (auto control: GetChildren())
- {
- const auto layout_params = control->GetLayoutParams();
- const auto size = control->GetDesiredSize();
-
- auto&& calculate_anchor = [](const float anchor, const Alignment alignment, const float layout_length, const float control_length) -> float
- {
- switch (alignment)
- {
- case Alignment::Center:
- return anchor + (layout_length - control_length) / 2;
- case Alignment::Start:
- return anchor;
- case Alignment::End:
- return anchor + layout_length - control_length;
- default:
- UnreachableCode();
- }
- };
-
- control->Layout(Rect(Point(
- calculate_anchor(rect.left, layout_params->width.alignment, rect.width, size.width),
- calculate_anchor(rect.top, layout_params->height.alignment, rect.height, size.height)
- ), size), additional_info);
- }
- }
-}
diff --git a/src/ui/controls/frame_layout.hpp b/src/ui/controls/frame_layout.hpp
deleted file mode 100644
index c2d6f0d6..00000000
--- a/src/ui/controls/frame_layout.hpp
+++ /dev/null
@@ -1,40 +0,0 @@
-#pragma once
-
-// ReSharper disable once CppUnusedIncludeDirective
-#include "pre.hpp"
-
-#include <initializer_list>
-
-#include "ui/control.hpp"
-
-namespace cru::ui::controls
-{
- class FrameLayout : public MultiChildControl
- {
- public:
- static constexpr auto control_type = L"FrameLayout";
-
- static FrameLayout* Create(const std::initializer_list<Control*>& children = std::initializer_list<Control*>{})
- {
- const auto layout = new FrameLayout();
- for (auto child : children)
- layout->AddChild(child);
- return layout;
- }
-
- protected:
- FrameLayout();
- public:
- FrameLayout(const FrameLayout& other) = delete;
- FrameLayout(FrameLayout&& other) = delete;
- FrameLayout& operator=(const FrameLayout& other) = delete;
- FrameLayout& operator=(FrameLayout&& other) = delete;
- ~FrameLayout() override;
-
- StringView GetControlType() const override final;
-
- protected:
- Size OnMeasureContent(const Size& available_size, const AdditionalMeasureInfo& additional_info) override;
- void OnLayoutContent(const Rect& rect, const AdditionalLayoutInfo& additional_info) override;
- };
-}
diff --git a/src/ui/controls/linear_layout.cpp b/src/ui/controls/linear_layout.cpp
deleted file mode 100644
index d3fdc9b5..00000000
--- a/src/ui/controls/linear_layout.cpp
+++ /dev/null
@@ -1,151 +0,0 @@
-#include "linear_layout.hpp"
-
-#include <algorithm>
-
-#include "math_util.hpp"
-
-namespace cru::ui::controls
-{
- LinearLayout::LinearLayout(const Orientation orientation)
- : orientation_(orientation)
- {
-
- }
-
- StringView LinearLayout::GetControlType() const
- {
- return control_type;
- }
-
- Size LinearLayout::OnMeasureContent(const Size& available_size, const AdditionalMeasureInfo& additional_info)
- {
- auto actual_size_for_children = Size::Zero();
-
- float secondary_side_child_max_length = 0;
-
- std::list<Control*> stretch_control_list;
-
- // First measure Content and Exactly and count Stretch.
- if (orientation_ == Orientation::Horizontal)
- for(auto control: GetInternalChildren())
- {
- const auto mode = control->GetLayoutParams()->width.mode;
- if (mode == MeasureMode::Content || mode == MeasureMode::Exactly)
- {
- Size current_available_size(AtLeast0(available_size.width - actual_size_for_children.width), available_size.height);
- control->Measure(current_available_size, additional_info);
- const auto size = control->GetDesiredSize();
- actual_size_for_children.width += size.width;
- secondary_side_child_max_length = std::max(size.height, secondary_side_child_max_length);
- }
- else
- stretch_control_list.push_back(control);
- }
- else
- for(auto control: GetInternalChildren())
- {
- const auto mode = control->GetLayoutParams()->height.mode;
- if (mode == MeasureMode::Content || mode == MeasureMode::Exactly)
- {
- Size current_available_size(available_size.width, AtLeast0(available_size.height - actual_size_for_children.height));
- control->Measure(current_available_size, additional_info);
- const auto size = control->GetDesiredSize();
- actual_size_for_children.height += size.height;
- secondary_side_child_max_length = std::max(size.width, secondary_side_child_max_length);
- }
- else
- stretch_control_list.push_back(control);
- }
-
- if (orientation_ == Orientation::Horizontal)
- {
- const auto available_width = AtLeast0(available_size.width - actual_size_for_children.width) / stretch_control_list.size();
- for (const auto control : stretch_control_list)
- {
- control->Measure(Size(available_width, available_size.height), additional_info);
- const auto size = control->GetDesiredSize();
- actual_size_for_children.width += size.width;
- secondary_side_child_max_length = std::max(size.height, secondary_side_child_max_length);
- }
- }
- else
- {
- const auto available_height = AtLeast0(available_size.height - actual_size_for_children.height) / stretch_control_list.size();
- for (const auto control : stretch_control_list)
- {
- control->Measure(Size(available_size.width, available_height), additional_info);
- const auto size = control->GetDesiredSize();
- actual_size_for_children.height += size.height;
- secondary_side_child_max_length = std::max(size.width, secondary_side_child_max_length);
- }
- }
-
- if (orientation_ == Orientation::Horizontal)
- {
- for (auto control : GetInternalChildren())
- {
- if (control->GetLayoutParams()->height.mode == MeasureMode::Stretch)
- {
- control->SetDesiredSize(Size(control->GetDesiredSize().width, secondary_side_child_max_length));
- }
- }
- actual_size_for_children.height = secondary_side_child_max_length;
- }
- else
- {
- for (auto control : GetInternalChildren())
- {
- if (control->GetLayoutParams()->width.mode == MeasureMode::Stretch)
- {
- control->SetDesiredSize(Size(secondary_side_child_max_length, control->GetDesiredSize().height));
- }
- }
-
- actual_size_for_children.width = secondary_side_child_max_length;
- }
-
- return actual_size_for_children;
- }
-
- void LinearLayout::OnLayoutContent(const Rect& rect, const AdditionalLayoutInfo& additional_info)
- {
- float current_main_side_anchor = 0;
- for(auto control: GetInternalChildren())
- {
- const auto layout_params = control->GetLayoutParams();
- const auto size = control->GetDesiredSize();
- const auto alignment = orientation_ == Orientation::Horizontal ? layout_params->height.alignment : layout_params->width.alignment;
-
- auto&& calculate_secondary_side_anchor = [alignment](const float layout_length, const float control_length) -> float
- {
- switch (alignment)
- {
- case Alignment::Center:
- return (layout_length - control_length) / 2;
- case Alignment::Start:
- return 0;
- case Alignment::End:
- return layout_length - control_length;
- default:
- UnreachableCode();
- }
- };
-
- auto&& calculate_rect = [rect, size](const float anchor_left, const float anchor_top)
- {
- return Rect(Point(rect.left + anchor_left, rect.top + anchor_top), size);
- };
-
- if (orientation_ == Orientation::Horizontal)
- {
- control->Layout(calculate_rect(current_main_side_anchor, calculate_secondary_side_anchor(rect.height, size.height)), additional_info);
- current_main_side_anchor += size.width;
- }
- else
- {
- control->Layout(calculate_rect(calculate_secondary_side_anchor(rect.width, size.width), current_main_side_anchor), additional_info);
- current_main_side_anchor += size.height;
- }
- }
- }
-}
diff --git a/src/ui/controls/linear_layout.hpp b/src/ui/controls/linear_layout.hpp
deleted file mode 100644
index ceb1c4e6..00000000
--- a/src/ui/controls/linear_layout.hpp
+++ /dev/null
@@ -1,50 +0,0 @@
-#pragma once
-
-// ReSharper disable once CppUnusedIncludeDirective
-#include "pre.hpp"
-
-#include "ui/control.hpp"
-
-namespace cru::ui::controls
-{
- // Min length of main side in layout params is of no meaning.
- // All children will layout from start and redundant length is blank.
- class LinearLayout : public MultiChildControl
- {
- public:
- static constexpr auto control_type = L"LinearLayout";
-
- enum class Orientation
- {
- Horizontal,
- Vertical
- };
-
- static LinearLayout* Create(const Orientation orientation = Orientation::Vertical, const std::initializer_list<Control*>& children = std::initializer_list<Control*>())
- {
- const auto linear_layout = new LinearLayout(orientation);
- for (const auto control : children)
- linear_layout->AddChild(control);
- return linear_layout;
- }
-
- protected:
- explicit LinearLayout(Orientation orientation = Orientation::Vertical);
-
- public:
- LinearLayout(const LinearLayout& other) = delete;
- LinearLayout(LinearLayout&& other) = delete;
- LinearLayout& operator=(const LinearLayout& other) = delete;
- LinearLayout& operator=(LinearLayout&& other) = delete;
- ~LinearLayout() override = default;
-
- StringView GetControlType() const override final;
-
- protected:
- Size OnMeasureContent(const Size& available_size, const AdditionalMeasureInfo& additional_info) override;
- void OnLayoutContent(const Rect& rect, const AdditionalLayoutInfo& additional_info) override;
-
- private:
- Orientation orientation_;
- };
-}
diff --git a/src/ui/controls/list_item.cpp b/src/ui/controls/list_item.cpp
deleted file mode 100644
index e0ca28a9..00000000
--- a/src/ui/controls/list_item.cpp
+++ /dev/null
@@ -1,63 +0,0 @@
-#include "list_item.hpp"
-
-#include "ui/ui_manager.hpp"
-#include "ui/convert_util.hpp"
-
-namespace cru::ui::controls
-{
- ListItem::ListItem()
- {
- const auto predefine_resources = UiManager::GetInstance()->GetPredefineResources();
-
- brushes_[State::Normal].border_brush = predefine_resources->list_item_normal_border_brush;
- brushes_[State::Normal].fill_brush = predefine_resources->list_item_normal_fill_brush;
- brushes_[State::Hover] .border_brush = predefine_resources->list_item_hover_border_brush;
- brushes_[State::Hover] .fill_brush = predefine_resources->list_item_hover_fill_brush;
- brushes_[State::Select].border_brush = predefine_resources->list_item_select_border_brush;
- brushes_[State::Select].fill_brush = predefine_resources->list_item_select_fill_brush;
-
- draw_foreground_event.AddHandler([this](events::DrawEventArgs& args)
- {
- const auto device_context = args.GetDeviceContext();
- const auto rect = Rect(Point::Zero(), GetRect(RectRange::Padding).GetSize());
- device_context->FillRectangle(Convert(rect), brushes_[state_].fill_brush.Get());
- device_context->DrawRectangle(Convert(rect.Shrink(Thickness(0.5))), brushes_[state_].border_brush.Get(), 1);
- });
-
- mouse_enter_event.direct.AddHandler([this](events::MouseEventArgs& args)
- {
- if (GetState() == State::Select)
- return;
-
- if (IsAnyMouseButtonDown())
- return;
-
- SetState(State::Hover);
- });
-
- mouse_leave_event.direct.AddHandler([this](events::MouseEventArgs& args)
- {
- if (GetState() == State::Select)
- return;
-
- SetState(State::Normal);
- });
-
- mouse_click_event.direct.AddHandler([this](events::MouseButtonEventArgs& args)
- {
- if (args.GetMouseButton() == MouseButton::Left)
- SetState(State::Select);
- });
- }
-
- StringView ListItem::GetControlType() const
- {
- return control_type;
- }
-
- void ListItem::SetState(const State state)
- {
- state_ = state;
- InvalidateDraw();
- }
-}
diff --git a/src/ui/controls/list_item.hpp b/src/ui/controls/list_item.hpp
deleted file mode 100644
index a50b2496..00000000
--- a/src/ui/controls/list_item.hpp
+++ /dev/null
@@ -1,62 +0,0 @@
-#pragma once
-
-// ReSharper disable once CppUnusedIncludeDirective
-#include "pre.hpp"
-
-#include <map>
-#include <initializer_list>
-
-#include "ui/control.hpp"
-
-namespace cru::ui::controls
-{
- class ListItem : public SingleChildControl
- {
- public:
- static constexpr auto control_type = L"ListItem";
-
- enum class State
- {
- Normal,
- Hover,
- Select
- };
-
- private:
- struct StateBrush
- {
- Microsoft::WRL::ComPtr<ID2D1Brush> border_brush;
- Microsoft::WRL::ComPtr<ID2D1Brush> fill_brush;
- };
-
- public:
- static ListItem* Create(Control* child = nullptr)
- {
- const auto list_item = new ListItem();
- list_item->SetChild(child);
- return list_item;
- }
-
- private:
- ListItem();
- public:
- ListItem(const ListItem& other) = delete;
- ListItem(ListItem&& other) = delete;
- ListItem& operator=(const ListItem& other) = delete;
- ListItem& operator=(ListItem&& other) = delete;
- ~ListItem() override = default;
-
- StringView GetControlType() const override;
-
- State GetState() const
- {
- return state_;
- }
-
- void SetState(State state);
-
- private:
- State state_ = State::Normal;
- std::map<State, StateBrush> brushes_{};
- };
-}
diff --git a/src/ui/controls/popup_menu.cpp b/src/ui/controls/popup_menu.cpp
deleted file mode 100644
index fbe9039d..00000000
--- a/src/ui/controls/popup_menu.cpp
+++ /dev/null
@@ -1,58 +0,0 @@
-#include "popup_menu.hpp"
-
-#include "ui/window.hpp"
-#include "text_block.hpp"
-#include "list_item.hpp"
-#include "linear_layout.hpp"
-#include "ui/events/ui_event.hpp"
-
-namespace cru::ui::controls
-{
- Window* CreatePopupMenu(const Point& anchor, const std::vector<MenuItemInfo>& items, Window* parent)
- {
- const auto popup = Window::CreatePopup(parent);
-
- popup->lose_focus_event.bubble.AddHandler([popup](events::FocusChangeEventArgs& args)
- {
- if (args.IsWindow())
- popup->Close();
- });
-
- const auto create_menu_item = [popup](const String& text, const std::function<void()>& action) -> ListItem*
- {
- auto text_block = TextBlock::Create(text);
- text_block->GetLayoutParams()->width.alignment = Alignment::Start;
-
- auto list_item = CreateWithLayout<ListItem>(
- LayoutSideParams::Stretch(Alignment::Center),
- LayoutSideParams::Content(Alignment::Start),
- text_block
- );
-
- list_item->mouse_click_event.bubble.AddHandler([popup, action](events::MouseButtonEventArgs& args)
- {
- if (args.GetMouseButton() == MouseButton::Left)
- {
- action();
- popup->Close();
- }
- });
-
- return list_item;
- };
-
- const auto menu = LinearLayout::Create(LinearLayout::Orientation::Vertical);
-
- menu->SetBordered(true);
-
- for (const auto& item : items)
- menu->AddChild(create_menu_item(item.first, item.second));
-
- popup->SetChild(menu);
-
- popup->SetSizeFitContent();
- popup->SetWindowPosition(anchor);
-
- return popup;
- }
-}
diff --git a/src/ui/controls/popup_menu.hpp b/src/ui/controls/popup_menu.hpp
deleted file mode 100644
index a2916590..00000000
--- a/src/ui/controls/popup_menu.hpp
+++ /dev/null
@@ -1,23 +0,0 @@
-#pragma once
-
-// ReSharper disable once CppUnusedIncludeDirective
-#include "pre.hpp"
-
-#include <vector>
-#include <utility>
-#include <functional>
-
-#include "base.hpp"
-#include "ui/ui_base.hpp"
-
-namespace cru::ui
-{
- class Window;
-}
-
-namespace cru::ui::controls
-{
- using MenuItemInfo = std::pair<String, std::function<void()>>;
-
- Window* CreatePopupMenu(const Point& anchor, const std::vector<MenuItemInfo>& items, Window* parent = nullptr);
-}
diff --git a/src/ui/controls/scroll_control.cpp b/src/ui/controls/scroll_control.cpp
deleted file mode 100644
index 622b4e4c..00000000
--- a/src/ui/controls/scroll_control.cpp
+++ /dev/null
@@ -1,384 +0,0 @@
-#include "scroll_control.hpp"
-
-#include <limits>
-
-#include "cru_debug.hpp"
-#include "ui/convert_util.hpp"
-#include "exception.hpp"
-#include "math_util.hpp"
-#include "ui/ui_manager.hpp"
-#include "ui/window.hpp"
-
-namespace cru::ui::controls
-{
- constexpr auto scroll_bar_width = 15.0f;
-
- ScrollControl::ScrollControl(const bool container)
- {
- SetClipContent(true);
-
- draw_foreground_event.AddHandler([this](events::DrawEventArgs& args)
- {
- const auto device_context = args.GetDeviceContext();
- const auto predefined = UiManager::GetInstance()->GetPredefineResources();
-
- if (is_horizontal_scroll_bar_visible_)
- {
- device_context->FillRectangle(
- Convert(horizontal_bar_info_.border),
- predefined->scroll_bar_background_brush.Get()
- );
-
- device_context->FillRectangle(
- Convert(horizontal_bar_info_.bar),
- predefined->scroll_bar_brush.Get()
- );
-
- device_context->DrawLine(
- Convert(horizontal_bar_info_.border.GetLeftTop()),
- Convert(horizontal_bar_info_.border.GetRightTop()),
- predefined->scroll_bar_border_brush.Get()
- );
- }
-
- if (is_vertical_scroll_bar_visible_)
- {
- device_context->FillRectangle(
- Convert(vertical_bar_info_.border),
- predefined->scroll_bar_background_brush.Get()
- );
-
- device_context->FillRectangle(
- Convert(vertical_bar_info_.bar),
- predefined->scroll_bar_brush.Get()
- );
-
- device_context->DrawLine(
- Convert(vertical_bar_info_.border.GetLeftTop()),
- Convert(vertical_bar_info_.border.GetLeftBottom()),
- predefined->scroll_bar_border_brush.Get()
- );
- }
- });
-
- mouse_down_event.tunnel.AddHandler([this](events::MouseButtonEventArgs& args)
- {
- if (args.GetMouseButton() == MouseButton::Left)
- {
- const auto point = args.GetPoint(this);
- if (is_vertical_scroll_bar_visible_ && vertical_bar_info_.bar.IsPointInside(point))
- {
- GetWindow()->CaptureMouseFor(this);
- is_pressing_scroll_bar_ = Orientation::Vertical;
- pressing_delta_ = point.y - vertical_bar_info_.bar.top;
- args.SetHandled();
- return;
- }
-
- if (is_horizontal_scroll_bar_visible_ && horizontal_bar_info_.bar.IsPointInside(point))
- {
- GetWindow()->CaptureMouseFor(this);
- pressing_delta_ = point.x - horizontal_bar_info_.bar.left;
- is_pressing_scroll_bar_ = Orientation::Horizontal;
- args.SetHandled();
- return;
- }
- }
- });
-
- mouse_move_event.tunnel.AddHandler([this](events::MouseEventArgs& args)
- {
- const auto mouse_point = args.GetPoint(this);
-
- if (is_pressing_scroll_bar_ == Orientation::Horizontal)
- {
- const auto new_head_position = mouse_point.x - pressing_delta_;
- const auto new_offset = new_head_position / horizontal_bar_info_.border.width * view_width_;
- SetScrollOffset(new_offset, std::nullopt);
- args.SetHandled();
- return;
- }
-
- if (is_pressing_scroll_bar_ == Orientation::Vertical)
- {
- const auto new_head_position = mouse_point.y - pressing_delta_;
- const auto new_offset = new_head_position / vertical_bar_info_.border.height * view_height_;
- SetScrollOffset(std::nullopt, new_offset);
- args.SetHandled();
- return;
- }
- });
-
- mouse_up_event.tunnel.AddHandler([this](events::MouseButtonEventArgs& args)
- {
- if (args.GetMouseButton() == MouseButton::Left && is_pressing_scroll_bar_.has_value())
- {
- GetWindow()->ReleaseCurrentMouseCapture();
- is_pressing_scroll_bar_ = std::nullopt;
- args.SetHandled();
- }
- });
-
- mouse_wheel_event.bubble.AddHandler([this](events::MouseWheelEventArgs& args)
- {
- constexpr const auto view_delta = 30.0f;
-
- if (args.GetDelta() == 0.0f)
- return;
-
- const auto content_rect = GetRect(RectRange::Content);
- if (IsVerticalScrollEnabled() && GetScrollOffsetY() != (args.GetDelta() > 0.0f ? 0.0f : AtLeast0(GetViewHeight() - content_rect.height)))
- {
- SetScrollOffset(std::nullopt, GetScrollOffsetY() - args.GetDelta() / WHEEL_DELTA * view_delta);
- args.SetHandled();
- return;
- }
-
- if (IsHorizontalScrollEnabled() && GetScrollOffsetX() != (args.GetDelta() > 0.0f ? 0.0f : AtLeast0(GetViewWidth() - content_rect.width)))
- {
- SetScrollOffset(GetScrollOffsetX() - args.GetDelta() / WHEEL_DELTA * view_delta, std::nullopt);
- args.SetHandled();
- return;
- }
- });
- }
-
- ScrollControl::~ScrollControl()
- {
-
- }
-
- StringView ScrollControl::GetControlType() const
- {
- return control_type;
- }
-
- void ScrollControl::SetHorizontalScrollEnabled(const bool enable)
- {
- horizontal_scroll_enabled_ = enable;
- InvalidateLayout();
- InvalidateDraw();
- }
-
- void ScrollControl::SetVerticalScrollEnabled(const bool enable)
- {
- vertical_scroll_enabled_ = enable;
- InvalidateLayout();
- InvalidateDraw();
- }
-
- void ScrollControl::SetHorizontalScrollBarVisibility(const ScrollBarVisibility visibility)
- {
- if (visibility != horizontal_scroll_bar_visibility_)
- {
- horizontal_scroll_bar_visibility_ = visibility;
- switch (visibility)
- {
- case ScrollBarVisibility::Always:
- is_horizontal_scroll_bar_visible_ = true;
- break;
- case ScrollBarVisibility::None:
- is_horizontal_scroll_bar_visible_ = false;
- break;
- case ScrollBarVisibility::Auto:
- UpdateScrollBarVisibility();
- }
- InvalidateDraw();
- }
- }
-
- void ScrollControl::SetVerticalScrollBarVisibility(const ScrollBarVisibility visibility)
- {
- if (visibility != vertical_scroll_bar_visibility_)
- {
- vertical_scroll_bar_visibility_ = visibility;
- switch (visibility)
- {
- case ScrollBarVisibility::Always:
- is_vertical_scroll_bar_visible_ = true;
- break;
- case ScrollBarVisibility::None:
- is_vertical_scroll_bar_visible_ = false;
- break;
- case ScrollBarVisibility::Auto:
- UpdateScrollBarVisibility();
- }
- InvalidateDraw();
- }
-
- }
-
- void ScrollControl::SetScrollOffset(std::optional<float> x, std::optional<float> y)
- {
- CoerceAndSetOffsets(x.value_or(GetScrollOffsetX()), y.value_or(GetScrollOffsetY()));
- }
-
- void ScrollControl::SetViewWidth(const float length)
- {
- view_width_ = length;
- }
-
- void ScrollControl::SetViewHeight(const float length)
- {
- view_height_ = length;
- }
-
- Size ScrollControl::OnMeasureContent(const Size& available_size, const AdditionalMeasureInfo& additional_info)
- {
- const auto layout_params = GetLayoutParams();
-
- auto available_size_for_children = available_size;
- if (IsHorizontalScrollEnabled())
- {
- if (layout_params->width.mode == MeasureMode::Content)
- debug::DebugMessage(L"ScrollControl: Width measure mode is Content and horizontal scroll is enabled. So Stretch is used instead.");
-
- available_size_for_children.width = std::numeric_limits<float>::max();
- }
-
- if (IsVerticalScrollEnabled())
- {
- if (layout_params->height.mode == MeasureMode::Content)
- debug::DebugMessage(L"ScrollControl: Height measure mode is Content and vertical scroll is enabled. So Stretch is used instead.");
-
- available_size_for_children.height = std::numeric_limits<float>::max();
- }
-
- const auto child = GetChild();
-
- auto size = Size::Zero();
- if (child)
- {
- child->Measure(available_size_for_children, AdditionalMeasureInfo{false, false});
- size = child->GetDesiredSize();
- }
-
-
- auto result = size;
- if (IsHorizontalScrollEnabled())
- {
- SetViewWidth(size.width);
- result.width = available_size.width;
- }
- if (IsVerticalScrollEnabled())
- {
- SetViewHeight(size.height);
- result.height = available_size.height;
- }
-
- return result;
- }
-
- void ScrollControl::OnLayoutContent(const Rect& rect, const AdditionalLayoutInfo& additional_info)
- {
- auto layout_rect = rect;
-
- if (IsHorizontalScrollEnabled())
- layout_rect.width = GetViewWidth();
- if (IsVerticalScrollEnabled())
- layout_rect.height = GetViewHeight();
-
- const auto child = GetChild();
-
- if (child)
- {
- const auto layout_params = child->GetLayoutParams();
- const auto size = child->GetDesiredSize();
-
- auto&& calculate_anchor = [](const float anchor, const Alignment alignment, const float layout_length, const float control_length) -> float
- {
- switch (alignment)
- {
- case Alignment::Center:
- return anchor + (layout_length - control_length) / 2;
- case Alignment::Start:
- return anchor;
- case Alignment::End:
- return anchor + layout_length - control_length;
- default:
- UnreachableCode();
- }
- };
-
- child->Layout(Rect(Point(
- IsHorizontalScrollEnabled() ? layout_rect.left + offset_x_ : calculate_anchor(layout_rect.left, layout_params->width.alignment, layout_rect.width, size.width),
- IsVerticalScrollEnabled() ? layout_rect.top + offset_y_ : calculate_anchor(layout_rect.top, layout_params->height.alignment, layout_rect.height, size.height)
- ), size), additional_info);
- }
- }
-
- void ScrollControl::OnRectChange(const Rect& old_rect, const Rect& new_rect)
- {
- UpdateScrollBarBorderInfo();
- CoerceAndSetOffsets(offset_x_, offset_y_, false);
- UpdateScrollBarVisibility();
- }
-
- void ScrollControl::CoerceAndSetOffsets(const float offset_x, const float offset_y, const bool update_children)
- {
- const auto old_offset_x = offset_x_;
- const auto old_offset_y = offset_y_;
-
- const auto content_rect = GetRect(RectRange::Content);
- offset_x_ = Coerce(offset_x, 0.0f, AtLeast0(view_width_ - content_rect.width));
- offset_y_ = Coerce(offset_y, 0.0f, AtLeast0(view_height_ - content_rect.height));
- UpdateScrollBarBarInfo();
-
- if (update_children)
- {
- if (const auto child = GetChild())
- {
- const auto old_position = child->GetOffset();
- child->SetRect(Rect(Point(
- old_position.x + old_offset_x - offset_x_,
- old_position.y + old_offset_y - offset_y_
- ), child->GetSize()));
- child->RefreshDescendantPositionCache();
- }
- }
- InvalidateDraw();
- }
-
- void ScrollControl::UpdateScrollBarVisibility()
- {
- const auto content_rect = GetRect(RectRange::Content);
- if (GetHorizontalScrollBarVisibility() == ScrollBarVisibility::Auto)
- is_horizontal_scroll_bar_visible_ = view_width_ > content_rect.width;
- if (GetVerticalScrollBarVisibility() == ScrollBarVisibility::Auto)
- is_vertical_scroll_bar_visible_ = view_height_ > content_rect.height;
- }
-
- void ScrollControl::UpdateScrollBarBorderInfo()
- {
- const auto content_rect = GetRect(RectRange::Content);
- horizontal_bar_info_.border = Rect(content_rect.left, content_rect.GetBottom() - scroll_bar_width, content_rect.width, scroll_bar_width);
- vertical_bar_info_.border = Rect(content_rect.GetRight() - scroll_bar_width , content_rect.top, scroll_bar_width, content_rect.height);
- }
-
- void ScrollControl::UpdateScrollBarBarInfo()
- {
- const auto content_rect = GetRect(RectRange::Content);
- {
- const auto& border = horizontal_bar_info_.border;
- if (view_width_ <= content_rect.width)
- horizontal_bar_info_.bar = border;
- else
- {
- const auto bar_length = border.width * content_rect.width / view_width_;
- const auto offset = border.width * offset_x_ / view_width_;
- horizontal_bar_info_.bar = Rect(border.left + offset, border.top, bar_length, border.height);
- }
- }
- {
- const auto& border = vertical_bar_info_.border;
- if (view_height_ <= content_rect.height)
- vertical_bar_info_.bar = border;
- else
- {
- const auto bar_length = border.height * content_rect.height / view_height_;
- const auto offset = border.height * offset_y_ / view_height_;
- vertical_bar_info_.bar = Rect(border.left, border.top + offset, border.width, bar_length);
- }
- }
- }
-}
diff --git a/src/ui/controls/scroll_control.hpp b/src/ui/controls/scroll_control.hpp
deleted file mode 100644
index 7138add6..00000000
--- a/src/ui/controls/scroll_control.hpp
+++ /dev/null
@@ -1,152 +0,0 @@
-#pragma once
-
-// ReSharper disable once CppUnusedIncludeDirective
-#include "pre.hpp"
-
-#include <optional>
-#include <initializer_list>
-
-#include "ui/control.hpp"
-
-namespace cru::ui::controls
-{
- // Done: OnMeasureContent
- // Done: OnLayoutContent
- // Done: HitTest(no need)
- // Done: Draw(no need)
- // Done: API
- // Done: ScrollBar
- // Done: MouseEvent
- class ScrollControl : public SingleChildControl
- {
- private:
- struct ScrollBarInfo
- {
- Rect border = Rect();
- Rect bar = Rect();
- };
-
- enum class Orientation
- {
- Horizontal,
- Vertical
- };
-
- public:
- enum class ScrollBarVisibility
- {
- None,
- Auto,
- Always
- };
-
- static ScrollControl* Create(Control* child = nullptr)
- {
- const auto control = new ScrollControl(true);
- control->SetChild(child);
- return control;
- }
-
- static constexpr auto control_type = L"ScrollControl";
-
- protected:
- explicit ScrollControl(bool container);
- public:
- ScrollControl(const ScrollControl& other) = delete;
- ScrollControl(ScrollControl&& other) = delete;
- ScrollControl& operator=(const ScrollControl& other) = delete;
- ScrollControl& operator=(ScrollControl&& other) = delete;
- ~ScrollControl() override;
-
- StringView GetControlType() const override final;
-
- bool IsHorizontalScrollEnabled() const
- {
- return horizontal_scroll_enabled_;
- }
-
- void SetHorizontalScrollEnabled(bool enable);
-
- bool IsVerticalScrollEnabled() const
- {
- return vertical_scroll_enabled_;
- }
-
- void SetVerticalScrollEnabled(bool enable);
-
-
- ScrollBarVisibility GetHorizontalScrollBarVisibility() const
- {
- return horizontal_scroll_bar_visibility_;
- }
-
- void SetHorizontalScrollBarVisibility(ScrollBarVisibility visibility);
-
- ScrollBarVisibility GetVerticalScrollBarVisibility() const
- {
- return vertical_scroll_bar_visibility_;
- }
-
- void SetVerticalScrollBarVisibility(ScrollBarVisibility visibility);
-
- float GetViewWidth() const
- {
- return view_width_;
- }
-
- float GetViewHeight() const
- {
- return view_height_;
- }
-
- float GetScrollOffsetX() const
- {
- return offset_x_;
- }
-
- float GetScrollOffsetY() const
- {
- return offset_y_;
- }
-
- // nullopt for not set. value is auto-coerced.
- void SetScrollOffset(std::optional<float> x, std::optional<float> y);
-
- protected:
- void SetViewWidth(float length);
- void SetViewHeight(float length);
-
- Size OnMeasureContent(const Size& available_size, const AdditionalMeasureInfo& additional_info) override final;
- void OnLayoutContent(const Rect& rect, const AdditionalLayoutInfo& additional_info) override final;
-
- void OnRectChange(const Rect& old_rect, const Rect& new_rect) override;
-
- private:
- void CoerceAndSetOffsets(float offset_x, float offset_y, bool update_children = true);
- void UpdateScrollBarVisibility();
- void UpdateScrollBarBorderInfo();
- void UpdateScrollBarBarInfo();
-
- private:
- bool horizontal_scroll_enabled_ = true;
- bool vertical_scroll_enabled_ = true;
-
- ScrollBarVisibility horizontal_scroll_bar_visibility_ = ScrollBarVisibility::Auto;
- ScrollBarVisibility vertical_scroll_bar_visibility_ = ScrollBarVisibility::Auto;
-
- bool is_horizontal_scroll_bar_visible_ = false;
- bool is_vertical_scroll_bar_visible_ = false;
-
- float offset_x_ = 0.0f;
- float offset_y_ = 0.0f;
-
- float view_width_ = 0.0f;
- float view_height_ = 0.0f;
-
- ScrollBarInfo horizontal_bar_info_;
- ScrollBarInfo vertical_bar_info_;
-
- std::optional<Orientation> is_pressing_scroll_bar_ = std::nullopt;
- float pressing_delta_ = 0.0f;
- };
-}
diff --git a/src/ui/controls/text_block.cpp b/src/ui/controls/text_block.cpp
index 8276ce4e..c2f8cd8e 100644
--- a/src/ui/controls/text_block.cpp
+++ b/src/ui/controls/text_block.cpp
@@ -1,19 +1,25 @@
#include "text_block.hpp"
+#include "ui/render/text_render_object.hpp"
#include "ui/ui_manager.hpp"
-namespace cru::ui::controls
-{
- TextBlock::TextBlock() : TextControl(
- UiManager::GetInstance()->GetPredefineResources()->text_block_text_format,
- UiManager::GetInstance()->GetPredefineResources()->text_block_text_brush
- )
- {
+namespace cru::ui::controls {
+using render::TextRenderObject;
- }
+TextBlock::TextBlock() {
+ const auto predefined_resources =
+ UiManager::GetInstance()->GetPredefineResources();
+ render_object_.reset(
+ new TextRenderObject(predefined_resources->text_block_text_brush,
+ predefined_resources->text_block_text_format,
+ predefined_resources->text_block_selection_brush));
+}
- StringView TextBlock::GetControlType() const
- {
- return control_type;
- }
+render::RenderObject* TextBlock::GetRenderObject() const {
+ return render_object_.get();
}
+
+String TextBlock::GetText() const { return render_object_->GetText(); }
+
+void TextBlock::SetText(const String& text) { render_object_->SetText(text); }
+} // namespace cru::ui::controls
diff --git a/src/ui/controls/text_block.hpp b/src/ui/controls/text_block.hpp
index 66f5defa..0d65dd67 100644
--- a/src/ui/controls/text_block.hpp
+++ b/src/ui/controls/text_block.hpp
@@ -1,35 +1,39 @@
#pragma once
-
-// ReSharper disable once CppUnusedIncludeDirective
#include "pre.hpp"
-#include "text_control.hpp"
-
-namespace cru::ui::controls
-{
- class TextBlock : public TextControl
- {
- public:
- static constexpr auto control_type = L"TextBlock";
-
- static TextBlock* Create(const String& text = L"")
- {
- const auto text_block = new TextBlock();
- text_block->SetText(text);
- return text_block;
- }
-
- protected:
- TextBlock();
- public:
- TextBlock(const TextBlock& other) = delete;
- TextBlock(TextBlock&& other) = delete;
- TextBlock& operator=(const TextBlock& other) = delete;
- TextBlock& operator=(TextBlock&& other) = delete;
- ~TextBlock() override = default;
-
- StringView GetControlType() const override final;
-
- using TextControl::SetSelectable; // Make this public.
- };
+#include <memory>
+
+#include "ui/no_child_control.hpp"
+
+namespace cru::ui::render {
+class TextRenderObject;
}
+
+namespace cru::ui::controls {
+class TextBlock : public NoChildControl {
+ public:
+ static constexpr auto control_type = L"TextBlock";
+
+ static TextBlock* Create() { return new TextBlock(); }
+
+ protected:
+ TextBlock();
+
+ public:
+ TextBlock(const TextBlock& other) = delete;
+ TextBlock(TextBlock&& other) = delete;
+ TextBlock& operator=(const TextBlock& other) = delete;
+ TextBlock& operator=(TextBlock&& other) = delete;
+ ~TextBlock() override = default;
+
+ StringView GetControlType() const override final { return control_type; }
+
+ render::RenderObject* GetRenderObject() const override;
+
+ String GetText() const;
+ void SetText(const String& text);
+
+ private:
+ std::shared_ptr<render::TextRenderObject> render_object_;
+};
+} // namespace cru::ui::controls
diff --git a/src/ui/controls/text_box.cpp b/src/ui/controls/text_box.cpp
deleted file mode 100644
index 893d6e8d..00000000
--- a/src/ui/controls/text_box.cpp
+++ /dev/null
@@ -1,198 +0,0 @@
-#include "text_box.hpp"
-
-#include <cwctype>
-#include <cassert>
-
-#include "graph/graph.hpp"
-#include "exception.hpp"
-#include "ui/ui_manager.hpp"
-
-namespace cru::ui::controls
-{
- TextBox::TextBox() : TextControl(
- UiManager::GetInstance()->GetPredefineResources()->text_box_text_format,
- UiManager::GetInstance()->GetPredefineResources()->text_box_text_brush
- )
- {
- SetSelectable(true);
-
- caret_brush_ = UiManager::GetInstance()->GetPredefineResources()->text_box_caret_brush;
-
- GetBorderProperty() = UiManager::GetInstance()->GetPredefineResources()->text_box_border;
- SetBordered(true);
-
- draw_content_event.AddHandler([this](events::DrawEventArgs& args)
- {
- const auto device_context = args.GetDeviceContext();
- if (is_caret_show_)
- {
- const auto caret_half_width = UiManager::GetInstance()->GetCaretInfo().half_caret_width;
- FLOAT x, y;
- DWRITE_HIT_TEST_METRICS metrics{};
- ThrowIfFailed(text_layout_->HitTestTextPosition(caret_position_, FALSE, &x, &y, &metrics));
- device_context->FillRectangle(D2D1::RectF(metrics.left - caret_half_width, metrics.top, metrics.left + caret_half_width, metrics.top + metrics.height), caret_brush_.Get());
- }
- });
-
- get_focus_event.direct.AddHandler([this](events::FocusChangeEventArgs& args)
- {
- assert(!caret_timer_.has_value());
- is_caret_show_ = true;
- caret_timer_ = SetInterval(UiManager::GetInstance()->GetCaretInfo().caret_blink_duration, [this]
- {
- is_caret_show_ = !is_caret_show_;
- InvalidateDraw();
- });
- });
-
- lose_focus_event.direct.AddHandler([this](events::FocusChangeEventArgs& args)
- {
- assert(caret_timer_.has_value());
- caret_timer_->Cancel();
- caret_timer_ = std::nullopt;
- is_caret_show_ = false;
- });
-
- key_down_event.bubble.AddHandler([this](events::KeyEventArgs& args)
- {
- if (args.GetVirtualCode() == VK_LEFT && caret_position_ > 0)
- {
- if (IsKeyDown(VK_SHIFT))
- {
- if (GetCaretSelectionSide())
- ShiftLeftSelectionRange(-1);
- else
- ShiftRightSelectionRange(-1);
- }
- else
- {
- const auto selection = GetSelectedRange();
- if (selection.has_value())
- {
- ClearSelection();
- caret_position_ = selection.value().position;
- }
- else
- caret_position_--;
- }
- InvalidateDraw();
- }
-
- if (args.GetVirtualCode() == VK_RIGHT && caret_position_ < GetText().size())
- {
- if (IsKeyDown(VK_SHIFT))
- {
- if (GetCaretSelectionSide())
- ShiftLeftSelectionRange(1);
- else
- ShiftRightSelectionRange(1);
- }
- else
- {
- const auto selection = GetSelectedRange();
- if (selection.has_value())
- {
- ClearSelection();
- caret_position_ = selection.value().position + selection.value().count;
- }
- else
- caret_position_++;
- }
- }
- });
-
- char_event.bubble.AddHandler([this](events::CharEventArgs& args)
- {
- if (args.GetChar() == L'\b')
- {
- if (GetSelectedRange().has_value())
- {
- const auto selection_range = GetSelectedRange().value();
- auto text = GetText();
- text.erase(text.cbegin() + selection_range.position, text.cbegin() + selection_range.position + selection_range.count);
- SetText(text);
- caret_position_ = selection_range.position;
- ClearSelection();
- }
- else
- {
- if (caret_position_ > 0)
- {
- auto text = GetText();
- if (!text.empty())
- {
- const auto position = --caret_position_;
- text.erase(text.cbegin() + position);
- SetText(text);
- }
- }
- }
- return;
- }
-
- if (std::iswprint(args.GetChar()))
- {
- if (GetSelectedRange().has_value())
- {
- const auto selection_range = GetSelectedRange().value();
- auto text = GetText();
- text.erase(selection_range.position, selection_range.count);
- text.insert(text.cbegin() + selection_range.position, args.GetChar());
- SetText(text);
- caret_position_ = selection_range.position + 1;
- ClearSelection();
- }
- else
- {
- ClearSelection();
- const auto position = caret_position_++;
- auto text = GetText();
- text.insert(text.cbegin() + position, { args.GetChar() });
- SetText(text);
- }
- }
- });
- }
-
- TextBox::~TextBox() = default;
-
- StringView TextBox::GetControlType() const
- {
- return control_type;
- }
-
- void TextBox::RequestChangeCaretPosition(const unsigned position)
- {
- caret_position_ = position;
- InvalidateDraw();
- }
-
- bool TextBox::GetCaretSelectionSide() const
- {
- const auto selection = TextRange::ToTwoSides(GetSelectedRange(), caret_position_);
- if (selection.first == caret_position_)
- return true;
- if (selection.second == caret_position_)
- return false;
- assert(false);
- return true;
- }
-
- void TextBox::ShiftLeftSelectionRange(const int count)
- {
- const auto selection_range_side = TextRange::ToTwoSides(GetSelectedRange(), caret_position_);
- int new_left = selection_range_side.first + count;
- new_left = new_left < 0 ? 0 : new_left; // at least 0
- caret_position_ = new_left;
- SetSelectedRange(TextRange::FromTwoSides(static_cast<unsigned>(new_left), selection_range_side.second));
- }
-
- void TextBox::ShiftRightSelectionRange(const int count)
- {
- const auto selection_range_side = TextRange::ToTwoSides(GetSelectedRange(), caret_position_);
- int new_right = selection_range_side.second + count;
- new_right = new_right < 0 ? 0 : new_right; // at least 0
- caret_position_ = new_right;
- SetSelectedRange(TextRange::FromTwoSides(selection_range_side.first, static_cast<unsigned>(new_right)));
- }
-}
diff --git a/src/ui/controls/text_box.hpp b/src/ui/controls/text_box.hpp
deleted file mode 100644
index e5cd7545..00000000
--- a/src/ui/controls/text_box.hpp
+++ /dev/null
@@ -1,47 +0,0 @@
-#pragma once
-
-// ReSharper disable once CppUnusedIncludeDirective
-#include "pre.hpp"
-
-#include "text_control.hpp"
-#include "timer.hpp"
-
-namespace cru::ui::controls
-{
- class TextBox : public TextControl
- {
- public:
- static constexpr auto control_type = L"TextBox";
-
- static TextBox* Create()
- {
- return new TextBox();
- }
-
- protected:
- TextBox();
- public:
- TextBox(const TextBox& other) = delete;
- TextBox(TextBox&& other) = delete;
- TextBox& operator=(const TextBox& other) = delete;
- TextBox& operator=(TextBox&& other) = delete;
- ~TextBox() override;
-
- StringView GetControlType() const override final;
-
- protected:
- void RequestChangeCaretPosition(unsigned position) override final;
-
- private:
- // return true if left
- bool GetCaretSelectionSide() const;
- void ShiftLeftSelectionRange(int count);
- void ShiftRightSelectionRange(int count);
-
- private:
- unsigned caret_position_ = 0;
- std::optional<TimerTask> caret_timer_{};
- Microsoft::WRL::ComPtr<ID2D1Brush> caret_brush_;
- bool is_caret_show_ = false;
- };
-}
diff --git a/src/ui/controls/text_control.cpp b/src/ui/controls/text_control.cpp
deleted file mode 100644
index e53d3c69..00000000
--- a/src/ui/controls/text_control.cpp
+++ /dev/null
@@ -1,250 +0,0 @@
-#include "text_control.hpp"
-
-#include <cassert>
-
-#include "ui/window.hpp"
-#include "graph/graph.hpp"
-#include "exception.hpp"
-#include "ui/ui_manager.hpp"
-
-namespace cru::ui::controls
-{
- namespace
- {
- unsigned TextLayoutHitTest(IDWriteTextLayout* text_layout, const Point& point)
- {
- BOOL is_trailing, is_inside;
- DWRITE_HIT_TEST_METRICS metrics{};
- text_layout->HitTestPoint(point.x, point.y, &is_trailing, &is_inside, &metrics);
- return is_trailing == 0 ? metrics.textPosition : metrics.textPosition + 1;
- }
-
- void DrawSelectionRect(ID2D1DeviceContext* device_context, IDWriteTextLayout* layout, ID2D1Brush* brush, const std::optional<TextRange> range)
- {
- if (range.has_value())
- {
- DWRITE_TEXT_METRICS text_metrics{};
- ThrowIfFailed(layout->GetMetrics(&text_metrics));
- const auto metrics_count = text_metrics.lineCount * text_metrics.maxBidiReorderingDepth;
-
- std::vector<DWRITE_HIT_TEST_METRICS> hit_test_metrics(metrics_count);
- UINT32 actual_count;
- layout->HitTestTextRange(
- range.value().position, range.value().count,
- 0, 0,
- hit_test_metrics.data(), metrics_count, &actual_count
- );
-
- hit_test_metrics.erase(hit_test_metrics.cbegin() + actual_count, hit_test_metrics.cend());
-
- for (const auto& metrics : hit_test_metrics)
- device_context->FillRoundedRectangle(D2D1::RoundedRect(D2D1::RectF(metrics.left, metrics.top, metrics.left + metrics.width, metrics.top + metrics.height), 3, 3), brush);
- }
- }
- }
-
- TextControl::TextControl(const Microsoft::WRL::ComPtr<IDWriteTextFormat>& init_text_format,
- const Microsoft::WRL::ComPtr<ID2D1Brush>& init_brush)
- {
- text_format_ = init_text_format;
-
- RecreateTextLayout();
-
- brush_ = init_brush;
-
- selection_brush_ = UiManager::GetInstance()->GetPredefineResources()->text_control_selection_brush;
-
- SetClipContent(true);
-
- draw_content_event.AddHandler([this](events::DrawEventArgs& args)
- {
- const auto device_context = args.GetDeviceContext();
- DrawSelectionRect(device_context, text_layout_.Get(), selection_brush_.Get(), selected_range_);
- device_context->DrawTextLayout(D2D1::Point2F(), text_layout_.Get(), brush_.Get());
- });
-
- mouse_down_event.bubble.AddHandler([this](events::MouseButtonEventArgs& args)
- {
- if (is_selectable_ && args.GetMouseButton() == MouseButton::Left && GetRect(RectRange::Padding).IsPointInside(args.GetPoint(this, RectRange::Margin)))
- {
- selected_range_ = std::nullopt;
- const auto hit_test_result = TextLayoutHitTest(text_layout_.Get(), args.GetPoint(this));
- RequestChangeCaretPosition(hit_test_result);
- mouse_down_position_ = hit_test_result;
- is_selecting_ = true;
- GetWindow()->CaptureMouseFor(this);
- InvalidateDraw();
- }
- });
-
- mouse_move_event.bubble.AddHandler([this](events::MouseEventArgs& args)
- {
- if (is_selecting_)
- {
- const auto hit_test_result = TextLayoutHitTest(text_layout_.Get(), args.GetPoint(this));
- RequestChangeCaretPosition(hit_test_result);
- selected_range_ = TextRange::FromTwoSides(hit_test_result, mouse_down_position_);
- InvalidateDraw();
- }
- UpdateCursor(args.GetPoint(this, RectRange::Margin));
- });
-
-
- mouse_up_event.bubble.AddHandler([this](events::MouseButtonEventArgs& args)
- {
- if (args.GetMouseButton() == MouseButton::Left)
- {
- if (is_selecting_)
- {
- is_selecting_ = false;
- GetWindow()->ReleaseCurrentMouseCapture();
- }
- }
- });
-
- lose_focus_event.direct.AddHandler([this](events::FocusChangeEventArgs& args)
- {
- if (is_selecting_)
- {
- is_selecting_ = false;
- GetWindow()->ReleaseCurrentMouseCapture();
- }
- if (!args.IsWindow()) // If the focus lose is triggered window-wide, then save the selection state. Otherwise, clear selection.
- {
- selected_range_ = std::nullopt;
- InvalidateDraw();
- }
- });
- }
-
-
- void TextControl::SetText(const String& text)
- {
- if (text_ != text)
- {
- const auto old_text = text_;
- text_ = text;
- OnTextChangedCore(old_text, text);
- }
- }
-
- void TextControl::SetBrush(const Microsoft::WRL::ComPtr<ID2D1Brush>& brush)
- {
- brush_ = brush;
- InvalidateDraw();
- }
-
- void TextControl::SetTextFormat(const Microsoft::WRL::ComPtr<IDWriteTextFormat>& text_format)
- {
- text_format_ = text_format;
- RecreateTextLayout();
- InvalidateDraw();
- }
-
- void TextControl::SetSelectable(const bool is_selectable)
- {
- if (is_selectable_ != is_selectable)
- {
- if (!is_selectable)
- {
- if (is_selecting_)
- {
- is_selecting_ = false;
- GetWindow()->ReleaseCurrentMouseCapture();
- }
- selected_range_ = std::nullopt;
- InvalidateDraw();
- }
- is_selectable_ = is_selectable;
- UpdateCursor(std::nullopt);
- }
- }
-
- void TextControl::SetSelectedRange(std::optional<TextRange> text_range)
- {
- if (is_selectable_)
- {
- selected_range_ = text_range;
- InvalidateDraw();
- }
- }
-
- Size TextControl::OnMeasureContent(const Size& available_size, const AdditionalMeasureInfo&)
- {
- ThrowIfFailed(text_layout_->SetMaxWidth(available_size.width));
- ThrowIfFailed(text_layout_->SetMaxHeight(available_size.height));
-
- DWRITE_TEXT_METRICS metrics{};
-
- ThrowIfFailed(text_layout_->GetMetrics(&metrics));
-
- const Size measure_result(metrics.width, metrics.height);
-
- return measure_result;
- }
-
- void TextControl::RequestChangeCaretPosition(unsigned position)
- {
-
- }
-
- void TextControl::OnRectChange(const Rect& old_rect, const Rect& new_rect)
- {
- const auto content = GetRect(RectRange::Content);
- ThrowIfFailed(text_layout_->SetMaxWidth(content.width));
- ThrowIfFailed(text_layout_->SetMaxHeight(content.height));
- }
-
- void TextControl::OnTextChangedCore(const String& old_text, const String& new_text)
- {
- RecreateTextLayout();
- InvalidateLayout();
- InvalidateDraw();
- }
-
- void TextControl::RecreateTextLayout()
- {
- assert(text_format_ != nullptr);
-
- text_layout_ = nullptr;
-
- const auto dwrite_factory = graph::GraphManager::GetInstance()->GetDWriteFactory();
-
- const auto&& size = GetSize();
-
- ThrowIfFailed(dwrite_factory->CreateTextLayout(
- text_.c_str(), static_cast<UINT32>(text_.size()),
- text_format_.Get(),
- size.width, size.height,
- &text_layout_
- ));
- }
-
- void TextControl::UpdateCursor(const std::optional<Point>& point)
- {
- if (!is_selectable_)
- {
- SetCursor(nullptr);
- return;
- }
-
- const auto window = GetWindow();
- if (window == nullptr)
- {
- SetCursor(nullptr);
- return;
- }
-
- if (is_selecting_)
- {
- SetCursor(cursors::i_beam);
- return;
- }
-
- const auto p = point.value_or(WindowToControl(window->GetMousePosition()));
- if (GetRect(RectRange::Padding).IsPointInside(p))
- SetCursor(cursors::i_beam);
- else
- SetCursor(nullptr);
- }
-}
diff --git a/src/ui/controls/text_control.hpp b/src/ui/controls/text_control.hpp
deleted file mode 100644
index 58d48c13..00000000
--- a/src/ui/controls/text_control.hpp
+++ /dev/null
@@ -1,95 +0,0 @@
-#pragma once
-
-// ReSharper disable once CppUnusedIncludeDirective
-#include "pre.hpp"
-
-#include "ui/control.hpp"
-
-namespace cru::ui::controls
-{
- class TextControl : public NoChildControl
- {
- protected:
- TextControl(
- const Microsoft::WRL::ComPtr<IDWriteTextFormat>& init_text_format,
- const Microsoft::WRL::ComPtr<ID2D1Brush>& init_brush
- );
- public:
- TextControl(const TextControl& other) = delete;
- TextControl(TextControl&& other) = delete;
- TextControl& operator=(const TextControl& other) = delete;
- TextControl& operator=(TextControl&& other) = delete;
- ~TextControl() override = default;
-
- String GetText() const
- {
- return text_;
- }
-
- void SetText(const String& text);
-
- Microsoft::WRL::ComPtr<ID2D1Brush> GetBrush() const
- {
- return brush_;
- }
-
- void SetBrush(const Microsoft::WRL::ComPtr<ID2D1Brush>& brush);
-
- Microsoft::WRL::ComPtr<IDWriteTextFormat> GetTextFormat() const
- {
- return text_format_;
- }
-
- void SetTextFormat(const Microsoft::WRL::ComPtr<IDWriteTextFormat>& text_format);
-
- bool IsSelectable() const
- {
- return is_selectable_;
- }
-
- std::optional<TextRange> GetSelectedRange() const
- {
- return selected_range_;
- }
-
- void SetSelectedRange(std::optional<TextRange> text_range);
-
- void ClearSelection()
- {
- SetSelectedRange(std::nullopt);
- }
-
- protected:
- void SetSelectable(bool is_selectable);
-
- Size OnMeasureContent(const Size& available_size, const AdditionalMeasureInfo&) override final;
-
- virtual void RequestChangeCaretPosition(unsigned position);
-
- void OnRectChange(const Rect& old_rect, const Rect& new_rect) override;
-
- private:
- void OnTextChangedCore(const String& old_text, const String& new_text);
-
- void RecreateTextLayout();
-
- // param point is the mouse point relative to this control.
- void UpdateCursor(const std::optional<Point>& point);
-
- private:
- String text_;
-
- Microsoft::WRL::ComPtr<ID2D1Brush> brush_;
- Microsoft::WRL::ComPtr<ID2D1Brush> selection_brush_;
- Microsoft::WRL::ComPtr<IDWriteTextFormat> text_format_;
- protected:
- Microsoft::WRL::ComPtr<IDWriteTextLayout> text_layout_;
-
- private:
- bool is_selectable_ = false;
-
- bool is_selecting_ = false;
- unsigned mouse_down_position_ = 0;
- std::optional<TextRange> selected_range_ = std::nullopt;
- };
-}
diff --git a/src/ui/controls/toggle_button.cpp b/src/ui/controls/toggle_button.cpp
deleted file mode 100644
index 6eb0bc40..00000000
--- a/src/ui/controls/toggle_button.cpp
+++ /dev/null
@@ -1,117 +0,0 @@
-#include "toggle_button.hpp"
-
-#include "graph/graph.hpp"
-#include "ui/animations/animation.hpp"
-#include "ui/ui_manager.hpp"
-#include "ui/convert_util.hpp"
-
-namespace cru::ui::controls
-{
- using animations::AnimationBuilder;
-
- // ui length parameters of toggle button.
- constexpr float half_height = 15;
- constexpr float half_width = half_height * 2;
- constexpr float stroke_width = 3;
- constexpr float inner_circle_radius = half_height - stroke_width;
- constexpr float inner_circle_x = half_width - half_height;
-
- ToggleButton::ToggleButton() : current_circle_position_(-inner_circle_x)
- {
- graph::GraphManager::GetInstance()->GetD2D1Factory()->CreateRoundedRectangleGeometry(D2D1::RoundedRect(D2D1::RectF(-half_width, -half_height, half_width, half_height), half_height, half_height), &frame_path_);
-
- on_brush_ = UiManager::GetInstance()->GetPredefineResources()->toggle_button_on_brush;
- off_brush_ = UiManager::GetInstance()->GetPredefineResources()->toggle_button_off_brush;
-
- draw_content_event.AddHandler([this](events::DrawEventArgs& args)
- {
- const auto device_context = args.GetDeviceContext();
- const auto size = GetSize();
- graph::WithTransform(device_context, D2D1::Matrix3x2F::Translation(size.width / 2, size.height / 2), [this](ID2D1DeviceContext* device_context)
- {
- if (state_)
- {
- device_context->DrawGeometry(frame_path_.Get(), on_brush_.Get(), stroke_width);
- device_context->FillEllipse(D2D1::Ellipse(D2D1::Point2F(current_circle_position_, 0), inner_circle_radius, inner_circle_radius), on_brush_.Get());
- }
- else
- {
- device_context->DrawGeometry(frame_path_.Get(), off_brush_.Get(), stroke_width);
- device_context->FillEllipse(D2D1::Ellipse(D2D1::Point2F(current_circle_position_, 0), inner_circle_radius, inner_circle_radius), off_brush_.Get());
- }
- });
- });
-
- mouse_click_event.bubble.AddHandler([this](events::MouseButtonEventArgs& args)
- {
- if (args.GetMouseButton() == MouseButton::Left)
- Toggle();
- });
- }
-
-
- StringView ToggleButton::GetControlType() const
- {
- return control_type;
- }
-
- bool ToggleButton::IsPointInside(const Point& point)
- {
- const auto size = GetSize();
- const auto transform = D2D1::Matrix3x2F::Translation(size.width / 2, size.height / 2);
- BOOL contains;
- frame_path_->FillContainsPoint(Convert(point), transform, &contains);
- if (!contains)
- frame_path_->StrokeContainsPoint(Convert(point), stroke_width, nullptr, transform, &contains);
- return contains != 0;
- }
-
- void ToggleButton::SetState(const bool state)
- {
- if (state != state_)
- {
- state_ = state;
- float destination_x;
-
- if (state)
- destination_x = inner_circle_x;
- else
- destination_x = -inner_circle_x;
-
- const auto previous_position = current_circle_position_;
- const auto delta = destination_x - current_circle_position_;
-
- constexpr auto total_time = FloatSecond(0.2);
-
- const auto time = total_time * (std::abs(delta) / (inner_circle_x * 2));
-
- // ReSharper disable once CppExpressionWithoutSideEffects
- AnimationBuilder(Format(L"ToggleButton {}", reinterpret_cast<size_t>(this)), time)
- .AddStepHandler([=](auto, const double percentage)
- {
- current_circle_position_ = static_cast<float>(previous_position + delta * percentage);
- InvalidateDraw();
- })
- .Start();
-
- events::ToggleEventArgs args(this, this, state);
- toggle_event.Raise(args);
- InvalidateDraw();
- }
- }
-
- void ToggleButton::Toggle()
- {
- SetState(!GetState());
- }
-
- Size ToggleButton::OnMeasureContent(const Size& available_size, const AdditionalMeasureInfo&)
- {
- const Size result_size(
- half_width * 2 + stroke_width,
- half_height * 2 + stroke_width
- );
-
- return result_size;
- }
-}
diff --git a/src/ui/controls/toggle_button.hpp b/src/ui/controls/toggle_button.hpp
deleted file mode 100644
index dee655d4..00000000
--- a/src/ui/controls/toggle_button.hpp
+++ /dev/null
@@ -1,57 +0,0 @@
-#pragma once
-
-// ReSharper disable once CppUnusedIncludeDirective
-#include "pre.hpp"
-
-#include "ui/control.hpp"
-
-namespace cru::ui::controls
-{
- class ToggleButton : public NoChildControl
- {
- public:
- static constexpr auto control_type = L"ToggleButton";
-
- static ToggleButton* Create()
- {
- return new ToggleButton();
- }
-
- protected:
- ToggleButton();
-
- public:
- ToggleButton(const ToggleButton& other) = delete;
- ToggleButton(ToggleButton&& other) = delete;
- ToggleButton& operator=(const ToggleButton& other) = delete;
- ToggleButton& operator=(ToggleButton&& other) = delete;
- ~ToggleButton() override = default;
-
- StringView GetControlType() const override final;
-
- bool IsPointInside(const Point& point) override;
-
- bool GetState() const
- {
- return state_;
- }
-
- void SetState(bool state);
-
- void Toggle();
-
- Event<events::ToggleEventArgs> toggle_event;
-
- protected:
- Size OnMeasureContent(const Size& available_size, const AdditionalMeasureInfo&) override;
-
- private:
- bool state_ = false;
-
- float current_circle_position_;
-
- Microsoft::WRL::ComPtr<ID2D1RoundedRectangleGeometry> frame_path_;
- Microsoft::WRL::ComPtr<ID2D1Brush> on_brush_;
- Microsoft::WRL::ComPtr<ID2D1Brush> off_brush_;
- };
-}
diff --git a/src/ui/convert_util.hpp b/src/ui/convert_util.hpp
deleted file mode 100644
index 5408f2e4..00000000
--- a/src/ui/convert_util.hpp
+++ /dev/null
@@ -1,21 +0,0 @@
-#pragma once
-
-// ReSharper disable once CppUnusedIncludeDirective
-#include "pre.hpp"
-
-#include "system_headers.hpp"
-
-#include "ui_base.hpp"
-
-namespace cru::ui
-{
- inline D2D1_POINT_2F Convert(const Point& point)
- {
- return D2D1::Point2F(point.x, point.y);
- }
-
- inline D2D1_RECT_F Convert(const Rect& rect)
- {
- return D2D1::RectF(rect.left, rect.top, rect.left + rect.width, rect.top + rect.height);
- }
-}
diff --git a/src/ui/cursor.cpp b/src/ui/cursor.cpp
deleted file mode 100644
index 91b94b16..00000000
--- a/src/ui/cursor.cpp
+++ /dev/null
@@ -1,32 +0,0 @@
-#include "cursor.hpp"
-
-#include "exception.hpp"
-
-namespace cru::ui
-{
- Cursor::Cursor(HCURSOR handle, const bool auto_release)
- : handle_(handle), auto_release_(auto_release)
- {
-
- }
-
- Cursor::~Cursor()
- {
- if (auto_release_)
- ::DestroyCursor(handle_);
- }
-
- namespace cursors
- {
- Cursor::Ptr arrow{};
- Cursor::Ptr hand{};
- Cursor::Ptr i_beam{};
-
- void LoadSystemCursors()
- {
- arrow = std::make_shared<Cursor>(::LoadCursorW(nullptr, IDC_ARROW), false);
- hand = std::make_shared<Cursor>(::LoadCursorW(nullptr, IDC_HAND), false);
- i_beam = std::make_shared<Cursor>(::LoadCursorW(nullptr, IDC_IBEAM), false);
- }
- }
-}
diff --git a/src/ui/cursor.hpp b/src/ui/cursor.hpp
deleted file mode 100644
index 273e524d..00000000
--- a/src/ui/cursor.hpp
+++ /dev/null
@@ -1,43 +0,0 @@
-#pragma once
-
-// ReSharper disable once CppUnusedIncludeDirective
-#include "pre.hpp"
-
-#include "system_headers.hpp"
-#include <memory>
-
-#include "base.hpp"
-
-namespace cru::ui
-{
- class Cursor : public Object
- {
- public:
- using Ptr = std::shared_ptr<Cursor>;
-
- Cursor(HCURSOR handle, bool auto_release);
- Cursor(const Cursor& other) = delete;
- Cursor(Cursor&& other) = delete;
- Cursor& operator=(const Cursor& other) = delete;
- Cursor& operator=(Cursor&& other) = delete;
- ~Cursor() override;
-
- HCURSOR GetHandle() const
- {
- return handle_;
- }
-
- private:
- HCURSOR handle_;
- bool auto_release_;
- };
-
- namespace cursors
- {
- extern Cursor::Ptr arrow;
- extern Cursor::Ptr hand;
- extern Cursor::Ptr i_beam;
-
- void LoadSystemCursors();
- }
-}
diff --git a/src/ui/d2d_util.hpp b/src/ui/d2d_util.hpp
new file mode 100644
index 00000000..2ec8ba98
--- /dev/null
+++ b/src/ui/d2d_util.hpp
@@ -0,0 +1,82 @@
+#pragma once
+#include "pre.hpp"
+
+#include <d2d1.h>
+
+#include "ui_base.hpp"
+
+namespace cru::ui {
+inline D2D1_POINT_2F Convert(const Point& point) {
+ return D2D1::Point2F(point.x, point.y);
+}
+
+inline D2D1_RECT_F Convert(const Rect& rect) {
+ return D2D1::RectF(rect.left, rect.top, rect.left + rect.width,
+ rect.top + rect.height);
+}
+
+inline D2D1_ROUNDED_RECT Convert(const RoundedRect& rounded_rect) {
+ return D2D1::RoundedRect(Convert(rounded_rect.rect), rounded_rect.radius_x,
+ rounded_rect.radius_y);
+}
+
+inline D2D1_ELLIPSE Convert(const Ellipse& ellipse) {
+ return D2D1::Ellipse(Convert(ellipse.center), ellipse.radius_x,
+ ellipse.radius_y);
+}
+
+inline Point Convert(const D2D1_POINT_2F& point) {
+ return Point(point.x, point.y);
+}
+
+inline Rect Convert(const D2D1_RECT_F& rect) {
+ return Rect(rect.left, rect.top, rect.right - rect.left,
+ rect.bottom - rect.top);
+}
+
+inline RoundedRect Convert(const D2D1_ROUNDED_RECT& rounded_rect) {
+ return RoundedRect(Convert(rounded_rect.rect), rounded_rect.radiusX,
+ rounded_rect.radiusY);
+}
+
+inline Ellipse Convert(const D2D1_ELLIPSE& ellipse) {
+ return Ellipse(Convert(ellipse.point), ellipse.radiusX, ellipse.radiusY);
+}
+
+inline bool operator==(const D2D1_POINT_2F& left, const D2D1_POINT_2F& right) {
+ return left.x == right.x && left.y == right.y;
+}
+
+inline bool operator!=(const D2D1_POINT_2F& left, const D2D1_POINT_2F& right) {
+ return !(left == right);
+}
+
+inline bool operator==(const D2D1_RECT_F& left, const D2D1_RECT_F& right) {
+ return left.left == right.left && left.top == right.top &&
+ left.right == right.right && left.bottom == right.bottom;
+}
+
+inline bool operator!=(const D2D1_RECT_F& left, const D2D1_RECT_F& right) {
+ return !(left == right);
+}
+
+inline bool operator==(const D2D1_ROUNDED_RECT& left,
+ const D2D1_ROUNDED_RECT& right) {
+ return left.rect == right.rect && left.radiusX == right.radiusX &&
+ left.radiusY == right.radiusY;
+}
+
+inline bool operator!=(const D2D1_ROUNDED_RECT& left,
+ const D2D1_ROUNDED_RECT& right) {
+ return !(left == right);
+}
+
+inline bool operator==(const D2D1_ELLIPSE& left, const D2D1_ELLIPSE& right) {
+ return left.point == right.point && left.radiusX == right.radiusX &&
+ left.radiusY == right.radiusY;
+}
+
+inline bool operator!=(const D2D1_ELLIPSE& left, const D2D1_ELLIPSE& right) {
+ return !(left == right);
+}
+} // namespace cru::ui
diff --git a/src/ui/events/ui_event.cpp b/src/ui/events/ui_event.cpp
deleted file mode 100644
index a1fc3d82..00000000
--- a/src/ui/events/ui_event.cpp
+++ /dev/null
@@ -1,13 +0,0 @@
-#include "ui_event.hpp"
-
-#include "ui/control.hpp"
-
-namespace cru::ui::events
-{
- Point MouseEventArgs::GetPoint(Control* control, const RectRange range) const
- {
- if (point_.has_value())
- return control->TransformPoint(control->WindowToControl(point_.value()), RectRange::Margin, range);
- return Point();
- }
-}
diff --git a/src/ui/events/ui_event.hpp b/src/ui/events/ui_event.hpp
index e0040942..7fe4e6eb 100644
--- a/src/ui/events/ui_event.hpp
+++ b/src/ui/events/ui_event.hpp
@@ -1,301 +1,195 @@
#pragma once
-
-// ReSharper disable once CppUnusedIncludeDirective
#include "pre.hpp"
-#include "system_headers.hpp"
#include <optional>
#include "base.hpp"
#include "cru_event.hpp"
#include "ui/ui_base.hpp"
-#include "ui/layout_base.hpp"
+#include "ui/input_util.hpp"
-namespace cru::ui
-{
- class Control;
-}
+struct ID2D1RenderTarget;
-namespace cru::ui::events
-{
- class UiEventArgs : public BasicEventArgs
- {
- public:
- UiEventArgs(Object* sender, Object* original_sender)
- : BasicEventArgs(sender), original_sender_(original_sender), handled_(false)
- {
-
- }
-
- UiEventArgs(const UiEventArgs& other) = default;
- UiEventArgs(UiEventArgs&& other) = default;
- UiEventArgs& operator=(const UiEventArgs& other) = default;
- UiEventArgs& operator=(UiEventArgs&& other) = default;
- ~UiEventArgs() override = default;
-
- Object* GetOriginalSender() const
- {
- return original_sender_;
- }
-
- bool IsHandled() const
- {
- return handled_;
- }
-
- void SetHandled(const bool handled = true)
- {
- handled_ = handled;
- }
-
- private:
- Object* original_sender_;
- bool handled_;
- };
-
- template <typename TEventArgs>
- class RoutedEvent
- {
- public:
- static_assert(std::is_base_of_v<UiEventArgs, TEventArgs>, "TEventArgs must be subclass of UiEventArgs.");
-
- using EventArgs = TEventArgs;
-
- RoutedEvent() = default;
- RoutedEvent(const RoutedEvent& other) = delete;
- RoutedEvent(RoutedEvent&& other) = delete;
- RoutedEvent& operator=(const RoutedEvent& other) = delete;
- RoutedEvent& operator=(RoutedEvent&& other) = delete;
- ~RoutedEvent() = default;
-
- Event<TEventArgs> direct;
- Event<TEventArgs> bubble;
- Event<TEventArgs> tunnel;
- };
-
- class MouseEventArgs : public UiEventArgs
- {
- public:
- MouseEventArgs(Object* sender, Object* original_sender, const std::optional<Point>& point = std::nullopt)
- : UiEventArgs(sender, original_sender), point_(point)
- {
-
- }
- MouseEventArgs(const MouseEventArgs& other) = default;
- MouseEventArgs(MouseEventArgs&& other) = default;
- MouseEventArgs& operator=(const MouseEventArgs& other) = default;
- MouseEventArgs& operator=(MouseEventArgs&& other) = default;
- ~MouseEventArgs() override = default;
-
- Point GetPoint(Control* control, RectRange range = RectRange::Content) const;
-
- private:
- std::optional<Point> point_;
- };
-
-
- class MouseButtonEventArgs : public MouseEventArgs
- {
- public:
- MouseButtonEventArgs(Object* sender, Object* original_sender, const Point& point, const MouseButton button)
- : MouseEventArgs(sender, original_sender, point), button_(button)
- {
-
- }
- MouseButtonEventArgs(const MouseButtonEventArgs& other) = default;
- MouseButtonEventArgs(MouseButtonEventArgs&& other) = default;
- MouseButtonEventArgs& operator=(const MouseButtonEventArgs& other) = default;
- MouseButtonEventArgs& operator=(MouseButtonEventArgs&& other) = default;
- ~MouseButtonEventArgs() override = default;
-
- MouseButton GetMouseButton() const
- {
- return button_;
- }
-
- private:
- MouseButton button_;
- };
-
-
- class MouseWheelEventArgs : public MouseEventArgs
- {
- public:
- MouseWheelEventArgs(Object* sender, Object* original_sender, const Point& point, const float delta)
- : MouseEventArgs(sender, original_sender, point), delta_(delta)
- {
-
- }
- MouseWheelEventArgs(const MouseWheelEventArgs& other) = default;
- MouseWheelEventArgs(MouseWheelEventArgs&& other) = default;
- MouseWheelEventArgs& operator=(const MouseWheelEventArgs& other) = default;
- MouseWheelEventArgs& operator=(MouseWheelEventArgs&& other) = default;
- ~MouseWheelEventArgs() override = default;
-
- float GetDelta() const
- {
- return delta_;
- }
-
- private:
- float delta_;
- };
-
-
- class DrawEventArgs : public UiEventArgs
- {
- public:
- DrawEventArgs(Object* sender, Object* original_sender, ID2D1DeviceContext* device_context)
- : UiEventArgs(sender, original_sender), device_context_(device_context)
- {
-
- }
- DrawEventArgs(const DrawEventArgs& other) = default;
- DrawEventArgs(DrawEventArgs&& other) = default;
- DrawEventArgs& operator=(const DrawEventArgs& other) = default;
- DrawEventArgs& operator=(DrawEventArgs&& other) = default;
- ~DrawEventArgs() = default;
-
- ID2D1DeviceContext* GetDeviceContext() const
- {
- return device_context_;
- }
-
- private:
- ID2D1DeviceContext * device_context_;
- };
-
-
- class FocusChangeEventArgs : public UiEventArgs
- {
- public:
- FocusChangeEventArgs(Object* sender, Object* original_sender, const bool is_window = false)
- : UiEventArgs(sender, original_sender), is_window_(is_window)
- {
-
- }
- FocusChangeEventArgs(const FocusChangeEventArgs& other) = default;
- FocusChangeEventArgs(FocusChangeEventArgs&& other) = default;
- FocusChangeEventArgs& operator=(const FocusChangeEventArgs& other) = default;
- FocusChangeEventArgs& operator=(FocusChangeEventArgs&& other) = default;
- ~FocusChangeEventArgs() override = default;
-
- // Return whether the focus change is caused by the window-wide focus change.
- bool IsWindow() const
- {
- return is_window_;
- }
-
- private:
- bool is_window_;
- };
-
- class ToggleEventArgs : public UiEventArgs
- {
- public:
- ToggleEventArgs(Object* sender, Object* original_sender, bool new_state)
- : UiEventArgs(sender, original_sender), new_state_(new_state)
- {
-
- }
- ToggleEventArgs(const ToggleEventArgs& other) = default;
- ToggleEventArgs(ToggleEventArgs&& other) = default;
- ToggleEventArgs& operator=(const ToggleEventArgs& other) = default;
- ToggleEventArgs& operator=(ToggleEventArgs&& other) = default;
- ~ToggleEventArgs() override = default;
-
- bool GetNewState() const
- {
- return new_state_;
- }
-
- private:
- bool new_state_;
- };
-
- struct WindowNativeMessage
- {
- HWND hwnd;
- int msg;
- WPARAM w_param;
- LPARAM l_param;
- };
-
- class WindowNativeMessageEventArgs : public UiEventArgs
- {
- public:
- WindowNativeMessageEventArgs(Object* sender, Object* original_sender, const WindowNativeMessage& message)
- : UiEventArgs(sender, original_sender), message_(message), result_(std::nullopt)
- {
-
- }
- WindowNativeMessageEventArgs(const WindowNativeMessageEventArgs& other) = default;
- WindowNativeMessageEventArgs(WindowNativeMessageEventArgs&& other) = default;
- WindowNativeMessageEventArgs& operator=(const WindowNativeMessageEventArgs& other) = default;
- WindowNativeMessageEventArgs& operator=(WindowNativeMessageEventArgs&& other) = default;
- ~WindowNativeMessageEventArgs() override = default;
-
- WindowNativeMessage GetWindowMessage() const
- {
- return message_;
- }
-
- std::optional<LRESULT> GetResult() const
- {
- return result_;
- }
-
- void SetResult(const std::optional<LRESULT> result)
- {
- result_ = result;
- }
-
- private:
- WindowNativeMessage message_;
- std::optional<LRESULT> result_;
- };
-
- class KeyEventArgs : public UiEventArgs
- {
- public:
- KeyEventArgs(Object* sender, Object* original_sender, int virtual_code)
- : UiEventArgs(sender, original_sender), virtual_code_(virtual_code)
- {
- }
- KeyEventArgs(const KeyEventArgs& other) = default;
- KeyEventArgs(KeyEventArgs&& other) = default;
- KeyEventArgs& operator=(const KeyEventArgs& other) = default;
- KeyEventArgs& operator=(KeyEventArgs&& other) = default;
- ~KeyEventArgs() override = default;
-
- int GetVirtualCode() const
- {
- return virtual_code_;
- }
-
- private:
- int virtual_code_;
- };
-
- class CharEventArgs : public UiEventArgs
- {
- public:
- CharEventArgs(Object* sender, Object* original_sender, wchar_t c)
- : UiEventArgs(sender, original_sender), c_(c)
- {
- }
- CharEventArgs(const CharEventArgs& other) = default;
- CharEventArgs(CharEventArgs&& other) = default;
- CharEventArgs& operator=(const CharEventArgs& other) = default;
- CharEventArgs& operator=(CharEventArgs&& other) = default;
- ~CharEventArgs() override = default;
-
- wchar_t GetChar() const
- {
- return c_;
- }
-
- private:
- wchar_t c_;
- };
+namespace cru::ui {
+class Control;
}
+
+namespace cru::ui::events {
+class UiEventArgs : public BasicEventArgs {
+ public:
+ UiEventArgs(Object* sender, Object* original_sender)
+ : BasicEventArgs(sender),
+ original_sender_(original_sender),
+ handled_(false) {}
+
+ UiEventArgs(const UiEventArgs& other) = default;
+ UiEventArgs(UiEventArgs&& other) = default;
+ UiEventArgs& operator=(const UiEventArgs& other) = default;
+ UiEventArgs& operator=(UiEventArgs&& other) = default;
+ ~UiEventArgs() override = default;
+
+ Object* GetOriginalSender() const { return original_sender_; }
+
+ bool IsHandled() const { return handled_; }
+
+ void SetHandled(const bool handled = true) { handled_ = handled; }
+
+ private:
+ Object* original_sender_;
+ bool handled_;
+};
+
+template <typename TEventArgs>
+class RoutedEvent {
+ public:
+ static_assert(std::is_base_of_v<UiEventArgs, TEventArgs>,
+ "TEventArgs must be subclass of UiEventArgs.");
+
+ using EventArgs = TEventArgs;
+
+ RoutedEvent() = default;
+ RoutedEvent(const RoutedEvent& other) = delete;
+ RoutedEvent(RoutedEvent&& other) = delete;
+ RoutedEvent& operator=(const RoutedEvent& other) = delete;
+ RoutedEvent& operator=(RoutedEvent&& other) = delete;
+ ~RoutedEvent() = default;
+
+ Event<TEventArgs> direct;
+ Event<TEventArgs> bubble;
+ Event<TEventArgs> tunnel;
+};
+
+class MouseEventArgs : public UiEventArgs {
+ public:
+ MouseEventArgs(Object* sender, Object* original_sender,
+ const std::optional<Point>& point = std::nullopt)
+ : UiEventArgs(sender, original_sender), point_(point) {}
+ MouseEventArgs(const MouseEventArgs& other) = default;
+ MouseEventArgs(MouseEventArgs&& other) = default;
+ MouseEventArgs& operator=(const MouseEventArgs& other) = default;
+ MouseEventArgs& operator=(MouseEventArgs&& other) = default;
+ ~MouseEventArgs() override = default;
+
+ Point GetPoint() const { return point_.value_or(Point::Zero()); }
+
+ private:
+ std::optional<Point> point_;
+};
+
+class MouseButtonEventArgs : public MouseEventArgs {
+ public:
+ MouseButtonEventArgs(Object* sender, Object* original_sender,
+ const Point& point, const MouseButton button)
+ : MouseEventArgs(sender, original_sender, point), button_(button) {}
+ MouseButtonEventArgs(const MouseButtonEventArgs& other) = default;
+ MouseButtonEventArgs(MouseButtonEventArgs&& other) = default;
+ MouseButtonEventArgs& operator=(const MouseButtonEventArgs& other) = default;
+ MouseButtonEventArgs& operator=(MouseButtonEventArgs&& other) = default;
+ ~MouseButtonEventArgs() override = default;
+
+ MouseButton GetMouseButton() const { return button_; }
+
+ private:
+ MouseButton button_;
+};
+
+class MouseWheelEventArgs : public MouseEventArgs {
+ public:
+ MouseWheelEventArgs(Object* sender, Object* original_sender,
+ const Point& point, const float delta)
+ : MouseEventArgs(sender, original_sender, point), delta_(delta) {}
+ MouseWheelEventArgs(const MouseWheelEventArgs& other) = default;
+ MouseWheelEventArgs(MouseWheelEventArgs&& other) = default;
+ MouseWheelEventArgs& operator=(const MouseWheelEventArgs& other) = default;
+ MouseWheelEventArgs& operator=(MouseWheelEventArgs&& other) = default;
+ ~MouseWheelEventArgs() override = default;
+
+ float GetDelta() const { return delta_; }
+
+ private:
+ float delta_;
+};
+
+class DrawEventArgs : public UiEventArgs {
+ public:
+ DrawEventArgs(Object* sender, Object* original_sender,
+ ID2D1RenderTarget* render_target)
+ : UiEventArgs(sender, original_sender), render_target_(render_target) {}
+ DrawEventArgs(const DrawEventArgs& other) = default;
+ DrawEventArgs(DrawEventArgs&& other) = default;
+ DrawEventArgs& operator=(const DrawEventArgs& other) = default;
+ DrawEventArgs& operator=(DrawEventArgs&& other) = default;
+ ~DrawEventArgs() = default;
+
+ ID2D1RenderTarget* GetRenderTarget() const { return render_target_; }
+
+ private:
+ ID2D1RenderTarget* render_target_;
+};
+
+class FocusChangeEventArgs : public UiEventArgs {
+ public:
+ FocusChangeEventArgs(Object* sender, Object* original_sender,
+ const bool is_window = false)
+ : UiEventArgs(sender, original_sender), is_window_(is_window) {}
+ FocusChangeEventArgs(const FocusChangeEventArgs& other) = default;
+ FocusChangeEventArgs(FocusChangeEventArgs&& other) = default;
+ FocusChangeEventArgs& operator=(const FocusChangeEventArgs& other) = default;
+ FocusChangeEventArgs& operator=(FocusChangeEventArgs&& other) = default;
+ ~FocusChangeEventArgs() override = default;
+
+ // Return whether the focus change is caused by the window-wide focus change.
+ bool IsWindow() const { return is_window_; }
+
+ private:
+ bool is_window_;
+};
+
+class ToggleEventArgs : public UiEventArgs {
+ public:
+ ToggleEventArgs(Object* sender, Object* original_sender, bool new_state)
+ : UiEventArgs(sender, original_sender), new_state_(new_state) {}
+ ToggleEventArgs(const ToggleEventArgs& other) = default;
+ ToggleEventArgs(ToggleEventArgs&& other) = default;
+ ToggleEventArgs& operator=(const ToggleEventArgs& other) = default;
+ ToggleEventArgs& operator=(ToggleEventArgs&& other) = default;
+ ~ToggleEventArgs() override = default;
+
+ bool GetNewState() const { return new_state_; }
+
+ private:
+ bool new_state_;
+};
+
+class KeyEventArgs : public UiEventArgs {
+ public:
+ KeyEventArgs(Object* sender, Object* original_sender, int virtual_code)
+ : UiEventArgs(sender, original_sender), virtual_code_(virtual_code) {}
+ KeyEventArgs(const KeyEventArgs& other) = default;
+ KeyEventArgs(KeyEventArgs&& other) = default;
+ KeyEventArgs& operator=(const KeyEventArgs& other) = default;
+ KeyEventArgs& operator=(KeyEventArgs&& other) = default;
+ ~KeyEventArgs() override = default;
+
+ int GetVirtualCode() const { return virtual_code_; }
+
+ private:
+ int virtual_code_;
+};
+
+class CharEventArgs : public UiEventArgs {
+ public:
+ CharEventArgs(Object* sender, Object* original_sender, wchar_t c)
+ : UiEventArgs(sender, original_sender), c_(c) {}
+ CharEventArgs(const CharEventArgs& other) = default;
+ CharEventArgs(CharEventArgs&& other) = default;
+ CharEventArgs& operator=(const CharEventArgs& other) = default;
+ CharEventArgs& operator=(CharEventArgs&& other) = default;
+ ~CharEventArgs() override = default;
+
+ wchar_t GetChar() const { return c_; }
+
+ private:
+ wchar_t c_;
+};
+} // namespace cru::ui::events
diff --git a/src/ui/events/window_event.hpp b/src/ui/events/window_event.hpp
new file mode 100644
index 00000000..21c644af
--- /dev/null
+++ b/src/ui/events/window_event.hpp
@@ -0,0 +1,42 @@
+#pragma once
+#include "pre.hpp"
+
+#include <Windows.h>
+
+#include "ui_event.hpp"
+
+namespace cru::ui::events {
+struct WindowNativeMessage {
+ HWND hwnd;
+ int msg;
+ WPARAM w_param;
+ LPARAM l_param;
+};
+
+class WindowNativeMessageEventArgs : public UiEventArgs {
+ public:
+ WindowNativeMessageEventArgs(Object* sender, Object* original_sender,
+ const WindowNativeMessage& message)
+ : UiEventArgs(sender, original_sender),
+ message_(message),
+ result_(std::nullopt) {}
+ WindowNativeMessageEventArgs(const WindowNativeMessageEventArgs& other) =
+ default;
+ WindowNativeMessageEventArgs(WindowNativeMessageEventArgs&& other) = default;
+ WindowNativeMessageEventArgs& operator=(
+ const WindowNativeMessageEventArgs& other) = default;
+ WindowNativeMessageEventArgs& operator=(
+ WindowNativeMessageEventArgs&& other) = default;
+ ~WindowNativeMessageEventArgs() override = default;
+
+ WindowNativeMessage GetWindowMessage() const { return message_; }
+
+ std::optional<LRESULT> GetResult() const { return result_; }
+
+ void SetResult(const std::optional<LRESULT> result) { result_ = result; }
+
+ private:
+ WindowNativeMessage message_;
+ std::optional<LRESULT> result_;
+};
+}
diff --git a/src/ui/input_util.cpp b/src/ui/input_util.cpp
new file mode 100644
index 00000000..193cba4a
--- /dev/null
+++ b/src/ui/input_util.cpp
@@ -0,0 +1,20 @@
+#include "input_util.hpp"
+
+#include <Windows.h>
+
+namespace cru::ui {
+bool IsKeyDown(const int virtual_code) {
+ const auto result = ::GetKeyState(virtual_code);
+ return (static_cast<unsigned short>(result) & 0x8000) != 0;
+}
+
+bool IsKeyToggled(const int virtual_code) {
+ const auto result = ::GetKeyState(virtual_code);
+ return (static_cast<unsigned short>(result) & 1) != 0;
+}
+
+bool IsAnyMouseButtonDown() {
+ return IsKeyDown(VK_LBUTTON) || IsKeyDown(VK_RBUTTON) ||
+ IsKeyDown(VK_MBUTTON);
+}
+} // namespace cru::ui
diff --git a/src/ui/input_util.hpp b/src/ui/input_util.hpp
new file mode 100644
index 00000000..2d01f725
--- /dev/null
+++ b/src/ui/input_util.hpp
@@ -0,0 +1,10 @@
+#pragma once
+#include "pre.hpp"
+
+namespace cru::ui {
+enum class MouseButton { Left, Right, Middle };
+
+bool IsKeyDown(int virtual_code);
+bool IsKeyToggled(int virtual_code);
+bool IsAnyMouseButtonDown();
+} // namespace cru::ui
diff --git a/src/ui/layout_base.cpp b/src/ui/layout_base.cpp
deleted file mode 100644
index 5898a623..00000000
--- a/src/ui/layout_base.cpp
+++ /dev/null
@@ -1,6 +0,0 @@
-#include "layout_base.hpp"
-
-namespace cru::ui
-{
-
-}
diff --git a/src/ui/layout_base.hpp b/src/ui/layout_base.hpp
deleted file mode 100644
index 527d9f98..00000000
--- a/src/ui/layout_base.hpp
+++ /dev/null
@@ -1,102 +0,0 @@
-#pragma once
-
-// ReSharper disable once CppUnusedIncludeDirective
-#include "pre.hpp"
-
-#include "ui_base.hpp"
-
-namespace cru::ui
-{
- enum class Alignment
- {
- Center,
- Start,
- End
- };
-
- enum class MeasureMode
- {
- Exactly,
- Content,
- Stretch
- };
-
- enum class RectRange
- {
- Content, // content excluding padding, border and margin
- Padding, // only including content and padding
- HalfBorder, // including content, padding and half border
- FullBorder, // including content, padding and full border
- Margin // including content, padding, border and margin
- };
-
- struct LayoutSideParams final
- {
- constexpr static LayoutSideParams Exactly(const float length, const Alignment alignment = Alignment::Center)
- {
- return LayoutSideParams(MeasureMode::Exactly, length, alignment);
- }
-
- constexpr static LayoutSideParams Content(const Alignment alignment = Alignment::Center)
- {
- return LayoutSideParams(MeasureMode::Content, 0, alignment);
- }
-
- constexpr static LayoutSideParams Stretch(const Alignment alignment = Alignment::Center)
- {
- return LayoutSideParams(MeasureMode::Stretch, 0, alignment);
- }
-
- constexpr LayoutSideParams() = default;
-
- constexpr explicit LayoutSideParams(const MeasureMode mode, const float length, const Alignment alignment)
- : length(length), mode(mode), alignment(alignment)
- {
-
- }
-
- constexpr bool Validate() const
- {
- if (length < 0.0)
- return false;
- if (min.has_value() && min.value() < 0.0)
- return false;
- if (max.has_value() && max.value() < 0.0)
- return false;
- if (min.has_value() && max.has_value() && min.value() > max.value())
- return false;
- return true;
- }
-
- // only used in exactly mode, specify the exactly side length of content.
- float length = 0.0;
- MeasureMode mode = MeasureMode::Content;
- Alignment alignment = Alignment::Center;
-
- // min and max specify the min/max side length of content.
- // they are used as hint and respect the actual size that content needs.
- // when mode is exactly, length is coerced into the min-max range.
- std::optional<float> min = std::nullopt;
- std::optional<float> max = std::nullopt;
- };
-
- struct BasicLayoutParams final
- {
- BasicLayoutParams() = default;
- BasicLayoutParams(const BasicLayoutParams&) = default;
- BasicLayoutParams(BasicLayoutParams&&) = default;
- BasicLayoutParams& operator = (const BasicLayoutParams&) = default;
- BasicLayoutParams& operator = (BasicLayoutParams&&) = default;
- ~BasicLayoutParams() = default;
-
- bool Validate() const
- {
- return width.Validate() && height.Validate() && margin.Validate() && padding.Validate();
- }
-
- LayoutSideParams width;
- LayoutSideParams height;
- Thickness padding;
- Thickness margin;
- };
-}
diff --git a/src/ui/layout_control.cpp b/src/ui/layout_control.cpp
new file mode 100644
index 00000000..c0c4a7fe
--- /dev/null
+++ b/src/ui/layout_control.cpp
@@ -0,0 +1,41 @@
+#include "layout_control.hpp"
+
+#include "window.hpp"
+
+namespace cru::ui {
+LayoutControl::~LayoutControl() {
+ for (const auto child : children_) delete child;
+}
+
+void LayoutControl::AddChild(Control* control, const int position) {
+ assert(control->GetParent() == nullptr); // The control already has a parent.
+ assert(!dynamic_cast<Window*>(control)); // Can't add a window as child.
+ assert(position >= 0 || position <= this->children_.size()); // The position is out of range.
+
+ children_.insert(this->children_.cbegin() + position, control);
+
+ control->_SetParent(this);
+ control->_SetDescendantWindow(GetWindow());
+
+ OnAddChild(control, position);
+}
+
+void LayoutControl::RemoveChild(const int position) {
+ assert(position >= 0 &&
+ position < this->children_.size()); // The position is out of range.
+
+ const auto i = children_.cbegin() + position;
+ const auto child = *i;
+
+ children_.erase(i);
+
+ child->_SetParent(nullptr);
+ child->_SetDescendantWindow(nullptr);
+
+ OnRemoveChild(child, position);
+}
+
+void LayoutControl::OnAddChild(Control* child, int position) {}
+
+void LayoutControl::OnRemoveChild(Control* child, int position) {}
+} // namespace cru::ui
diff --git a/src/ui/layout_control.hpp b/src/ui/layout_control.hpp
new file mode 100644
index 00000000..53f53186
--- /dev/null
+++ b/src/ui/layout_control.hpp
@@ -0,0 +1,33 @@
+#pragma once
+#include "pre.hpp"
+
+#include "control.hpp"
+
+namespace cru::ui {
+class LayoutControl : public Control {
+ protected:
+ LayoutControl() = default;
+
+ public:
+ LayoutControl(const LayoutControl& other) = delete;
+ LayoutControl(LayoutControl&& other) = delete;
+ LayoutControl& operator=(const LayoutControl& other) = delete;
+ LayoutControl& operator=(LayoutControl&& other) = delete;
+ ~LayoutControl() override;
+
+ const std::vector<Control*>& GetChildren() const override final {
+ return children_;
+ }
+
+ void AddChild(Control* control, int position);
+
+ void RemoveChild(int position);
+
+ protected:
+ virtual void OnAddChild(Control* child, int position);
+ virtual void OnRemoveChild(Control* child, int position);
+
+ private:
+ std::vector<Control*> children_;
+};
+} // namespace cru::ui
diff --git a/src/ui/no_child_control.cpp b/src/ui/no_child_control.cpp
new file mode 100644
index 00000000..e6bbe813
--- /dev/null
+++ b/src/ui/no_child_control.cpp
@@ -0,0 +1,5 @@
+#include "no_child_control.hpp"
+
+namespace cru::ui {
+const std::vector<Control*> NoChildControl::empty_control_vector{};
+}
diff --git a/src/ui/no_child_control.hpp b/src/ui/no_child_control.hpp
new file mode 100644
index 00000000..26b5546f
--- /dev/null
+++ b/src/ui/no_child_control.hpp
@@ -0,0 +1,26 @@
+#pragma once
+#include "pre.hpp"
+
+#include "control.hpp"
+
+namespace cru::ui {
+class NoChildControl : public Control {
+ private:
+ static const std::vector<Control*> empty_control_vector;
+
+ protected:
+ NoChildControl() = default;
+
+ public:
+ NoChildControl(const NoChildControl& other) = delete;
+ NoChildControl(NoChildControl&& other) = delete;
+ NoChildControl& operator=(const NoChildControl& other) = delete;
+ NoChildControl& operator=(NoChildControl&& other) = delete;
+ ~NoChildControl() override = default;
+
+ protected:
+ const std::vector<Control*>& GetChildren() const override final {
+ return empty_control_vector;
+ }
+};
+} // namespace cru::ui
diff --git a/src/ui/render/border_render_object.cpp b/src/ui/render/border_render_object.cpp
new file mode 100644
index 00000000..72cea756
--- /dev/null
+++ b/src/ui/render/border_render_object.cpp
@@ -0,0 +1,234 @@
+#include "border_render_object.hpp"
+
+#include <d2d1_1.h>
+#include <wrl/client.h>
+#include <algorithm>
+
+#include "cru_debug.hpp"
+#include "exception.hpp"
+#include "graph/graph_manager.hpp"
+#include "graph/graph_util.hpp"
+#include "util/com_util.hpp"
+
+namespace cru::ui::render {
+BorderRenderObject::BorderRenderObject(ID2D1Brush* brush) {
+ assert(brush);
+ brush->AddRef();
+ this->border_brush_ = brush;
+ try {
+ RecreateGeometry();
+ } catch (...) {
+ brush->Release();
+ throw;
+ }
+}
+
+BorderRenderObject::~BorderRenderObject() {
+ util::SafeRelease(border_brush_);
+ util::SafeRelease(geometry_);
+ util::SafeRelease(border_outer_geometry_);
+}
+
+void BorderRenderObject::SetBrush(ID2D1Brush* new_brush) {
+ assert(new_brush);
+ util::SafeRelease(border_brush_);
+ new_brush->AddRef();
+ border_brush_ = new_brush;
+}
+
+void BorderRenderObject::Draw(ID2D1RenderTarget* render_target) {
+ render_target->FillGeometry(geometry_, border_brush_);
+ if (const auto child = GetChild()) {
+ auto offset = child->GetOffset();
+ graph::WithTransform(render_target,
+ D2D1::Matrix3x2F::Translation(offset.x, offset.y),
+ [child](auto rt) { child->Draw(rt); });
+ }
+}
+
+RenderObject* BorderRenderObject::HitTest(const Point& point) {
+ if (const auto child = GetChild()) {
+ auto offset = child->GetOffset();
+ Point p{point.x - offset.x, point.y - offset.y};
+ const auto result = child->HitTest(point);
+ if (result != nullptr) {
+ return result;
+ }
+ }
+
+ if (is_enabled_) {
+ BOOL contains;
+ ThrowIfFailed(border_outer_geometry_->FillContainsPoint(
+ D2D1::Point2F(point.x, point.y), D2D1::Matrix3x2F::Identity(),
+ &contains));
+ return contains != 0 ? this : nullptr;
+ } else {
+ const auto margin = GetMargin();
+ const auto size = GetSize();
+ return Rect{margin.left, margin.top,
+ std::max(size.width - margin.GetHorizontalTotal(), 0.0f),
+ std::max(size.height - margin.GetVerticalTotal(), 0.0f)}
+ .IsPointInside(point)
+ ? this
+ : nullptr;
+ }
+}
+
+void BorderRenderObject::OnAddChild(RenderObject* new_child, int position) {
+ assert(GetChildren().size() == 1);
+}
+
+void BorderRenderObject::OnSizeChanged(const Size& old_size,
+ const Size& new_size) {
+ RecreateGeometry();
+}
+
+void BorderRenderObject::OnMeasureCore(const Size& available_size) {
+ const auto margin = GetMargin();
+ const auto padding = GetPadding();
+ Size margin_border_padding_size{
+ margin.GetHorizontalTotal() + padding.GetHorizontalTotal(),
+ margin.GetVerticalTotal() + padding.GetVerticalTotal()};
+
+ if (is_enabled_) {
+ margin_border_padding_size.width += border_thickness_.GetHorizontalTotal();
+ margin_border_padding_size.height += border_thickness_.GetVerticalTotal();
+ }
+
+ auto coerced_margin_border_padding_size = margin_border_padding_size;
+ if (coerced_margin_border_padding_size.width > available_size.width) {
+ debug::DebugMessage(
+ L"Measure: horizontal length of padding, border and margin is bigger "
+ L"than available length.");
+ coerced_margin_border_padding_size.width = available_size.width;
+ }
+ if (coerced_margin_border_padding_size.height > available_size.height) {
+ debug::DebugMessage(
+ L"Measure: vertical length of padding, border and margin is bigger "
+ L"than available length.");
+ coerced_margin_border_padding_size.height = available_size.height;
+ }
+
+ const auto coerced_content_available_size =
+ available_size - coerced_margin_border_padding_size;
+
+ const auto actual_content_size =
+ OnMeasureContent(coerced_content_available_size);
+
+ SetPreferredSize(coerced_margin_border_padding_size + actual_content_size);
+}
+
+void BorderRenderObject::OnLayoutCore(const Rect& rect) {
+ const auto margin = GetMargin();
+ const auto padding = GetPadding();
+ Size margin_border_padding_size{
+ margin.GetHorizontalTotal() + padding.GetHorizontalTotal(),
+ margin.GetVerticalTotal() + padding.GetVerticalTotal()};
+
+ if (is_enabled_) {
+ margin_border_padding_size.width += border_thickness_.GetHorizontalTotal();
+ margin_border_padding_size.height += border_thickness_.GetVerticalTotal();
+ }
+
+ const auto content_available_size =
+ rect.GetSize() - margin_border_padding_size;
+ auto coerced_content_available_size = content_available_size;
+
+ if (coerced_content_available_size.width < 0) {
+ debug::DebugMessage(
+ L"Layout: horizontal length of padding, border and margin is bigger "
+ L"than available length.");
+ coerced_content_available_size.width = 0;
+ }
+ if (coerced_content_available_size.height < 0) {
+ debug::DebugMessage(
+ L"Layout: vertical length of padding, border and margin is bigger "
+ L"than "
+ L"available length.");
+ coerced_content_available_size.height = 0;
+ }
+
+ OnLayoutContent(Rect{
+ margin.left + (is_enabled_ ? border_thickness_.left : 0) + padding.left,
+ margin.top + (is_enabled_ ? border_thickness_.top : 0) + padding.top,
+ coerced_content_available_size.width,
+ coerced_content_available_size.height});
+}
+
+Size BorderRenderObject::OnMeasureContent(const Size& available_size) {
+ const auto child = GetChild();
+ if (child) {
+ child->Measure(available_size);
+ return child->GetPreferredSize();
+ } else {
+ return Size::Zero();
+ }
+}
+
+void BorderRenderObject::OnLayoutContent(const Rect& content_rect) {
+ const auto child = GetChild();
+ if (child) {
+ child->Layout(content_rect);
+ }
+}
+
+void BorderRenderObject::RecreateGeometry() {
+ util::SafeRelease(geometry_);
+ util::SafeRelease(border_outer_geometry_);
+
+ const auto d2d_factory = graph::GraphManager::GetInstance()->GetD2D1Factory();
+
+ Microsoft::WRL::ComPtr<ID2D1PathGeometry> geometry;
+ ThrowIfFailed(d2d_factory->CreatePathGeometry(&geometry));
+
+ Microsoft::WRL::ComPtr<ID2D1PathGeometry> border_outer_geometry;
+ ThrowIfFailed(d2d_factory->CreatePathGeometry(&border_outer_geometry));
+
+ Microsoft::WRL::ComPtr<ID2D1GeometrySink> sink;
+ auto f = [&sink](const Rect& rect, const CornerRadius& corner) {
+ sink->BeginFigure(D2D1::Point2F(rect.left + corner.left_top.x, rect.top),
+ D2D1_FIGURE_BEGIN_FILLED);
+ sink->AddLine(
+ D2D1::Point2F(rect.GetRight() - corner.right_top.x, rect.top));
+ sink->AddQuadraticBezier(D2D1::QuadraticBezierSegment(
+ D2D1::Point2F(rect.GetRight(), rect.top),
+ D2D1::Point2F(rect.GetRight(), rect.top + corner.right_top.y)));
+ sink->AddLine(D2D1::Point2F(rect.GetRight(),
+ rect.GetBottom() - corner.right_bottom.y));
+ sink->AddQuadraticBezier(D2D1::QuadraticBezierSegment(
+ D2D1::Point2F(rect.GetRight(), rect.GetBottom()),
+ D2D1::Point2F(rect.GetRight() - corner.right_bottom.x,
+ rect.GetBottom())));
+ sink->AddLine(
+ D2D1::Point2F(rect.left + corner.left_bottom.x, rect.GetBottom()));
+ sink->AddQuadraticBezier(D2D1::QuadraticBezierSegment(
+ D2D1::Point2F(rect.left, rect.GetBottom()),
+ D2D1::Point2F(rect.left, rect.GetBottom() - corner.left_bottom.y)));
+ sink->AddLine(D2D1::Point2F(rect.left, rect.top + corner.left_top.y));
+ sink->AddQuadraticBezier(D2D1::QuadraticBezierSegment(
+ D2D1::Point2F(rect.left, rect.top),
+ D2D1::Point2F(rect.left + corner.left_top.x, rect.top)));
+ sink->EndFigure(D2D1_FIGURE_END_CLOSED);
+ };
+
+ const auto size = GetSize();
+ const auto margin = GetMargin();
+ const Rect outer_rect{margin.left, margin.top,
+ size.width - margin.GetHorizontalTotal(),
+ size.height - margin.GetVerticalTotal()};
+ ThrowIfFailed(border_outer_geometry->Open(&sink));
+ f(outer_rect, corner_radius_);
+ ThrowIfFailed(sink->Close());
+ sink = nullptr;
+
+ const Rect inner_rect = outer_rect.Shrink(border_thickness_);
+ ThrowIfFailed(geometry->Open(&sink));
+ f(outer_rect, corner_radius_);
+ f(inner_rect, corner_radius_);
+ ThrowIfFailed(sink->Close());
+ sink = nullptr;
+
+ geometry_ = geometry.Detach();
+ border_outer_geometry_ = border_outer_geometry.Detach();
+}
+} // namespace cru::ui::render
diff --git a/src/ui/render/border_render_object.hpp b/src/ui/render/border_render_object.hpp
new file mode 100644
index 00000000..80db648a
--- /dev/null
+++ b/src/ui/render/border_render_object.hpp
@@ -0,0 +1,92 @@
+#pragma once
+#include "pre.hpp"
+
+#include <wrl/client.h> // for ComPtr
+
+#include "render_object.hpp"
+
+// forward declarations
+struct ID2D1Brush;
+struct ID2D1Geometry;
+
+namespace cru::ui::render {
+struct CornerRadius {
+ constexpr CornerRadius()
+ : left_top(), right_top(), left_bottom(), right_bottom() {}
+ constexpr CornerRadius(const Point& value)
+ : left_top(value),
+ right_top(value),
+ left_bottom(value),
+ right_bottom(value) {}
+ constexpr CornerRadius(Point left_top, Point right_top, Point left_bottom,
+ Point right_bottom)
+ : left_top(left_top),
+ right_top(right_top),
+ left_bottom(left_bottom),
+ right_bottom(right_bottom) {}
+
+ Point left_top;
+ Point right_top;
+ Point left_bottom;
+ Point right_bottom;
+};
+
+class BorderRenderObject : public RenderObject {
+ public:
+ explicit BorderRenderObject(ID2D1Brush* brush);
+ BorderRenderObject(const BorderRenderObject& other) = delete;
+ BorderRenderObject(BorderRenderObject&& other) = delete;
+ BorderRenderObject& operator=(const BorderRenderObject& other) = delete;
+ BorderRenderObject& operator=(BorderRenderObject&& other) = delete;
+ ~BorderRenderObject() override;
+
+ bool IsEnabled() const { return is_enabled_; }
+ void SetEnabled(bool enabled) { is_enabled_ = enabled; }
+
+ ID2D1Brush* GetBrush() const { return border_brush_; }
+ void SetBrush(ID2D1Brush* new_brush);
+
+ Thickness GetBorderWidth() const { return border_thickness_; }
+ void SetBorderWidth(const Thickness& thickness) {
+ border_thickness_ = thickness;
+ }
+
+ CornerRadius GetCornerRadius() const { return corner_radius_; }
+ void SetCornerRadius(const CornerRadius& new_corner_radius) {
+ corner_radius_ = new_corner_radius;
+ }
+
+ void Refresh() { RecreateGeometry(); }
+
+ void Draw(ID2D1RenderTarget* render_target) override;
+
+ RenderObject* HitTest(const Point& point) override;
+
+ protected:
+ void OnAddChild(RenderObject* new_child, int position) override;
+
+ void OnSizeChanged(const Size& old_size, const Size& new_size) override;
+
+ void OnMeasureCore(const Size& available_size) override;
+ void OnLayoutCore(const Rect& rect) override;
+ Size OnMeasureContent(const Size& available_size) override;
+ void OnLayoutContent(const Rect& content_rect) override;
+
+ private:
+ RenderObject* GetChild() const {
+ return GetChildren().empty() ? nullptr : GetChildren()[0];
+ }
+
+ void RecreateGeometry();
+
+ private:
+ bool is_enabled_ = false;
+
+ ID2D1Brush* border_brush_ = nullptr;
+ Thickness border_thickness_ = Thickness::Zero();
+ CornerRadius corner_radius_{};
+
+ ID2D1Geometry* geometry_ = nullptr;
+ ID2D1Geometry* border_outer_geometry_ = nullptr;
+};
+} // namespace cru::ui::render
diff --git a/src/ui/render/flex_layout_render_object.cpp b/src/ui/render/flex_layout_render_object.cpp
new file mode 100644
index 00000000..e4d327f1
--- /dev/null
+++ b/src/ui/render/flex_layout_render_object.cpp
@@ -0,0 +1,237 @@
+#include "flex_layout_render_object.hpp"
+
+#include <algorithm>
+#include <functional>
+
+#include "cru_debug.hpp"
+#include "graph/graph_util.hpp"
+
+namespace cru::ui::render {
+FlexChildLayoutData* FlexLayoutRenderObject::GetChildLayoutData(int position) {
+ assert(position >= 0 &&
+ position < child_layout_data_.size()); // Position out of bound.
+
+ return &child_layout_data_[position];
+}
+
+void FlexLayoutRenderObject::Draw(ID2D1RenderTarget* render_target) {
+ for (const auto child : GetChildren()) {
+ auto offset = child->GetOffset();
+ graph::WithTransform(render_target,
+ D2D1::Matrix3x2F::Translation(offset.x, offset.y),
+ [child](auto rt) { child->Draw(rt); });
+ }
+}
+
+RenderObject* FlexLayoutRenderObject::HitTest(const Point& point) {
+ const auto& children = GetChildren();
+ for (auto i = children.crbegin(); i != children.crend(); ++i) {
+ auto offset = (*i)->GetOffset();
+ Point p{point.x - offset.x, point.y - offset.y};
+ const auto result = (*i)->HitTest(point);
+ if (result != nullptr) {
+ return result;
+ }
+ }
+
+ const auto margin = GetMargin();
+ const auto size = GetSize();
+ return Rect{margin.left, margin.top,
+ std::max(size.width - margin.GetHorizontalTotal(), 0.0f),
+ std::max(size.height - margin.GetVerticalTotal(), 0.0f)}
+ .IsPointInside(point)
+ ? this
+ : nullptr;
+} // namespace cru::ui::render
+
+void FlexLayoutRenderObject::OnAddChild(RenderObject* new_child, int position) {
+ child_layout_data_.emplace(child_layout_data_.cbegin() + position);
+}
+
+void FlexLayoutRenderObject::OnRemoveChild(RenderObject* removed_child,
+ int position) {
+ child_layout_data_.erase(child_layout_data_.cbegin() + position);
+}
+
+Size FlexLayoutRenderObject::OnMeasureContent(const Size& available_size) {
+ std::vector<int> has_basis_children;
+ std::vector<int> no_basis_children;
+ std::vector<int> grow_children;
+ std::vector<int> shrink_chilren;
+ for (int i = 0; i < child_layout_data_.size(); i++) {
+ const auto& layout_data = child_layout_data_[i];
+ if (layout_data.flex_basis.has_value())
+ has_basis_children.push_back(i);
+ else
+ no_basis_children.push_back(i);
+ if (layout_data.flex_grow > 0) grow_children.push_back(i);
+ if (layout_data.flex_shrink > 0) shrink_chilren.push_back(i);
+ }
+
+ std::function<float(const Size&)> get_main_length;
+ std::function<float(const Size&)> get_cross_length;
+ std::function<Size(float main, float cross)> create_size;
+
+ if (direction_ == FlexDirection::Horizontal ||
+ direction_ == FlexDirection::HorizontalReverse) {
+ get_main_length = [](const Size& size) { return size.width; };
+ get_cross_length = [](const Size& size) { return size.height; };
+ create_size = [](float main, float cross) { return Size(main, cross); };
+ } else {
+ get_main_length = [](const Size& size) { return size.height; };
+ get_cross_length = [](const Size& size) { return size.width; };
+ create_size = [](float main, float cross) { return Size(cross, main); };
+ }
+
+ const auto& children = GetChildren();
+
+ float remain_main_length = get_main_length(available_size);
+ float max_cross_length = 0;
+
+ for (const int i : has_basis_children) {
+ const auto child = children[i];
+ const float basis = child_layout_data_[i].flex_basis.value();
+ child->Measure(create_size(basis, get_cross_length(available_size)));
+ remain_main_length -= basis;
+ const float child_preferred_cross_length =
+ get_cross_length(child->GetPreferredSize());
+ child->SetPreferredSize(create_size(basis, child_preferred_cross_length));
+ max_cross_length = std::max(max_cross_length, child_preferred_cross_length);
+ }
+
+ for (const int i : no_basis_children) {
+ const auto child = children[i];
+ child->Measure(create_size(remain_main_length > 0 ? remain_main_length : 0,
+ get_cross_length(available_size)));
+ remain_main_length -= get_main_length(child->GetPreferredSize());
+ max_cross_length =
+ std::max(max_cross_length, get_cross_length(child->GetPreferredSize()));
+ }
+
+ if (remain_main_length > 0) {
+ float total_grow = 0;
+ for (const int i : grow_children)
+ total_grow += child_layout_data_[i].flex_grow;
+
+ for (const int i : grow_children) {
+ const float distributed_grow_length =
+ remain_main_length * (child_layout_data_[i].flex_grow / total_grow);
+ const auto child = children[i];
+ const float new_main_length =
+ get_main_length(child->GetPreferredSize()) + distributed_grow_length;
+ child->Measure(
+ create_size(new_main_length, get_cross_length(available_size)));
+ const float new_child_preferred_cross_length =
+ get_cross_length(child->GetPreferredSize());
+ child->SetPreferredSize(
+ create_size(new_main_length, new_child_preferred_cross_length));
+ max_cross_length =
+ std::max(max_cross_length, new_child_preferred_cross_length);
+ }
+ }
+
+ if (remain_main_length < 0) {
+ float total_shrink = 0;
+ for (const int i : shrink_chilren)
+ total_shrink += child_layout_data_[i].flex_shrink;
+
+ for (const int i : shrink_chilren) {
+ const float distributed_shrink_length = // negative
+ remain_main_length *
+ (child_layout_data_[i].flex_shrink / total_shrink);
+ const auto child = children[i];
+ float new_main_length = get_main_length(child->GetPreferredSize()) +
+ distributed_shrink_length;
+ new_main_length = new_main_length > 0 ? new_main_length : 0;
+ child->Measure(
+ create_size(new_main_length, get_cross_length(available_size)));
+ const float new_child_preferred_cross_length =
+ get_cross_length(child->GetPreferredSize());
+ child->SetPreferredSize(
+ create_size(new_main_length, new_child_preferred_cross_length));
+ max_cross_length =
+ std::max(max_cross_length, new_child_preferred_cross_length);
+ }
+ }
+
+ return create_size(get_main_length(available_size) -
+ (remain_main_length > 0 ? remain_main_length : 0),
+ max_cross_length);
+}
+
+void FlexLayoutRenderObject::OnLayoutContent(const Rect& content_rect) {
+ auto calculate_anchor = [](Alignment alignment, float start_point,
+ float total_length,
+ float content_length) -> float {
+ switch (alignment) {
+ case Alignment::Start:
+ return start_point;
+ case Alignment::Center:
+ return start_point + (total_length - content_length) / 2.0f;
+ case Alignment::End:
+ return start_point + total_length - content_length;
+ default:
+ UnreachableCode();
+ }
+ };
+
+ const auto& children = GetChildren();
+ if (direction_ == FlexDirection::Horizontal ||
+ direction_ == FlexDirection::HorizontalReverse) {
+ float actual_content_width = 0;
+ for (const auto child : children) {
+ actual_content_width += child->GetPreferredSize().width;
+ }
+
+ const float content_anchor_x = calculate_anchor(
+ content_main_align_, 0, content_rect.width, actual_content_width);
+
+ auto anchor_x = 0;
+ for (int i = 0; i < children.size(); i++) {
+ const auto child = children[i];
+ const auto size = child->GetPreferredSize();
+
+ float real_anchor_x = anchor_x + content_anchor_x;
+ if (direction_ == FlexDirection::Horizontal)
+ real_anchor_x = content_rect.left + real_anchor_x;
+ else
+ real_anchor_x = content_rect.GetRight() - real_anchor_x;
+ child->Layout(Rect{
+ real_anchor_x,
+ calculate_anchor(child_layout_data_[i].alignment, content_rect.top,
+ content_rect.height, size.height),
+ size.width, size.height});
+
+ anchor_x += size.width;
+ }
+ } else {
+ float actual_content_height = 0;
+ for (const auto child : children) {
+ actual_content_height = child->GetPreferredSize().height;
+ }
+
+ const float content_anchor_y = calculate_anchor(
+ content_main_align_, 0, content_rect.height, actual_content_height);
+
+ auto anchor_y = 0;
+ for (int i = 0; i < children.size(); i++) {
+ const auto child = children[i];
+ const auto size = child->GetPreferredSize();
+
+ float real_anchor_y = anchor_y + content_anchor_y;
+ if (direction_ == FlexDirection::Vertical) {
+ real_anchor_y = content_rect.top + real_anchor_y;
+ } else {
+ real_anchor_y = content_rect.GetBottom() - real_anchor_y;
+ }
+ child->Layout(Rect{
+ real_anchor_y,
+ calculate_anchor(child_layout_data_[i].alignment, content_rect.left,
+ content_rect.width, size.width),
+ size.width, size.height});
+
+ anchor_y += size.height;
+ }
+ }
+}
+} // namespace cru::ui::render
diff --git a/src/ui/render/flex_layout_render_object.hpp b/src/ui/render/flex_layout_render_object.hpp
new file mode 100644
index 00000000..ac4c2c0f
--- /dev/null
+++ b/src/ui/render/flex_layout_render_object.hpp
@@ -0,0 +1,59 @@
+#pragma once
+#include "pre.hpp"
+
+#include <optional>
+
+#include "render_object.hpp"
+
+namespace cru::ui::render {
+enum class FlexDirection {
+ Horizontal,
+ HorizontalReverse,
+ Vertical,
+ VertivalReverse
+};
+
+enum class Alignment { Start, End, Center };
+
+struct FlexChildLayoutData {
+ std::optional<float> flex_basis; // nullopt stands for content
+ float flex_grow = 0;
+ float flex_shrink = 0;
+ Alignment alignment = Alignment::Center;
+};
+
+class FlexLayoutRenderObject : public RenderObject {
+ public:
+ FlexLayoutRenderObject() = default;
+ FlexLayoutRenderObject(const FlexLayoutRenderObject& other) = delete;
+ FlexLayoutRenderObject& operator=(const FlexLayoutRenderObject& other) =
+ delete;
+ FlexLayoutRenderObject(FlexLayoutRenderObject&& other) = delete;
+ FlexLayoutRenderObject& operator=(FlexLayoutRenderObject&& other) = delete;
+ ~FlexLayoutRenderObject() override = default;
+
+ FlexDirection GetFlexDirection() const { return direction_; }
+ void SetFlexDirection(FlexDirection direction) { direction_ = direction; }
+
+ Alignment GetContentMainAlign() const { return content_main_align_; }
+ void SetContentMainAlign(Alignment align) { content_main_align_ = align; }
+
+ FlexChildLayoutData* GetChildLayoutData(int position);
+
+ void Draw(ID2D1RenderTarget* render_target) override;
+
+ RenderObject* HitTest(const Point& point) override;
+
+ protected:
+ void OnAddChild(RenderObject* new_child, int position) override;
+ void OnRemoveChild(RenderObject* removed_child, int position) override;
+
+ Size OnMeasureContent(const Size& available_size) override;
+ void OnLayoutContent(const Rect& content_rect) override;
+
+ private:
+ FlexDirection direction_ = FlexDirection::Horizontal;
+ Alignment content_main_align_ = Alignment::Start;
+ std::vector<FlexChildLayoutData> child_layout_data_{};
+};
+} // namespace cru::ui::render
diff --git a/src/ui/render/render_object.cpp b/src/ui/render/render_object.cpp
new file mode 100644
index 00000000..5c6af580
--- /dev/null
+++ b/src/ui/render/render_object.cpp
@@ -0,0 +1,115 @@
+#include "render_object.hpp"
+
+#include <algorithm>
+
+#include "cru_debug.hpp"
+
+namespace cru::ui::render {
+void RenderObject::AddChild(RenderObject* render_object, const int position) {
+ assert(render_object->GetParent() ==
+ nullptr); // Render object already has a parent.
+ assert(position >= 0); // Position index is less than 0.
+ assert(position <= children_.size()); // Position index is out of bound.
+
+ children_.insert(children_.cbegin() + position, render_object);
+ render_object->SetParent(this);
+ OnAddChild(render_object, position);
+}
+
+void RenderObject::RemoveChild(const int position) {
+ assert(position >= 0); // Position index is less than 0.
+ assert(position < children_.size()); // Position index is out of bound.
+
+ const auto i = children_.cbegin() + position;
+ const auto removed_child = *i;
+ children_.erase(i);
+ removed_child->SetParent(nullptr);
+ OnRemoveChild(removed_child, position);
+}
+
+void RenderObject::Measure(const Size& available_size) {
+ OnMeasureCore(available_size);
+}
+
+void RenderObject::Layout(const Rect& rect) {
+ SetOffset(rect.GetLeftTop());
+ SetSize(rect.GetSize());
+ OnLayoutCore(Rect{Point::Zero(), rect.GetSize()});
+}
+
+void RenderObject::OnParentChanged(RenderObject* old_parent,
+ RenderObject* new_parent) {}
+
+void RenderObject::OnAddChild(RenderObject* new_child, int position) {}
+
+void RenderObject::OnRemoveChild(RenderObject* removed_child, int position) {}
+
+void RenderObject::OnSizeChanged(const Size& old_size, const Size& new_size) {}
+
+void RenderObject::OnMeasureCore(const Size& available_size) {
+ Size margin_padding_size{
+ margin_.GetHorizontalTotal() + padding_.GetHorizontalTotal(),
+ margin_.GetVerticalTotal() + padding_.GetVerticalTotal()};
+
+ auto coerced_margin_padding_size = margin_padding_size;
+ if (coerced_margin_padding_size.width > available_size.width) {
+ debug::DebugMessage(
+ L"Measure: horizontal length of padding and margin is bigger than "
+ L"available length.");
+ coerced_margin_padding_size.width = available_size.width;
+ }
+ if (coerced_margin_padding_size.height > available_size.height) {
+ debug::DebugMessage(
+ L"Measure: vertical length of padding and margin is bigger than "
+ L"available length.");
+ coerced_margin_padding_size.height = available_size.height;
+ }
+
+ const auto coerced_content_available_size =
+ available_size - coerced_margin_padding_size;
+ const auto actual_content_size =
+ OnMeasureContent(coerced_content_available_size);
+
+ SetPreferredSize(coerced_margin_padding_size + actual_content_size);
+}
+
+void RenderObject::OnLayoutCore(const Rect& rect) {
+ Size margin_padding_size{
+ margin_.GetHorizontalTotal() + padding_.GetHorizontalTotal(),
+ margin_.GetVerticalTotal() + padding_.GetVerticalTotal()};
+ const auto content_available_size = rect.GetSize() - margin_padding_size;
+ auto coerced_content_available_size = content_available_size;
+
+ if (coerced_content_available_size.width < 0) {
+ debug::DebugMessage(
+ L"Layout: horizontal length of padding and margin is bigger than "
+ L"available length.");
+ coerced_content_available_size.width = 0;
+ }
+ if (coerced_content_available_size.height < 0) {
+ debug::DebugMessage(
+ L"Layout: vertical length of padding and margin is bigger than "
+ L"available length.");
+ coerced_content_available_size.height = 0;
+ }
+
+ OnLayoutContent(Rect{margin_.left + padding_.left, margin_.top + padding_.top,
+ coerced_content_available_size.width,
+ coerced_content_available_size.height});
+}
+
+Rect RenderObject::GetContentRect() const {
+ Rect rect{Point::Zero(), GetSize()};
+ rect = rect.Shrink(GetMargin());
+ rect = rect.Shrink(GetPadding());
+ rect.width = std::max(rect.width, 0.0f);
+ rect.height = std::max(rect.height, 0.0f);
+ return rect;
+}
+
+void RenderObject::SetParent(RenderObject* new_parent) {
+ const auto old_parent = parent_;
+ parent_ = new_parent;
+ OnParentChanged(old_parent, new_parent);
+}
+} // namespace cru::ui::render
diff --git a/src/ui/render/render_object.hpp b/src/ui/render/render_object.hpp
new file mode 100644
index 00000000..824b88e6
--- /dev/null
+++ b/src/ui/render/render_object.hpp
@@ -0,0 +1,97 @@
+#pragma once
+#include "pre.hpp"
+
+#include <vector>
+
+#include "base.hpp"
+#include "ui/ui_base.hpp"
+
+// forward declarations
+struct ID2D1RenderTarget;
+namespace cru::ui {
+class Control;
+}
+
+namespace cru::ui::render {
+
+class RenderObject : public Object {
+ protected:
+ RenderObject() = default;
+
+ public:
+ RenderObject(const RenderObject& other) = delete;
+ RenderObject(RenderObject&& other) = delete;
+ RenderObject& operator=(const RenderObject& other) = delete;
+ RenderObject& operator=(RenderObject&& other) = delete;
+ ~RenderObject() override = default;
+
+ Control* GetAttachedControl() const { return control_; }
+ void SetAttachedControl(Control* new_control) { control_ = new_control; }
+
+ RenderObject* GetParent() const { return parent_; }
+
+ const std::vector<RenderObject*>& GetChildren() const { return children_; }
+ void AddChild(RenderObject* render_object, int position);
+ void RemoveChild(int position);
+
+ Point GetOffset() const { return offset_; }
+ void SetOffset(const Point& offset) { offset_ = offset; }
+ Size GetSize() const { return size_; }
+ void SetSize(const Size& size) {
+ const auto old_size = size_;
+ size_ = size;
+ OnSizeChanged(old_size, size);
+ }
+
+ Thickness GetMargin() const { return margin_; }
+ void SetMargin(const Thickness& margin) { margin_ = margin; }
+
+ Thickness GetPadding() const { return padding_; }
+ void SetPadding(const Thickness& padding) { padding_ = padding; }
+
+ Size GetPreferredSize() const { return preferred_size_; }
+ void SetPreferredSize(const Size& preferred_size) {
+ preferred_size_ = preferred_size;
+ }
+
+ void Measure(const Size& available_size);
+ void Layout(const Rect& rect);
+
+ virtual void Draw(ID2D1RenderTarget* render_target) = 0;
+
+ virtual RenderObject* HitTest(const Point& point) = 0;
+
+ protected:
+ virtual void OnParentChanged(RenderObject* old_parent,
+ RenderObject* new_parent);
+
+ virtual void OnAddChild(RenderObject* new_child, int position);
+ virtual void OnRemoveChild(RenderObject* removed_child, int position);
+
+ virtual void OnSizeChanged(const Size& old_size, const Size& new_size);
+
+ virtual void OnMeasureCore(const Size& available_size);
+ virtual void OnLayoutCore(const Rect& rect);
+ virtual Size OnMeasureContent(const Size& available_size) = 0;
+ virtual void OnLayoutContent(const Rect& content_rect) = 0;
+
+ Rect GetContentRect() const;
+
+ private:
+ void SetParent(RenderObject* new_parent);
+
+ private:
+ Control* control_ = nullptr;
+
+ RenderObject* parent_ = nullptr;
+ std::vector<RenderObject*> children_{};
+
+ Point offset_ = Point::Zero();
+ Size size_ = Size::Zero();
+
+ Thickness margin_ = Thickness::Zero();
+ Thickness padding_ = Thickness::Zero();
+
+ Size preferred_size_ = Size::Zero();
+};
+} // namespace cru::ui::render
diff --git a/src/ui/render/text_render_object.cpp b/src/ui/render/text_render_object.cpp
new file mode 100644
index 00000000..69563ad7
--- /dev/null
+++ b/src/ui/render/text_render_object.cpp
@@ -0,0 +1,148 @@
+#include "text_render_object.hpp"
+
+#include <d2d1.h>
+#include <dwrite.h>
+#include <algorithm>
+
+#include "exception.hpp"
+#include "graph/graph_manager.hpp"
+#include "graph/graph_util.hpp"
+#include "util/com_util.hpp"
+
+namespace cru::ui::render {
+TextRenderObject::TextRenderObject(ID2D1Brush* brush, IDWriteTextFormat* format,
+ ID2D1Brush* selection_brush) {
+ assert(brush);
+ assert(format);
+ assert(selection_brush);
+ brush->AddRef();
+ format->AddRef();
+ selection_brush->AddRef();
+ this->brush_ = brush;
+ this->text_format_ = format;
+ this->selection_brush_ = selection_brush;
+ try {
+ RecreateTextLayout();
+ } catch (...) {
+ brush->Release();
+ format->Release();
+ selection_brush->Release();
+ throw;
+ }
+}
+
+TextRenderObject::~TextRenderObject() {
+ util::SafeRelease(brush_);
+ util::SafeRelease(text_format_);
+ util::SafeRelease(text_layout_);
+ util::SafeRelease(selection_brush_);
+}
+
+void TextRenderObject::SetBrush(ID2D1Brush* new_brush) {
+ assert(new_brush);
+ util::SafeRelease(brush_);
+ new_brush->AddRef();
+ brush_ = new_brush;
+}
+
+void TextRenderObject::SetTextFormat(IDWriteTextFormat* new_text_format) {
+ assert(new_text_format);
+ util::SafeRelease(text_format_);
+ new_text_format->AddRef();
+ text_format_ = new_text_format;
+ RecreateTextLayout();
+}
+
+void TextRenderObject::SetSelectionBrush(ID2D1Brush* new_brush) {
+ assert(new_brush);
+ util::SafeRelease(selection_brush_);
+ new_brush->AddRef();
+ selection_brush_ = new_brush;
+}
+
+namespace {
+void DrawSelectionRect(ID2D1RenderTarget* render_target,
+ IDWriteTextLayout* layout, ID2D1Brush* brush,
+ const std::optional<TextRange> range) {
+ if (range.has_value()) {
+ DWRITE_TEXT_METRICS text_metrics{};
+ ThrowIfFailed(layout->GetMetrics(&text_metrics));
+ const auto metrics_count =
+ text_metrics.lineCount * text_metrics.maxBidiReorderingDepth;
+
+ std::vector<DWRITE_HIT_TEST_METRICS> hit_test_metrics(metrics_count);
+ UINT32 actual_count;
+ layout->HitTestTextRange(range.value().position, range.value().count, 0, 0,
+ hit_test_metrics.data(), metrics_count,
+ &actual_count);
+
+ hit_test_metrics.erase(hit_test_metrics.cbegin() + actual_count,
+ hit_test_metrics.cend());
+
+ for (const auto& metrics : hit_test_metrics)
+ render_target->FillRoundedRectangle(
+ D2D1::RoundedRect(D2D1::RectF(metrics.left, metrics.top,
+ metrics.left + metrics.width,
+ metrics.top + metrics.height),
+ 3, 3),
+ brush);
+ }
+}
+} // namespace
+
+void TextRenderObject::Draw(ID2D1RenderTarget* render_target) {
+ graph::WithTransform(
+ render_target,
+ D2D1::Matrix3x2F::Translation(GetMargin().left + GetPadding().left,
+ GetMargin().top + GetPadding().top),
+ [this](auto rt) {
+ DrawSelectionRect(rt, text_layout_, selection_brush_, selection_range_);
+ rt->DrawTextLayout(D2D1::Point2F(), text_layout_, brush_);
+ });
+}
+
+RenderObject* TextRenderObject::HitTest(const Point& point) {
+ const auto margin = GetMargin();
+ const auto size = GetSize();
+ return Rect{margin.left, margin.top,
+ std::max(size.width - margin.GetHorizontalTotal(), 0.0f),
+ std::max(size.height - margin.GetVerticalTotal(), 0.0f)}
+ .IsPointInside(point)
+ ? this
+ : nullptr;
+}
+
+void TextRenderObject::OnSizeChanged(const Size& old_size,
+ const Size& new_size) {
+ const auto&& size = GetContentRect().GetSize();
+ ThrowIfFailed(text_layout_->SetMaxWidth(size.width));
+ ThrowIfFailed(text_layout_->SetMaxHeight(size.height));
+}
+
+Size TextRenderObject::OnMeasureContent(const Size& available_size) {
+ ThrowIfFailed(text_layout_->SetMaxWidth(available_size.width));
+ ThrowIfFailed(text_layout_->SetMaxHeight(available_size.height));
+
+ DWRITE_TEXT_METRICS metrics;
+ ThrowIfFailed(text_layout_->GetMetrics(&metrics));
+
+ return Size(metrics.width, metrics.height);
+}
+
+void TextRenderObject::OnLayoutContent(const Rect& content_rect) {}
+
+void TextRenderObject::RecreateTextLayout() {
+ assert(text_format_ != nullptr);
+
+ util::SafeRelease(text_layout_);
+
+ const auto dwrite_factory =
+ graph::GraphManager::GetInstance()->GetDWriteFactory();
+
+ const auto&& size = GetContentRect().GetSize();
+
+ ThrowIfFailed(dwrite_factory->CreateTextLayout(
+ text_.c_str(), static_cast<UINT32>(text_.size()), text_format_,
+ size.width, size.height, &text_layout_));
+}
+} // namespace cru::ui::render
diff --git a/src/ui/render/text_render_object.hpp b/src/ui/render/text_render_object.hpp
new file mode 100644
index 00000000..7827f994
--- /dev/null
+++ b/src/ui/render/text_render_object.hpp
@@ -0,0 +1,69 @@
+#pragma once
+#include "pre.hpp"
+
+#include "render_object.hpp"
+
+// forward declarations
+struct ID2D1Brush;
+struct IDWriteTextFormat;
+struct IDWriteTextLayout;
+
+namespace cru::ui::render {
+class TextRenderObject : public RenderObject {
+ public:
+ TextRenderObject(ID2D1Brush* brush, IDWriteTextFormat* format,
+ ID2D1Brush* selection_brush);
+ TextRenderObject(const TextRenderObject& other) = delete;
+ TextRenderObject(TextRenderObject&& other) = delete;
+ TextRenderObject& operator=(const TextRenderObject& other) = delete;
+ TextRenderObject& operator=(TextRenderObject&& other) = delete;
+ ~TextRenderObject() override;
+
+ String GetText() const { return text_; }
+ void SetText(String new_text) {
+ text_ = std::move(new_text);
+ RecreateTextLayout();
+ }
+
+ ID2D1Brush* GetBrush() const { return brush_; }
+ void SetBrush(ID2D1Brush* new_brush);
+
+ IDWriteTextFormat* GetTextFormat() const { return text_format_; }
+ void SetTextFormat(IDWriteTextFormat* new_text_format);
+
+ std::optional<TextRange> GetSelectionRange() const {
+ return selection_range_;
+ }
+ void SetSelectionRange(std::optional<TextRange> new_range) {
+ selection_range_ = std::move(new_range);
+ }
+
+ ID2D1Brush* GetSelectionBrush() const { return selection_brush_; }
+ void SetSelectionBrush(ID2D1Brush* new_brush);
+
+ void Refresh() { RecreateTextLayout(); }
+
+ void Draw(ID2D1RenderTarget* render_target) override;
+
+ RenderObject* HitTest(const Point& point) override;
+
+ protected:
+ void OnSizeChanged(const Size& old_size, const Size& new_size) override;
+
+ Size OnMeasureContent(const Size& available_size) override;
+ void OnLayoutContent(const Rect& content_rect) override;
+
+ private:
+ void RecreateTextLayout();
+
+ private:
+ String text_;
+
+ ID2D1Brush* brush_ = nullptr;
+ IDWriteTextFormat* text_format_ = nullptr;
+ IDWriteTextLayout* text_layout_ = nullptr;
+
+ std::optional<TextRange> selection_range_ = std::nullopt;
+ ID2D1Brush* selection_brush_ = nullptr;
+};
+} // namespace cru::ui::render
diff --git a/src/ui/render/window_render_object.cpp b/src/ui/render/window_render_object.cpp
new file mode 100644
index 00000000..44c3c426
--- /dev/null
+++ b/src/ui/render/window_render_object.cpp
@@ -0,0 +1,46 @@
+#include "window_render_object.hpp"
+
+#include "graph/graph_util.hpp"
+#include "ui/window.hpp"
+
+namespace cru::ui::render {
+void WindowRenderObject::MeasureAndLayout() {
+ const auto client_size = window_->GetClientSize();
+ Measure(client_size);
+ Layout(Rect{Point::Zero(), client_size});
+}
+
+void WindowRenderObject::Draw(ID2D1RenderTarget* render_target) {
+ if (const auto child = GetChild()) {
+ auto offset = child->GetOffset();
+ graph::WithTransform(render_target,
+ D2D1::Matrix3x2F::Translation(offset.x, offset.y),
+ [child](auto rt) { child->Draw(rt); });
+ }
+}
+
+RenderObject* WindowRenderObject::HitTest(const Point& point) {
+ if (const auto child = GetChild()) {
+ auto offset = child->GetOffset();
+ Point p{point.x - offset.x, point.y - offset.y};
+ const auto result = child->HitTest(point);
+ if (result != nullptr) {
+ return result;
+ }
+ }
+ return Rect{Point::Zero(), GetSize()}.IsPointInside(point) ? this : nullptr;
+}
+
+void WindowRenderObject::OnAddChild(RenderObject* new_child, int position) {
+ assert(GetChildren().size() == 1);
+}
+
+Size WindowRenderObject::OnMeasureContent(const Size& available_size) {
+ if (const auto child = GetChild()) child->Measure(available_size);
+ return available_size;
+}
+
+void WindowRenderObject::OnLayoutContent(const Rect& content_rect) {
+ if (const auto child = GetChild()) child->Layout(content_rect);
+}
+} // namespace cru::ui::render
diff --git a/src/ui/render/window_render_object.hpp b/src/ui/render/window_render_object.hpp
new file mode 100644
index 00000000..63eb8588
--- /dev/null
+++ b/src/ui/render/window_render_object.hpp
@@ -0,0 +1,40 @@
+#pragma once
+#include "pre.hpp"
+
+#include "render_object.hpp"
+
+namespace cru::ui {
+class Window;
+}
+
+namespace cru::ui::render {
+class WindowRenderObject : public RenderObject {
+ public:
+ WindowRenderObject(Window* window) : window_(window) {}
+ WindowRenderObject(const WindowRenderObject& other) = delete;
+ WindowRenderObject(WindowRenderObject&& other) = delete;
+ WindowRenderObject& operator=(const WindowRenderObject& other) = delete;
+ WindowRenderObject& operator=(WindowRenderObject&& other) = delete;
+ ~WindowRenderObject() override = default;
+
+ void MeasureAndLayout();
+
+ void Draw(ID2D1RenderTarget* render_target) override;
+
+ RenderObject* HitTest(const Point& point) override;
+
+ protected:
+ void OnAddChild(RenderObject* new_child, int position) override;
+
+ Size OnMeasureContent(const Size& available_size) override;
+ void OnLayoutContent(const Rect& content_rect) override;
+
+ private:
+ RenderObject* GetChild() const {
+ return GetChildren().empty() ? nullptr : GetChildren()[0];
+ }
+
+ private:
+ Window* window_;
+};
+} // namespace cru::ui::render
diff --git a/src/ui/ui_base.cpp b/src/ui/ui_base.cpp
deleted file mode 100644
index c91fcd7b..00000000
--- a/src/ui/ui_base.cpp
+++ /dev/null
@@ -1,23 +0,0 @@
-#include "ui_base.hpp"
-
-#include "system_headers.hpp"
-
-namespace cru::ui
-{
- bool IsKeyDown(const int virtual_code)
- {
- const auto result = ::GetKeyState(virtual_code);
- return (static_cast<unsigned short>(result) & 0x8000) != 0;
- }
-
- bool IsKeyToggled(const int virtual_code)
- {
- const auto result = ::GetKeyState(virtual_code);
- return (static_cast<unsigned short>(result) & 1) != 0;
- }
-
- bool IsAnyMouseButtonDown()
- {
- return IsKeyDown(VK_LBUTTON) || IsKeyDown(VK_RBUTTON) || IsKeyDown(VK_MBUTTON);
- }
-}
diff --git a/src/ui/ui_base.hpp b/src/ui/ui_base.hpp
index b898b2ed..ba6c8b9a 100644
--- a/src/ui/ui_base.hpp
+++ b/src/ui/ui_base.hpp
@@ -1,230 +1,238 @@
#pragma once
-
-// ReSharper disable once CppUnusedIncludeDirective
#include "pre.hpp"
#include <optional>
+namespace cru::ui {
+struct Point final {
+ constexpr static Point Zero() { return Point(0, 0); }
+
+ constexpr Point() = default;
+ constexpr Point(const float x, const float y) : x(x), y(y) {}
+
+ float x = 0;
+ float y = 0;
+};
+
+constexpr bool operator==(const Point& left, const Point& right) {
+ return left.x == right.x && left.y == right.y;
+}
-namespace cru::ui
-{
- struct Point
- {
- constexpr static Point Zero()
- {
- return Point(0, 0);
- }
-
- constexpr Point() = default;
- constexpr Point(const float x, const float y) : x(x), y(y) { }
-
- float x = 0;
- float y = 0;
- };
-
- constexpr bool operator==(const Point& left, const Point& right)
- {
- return left.x == right.x && left.y == right.y;
- }
-
- constexpr bool operator!=(const Point& left, const Point& right)
- {
- return !(left == right);
- }
-
- struct Size
- {
- constexpr static Size Zero()
- {
- return Size(0, 0);
- }
-
- constexpr Size() = default;
- constexpr Size(const float width, const float height) : width(width), height(height) { }
-
- float width = 0;
- float height = 0;
- };
-
- constexpr Size operator + (const Size& left, const Size& right)
- {
- return Size(left.width + right.width, left.height + right.height);
- }
-
- constexpr Size operator - (const Size& left, const Size& right)
- {
- return Size(left.width - right.width, left.height - right.height);
- }
-
- constexpr bool operator==(const Size& left, const Size& right)
- {
- return left.width == right.width && left.height == right.height;
- }
-
- constexpr bool operator!=(const Size& left, const Size& right)
- {
- return !(left == right);
- }
-
- struct Thickness
- {
- constexpr static Thickness Zero()
- {
- return Thickness(0);
- }
-
- constexpr Thickness() : Thickness(0) { }
-
- constexpr explicit Thickness(const float width)
- : left(width), top(width), right(width), bottom(width) { }
-
- constexpr explicit Thickness(const float horizontal, const float vertical)
- : left(horizontal), top(vertical), right(horizontal), bottom(vertical) { }
-
- constexpr Thickness(const float left, const float top, const float right, const float bottom)
- : left(left), top(top), right(right), bottom(bottom) { }
-
- float GetHorizontalTotal() const
- {
- return left + right;
- }
-
- float GetVerticalTotal() const
- {
- return top + bottom;
- }
-
- void SetLeftRight(const float value)
- {
- left = right = value;
- }
-
- void SetTopBottom(const float value)
- {
- top = bottom = value;
- }
-
- void SetAll(const float value)
- {
- left = top = right = bottom = value;
- }
-
- float Validate() const
- {
- return left >= 0.0 && top >= 0.0 && right >= 0.0 && bottom >= 0.0;
- }
-
- float left;
- float top;
- float right;
- float bottom;
- };
-
- struct Rect
- {
- constexpr Rect() = default;
- constexpr Rect(const float left, const float top, const float width, const float height)
- : left(left), top(top), width(width), height(height) { }
- constexpr Rect(const Point& lefttop, const Size& size)
- : left(lefttop.x), top(lefttop.y), width(size.width), height(size.height) { }
-
- constexpr static Rect FromVertices(const float left, const float top, const float right, const float bottom)
- {
- return Rect(left, top, right - left, bottom - top);
- }
-
- constexpr float GetRight() const
- {
- return left + width;
- }
-
- constexpr float GetBottom() const
- {
- return top + height;
- }
-
- constexpr Point GetLeftTop() const
- {
- return Point(left, top);
- }
-
- constexpr Point GetRightBottom() const
- {
- return Point(left + width, top + height);
- }
-
- constexpr Point GetLeftBottom() const
- {
- return Point(left, top + height);
- }
-
- constexpr Point GetRightTop() const
- {
- return Point(left + width, top);
- }
-
- constexpr Size GetSize() const
- {
- return Size(width, height);
- }
-
- constexpr Rect Shrink(const Thickness& thickness) const
- {
- return Rect(left + thickness.left, top + thickness.top, width - thickness.GetHorizontalTotal(), height - thickness.GetVerticalTotal());
- }
-
- constexpr bool IsPointInside(const Point& point) const
- {
- return
- point.x >= left &&
- point.x < GetRight() &&
- point.y >= top &&
- point.y < GetBottom();
- }
-
- float left = 0.0f;
- float top = 0.0f;
- float width = 0.0f;
- float height = 0.0f;
- };
-
- enum class MouseButton
- {
- Left,
- Right,
- Middle
- };
-
- struct TextRange
- {
- constexpr static std::optional<TextRange> FromTwoSides(unsigned first, unsigned second)
- {
- if (first > second)
- return std::make_optional<TextRange>(second, first - second);
- if (first < second)
- return std::make_optional<TextRange>(first, second - first);
- return std::nullopt;
- }
-
- constexpr static std::pair<unsigned, unsigned> ToTwoSides(std::optional<TextRange> text_range, unsigned default_position = 0)
- {
- if (text_range.has_value())
- return std::make_pair(text_range.value().position, text_range.value().position + text_range.value().count);
- return std::make_pair(default_position, default_position);
- }
-
- constexpr TextRange() = default;
- constexpr TextRange(const unsigned position, const unsigned count)
- : position(position), count(count)
- {
-
- }
-
- unsigned position = 0;
- unsigned count = 0;
- };
-
- bool IsKeyDown(int virtual_code);
- bool IsKeyToggled(int virtual_code);
- bool IsAnyMouseButtonDown();
+constexpr bool operator!=(const Point& left, const Point& right) {
+ return !(left == right);
}
+
+struct Size final {
+ constexpr static Size Zero() { return Size(0, 0); }
+
+ constexpr Size() = default;
+ constexpr Size(const float width, const float height)
+ : width(width), height(height) {}
+
+ float width = 0;
+ float height = 0;
+};
+
+constexpr Size operator+(const Size& left, const Size& right) {
+ return Size(left.width + right.width, left.height + right.height);
+}
+
+constexpr Size operator-(const Size& left, const Size& right) {
+ return Size(left.width - right.width, left.height - right.height);
+}
+
+constexpr bool operator==(const Size& left, const Size& right) {
+ return left.width == right.width && left.height == right.height;
+}
+
+constexpr bool operator!=(const Size& left, const Size& right) {
+ return !(left == right);
+}
+
+struct Thickness final {
+ constexpr static Thickness Zero() { return Thickness(0); }
+
+ constexpr Thickness() : Thickness(0) {}
+
+ constexpr explicit Thickness(const float width)
+ : left(width), top(width), right(width), bottom(width) {}
+
+ constexpr explicit Thickness(const float horizontal, const float vertical)
+ : left(horizontal), top(vertical), right(horizontal), bottom(vertical) {}
+
+ constexpr Thickness(const float left, const float top, const float right,
+ const float bottom)
+ : left(left), top(top), right(right), bottom(bottom) {}
+
+ float GetHorizontalTotal() const { return left + right; }
+
+ float GetVerticalTotal() const { return top + bottom; }
+
+ void SetLeftRight(const float value) { left = right = value; }
+
+ void SetTopBottom(const float value) { top = bottom = value; }
+
+ void SetAll(const float value) { left = top = right = bottom = value; }
+
+ float Validate() const {
+ return left >= 0.0 && top >= 0.0 && right >= 0.0 && bottom >= 0.0;
+ }
+
+ float left;
+ float top;
+ float right;
+ float bottom;
+};
+
+constexpr bool operator==(const Thickness& left, const Thickness& right) {
+ return left.left == right.left && left.top == right.top &&
+ left.right == right.right && left.bottom == right.bottom;
+}
+
+constexpr bool operator!=(const Thickness& left, const Thickness& right) {
+ return !(left == right);
+}
+
+struct Rect final {
+ constexpr Rect() = default;
+ constexpr Rect(const float left, const float top, const float width,
+ const float height)
+ : left(left), top(top), width(width), height(height) {}
+ constexpr Rect(const Point& lefttop, const Size& size)
+ : left(lefttop.x),
+ top(lefttop.y),
+ width(size.width),
+ height(size.height) {}
+
+ constexpr static Rect FromVertices(const float left, const float top,
+ const float right, const float bottom) {
+ return Rect(left, top, right - left, bottom - top);
+ }
+
+ constexpr static Rect FromCenter(const Point& center, const float width,
+ const float height) {
+ return Rect(center.x - width / 2.0f, center.y - height / 2.0f, width,
+ height);
+ }
+
+ constexpr float GetRight() const { return left + width; }
+
+ constexpr float GetBottom() const { return top + height; }
+
+ constexpr Point GetLeftTop() const { return Point(left, top); }
+
+ constexpr Point GetRightBottom() const {
+ return Point(left + width, top + height);
+ }
+
+ constexpr Point GetLeftBottom() const { return Point(left, top + height); }
+
+ constexpr Point GetRightTop() const { return Point(left + width, top); }
+
+ constexpr Point GetCenter() const {
+ return Point(left + width / 2.0f, top + height / 2.0f);
+ }
+
+ constexpr Size GetSize() const { return Size(width, height); }
+
+ constexpr Rect Shrink(const Thickness& thickness) const {
+ return Rect(left + thickness.left, top + thickness.top,
+ width - thickness.GetHorizontalTotal(),
+ height - thickness.GetVerticalTotal());
+ }
+
+ constexpr bool IsPointInside(const Point& point) const {
+ return point.x >= left && point.x < GetRight() && point.y >= top &&
+ point.y < GetBottom();
+ }
+
+ float left = 0.0f;
+ float top = 0.0f;
+ float width = 0.0f;
+ float height = 0.0f;
+};
+
+constexpr bool operator==(const Rect& left, const Rect& right) {
+ return left.left == right.left && left.top == right.top &&
+ left.width == right.width && left.height == right.height;
+}
+
+constexpr bool operator!=(const Rect& left, const Rect& right) {
+ return !(left == right);
+}
+
+struct RoundedRect final {
+ constexpr RoundedRect() = default;
+ constexpr RoundedRect(const Rect& rect, const float radius_x,
+ const float radius_y)
+ : rect(rect), radius_x(radius_x), radius_y(radius_y) {}
+
+ Rect rect{};
+ float radius_x = 0.0f;
+ float radius_y = 0.0f;
+};
+
+constexpr bool operator==(const RoundedRect& left, const RoundedRect& right) {
+ return left.rect == right.rect && left.radius_x == right.radius_x &&
+ left.radius_y == right.radius_y;
+}
+
+constexpr bool operator!=(const RoundedRect& left, const RoundedRect& right) {
+ return !(left == right);
+}
+
+struct Ellipse final {
+ constexpr Ellipse() = default;
+ constexpr Ellipse(const Point& center, const float radius_x,
+ const float radius_y)
+ : center(center), radius_x(radius_x), radius_y(radius_y) {}
+
+ constexpr static Ellipse FromRect(const Rect& rect) {
+ return Ellipse(rect.GetCenter(), rect.width / 2.0f, rect.height / 2.0f);
+ }
+
+ constexpr Rect GetBoundRect() const {
+ return Rect::FromCenter(center, radius_x * 2.0f, radius_y * 2.0f);
+ }
+
+ Point center{};
+ float radius_x = 0.0f;
+ float radius_y = 0.0f;
+};
+
+constexpr bool operator==(const Ellipse& left, const Ellipse& right) {
+ return left.center == right.center && left.radius_x == right.radius_x &&
+ left.radius_y == right.radius_y;
+}
+
+constexpr bool operator!=(const Ellipse& left, const Ellipse& right) {
+ return !(left == right);
+}
+
+struct TextRange final {
+ constexpr static std::optional<TextRange> FromTwoSides(unsigned first,
+ unsigned second) {
+ if (first > second)
+ return std::make_optional<TextRange>(second, first - second);
+ if (first < second)
+ return std::make_optional<TextRange>(first, second - first);
+ return std::nullopt;
+ }
+
+ constexpr static std::pair<unsigned, unsigned> ToTwoSides(
+ std::optional<TextRange> text_range, unsigned default_position = 0) {
+ if (text_range.has_value())
+ return std::make_pair(
+ text_range.value().position,
+ text_range.value().position + text_range.value().count);
+ return std::make_pair(default_position, default_position);
+ }
+
+ constexpr TextRange() = default;
+ constexpr TextRange(const unsigned position, const unsigned count)
+ : position(position), count(count) {}
+
+ unsigned position = 0;
+ unsigned count = 0;
+};
+} // namespace cru::ui
diff --git a/src/ui/ui_manager.cpp b/src/ui/ui_manager.cpp
index 689a04a2..4d14575b 100644
--- a/src/ui/ui_manager.cpp
+++ b/src/ui/ui_manager.cpp
@@ -1,107 +1,74 @@
#include "ui_manager.hpp"
+#include <Windows.h>
+#include <d2d1.h>
+#include <dwrite.h>
+
#include "application.hpp"
-#include "border_property.hpp"
-#include "graph/graph.hpp"
#include "exception.hpp"
+#include "graph/graph_manager.hpp"
+#include "graph/graph_util.hpp"
+#include "util/com_util.hpp"
+
+namespace cru::ui {
+namespace {
+void GetSystemCaretInfo(CaretInfo* caret_info) {
+ caret_info->caret_blink_duration =
+ std::chrono::milliseconds(::GetCaretBlinkTime());
+ DWORD caret_width;
+ if (!::SystemParametersInfoW(SPI_GETCARETWIDTH, 0, &caret_width, 0))
+ throw Win32Error(::GetLastError(), "Failed to get system caret width.");
+ caret_info->half_caret_width = caret_width / 2.0f;
+}
+IDWriteTextFormat* CreateDefaultTextFormat() {
+ const auto dwrite_factory =
+ graph::GraphManager::GetInstance()->GetDWriteFactory();
+ IDWriteTextFormat* text_format;
-namespace cru::ui
-{
- namespace
- {
- void GetSystemCaretInfo(CaretInfo* caret_info)
- {
- caret_info->caret_blink_duration = std::chrono::milliseconds(::GetCaretBlinkTime());
- DWORD caret_width;
- if (!::SystemParametersInfoW(SPI_GETCARETWIDTH, 0 , &caret_width, 0))
- throw Win32Error(::GetLastError(), "Failed to get system caret width.");
- caret_info->half_caret_width = caret_width / 2.0f;
- }
-
- Microsoft::WRL::ComPtr<ID2D1Brush> CreateSolidBrush(graph::GraphManager* graph_manager, const D2D1_COLOR_F& color)
- {
- const auto device_context = graph_manager->GetD2D1DeviceContext();
- Microsoft::WRL::ComPtr<ID2D1SolidColorBrush> solid_color_brush;
- device_context->CreateSolidColorBrush(color, &solid_color_brush);
- return solid_color_brush;
- }
-
- Microsoft::WRL::ComPtr<IDWriteTextFormat> CreateDefaultTextFormat(graph::GraphManager* graph_manager)
- {
- const auto dwrite_factory = graph_manager->GetDWriteFactory();
-
- Microsoft::WRL::ComPtr<IDWriteTextFormat> text_format;
-
- ThrowIfFailed(dwrite_factory->CreateTextFormat(
- L"等线", nullptr,
- DWRITE_FONT_WEIGHT_NORMAL,
- DWRITE_FONT_STYLE_NORMAL,
- DWRITE_FONT_STRETCH_NORMAL,
- 24.0, L"zh-cn",
- &text_format
- ));
-
- ThrowIfFailed(text_format->SetTextAlignment(DWRITE_TEXT_ALIGNMENT_CENTER));
- ThrowIfFailed(text_format->SetParagraphAlignment(DWRITE_PARAGRAPH_ALIGNMENT_CENTER));
-
- return text_format;
- }
- }
-
-
- //!!! never use default constructor of border at here, because it will recursively call this method!
- PredefineResources::PredefineResources(graph::GraphManager* graph_manager) :
- border_property_brush {CreateSolidBrush(graph_manager, D2D1::ColorF(D2D1::ColorF::Black))},
-
- button_normal_border {CreateSolidBrush(graph_manager, D2D1::ColorF(D2D1::ColorF::RoyalBlue)), 2, 6, 6},
- button_press_border {CreateSolidBrush(graph_manager, D2D1::ColorF(D2D1::ColorF::Blue)), 2, 6, 6},
-
- text_control_selection_brush {CreateSolidBrush(graph_manager, D2D1::ColorF(D2D1::ColorF::LightSkyBlue))},
-
- text_box_border {CreateSolidBrush(graph_manager, D2D1::ColorF(D2D1::ColorF::Black))},
- text_box_text_brush {CreateSolidBrush(graph_manager, D2D1::ColorF(D2D1::ColorF::Black))},
- text_box_text_format {CreateDefaultTextFormat(graph_manager)},
- text_box_caret_brush {CreateSolidBrush(graph_manager, D2D1::ColorF(D2D1::ColorF::Black))},
-
- text_block_text_brush {CreateSolidBrush(graph_manager, D2D1::ColorF(D2D1::ColorF::Black))},
- text_block_text_format {CreateDefaultTextFormat(graph_manager)},
-
- toggle_button_on_brush {CreateSolidBrush(graph_manager, D2D1::ColorF(D2D1::ColorF::DeepSkyBlue))},
- toggle_button_off_brush {CreateSolidBrush(graph_manager, D2D1::ColorF(D2D1::ColorF::LightGray))},
+ ThrowIfFailed(dwrite_factory->CreateTextFormat(
+ L"等线", nullptr, DWRITE_FONT_WEIGHT_NORMAL, DWRITE_FONT_STYLE_NORMAL,
+ DWRITE_FONT_STRETCH_NORMAL, 24.0, L"zh-cn", &text_format));
- list_item_normal_border_brush {CreateSolidBrush(graph_manager, D2D1::ColorF(D2D1::ColorF::White, 0))},
- list_item_normal_fill_brush {CreateSolidBrush(graph_manager, D2D1::ColorF(D2D1::ColorF::White, 0))},
- list_item_hover_border_brush {CreateSolidBrush(graph_manager, D2D1::ColorF(D2D1::ColorF::SkyBlue))},
- list_item_hover_fill_brush {CreateSolidBrush(graph_manager, D2D1::ColorF(D2D1::ColorF::SkyBlue, 0.3f))},
- list_item_select_border_brush {CreateSolidBrush(graph_manager, D2D1::ColorF(D2D1::ColorF::MediumBlue))},
- list_item_select_fill_brush {CreateSolidBrush(graph_manager, D2D1::ColorF(D2D1::ColorF::SkyBlue, 0.3f))},
+ ThrowIfFailed(text_format->SetTextAlignment(DWRITE_TEXT_ALIGNMENT_CENTER));
+ ThrowIfFailed(
+ text_format->SetParagraphAlignment(DWRITE_PARAGRAPH_ALIGNMENT_CENTER));
- scroll_bar_background_brush {CreateSolidBrush(graph_manager, D2D1::ColorF(D2D1::ColorF::Gainsboro, 0.3f))},
- scroll_bar_border_brush {CreateSolidBrush(graph_manager, D2D1::ColorF(D2D1::ColorF::DimGray))},
- scroll_bar_brush {CreateSolidBrush(graph_manager, D2D1::ColorF(D2D1::ColorF::DimGray))}
+ return text_format;
+}
+} // namespace
+
+PredefineResources::PredefineResources() {
+ try {
+ button_normal_border_brush =
+ graph::CreateSolidColorBrush(D2D1::ColorF(D2D1::ColorF::Black));
+
+ text_block_selection_brush =
+ graph::CreateSolidColorBrush(D2D1::ColorF(D2D1::ColorF::LightSkyBlue));
+ text_block_text_brush =
+ graph::CreateSolidColorBrush(D2D1::ColorF(D2D1::ColorF::Black));
+ text_block_text_format = CreateDefaultTextFormat();
+ } catch (...) {
+ util::SafeRelease(button_normal_border_brush);
+ util::SafeRelease(text_block_selection_brush);
+ util::SafeRelease(text_block_text_brush);
+ util::SafeRelease(text_block_text_format);
+ }
+}
-#ifdef CRU_DEBUG_LAYOUT
- ,
- debug_layout_out_border_brush {CreateSolidBrush(graph_manager, D2D1::ColorF(D2D1::ColorF::Crimson))},
- debug_layout_margin_brush {CreateSolidBrush(graph_manager, D2D1::ColorF(D2D1::ColorF::LightCoral, 0.25f))},
- debug_layout_padding_brush {CreateSolidBrush(graph_manager, D2D1::ColorF(D2D1::ColorF::SkyBlue, 0.25f))}
-#endif
- {
-
- }
+PredefineResources::~PredefineResources() {
+ util::SafeRelease(button_normal_border_brush);
+ util::SafeRelease(text_block_selection_brush);
+ util::SafeRelease(text_block_text_brush);
+ util::SafeRelease(text_block_text_format);
+}
- UiManager* UiManager::GetInstance()
- {
- return Application::GetInstance()->ResolveSingleton<UiManager>([](auto)
- {
- return new UiManager{};
- });
- }
+UiManager* UiManager::GetInstance() {
+ return Application::GetInstance()->ResolveSingleton<UiManager>(
+ [](auto) { return new UiManager{}; });
+}
- UiManager::UiManager()
- : predefine_resources_(graph::GraphManager::GetInstance())
- {
- GetSystemCaretInfo(&caret_info_);
- }
+UiManager::UiManager() : predefine_resources_() {
+ GetSystemCaretInfo(&caret_info_);
}
+} // namespace cru::ui
diff --git a/src/ui/ui_manager.hpp b/src/ui/ui_manager.hpp
index f0e1e8ce..107b536c 100644
--- a/src/ui/ui_manager.hpp
+++ b/src/ui/ui_manager.hpp
@@ -1,108 +1,61 @@
#pragma once
-
-// ReSharper disable once CppUnusedIncludeDirective
#include "pre.hpp"
-#include "system_headers.hpp"
-
#include "base.hpp"
-#include "border_property.hpp"
-namespace cru::graph
-{
- class GraphManager;
+struct ID2D1Brush;
+struct IDWriteTextFormat;
+namespace cru::graph {
+class GraphManager;
}
-namespace cru::ui
-{
- struct CaretInfo
- {
- std::chrono::milliseconds caret_blink_duration;
- float half_caret_width;
- };
-
-
- class PredefineResources : public Object
- {
- public:
- explicit PredefineResources(graph::GraphManager* graph_manager);
- PredefineResources(const PredefineResources& other) = delete;
- PredefineResources(PredefineResources&& other) = delete;
- PredefineResources& operator=(const PredefineResources& other) = delete;
- PredefineResources& operator=(PredefineResources&& other) = delete;
- ~PredefineResources() override = default;
-
- //region BorderProperty
- Microsoft::WRL::ComPtr<ID2D1Brush> border_property_brush;
-
- //region Button
- BorderProperty button_normal_border;
- BorderProperty button_press_border;
-
- //region TextControl
- Microsoft::WRL::ComPtr<ID2D1Brush> text_control_selection_brush;
-
- //region TextBox
- BorderProperty text_box_border;
- Microsoft::WRL::ComPtr<ID2D1Brush> text_box_text_brush;
- Microsoft::WRL::ComPtr<IDWriteTextFormat> text_box_text_format;
- Microsoft::WRL::ComPtr<ID2D1Brush> text_box_caret_brush;
-
- //region TextBlock
- Microsoft::WRL::ComPtr<ID2D1Brush> text_block_text_brush;
- Microsoft::WRL::ComPtr<IDWriteTextFormat> text_block_text_format;
-
- //region ToggleButton
- Microsoft::WRL::ComPtr<ID2D1Brush> toggle_button_on_brush;
- Microsoft::WRL::ComPtr<ID2D1Brush> toggle_button_off_brush;
-
- //region ListItem
- Microsoft::WRL::ComPtr<ID2D1Brush> list_item_normal_border_brush;
- Microsoft::WRL::ComPtr<ID2D1Brush> list_item_normal_fill_brush;
- Microsoft::WRL::ComPtr<ID2D1Brush> list_item_hover_border_brush;
- Microsoft::WRL::ComPtr<ID2D1Brush> list_item_hover_fill_brush;
- Microsoft::WRL::ComPtr<ID2D1Brush> list_item_select_border_brush;
- Microsoft::WRL::ComPtr<ID2D1Brush> list_item_select_fill_brush;
-
- //region ScrollControl
- Microsoft::WRL::ComPtr<ID2D1Brush> scroll_bar_background_brush;
- Microsoft::WRL::ComPtr<ID2D1Brush> scroll_bar_border_brush;
- Microsoft::WRL::ComPtr<ID2D1Brush> scroll_bar_brush;
-
-#ifdef CRU_DEBUG_LAYOUT
- //region debug
- Microsoft::WRL::ComPtr<ID2D1Brush> debug_layout_out_border_brush;
- Microsoft::WRL::ComPtr<ID2D1Brush> debug_layout_margin_brush;
- Microsoft::WRL::ComPtr<ID2D1Brush> debug_layout_padding_brush;
-#endif
- };
-
- class UiManager : public Object
- {
- public:
- static UiManager* GetInstance();
- private:
- UiManager();
- public:
- UiManager(const UiManager& other) = delete;
- UiManager(UiManager&& other) = delete;
- UiManager& operator=(const UiManager& other) = delete;
- UiManager& operator=(UiManager&& other) = delete;
- ~UiManager() override = default;
-
- CaretInfo GetCaretInfo() const
- {
- return caret_info_;
- }
-
- const PredefineResources* GetPredefineResources() const
- {
- return &predefine_resources_;
- }
-
- private:
- CaretInfo caret_info_;
-
- PredefineResources predefine_resources_;
- };
-}
+namespace cru::ui {
+struct CaretInfo {
+ std::chrono::milliseconds caret_blink_duration;
+ float half_caret_width;
+};
+
+class PredefineResources : public Object {
+ public:
+ PredefineResources();
+ PredefineResources(const PredefineResources& other) = delete;
+ PredefineResources(PredefineResources&& other) = delete;
+ PredefineResources& operator=(const PredefineResources& other) = delete;
+ PredefineResources& operator=(PredefineResources&& other) = delete;
+ ~PredefineResources() override;
+
+ // region Button
+ ID2D1Brush* button_normal_border_brush = nullptr;
+
+ // region TextBlock
+ ID2D1Brush* text_block_selection_brush = nullptr;
+ ID2D1Brush* text_block_text_brush = nullptr;
+ IDWriteTextFormat* text_block_text_format = nullptr;
+};
+
+class UiManager : public Object {
+ public:
+ static UiManager* GetInstance();
+
+ private:
+ UiManager();
+
+ public:
+ UiManager(const UiManager& other) = delete;
+ UiManager(UiManager&& other) = delete;
+ UiManager& operator=(const UiManager& other) = delete;
+ UiManager& operator=(UiManager&& other) = delete;
+ ~UiManager() override = default;
+
+ CaretInfo GetCaretInfo() const { return caret_info_; }
+
+ const PredefineResources* GetPredefineResources() const {
+ return &predefine_resources_;
+ }
+
+ private:
+ CaretInfo caret_info_;
+
+ PredefineResources predefine_resources_;
+};
+} // namespace cru::ui
diff --git a/src/ui/window.cpp b/src/ui/window.cpp
index 86fa4436..91319db7 100644
--- a/src/ui/window.cpp
+++ b/src/ui/window.cpp
@@ -1,790 +1,707 @@
#include "window.hpp"
+#include <d2d1_1.h>
+#include <windowsx.h>
+
#include "application.hpp"
-#include "graph/graph.hpp"
#include "exception.hpp"
-#include "cursor.hpp"
-
-namespace cru::ui
-{
- WindowClass::WindowClass(const String& name, WNDPROC window_proc, HINSTANCE h_instance)
- : name_(name)
- {
- WNDCLASSEX window_class;
- window_class.cbSize = sizeof(WNDCLASSEX);
-
- window_class.style = CS_HREDRAW | CS_VREDRAW;
- window_class.lpfnWndProc = window_proc;
- window_class.cbClsExtra = 0;
- window_class.cbWndExtra = 0;
- window_class.hInstance = h_instance;
- window_class.hIcon = LoadIcon(NULL, IDI_APPLICATION);
- window_class.hCursor = LoadCursor(NULL, IDC_ARROW);
- window_class.hbrBackground = GetSysColorBrush(COLOR_BTNFACE);
- window_class.lpszMenuName = NULL;
- window_class.lpszClassName = name.c_str();
- window_class.hIconSm = NULL;
-
- atom_ = RegisterClassEx(&window_class);
- if (atom_ == 0)
- throw std::runtime_error("Failed to create window class.");
- }
-
- LRESULT __stdcall GeneralWndProc(HWND hWnd, UINT Msg, WPARAM wParam, LPARAM lParam) {
- auto window = WindowManager::GetInstance()->FromHandle(hWnd);
-
- LRESULT result;
- if (window != nullptr && window->HandleWindowMessage(hWnd, Msg, wParam, lParam, result))
- return result;
-
- return DefWindowProc(hWnd, Msg, wParam, lParam);
- }
-
- WindowManager* WindowManager::GetInstance()
- {
- return Application::GetInstance()->ResolveSingleton<WindowManager>([](auto)
- {
- return new WindowManager{};
- });
- }
-
- WindowManager::WindowManager() {
- general_window_class_ = std::make_unique<WindowClass>(
- L"CruUIWindowClass",
- GeneralWndProc,
- Application::GetInstance()->GetInstanceHandle()
- );
- }
-
- void WindowManager::RegisterWindow(HWND hwnd, Window * window) {
- const auto find_result = window_map_.find(hwnd);
- if (find_result != window_map_.end())
- throw std::runtime_error("The hwnd is already in the map.");
-
- window_map_.emplace(hwnd, window);
- }
-
- void WindowManager::UnregisterWindow(HWND hwnd) {
- const auto find_result = window_map_.find(hwnd);
- if (find_result == window_map_.end())
- throw std::runtime_error("The hwnd is not in the map.");
- window_map_.erase(find_result);
-
- if (window_map_.empty())
- Application::GetInstance()->Quit(0);
- }
-
- Window* WindowManager::FromHandle(HWND hwnd) {
- const auto find_result = window_map_.find(hwnd);
- if (find_result == window_map_.end())
- return nullptr;
- else
- return find_result->second;
- }
-
- std::vector<Window*> WindowManager::GetAllWindows() const
- {
- std::vector<Window*> windows;
- for (auto [key, value] : window_map_)
- windows.push_back(value);
- return windows;
- }
+#include "graph/graph_manager.hpp"
+#include "graph/graph_util.hpp"
+#include "graph/window_render_target.hpp"
+#include "render/window_render_object.hpp"
+
+namespace cru::ui {
+namespace {
+// Dispatch the event.
+//
+// This will raise routed event of the control and its parent and parent's
+// parent ... (until "last_receiver" if it's not nullptr) with appropriate args.
+//
+// First tunnel from top to bottom possibly stopped by "handled" flag in
+// EventArgs. Second bubble from bottom to top possibly stopped by "handled"
+// flag in EventArgs. Last direct to each control.
+//
+// Args is of type "EventArgs". The first init argument is "sender", which is
+// automatically bound to each receiving control. The second init argument is
+// "original_sender", which is unchanged. And "args" will be perfectly forwarded
+// as the rest arguments.
+template <typename EventArgs, typename... Args>
+void DispatchEvent(Control* const original_sender,
+ events::RoutedEvent<EventArgs>* (Control::*event_ptr)(),
+ Control* const last_receiver, Args&&... args) {
+ std::list<Control*> receive_list;
+
+ auto parent = original_sender;
+ while (parent != last_receiver) {
+ receive_list.push_back(parent);
+ parent = parent->GetParent();
+ }
+
+ auto handled = false;
+
+ // tunnel
+ for (auto i = receive_list.crbegin(); i != receive_list.crend(); ++i) {
+ EventArgs event_args(*i, original_sender, std::forward<Args>(args)...);
+ (*i->*event_ptr)()->tunnel.Raise(event_args);
+ if (event_args.IsHandled()) {
+ handled = true;
+ break;
+ }
+ }
+
+ // bubble
+ if (!handled) {
+ for (auto i : receive_list) {
+ EventArgs event_args(i, original_sender, std::forward<Args>(args)...);
+ (i->*event_ptr)()->bubble.Raise(event_args);
+ if (event_args.IsHandled()) break;
+ }
+ }
+
+ // direct
+ for (auto i : receive_list) {
+ EventArgs event_args(i, original_sender, std::forward<Args>(args)...);
+ (i->*event_ptr)()->direct.Raise(event_args);
+ }
+}
- inline Point PiToDip(const POINT& pi_point)
- {
- return Point(
- graph::PixelToDipX(pi_point.x),
- graph::PixelToDipY(pi_point.y)
- );
- }
+std::list<Control*> GetAncestorList(Control* control) {
+ std::list<Control*> l;
+ while (control != nullptr) {
+ l.push_front(control);
+ control = control->GetParent();
+ }
+ return l;
+}
- inline POINT DipToPi(const Point& dip_point)
- {
- POINT result;
- result.x = graph::DipToPixelX(dip_point.x);
- result.y = graph::DipToPixelY(dip_point.y);
- return result;
- }
+Control* FindLowestCommonAncestor(Control* left, Control* right) {
+ if (left == nullptr || right == nullptr) return nullptr;
+
+ auto&& left_list = GetAncestorList(left);
+ auto&& right_list = GetAncestorList(right);
+
+ // the root is different
+ if (left_list.front() != right_list.front()) return nullptr;
+
+ // find the last same control or the last control (one is ancestor of the
+ // other)
+ auto left_i = left_list.cbegin();
+ auto right_i = right_list.cbegin();
+ while (true) {
+ if (left_i == left_list.cend()) return *(--left_i);
+ if (right_i == right_list.cend()) return *(--right_i);
+ if (*left_i != *right_i) return *(--left_i);
+ ++left_i;
+ ++right_i;
+ }
+}
+} // namespace
+LRESULT __stdcall GeneralWndProc(HWND hWnd, UINT Msg, WPARAM wParam,
+ LPARAM lParam) {
+ auto window = WindowManager::GetInstance()->FromHandle(hWnd);
- namespace
- {
- Cursor::Ptr GetCursorInherit(Control* control)
- {
- while (control != nullptr)
- {
- const auto cursor = control->GetCursor();
- if (cursor != nullptr)
- return cursor;
- control = control->GetInternalParent();
- }
- return cursors::arrow;
- }
- }
+ LRESULT result;
+ if (window != nullptr &&
+ window->HandleWindowMessage(hWnd, Msg, wParam, lParam, result))
+ return result;
- Window* Window::CreateOverlapped()
- {
- return new Window(tag_overlapped_constructor{});
- }
-
- Window* Window::CreatePopup(Window* parent, const bool caption)
- {
- return new Window(tag_popup_constructor{}, parent, caption);
- }
+ return DefWindowProc(hWnd, Msg, wParam, lParam);
+}
+WindowManager* WindowManager::GetInstance() {
+ return Application::GetInstance()->ResolveSingleton<WindowManager>(
+ [](auto) { return new WindowManager{}; });
+}
- Window::Window(tag_overlapped_constructor)
- {
- BeforeCreateHwnd();
+WindowManager::WindowManager() {
+ general_window_class_ = std::make_unique<WindowClass>(
+ L"CruUIWindowClass", GeneralWndProc,
+ Application::GetInstance()->GetInstanceHandle());
+}
- const auto window_manager = WindowManager::GetInstance();
+void WindowManager::RegisterWindow(HWND hwnd, Window* window) {
+ assert(window_map_.count(hwnd) == 0); // The hwnd is already in the map.
+ window_map_.emplace(hwnd, window);
+}
- hwnd_ = CreateWindowEx(0,
- window_manager->GetGeneralWindowClass()->GetName(),
- L"", WS_OVERLAPPEDWINDOW,
- CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,
- nullptr, nullptr, Application::GetInstance()->GetInstanceHandle(), nullptr
- );
+void WindowManager::UnregisterWindow(HWND hwnd) {
+ const auto find_result = window_map_.find(hwnd);
+ assert(find_result != window_map_.end()); // The hwnd is not in the map.
+ window_map_.erase(find_result);
+ if (window_map_.empty()) Application::GetInstance()->Quit(0);
+}
- if (hwnd_ == nullptr)
- throw std::runtime_error("Failed to create window.");
+Window* WindowManager::FromHandle(HWND hwnd) {
+ const auto find_result = window_map_.find(hwnd);
+ if (find_result == window_map_.end())
+ return nullptr;
+ else
+ return find_result->second;
+}
- AfterCreateHwnd(window_manager);
- }
+std::vector<Window*> WindowManager::GetAllWindows() const {
+ std::vector<Window*> windows;
+ for (auto [key, value] : window_map_) windows.push_back(value);
+ return windows;
+}
- Window::Window(tag_popup_constructor, Window* parent, const bool caption)
- {
- if (parent != nullptr && !parent->IsWindowValid())
- throw std::runtime_error("Parent window is not valid.");
+inline Point PiToDip(const POINT& pi_point) {
+ return Point(graph::PixelToDipX(pi_point.x), graph::PixelToDipY(pi_point.y));
+}
- BeforeCreateHwnd();
+inline POINT DipToPi(const Point& dip_point) {
+ POINT result;
+ result.x = graph::DipToPixelX(dip_point.x);
+ result.y = graph::DipToPixelY(dip_point.y);
+ return result;
+}
- parent_window_ = parent;
+Window* Window::CreateOverlapped() {
+ return new Window(tag_overlapped_constructor{});
+}
- const auto window_manager = WindowManager::GetInstance();
+Window* Window::CreatePopup(Window* parent, const bool caption) {
+ return new Window(tag_popup_constructor{}, parent, caption);
+}
- hwnd_ = CreateWindowEx(0,
- window_manager->GetGeneralWindowClass()->GetName(),
- L"", caption ? (WS_POPUPWINDOW | WS_CAPTION) : WS_POPUP,
- CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,
- parent == nullptr ? nullptr : parent->GetWindowHandle(),
- nullptr, Application::GetInstance()->GetInstanceHandle(), nullptr
- );
+Window::Window(tag_overlapped_constructor) {
+ BeforeCreateHwnd();
- if (hwnd_ == nullptr)
- throw std::runtime_error("Failed to create window.");
+ const auto window_manager = WindowManager::GetInstance();
- AfterCreateHwnd(window_manager);
- }
+ hwnd_ =
+ CreateWindowEx(0, window_manager->GetGeneralWindowClass()->GetName(), L"",
+ WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT,
+ CW_USEDEFAULT, CW_USEDEFAULT, nullptr, nullptr,
+ Application::GetInstance()->GetInstanceHandle(), nullptr);
- void Window::BeforeCreateHwnd()
- {
- window_ = this;
- }
+ if (hwnd_ == nullptr)
+ throw Win32Error(::GetLastError(), "Failed to create window.");
- void Window::AfterCreateHwnd(WindowManager* window_manager)
- {
- window_manager->RegisterWindow(hwnd_, this);
+ AfterCreateHwnd(window_manager);
+}
- render_target_ = graph::GraphManager::GetInstance()->CreateWindowRenderTarget(hwnd_);
+Window::Window(tag_popup_constructor, Window* parent, const bool caption) {
+ assert(parent == nullptr ||
+ parent->IsWindowValid()); // Parent window is not valid.
- SetCursor(cursors::arrow);
- }
+ BeforeCreateHwnd();
- Window::~Window() {
- if (IsWindowValid())
- {
- SetDeleteThisOnDestroy(false); // avoid double delete.
- Close();
- }
- TraverseDescendants([this](Control* control) {
- control->OnDetachToWindow(this);
- });
- }
+ parent_window_ = parent;
- StringView Window::GetControlType() const
- {
- return control_type;
- }
+ const auto window_manager = WindowManager::GetInstance();
- void Window::SetDeleteThisOnDestroy(bool value)
- {
- delete_this_on_destroy_ = value;
- }
+ hwnd_ = CreateWindowEx(
+ 0, window_manager->GetGeneralWindowClass()->GetName(), L"",
+ caption ? (WS_POPUPWINDOW | WS_CAPTION) : WS_POPUP, CW_USEDEFAULT,
+ CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,
+ parent == nullptr ? nullptr : parent->GetWindowHandle(), nullptr,
+ Application::GetInstance()->GetInstanceHandle(), nullptr);
- void Window::Close() {
- if (IsWindowValid())
- DestroyWindow(hwnd_);
- }
+ if (hwnd_ == nullptr)
+ throw Win32Error(::GetLastError(), "Failed to create window.");
- void Window::InvalidateDraw() {
- if (IsWindowValid()) {
- InvalidateRect(hwnd_, nullptr, false);
- }
- }
+ AfterCreateHwnd(window_manager);
+}
- void Window::Show() {
- if (IsWindowValid()) {
- ShowWindow(hwnd_, SW_SHOWNORMAL);
- }
- }
+void Window::BeforeCreateHwnd() { window_ = this; }
- void Window::Hide() {
- if (IsWindowValid()) {
- ShowWindow(hwnd_, SW_HIDE);
- }
- }
+void Window::AfterCreateHwnd(WindowManager* window_manager) {
+ window_manager->RegisterWindow(hwnd_, this);
- Size Window::GetClientSize() {
- if (!IsWindowValid())
- return Size();
+ render_target_.reset(
+ new graph::WindowRenderTarget(graph::GraphManager::GetInstance(), hwnd_));
- const auto pixel_rect = GetClientRectPixel();
- return Size(
- graph::PixelToDipX(pixel_rect.right),
- graph::PixelToDipY(pixel_rect.bottom)
- );
- }
+ render_object_.reset(new render::WindowRenderObject(this));
+}
- void Window::SetClientSize(const Size & size) {
- if (IsWindowValid()) {
- const auto window_style = static_cast<DWORD>(GetWindowLongPtr(hwnd_, GWL_STYLE));
- const auto window_ex_style = static_cast<DWORD>(GetWindowLongPtr(hwnd_, GWL_EXSTYLE));
-
- RECT rect;
- rect.left = 0;
- rect.top = 0;
- rect.right = graph::DipToPixelX(size.width);
- rect.bottom = graph::DipToPixelY(size.height);
- AdjustWindowRectEx(&rect, window_style, FALSE, window_ex_style);
-
- SetWindowPos(
- hwnd_, nullptr, 0, 0,
- rect.right - rect.left,
- rect.bottom - rect.top,
- SWP_NOZORDER | SWP_NOMOVE
- );
- }
- }
+Window::~Window() {
+ if (IsWindowValid()) {
+ SetDeleteThisOnDestroy(false); // avoid double delete.
+ Close();
+ }
+ TraverseDescendants(
+ [this](Control* control) { control->OnDetachToWindow(this); });
+}
- Rect Window::GetWindowRect() {
- if (!IsWindowValid())
- return Rect();
+StringView Window::GetControlType() const { return control_type; }
- RECT rect;
- ::GetWindowRect(hwnd_, &rect);
+render::RenderObject* Window::GetRenderObject() const {
+ return render_object_.get();
+}
- return Rect::FromVertices(
- graph::PixelToDipX(rect.left),
- graph::PixelToDipY(rect.top),
- graph::PixelToDipX(rect.right),
- graph::PixelToDipY(rect.bottom)
- );
- }
+void Window::SetDeleteThisOnDestroy(bool value) {
+ delete_this_on_destroy_ = value;
+}
- void Window::SetWindowRect(const Rect & rect) {
- if (IsWindowValid()) {
- SetWindowPos(
- hwnd_, nullptr,
- graph::DipToPixelX(rect.left),
- graph::DipToPixelY(rect.top),
- graph::DipToPixelX(rect.GetRight()),
- graph::DipToPixelY(rect.GetBottom()),
- SWP_NOZORDER
- );
- }
- }
+void Window::Close() {
+ if (IsWindowValid()) DestroyWindow(hwnd_);
+}
- void Window::SetWindowPosition(const Point& position)
- {
- if (IsWindowValid()) {
- SetWindowPos(
- hwnd_, nullptr,
- graph::DipToPixelX(position.x),
- graph::DipToPixelY(position.y),
- 0, 0,
- SWP_NOZORDER | SWP_NOSIZE
- );
- }
- }
+void Window::InvalidateDraw() {
+ if (IsWindowValid()) {
+ InvalidateRect(hwnd_, nullptr, false);
+ }
+}
- Point Window::PointToScreen(const Point& point)
- {
- if (!IsWindowValid())
- return Point::Zero();
+void Window::Show() {
+ if (IsWindowValid()) {
+ ShowWindow(hwnd_, SW_SHOWNORMAL);
+ }
+}
- auto p = DipToPi(point);
- if (::ClientToScreen(GetWindowHandle(), &p) == 0)
- throw Win32Error(::GetLastError(), "Failed transform point from window to screen.");
- return PiToDip(p);
- }
+void Window::Hide() {
+ if (IsWindowValid()) {
+ ShowWindow(hwnd_, SW_HIDE);
+ }
+}
- Point Window::PointFromScreen(const Point& point)
- {
- if (!IsWindowValid())
- return Point::Zero();
+Size Window::GetClientSize() {
+ if (!IsWindowValid()) return Size();
- auto p = DipToPi(point);
- if (::ScreenToClient(GetWindowHandle(), &p) == 0)
- throw Win32Error(::GetLastError(), "Failed transform point from screen to window.");
- return PiToDip(p);
- }
+ const auto pixel_rect = GetClientRectPixel();
+ return Size(graph::PixelToDipX(pixel_rect.right),
+ graph::PixelToDipY(pixel_rect.bottom));
+}
- bool Window::HandleWindowMessage(HWND hwnd, int msg, WPARAM w_param, LPARAM l_param, LRESULT & result) {
-
- events::WindowNativeMessageEventArgs args(this, this, {hwnd, msg, w_param, l_param});
- native_message_event.Raise(args);
- if (args.GetResult().has_value())
- {
- result = args.GetResult().value();
- return true;
- }
-
- switch (msg) {
- case WM_PAINT:
- OnPaintInternal();
- result = 0;
- return true;
- case WM_ERASEBKGND:
- result = 1;
- return true;
- case WM_SETFOCUS:
- OnSetFocusInternal();
- result = 0;
- return true;
- case WM_KILLFOCUS:
- OnKillFocusInternal();
- result = 0;
- return true;
- case WM_MOUSEMOVE:
- {
- POINT point;
- point.x = GET_X_LPARAM(l_param);
- point.y = GET_Y_LPARAM(l_param);
- OnMouseMoveInternal(point);
- result = 0;
- return true;
- }
- case WM_LBUTTONDOWN:
- {
- POINT point;
- point.x = GET_X_LPARAM(l_param);
- point.y = GET_Y_LPARAM(l_param);
- OnMouseDownInternal(MouseButton::Left, point);
- result = 0;
- return true;
- }
- case WM_LBUTTONUP:
- {
- POINT point;
- point.x = GET_X_LPARAM(l_param);
- point.y = GET_Y_LPARAM(l_param);
- OnMouseUpInternal(MouseButton::Left, point);
- result = 0;
- return true;
- }
- case WM_RBUTTONDOWN:
- {
- POINT point;
- point.x = GET_X_LPARAM(l_param);
- point.y = GET_Y_LPARAM(l_param);
- OnMouseDownInternal(MouseButton::Right, point);
- result = 0;
- return true;
- }
- case WM_RBUTTONUP:
- {
- POINT point;
- point.x = GET_X_LPARAM(l_param);
- point.y = GET_Y_LPARAM(l_param);
- OnMouseUpInternal(MouseButton::Right, point);
- result = 0;
- return true;
- }
- case WM_MBUTTONDOWN:
- {
- POINT point;
- point.x = GET_X_LPARAM(l_param);
- point.y = GET_Y_LPARAM(l_param);
- OnMouseDownInternal(MouseButton::Middle, point);
- result = 0;
- return true;
- }
- case WM_MBUTTONUP:
- {
- POINT point;
- point.x = GET_X_LPARAM(l_param);
- point.y = GET_Y_LPARAM(l_param);
- OnMouseUpInternal(MouseButton::Middle, point);
- result = 0;
- return true;
- }
- case WM_MOUSEWHEEL:
- POINT point;
- point.x = GET_X_LPARAM(l_param);
- point.y = GET_Y_LPARAM(l_param);
- ScreenToClient(hwnd, &point);
- OnMouseWheelInternal(GET_WHEEL_DELTA_WPARAM(w_param), point);
- result = 0;
- return true;
- case WM_KEYDOWN:
- OnKeyDownInternal(static_cast<int>(w_param));
- result = 0;
- return true;
- case WM_KEYUP:
- OnKeyUpInternal(static_cast<int>(w_param));
- result = 0;
- return true;
- case WM_CHAR:
- OnCharInternal(static_cast<wchar_t>(w_param));
- result = 0;
- return true;
- case WM_SIZE:
- OnResizeInternal(LOWORD(l_param), HIWORD(l_param));
- result = 0;
- return true;
- case WM_ACTIVATE:
- if (w_param == WA_ACTIVE || w_param == WA_CLICKACTIVE)
- OnActivatedInternal();
- else if (w_param == WA_INACTIVE)
- OnDeactivatedInternal();
- result = 0;
- return true;
- case WM_DESTROY:
- OnDestroyInternal();
- result = 0;
- return true;
- default:
- return false;
- }
- }
+void Window::SetClientSize(const Size& size) {
+ if (IsWindowValid()) {
+ const auto window_style =
+ static_cast<DWORD>(GetWindowLongPtr(hwnd_, GWL_STYLE));
+ const auto window_ex_style =
+ static_cast<DWORD>(GetWindowLongPtr(hwnd_, GWL_EXSTYLE));
+
+ RECT rect;
+ rect.left = 0;
+ rect.top = 0;
+ rect.right = graph::DipToPixelX(size.width);
+ rect.bottom = graph::DipToPixelY(size.height);
+ AdjustWindowRectEx(&rect, window_style, FALSE, window_ex_style);
+
+ SetWindowPos(hwnd_, nullptr, 0, 0, rect.right - rect.left,
+ rect.bottom - rect.top, SWP_NOZORDER | SWP_NOMOVE);
+ }
+}
- Point Window::GetMousePosition()
- {
- if (!IsWindowValid())
- return Point::Zero();
- POINT point;
- ::GetCursorPos(&point);
- ::ScreenToClient(hwnd_, &point);
- return PiToDip(point);
- }
+Rect Window::GetWindowRect() {
+ if (!IsWindowValid()) return Rect();
- Point Window::GetOffset()
- {
- return Point();
- }
+ RECT rect;
+ ::GetWindowRect(hwnd_, &rect);
- Size Window::GetSize()
- {
- return GetClientSize();
- }
+ return Rect::FromVertices(
+ graph::PixelToDipX(rect.left), graph::PixelToDipY(rect.top),
+ graph::PixelToDipX(rect.right), graph::PixelToDipY(rect.bottom));
+}
- void Window::SetRect(const Rect& size)
- {
+void Window::SetWindowRect(const Rect& rect) {
+ if (IsWindowValid()) {
+ SetWindowPos(hwnd_, nullptr, graph::DipToPixelX(rect.left),
+ graph::DipToPixelY(rect.top),
+ graph::DipToPixelX(rect.GetRight()),
+ graph::DipToPixelY(rect.GetBottom()), SWP_NOZORDER);
+ }
+}
- }
+void Window::SetWindowPosition(const Point& position) {
+ if (IsWindowValid()) {
+ SetWindowPos(hwnd_, nullptr, graph::DipToPixelX(position.x),
+ graph::DipToPixelY(position.y), 0, 0,
+ SWP_NOZORDER | SWP_NOSIZE);
+ }
+}
- bool Window::IsPointInside(const Point& point)
- {
- return Rect(Point::Zero(), GetClientSize()).IsPointInside(point);
- }
+Point Window::PointToScreen(const Point& point) {
+ if (!IsWindowValid()) return Point::Zero();
- void Window::WindowInvalidateLayout()
- {
- if (is_layout_invalid_)
- return;
-
- is_layout_invalid_ = true;
- InvokeLater([this]
- {
- if (is_layout_invalid_)
- Relayout();
- });
- }
+ auto p = DipToPi(point);
+ if (::ClientToScreen(GetWindowHandle(), &p) == 0)
+ throw Win32Error(::GetLastError(),
+ "Failed transform point from window to screen.");
+ return PiToDip(p);
+}
- void Window::Relayout()
- {
- Measure(GetSize(), AdditionalMeasureInfo{});
- OnLayoutCore(Rect(Point::Zero(), GetSize()), AdditionalLayoutInfo{});
- is_layout_invalid_ = false;
- }
+Point Window::PointFromScreen(const Point& point) {
+ if (!IsWindowValid()) return Point::Zero();
- void Window::SetSizeFitContent(const Size& max_size)
- {
- Measure(max_size, AdditionalMeasureInfo{});
- SetClientSize(GetDesiredSize());
- OnLayoutCore(Rect(Point::Zero(), GetSize()), AdditionalLayoutInfo{});
- is_layout_invalid_ = false;
- }
+ auto p = DipToPi(point);
+ if (::ScreenToClient(GetWindowHandle(), &p) == 0)
+ throw Win32Error(::GetLastError(),
+ "Failed transform point from screen to window.");
+ return PiToDip(p);
+}
- bool Window::RequestFocusFor(Control * control)
- {
- if (control == nullptr)
- throw std::invalid_argument("The control to request focus can't be null. You can set it as the window.");
+bool Window::HandleWindowMessage(HWND hwnd, int msg, WPARAM w_param,
+ LPARAM l_param, LRESULT& result) {
+ events::WindowNativeMessageEventArgs args(this, this,
+ {hwnd, msg, w_param, l_param});
+ native_message_event_.Raise(args);
+ if (args.GetResult().has_value()) {
+ result = args.GetResult().value();
+ return true;
+ }
+
+ switch (msg) {
+ case WM_PAINT:
+ OnPaintInternal();
+ result = 0;
+ return true;
+ case WM_ERASEBKGND:
+ result = 1;
+ return true;
+ case WM_SETFOCUS:
+ OnSetFocusInternal();
+ result = 0;
+ return true;
+ case WM_KILLFOCUS:
+ OnKillFocusInternal();
+ result = 0;
+ return true;
+ case WM_MOUSEMOVE: {
+ POINT point;
+ point.x = GET_X_LPARAM(l_param);
+ point.y = GET_Y_LPARAM(l_param);
+ OnMouseMoveInternal(point);
+ result = 0;
+ return true;
+ }
+ case WM_LBUTTONDOWN: {
+ POINT point;
+ point.x = GET_X_LPARAM(l_param);
+ point.y = GET_Y_LPARAM(l_param);
+ OnMouseDownInternal(MouseButton::Left, point);
+ result = 0;
+ return true;
+ }
+ case WM_LBUTTONUP: {
+ POINT point;
+ point.x = GET_X_LPARAM(l_param);
+ point.y = GET_Y_LPARAM(l_param);
+ OnMouseUpInternal(MouseButton::Left, point);
+ result = 0;
+ return true;
+ }
+ case WM_RBUTTONDOWN: {
+ POINT point;
+ point.x = GET_X_LPARAM(l_param);
+ point.y = GET_Y_LPARAM(l_param);
+ OnMouseDownInternal(MouseButton::Right, point);
+ result = 0;
+ return true;
+ }
+ case WM_RBUTTONUP: {
+ POINT point;
+ point.x = GET_X_LPARAM(l_param);
+ point.y = GET_Y_LPARAM(l_param);
+ OnMouseUpInternal(MouseButton::Right, point);
+ result = 0;
+ return true;
+ }
+ case WM_MBUTTONDOWN: {
+ POINT point;
+ point.x = GET_X_LPARAM(l_param);
+ point.y = GET_Y_LPARAM(l_param);
+ OnMouseDownInternal(MouseButton::Middle, point);
+ result = 0;
+ return true;
+ }
+ case WM_MBUTTONUP: {
+ POINT point;
+ point.x = GET_X_LPARAM(l_param);
+ point.y = GET_Y_LPARAM(l_param);
+ OnMouseUpInternal(MouseButton::Middle, point);
+ result = 0;
+ return true;
+ }
+ case WM_MOUSEWHEEL:
+ POINT point;
+ point.x = GET_X_LPARAM(l_param);
+ point.y = GET_Y_LPARAM(l_param);
+ ScreenToClient(hwnd, &point);
+ OnMouseWheelInternal(GET_WHEEL_DELTA_WPARAM(w_param), point);
+ result = 0;
+ return true;
+ case WM_KEYDOWN:
+ OnKeyDownInternal(static_cast<int>(w_param));
+ result = 0;
+ return true;
+ case WM_KEYUP:
+ OnKeyUpInternal(static_cast<int>(w_param));
+ result = 0;
+ return true;
+ case WM_CHAR:
+ OnCharInternal(static_cast<wchar_t>(w_param));
+ result = 0;
+ return true;
+ case WM_SIZE:
+ OnResizeInternal(LOWORD(l_param), HIWORD(l_param));
+ result = 0;
+ return true;
+ case WM_ACTIVATE:
+ if (w_param == WA_ACTIVE || w_param == WA_CLICKACTIVE)
+ OnActivatedInternal();
+ else if (w_param == WA_INACTIVE)
+ OnDeactivatedInternal();
+ result = 0;
+ return true;
+ case WM_DESTROY:
+ OnDestroyInternal();
+ result = 0;
+ return true;
+ default:
+ return false;
+ }
+}
- if (!IsWindowValid())
- return false;
+Point Window::GetMousePosition() {
+ if (!IsWindowValid()) return Point::Zero();
+ POINT point;
+ ::GetCursorPos(&point);
+ ::ScreenToClient(hwnd_, &point);
+ return PiToDip(point);
+}
- if (!window_focus_)
- {
- focus_control_ = control;
- ::SetFocus(hwnd_);
- return true; // event dispatch will be done in window message handling function "OnSetFocusInternal".
- }
+bool Window::RequestFocusFor(Control* control) {
+ assert(control != nullptr); // The control to request focus can't be null.
+ // You can set it as the window.
- if (focus_control_ == control)
- return true;
+ if (!IsWindowValid()) return false;
- DispatchEvent(focus_control_, &Control::lose_focus_event, nullptr, false);
+ if (!window_focus_) {
+ focus_control_ = control;
+ ::SetFocus(hwnd_);
+ return true; // event dispatch will be done in window message handling
+ // function "OnSetFocusInternal".
+ }
- focus_control_ = control;
+ if (focus_control_ == control) return true;
- DispatchEvent(control, &Control::get_focus_event, nullptr, false);
+ DispatchEvent(focus_control_, &Control::LoseFocusEvent, nullptr, false);
- return true;
- }
+ focus_control_ = control;
- Control* Window::GetFocusControl()
- {
- return focus_control_;
- }
+ DispatchEvent(control, &Control::GainFocusEvent, nullptr, false);
- Control* Window::CaptureMouseFor(Control* control)
- {
- if (control != nullptr)
- {
- ::SetCapture(hwnd_);
- std::swap(mouse_capture_control_, control);
- DispatchMouseHoverControlChangeEvent(control ? control : mouse_hover_control_, mouse_capture_control_, GetMousePosition());
- return control;
- }
- else
- {
- return ReleaseCurrentMouseCapture();
- }
- }
+ return true;
+}
- Control* Window::ReleaseCurrentMouseCapture()
- {
- if (mouse_capture_control_)
- {
- const auto previous = mouse_capture_control_;
- mouse_capture_control_ = nullptr;
- ::ReleaseCapture();
- DispatchMouseHoverControlChangeEvent(previous, mouse_hover_control_, GetMousePosition());
- return previous;
- }
- else
- {
- return nullptr;
- }
- }
+Control* Window::GetFocusControl() { return focus_control_; }
+
+Control* Window::CaptureMouseFor(Control* control) {
+ if (control != nullptr) {
+ ::SetCapture(hwnd_);
+ std::swap(mouse_capture_control_, control);
+ DispatchMouseHoverControlChangeEvent(
+ control ? control : mouse_hover_control_, mouse_capture_control_,
+ GetMousePosition());
+ return control;
+ } else {
+ return ReleaseCurrentMouseCapture();
+ }
+}
- void Window::UpdateCursor()
- {
- if (IsWindowValid() && mouse_hover_control_ != nullptr)
- {
- SetCursorInternal(GetCursorInherit(mouse_hover_control_)->GetHandle());
- }
- }
+Control* Window::ReleaseCurrentMouseCapture() {
+ if (mouse_capture_control_) {
+ const auto previous = mouse_capture_control_;
+ mouse_capture_control_ = nullptr;
+ ::ReleaseCapture();
+ DispatchMouseHoverControlChangeEvent(previous, mouse_hover_control_,
+ GetMousePosition());
+ return previous;
+ } else {
+ return nullptr;
+ }
+}
#ifdef CRU_DEBUG_LAYOUT
- void Window::SetDebugLayout(const bool value)
- {
- if (debug_layout_ != value)
- {
- debug_layout_ = value;
- InvalidateDraw();
- }
- }
+void Window::SetDebugLayout(const bool value) {
+ if (debug_layout_ != value) {
+ debug_layout_ = value;
+ InvalidateDraw();
+ }
+}
#endif
- RECT Window::GetClientRectPixel() {
- RECT rect{ };
- GetClientRect(hwnd_, &rect);
- return rect;
- }
-
- bool Window::IsMessageInQueue(UINT message)
- {
- MSG msg;
- return ::PeekMessageW(&msg, hwnd_, message, message, PM_NOREMOVE) != 0;
- }
-
- void Window::SetCursorInternal(HCURSOR cursor)
- {
- if (IsWindowValid())
- {
- ::SetClassLongPtrW(GetWindowHandle(), GCLP_HCURSOR, reinterpret_cast<LONG_PTR>(cursor));
- if (mouse_hover_control_ != nullptr)
- ::SetCursor(cursor);
- }
- }
-
- void Window::OnDestroyInternal() {
- WindowManager::GetInstance()->UnregisterWindow(hwnd_);
- hwnd_ = nullptr;
- if (delete_this_on_destroy_)
- InvokeLater([this]{ delete this; });
- }
+void Window::OnChildChanged(Control* old_child, Control* new_child) {
+ if (old_child) render_object_->RemoveChild(0);
+ if (new_child) render_object_->AddChild(new_child->GetRenderObject(), 0);
+}
- void Window::OnPaintInternal() {
- render_target_->SetAsTarget();
+Control* Window::HitTest(const Point& point) {
+ return render_object_->HitTest(point)->GetAttachedControl();
+}
- auto device_context = render_target_->GetD2DDeviceContext();
+RECT Window::GetClientRectPixel() {
+ RECT rect{};
+ GetClientRect(hwnd_, &rect);
+ return rect;
+}
- device_context->BeginDraw();
+bool Window::IsMessageInQueue(UINT message) {
+ MSG msg;
+ return ::PeekMessageW(&msg, hwnd_, message, message, PM_NOREMOVE) != 0;
+}
- //Clear the background.
- device_context->Clear(D2D1::ColorF(D2D1::ColorF::White));
+void Window::SetCursorInternal(HCURSOR cursor) {
+ if (IsWindowValid()) {
+ ::SetClassLongPtrW(GetWindowHandle(), GCLP_HCURSOR,
+ reinterpret_cast<LONG_PTR>(cursor));
+ if (mouse_hover_control_ != nullptr) ::SetCursor(cursor);
+ }
+}
- Draw(device_context.Get());
+void Window::OnDestroyInternal() {
+ WindowManager::GetInstance()->UnregisterWindow(hwnd_);
+ hwnd_ = nullptr;
+ if (delete_this_on_destroy_) InvokeLater([this] { delete this; });
+}
- ThrowIfFailed(
- device_context->EndDraw(), "Failed to draw window."
- );
+void Window::OnPaintInternal() {
+ render_target_->SetAsTarget();
- render_target_->Present();
+ auto device_context =
+ render_target_->GetGraphManager()->GetD2D1DeviceContext();
+ device_context->BeginDraw();
+ // Clear the background.
+ device_context->Clear(D2D1::ColorF(D2D1::ColorF::White));
+ render_object_->Draw(device_context);
+ ThrowIfFailed(device_context->EndDraw(), "Failed to draw window.");
+ render_target_->Present();
- ValidateRect(hwnd_, nullptr);
- }
+ ValidateRect(hwnd_, nullptr);
+}
- void Window::OnResizeInternal(const int new_width, const int new_height) {
- render_target_->ResizeBuffer(new_width, new_height);
- if (!(new_width == 0 && new_height == 0))
- WindowInvalidateLayout();
- }
+void Window::OnResizeInternal(const int new_width, const int new_height) {
+ render_target_->ResizeBuffer(new_width, new_height);
+ if (!(new_width == 0 && new_height == 0)) render_object_->MeasureAndLayout();
+}
- void Window::OnSetFocusInternal()
- {
- window_focus_ = true;
- DispatchEvent(focus_control_, &Control::get_focus_event, nullptr, true);
- }
+void Window::OnSetFocusInternal() {
+ window_focus_ = true;
+ DispatchEvent(focus_control_, &Control::GainFocusEvent, nullptr, true);
+}
- void Window::OnKillFocusInternal()
- {
- window_focus_ = false;
- DispatchEvent(focus_control_, &Control::lose_focus_event, nullptr, true);
- }
+void Window::OnKillFocusInternal() {
+ window_focus_ = false;
+ DispatchEvent(focus_control_, &Control::LoseFocusEvent, nullptr, true);
+}
- void Window::OnMouseMoveInternal(const POINT point)
- {
- const auto dip_point = PiToDip(point);
-
- //when mouse was previous outside the window
- if (mouse_hover_control_ == nullptr) {
- //invoke TrackMouseEvent to have WM_MOUSELEAVE sent.
- TRACKMOUSEEVENT tme;
- tme.cbSize = sizeof tme;
- tme.dwFlags = TME_LEAVE;
- tme.hwndTrack = hwnd_;
-
- TrackMouseEvent(&tme);
- }
-
- //Find the first control that hit test succeed.
- const auto new_control_mouse_hover = HitTest(dip_point);
- const auto old_control_mouse_hover = mouse_hover_control_;
- mouse_hover_control_ = new_control_mouse_hover;
-
- if (mouse_capture_control_) // if mouse is captured
- {
- DispatchEvent(mouse_capture_control_, &Control::mouse_move_event, nullptr, dip_point);
- }
- else
- {
- DispatchMouseHoverControlChangeEvent(old_control_mouse_hover, new_control_mouse_hover, dip_point);
- DispatchEvent(new_control_mouse_hover, &Control::mouse_move_event, nullptr, dip_point);
- }
- }
+void Window::OnMouseMoveInternal(const POINT point) {
+ const auto dip_point = PiToDip(point);
+
+ // when mouse was previous outside the window
+ if (mouse_hover_control_ == nullptr) {
+ // invoke TrackMouseEvent to have WM_MOUSELEAVE sent.
+ TRACKMOUSEEVENT tme;
+ tme.cbSize = sizeof tme;
+ tme.dwFlags = TME_LEAVE;
+ tme.hwndTrack = hwnd_;
+
+ TrackMouseEvent(&tme);
+ }
+
+ // Find the first control that hit test succeed.
+ const auto new_control_mouse_hover = HitTest(dip_point);
+ const auto old_control_mouse_hover = mouse_hover_control_;
+ mouse_hover_control_ = new_control_mouse_hover;
+
+ if (mouse_capture_control_) // if mouse is captured
+ {
+ DispatchEvent(mouse_capture_control_, &Control::MouseMoveEvent, nullptr,
+ dip_point);
+ } else {
+ DispatchMouseHoverControlChangeEvent(old_control_mouse_hover,
+ new_control_mouse_hover, dip_point);
+ DispatchEvent(new_control_mouse_hover, &Control::MouseMoveEvent, nullptr,
+ dip_point);
+ }
+}
- void Window::OnMouseLeaveInternal()
- {
- DispatchEvent(mouse_hover_control_, &Control::mouse_leave_event, nullptr);
- mouse_hover_control_ = nullptr;
- }
+void Window::OnMouseLeaveInternal() {
+ DispatchEvent(mouse_hover_control_, &Control::MouseLeaveEvent, nullptr);
+ mouse_hover_control_ = nullptr;
+}
- void Window::OnMouseDownInternal(MouseButton button, POINT point)
- {
- const auto dip_point = PiToDip(point);
+void Window::OnMouseDownInternal(MouseButton button, POINT point) {
+ const auto dip_point = PiToDip(point);
- Control* control;
+ Control* control;
- if (mouse_capture_control_)
- control = mouse_capture_control_;
- else
- control = HitTest(dip_point);
+ if (mouse_capture_control_)
+ control = mouse_capture_control_;
+ else
+ control = HitTest(dip_point);
- DispatchEvent(control, &Control::mouse_down_event, nullptr, dip_point, button);
- }
+ DispatchEvent(control, &Control::MouseDownEvent, nullptr, dip_point,
+ button);
+}
- void Window::OnMouseUpInternal(MouseButton button, POINT point)
- {
- const auto dip_point = PiToDip(point);
+void Window::OnMouseUpInternal(MouseButton button, POINT point) {
+ const auto dip_point = PiToDip(point);
- Control* control;
+ Control* control;
- if (mouse_capture_control_)
- control = mouse_capture_control_;
- else
- control = HitTest(dip_point);
+ if (mouse_capture_control_)
+ control = mouse_capture_control_;
+ else
+ control = HitTest(dip_point);
- DispatchEvent(control, &Control::mouse_up_event, nullptr, dip_point, button);
- }
+ DispatchEvent(control, &Control::MouseUpEvent, nullptr, dip_point, button);
+}
- void Window::OnMouseWheelInternal(short delta, POINT point)
- {
- const auto dip_point = PiToDip(point);
+void Window::OnMouseWheelInternal(short delta, POINT point) {
+ const auto dip_point = PiToDip(point);
- Control* control;
+ Control* control;
- if (mouse_capture_control_)
- control = mouse_capture_control_;
- else
- control = HitTest(dip_point);
+ if (mouse_capture_control_)
+ control = mouse_capture_control_;
+ else
+ control = HitTest(dip_point);
- DispatchEvent(control, &Control::mouse_wheel_event, nullptr, dip_point, static_cast<float>(delta));
- }
+ DispatchEvent(control, &Control::MouseWheelEvent, nullptr, dip_point,
+ static_cast<float>(delta));
+}
- void Window::OnKeyDownInternal(int virtual_code)
- {
- DispatchEvent(focus_control_, &Control::key_down_event, nullptr, virtual_code);
- }
+void Window::OnKeyDownInternal(int virtual_code) {
+ DispatchEvent(focus_control_, &Control::KeyDownEvent, nullptr,
+ virtual_code);
+}
- void Window::OnKeyUpInternal(int virtual_code)
- {
- DispatchEvent(focus_control_, &Control::key_up_event, nullptr, virtual_code);
- }
+void Window::OnKeyUpInternal(int virtual_code) {
+ DispatchEvent(focus_control_, &Control::KeyUpEvent, nullptr, virtual_code);
+}
- void Window::OnCharInternal(wchar_t c)
- {
- DispatchEvent(focus_control_, &Control::char_event, nullptr, c);
- }
+void Window::OnCharInternal(wchar_t c) {
+ DispatchEvent(focus_control_, &Control::CharEvent, nullptr, c);
+}
- void Window::OnActivatedInternal()
- {
- events::UiEventArgs args(this, this);
- activated_event.Raise(args);
- }
+void Window::OnActivatedInternal() {
+ events::UiEventArgs args(this, this);
+ activated_event_.Raise(args);
+}
- void Window::OnDeactivatedInternal()
- {
- events::UiEventArgs args(this, this);
- deactivated_event.Raise(args);
- }
+void Window::OnDeactivatedInternal() {
+ events::UiEventArgs args(this, this);
+ deactivated_event_.Raise(args);
+}
- void Window::DispatchMouseHoverControlChangeEvent(Control* old_control, Control* new_control, const Point& point)
- {
- if (new_control != old_control) //if the mouse-hover-on control changed
- {
- const auto lowest_common_ancestor = FindLowestCommonAncestor(old_control, new_control);
- if (old_control != nullptr) // if last mouse-hover-on control exists
- DispatchEvent(old_control, &Control::mouse_leave_event, lowest_common_ancestor); // dispatch mouse leave event.
- if (new_control != nullptr)
- {
- DispatchEvent(new_control, &Control::mouse_enter_event, lowest_common_ancestor, point); // dispatch mouse enter event.
- UpdateCursor();
- }
- }
- }
+void Window::DispatchMouseHoverControlChangeEvent(Control* old_control,
+ Control* new_control,
+ const Point& point) {
+ if (new_control != old_control) // if the mouse-hover-on control changed
+ {
+ const auto lowest_common_ancestor =
+ FindLowestCommonAncestor(old_control, new_control);
+ if (old_control != nullptr) // if last mouse-hover-on control exists
+ DispatchEvent(old_control, &Control::MouseLeaveEvent,
+ lowest_common_ancestor); // dispatch mouse leave event.
+ if (new_control != nullptr) {
+ DispatchEvent(new_control, &Control::MouseEnterEvent,
+ lowest_common_ancestor,
+ point); // dispatch mouse enter event.
+ }
+ }
}
+} // namespace cru::ui
diff --git a/src/ui/window.hpp b/src/ui/window.hpp
index e96d4d92..f1d5386f 100644
--- a/src/ui/window.hpp
+++ b/src/ui/window.hpp
@@ -1,304 +1,238 @@
#pragma once
-
-// ReSharper disable once CppUnusedIncludeDirective
#include "pre.hpp"
-#include "system_headers.hpp"
+#include <Windows.h>
#include <map>
#include <memory>
-#include "control.hpp"
+#include "content_control.hpp"
#include "events/ui_event.hpp"
+#include "events/window_event.hpp"
+#include "window_class.hpp"
+
+namespace cru::graph {
+class WindowRenderTarget;
+}
-namespace cru::graph
-{
- class WindowRenderTarget;
+namespace cru::ui::render {
+class WindowRenderObject;
}
-namespace cru::ui
-{
- class WindowClass : public Object
- {
- public:
- WindowClass(const String& name, WNDPROC window_proc, HINSTANCE h_instance);
- WindowClass(const WindowClass& other) = delete;
- WindowClass(WindowClass&& other) = delete;
- WindowClass& operator=(const WindowClass& other) = delete;
- WindowClass& operator=(WindowClass&& other) = delete;
- ~WindowClass() override = default;
-
-
- const wchar_t* GetName() const
- {
- return name_.c_str();
- }
-
- ATOM GetAtom() const
- {
- return atom_;
- }
-
- private:
- String name_;
- ATOM atom_;
- };
-
- class WindowManager : public Object
- {
- public:
- static WindowManager* GetInstance();
- private:
- WindowManager();
- public:
- WindowManager(const WindowManager& other) = delete;
- WindowManager(WindowManager&& other) = delete;
- WindowManager& operator=(const WindowManager& other) = delete;
- WindowManager& operator=(WindowManager&& other) = delete;
- ~WindowManager() override = default;
+namespace cru::ui {
+class WindowManager : public Object {
+ public:
+ static WindowManager* GetInstance();
+ private:
+ WindowManager();
- //Get the general window class for creating ordinary window.
- WindowClass* GetGeneralWindowClass() const
- {
- return general_window_class_.get();
- }
+ public:
+ WindowManager(const WindowManager& other) = delete;
+ WindowManager(WindowManager&& other) = delete;
+ WindowManager& operator=(const WindowManager& other) = delete;
+ WindowManager& operator=(WindowManager&& other) = delete;
+ ~WindowManager() override = default;
- //Register a window newly created.
- //This function adds the hwnd to hwnd-window map.
- //It should be called immediately after a window was created.
- void RegisterWindow(HWND hwnd, Window* window);
+ // Get the general window class for creating ordinary window.
+ WindowClass* GetGeneralWindowClass() const {
+ return general_window_class_.get();
+ }
- //Unregister a window that is going to be destroyed.
- //This function removes the hwnd from the hwnd-window map.
- //It should be called immediately before a window is going to be destroyed,
- void UnregisterWindow(HWND hwnd);
+ // Register a window newly created.
+ // This function adds the hwnd to hwnd-window map.
+ // It should be called immediately after a window was created.
+ void RegisterWindow(HWND hwnd, Window* window);
- //Return a pointer to the Window object related to the HWND or nullptr if the hwnd is not in the map.
- Window* FromHandle(HWND hwnd);
+ // Unregister a window that is going to be destroyed.
+ // This function removes the hwnd from the hwnd-window map.
+ // It should be called immediately before a window is going to be destroyed,
+ void UnregisterWindow(HWND hwnd);
- std::vector<Window*> GetAllWindows() const;
+ // Return a pointer to the Window object related to the HWND or nullptr if the
+ // hwnd is not in the map.
+ Window* FromHandle(HWND hwnd);
- private:
- std::unique_ptr<WindowClass> general_window_class_;
- std::map<HWND, Window*> window_map_;
- };
+ std::vector<Window*> GetAllWindows() const;
+ private:
+ std::unique_ptr<WindowClass> general_window_class_;
+ std::map<HWND, Window*> window_map_;
+};
+class Window final : public ContentControl {
+ friend class WindowManager;
- class Window final : public SingleChildControl
- {
- friend class WindowManager;
- public:
- static constexpr auto control_type = L"Window";
+ public:
+ static constexpr auto control_type = L"Window";
- public:
- static Window* CreateOverlapped();
- static Window* CreatePopup(Window* parent, bool caption = false);
+ public:
+ static Window* CreateOverlapped();
+ static Window* CreatePopup(Window* parent, bool caption = false);
- private:
- struct tag_overlapped_constructor {};
- struct tag_popup_constructor {};
+ private:
+ struct tag_overlapped_constructor {};
+ struct tag_popup_constructor {};
- explicit Window(tag_overlapped_constructor);
- Window(tag_popup_constructor, Window* parent, bool caption);
+ explicit Window(tag_overlapped_constructor);
+ Window(tag_popup_constructor, Window* parent, bool caption);
- void BeforeCreateHwnd();
- void AfterCreateHwnd(WindowManager* window_manager);
+ void BeforeCreateHwnd();
+ void AfterCreateHwnd(WindowManager* window_manager);
- public:
- Window(const Window& other) = delete;
- Window(Window&& other) = delete;
- Window& operator=(const Window& other) = delete;
- Window& operator=(Window&& other) = delete;
- ~Window() override;
+ public:
+ Window(const Window& other) = delete;
+ Window(Window&& other) = delete;
+ Window& operator=(const Window& other) = delete;
+ Window& operator=(Window&& other) = delete;
+ ~Window() override;
- public:
- StringView GetControlType() const override final;
+ public:
+ StringView GetControlType() const override final;
- void SetDeleteThisOnDestroy(bool value);
+ render::RenderObject* GetRenderObject() const override;
- //*************** region: handle ***************
+ void SetDeleteThisOnDestroy(bool value);
- //Get the handle of the window. Return null if window is invalid.
- HWND GetWindowHandle() const
- {
- return hwnd_;
- }
+ //*************** region: handle ***************
- //Return if the window is still valid, that is, hasn't been closed or destroyed.
- bool IsWindowValid() const
- {
- return hwnd_ != nullptr;
- }
+ // Get the handle of the window. Return null if window is invalid.
+ HWND GetWindowHandle() const { return hwnd_; }
+ // Return if the window is still valid, that is, hasn't been closed or
+ // destroyed.
+ bool IsWindowValid() const { return hwnd_ != nullptr; }
- //*************** region: window operations ***************
+ //*************** region: window operations ***************
- Window* GetParentWindow() const
- {
- return parent_window_;
- }
+ Window* GetParentWindow() const { return parent_window_; }
- //Close and destroy the window if the window is valid.
- void Close();
+ // Close and destroy the window if the window is valid.
+ void Close();
- //Send a repaint message to the window's message queue which may make the window repaint.
- void InvalidateDraw() override final;
+ // Send a repaint message to the window's message queue which may make the
+ // window repaint.
+ void InvalidateDraw();
- //Show the window.
- void Show();
+ // Show the window.
+ void Show();
- //Hide thw window.
- void Hide();
+ // Hide thw window.
+ void Hide();
- //Get the client size.
- Size GetClientSize();
+ // Get the client size.
+ Size GetClientSize();
- //Set the client size and repaint.
- void SetClientSize(const Size& size);
+ // Set the client size and repaint.
+ void SetClientSize(const Size& size);
- //Get the rect of the window containing frame.
- //The lefttop of the rect is relative to screen lefttop.
- Rect GetWindowRect();
+ // Get the rect of the window containing frame.
+ // The lefttop of the rect is relative to screen lefttop.
+ Rect GetWindowRect();
- //Set the rect of the window containing frame.
- //The lefttop of the rect is relative to screen lefttop.
- void SetWindowRect(const Rect& rect);
+ // Set the rect of the window containing frame.
+ // The lefttop of the rect is relative to screen lefttop.
+ void SetWindowRect(const Rect& rect);
- //Set the lefttop of the window relative to screen.
- void SetWindowPosition(const Point& position);
+ // Set the lefttop of the window relative to screen.
+ void SetWindowPosition(const Point& position);
- Point PointToScreen(const Point& point);
+ Point PointToScreen(const Point& point);
- Point PointFromScreen(const Point& point);
+ Point PointFromScreen(const Point& point);
- //Handle the raw window message.
- //Return true if the message is handled and get the result through "result" argument.
- //Return false if the message is not handled.
- bool HandleWindowMessage(HWND hwnd, int msg, WPARAM w_param, LPARAM l_param, LRESULT& result);
+ // Handle the raw window message.
+ // Return true if the message is handled and get the result through "result"
+ // argument. Return false if the message is not handled.
+ bool HandleWindowMessage(HWND hwnd, int msg, WPARAM w_param, LPARAM l_param,
+ LRESULT& result);
- //*************** region: mouse ***************
+ //*************** region: mouse ***************
- Point GetMousePosition();
+ Point GetMousePosition();
- Control* GetMouseHoverControl() const
- {
- return mouse_hover_control_;
- }
+ Control* GetMouseHoverControl() const { return mouse_hover_control_; }
- //*************** region: position and size ***************
+ //*************** region: focus ***************
- //Always return (0, 0) for a window.
- Point GetOffset() override final;
+ // Request focus for specified control.
+ bool RequestFocusFor(Control* control);
- //Get the size of client area for a window.
- Size GetSize() override final;
+ // Get the control that has focus.
+ Control* GetFocusControl();
- //This method has no effect for a window. Use SetClientSize instead.
- void SetRect(const Rect& size) override final;
+ //*************** region: mouse capture ***************
- //Override. If point is in client area, it is in window.
- bool IsPointInside(const Point& point) override final;
+ Control* CaptureMouseFor(Control* control);
+ Control* ReleaseCurrentMouseCapture();
- //*************** region: layout ***************
+ //*************** region: events ***************
+ public:
+ Event<events::UiEventArgs>* ActivatedEvent() { return &activated_event_; }
+ Event<events::UiEventArgs>* DeactivatedEvent() { return &deactivated_event_; }
+ Event<events::WindowNativeMessageEventArgs>* NativeMessageEvent() {
+ return &native_message_event_;
+ }
- void WindowInvalidateLayout();
+ private:
+ Event<events::UiEventArgs> activated_event_;
+ Event<events::UiEventArgs> deactivated_event_;
+ Event<events::WindowNativeMessageEventArgs> native_message_event_;
- void Relayout();
-
- void SetSizeFitContent(const Size& max_size = Size(1000, 1000));
+ protected:
+ void OnChildChanged(Control* old_child, Control* new_child) override;
+ private:
+ Control* HitTest(const Point& point);
- //*************** region: focus ***************
+ //*************** region: native operations ***************
- //Request focus for specified control.
- bool RequestFocusFor(Control* control);
+ // Get the client rect in pixel.
+ RECT GetClientRectPixel();
- //Get the control that has focus.
- Control* GetFocusControl();
+ bool IsMessageInQueue(UINT message);
+ void SetCursorInternal(HCURSOR cursor);
- //*************** region: mouse capture ***************
-
- Control* CaptureMouseFor(Control* control);
- Control* ReleaseCurrentMouseCapture();
-
-
- //*************** region: cursor ***************
- void UpdateCursor();
+ //*************** region: native messages ***************
- //*************** region: debug ***************
-#ifdef CRU_DEBUG_LAYOUT
- bool IsDebugLayout() const
- {
- return debug_layout_;
- }
-
- void SetDebugLayout(bool value);
-#endif
-
- public:
- //*************** region: events ***************
- Event<events::UiEventArgs> activated_event;
- Event<events::UiEventArgs> deactivated_event;
-
- Event<events::WindowNativeMessageEventArgs> native_message_event;
-
- private:
- //*************** region: native operations ***************
-
- //Get the client rect in pixel.
- RECT GetClientRectPixel();
-
- bool IsMessageInQueue(UINT message);
-
- void SetCursorInternal(HCURSOR cursor);
-
-
- //*************** region: native messages ***************
-
- void OnDestroyInternal();
- void OnPaintInternal();
- void OnResizeInternal(int new_width, int new_height);
-
- void OnSetFocusInternal();
- void OnKillFocusInternal();
-
- void OnMouseMoveInternal(POINT point);
- void OnMouseLeaveInternal();
- void OnMouseDownInternal(MouseButton button, POINT point);
- void OnMouseUpInternal(MouseButton button, POINT point);
-
- void OnMouseWheelInternal(short delta, POINT point);
- void OnKeyDownInternal(int virtual_code);
- void OnKeyUpInternal(int virtual_code);
- void OnCharInternal(wchar_t c);
-
- void OnActivatedInternal();
- void OnDeactivatedInternal();
-
- //*************** region: event dispatcher helper ***************
-
- void DispatchMouseHoverControlChangeEvent(Control* old_control, Control * new_control, const Point& point);
-
- private:
- bool delete_this_on_destroy_ = true;
-
- HWND hwnd_ = nullptr;
- Window* parent_window_ = nullptr;
- std::shared_ptr<graph::WindowRenderTarget> render_target_{};
-
- Control* mouse_hover_control_ = nullptr;
-
- bool window_focus_ = false;
- Control* focus_control_ = this; // "focus_control_" can't be nullptr
-
- Control* mouse_capture_control_ = nullptr;
-
- bool is_layout_invalid_ = false;
-
-#ifdef CRU_DEBUG_LAYOUT
- bool debug_layout_ = false;
-#endif
- };
-}
+ void OnDestroyInternal();
+ void OnPaintInternal();
+ void OnResizeInternal(int new_width, int new_height);
+
+ void OnSetFocusInternal();
+ void OnKillFocusInternal();
+
+ void OnMouseMoveInternal(POINT point);
+ void OnMouseLeaveInternal();
+ void OnMouseDownInternal(MouseButton button, POINT point);
+ void OnMouseUpInternal(MouseButton button, POINT point);
+
+ void OnMouseWheelInternal(short delta, POINT point);
+ void OnKeyDownInternal(int virtual_code);
+ void OnKeyUpInternal(int virtual_code);
+ void OnCharInternal(wchar_t c);
+
+ void OnActivatedInternal();
+ void OnDeactivatedInternal();
+
+ //*************** region: event dispatcher helper ***************
+
+ void DispatchMouseHoverControlChangeEvent(Control* old_control,
+ Control* new_control,
+ const Point& point);
+
+ private:
+ bool delete_this_on_destroy_ = true;
+
+ HWND hwnd_ = nullptr;
+ Window* parent_window_ = nullptr;
+ std::shared_ptr<graph::WindowRenderTarget> render_target_{};
+ std::shared_ptr<render::WindowRenderObject> render_object_{};
+
+ Control* mouse_hover_control_ = nullptr;
+
+ bool window_focus_ = false;
+ Control* focus_control_ = this; // "focus_control_" can't be nullptr
+ Control* mouse_capture_control_ = nullptr;
+};
+} // namespace cru::ui
diff --git a/src/ui/window_class.cpp b/src/ui/window_class.cpp
new file mode 100644
index 00000000..5e8b3454
--- /dev/null
+++ b/src/ui/window_class.cpp
@@ -0,0 +1,28 @@
+#include "window_class.hpp"
+
+#include "exception.hpp"
+
+namespace cru::ui {
+WindowClass::WindowClass(const String& name, WNDPROC window_proc,
+ HINSTANCE h_instance)
+ : name_(name) {
+ WNDCLASSEX window_class;
+ window_class.cbSize = sizeof(WNDCLASSEX);
+
+ window_class.style = CS_HREDRAW | CS_VREDRAW;
+ window_class.lpfnWndProc = window_proc;
+ window_class.cbClsExtra = 0;
+ window_class.cbWndExtra = 0;
+ window_class.hInstance = h_instance;
+ window_class.hIcon = LoadIcon(NULL, IDI_APPLICATION);
+ window_class.hCursor = LoadCursor(NULL, IDC_ARROW);
+ window_class.hbrBackground = GetSysColorBrush(COLOR_BTNFACE);
+ window_class.lpszMenuName = NULL;
+ window_class.lpszClassName = name.c_str();
+ window_class.hIconSm = NULL;
+
+ atom_ = ::RegisterClassExW(&window_class);
+ if (atom_ == 0)
+ throw Win32Error(::GetLastError(), "Failed to create window class.");
+}
+} // namespace cru::ui
diff --git a/src/ui/window_class.hpp b/src/ui/window_class.hpp
new file mode 100644
index 00000000..72a7c431
--- /dev/null
+++ b/src/ui/window_class.hpp
@@ -0,0 +1,26 @@
+#pragma once
+#include "pre.hpp"
+
+#include <Windows.h>
+
+#include "base.hpp"
+
+namespace cru::ui {
+class WindowClass : public Object {
+ public:
+ WindowClass(const String& name, WNDPROC window_proc, HINSTANCE h_instance);
+ WindowClass(const WindowClass& other) = delete;
+ WindowClass(WindowClass&& other) = delete;
+ WindowClass& operator=(const WindowClass& other) = delete;
+ WindowClass& operator=(WindowClass&& other) = delete;
+ ~WindowClass() override = default;
+
+ const wchar_t* GetName() const { return name_.c_str(); }
+
+ ATOM GetAtom() const { return atom_; }
+
+ private:
+ String name_;
+ ATOM atom_;
+};
+} // namespace cru::ui
diff --git a/src/util/com_util.hpp b/src/util/com_util.hpp
new file mode 100644
index 00000000..bbaf1c27
--- /dev/null
+++ b/src/util/com_util.hpp
@@ -0,0 +1,22 @@
+#pragma once
+#include "pre.hpp"
+
+#include <memory>
+
+namespace cru::util {
+
+template <typename TInterface>
+std::shared_ptr<TInterface> CreateComSharedPtr(TInterface* p) {
+ return std::shared_ptr<TInterface>(p, [](TInterface* ptr) {
+ if (ptr != nullptr) ptr->Release();
+ });
+}
+
+template <class TInterface>
+void SafeRelease(TInterface*& p) {
+ if (p != nullptr) {
+ p->Release();
+ p = nullptr;
+ }
+}
+} // namespace cru::util
diff --git a/src/util/format.hpp b/src/util/format.hpp
new file mode 100644
index 00000000..7c1cee05
--- /dev/null
+++ b/src/util/format.hpp
@@ -0,0 +1,100 @@
+#pragma once
+#include "pre.hpp"
+
+#include "base.hpp"
+
+namespace cru::util {
+namespace details {
+template <typename T>
+struct TypeTag {};
+
+constexpr StringView PlaceHolder(TypeTag<String>) { return StringView(L"{}"); }
+
+constexpr MultiByteStringView PlaceHolder(TypeTag<MultiByteString>) {
+ return MultiByteStringView("{}");
+}
+
+template <typename TString>
+void FormatInternal(TString& string) {
+ const auto find_result = string.find(PlaceHolder(TypeTag<TString>{}));
+ if (find_result != TString::npos)
+ throw std::invalid_argument("There is more placeholders than args.");
+}
+
+template <typename TString, typename T, typename... TRest>
+void FormatInternal(TString& string, const T& arg, const TRest&... args) {
+ const auto find_result = string.find(PlaceHolder(TypeTag<TString>{}));
+ if (find_result == TString::npos)
+ throw std::invalid_argument("There is less placeholders than args.");
+
+ string.replace(find_result, 2, FormatToString(arg, TypeTag<TString>{}));
+ FormatInternal<TString>(string, args...);
+}
+} // namespace details
+
+template <typename... T>
+String Format(const StringView& format, const T&... args) {
+ String result(format);
+ details::FormatInternal<String>(result, args...);
+ return result;
+}
+
+template <typename... T>
+MultiByteString Format(const MultiByteStringView& format, const T&... args) {
+ MultiByteString result(format);
+ details::FormatInternal<MultiByteString>(result, args...);
+ return result;
+}
+
+#define CRU_FORMAT_NUMBER(type) \
+ inline String FormatToString(const type number, details::TypeTag<String>) { \
+ return std::to_wstring(number); \
+ } \
+ inline MultiByteString FormatToString(const type number, \
+ details::TypeTag<MultiByteString>) { \
+ return std::to_string(number); \
+ }
+
+CRU_FORMAT_NUMBER(int)
+CRU_FORMAT_NUMBER(short)
+CRU_FORMAT_NUMBER(long)
+CRU_FORMAT_NUMBER(long long)
+CRU_FORMAT_NUMBER(unsigned int)
+CRU_FORMAT_NUMBER(unsigned short)
+CRU_FORMAT_NUMBER(unsigned long)
+CRU_FORMAT_NUMBER(unsigned long long)
+CRU_FORMAT_NUMBER(float)
+CRU_FORMAT_NUMBER(double)
+
+#undef CRU_FORMAT_NUMBER
+
+inline StringView FormatToString(const String& string,
+ details::TypeTag<String>) {
+ return string;
+}
+
+inline MultiByteString FormatToString(const MultiByteString& string,
+ details::TypeTag<MultiByteString>) {
+ return string;
+}
+
+inline StringView FormatToString(const StringView& string,
+ details::TypeTag<String>) {
+ return string;
+}
+
+inline MultiByteStringView FormatToString(const MultiByteStringView& string,
+ details::TypeTag<MultiByteString>) {
+ return string;
+}
+
+inline StringView FormatToString(const wchar_t* string,
+ details::TypeTag<String>) {
+ return StringView(string);
+}
+
+inline MultiByteStringView FormatToString(const char* string,
+ details::TypeTag<MultiByteString>) {
+ return MultiByteString(string);
+}
+} // namespace cru::util
diff --git a/src/util/math_util.hpp b/src/util/math_util.hpp
new file mode 100644
index 00000000..01348641
--- /dev/null
+++ b/src/util/math_util.hpp
@@ -0,0 +1,51 @@
+#pragma once
+#include "pre.hpp"
+
+#include <optional>
+#include <type_traits>
+
+namespace cru::util {
+template <typename T, typename = std::enable_if_t<std::is_arithmetic_v<T>>>
+float Coerce(const T n, const std::optional<T> min,
+ const std::optional<T> max) {
+ if (min.has_value() && n < min.value()) return min.value();
+ if (max.has_value() && n > max.value()) return max.value();
+ return n;
+}
+
+template <typename T, typename = std::enable_if_t<std::is_arithmetic_v<T>>>
+float Coerce(const T n, const T min, const T max) {
+ if (n < min) return min;
+ if (n > max) return max;
+ return n;
+}
+
+template <typename T, typename = std::enable_if_t<std::is_arithmetic_v<T>>>
+float Coerce(const T n, const std::nullopt_t, const std::optional<T> max) {
+ if (max.has_value() && n > max.value()) return max.value();
+ return n;
+}
+
+template <typename T, typename = std::enable_if_t<std::is_arithmetic_v<T>>>
+float Coerce(const T n, const std::optional<T> min, const std::nullopt_t) {
+ if (min.has_value() && n < min.value()) return min.value();
+ return n;
+}
+
+template <typename T, typename = std::enable_if_t<std::is_arithmetic_v<T>>>
+float Coerce(const T n, const std::nullopt_t, const T max) {
+ if (n > max) return max;
+ return n;
+}
+
+template <typename T, typename = std::enable_if_t<std::is_arithmetic_v<T>>>
+float Coerce(const T n, const T min, const std::nullopt_t) {
+ if (n < min) return min;
+ return n;
+}
+
+template <typename T, typename = std::enable_if_t<std::is_arithmetic_v<T>>>
+T AtLeast0(const T value) {
+ return value < static_cast<T>(0) ? static_cast<T>(0) : value;
+}
+} // namespace cru::util
diff --git a/src/util/string_util.cpp b/src/util/string_util.cpp
new file mode 100644
index 00000000..c9391fc6
--- /dev/null
+++ b/src/util/string_util.cpp
@@ -0,0 +1,22 @@
+#include "string_util.hpp"
+
+#include <Windows.h>
+
+#include "exception.hpp"
+
+namespace cru::util {
+MultiByteString ToUtf8String(const StringView& string) {
+ if (string.empty()) return MultiByteString();
+
+ const auto length = ::WideCharToMultiByte(CP_UTF8, 0, string.data(), -1,
+ nullptr, 0, nullptr, nullptr);
+ MultiByteString result;
+ result.reserve(length);
+ if (::WideCharToMultiByte(CP_UTF8, 0, string.data(), -1, result.data(),
+ static_cast<int>(result.capacity()), nullptr,
+ nullptr) == 0)
+ throw Win32Error(::GetLastError(),
+ "Failed to convert wide string to UTF-8.");
+ return result;
+}
+} // namespace cru::util
diff --git a/src/util/string_util.hpp b/src/util/string_util.hpp
new file mode 100644
index 00000000..6d060089
--- /dev/null
+++ b/src/util/string_util.hpp
@@ -0,0 +1,8 @@
+#pragma once
+#include "pre.hpp"
+
+#include "base.hpp"
+
+namespace cru::util {
+MultiByteString ToUtf8String(const StringView& string);
+}