diff options
author | 杨宇千 <crupest@outlook.com> | 2019-03-28 20:39:36 +0800 |
---|---|---|
committer | GitHub <noreply@github.com> | 2019-03-28 20:39:36 +0800 |
commit | c45a6e62298e972f5945f5f3461ed723aea80317 (patch) | |
tree | f46ef303ee87a8e3814ea8743bd7062d432bfee3 /src | |
parent | b028e74a48de181ca078ad3bf4ababf4fa146cd3 (diff) | |
parent | 37216f211b0e22205a3a0d3373d985fc68aea59b (diff) | |
download | cru-c45a6e62298e972f5945f5f3461ed723aea80317.tar.gz cru-c45a6e62298e972f5945f5f3461ed723aea80317.tar.bz2 cru-c45a6e62298e972f5945f5f3461ed723aea80317.zip |
Merge pull request #37 from crupest/render
Refactor.
Diffstat (limited to 'src')
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); +} |