From 2acd4830d71715054338126d7968762b0bf06cf6 Mon Sep 17 00:00:00 2001 From: crupest Date: Tue, 25 Sep 2018 01:38:56 +0800 Subject: Develop ci. --- script/build.ps1 | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 script/build.ps1 (limited to 'script/build.ps1') diff --git a/script/build.ps1 b/script/build.ps1 new file mode 100644 index 00000000..fb928555 --- /dev/null +++ b/script/build.ps1 @@ -0,0 +1,6 @@ +cd ..\CruUI +Remove-Item build -Recurse -ErrorAction Ignore +New-Item build -ItemType Directory +cd .\build +cmake -G "Visual Studio 15 2017 Win64" .. +cmake --build . -- cgit v1.2.3 From 2f015d2b6d221ac47aaaee9b675648370b6bca7c Mon Sep 17 00:00:00 2001 From: crupest Date: Tue, 25 Sep 2018 01:45:17 +0800 Subject: Update ci. --- .gitlab-ci.yml | 1 - script/build.ps1 | 2 ++ 2 files changed, 2 insertions(+), 1 deletion(-) (limited to 'script/build.ps1') diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 46876fa6..de979b71 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -4,5 +4,4 @@ stages: build: stage: build script: - - vcpkg.exe install folly:x64-windows fmt:x64-windows - ./script/build.ps1 diff --git a/script/build.ps1 b/script/build.ps1 index fb928555..125c305c 100644 --- a/script/build.ps1 +++ b/script/build.ps1 @@ -1,3 +1,5 @@ +vcpkg.exe install folly:x64-windows fmt:x64-windows + cd ..\CruUI Remove-Item build -Recurse -ErrorAction Ignore New-Item build -ItemType Directory -- cgit v1.2.3 From 4b86554a0354d78efeb40e551eaccaac0fecd1d1 Mon Sep 17 00:00:00 2001 From: crupest Date: Tue, 25 Sep 2018 13:08:40 +0800 Subject: Change the structure of project. --- CruUI.sln | 22 +- CruUI.vcxproj | 161 ++++++++ CruUI.vcxproj.filters | 138 +++++++ CruUI/CMakeLists.txt | 32 -- CruUI/CruUI.vcxproj | 201 ---------- CruUI/CruUI.vcxproj.filters | 138 ------- CruUI/application.cpp | 145 ------- CruUI/application.h | 141 ------- CruUI/base.cpp | 20 - CruUI/base.h | 107 ----- CruUI/cru_event.h | 91 ----- CruUI/debug_base.h | 45 --- CruUI/exception.cpp | 34 -- CruUI/exception.h | 58 --- CruUI/global_macros.h | 11 - CruUI/graph/graph.cpp | 245 ------------ CruUI/graph/graph.h | 180 --------- CruUI/main.cpp | 150 ------- CruUI/system_headers.h | 21 - CruUI/timer.cpp | 79 ---- CruUI/timer.h | 43 -- CruUI/ui/animations/animation.cpp | 190 --------- CruUI/ui/animations/animation.h | 107 ----- CruUI/ui/control.cpp | 712 --------------------------------- CruUI/ui/control.h | 380 ------------------ CruUI/ui/controls/button.cpp | 33 -- CruUI/ui/controls/button.h | 41 -- CruUI/ui/controls/linear_layout.cpp | 178 --------- CruUI/ui/controls/linear_layout.h | 41 -- CruUI/ui/controls/margin_container.cpp | 63 --- CruUI/ui/controls/margin_container.h | 44 -- CruUI/ui/controls/text_block.cpp | 283 ------------- CruUI/ui/controls/text_block.h | 116 ------ CruUI/ui/controls/text_box.cpp | 244 ----------- CruUI/ui/controls/text_box.h | 85 ---- CruUI/ui/controls/toggle_button.cpp | 150 ------- CruUI/ui/controls/toggle_button.h | 61 --- CruUI/ui/events/ui_event.cpp | 19 - CruUI/ui/events/ui_event.h | 317 --------------- CruUI/ui/layout_base.cpp | 79 ---- CruUI/ui/layout_base.h | 136 ------- CruUI/ui/ui_base.cpp | 6 - CruUI/ui/ui_base.h | 154 ------- CruUI/ui/window.cpp | 606 ---------------------------- CruUI/ui/window.h | 276 ------------- script/build.ps1 | 16 +- src/application.cpp | 145 +++++++ src/application.h | 141 +++++++ src/base.cpp | 20 + src/base.h | 107 +++++ src/cru_event.h | 91 +++++ src/debug_base.h | 45 +++ src/exception.cpp | 34 ++ src/exception.h | 58 +++ src/global_macros.h | 11 + src/graph/graph.cpp | 245 ++++++++++++ src/graph/graph.h | 180 +++++++++ src/main.cpp | 150 +++++++ src/system_headers.h | 21 + src/timer.cpp | 79 ++++ src/timer.h | 43 ++ src/ui/animations/animation.cpp | 190 +++++++++ src/ui/animations/animation.h | 107 +++++ src/ui/control.cpp | 712 +++++++++++++++++++++++++++++++++ src/ui/control.h | 380 ++++++++++++++++++ src/ui/controls/button.cpp | 33 ++ src/ui/controls/button.h | 41 ++ src/ui/controls/linear_layout.cpp | 178 +++++++++ src/ui/controls/linear_layout.h | 41 ++ src/ui/controls/margin_container.cpp | 63 +++ src/ui/controls/margin_container.h | 44 ++ src/ui/controls/text_block.cpp | 283 +++++++++++++ src/ui/controls/text_block.h | 116 ++++++ src/ui/controls/text_box.cpp | 244 +++++++++++ src/ui/controls/text_box.h | 85 ++++ src/ui/controls/toggle_button.cpp | 150 +++++++ src/ui/controls/toggle_button.h | 61 +++ src/ui/events/ui_event.cpp | 19 + src/ui/events/ui_event.h | 317 +++++++++++++++ src/ui/layout_base.cpp | 79 ++++ src/ui/layout_base.h | 136 +++++++ src/ui/ui_base.cpp | 6 + src/ui/ui_base.h | 154 +++++++ src/ui/window.cpp | 606 ++++++++++++++++++++++++++++ src/ui/window.h | 276 +++++++++++++ 85 files changed, 6011 insertions(+), 6079 deletions(-) create mode 100644 CruUI.vcxproj create mode 100644 CruUI.vcxproj.filters delete mode 100644 CruUI/CMakeLists.txt delete mode 100644 CruUI/CruUI.vcxproj delete mode 100644 CruUI/CruUI.vcxproj.filters delete mode 100644 CruUI/application.cpp delete mode 100644 CruUI/application.h delete mode 100644 CruUI/base.cpp delete mode 100644 CruUI/base.h delete mode 100644 CruUI/cru_event.h delete mode 100644 CruUI/debug_base.h delete mode 100644 CruUI/exception.cpp delete mode 100644 CruUI/exception.h delete mode 100644 CruUI/global_macros.h delete mode 100644 CruUI/graph/graph.cpp delete mode 100644 CruUI/graph/graph.h delete mode 100644 CruUI/main.cpp delete mode 100644 CruUI/system_headers.h delete mode 100644 CruUI/timer.cpp delete mode 100644 CruUI/timer.h delete mode 100644 CruUI/ui/animations/animation.cpp delete mode 100644 CruUI/ui/animations/animation.h delete mode 100644 CruUI/ui/control.cpp delete mode 100644 CruUI/ui/control.h delete mode 100644 CruUI/ui/controls/button.cpp delete mode 100644 CruUI/ui/controls/button.h delete mode 100644 CruUI/ui/controls/linear_layout.cpp delete mode 100644 CruUI/ui/controls/linear_layout.h delete mode 100644 CruUI/ui/controls/margin_container.cpp delete mode 100644 CruUI/ui/controls/margin_container.h delete mode 100644 CruUI/ui/controls/text_block.cpp delete mode 100644 CruUI/ui/controls/text_block.h delete mode 100644 CruUI/ui/controls/text_box.cpp delete mode 100644 CruUI/ui/controls/text_box.h delete mode 100644 CruUI/ui/controls/toggle_button.cpp delete mode 100644 CruUI/ui/controls/toggle_button.h delete mode 100644 CruUI/ui/events/ui_event.cpp delete mode 100644 CruUI/ui/events/ui_event.h delete mode 100644 CruUI/ui/layout_base.cpp delete mode 100644 CruUI/ui/layout_base.h delete mode 100644 CruUI/ui/ui_base.cpp delete mode 100644 CruUI/ui/ui_base.h delete mode 100644 CruUI/ui/window.cpp delete mode 100644 CruUI/ui/window.h create mode 100644 src/application.cpp create mode 100644 src/application.h create mode 100644 src/base.cpp create mode 100644 src/base.h create mode 100644 src/cru_event.h create mode 100644 src/debug_base.h create mode 100644 src/exception.cpp create mode 100644 src/exception.h create mode 100644 src/global_macros.h create mode 100644 src/graph/graph.cpp create mode 100644 src/graph/graph.h create mode 100644 src/main.cpp create mode 100644 src/system_headers.h create mode 100644 src/timer.cpp create mode 100644 src/timer.h create mode 100644 src/ui/animations/animation.cpp create mode 100644 src/ui/animations/animation.h create mode 100644 src/ui/control.cpp create mode 100644 src/ui/control.h create mode 100644 src/ui/controls/button.cpp create mode 100644 src/ui/controls/button.h create mode 100644 src/ui/controls/linear_layout.cpp create mode 100644 src/ui/controls/linear_layout.h create mode 100644 src/ui/controls/margin_container.cpp create mode 100644 src/ui/controls/margin_container.h create mode 100644 src/ui/controls/text_block.cpp create mode 100644 src/ui/controls/text_block.h create mode 100644 src/ui/controls/text_box.cpp create mode 100644 src/ui/controls/text_box.h create mode 100644 src/ui/controls/toggle_button.cpp create mode 100644 src/ui/controls/toggle_button.h create mode 100644 src/ui/events/ui_event.cpp create mode 100644 src/ui/events/ui_event.h create mode 100644 src/ui/layout_base.cpp create mode 100644 src/ui/layout_base.h create mode 100644 src/ui/ui_base.cpp create mode 100644 src/ui/ui_base.h create mode 100644 src/ui/window.cpp create mode 100644 src/ui/window.h (limited to 'script/build.ps1') diff --git a/CruUI.sln b/CruUI.sln index 60437b34..876bbf39 100644 --- a/CruUI.sln +++ b/CruUI.sln @@ -1,9 +1,9 @@  Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio 15 -VisualStudioVersion = 15.0.27130.2027 +VisualStudioVersion = 15.0.28010.2036 MinimumVisualStudioVersion = 10.0.40219.1 -Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "CruUI", "CruUI\CruUI.vcxproj", "{41F57B7F-DECE-4021-8EEC-1E8E54E9DFAC}" +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "CruUI", "CruUI.vcxproj", "{A987AC8E-C365-44F3-8324-CBA95B33AB97}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -13,19 +13,19 @@ Global Release|x86 = Release|x86 EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution - {41F57B7F-DECE-4021-8EEC-1E8E54E9DFAC}.Debug|x64.ActiveCfg = Debug|x64 - {41F57B7F-DECE-4021-8EEC-1E8E54E9DFAC}.Debug|x64.Build.0 = Debug|x64 - {41F57B7F-DECE-4021-8EEC-1E8E54E9DFAC}.Debug|x86.ActiveCfg = Debug|Win32 - {41F57B7F-DECE-4021-8EEC-1E8E54E9DFAC}.Debug|x86.Build.0 = Debug|Win32 - {41F57B7F-DECE-4021-8EEC-1E8E54E9DFAC}.Release|x64.ActiveCfg = Release|x64 - {41F57B7F-DECE-4021-8EEC-1E8E54E9DFAC}.Release|x64.Build.0 = Release|x64 - {41F57B7F-DECE-4021-8EEC-1E8E54E9DFAC}.Release|x86.ActiveCfg = Release|Win32 - {41F57B7F-DECE-4021-8EEC-1E8E54E9DFAC}.Release|x86.Build.0 = Release|Win32 + {A987AC8E-C365-44F3-8324-CBA95B33AB97}.Debug|x64.ActiveCfg = Debug|x64 + {A987AC8E-C365-44F3-8324-CBA95B33AB97}.Debug|x64.Build.0 = Debug|x64 + {A987AC8E-C365-44F3-8324-CBA95B33AB97}.Debug|x86.ActiveCfg = Debug|Win32 + {A987AC8E-C365-44F3-8324-CBA95B33AB97}.Debug|x86.Build.0 = Debug|Win32 + {A987AC8E-C365-44F3-8324-CBA95B33AB97}.Release|x64.ActiveCfg = Release|x64 + {A987AC8E-C365-44F3-8324-CBA95B33AB97}.Release|x64.Build.0 = Release|x64 + {A987AC8E-C365-44F3-8324-CBA95B33AB97}.Release|x86.ActiveCfg = Release|Win32 + {A987AC8E-C365-44F3-8324-CBA95B33AB97}.Release|x86.Build.0 = Release|Win32 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution - SolutionGuid = {B0298D8B-D319-488F-99DA-9D5E6851B9F2} + SolutionGuid = {89B380B3-5C97-4753-AA00-5E73F9023E30} EndGlobalSection EndGlobal diff --git a/CruUI.vcxproj b/CruUI.vcxproj new file mode 100644 index 00000000..bb1e1f63 --- /dev/null +++ b/CruUI.vcxproj @@ -0,0 +1,161 @@ + + + + + Debug + Win32 + + + Release + Win32 + + + Debug + x64 + + + Release + x64 + + + + 15.0 + {A987AC8E-C365-44F3-8324-CBA95B33AB97} + Win32Proj + 10.0.17134.0 + + + + Application + true + v141 + + + Application + false + v141 + + + Application + true + v141 + Unicode + + + Application + false + v141 + Unicode + + + + + + + + + + + + + + + + + + + + + true + + + true + + + + WIN32;_DEBUG;_WINDOWS;%(PreprocessorDefinitions) + MultiThreadedDebugDLL + Level3 + ProgramDatabase + Disabled + + + MachineX86 + true + Windows + + + + + WIN32;NDEBUG;_WINDOWS;%(PreprocessorDefinitions) + MultiThreadedDLL + Level3 + ProgramDatabase + + + MachineX86 + true + Windows + true + true + + + + + $(ProjectDir)/src;%(AdditionalIncludeDirectories) + stdcpplatest + + + + + $(ProjectDir)/src;%(AdditionalIncludeDirectories) + stdcpplatest + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/CruUI.vcxproj.filters b/CruUI.vcxproj.filters new file mode 100644 index 00000000..0933ffc9 --- /dev/null +++ b/CruUI.vcxproj.filters @@ -0,0 +1,138 @@ + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hh;hpp;hxx;hm;inl;inc;xsd + + + {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} + rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav + + + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + \ No newline at end of file diff --git a/CruUI/CMakeLists.txt b/CruUI/CMakeLists.txt deleted file mode 100644 index e7e4fb3f..00000000 --- a/CruUI/CMakeLists.txt +++ /dev/null @@ -1,32 +0,0 @@ -cmake_minimum_required(VERSION 3.11) -project(CruUI) - -if (MSVC_VERSION GREATER_EQUAL "1900") - include(CheckCXXCompilerFlag) - CHECK_CXX_COMPILER_FLAG("/std:c++latest" _cpp_latest_flag_supported) - if (_cpp_latest_flag_supported) - add_compile_options("/std:c++latest") - endif() -endif() - -include_directories(.) - -add_executable(CruUI - graph/graph.cpp - ui/animations/animation.cpp - ui/controls/button.cpp - ui/controls/linear_layout.cpp - ui/controls/margin_container.cpp - ui/controls/text_block.cpp - ui/controls/text_box.cpp - ui/controls/toggle_button.cpp - ui/events/ui_event.cpp - ui/control.cpp - ui/layout_base.cpp - ui/ui_base.cpp - ui/window.cpp - application.cpp - base.cpp - exception.cpp - main.cpp - timer.cpp) diff --git a/CruUI/CruUI.vcxproj b/CruUI/CruUI.vcxproj deleted file mode 100644 index 84462410..00000000 --- a/CruUI/CruUI.vcxproj +++ /dev/null @@ -1,201 +0,0 @@ - - - - - Debug - Win32 - - - Release - Win32 - - - Debug - x64 - - - Release - x64 - - - - 15.0 - {41F57B7F-DECE-4021-8EEC-1E8E54E9DFAC} - Win32Proj - CruUI - 10.0.17134.0 - - - - Application - true - v141 - Unicode - - - Application - false - v141 - true - Unicode - - - Application - true - v141 - Unicode - - - Application - false - v141 - true - Unicode - - - - - - - - - - - - - - - - - - - - - true - - - true - $(ProjectDir);$(IncludePath) - - - false - - - false - - - - NotUsing - Level3 - Disabled - true - WIN32;_DEBUG;_WINDOWS;%(PreprocessorDefinitions) - true - $(ProjectDir);%(AdditionalIncludeDirectories) - - - - Windows - true - - - - - NotUsing - Level3 - Disabled - true - _SILENCE_ALL_CXX17_DEPRECATION_WARNINGS;_DEBUG;_WINDOWS;%(PreprocessorDefinitions) - true - stdcpplatest - - - Windows - true - - - - - Use - Level3 - MaxSpeed - true - true - true - WIN32;NDEBUG;_WINDOWS;%(PreprocessorDefinitions) - true - - - Windows - true - true - true - - - - - NotUsing - Level3 - MaxSpeed - true - true - true - _SILENCE_CXX17_OLD_ALLOCATOR_MEMBERS_DEPRECATION_WARNING;NDEBUG;_WINDOWS;%(PreprocessorDefinitions) - true - $(ProjectDir);%(AdditionalIncludeDirectories) - stdcpplatest - - - Windows - true - true - true - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/CruUI/CruUI.vcxproj.filters b/CruUI/CruUI.vcxproj.filters deleted file mode 100644 index 3e4538fc..00000000 --- a/CruUI/CruUI.vcxproj.filters +++ /dev/null @@ -1,138 +0,0 @@ - - - - - {4FC737F1-C7A5-4376-A066-2A32D752A2FF} - cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx - - - {93995380-89BD-4b04-88EB-625FBE52EBFB} - h;hh;hpp;hxx;hm;inl;inc;xsd - - - {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} - rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms - - - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - \ No newline at end of file diff --git a/CruUI/application.cpp b/CruUI/application.cpp deleted file mode 100644 index af38116f..00000000 --- a/CruUI/application.cpp +++ /dev/null @@ -1,145 +0,0 @@ -#include "application.h" - -#include - -#include "exception.h" -#include "timer.h" -#include "ui/window.h" -#include "graph/graph.h" -#include "ui/animations/animation.h" - -namespace cru { - constexpr auto god_window_class_name = L"GodWindowClass"; - constexpr int invoke_later_message_id = WM_USER + 2000; - - - LRESULT 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) : application_(application) - { - const auto h_instance = application->GetInstanceHandle(); - - god_window_class_ = std::make_unique(god_window_class_name, GodWndProc, h_instance); - - hwnd_ = CreateWindowEx(0, - god_window_class_name, - L"", 0, - CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, - nullptr, nullptr, h_instance, nullptr - ); - - if (hwnd_ == nullptr) - throw std::runtime_error("Failed to create window."); - } - - GodWindow::~GodWindow() - { - ::DestroyWindow(hwnd_); - } - - std::optional 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(w_param); - (*p_action)(); - delete p_action; - return 0; - } - case WM_TIMER: - { - const auto action = application_->GetTimerManager()->GetAction(static_cast(w_param)); - if (action) - { - (*action)(); - return 0; - } - break; - } - default: - return std::nullopt; - } - return std::nullopt; - } - - - - Application* Application::instance_ = nullptr; - - Application * Application::GetInstance() { - return instance_; - } - - Application::Application(HINSTANCE h_instance) - : h_instance_(h_instance) { - - if (instance_) - throw std::runtime_error("A application instance already exists."); - - instance_ = this; - - window_manager_ = std::make_unique(); - graph_manager_ = std::make_unique(); - timer_manager_ = std::make_unique(); - animation_manager_ = std::make_unique(); - - god_window_ = std::make_unique(this); - -#ifdef CRU_DEBUG_DRAW_CONTROL_BORDER - debug_border_brush_ = graph::CreateSolidBrush(D2D1::ColorF(D2D1::ColorF::Crimson)); -#endif - - 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; - } - - Application::~Application() - { - animation_manager_.reset(); - instance_ = nullptr; - } - - int Application::Run() - { - MSG msg; - - while (GetMessage(&msg, nullptr, 0, 0)) - { - TranslateMessage(&msg); - DispatchMessage(&msg); - } - - return static_cast(msg.wParam); - } - - void Application::Quit(const int quit_code) { - ::PostQuitMessage(quit_code); - } - - void InvokeLater(InvokeLaterAction&& action) { - //copy the action to a safe place - auto p_action_copy = new InvokeLaterAction(std::move(action)); - - if (PostMessageW(Application::GetInstance()->GetGodWindow()->GetHandle(), invoke_later_message_id, reinterpret_cast(p_action_copy), 0) == 0) - throw Win32Error(::GetLastError(), "InvokeLater failed to post message."); - } -} diff --git a/CruUI/application.h b/CruUI/application.h deleted file mode 100644 index 106e7171..00000000 --- a/CruUI/application.h +++ /dev/null @@ -1,141 +0,0 @@ -#pragma once - -#include "system_headers.h" -#include -#include - -#include "base.h" - -namespace cru -{ - class Application; - - namespace ui - { - class WindowClass; - class WindowManager; - - namespace animations::details - { - class AnimationManager; - } - } - - namespace graph - { - class GraphManager; - } - - class TimerManager; - - struct CaretInfo - { - std::chrono::milliseconds caret_blink_duration; - float half_caret_width; - }; - - 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 HandleGodWindowMessage(HWND hwnd, int msg, WPARAM w_param, LPARAM l_param); - - private: - Application* application_; - std::unique_ptr 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); - - ui::WindowManager* GetWindowManager() const - { - return window_manager_.get(); - } - - graph::GraphManager* GetGraphManager() const - { - return graph_manager_.get(); - } - - TimerManager* GetTimerManager() const - { - return timer_manager_.get(); - } - - ui::animations::details::AnimationManager* GetAnimationManager() const - { - return animation_manager_.get(); - } - - HINSTANCE GetInstanceHandle() const - { - return h_instance_; - } - - GodWindow* GetGodWindow() const - { - return god_window_.get(); - } - - CaretInfo GetCaretInfo() const - { - return caret_info_; - } - -#ifdef CRU_DEBUG_DRAW_CONTROL_BORDER - Microsoft::WRL::ComPtr GetDebugBorderBrush() const - { - return debug_border_brush_; - } -#endif - - private: - HINSTANCE h_instance_; - - std::unique_ptr window_manager_; - std::unique_ptr graph_manager_; - std::unique_ptr timer_manager_; - std::unique_ptr animation_manager_; - - std::unique_ptr god_window_; - -#ifdef CRU_DEBUG_DRAW_CONTROL_BORDER - Microsoft::WRL::ComPtr debug_border_brush_; -#endif - - CaretInfo caret_info_; - }; - - - using InvokeLaterAction = Function; - void InvokeLater(InvokeLaterAction&& action); -} diff --git a/CruUI/base.cpp b/CruUI/base.cpp deleted file mode 100644 index f5868170..00000000 --- a/CruUI/base.cpp +++ /dev/null @@ -1,20 +0,0 @@ -#include "base.h" - -#include "system_headers.h" -#include "exception.h" - -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(result.capacity()), nullptr, nullptr) == 0) - throw Win32Error(::GetLastError(), "Failed to convert wide string to UTF-8."); - return result; - } -} diff --git a/CruUI/base.h b/CruUI/base.h deleted file mode 100644 index 7ef78014..00000000 --- a/CruUI/base.h +++ /dev/null @@ -1,107 +0,0 @@ -#pragma once - -// ReSharper disable once CppUnusedIncludeDirective -#include "global_macros.h" - - -#ifdef CRU_DEBUG -#include -#include -#else -#include -#include -#endif - -#include - -#include -#include -#include -#include -#include -#include - -namespace cru -{ - template struct is_shared_ptr : std::false_type {}; - template struct is_shared_ptr> : std::true_type {}; - template constexpr bool is_shared_ptr_v = is_shared_ptr::value; - - enum class FlowControl - { - Continue, - Break - }; - -#ifdef CRU_DEBUG - using String = std::wstring; - using MultiByteString = std::string; -#else - using String = folly::basic_fbstring; - using MultiByteString = folly::fbstring; -#endif - - using StringView = std::wstring_view; - using MultiByteStringView = std::string_view; - - template - using Function = folly::Function; - - template - using FunctionPtr = std::shared_ptr>; - - using Action = Function; - using ActionPtr = FunctionPtr; - - template - Type CreatePtr(Args&&... args) - { - static_assert(is_shared_ptr_v); - return std::make_shared(std::forward(args)...); - } - - inline ActionPtr CreateActionPtr(Action&& action) - { - return std::make_shared(std::move(action)); - } - -#ifdef CRU_DEBUG - template - using Vector = std::vector; -#else - template - using Vector = folly::fbvector; -#endif - - using FloatSecond = std::chrono::duration; - - 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."); - } - - struct ICancelable : virtual Interface - { - virtual void Cancel() = 0; - }; - - using CancelablePtr = std::shared_ptr; - - MultiByteString ToUtf8String(const StringView& string); -} diff --git a/CruUI/cru_event.h b/CruUI/cru_event.h deleted file mode 100644 index d0a7eb82..00000000 --- a/CruUI/cru_event.h +++ /dev/null @@ -1,91 +0,0 @@ -#pragma once - -#include -#include -#include -#include - -#include "base.h" - -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 - class Event - { - public: - static_assert(std::is_base_of_v, - "TArgsType must be subclass of BasicEventArgs."); - - - using ArgsType = TArgsType; - using EventHandler = Function; - using EventHandlerPtr = std::shared_ptr; - - Event() = default; - Event(const Event&) = delete; - Event& operator = (const Event&) = delete; - Event(Event&&) = delete; - Event& operator = (Event&&) = delete; - ~Event() = default; - - //Create a EventHandlerPtr from the given handler, - //add it to list and return it. - EventHandlerPtr AddHandler(EventHandler&& handler) - { - EventHandlerPtr ptr = std::make_shared(std::move(handler)); - handlers_.push_back(ptr); - return ptr; - } - - void AddHandler(EventHandlerPtr handler) { - handlers_.push_back(handler); - } - - void RemoveHandler(EventHandlerPtr handler) { - auto find_result = std::find(handlers_.cbegin(), handlers_.cend(), handler); - if (find_result != handlers_.cend()) - handlers_.erase(find_result); - } - - void Raise(ArgsType& args) { - for (auto ptr : handlers_) - (*ptr)(args); - } - - bool IsNoHandler() const - { - return handlers_.empty(); - } - - private: - std::list handlers_; - }; -} diff --git a/CruUI/debug_base.h b/CruUI/debug_base.h deleted file mode 100644 index 7669b9df..00000000 --- a/CruUI/debug_base.h +++ /dev/null @@ -1,45 +0,0 @@ -#pragma once - - -#include "system_headers.h" -#include -#include -#include - -#include "base.h" - -namespace cru::debug -{ -#ifdef CRU_DEBUG - inline void DebugTime(Function&& 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(after - before); - OutputDebugStringW(fmt::format(L"{}: {}ms.\n", hint_message, duration.count()).c_str()); - } - - template - TReturn DebugTime(Function&& 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(after - before); - OutputDebugStringW(fmt::format(L"{}: {}ms.\n", hint_message, duration.count()).c_str()); - return std::move(result); - } -#else - inline void DebugTime(Function&& action, const StringView& hint_message) - { - action(); - } - - template - TReturn DebugTime(Function&& action, const StringView& hint_message) - { - return action(); - } -#endif -} diff --git a/CruUI/exception.cpp b/CruUI/exception.cpp deleted file mode 100644 index a1d59ceb..00000000 --- a/CruUI/exception.cpp +++ /dev/null @@ -1,34 +0,0 @@ -#include "exception.h" - -#include - -namespace cru -{ - inline std::string HResultMakeMessage(HRESULT h_result, std::optional message) - { - if (message.has_value()) - return fmt::format("An HResultError is thrown. HRESULT: {:#08x}.\nAdditional message: {}\n", h_result, message.value()); - else - return fmt::format("An HResultError is thrown. HRESULT: {:#08x}.\n", h_result); - } - - HResultError::HResultError(HRESULT h_result, std::optional additional_message) - : runtime_error(HResultMakeMessage(h_result, std::nullopt)), h_result_(h_result) - { - - } - - inline std::string Win32MakeMessage(DWORD error_code, std::optional message) - { - if (message.has_value()) - return fmt::format("Last error code: {:#04x}.\nAdditional message: {}\n", error_code, message.value()); - else - return fmt::format("Last error code: {:#04x}.\n", error_code); - } - - Win32Error::Win32Error(DWORD error_code, std::optional additional_message) - : runtime_error(Win32MakeMessage(error_code, std::nullopt)), error_code_(error_code) - { - - } -} diff --git a/CruUI/exception.h b/CruUI/exception.h deleted file mode 100644 index 2817f261..00000000 --- a/CruUI/exception.h +++ /dev/null @@ -1,58 +0,0 @@ -#pragma once - -#include "system_headers.h" -#include -#include - -#include "base.h" - - -namespace cru { - class HResultError : public std::runtime_error - { - public: - explicit HResultError(HRESULT h_result, std::optional 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 std::string& message) { - if (FAILED(h_result)) - throw HResultError(h_result, message); - } - - class Win32Error : public std::runtime_error - { - public: - explicit Win32Error(DWORD error_code, std::optional 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_; - }; -} diff --git a/CruUI/global_macros.h b/CruUI/global_macros.h deleted file mode 100644 index 696c5d2b..00000000 --- a/CruUI/global_macros.h +++ /dev/null @@ -1,11 +0,0 @@ -#pragma once - -#ifdef _DEBUG -#define CRU_DEBUG -#endif - -#define GLOG_NO_ABBREVIATED_SEVERITIES - -#ifdef CRU_DEBUG -#define CRU_DEBUG_DRAW_CONTROL_BORDER -#endif diff --git a/CruUI/graph/graph.cpp b/CruUI/graph/graph.cpp deleted file mode 100644 index 30b51413..00000000 --- a/CruUI/graph/graph.cpp +++ /dev/null @@ -1,245 +0,0 @@ -#include "graph.h" - -#include "application.h" -#include "exception.h" - -namespace cru { - namespace 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 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 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()->GetGraphManager(); - } - - GraphManager::GraphManager() - { - UINT creation_flags = D3D11_CREATE_DEVICE_BGRA_SUPPORT; - -#ifdef _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 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 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(dwrite_factory_.GetAddressOf()) - )); - - dwrite_factory_->GetSystemFontCollection(&dwrite_system_font_collection_); - } - - GraphManager::~GraphManager() - { - - } - - std::shared_ptr GraphManager::CreateWindowRenderTarget(HWND hwnd) - { - return std::make_shared(this, hwnd); - } - - Dpi GraphManager::GetDpi() const - { - Dpi dpi; - d2d1_factory_->GetDesktopDpi(&dpi.x, &dpi.y); - return dpi; - } - - void GraphManager::ReloadSystemMetrics() - { - ThrowIfFailed( - d2d1_factory_->ReloadSystemMetrics() - ); - } - - ComPtr CreateSolidBrush(const D2D1_COLOR_F& color) - { - const auto device_context = GraphManager::GetInstance()->GetD2D1DeviceContext(); - ComPtr solid_color_brush; - device_context->CreateSolidColorBrush(color, &solid_color_brush); - return solid_color_brush; - } - - ComPtr CreateDefaultTextFormat() - { - const auto dwrite_factory = GraphManager::GetInstance()->GetDWriteFactory(); - - ComPtr 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; - } - } -} diff --git a/CruUI/graph/graph.h b/CruUI/graph/graph.h deleted file mode 100644 index 0f1d29d1..00000000 --- a/CruUI/graph/graph.h +++ /dev/null @@ -1,180 +0,0 @@ -#pragma once - -#include "system_headers.h" -#include - -#include "base.h" -#include "application.h" - - -namespace cru -{ - namespace 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 GetD2DDeviceContext() const; - - //Get the target bitmap which can be set as the ID2D1DeviceContext's target. - Microsoft::WRL::ComPtr 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 dxgi_swap_chain_; - Microsoft::WRL::ComPtr target_bitmap_; - }; - - struct Dpi - { - float x; - float y; - }; - - class GraphManager : public Object - { - public: - static GraphManager* GetInstance(); - - public: - GraphManager(); - 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 GetD2D1Factory() const - { - return d2d1_factory_; - } - - Microsoft::WRL::ComPtr GetD2D1DeviceContext() const - { - return d2d1_device_context_; - } - - Microsoft::WRL::ComPtr GetD3D11Device() const - { - return d3d11_device_; - } - - Microsoft::WRL::ComPtr GetDxgiFactory() const - { - return dxgi_factory_; - } - - Microsoft::WRL::ComPtr GetDWriteFactory() const - { - return dwrite_factory_; - } - - - //Create a window render target with the HWND. - std::shared_ptr CreateWindowRenderTarget(HWND hwnd); - - //Get the desktop dpi. - Dpi GetDpi() const; - - //Reload system metrics including desktop dpi. - void ReloadSystemMetrics(); - - Microsoft::WRL::ComPtr GetSystemFontCollection() const - { - return dwrite_system_font_collection_.Get(); - } - - private: - Microsoft::WRL::ComPtr d3d11_device_; - Microsoft::WRL::ComPtr d3d11_device_context_; - Microsoft::WRL::ComPtr d2d1_factory_; - Microsoft::WRL::ComPtr d2d1_device_; - Microsoft::WRL::ComPtr d2d1_device_context_; - Microsoft::WRL::ComPtr dxgi_factory_; - - Microsoft::WRL::ComPtr dwrite_factory_; - Microsoft::WRL::ComPtr dwrite_system_font_collection_; - }; - - inline int DipToPixelInternal(const float dip, const float dpi) - { - return static_cast(dip * dpi / 96.0f); - } - - inline int DipToPixelX(const float dip_x) - { - return DipToPixelInternal(dip_x, Application::GetInstance()->GetGraphManager()->GetDpi().x); - } - - inline int DipToPixelY(const float dip_y) - { - return DipToPixelInternal(dip_y, Application::GetInstance()->GetGraphManager()->GetDpi().y); - } - - inline float DipToPixelInternal(const int pixel, const float dpi) - { - return static_cast(pixel) * 96.0f / dpi; - } - - inline float PixelToDipX(const int pixel_x) - { - return DipToPixelInternal(pixel_x, Application::GetInstance()->GetGraphManager()->GetDpi().x); - } - - inline float PixelToDipY(const int pixel_y) - { - return DipToPixelInternal(pixel_y, Application::GetInstance()->GetGraphManager()->GetDpi().y); - } - - Microsoft::WRL::ComPtr WindowRenderTarget::GetD2DDeviceContext() const - { - return graph_manager_->GetD2D1DeviceContext(); - } - - Microsoft::WRL::ComPtr CreateSolidBrush(const D2D1_COLOR_F& color); - Microsoft::WRL::ComPtr CreateDefaultTextFormat(); - - inline void WithTransform(ID2D1DeviceContext* device_context, const D2D1_MATRIX_3X2_F matrix, Function&& 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); - } - } -} diff --git a/CruUI/main.cpp b/CruUI/main.cpp deleted file mode 100644 index 67b35406..00000000 --- a/CruUI/main.cpp +++ /dev/null @@ -1,150 +0,0 @@ -#include "application.h" -#include "ui/window.h" -#include "ui/controls/linear_layout.h" -#include "ui/controls/text_block.h" -#include "ui/controls/toggle_button.h" -#include "ui/controls/button.h" -#include "ui/controls/margin_container.h" -#include "ui/events/ui_event.h" -#include "ui/controls/text_box.h" - - -using cru::String; -using cru::Application; -using cru::ui::Window; -using cru::ui::Alignment; -using cru::ui::LayoutSideParams; -using cru::ui::Thickness; -using cru::ui::CreateWithLayout; -using cru::ui::controls::LinearLayout; -using cru::ui::controls::TextBlock; -using cru::ui::controls::ToggleButton; -using cru::ui::controls::Button; -using cru::ui::controls::MarginContainer; -using cru::ui::controls::TextBox; - -int APIENTRY wWinMain( - HINSTANCE hInstance, - HINSTANCE hPrevInstance, - LPWSTR lpCmdLine, - int nCmdShow) { - - Application application(hInstance); - Window window; - /* - 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 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 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.Repaint(); - - auto task = cru::SetInterval(0.5, [&window]() { - window.Repaint(); - }); - - cru::SetTimeout(4, [task]() { - task->Cancel(); - task->Cancel(); // test for idempotency. - }); - }); - */ - - /* - //test 2 - - const auto layout = CreateWithLayout(LayoutSideParams::Exactly(500), LayoutSideParams::Content()); - - layout->mouse_click_event.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(LayoutSideParams::Content(Alignment::End), LayoutSideParams::Content(), LinearLayout::Orientation::Horizontal); - - inner_layout->AddChild(TextBlock::Create(L"Toggle debug border")); - - const auto toggle_button = ToggleButton::Create(); - toggle_button->toggle_event.AddHandler([&window](cru::ui::events::ToggleEventArgs& args) - { - window.SetDebugDrawControlBorder(args.GetNewState()); - }); - - inner_layout->AddChild(toggle_button); - layout->AddChild(inner_layout); - } - - { - const auto button = Button::Create(); - button->AddChild(MarginContainer::Create(Thickness(20, 5), { TextBlock::Create(L"button") })); - layout->AddChild(button); - } - - { - const auto text_block = CreateWithLayout(LayoutSideParams::Exactly(200), LayoutSideParams::Exactly(80), L"Hello World!!!"); - - text_block->mouse_click_event.AddHandler([layout](cru::ui::events::MouseButtonEventArgs& args) - { - layout->AddChild(TextBlock::Create(L"Hello world is clicked!")); - }); - - layout->AddChild(text_block); - } - - { - const auto text_block = CreateWithLayout(LayoutSideParams::Stretch(), LayoutSideParams::Stretch(), L"This is a very very very very very long sentence!!!"); - text_block->SetSelectable(true); - layout->AddChild(text_block); - } - - layout->AddChild(CreateWithLayout(LayoutSideParams::Content(Alignment::Start), LayoutSideParams::Content(), L"This is a little short sentence!!!")); - layout->AddChild(CreateWithLayout(LayoutSideParams::Content(Alignment::End), LayoutSideParams::Stretch(), L"By crupest!!!")); - - - window.AddChild(layout); - */ - - window.AddChild(CreateWithLayout(LayoutSideParams::Stretch(), LayoutSideParams::Stretch())); - - window.Show(); - - return application.Run(); -} diff --git a/CruUI/system_headers.h b/CruUI/system_headers.h deleted file mode 100644 index 99c091e1..00000000 --- a/CruUI/system_headers.h +++ /dev/null @@ -1,21 +0,0 @@ -#pragma once - - -//include system headers - -#define NOMINMAX -#define WIN32_LEAN_AND_MEAN -#include -#include - -#pragma comment(lib, "D3D11.lib") -#include - -#pragma comment(lib, "D2d1.lib") -#include - -#pragma comment(lib, "DWrite.lib") -#include - -#include -#include diff --git a/CruUI/timer.cpp b/CruUI/timer.cpp deleted file mode 100644 index ce800eee..00000000 --- a/CruUI/timer.cpp +++ /dev/null @@ -1,79 +0,0 @@ -#include "timer.h" - -namespace cru -{ - UINT_PTR TimerManager::CreateTimer(const UINT milliseconds, const bool loop, ActionPtr action) - { - const auto id = current_count_++; - ::SetTimer(Application::GetInstance()->GetGodWindow()->GetHandle(), id, milliseconds, nullptr); - if (loop) - map_[id] = std::move(action); - else - map_[id] = CreateActionPtr([this, action, id]() mutable { - (*action)(); - this->KillTimer(id); - }); - 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); - // When timer is killed in tick action, we need to retain the action itself until the action finishes, so InvokeLater! - InvokeLater([=] - { - map_.erase(find_result); - }); - } - } - - ActionPtr TimerManager::GetAction(const UINT_PTR id) - { - const auto find_result = map_.find(id); - if (find_result == map_.cend()) - return nullptr; - return find_result->second; - } - - class TimerTaskImpl : public virtual ICancelable - { - public: - explicit TimerTaskImpl(UINT_PTR id); - TimerTaskImpl(const TimerTaskImpl& other) = delete; - TimerTaskImpl(TimerTaskImpl&& other) = delete; - TimerTaskImpl& operator=(const TimerTaskImpl& other) = delete; - TimerTaskImpl& operator=(TimerTaskImpl&& other) = delete; - ~TimerTaskImpl() override = default; - - void Cancel() override; - - private: - UINT_PTR id_; - }; - - TimerTaskImpl::TimerTaskImpl(const UINT_PTR id) - : id_(id) - { - - } - - void TimerTaskImpl::Cancel() - { - TimerManager::GetInstance()->KillTimer(id_); - } - - TimerTask SetTimeout(std::chrono::milliseconds milliseconds, ActionPtr action) - { - auto id = TimerManager::GetInstance()->CreateTimer(static_cast(milliseconds.count()), false, std::move(action)); - return std::make_shared(id); - } - - TimerTask SetInterval(std::chrono::milliseconds milliseconds, ActionPtr action) - { - auto id = TimerManager::GetInstance()->CreateTimer(static_cast(milliseconds.count()), true, std::move(action)); - return std::make_shared(id); - } -} diff --git a/CruUI/timer.h b/CruUI/timer.h deleted file mode 100644 index 9884e46a..00000000 --- a/CruUI/timer.h +++ /dev/null @@ -1,43 +0,0 @@ -#pragma once - - -#include "system_headers.h" -#include -#include -#include - -#include "base.h" -#include "application.h" - -namespace cru -{ - class TimerManager : public Object - { - public: - static TimerManager* GetInstance() - { - return Application::GetInstance()->GetTimerManager(); - } - - public: - TimerManager() = default; - 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, ActionPtr action); - void KillTimer(UINT_PTR id); - ActionPtr GetAction(UINT_PTR id); - - private: - std::map map_{}; - UINT_PTR current_count_ = 0; - }; - - using TimerTask = CancelablePtr; - - TimerTask SetTimeout(std::chrono::milliseconds milliseconds, ActionPtr action); - TimerTask SetInterval(std::chrono::milliseconds milliseconds, ActionPtr action); -} diff --git a/CruUI/ui/animations/animation.cpp b/CruUI/ui/animations/animation.cpp deleted file mode 100644 index 9d05860a..00000000 --- a/CruUI/ui/animations/animation.cpp +++ /dev/null @@ -1,190 +0,0 @@ -#include "animation.h" - -#include -#include - -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( - String tag, - AnimationTimeUnit duration, - Vector step_handlers, - Vector start_handlers, - Vector finish_handlers, - Vector cancel_handlers, - AnimationDelegatePtr 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 tag_; - } - - private: - const String tag_; - const AnimationTimeUnit duration_; - Vector step_handlers_; - Vector start_handlers_; - Vector finish_handlers_; - Vector cancel_handlers_; - AnimationDelegatePtr delegate_; - - AnimationTimeUnit current_time_ = AnimationTimeUnit::zero(); - }; - - AnimationManager::AnimationManager() - : timer_action_(CreateActionPtr([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(); - })) - { - - } - - AnimationManager::~AnimationManager() - { - KillTimer(); - } - - AnimationDelegatePtr AnimationManager::CreateAnimation(String tag, AnimationTimeUnit duration, - Vector step_handlers, Vector start_handlers, - Vector finish_handlers, Vector cancel_handlers) - { - if (animations_.empty()) - SetTimer(); - - auto delegate = std::make_shared(tag); - - animations_[tag] = std::make_unique(tag, duration, std::move(step_handlers), std::move(start_handlers), std::move(finish_handlers), std::move(cancel_handlers), 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_ == nullptr) - timer_ = SetInterval(std::chrono::duration_cast(frame_step_time), timer_action_); - } - - void AnimationManager::KillTimer() - { - if (timer_ != nullptr) - { - timer_->Cancel(); - timer_ = nullptr; - } - } - - Animation::Animation( - String tag, - AnimationTimeUnit duration, - Vector step_handlers, - Vector start_handlers, - Vector finish_handlers, - Vector cancel_handlers, - AnimationDelegatePtr delegate - ) : tag_(std::move(tag)), duration_(duration), - step_handlers_(std::move(step_handlers)), - start_handlers_(std::move(start_handlers)), - finish_handlers_(std::move(finish_handlers)), - cancel_handlers_(std::move(cancel_handlers)), - delegate_(std::move(delegate)) - { - - } - - Animation::~Animation() - { - if (current_time_ < duration_) - for (auto& handler : cancel_handlers_) - (*handler)(); - } - - bool Animation::Step(const AnimationTimeUnit time) - { - current_time_ += time; - if (current_time_ > duration_) - { - for (auto& handler : step_handlers_) - (*handler)(delegate_, 1); - for (auto& handler : finish_handlers_) - (*handler)(); - return true; - } - else - { - for (auto& handler : step_handlers_) - (*handler)(delegate_, current_time_ / duration_); - return false; - } - } - - } - - AnimationDelegatePtr AnimationBuilder::Start() const - { - return details::AnimationManager::GetInstance()->CreateAnimation(tag, duration, step_handlers_, start_handlers_, finish_handlers_, cancel_handlers_); - } -} diff --git a/CruUI/ui/animations/animation.h b/CruUI/ui/animations/animation.h deleted file mode 100644 index 69b08b0c..00000000 --- a/CruUI/ui/animations/animation.h +++ /dev/null @@ -1,107 +0,0 @@ -#pragma once - -#include - -#include "base.h" -#include "application.h" -#include "timer.h" - -namespace cru::ui::animations -{ - using AnimationTimeUnit = FloatSecond; - - - using IAnimationDelegate = ICancelable; - using AnimationDelegatePtr = CancelablePtr; - - using AnimationStepHandlerPtr = FunctionPtr; - using AnimationStartHandlerPtr = FunctionPtr; - - - namespace details - { - class Animation; - using AnimationPtr = std::unique_ptr; - - class AnimationManager : public Object - { - public: - static AnimationManager* GetInstance() - { - return Application::GetInstance()->GetAnimationManager(); - } - - public: - AnimationManager(); - AnimationManager(const AnimationManager& other) = delete; - AnimationManager(AnimationManager&& other) = delete; - AnimationManager& operator=(const AnimationManager& other) = delete; - AnimationManager& operator=(AnimationManager&& other) = delete; - ~AnimationManager() override; - - AnimationDelegatePtr CreateAnimation( - String tag, - AnimationTimeUnit duration, - Vector step_handlers, - Vector start_handlers, - Vector finish_handlers, - Vector cancel_handlers - ); - void RemoveAnimation(const String& tag); - - private: - void SetTimer(); - void KillTimer(); - - private: - std::unordered_map animations_; - std::shared_ptr timer_; - ActionPtr timer_action_; - }; - } - - class AnimationBuilder : public Object - { - public: - AnimationBuilder(String tag, const AnimationTimeUnit duration) - : tag(std::move(tag)), duration(duration) - { - - } - - String tag; - AnimationTimeUnit duration; - - AnimationBuilder& AddStepHandler(AnimationStepHandlerPtr handler) - { - step_handlers_.push_back(std::move(handler)); - return *this; - } - - AnimationBuilder& AddStartHandler(AnimationStartHandlerPtr handler) - { - start_handlers_.push_back(std::move(handler)); - return *this; - } - - AnimationBuilder& AddFinishHandler(ActionPtr handler) - { - finish_handlers_.push_back(std::move(handler)); - return *this; - } - - AnimationBuilder& AddCancelHandler(ActionPtr handler) - { - cancel_handlers_.push_back(std::move(handler)); - return *this; - } - - AnimationDelegatePtr Start() const; - - private: - Vector step_handlers_; - Vector start_handlers_; - Vector finish_handlers_; - Vector cancel_handlers_; - }; -} diff --git a/CruUI/ui/control.cpp b/CruUI/ui/control.cpp deleted file mode 100644 index 8aec8640..00000000 --- a/CruUI/ui/control.cpp +++ /dev/null @@ -1,712 +0,0 @@ -#include "control.h" - -#include -#include -#include - -#include "window.h" -#include "timer.h" -#include "debug_base.h" - -namespace cru { - namespace ui { - using namespace events; - - Control::Control(const bool container) : - is_container_(container) - { - - } - - Control::Control(WindowConstructorTag, Window* window) : Control(true) - { - window_ = window; - } - - Control::~Control() - { - ForeachChild([](auto control) - { - delete control; - }); - } - - void Control::ForeachChild(Function&& predicate) const - { - if (is_container_) - for (const auto child : children_) - predicate(child); - } - - void Control::ForeachChild(Function&& predicate) const - { - if (is_container_) - for (const auto child : children_) - { - if (predicate(child) == FlowControl::Break) - break; - } - } - - void AddChildCheck(Control* control) - { - if (control->GetParent() != nullptr) - throw std::invalid_argument("The control already has a parent."); - - if (dynamic_cast(control)) - throw std::invalid_argument("Can't add a window as child."); - } - - void Control::AddChild(Control* control) - { - ThrowIfNotContainer(); - AddChildCheck(control); - - this->children_.push_back(control); - - control->parent_ = this; - - this->OnAddChild(control); - } - - void Control::AddChild(Control* control, int position) - { - ThrowIfNotContainer(); - AddChildCheck(control); - - if (position < 0 || static_castchildren_.size())>(position) > this->children_.size()) - throw std::invalid_argument("The position is out of range."); - - this->children_.insert(this->children_.cbegin() + position, control); - - control->parent_ = this; - - this->OnAddChild(this); - } - - void Control::RemoveChild(Control* child) - { - ThrowIfNotContainer(); - 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."); - - this->children_.erase(i); - - child->parent_ = nullptr; - - this->OnRemoveChild(this); - } - - void Control::RemoveChild(const int position) - { - ThrowIfNotContainer(); - if (position < 0 || static_castchildren_.size())>(position) >= this->children_.size()) - throw std::invalid_argument("The position is out of range."); - - const auto p = children_.cbegin() + position; - const auto child = *p; - children_.erase(p); - - child->parent_ = nullptr; - - this->OnRemoveChild(child); - } - - Control* Control::GetAncestor() - { - // if attached to window, the window is the ancestor. - if (window_) - return window_; - - // otherwise find the ancestor - auto ancestor = this; - while (const auto parent = ancestor->GetParent()) - ancestor = parent; - return ancestor; - } - - void TraverseDescendantsInternal(Control* control, Function& predicate) - { - predicate(control); - control->ForeachChild([&predicate](Control* c) { - TraverseDescendantsInternal(c, predicate); - }); - } - - void Control::TraverseDescendants(Function&& predicate) - { - if (is_container_) - TraverseDescendantsInternal(this, predicate); - else - predicate(this); - } - - Point Control::GetPositionRelative() - { - return position_; - } - - void Control::SetPositionRelative(const Point & position) - { - if (position != position_) - { - if (old_position_ == position) // if cache has been refreshed and no pending notify - old_position_ = position_; - position_ = position; - LayoutManager::GetInstance()->InvalidateControlPositionCache(this); - if (auto window = GetWindow()) - { - window->Repaint(); - } - } - } - - Size Control::GetSize() - { - return size_; - } - - void Control::SetSize(const Size & size) - { - const auto old_size = size_; - size_ = size; - SizeChangedEventArgs args(this, this, old_size, size); - RaiseSizeChangedEvent(args); - if (auto window = GetWindow()) - window->Repaint(); - } - - Point Control::GetPositionAbsolute() const - { - return position_cache_.lefttop_position_absolute; - } - - Point Control::LocalToAbsolute(const Point& point) const - { - return Point(point.x + position_cache_.lefttop_position_absolute.x, - point.y + position_cache_.lefttop_position_absolute.y); - } - - Point Control::AbsoluteToLocal(const Point & point) const - { - return Point(point.x - position_cache_.lefttop_position_absolute.x, - point.y - position_cache_.lefttop_position_absolute.y); - } - - bool Control::IsPointInside(const Point & point) - { - const auto size = GetSize(); - return point.x >= 0.0f && point.x < size.width && point.y >= 0.0f && point.y < size.height; - } - - void Control::Draw(ID2D1DeviceContext* device_context) - { - D2D1::Matrix3x2F old_transform; - device_context->GetTransform(&old_transform); - - const auto position = GetPositionRelative(); - device_context->SetTransform(old_transform * D2D1::Matrix3x2F::Translation(position.x, position.y)); - - OnDraw(device_context); - DrawEventArgs args(this, this, device_context); - draw_event.Raise(args); - - for (auto child : GetChildren()) - child->Draw(device_context); - - device_context->SetTransform(old_transform); - } - - void Control::Repaint() - { - if (window_ != nullptr) - window_->Repaint(); - } - - 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::Relayout() - { - OnMeasure(GetSize()); - OnLayout(Rect(GetPositionRelative(), GetSize())); - } - - void Control::Measure(const Size& available_size) - { - SetDesiredSize(OnMeasure(available_size)); - } - - void Control::Layout(const Rect& rect) - { - SetPositionRelative(rect.GetLeftTop()); - SetSize(rect.GetSize()); - OnLayout(rect); - } - - Size Control::GetDesiredSize() const - { - return desired_size_; - } - - void Control::SetDesiredSize(const Size& desired_size) - { - desired_size_ = desired_size; - } - - void Control::OnAddChild(Control* child) - { - if (auto window = GetWindow()) - { - child->TraverseDescendants([window](Control* control) { - control->OnAttachToWindow(window); - }); - window->RefreshControlList(); - } - Relayout(); - } - - void Control::OnRemoveChild(Control* child) - { - if (auto window = GetWindow()) - { - child->TraverseDescendants([window](Control* control) { - control->OnDetachToWindow(window); - }); - window->RefreshControlList(); - } - Relayout(); - } - - void Control::OnAttachToWindow(Window* window) - { - window_ = window; - } - - void Control::OnDetachToWindow(Window * window) - { - window_ = nullptr; - } - - void Control::OnDraw(ID2D1DeviceContext * device_context) - { -#ifdef CRU_DEBUG_DRAW_CONTROL_BORDER - if (GetWindow()->GetDebugDrawControlBorder()) - { - auto brush = Application::GetInstance()->GetDebugBorderBrush(); - const auto size = GetSize(); - device_context->DrawRectangle(D2D1::RectF(0, 0, size.width, size.height), brush.Get()); - } -#endif - } - - void Control::OnPositionChanged(PositionChangedEventArgs & args) - { - - } - - void Control::OnSizeChanged(SizeChangedEventArgs & args) - { - } - - void Control::OnPositionChangedCore(PositionChangedEventArgs & args) - { - - } - - void Control::OnSizeChangedCore(SizeChangedEventArgs & args) - { - - } - - void Control::RaisePositionChangedEvent(PositionChangedEventArgs& args) - { - OnPositionChangedCore(args); - OnPositionChanged(args); - position_changed_event.Raise(args); - } - - void Control::RaiseSizeChangedEvent(SizeChangedEventArgs& args) - { - OnSizeChangedCore(args); - OnSizeChanged(args); - size_changed_event.Raise(args); - } - - void Control::OnMouseEnter(MouseEventArgs & args) - { - } - - void Control::OnMouseLeave(MouseEventArgs & args) - { - } - - void Control::OnMouseMove(MouseEventArgs & args) - { - } - - void Control::OnMouseDown(MouseButtonEventArgs & args) - { - } - - void Control::OnMouseUp(MouseButtonEventArgs & args) - { - } - - void Control::OnMouseClick(MouseButtonEventArgs& args) - { - - } - - void Control::OnMouseEnterCore(MouseEventArgs & args) - { - is_mouse_inside_ = true; - } - - void Control::OnMouseLeaveCore(MouseEventArgs & args) - { - is_mouse_inside_ = false; - 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); - } - } - } - - void Control::OnMouseMoveCore(MouseEventArgs & args) - { - - } - - void Control::OnMouseDownCore(MouseButtonEventArgs & args) - { - if (is_focus_on_pressed_ && args.GetSender() == args.GetOriginalSender()) - RequestFocus(); - is_mouse_click_valid_map_[args.GetMouseButton()] = true; - OnMouseClickBegin(args.GetMouseButton()); - } - - void Control::OnMouseUpCore(MouseButtonEventArgs & args) - { - if (is_mouse_click_valid_map_[args.GetMouseButton()]) - { - is_mouse_click_valid_map_[args.GetMouseButton()] = false; - RaiseMouseClickEvent(args); - OnMouseClickEnd(args.GetMouseButton()); - } - } - - void Control::OnMouseClickCore(MouseButtonEventArgs& args) - { - - } - - void Control::RaiseMouseEnterEvent(MouseEventArgs& args) - { - OnMouseEnterCore(args); - OnMouseEnter(args); - mouse_enter_event.Raise(args); - } - - void Control::RaiseMouseLeaveEvent(MouseEventArgs& args) - { - OnMouseLeaveCore(args); - OnMouseLeave(args); - mouse_leave_event.Raise(args); - } - - void Control::RaiseMouseMoveEvent(MouseEventArgs& args) - { - OnMouseMoveCore(args); - OnMouseMove(args); - mouse_move_event.Raise(args); - } - - void Control::RaiseMouseDownEvent(MouseButtonEventArgs& args) - { - OnMouseDownCore(args); - OnMouseDown(args); - mouse_down_event.Raise(args); - } - - void Control::RaiseMouseUpEvent(MouseButtonEventArgs& args) - { - OnMouseUpCore(args); - OnMouseUp(args); - mouse_up_event.Raise(args); - } - - void Control::RaiseMouseClickEvent(MouseButtonEventArgs& args) - { - OnMouseClickCore(args); - OnMouseClick(args); - mouse_click_event.Raise(args); - } - - void Control::OnMouseClickBegin(MouseButton button) - { - - } - - void Control::OnMouseClickEnd(MouseButton button) - { - - } - - void Control::OnKeyDown(KeyEventArgs& args) - { - } - - void Control::OnKeyUp(KeyEventArgs& args) - { - } - - void Control::OnChar(CharEventArgs& args) - { - } - - void Control::OnKeyDownCore(KeyEventArgs& args) - { - } - - void Control::OnKeyUpCore(KeyEventArgs& args) - { - } - - void Control::OnCharCore(CharEventArgs& args) - { - } - - void Control::RaiseKeyDownEvent(KeyEventArgs& args) - { - OnKeyDownCore(args); - OnKeyDown(args); - key_down_event.Raise(args); - } - - void Control::RaiseKeyUpEvent(KeyEventArgs& args) - { - OnKeyUpCore(args); - OnKeyUp(args); - key_up_event.Raise(args); - } - - void Control::RaiseCharEvent(CharEventArgs& args) - { - OnCharCore(args); - OnChar(args); - char_event.Raise(args); - } - - void Control::OnGetFocus(FocusChangeEventArgs& args) - { - - } - - void Control::OnLoseFocus(FocusChangeEventArgs& args) - { - - } - - void Control::OnGetFocusCore(FocusChangeEventArgs& args) - { - - } - - void Control::OnLoseFocusCore(FocusChangeEventArgs& args) - { - - } - - void Control::RaiseGetFocusEvent(FocusChangeEventArgs& args) - { - OnGetFocusCore(args); - OnGetFocus(args); - get_focus_event.Raise(args); - } - - void Control::RaiseLoseFocusEvent(FocusChangeEventArgs& args) - { - OnLoseFocusCore(args); - OnLoseFocus(args); - lose_focus_event.Raise(args); - } - - Size Control::OnMeasure(const Size& available_size) - { - const auto layout_params = GetLayoutParams(); - - if (!layout_params->Validate()) - throw std::runtime_error("LayoutParams is not valid. Please check it."); - - auto&& get_available_length_for_child = [](const LayoutSideParams& layout_length, const float available_length) -> float - { - switch (layout_length.mode) - { - case MeasureMode::Exactly: - { - return std::min(layout_length.length, available_length); - } - case MeasureMode::Stretch: - case MeasureMode::Content: - { - return available_length; - } - default: - UnreachableCode(); - } - }; - - const Size size_for_children(get_available_length_for_child(layout_params->width, available_size.width), - get_available_length_for_child(layout_params->height, available_size.height)); - - auto max_child_size = Size::Zero(); - ForeachChild([&](Control* control) - { - control->Measure(size_for_children); - 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; - }); - - auto&& calculate_final_length = [](const LayoutSideParams& layout_length, const float length_for_children, const float max_child_length) -> float - { - switch (layout_length.mode) - { - case MeasureMode::Exactly: - case MeasureMode::Stretch: - return length_for_children; - case MeasureMode::Content: - return max_child_length; - default: - UnreachableCode(); - } - }; - - return Size( - calculate_final_length(layout_params->width, size_for_children.width, max_child_size.width), - calculate_final_length(layout_params->height, size_for_children.height, max_child_size.height) - ); - } - - void Control::OnLayout(const Rect& rect) - { - ForeachChild([rect](Control* control) - { - const auto layout_params = control->GetLayoutParams(); - const auto size = control->GetDesiredSize(); - - auto&& calculate_anchor = [](const Alignment 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(); - } - }; - - control->Layout(Rect(Point( - calculate_anchor(layout_params->width.alignment, rect.width, size.width), - calculate_anchor(layout_params->height.alignment, rect.height, size.height) - ), size)); - }); - } - - void Control::CheckAndNotifyPositionChanged() - { - if (this->old_position_ != this->position_) - { - PositionChangedEventArgs args(this, this, this->old_position_, this->position_); - this->RaisePositionChangedEvent(args); - this->old_position_ = this->position_; - } - } - - std::list GetAncestorList(Control* control) - { - std::list l; - while (control != nullptr) - { - l.push_front(control); - control = control->GetParent(); - } - return l; - } - - 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; - } - } - - Control * IsAncestorOrDescendant(Control * left, Control * right) - { - //Search up along the trunk from "left". Return if find "right". - auto control = left; - while (control != nullptr) - { - if (control == right) - return control; - control = control->GetParent(); - } - //Search up along the trunk from "right". Return if find "left". - control = right; - while (control != nullptr) - { - if (control == left) - return control; - control = control->GetParent(); - } - return nullptr; - } - } -} diff --git a/CruUI/ui/control.h b/CruUI/ui/control.h deleted file mode 100644 index d6cbae40..00000000 --- a/CruUI/ui/control.h +++ /dev/null @@ -1,380 +0,0 @@ -#pragma once - -#include "system_headers.h" -#include -#include -#include -#include -#include - -#include "base.h" -#include "ui_base.h" -#include "layout_base.h" -#include "events/ui_event.h" - -namespace cru -{ - namespace ui - { - class Control; - class Window; - - - //the position cache - struct ControlPositionCache - { - //The lefttop relative to the ancestor. - Point lefttop_position_absolute; - }; - - - class Control : public Object - { - friend class Window; - friend class LayoutManager; - - protected: - struct WindowConstructorTag {}; //Used for constructor for class Window. - - explicit Control(bool container = false); - - // Used only for creating Window. It will set window_ as window. - Control(WindowConstructorTag, Window* window); - - public: - Control(const Control& other) = delete; - Control(Control&& other) = delete; - Control& operator=(const Control& other) = delete; - Control& operator=(Control&& other) = delete; - ~Control() override; - - public: - - //*************** region: tree *************** - - bool IsContainer() const - { - return is_container_; - } - - //Get parent of control, return nullptr if it has no parent. - Control* GetParent() const - { - return parent_; - } - - //Traverse the children - void ForeachChild(Function&& predicate) const; - void ForeachChild(Function&& predicate) const; - - //Return a vector of all children. This function will create a - //temporary copy of vector of children. If you just want to - //traverse all children, just call ForeachChild. - Vector 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); - - //Get the ancestor of the control. - Control* GetAncestor(); - - //Get the window if attached, otherwise, return nullptr. - Window* GetWindow() const - { - return window_; - } - - //Traverse the tree rooted the control including itself. - void TraverseDescendants(Function&& predicate); - - //*************** region: position and size *************** - // Position and size part must be isolated from layout part. - // All the operations in this part must be done independently. - // And layout part must use api of this part. - - //Get the lefttop relative to its parent. - virtual Point GetPositionRelative(); - - //Set the lefttop relative to its parent. - virtual void SetPositionRelative(const Point& position); - - //Get the actual size. - virtual Size GetSize(); - - //Set the actual size directly without re-layout. - virtual void SetSize(const Size& size); - - //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 LocalToAbsolute(const Point& point) const; - - //Absolute point to local point. - Point AbsoluteToLocal(const Point& point) const; - - virtual bool IsPointInside(const Point& point); - - - //*************** region: graphic *************** - - //Draw this control and its child controls. - void Draw(ID2D1DeviceContext* device_context); - - virtual void Repaint(); - - //*************** 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 Relayout(); - - void Measure(const Size& available_size); - - void Layout(const Rect& rect); - - Size GetDesiredSize() const; - - void SetDesiredSize(const Size& desired_size); - - BasicLayoutParams* GetLayoutParams() - { - return &layout_params_; - } - - //*************** region: additional properties *************** - template - std::optional GetAdditionalProperty(const String& key) - { - try - { - const auto find_result = additional_properties_.find(key); - if (find_result != additional_properties_.cend()) - return std::any_cast(find_result->second); - else - return std::nullopt; - } - catch (const std::bad_any_cast&) - { - throw std::runtime_error(fmt::format("Key \"{}\" is not of the type {}.", ToUtf8String(key), typeid(T).name())); - } - } - - template - void SetAdditionalProperty(const String& key, const T& value) - { - additional_properties_[key] = std::make_any(value); - } - - template - void SetAdditionalProperty(const String& key, T&& value) - { - additional_properties_[key] = std::make_any(std::move(value)); - } - - //*************** region: events *************** - //Raised when mouse enter the control. - events::MouseEvent mouse_enter_event; - //Raised when mouse is leave the control. - events::MouseEvent mouse_leave_event; - //Raised when mouse is move in the control. - events::MouseEvent mouse_move_event; - //Raised when a mouse button is pressed in the control. - events::MouseButtonEvent mouse_down_event; - //Raised when a mouse button is released in the control. - events::MouseButtonEvent 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::MouseButtonEvent mouse_click_event; - - events::KeyEvent key_down_event; - events::KeyEvent key_up_event; - events::CharEvent char_event; - - events::FocusChangeEvent get_focus_event; - events::FocusChangeEvent lose_focus_event; - - events::DrawEvent draw_event; - - events::PositionChangedEvent position_changed_event; - events::SizeChangedEvent size_changed_event; - - 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); - - //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); - - virtual void OnDraw(ID2D1DeviceContext* device_context); - - - // For a event, the window event system will first dispatch event to core functions. - // Therefore for particular controls, you should do essential actions in core functions, - // and override version should invoke base version. The base core function - // in "Control" class will call corresponding non-core function and call "Raise" on - // event objects. So user custom actions should be done by overriding non-core function - // and calling the base version is optional. - - //*************** region: position and size event *************** - virtual void OnPositionChanged(events::PositionChangedEventArgs& args); - virtual void OnSizeChanged(events::SizeChangedEventArgs& args); - - virtual void OnPositionChangedCore(events::PositionChangedEventArgs& args); - virtual void OnSizeChangedCore(events::SizeChangedEventArgs& args); - - void RaisePositionChangedEvent(events::PositionChangedEventArgs& args); - void RaiseSizeChangedEvent(events::SizeChangedEventArgs& args); - - //*************** region: mouse event *************** - virtual void OnMouseEnter(events::MouseEventArgs& args); - virtual void OnMouseLeave(events::MouseEventArgs& args); - virtual void OnMouseMove(events::MouseEventArgs& args); - virtual void OnMouseDown(events::MouseButtonEventArgs& args); - virtual void OnMouseUp(events::MouseButtonEventArgs& args); - virtual void OnMouseClick(events::MouseButtonEventArgs& args); - - virtual void OnMouseEnterCore(events::MouseEventArgs& args); - virtual void OnMouseLeaveCore(events::MouseEventArgs& args); - virtual void OnMouseMoveCore(events::MouseEventArgs& args); - virtual void OnMouseDownCore(events::MouseButtonEventArgs& args); - virtual void OnMouseUpCore(events::MouseButtonEventArgs& args); - virtual void OnMouseClickCore(events::MouseButtonEventArgs& args); - - void RaiseMouseEnterEvent(events::MouseEventArgs& args); - void RaiseMouseLeaveEvent(events::MouseEventArgs& args); - void RaiseMouseMoveEvent(events::MouseEventArgs& args); - void RaiseMouseDownEvent(events::MouseButtonEventArgs& args); - void RaiseMouseUpEvent(events::MouseButtonEventArgs& args); - void RaiseMouseClickEvent(events::MouseButtonEventArgs& args); - - virtual void OnMouseClickBegin(MouseButton button); - virtual void OnMouseClickEnd(MouseButton button); - - //*************** region: keyboard event *************** - virtual void OnKeyDown(events::KeyEventArgs& args); - virtual void OnKeyUp(events::KeyEventArgs& args); - virtual void OnChar(events::CharEventArgs& args); - - virtual void OnKeyDownCore(events::KeyEventArgs& args); - virtual void OnKeyUpCore(events::KeyEventArgs& args); - virtual void OnCharCore(events::CharEventArgs& args); - - void RaiseKeyDownEvent(events::KeyEventArgs& args); - void RaiseKeyUpEvent(events::KeyEventArgs& args); - void RaiseCharEvent(events::CharEventArgs& args); - - //*************** region: focus event *************** - virtual void OnGetFocus(events::FocusChangeEventArgs& args); - virtual void OnLoseFocus(events::FocusChangeEventArgs& args); - - virtual void OnGetFocusCore(events::FocusChangeEventArgs& args); - virtual void OnLoseFocusCore(events::FocusChangeEventArgs& args); - - void RaiseGetFocusEvent(events::FocusChangeEventArgs& args); - void RaiseLoseFocusEvent(events::FocusChangeEventArgs& args); - - //*************** region: layout *************** - virtual Size OnMeasure(const Size& available_size); - virtual void OnLayout(const Rect& rect); - - private: - // Only for layout manager to use. - // Check if the old position is updated to current position. - // If not, then a notify of position change and update will - // be done. - void CheckAndNotifyPositionChanged(); - - void ThrowIfNotContainer() const - { - if (!is_container_) - throw std::runtime_error("You can't perform such operation on a non-container control."); - } - - private: - bool is_container_; - - protected: - Window * window_ = nullptr; // protected for Window class to write it as itself in constructor. - - private: - Control * parent_ = nullptr; - Vector children_{}; - - // When position is changed and notification hasn't been - // sent, it will be the old position. When position is changed - // more than once, it will be the oldest position since last - // notification. If notification has been sent, it will be updated - // to position_. - Point old_position_ = Point::Zero(); - Point position_ = Point::Zero(); - Size size_ = Size::Zero(); - - ControlPositionCache position_cache_{}; - - bool is_mouse_inside_ = false; - - std::unordered_map 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(); - - std::unordered_map additional_properties_{}; - - bool is_focus_on_pressed_ = true; - }; - - // Find the lowest common ancestor. - // Return nullptr if "left" and "right" are not in the same tree. - Control* FindLowestCommonAncestor(Control* left, Control* right); - - // Return the ancestor if one control is the ancestor of the other one, otherwise nullptr. - Control* IsAncestorOrDescendant(Control* left, Control* right); - - template - TControl* CreateWithLayout(const LayoutSideParams& width, const LayoutSideParams& height, Args&&... args) - { - static_assert(std::is_base_of_v, "TControl is not a control class."); - TControl* control = TControl::Create(std::forward(args)...); - control->GetLayoutParams()->width = width; - control->GetLayoutParams()->height = height; - return control; - } - } -} diff --git a/CruUI/ui/controls/button.cpp b/CruUI/ui/controls/button.cpp deleted file mode 100644 index b7614f93..00000000 --- a/CruUI/ui/controls/button.cpp +++ /dev/null @@ -1,33 +0,0 @@ -#include "button.h" - -#include "graph/graph.h" - -namespace cru::ui::controls -{ - using graph::CreateSolidBrush; - - Button::Button() : Control(true) - { - normal_border_brush_ = CreateSolidBrush(D2D1::ColorF(D2D1::ColorF::RoyalBlue)); - pressed_border_brush_ = CreateSolidBrush(D2D1::ColorF(D2D1::ColorF::MediumBlue)); - current_border_brush_ = normal_border_brush_.Get(); - } - - void Button::OnDraw(ID2D1DeviceContext* device_context) - { - Control::OnDraw(device_context); - device_context->DrawRoundedRectangle(D2D1::RoundedRect(D2D1::RectF(0, 0, GetSize().width, GetSize().height), 6, 6), current_border_brush_, 2); - } - - void Button::OnMouseClickBegin(MouseButton button) - { - current_border_brush_ = pressed_border_brush_.Get(); - Repaint(); - } - - void Button::OnMouseClickEnd(MouseButton button) - { - current_border_brush_ = normal_border_brush_.Get(); - Repaint(); - } -} diff --git a/CruUI/ui/controls/button.h b/CruUI/ui/controls/button.h deleted file mode 100644 index bd3f6eb3..00000000 --- a/CruUI/ui/controls/button.h +++ /dev/null @@ -1,41 +0,0 @@ -#pragma once - -#include - -#include "ui/control.h" - -namespace cru::ui::controls -{ - class Button : public Control - { - public: - static Button* Create(const std::initializer_list& children = std::initializer_list()) - { - const auto button = new Button(); - for (const auto control : children) - button->AddChild(control); - return button; - } - - protected: - 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: - void OnDraw(ID2D1DeviceContext* device_context) override; - - void OnMouseClickBegin(MouseButton button) override final; - void OnMouseClickEnd(MouseButton button) override final; - - private: - Microsoft::WRL::ComPtr normal_border_brush_; - Microsoft::WRL::ComPtr pressed_border_brush_; - ID2D1Brush* current_border_brush_; - }; -} diff --git a/CruUI/ui/controls/linear_layout.cpp b/CruUI/ui/controls/linear_layout.cpp deleted file mode 100644 index 8f537ea8..00000000 --- a/CruUI/ui/controls/linear_layout.cpp +++ /dev/null @@ -1,178 +0,0 @@ -#include "linear_layout.h" - -namespace cru::ui::controls -{ - LinearLayout::LinearLayout(const Orientation orientation) - : Control(true), orientation_(orientation) - { - - } - - inline float AtLeast0(const float value) - { - return value < 0 ? 0 : value; - } - - inline Size AtLeast0(const Size& size) - { - return Size(AtLeast0(size.width), AtLeast0(size.height)); - } - - Size LinearLayout::OnMeasure(const Size& available_size) - { - const auto layout_params = GetLayoutParams(); - - if (!layout_params->Validate()) - throw std::runtime_error("LayoutParams is not valid. Please check it."); - - auto&& get_available_length_for_child = [](const LayoutSideParams& layout_length, const float available_length) -> float - { - switch (layout_length.mode) - { - case MeasureMode::Exactly: - { - return std::min(layout_length.length, available_length); - } - case MeasureMode::Stretch: - case MeasureMode::Content: - { - return available_length; - } - default: - UnreachableCode(); - } - }; - - Size total_available_size_for_children( - get_available_length_for_child(layout_params->width, available_size.width), - get_available_length_for_child(layout_params->height, available_size.height) - ); - - auto rest_available_size_for_children = total_available_size_for_children; - - float secondary_side_child_max_length = 0; - - std::list stretch_control_list; - - // First measure Content and Exactly and count Stretch. - if (orientation_ == Orientation::Horizontal) - ForeachChild([&](Control* const control) - { - const auto mode = control->GetLayoutParams()->width.mode; - if (mode == MeasureMode::Content || mode == MeasureMode::Exactly) - { - control->Measure(AtLeast0(rest_available_size_for_children)); - const auto size = control->GetDesiredSize(); - rest_available_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 - ForeachChild([&](Control* const control) - { - const auto mode = control->GetLayoutParams()->height.mode; - if (mode == MeasureMode::Content || mode == MeasureMode::Exactly) - { - control->Measure(AtLeast0(rest_available_size_for_children)); - const auto size = control->GetDesiredSize(); - rest_available_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 = rest_available_size_for_children.width / stretch_control_list.size(); - for (const auto control : stretch_control_list) - { - control->Measure(Size(AtLeast0(available_width), rest_available_size_for_children.height)); - const auto size = control->GetDesiredSize(); - rest_available_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 = rest_available_size_for_children.height / stretch_control_list.size(); - for (const auto control : stretch_control_list) - { - control->Measure(Size(rest_available_size_for_children.width, AtLeast0(available_height))); - const auto size = control->GetDesiredSize(); - rest_available_size_for_children.height -= size.height; - secondary_side_child_max_length = std::max(size.width, secondary_side_child_max_length); - } - } - - auto actual_size_for_children = total_available_size_for_children; - if (orientation_ == Orientation::Horizontal) - { - actual_size_for_children.width -= rest_available_size_for_children.width; - actual_size_for_children.height = secondary_side_child_max_length; - } - else - { - actual_size_for_children.width = secondary_side_child_max_length; - actual_size_for_children.height -= rest_available_size_for_children.height; - } - - auto&& calculate_final_length = [](const LayoutSideParams& layout_length, const float length_for_children, const float max_child_length) -> float - { - switch (layout_length.mode) - { - case MeasureMode::Exactly: - case MeasureMode::Stretch: - return length_for_children; - case MeasureMode::Content: - return max_child_length; - default: - UnreachableCode(); - } - }; - - return Size( - calculate_final_length(layout_params->width, total_available_size_for_children.width, actual_size_for_children.width), - calculate_final_length(layout_params->height, total_available_size_for_children.height, actual_size_for_children.height) - ); - } - - void LinearLayout::OnLayout(const Rect& rect) - { - float current_anchor_length = 0; - ForeachChild([this, ¤t_anchor_length, rect](Control* control) - { - 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_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(); - } - }; - - if (orientation_ == Orientation::Horizontal) - { - control->Layout(Rect(Point(current_anchor_length, calculate_anchor(rect.height, size.height)), size)); - current_anchor_length += size.width; - } - else - { - control->Layout(Rect(Point(calculate_anchor(rect.width, size.width), current_anchor_length), size)); - current_anchor_length += size.height; - } - }); - } -} diff --git a/CruUI/ui/controls/linear_layout.h b/CruUI/ui/controls/linear_layout.h deleted file mode 100644 index 369824d4..00000000 --- a/CruUI/ui/controls/linear_layout.h +++ /dev/null @@ -1,41 +0,0 @@ -#pragma once - -#include "ui/control.h" - -namespace cru::ui::controls -{ - class LinearLayout : public Control - { - public: - enum class Orientation - { - Horizontal, - Vertical - }; - - static LinearLayout* Create(const Orientation orientation = Orientation::Vertical, const std::initializer_list& children = std::initializer_list()) - { - 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; - - protected: - Size OnMeasure(const Size& available_size) override; - void OnLayout(const Rect& rect) override; - - private: - Orientation orientation_; - }; -} diff --git a/CruUI/ui/controls/margin_container.cpp b/CruUI/ui/controls/margin_container.cpp deleted file mode 100644 index 8f9101b2..00000000 --- a/CruUI/ui/controls/margin_container.cpp +++ /dev/null @@ -1,63 +0,0 @@ -#include "margin_container.h" - -namespace cru::ui::controls -{ - inline float AtLeast0(const float value) - { - return value < 0 ? 0 : value; - } - - inline Size AtLeast0(const Size& size) - { - return Size(AtLeast0(size.width), AtLeast0(size.height)); - } - - MarginContainer::MarginContainer(const Thickness& margin) - : Control(true), margin_(margin) - { - } - - void MarginContainer::SetMargin(const Thickness& margin) - { - margin_ = margin; - Relayout(); - } - - Size MarginContainer::OnMeasure(const Size& available_size) - { - const auto margin_size = Size(margin_.left + margin_.right, margin_.top + margin_.bottom); - const auto coerced_available_size = AtLeast0(available_size - margin_size); - return Control::OnMeasure(coerced_available_size) + margin_size; - } - - void MarginContainer::OnLayout(const Rect& rect) - { - const auto anchor = Point(margin_.left, margin_.top); - const auto margin_size = Size(margin_.left + margin_.right, margin_.top + margin_.bottom); - ForeachChild([anchor, margin_size, rect](Control* control) - { - 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(anchor.x, layout_params->width.alignment, rect.width - margin_size.width, size.width), - calculate_anchor(anchor.y, layout_params->height.alignment, rect.height - margin_size.height, size.height) - ), size)); - }); - } -} diff --git a/CruUI/ui/controls/margin_container.h b/CruUI/ui/controls/margin_container.h deleted file mode 100644 index 0eafc40e..00000000 --- a/CruUI/ui/controls/margin_container.h +++ /dev/null @@ -1,44 +0,0 @@ -#pragma once - -#include - -#include "ui/control.h" - -namespace cru::ui::controls -{ - class MarginContainer : public Control - { - public: - static MarginContainer* Create(const Thickness& margin = Thickness::Zero(), const std::initializer_list& children = std::initializer_list()) - { - const auto margin_container = new MarginContainer(margin); - for (const auto control : children) - margin_container->AddChild(control); - return margin_container; - } - - protected: - explicit MarginContainer(const Thickness& margin); - - public: - MarginContainer(const MarginContainer& other) = delete; - MarginContainer(MarginContainer&& other) = delete; - MarginContainer& operator=(const MarginContainer& other) = delete; - MarginContainer& operator=(MarginContainer&& other) = delete; - ~MarginContainer() override = default; - - Thickness GetMargin() const - { - return margin_; - } - - void SetMargin(const Thickness& margin); - - protected: - Size OnMeasure(const Size& available_size) override; - void OnLayout(const Rect& rect) override; - - private: - Thickness margin_; - }; -} diff --git a/CruUI/ui/controls/text_block.cpp b/CruUI/ui/controls/text_block.cpp deleted file mode 100644 index 93d66ba6..00000000 --- a/CruUI/ui/controls/text_block.cpp +++ /dev/null @@ -1,283 +0,0 @@ -#include "text_block.h" - -#include "ui/window.h" -#include "graph/graph.h" -#include "exception.h" - -namespace cru -{ - namespace ui - { - namespace controls - { - using graph::CreateSolidBrush; - - inline Microsoft::WRL::ComPtr GetDWriteFactory() - { - return graph::GraphManager::GetInstance()->GetDWriteFactory(); - } - - TextBlock::TextBlock(const Microsoft::WRL::ComPtr& init_text_format, - const Microsoft::WRL::ComPtr& init_brush) : Control(false) - { - text_format_ = init_text_format == nullptr ? graph::CreateDefaultTextFormat() : init_text_format; - - RecreateTextLayout(); - - brush_ = init_brush == nullptr ? CreateSolidBrush(D2D1::ColorF(D2D1::ColorF::Black)) : init_brush; - - selection_brush_ = CreateSolidBrush(D2D1::ColorF(D2D1::ColorF::LightSkyBlue)); - } - - TextBlock::~TextBlock() = default; - - void TextBlock::SetText(const String& text) - { - if (text_ != text) - { - const auto old_text = text_; - text_ = text; - OnTextChangedCore(old_text, text); - } - } - - void TextBlock::SetBrush(const Microsoft::WRL::ComPtr& brush) - { - brush_ = brush; - Repaint(); - } - - void TextBlock::SetTextFormat(const Microsoft::WRL::ComPtr& text_format) - { - text_format_ = text_format; - RecreateTextLayout(); - Repaint(); - } - - void TextBlock::AddTextLayoutHandler(TextLayoutHandlerPtr handler) - { - text_layout_handlers_.push_back(std::move(handler)); - } - - void TextBlock::RemoveTextLayoutHandler(const TextLayoutHandlerPtr& handler) - { - const auto find_result = std::find(text_layout_handlers_.cbegin(), text_layout_handlers_.cend(), - handler); - if (find_result != text_layout_handlers_.cend()) - text_layout_handlers_.erase(find_result); - } - - void TextBlock::SetSelectable(const bool is_selectable) - { - if (!is_selectable) - { - is_selecting_ = false; - selected_range_ = std::nullopt; - Repaint(); - } - is_selectable_ = is_selectable; - } - - void TextBlock::SetSelectedRange(std::optional text_range) - { - if (is_selectable_) - { - selected_range_ = text_range; - Repaint(); - } - } - - void TextBlock::OnSizeChangedCore(events::SizeChangedEventArgs& args) - { - Control::OnSizeChangedCore(args); - text_layout_->SetMaxWidth(args.GetNewSize().width); - text_layout_->SetMaxHeight(args.GetNewSize().height); - Repaint(); - } - - void TextBlock::OnDraw(ID2D1DeviceContext* device_context) - { - Control::OnDraw(device_context); - if (selected_range_.has_value()) - { - DWRITE_TEXT_METRICS text_metrics{}; - ThrowIfFailed(text_layout_->GetMetrics(&text_metrics)); - const auto metrics_count = text_metrics.lineCount * text_metrics.maxBidiReorderingDepth; - - Vector hit_test_metrics(metrics_count); - UINT32 actual_count; - text_layout_->HitTestTextRange( - selected_range_.value().position, selected_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), selection_brush_.Get()); - } - } - device_context->DrawTextLayout(D2D1::Point2F(), text_layout_.Get(), brush_.Get()); - } - - namespace - { - std::optional TextLayoutHitTest(IDWriteTextLayout* text_layout, const Point& point, const bool test_inside = true) - { - BOOL is_trailing, is_inside; - DWRITE_HIT_TEST_METRICS metrics{}; - text_layout->HitTestPoint(point.x, point.y, &is_trailing, &is_inside, &metrics); - if (!test_inside || is_inside) - return is_trailing == 0 ? metrics.textPosition : metrics.textPosition + 1; - else - return std::nullopt; - } - } - - void TextBlock::OnMouseDownCore(events::MouseButtonEventArgs& args) - { - Control::OnMouseDownCore(args); - if (is_selectable_ && args.GetMouseButton() == MouseButton::Left) - { - selected_range_ = std::nullopt; - const auto hit_test_result = TextLayoutHitTest(text_layout_.Get(), args.GetPoint(this), true); - if (hit_test_result.has_value()) - { - mouse_down_position_ = hit_test_result.value(); - is_selecting_ = true; - GetWindow()->CaptureMouseFor(this); - } - Repaint(); - } - } - - void TextBlock::OnMouseMoveCore(events::MouseEventArgs& args) - { - Control::OnMouseMoveCore(args); - if (is_selecting_) - { - const auto hit_test_result = TextLayoutHitTest(text_layout_.Get(), args.GetPoint(this), false).value(); - if (hit_test_result > mouse_down_position_) - selected_range_ = TextRange(mouse_down_position_, hit_test_result - mouse_down_position_); - else if (hit_test_result < mouse_down_position_) - selected_range_ = TextRange(hit_test_result, mouse_down_position_ - hit_test_result); - else - selected_range_ = std::nullopt; - Repaint(); - } - } - - void TextBlock::OnMouseUpCore(events::MouseButtonEventArgs& args) - { - Control::OnMouseUpCore(args); - if (args.GetMouseButton() == MouseButton::Left) - { - if (is_selecting_) - { - is_selecting_ = false; - GetWindow()->ReleaseCurrentMouseCapture(); - } - } - } - - void TextBlock::OnLoseFocusCore(events::FocusChangeEventArgs& args) - { - Control::OnLoseFocusCore(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; - Repaint(); - } - } - - Size TextBlock::OnMeasure(const Size& available_size) - { - const auto layout_params = GetLayoutParams(); - - if (layout_params->width.mode == MeasureMode::Stretch && layout_params->height.mode == MeasureMode::Stretch) - return available_size; - - auto&& get_measure_length = [](const LayoutSideParams& layout_length, const float available_length) -> float - { - switch (layout_length.mode) - { - case MeasureMode::Exactly: - { - return std::min(layout_length.length, available_length); - } - case MeasureMode::Stretch: - case MeasureMode::Content: - { - return available_length; - } - default: - UnreachableCode(); - } - }; - - const Size measure_size(get_measure_length(layout_params->width, available_size.width), - get_measure_length(layout_params->height, available_size.height)); - - ThrowIfFailed(text_layout_->SetMaxWidth(measure_size.width)); - ThrowIfFailed(text_layout_->SetMaxHeight(measure_size.height)); - - DWRITE_TEXT_METRICS metrics{}; - - ThrowIfFailed(text_layout_->GetMetrics(&metrics)); - - const Size measure_result(metrics.width, metrics.height); - - auto&& calculate_final_length = [](const LayoutSideParams& layout_length, const float measure_length, const float measure_result_length) -> float - { - if ((layout_length.mode == MeasureMode::Stretch || - layout_length.mode == MeasureMode::Exactly) - && measure_result_length < measure_length) - return measure_length; - else - return measure_result_length; - }; - - const Size result_size( - calculate_final_length(layout_params->width, measure_size.width, measure_result.width), - calculate_final_length(layout_params->height, measure_size.height, measure_result.height) - ); - - return result_size; - } - - void TextBlock::OnTextChangedCore(const String& old_text, const String& new_text) - { - RecreateTextLayout(); - Repaint(); - } - - void TextBlock::RecreateTextLayout() - { - assert(text_format_ != nullptr); - - const auto dwrite_factory = GetDWriteFactory(); - - const auto&& size = GetSize(); - - ThrowIfFailed(dwrite_factory->CreateTextLayout( - text_.c_str(), static_cast(text_.size()), - text_format_.Get(), - size.width, size.height, - &text_layout_ - )); - - std::for_each(text_layout_handlers_.cbegin(), text_layout_handlers_.cend(), [this](const TextLayoutHandlerPtr& handler) - { - (*handler)(text_layout_); - }); - } - } - } -} diff --git a/CruUI/ui/controls/text_block.h b/CruUI/ui/controls/text_block.h deleted file mode 100644 index c87ffc51..00000000 --- a/CruUI/ui/controls/text_block.h +++ /dev/null @@ -1,116 +0,0 @@ -#pragma once - -#include -#include - -#include "ui/control.h" - -namespace cru -{ - namespace ui - { - namespace controls - { - class TextBlock : public Control - { - public: - using TextLayoutHandlerPtr = FunctionPtr)>; - - static TextBlock* Create( - const String& text = L"", - const Microsoft::WRL::ComPtr& init_text_format = nullptr, - const Microsoft::WRL::ComPtr& init_brush = nullptr) - { - const auto text_block = new TextBlock(init_text_format, init_brush); - text_block->SetText(text); - return text_block; - } - - protected: - explicit TextBlock( - const Microsoft::WRL::ComPtr& init_text_format = nullptr, - const Microsoft::WRL::ComPtr& init_brush = nullptr - ); - public: - TextBlock(const TextBlock& other) = delete; - TextBlock(TextBlock&& other) = delete; - TextBlock& operator=(const TextBlock& other) = delete; - TextBlock& operator=(TextBlock&& other) = delete; - ~TextBlock() override; - - String GetText() const - { - return text_; - } - - void SetText(const String& text); - - Microsoft::WRL::ComPtr GetBrush() const - { - return brush_; - } - - void SetBrush(const Microsoft::WRL::ComPtr& brush); - - Microsoft::WRL::ComPtr GetTextFormat() const - { - return text_format_; - } - - void SetTextFormat(const Microsoft::WRL::ComPtr& text_format); - - - void AddTextLayoutHandler(TextLayoutHandlerPtr handler); - - void RemoveTextLayoutHandler(const TextLayoutHandlerPtr& handler); - - bool IsSelectable() const - { - return is_selectable_; - } - - void SetSelectable(bool is_selectable); - - std::optional GetSelectedRange() const - { - return selected_range_; - } - - void SetSelectedRange(std::optional text_range); - - protected: - void OnSizeChangedCore(events::SizeChangedEventArgs& args) override final; - void OnDraw(ID2D1DeviceContext* device_context) override; - - void OnMouseDownCore(events::MouseButtonEventArgs& args) override final; - void OnMouseMoveCore(events::MouseEventArgs& args) override final; - void OnMouseUpCore(events::MouseButtonEventArgs& args) override final; - - void OnLoseFocusCore(events::FocusChangeEventArgs& args) override final; - - Size OnMeasure(const Size& available_size) override final; - - private: - void OnTextChangedCore(const String& old_text, const String& new_text); - - void RecreateTextLayout(); - - private: - String text_; - - Microsoft::WRL::ComPtr brush_; - Microsoft::WRL::ComPtr selection_brush_; - Microsoft::WRL::ComPtr text_format_; - Microsoft::WRL::ComPtr text_layout_; - - Vector text_layout_handlers_; - - bool is_selectable_ = false; - - bool is_selecting_ = false; - unsigned mouse_down_position_ = 0; - std::optional selected_range_ = std::nullopt; - }; - } - } -} diff --git a/CruUI/ui/controls/text_box.cpp b/CruUI/ui/controls/text_box.cpp deleted file mode 100644 index a8d78398..00000000 --- a/CruUI/ui/controls/text_box.cpp +++ /dev/null @@ -1,244 +0,0 @@ -#include "text_box.h" - -#include - -#include "graph/graph.h" -#include "exception.h" - -namespace cru::ui::controls -{ - using graph::CreateSolidBrush; - - inline Microsoft::WRL::ComPtr GetDWriteFactory() - { - return graph::GraphManager::GetInstance()->GetDWriteFactory(); - } - - TextBox::TextBox(const Microsoft::WRL::ComPtr& init_text_format, - const Microsoft::WRL::ComPtr& init_brush) : Control(false) - { - text_format_ = init_text_format == nullptr ? graph::CreateDefaultTextFormat() : init_text_format; - - RecreateTextLayout(); - - brush_ = init_brush == nullptr ? CreateSolidBrush(D2D1::ColorF(D2D1::ColorF::Black)) : init_brush; - - caret_brush_ = CreateSolidBrush(D2D1::ColorF(D2D1::ColorF::Black)); - - caret_action_ = CreateActionPtr([this] - { - is_caret_show_ = !is_caret_show_; - Repaint(); - }); - - //selection_brush_ = CreateSolidBrush(D2D1::ColorF(D2D1::ColorF::LightSkyBlue)); - } - - TextBox::~TextBox() = default; - - void TextBox::SetText(const String& text) - { - if (text_ != text) - { - const auto old_text = text_; - text_ = text; - OnTextChangedCore(old_text, text); - } - } - - void TextBox::SetBrush(const Microsoft::WRL::ComPtr& brush) - { - brush_ = brush; - Repaint(); - } - - void TextBox::SetTextFormat(const Microsoft::WRL::ComPtr& text_format) - { - text_format_ = text_format; - RecreateTextLayout(); - Repaint(); - } - - void TextBox::OnSizeChangedCore(events::SizeChangedEventArgs& args) - { - Control::OnSizeChangedCore(args); - text_layout_->SetMaxWidth(args.GetNewSize().width); - text_layout_->SetMaxHeight(args.GetNewSize().height); - Repaint(); - } - - void TextBox::OnDraw(ID2D1DeviceContext* device_context) - { - Control::OnDraw(device_context); - if (text_layout_ != nullptr) - { - device_context->DrawTextLayout(D2D1::Point2F(), text_layout_.Get(), brush_.Get()); - if (is_caret_show_) - { - const auto caret_half_width = Application::GetInstance()->GetCaretInfo().half_caret_width; - FLOAT x, y; - DWRITE_HIT_TEST_METRICS metrics{}; - ThrowIfFailed(text_layout_->HitTestTextPosition(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()); - } - } - } - - namespace - { - std::optional TextLayoutHitTest(IDWriteTextLayout* text_layout, const Point& point, bool test_inside = true) - { - BOOL is_trailing, is_inside; - DWRITE_HIT_TEST_METRICS metrics{}; - text_layout->HitTestPoint(point.x, point.y, &is_trailing, &is_inside, &metrics); - if (!test_inside || is_inside) - return is_trailing == 0 ? metrics.textPosition : metrics.textPosition + 1; - else - return std::nullopt; - } - } - - void TextBox::OnMouseDownCore(events::MouseButtonEventArgs& args) - { - Control::OnMouseDownCore(args); - if (args.GetMouseButton() == MouseButton::Left) - { - position_ = TextLayoutHitTest(text_layout_.Get(), args.GetPoint(this), false).value(); - - Repaint(); - } - } - - void TextBox::OnGetFocusCore(events::FocusChangeEventArgs& args) - { - Control::OnGetFocusCore(args); - assert(caret_timer_ == nullptr); - is_caret_show_ = true; - caret_timer_ = SetInterval(Application::GetInstance()->GetCaretInfo().caret_blink_duration, caret_action_); - } - - void TextBox::OnLoseFocusCore(events::FocusChangeEventArgs& args) - { - Control::OnLoseFocusCore(args); - assert(caret_timer_ != nullptr); - caret_timer_->Cancel(); - is_caret_show_ = false; - } - - void TextBox::OnKeyDownCore(events::KeyEventArgs& args) - { - Control::OnKeyDownCore(args); - if (args.GetVirtualCode() == VK_LEFT && position_ > 0) - { - position_--; - Repaint(); - } - - if (args.GetVirtualCode() == VK_RIGHT && position_ < GetText().size()) - { - position_++; - Repaint(); - } - } - - void TextBox::OnCharCore(events::CharEventArgs& args) - { - Control::OnCharCore(args); - if (args.GetChar() == L'\b') - { - auto text = GetText(); - if (!text.empty() && position_ > 0) - { - const auto position = --position_; - text.erase(position); - SetText(text); - } - return; - } - - if (std::iswprint(args.GetChar())) - { - const auto position = position_++; - auto text = GetText(); - text.insert(text.cbegin() + position, { args.GetChar() }); - SetText(text); - } - } - - Size TextBox::OnMeasure(const Size& available_size) - { - const auto layout_params = GetLayoutParams(); - - if (layout_params->width.mode == MeasureMode::Stretch && layout_params->height.mode == MeasureMode::Stretch) - return available_size; - - auto&& get_measure_length = [](const LayoutSideParams& layout_length, const float available_length) -> float - { - switch (layout_length.mode) - { - case MeasureMode::Exactly: - { - return std::min(layout_length.length, available_length); - } - case MeasureMode::Stretch: - case MeasureMode::Content: - { - return available_length; - } - default: - UnreachableCode(); - } - }; - - const Size measure_size(get_measure_length(layout_params->width, available_size.width), - get_measure_length(layout_params->height, available_size.height)); - - ThrowIfFailed(text_layout_->SetMaxWidth(measure_size.width)); - ThrowIfFailed(text_layout_->SetMaxHeight(measure_size.height)); - - DWRITE_TEXT_METRICS metrics{}; - - ThrowIfFailed(text_layout_->GetMetrics(&metrics)); - - const Size measure_result(metrics.width, metrics.height); - - auto&& calculate_final_length = [](const LayoutSideParams& layout_length, const float measure_length, const float measure_result_length) -> float - { - if ((layout_length.mode == MeasureMode::Stretch || - layout_length.mode == MeasureMode::Exactly) - && measure_result_length < measure_length) - return measure_length; - else - return measure_result_length; - }; - - const Size result_size( - calculate_final_length(layout_params->width, measure_size.width, measure_result.width), - calculate_final_length(layout_params->height, measure_size.height, measure_result.height) - ); - - return result_size; - } - - void TextBox::OnTextChangedCore(const String& old_text, const String& new_text) - { - RecreateTextLayout(); - Repaint(); - } - - void TextBox::RecreateTextLayout() - { - assert(text_format_ != nullptr); - - const auto dwrite_factory = GetDWriteFactory(); - - const auto&& size = GetSize(); - - ThrowIfFailed(dwrite_factory->CreateTextLayout( - text_.c_str(), static_cast(text_.size()), - text_format_.Get(), - size.width, size.height, - &text_layout_ - )); - } -} diff --git a/CruUI/ui/controls/text_box.h b/CruUI/ui/controls/text_box.h deleted file mode 100644 index b815ed1f..00000000 --- a/CruUI/ui/controls/text_box.h +++ /dev/null @@ -1,85 +0,0 @@ -#pragma once - -#include "ui/control.h" -#include "timer.h" - -namespace cru::ui::controls -{ - class TextBox : public Control - { - public: - static TextBox* Create( - const Microsoft::WRL::ComPtr& init_text_format = nullptr, - const Microsoft::WRL::ComPtr& init_brush = nullptr) - { - return new TextBox(init_text_format, init_brush); - } - - protected: - explicit TextBox( - const Microsoft::WRL::ComPtr& init_text_format = nullptr, - const Microsoft::WRL::ComPtr& init_brush = nullptr - ); - public: - TextBox(const TextBox& other) = delete; - TextBox(TextBox&& other) = delete; - TextBox& operator=(const TextBox& other) = delete; - TextBox& operator=(TextBox&& other) = delete; - ~TextBox() override; - - String GetText() const - { - return text_; - } - - void SetText(const String& text); - - Microsoft::WRL::ComPtr GetBrush() const - { - return brush_; - } - - void SetBrush(const Microsoft::WRL::ComPtr& brush); - - Microsoft::WRL::ComPtr GetTextFormat() const - { - return text_format_; - } - - void SetTextFormat(const Microsoft::WRL::ComPtr& text_format); - - protected: - void OnSizeChangedCore(events::SizeChangedEventArgs& args) override final; - void OnDraw(ID2D1DeviceContext* device_context) override; - - void OnMouseDownCore(events::MouseButtonEventArgs& args) override final; - - void OnGetFocusCore(events::FocusChangeEventArgs& args) override final; - void OnLoseFocusCore(events::FocusChangeEventArgs& args) override final; - - void OnKeyDownCore(events::KeyEventArgs& args) override final; - void OnCharCore(events::CharEventArgs& args) override final; - - Size OnMeasure(const Size& available_size) override final; - - private: - void OnTextChangedCore(const String& old_text, const String& new_text); - - void RecreateTextLayout(); - - private: - String text_; - - Microsoft::WRL::ComPtr brush_; - Microsoft::WRL::ComPtr caret_brush_; - //Microsoft::WRL::ComPtr selection_brush_; - Microsoft::WRL::ComPtr text_format_; - Microsoft::WRL::ComPtr text_layout_; - - unsigned position_ = 0; - - TimerTask caret_timer_; - ActionPtr caret_action_; - bool is_caret_show_; - }; -} diff --git a/CruUI/ui/controls/toggle_button.cpp b/CruUI/ui/controls/toggle_button.cpp deleted file mode 100644 index 68bd0fc9..00000000 --- a/CruUI/ui/controls/toggle_button.cpp +++ /dev/null @@ -1,150 +0,0 @@ -#include "toggle_button.h" - -#include - -#include "graph/graph.h" -#include "ui/animations/animation.h" - -namespace cru::ui::controls -{ - using graph::CreateSolidBrush; - 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_ = CreateSolidBrush(D2D1::ColorF(D2D1::ColorF::DeepSkyBlue)); - off_brush_ = CreateSolidBrush(D2D1::ColorF(D2D1::ColorF::LightGray)); - } - - inline D2D1_POINT_2F ConvertPoint(const Point& point) - { - return D2D1::Point2F(point.x, point.y); - } - - 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(ConvertPoint(point), transform, &contains); - if (!contains) - frame_path_->StrokeContainsPoint(ConvertPoint(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(fmt::format(L"ToggleButton {}", reinterpret_cast(this)), time) - .AddStepHandler(CreatePtr([=](animations::AnimationDelegatePtr, const double percentage) - { - current_circle_position_ = static_cast(previous_position + delta * percentage); - Repaint(); - })).Start(); - - RaiseToggleEvent(state); - Repaint(); - } - } - - void ToggleButton::Toggle() - { - SetState(!GetState()); - } - - void ToggleButton::OnToggle(events::ToggleEventArgs& args) - { - - } - - void ToggleButton::OnDraw(ID2D1DeviceContext* device_context) - { - Control::OnDraw(device_context); - 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()); - } - }); - } - - void ToggleButton::OnMouseClickCore(events::MouseButtonEventArgs& args) - { - Control::OnMouseClickCore(args); - Toggle(); - } - - Size ToggleButton::OnMeasure(const Size& available_size) - { - const auto layout_params = GetLayoutParams(); - - auto&& get_measure_length = [](const LayoutSideParams& layout_length, const float available_length, const float fix_length) -> float - { - switch (layout_length.mode) - { - case MeasureMode::Exactly: - { - return std::max(std::min(layout_length.length, available_length), fix_length); - } - case MeasureMode::Stretch: - { - return std::max(available_length, fix_length); - } - case MeasureMode::Content: - { - return fix_length; - } - default: - UnreachableCode(); - } - }; - - const Size result_size( - get_measure_length(layout_params->width, available_size.width, half_width * 2 + stroke_width), - get_measure_length(layout_params->height, available_size.height, half_height * 2 + stroke_width) - ); - - return result_size; - } - - void ToggleButton::RaiseToggleEvent(bool new_state) - { - events::ToggleEventArgs args(this, this, new_state); - OnToggle(args); - toggle_event.Raise(args); - } -} diff --git a/CruUI/ui/controls/toggle_button.h b/CruUI/ui/controls/toggle_button.h deleted file mode 100644 index d496f21a..00000000 --- a/CruUI/ui/controls/toggle_button.h +++ /dev/null @@ -1,61 +0,0 @@ -#pragma once - -#include "ui/control.h" - -namespace cru::ui::controls -{ - class ToggleButton : public Control - { - public: - 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; - - bool IsPointInside(const Point& point) override; - - bool GetState() const - { - return state_; - } - - void SetState(bool state); - - void Toggle(); - - public: - events::ToggleEvent toggle_event; - - protected: - virtual void OnToggle(events::ToggleEventArgs& args); - - protected: - void OnDraw(ID2D1DeviceContext* device_context) override; - - void OnMouseClickCore(events::MouseButtonEventArgs& args) override; - - Size OnMeasure(const Size& available_size) override; - - private: - void RaiseToggleEvent(bool new_state); - - private: - bool state_ = false; - - float current_circle_position_; - - Microsoft::WRL::ComPtr frame_path_; - Microsoft::WRL::ComPtr on_brush_; - Microsoft::WRL::ComPtr off_brush_; - }; -} diff --git a/CruUI/ui/events/ui_event.cpp b/CruUI/ui/events/ui_event.cpp deleted file mode 100644 index 59623bab..00000000 --- a/CruUI/ui/events/ui_event.cpp +++ /dev/null @@ -1,19 +0,0 @@ -#include "ui_event.h" - -#include "ui/control.h" - -namespace cru -{ - namespace ui - { - namespace events - { - Point MouseEventArgs::GetPoint(Control* control) const - { - if (point_.has_value()) - return control->AbsoluteToLocal(point_.value()); - return Point(); - } - } - } -} diff --git a/CruUI/ui/events/ui_event.h b/CruUI/ui/events/ui_event.h deleted file mode 100644 index b042b706..00000000 --- a/CruUI/ui/events/ui_event.h +++ /dev/null @@ -1,317 +0,0 @@ -#pragma once - -#include "system_headers.h" -#include - -#include "base.h" -#include "cru_event.h" -#include "ui/ui_base.h" - -namespace cru -{ - namespace ui - { - class Control; - - namespace events - { - class UiEventArgs : public BasicEventArgs - { - public: - UiEventArgs(Object* sender, Object* original_sender) - : BasicEventArgs(sender), original_sender_(original_sender) - { - - } - - 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_; - } - - private: - Object* original_sender_; - }; - - - class MouseEventArgs : public UiEventArgs - { - public: - MouseEventArgs(Object* sender, Object* original_sender, const std::optional& 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) const; - - private: - std::optional 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 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 PositionChangedEventArgs : public UiEventArgs - { - public: - PositionChangedEventArgs(Object* sender, Object* original_sender, const Point& old_position, const Point& new_position) - : UiEventArgs(sender, original_sender), old_position_(old_position), new_position_(new_position) - { - - } - PositionChangedEventArgs(const PositionChangedEventArgs& other) = default; - PositionChangedEventArgs(PositionChangedEventArgs&& other) = default; - PositionChangedEventArgs& operator=(const PositionChangedEventArgs& other) = default; - PositionChangedEventArgs& operator=(PositionChangedEventArgs&& other) = default; - ~PositionChangedEventArgs() override = default; - - Point GetOldPosition() const - { - return old_position_; - } - - Point GetNewPosition() const - { - return new_position_; - } - - private: - Point old_position_; - Point new_position_; - }; - - - class SizeChangedEventArgs : public UiEventArgs - { - public: - SizeChangedEventArgs(Object* sender, Object* original_sender, const Size& old_size, const Size& new_size) - : UiEventArgs(sender, original_sender), old_size_(old_size), new_size_(new_size) - { - - } - SizeChangedEventArgs(const SizeChangedEventArgs& other) = default; - SizeChangedEventArgs(SizeChangedEventArgs&& other) = default; - SizeChangedEventArgs& operator=(const SizeChangedEventArgs& other) = default; - SizeChangedEventArgs& operator=(SizeChangedEventArgs&& other) = default; - ~SizeChangedEventArgs() override = default; - - Size GetOldSize() const - { - return old_size_; - } - - Size GetNewSize() const - { - return new_size_; - } - - private: - Size old_size_; - Size new_size_; - }; - - 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 GetResult() const - { - return result_; - } - - void SetResult(const std::optional result) - { - result_ = result; - } - - private: - WindowNativeMessage message_; - std::optional 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_; - }; - - using UiEvent = Event; - using MouseEvent = Event; - using MouseButtonEvent = Event; - using DrawEvent = Event; - using PositionChangedEvent = Event; - using SizeChangedEvent = Event; - using FocusChangeEvent = Event; - using ToggleEvent = Event; - using WindowNativeMessageEvent = Event; - using KeyEvent = Event; - using CharEvent = Event; - } - } -} \ No newline at end of file diff --git a/CruUI/ui/layout_base.cpp b/CruUI/ui/layout_base.cpp deleted file mode 100644 index a26379a0..00000000 --- a/CruUI/ui/layout_base.cpp +++ /dev/null @@ -1,79 +0,0 @@ -#include "layout_base.h" - -#include "application.h" -#include "control.h" - -namespace cru::ui -{ - LayoutManager* LayoutManager::GetInstance() - { - static LayoutManager layout_manager; - return &layout_manager; - } - - void LayoutManager::InvalidateControlPositionCache(Control * control) - { - if (cache_invalid_controls_.count(control) == 1) - return; - - // find descendant then erase it; find ancestor then just return. - auto i = cache_invalid_controls_.cbegin(); - while (i != cache_invalid_controls_.cend()) - { - auto current_i = i++; - const auto result = IsAncestorOrDescendant(*current_i, control); - if (result == control) - cache_invalid_controls_.erase(current_i); - else if (result != nullptr) - return; // find a ancestor of "control", just return - } - - cache_invalid_controls_.insert(control); - - if (cache_invalid_controls_.size() == 1) // when insert just now and not repeat to "InvokeLater". - { - InvokeLater([this] { - - RefreshInvalidControlPositionCache(); // first refresh position cache. - for (const auto i : cache_invalid_controls_) // traverse all descendants of position-invalid controls and notify position change event - i->TraverseDescendants([](Control* control) - { - control->CheckAndNotifyPositionChanged(); - }); - cache_invalid_controls_.clear(); // after update and notify, clear the set. - - }); - } - } - - void LayoutManager::RefreshInvalidControlPositionCache() - { - for (const auto i : cache_invalid_controls_) - RefreshControlPositionCache(i); - } - - void LayoutManager::RefreshControlPositionCache(Control * control) - { - auto point = Point::Zero(); - auto parent = control; - while ((parent = parent->GetParent())) { - const auto p = parent->GetPositionRelative(); - point.x += p.x; - point.y += p.y; - } - RefreshControlPositionCacheInternal(control, point); - } - - void LayoutManager::RefreshControlPositionCacheInternal(Control * control, const Point & parent_lefttop_absolute) - { - const auto position = control->GetPositionRelative(); - Point lefttop( - parent_lefttop_absolute.x + position.x, - parent_lefttop_absolute.y + position.y - ); - control->position_cache_.lefttop_position_absolute = lefttop; - control->ForeachChild([lefttop](Control* c) { - RefreshControlPositionCacheInternal(c, lefttop); - }); - } -} diff --git a/CruUI/ui/layout_base.h b/CruUI/ui/layout_base.h deleted file mode 100644 index 163b99b2..00000000 --- a/CruUI/ui/layout_base.h +++ /dev/null @@ -1,136 +0,0 @@ -#pragma once - -#include "system_headers.h" -#include - -#include "base.h" -#include "ui_base.h" - -namespace cru -{ - namespace ui - { - class Control; - - enum class Alignment - { - Center, - Start, - End - }; - - enum class MeasureMode - { - Exactly, - Content, - Stretch - }; - - 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 (mode == MeasureMode::Exactly && length < 0.0) - { -#ifdef CRU_DEBUG - ::OutputDebugStringW(L"LayoutSideParams validation error: mode is Exactly but length is less than 0.\n"); -#endif - return false; - } - return true; - } - - float length = 0.0; - MeasureMode mode = MeasureMode::Content; - Alignment alignment = Alignment::Center; - }; - - 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 - { - if (!width.Validate()) - { -#ifdef CRU_DEBUG - ::OutputDebugStringW(L"Width(LayoutSideParams) is not valid."); -#endif - return false; - } - if (!height.Validate()) - { -#ifdef CRU_DEBUG - ::OutputDebugStringW(L"Height(LayoutSideParams) is not valid."); -#endif - return false; - } - return true; - } - - LayoutSideParams width; - LayoutSideParams height; - }; - - - class LayoutManager : public Object - { - public: - static LayoutManager* GetInstance(); - - public: - LayoutManager() = default; - LayoutManager(const LayoutManager& other) = delete; - LayoutManager(LayoutManager&& other) = delete; - LayoutManager& operator=(const LayoutManager& other) = delete; - LayoutManager& operator=(LayoutManager&& other) = delete; - ~LayoutManager() override = default; - - //Mark position cache of the control and its descendants invalid, - //(which is saved as an auto-managed list internal) - //and send a message to refresh them. - void InvalidateControlPositionCache(Control* control); - - //Refresh position cache of the control and its descendants whose cache - //has been marked as invalid. - void RefreshInvalidControlPositionCache(); - - //Refresh position cache of the control and its descendants immediately. - static void RefreshControlPositionCache(Control* control); - - private: - static void RefreshControlPositionCacheInternal(Control* control, const Point& parent_lefttop_absolute); - - private: - std::unordered_set cache_invalid_controls_; - }; - } -} diff --git a/CruUI/ui/ui_base.cpp b/CruUI/ui/ui_base.cpp deleted file mode 100644 index 550432e4..00000000 --- a/CruUI/ui/ui_base.cpp +++ /dev/null @@ -1,6 +0,0 @@ -#include "ui_base.h" - -namespace cru { - namespace ui { - } -} diff --git a/CruUI/ui/ui_base.h b/CruUI/ui/ui_base.h deleted file mode 100644 index 43f3c498..00000000 --- a/CruUI/ui/ui_base.h +++ /dev/null @@ -1,154 +0,0 @@ -#pragma once - - -namespace cru -{ - namespace 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); - } - - 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 Size GetSize() const - { - return Size(width, height); - } - - 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; - }; - - 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 left; - float top; - float right; - float bottom; - }; - - enum class MouseButton - { - Left, - Right, - Middle - }; - - struct TextRange - { - constexpr TextRange() = default; - constexpr TextRange(const int position, const int count) - : position(position), count(count) - { - - } - - unsigned position = 0; - unsigned count = 0; - }; - } -} diff --git a/CruUI/ui/window.cpp b/CruUI/ui/window.cpp deleted file mode 100644 index 34a54512..00000000 --- a/CruUI/ui/window.cpp +++ /dev/null @@ -1,606 +0,0 @@ -#include "window.h" - -#include - -#include "application.h" -#include "graph/graph.h" -#include "exception.h" - -namespace cru -{ - namespace 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 = Application::GetInstance()->GetWindowManager()->FromHandle(hWnd); - - LRESULT result; - if (window != nullptr && window->HandleWindowMessage(hWnd, Msg, wParam, lParam, result)) - return result; - - return DefWindowProc(hWnd, Msg, wParam, lParam); - } - - WindowManager::WindowManager() { - general_window_class_ = std::make_unique( - 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; - } - - Vector WindowManager::GetAllWindows() const - { - Vector windows; - for (auto [key, value] : window_map_) - windows.push_back(value); - return windows; - } - - inline Point PiToDip(const POINT& pi_point) - { - return Point( - graph::PixelToDipX(pi_point.x), - graph::PixelToDipY(pi_point.y) - ); - } - - Window::Window() : Control(WindowConstructorTag{}, this), control_list_({ this }) { - const auto app = Application::GetInstance(); - hwnd_ = CreateWindowEx(0, - app->GetWindowManager()->GetGeneralWindowClass()->GetName(), - L"", WS_OVERLAPPEDWINDOW, - CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, - nullptr, nullptr, app->GetInstanceHandle(), nullptr - ); - - if (hwnd_ == nullptr) - throw std::runtime_error("Failed to create window."); - - app->GetWindowManager()->RegisterWindow(hwnd_, this); - - render_target_ = app->GetGraphManager()->CreateWindowRenderTarget(hwnd_); - } - - Window::~Window() { - Close(); - TraverseDescendants([this](Control* control) { - control->OnDetachToWindow(this); - }); - } - - void Window::Close() { - if (IsWindowValid()) - DestroyWindow(hwnd_); - } - - void Window::Repaint() { - if (IsWindowValid()) { - InvalidateRect(hwnd_, nullptr, false); - } - } - - void Window::Show() { - if (IsWindowValid()) { - ShowWindow(hwnd_, SW_SHOWNORMAL); - } - } - - void Window::Hide() { - if (IsWindowValid()) { - ShowWindow(hwnd_, SW_HIDE); - } - } - - Size Window::GetClientSize() { - if (!IsWindowValid()) - return Size(); - - const auto pixel_rect = GetClientRectPixel(); - return Size( - graph::PixelToDipX(pixel_rect.right), - graph::PixelToDipY(pixel_rect.bottom) - ); - } - - void Window::SetClientSize(const Size & size) { - if (IsWindowValid()) { - const auto window_style = static_cast(GetWindowLongPtr(hwnd_, GWL_STYLE)); - const auto window_ex_style = static_cast(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 - ); - } - } - - Rect Window::GetWindowRect() { - if (!IsWindowValid()) - return Rect(); - - RECT rect; - ::GetWindowRect(hwnd_, &rect); - - return Rect::FromVertices( - graph::PixelToDipX(rect.left), - graph::PixelToDipY(rect.top), - graph::PixelToDipX(rect.right), - graph::PixelToDipY(rect.bottom) - ); - } - - 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 - ); - } - } - - bool Window::HandleWindowMessage(HWND hwnd, int msg, WPARAM w_param, LPARAM l_param, LRESULT & result) { - - if (!native_message_event.IsNoHandler()) - { - 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_KEYDOWN: - OnKeyDownInternal(static_cast(w_param)); - result = 0; - return true; - case WM_KEYUP: - OnKeyUpInternal(static_cast(w_param)); - result = 0; - return true; - case WM_CHAR: - OnCharInternal(static_cast(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; - } - } - - Point Window::GetMousePosition() - { - POINT point; - ::GetCursorPos(&point); - ::ScreenToClient(hwnd_, &point); - return PiToDip(point); - } - - Point Window::GetPositionRelative() - { - return Point(); - } - - void Window::SetPositionRelative(const Point & position) - { - - } - - Size Window::GetSize() - { - return GetClientSize(); - } - - void Window::SetSize(const Size & size) - { - - } - - void Window::RefreshControlList() { - control_list_.clear(); - TraverseDescendants([this](Control* control) { - this->control_list_.push_back(control); - }); - } - - Control * Window::HitTest(const Point & point) - { - for (auto i = control_list_.crbegin(); i != control_list_.crend(); ++i) { - auto control = *i; - if (control->IsPointInside(control->AbsoluteToLocal(point))) { - return control; - } - } - return nullptr; - } - - 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."); - - if (!IsWindowValid()) - return false; - - if (!window_focus_) - { - focus_control_ = control; - ::SetFocus(hwnd_); - return true; // event dispatch will be done in window message handling function "OnSetFocusInternal". - } - - if (focus_control_ == control) - return true; - - DispatchEvent(focus_control_, &Control::RaiseLoseFocusEvent, nullptr, false); - - focus_control_ = control; - - DispatchEvent(control, &Control::RaiseGetFocusEvent, nullptr, false); - - return true; - } - - 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(); - } - } - - 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_DRAW_CONTROL_BORDER - void Window::SetDebugDrawControlBorder(const bool value) - { - if (debug_draw_control_border_ != value) - { - debug_draw_control_border_ = value; - Repaint(); - } - } -#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::OnDestroyInternal() { - Application::GetInstance()->GetWindowManager()->UnregisterWindow(hwnd_); - hwnd_ = nullptr; - } - - void Window::OnPaintInternal() { - render_target_->SetAsTarget(); - - auto device_context = render_target_->GetD2DDeviceContext(); - - device_context->BeginDraw(); - - //Clear the background. - device_context->Clear(D2D1::ColorF(D2D1::ColorF::White)); - - Draw(device_context.Get()); - - ThrowIfFailed( - device_context->EndDraw(), "Failed to draw window." - ); - - render_target_->Present(); - - ValidateRect(hwnd_, nullptr); - } - - void Window::OnResizeInternal(int new_width, int new_height) { - render_target_->ResizeBuffer(new_width, new_height); - Relayout(); - } - - void Window::OnSetFocusInternal() - { - window_focus_ = true; - DispatchEvent(focus_control_, &Control::RaiseGetFocusEvent, nullptr, true); - } - - void Window::OnKillFocusInternal() - { - window_focus_ = false; - DispatchEvent(focus_control_, &Control::RaiseLoseFocusEvent, 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::RaiseMouseMoveEvent, nullptr, dip_point); - } - else - { - DispatchMouseHoverControlChangeEvent(old_control_mouse_hover, new_control_mouse_hover, dip_point); - DispatchEvent(new_control_mouse_hover, &Control::RaiseMouseMoveEvent, nullptr, dip_point); - } - } - - void Window::OnMouseLeaveInternal() - { - DispatchEvent(mouse_hover_control_, &Control::RaiseMouseLeaveEvent, nullptr); - mouse_hover_control_ = nullptr; - } - - void Window::OnMouseDownInternal(MouseButton button, POINT point) - { - const auto dip_point = PiToDip(point); - - Control* control; - - if (mouse_capture_control_) - control = mouse_capture_control_; - else - control = HitTest(dip_point); - - DispatchEvent(control, &Control::RaiseMouseDownEvent, nullptr, dip_point, button); - } - - void Window::OnMouseUpInternal(MouseButton button, POINT point) - { - const auto dip_point = PiToDip(point); - - Control* control; - - if (mouse_capture_control_) - control = mouse_capture_control_; - else - control = HitTest(dip_point); - - DispatchEvent(control, &Control::RaiseMouseUpEvent, nullptr, dip_point, button); - } - - void Window::OnKeyDownInternal(int virtual_code) - { - DispatchEvent(focus_control_, &Control::RaiseKeyDownEvent, nullptr, virtual_code); - } - - void Window::OnKeyUpInternal(int virtual_code) - { - DispatchEvent(focus_control_, &Control::RaiseKeyUpEvent, nullptr, virtual_code); - } - - void Window::OnCharInternal(wchar_t c) - { - DispatchEvent(focus_control_, &Control::RaiseCharEvent, nullptr, c); - } - - 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::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::RaiseMouseLeaveEvent, lowest_common_ancestor); // dispatch mouse leave event. - if (new_control != nullptr) - DispatchEvent(new_control, &Control::RaiseMouseEnterEvent, lowest_common_ancestor, point); // dispatch mouse enter event. - } - } - } -} diff --git a/CruUI/ui/window.h b/CruUI/ui/window.h deleted file mode 100644 index 40d81a06..00000000 --- a/CruUI/ui/window.h +++ /dev/null @@ -1,276 +0,0 @@ -#pragma once - -#include "system_headers.h" -#include -#include -#include - -#include "control.h" -#include "events/ui_event.h" - -namespace cru { - namespace graph { - class WindowRenderTarget; - } - - namespace 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: - WindowManager(); - WindowManager(const WindowManager& other) = delete; - WindowManager(WindowManager&& other) = delete; - WindowManager& operator=(const WindowManager& other) = delete; - WindowManager& operator=(WindowManager&& other) = delete; - ~WindowManager() override = default; - - - //Get the general window class for creating ordinary window. - WindowClass* GetGeneralWindowClass() const - { - return general_window_class_.get(); - } - - //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); - - //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); - - //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); - - Vector GetAllWindows() const; - - private: - std::unique_ptr general_window_class_; - std::map window_map_; - }; - - - - class Window : public Control - { - friend class WindowManager; - public: - Window(); - Window(const Window& other) = delete; - Window(Window&& other) = delete; - Window& operator=(const Window& other) = delete; - Window& operator=(Window&& other) = delete; - ~Window() override; - - public: - //*************** region: handle *************** - - //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 *************** - - //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 Repaint() override; - - //Show the window. - void Show(); - - //Hide thw window. - void Hide(); - - //Get the client size. - Size GetClientSize(); - - //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(); - - //Set the rect of the window containing frame. - //The lefttop of the rect is relative to screen lefttop. - void SetWindowRect(const Rect& rect); - - //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); - - Point GetMousePosition(); - - //*************** region: position and size *************** - - //Always return (0, 0) for a window. - Point GetPositionRelative() override final; - - //This method has no effect for a window. - void SetPositionRelative(const Point& position) override final; - - //Get the size of client area for a window. - Size GetSize() override final; - - //This method has no effect for a window. Use SetClientSize instead. - void SetSize(const Size& size) override final; - - - //*************** region: features *************** - - //Refresh control list. - //It should be invoked every time a control is added or removed from the tree. - void RefreshControlList(); - - //Get the most top control at "point". - Control* HitTest(const Point& point); - - - //*************** region: focus *************** - - //Request focus for specified control. - bool RequestFocusFor(Control* control); - - //Get the control that has focus. - Control* GetFocusControl(); - - - //*************** region: mouse capture *************** - - Control* CaptureMouseFor(Control* control); - Control* ReleaseCurrentMouseCapture(); - - //*************** region: debug *************** -#ifdef CRU_DEBUG_DRAW_CONTROL_BORDER - bool GetDebugDrawControlBorder() const - { - return debug_draw_control_border_; - } - - void SetDebugDrawControlBorder(bool value); -#endif - - public: - //*************** region: events *************** - events::UiEvent activated_event; - events::UiEvent deactivated_event; - - events::WindowNativeMessageEvent native_message_event; - - private: - //*************** region: native operations *************** - - //Get the client rect in pixel. - RECT GetClientRectPixel(); - - bool IsMessageInQueue(UINT message); - - - //*************** 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 OnKeyDownInternal(int virtual_code); - void OnKeyUpInternal(int virtual_code); - void OnCharInternal(wchar_t c); - - void OnActivatedInternal(); - void OnDeactivatedInternal(); - - //*************** region: event dispatcher helper *************** - - template - using EventMethod = void (Control::*)(EventArgs&); - - // Dispatch the event. - // - // This will invoke the "event_method" of the control and its parent and parent's - // parent ... (until "last_receiver" if it's not nullptr) with appropriate args. - // - // 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 - void DispatchEvent(Control* original_sender, EventMethod event_method, Control* last_receiver, Args&&... args) - { - auto control = original_sender; - while (control != nullptr && control != last_receiver) - { - EventArgs event_args(control, original_sender, std::forward(args)...); - (control->*event_method)(event_args); - control = control->GetParent(); - } - } - - void DispatchMouseHoverControlChangeEvent(Control* old_control, Control * new_control, const Point& point); - - private: - HWND hwnd_ = nullptr; - std::shared_ptr render_target_{}; - - std::list control_list_{}; - - Control* mouse_hover_control_ = nullptr; - - bool window_focus_ = false; - Control* focus_control_ = this; // "focus_control_" can't be nullptr - - Control* mouse_capture_control_ = nullptr; - -#ifdef CRU_DEBUG_DRAW_CONTROL_BORDER - bool debug_draw_control_border_ = false; -#endif - }; - } -} diff --git a/script/build.ps1 b/script/build.ps1 index 125c305c..0a72d30c 100644 --- a/script/build.ps1 +++ b/script/build.ps1 @@ -1,8 +1,12 @@ +function CheckLastExitCode { + if ($LASTEXITCODE -ne 0) + { + exit $LASTEXITCODE + } +} + vcpkg.exe install folly:x64-windows fmt:x64-windows +& CheckLastExitCode -cd ..\CruUI -Remove-Item build -Recurse -ErrorAction Ignore -New-Item build -ItemType Directory -cd .\build -cmake -G "Visual Studio 15 2017 Win64" .. -cmake --build . +& "C:\Program Files (x86)\Microsoft Visual Studio\2017\Community\MSBuild\15.0\Bin\MSBuild.exe" CruUI.sln /target:Rebuild /p:Platform=x64 /p:Configuration=Debug +& CheckLastExitCode diff --git a/src/application.cpp b/src/application.cpp new file mode 100644 index 00000000..af38116f --- /dev/null +++ b/src/application.cpp @@ -0,0 +1,145 @@ +#include "application.h" + +#include + +#include "exception.h" +#include "timer.h" +#include "ui/window.h" +#include "graph/graph.h" +#include "ui/animations/animation.h" + +namespace cru { + constexpr auto god_window_class_name = L"GodWindowClass"; + constexpr int invoke_later_message_id = WM_USER + 2000; + + + LRESULT 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) : application_(application) + { + const auto h_instance = application->GetInstanceHandle(); + + god_window_class_ = std::make_unique(god_window_class_name, GodWndProc, h_instance); + + hwnd_ = CreateWindowEx(0, + god_window_class_name, + L"", 0, + CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, + nullptr, nullptr, h_instance, nullptr + ); + + if (hwnd_ == nullptr) + throw std::runtime_error("Failed to create window."); + } + + GodWindow::~GodWindow() + { + ::DestroyWindow(hwnd_); + } + + std::optional 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(w_param); + (*p_action)(); + delete p_action; + return 0; + } + case WM_TIMER: + { + const auto action = application_->GetTimerManager()->GetAction(static_cast(w_param)); + if (action) + { + (*action)(); + return 0; + } + break; + } + default: + return std::nullopt; + } + return std::nullopt; + } + + + + Application* Application::instance_ = nullptr; + + Application * Application::GetInstance() { + return instance_; + } + + Application::Application(HINSTANCE h_instance) + : h_instance_(h_instance) { + + if (instance_) + throw std::runtime_error("A application instance already exists."); + + instance_ = this; + + window_manager_ = std::make_unique(); + graph_manager_ = std::make_unique(); + timer_manager_ = std::make_unique(); + animation_manager_ = std::make_unique(); + + god_window_ = std::make_unique(this); + +#ifdef CRU_DEBUG_DRAW_CONTROL_BORDER + debug_border_brush_ = graph::CreateSolidBrush(D2D1::ColorF(D2D1::ColorF::Crimson)); +#endif + + 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; + } + + Application::~Application() + { + animation_manager_.reset(); + instance_ = nullptr; + } + + int Application::Run() + { + MSG msg; + + while (GetMessage(&msg, nullptr, 0, 0)) + { + TranslateMessage(&msg); + DispatchMessage(&msg); + } + + return static_cast(msg.wParam); + } + + void Application::Quit(const int quit_code) { + ::PostQuitMessage(quit_code); + } + + void InvokeLater(InvokeLaterAction&& action) { + //copy the action to a safe place + auto p_action_copy = new InvokeLaterAction(std::move(action)); + + if (PostMessageW(Application::GetInstance()->GetGodWindow()->GetHandle(), invoke_later_message_id, reinterpret_cast(p_action_copy), 0) == 0) + throw Win32Error(::GetLastError(), "InvokeLater failed to post message."); + } +} diff --git a/src/application.h b/src/application.h new file mode 100644 index 00000000..106e7171 --- /dev/null +++ b/src/application.h @@ -0,0 +1,141 @@ +#pragma once + +#include "system_headers.h" +#include +#include + +#include "base.h" + +namespace cru +{ + class Application; + + namespace ui + { + class WindowClass; + class WindowManager; + + namespace animations::details + { + class AnimationManager; + } + } + + namespace graph + { + class GraphManager; + } + + class TimerManager; + + struct CaretInfo + { + std::chrono::milliseconds caret_blink_duration; + float half_caret_width; + }; + + 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 HandleGodWindowMessage(HWND hwnd, int msg, WPARAM w_param, LPARAM l_param); + + private: + Application* application_; + std::unique_ptr 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); + + ui::WindowManager* GetWindowManager() const + { + return window_manager_.get(); + } + + graph::GraphManager* GetGraphManager() const + { + return graph_manager_.get(); + } + + TimerManager* GetTimerManager() const + { + return timer_manager_.get(); + } + + ui::animations::details::AnimationManager* GetAnimationManager() const + { + return animation_manager_.get(); + } + + HINSTANCE GetInstanceHandle() const + { + return h_instance_; + } + + GodWindow* GetGodWindow() const + { + return god_window_.get(); + } + + CaretInfo GetCaretInfo() const + { + return caret_info_; + } + +#ifdef CRU_DEBUG_DRAW_CONTROL_BORDER + Microsoft::WRL::ComPtr GetDebugBorderBrush() const + { + return debug_border_brush_; + } +#endif + + private: + HINSTANCE h_instance_; + + std::unique_ptr window_manager_; + std::unique_ptr graph_manager_; + std::unique_ptr timer_manager_; + std::unique_ptr animation_manager_; + + std::unique_ptr god_window_; + +#ifdef CRU_DEBUG_DRAW_CONTROL_BORDER + Microsoft::WRL::ComPtr debug_border_brush_; +#endif + + CaretInfo caret_info_; + }; + + + using InvokeLaterAction = Function; + void InvokeLater(InvokeLaterAction&& action); +} diff --git a/src/base.cpp b/src/base.cpp new file mode 100644 index 00000000..f5868170 --- /dev/null +++ b/src/base.cpp @@ -0,0 +1,20 @@ +#include "base.h" + +#include "system_headers.h" +#include "exception.h" + +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(result.capacity()), nullptr, nullptr) == 0) + throw Win32Error(::GetLastError(), "Failed to convert wide string to UTF-8."); + return result; + } +} diff --git a/src/base.h b/src/base.h new file mode 100644 index 00000000..7ef78014 --- /dev/null +++ b/src/base.h @@ -0,0 +1,107 @@ +#pragma once + +// ReSharper disable once CppUnusedIncludeDirective +#include "global_macros.h" + + +#ifdef CRU_DEBUG +#include +#include +#else +#include +#include +#endif + +#include + +#include +#include +#include +#include +#include +#include + +namespace cru +{ + template struct is_shared_ptr : std::false_type {}; + template struct is_shared_ptr> : std::true_type {}; + template constexpr bool is_shared_ptr_v = is_shared_ptr::value; + + enum class FlowControl + { + Continue, + Break + }; + +#ifdef CRU_DEBUG + using String = std::wstring; + using MultiByteString = std::string; +#else + using String = folly::basic_fbstring; + using MultiByteString = folly::fbstring; +#endif + + using StringView = std::wstring_view; + using MultiByteStringView = std::string_view; + + template + using Function = folly::Function; + + template + using FunctionPtr = std::shared_ptr>; + + using Action = Function; + using ActionPtr = FunctionPtr; + + template + Type CreatePtr(Args&&... args) + { + static_assert(is_shared_ptr_v); + return std::make_shared(std::forward(args)...); + } + + inline ActionPtr CreateActionPtr(Action&& action) + { + return std::make_shared(std::move(action)); + } + +#ifdef CRU_DEBUG + template + using Vector = std::vector; +#else + template + using Vector = folly::fbvector; +#endif + + using FloatSecond = std::chrono::duration; + + 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."); + } + + struct ICancelable : virtual Interface + { + virtual void Cancel() = 0; + }; + + using CancelablePtr = std::shared_ptr; + + MultiByteString ToUtf8String(const StringView& string); +} diff --git a/src/cru_event.h b/src/cru_event.h new file mode 100644 index 00000000..d0a7eb82 --- /dev/null +++ b/src/cru_event.h @@ -0,0 +1,91 @@ +#pragma once + +#include +#include +#include +#include + +#include "base.h" + +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 + class Event + { + public: + static_assert(std::is_base_of_v, + "TArgsType must be subclass of BasicEventArgs."); + + + using ArgsType = TArgsType; + using EventHandler = Function; + using EventHandlerPtr = std::shared_ptr; + + Event() = default; + Event(const Event&) = delete; + Event& operator = (const Event&) = delete; + Event(Event&&) = delete; + Event& operator = (Event&&) = delete; + ~Event() = default; + + //Create a EventHandlerPtr from the given handler, + //add it to list and return it. + EventHandlerPtr AddHandler(EventHandler&& handler) + { + EventHandlerPtr ptr = std::make_shared(std::move(handler)); + handlers_.push_back(ptr); + return ptr; + } + + void AddHandler(EventHandlerPtr handler) { + handlers_.push_back(handler); + } + + void RemoveHandler(EventHandlerPtr handler) { + auto find_result = std::find(handlers_.cbegin(), handlers_.cend(), handler); + if (find_result != handlers_.cend()) + handlers_.erase(find_result); + } + + void Raise(ArgsType& args) { + for (auto ptr : handlers_) + (*ptr)(args); + } + + bool IsNoHandler() const + { + return handlers_.empty(); + } + + private: + std::list handlers_; + }; +} diff --git a/src/debug_base.h b/src/debug_base.h new file mode 100644 index 00000000..7669b9df --- /dev/null +++ b/src/debug_base.h @@ -0,0 +1,45 @@ +#pragma once + + +#include "system_headers.h" +#include +#include +#include + +#include "base.h" + +namespace cru::debug +{ +#ifdef CRU_DEBUG + inline void DebugTime(Function&& 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(after - before); + OutputDebugStringW(fmt::format(L"{}: {}ms.\n", hint_message, duration.count()).c_str()); + } + + template + TReturn DebugTime(Function&& 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(after - before); + OutputDebugStringW(fmt::format(L"{}: {}ms.\n", hint_message, duration.count()).c_str()); + return std::move(result); + } +#else + inline void DebugTime(Function&& action, const StringView& hint_message) + { + action(); + } + + template + TReturn DebugTime(Function&& action, const StringView& hint_message) + { + return action(); + } +#endif +} diff --git a/src/exception.cpp b/src/exception.cpp new file mode 100644 index 00000000..a1d59ceb --- /dev/null +++ b/src/exception.cpp @@ -0,0 +1,34 @@ +#include "exception.h" + +#include + +namespace cru +{ + inline std::string HResultMakeMessage(HRESULT h_result, std::optional message) + { + if (message.has_value()) + return fmt::format("An HResultError is thrown. HRESULT: {:#08x}.\nAdditional message: {}\n", h_result, message.value()); + else + return fmt::format("An HResultError is thrown. HRESULT: {:#08x}.\n", h_result); + } + + HResultError::HResultError(HRESULT h_result, std::optional additional_message) + : runtime_error(HResultMakeMessage(h_result, std::nullopt)), h_result_(h_result) + { + + } + + inline std::string Win32MakeMessage(DWORD error_code, std::optional message) + { + if (message.has_value()) + return fmt::format("Last error code: {:#04x}.\nAdditional message: {}\n", error_code, message.value()); + else + return fmt::format("Last error code: {:#04x}.\n", error_code); + } + + Win32Error::Win32Error(DWORD error_code, std::optional additional_message) + : runtime_error(Win32MakeMessage(error_code, std::nullopt)), error_code_(error_code) + { + + } +} diff --git a/src/exception.h b/src/exception.h new file mode 100644 index 00000000..2817f261 --- /dev/null +++ b/src/exception.h @@ -0,0 +1,58 @@ +#pragma once + +#include "system_headers.h" +#include +#include + +#include "base.h" + + +namespace cru { + class HResultError : public std::runtime_error + { + public: + explicit HResultError(HRESULT h_result, std::optional 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 std::string& message) { + if (FAILED(h_result)) + throw HResultError(h_result, message); + } + + class Win32Error : public std::runtime_error + { + public: + explicit Win32Error(DWORD error_code, std::optional 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_; + }; +} diff --git a/src/global_macros.h b/src/global_macros.h new file mode 100644 index 00000000..696c5d2b --- /dev/null +++ b/src/global_macros.h @@ -0,0 +1,11 @@ +#pragma once + +#ifdef _DEBUG +#define CRU_DEBUG +#endif + +#define GLOG_NO_ABBREVIATED_SEVERITIES + +#ifdef CRU_DEBUG +#define CRU_DEBUG_DRAW_CONTROL_BORDER +#endif diff --git a/src/graph/graph.cpp b/src/graph/graph.cpp new file mode 100644 index 00000000..30b51413 --- /dev/null +++ b/src/graph/graph.cpp @@ -0,0 +1,245 @@ +#include "graph.h" + +#include "application.h" +#include "exception.h" + +namespace cru { + namespace 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 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 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()->GetGraphManager(); + } + + GraphManager::GraphManager() + { + UINT creation_flags = D3D11_CREATE_DEVICE_BGRA_SUPPORT; + +#ifdef _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 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 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(dwrite_factory_.GetAddressOf()) + )); + + dwrite_factory_->GetSystemFontCollection(&dwrite_system_font_collection_); + } + + GraphManager::~GraphManager() + { + + } + + std::shared_ptr GraphManager::CreateWindowRenderTarget(HWND hwnd) + { + return std::make_shared(this, hwnd); + } + + Dpi GraphManager::GetDpi() const + { + Dpi dpi; + d2d1_factory_->GetDesktopDpi(&dpi.x, &dpi.y); + return dpi; + } + + void GraphManager::ReloadSystemMetrics() + { + ThrowIfFailed( + d2d1_factory_->ReloadSystemMetrics() + ); + } + + ComPtr CreateSolidBrush(const D2D1_COLOR_F& color) + { + const auto device_context = GraphManager::GetInstance()->GetD2D1DeviceContext(); + ComPtr solid_color_brush; + device_context->CreateSolidColorBrush(color, &solid_color_brush); + return solid_color_brush; + } + + ComPtr CreateDefaultTextFormat() + { + const auto dwrite_factory = GraphManager::GetInstance()->GetDWriteFactory(); + + ComPtr 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; + } + } +} diff --git a/src/graph/graph.h b/src/graph/graph.h new file mode 100644 index 00000000..0f1d29d1 --- /dev/null +++ b/src/graph/graph.h @@ -0,0 +1,180 @@ +#pragma once + +#include "system_headers.h" +#include + +#include "base.h" +#include "application.h" + + +namespace cru +{ + namespace 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 GetD2DDeviceContext() const; + + //Get the target bitmap which can be set as the ID2D1DeviceContext's target. + Microsoft::WRL::ComPtr 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 dxgi_swap_chain_; + Microsoft::WRL::ComPtr target_bitmap_; + }; + + struct Dpi + { + float x; + float y; + }; + + class GraphManager : public Object + { + public: + static GraphManager* GetInstance(); + + public: + GraphManager(); + 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 GetD2D1Factory() const + { + return d2d1_factory_; + } + + Microsoft::WRL::ComPtr GetD2D1DeviceContext() const + { + return d2d1_device_context_; + } + + Microsoft::WRL::ComPtr GetD3D11Device() const + { + return d3d11_device_; + } + + Microsoft::WRL::ComPtr GetDxgiFactory() const + { + return dxgi_factory_; + } + + Microsoft::WRL::ComPtr GetDWriteFactory() const + { + return dwrite_factory_; + } + + + //Create a window render target with the HWND. + std::shared_ptr CreateWindowRenderTarget(HWND hwnd); + + //Get the desktop dpi. + Dpi GetDpi() const; + + //Reload system metrics including desktop dpi. + void ReloadSystemMetrics(); + + Microsoft::WRL::ComPtr GetSystemFontCollection() const + { + return dwrite_system_font_collection_.Get(); + } + + private: + Microsoft::WRL::ComPtr d3d11_device_; + Microsoft::WRL::ComPtr d3d11_device_context_; + Microsoft::WRL::ComPtr d2d1_factory_; + Microsoft::WRL::ComPtr d2d1_device_; + Microsoft::WRL::ComPtr d2d1_device_context_; + Microsoft::WRL::ComPtr dxgi_factory_; + + Microsoft::WRL::ComPtr dwrite_factory_; + Microsoft::WRL::ComPtr dwrite_system_font_collection_; + }; + + inline int DipToPixelInternal(const float dip, const float dpi) + { + return static_cast(dip * dpi / 96.0f); + } + + inline int DipToPixelX(const float dip_x) + { + return DipToPixelInternal(dip_x, Application::GetInstance()->GetGraphManager()->GetDpi().x); + } + + inline int DipToPixelY(const float dip_y) + { + return DipToPixelInternal(dip_y, Application::GetInstance()->GetGraphManager()->GetDpi().y); + } + + inline float DipToPixelInternal(const int pixel, const float dpi) + { + return static_cast(pixel) * 96.0f / dpi; + } + + inline float PixelToDipX(const int pixel_x) + { + return DipToPixelInternal(pixel_x, Application::GetInstance()->GetGraphManager()->GetDpi().x); + } + + inline float PixelToDipY(const int pixel_y) + { + return DipToPixelInternal(pixel_y, Application::GetInstance()->GetGraphManager()->GetDpi().y); + } + + Microsoft::WRL::ComPtr WindowRenderTarget::GetD2DDeviceContext() const + { + return graph_manager_->GetD2D1DeviceContext(); + } + + Microsoft::WRL::ComPtr CreateSolidBrush(const D2D1_COLOR_F& color); + Microsoft::WRL::ComPtr CreateDefaultTextFormat(); + + inline void WithTransform(ID2D1DeviceContext* device_context, const D2D1_MATRIX_3X2_F matrix, Function&& 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); + } + } +} diff --git a/src/main.cpp b/src/main.cpp new file mode 100644 index 00000000..67b35406 --- /dev/null +++ b/src/main.cpp @@ -0,0 +1,150 @@ +#include "application.h" +#include "ui/window.h" +#include "ui/controls/linear_layout.h" +#include "ui/controls/text_block.h" +#include "ui/controls/toggle_button.h" +#include "ui/controls/button.h" +#include "ui/controls/margin_container.h" +#include "ui/events/ui_event.h" +#include "ui/controls/text_box.h" + + +using cru::String; +using cru::Application; +using cru::ui::Window; +using cru::ui::Alignment; +using cru::ui::LayoutSideParams; +using cru::ui::Thickness; +using cru::ui::CreateWithLayout; +using cru::ui::controls::LinearLayout; +using cru::ui::controls::TextBlock; +using cru::ui::controls::ToggleButton; +using cru::ui::controls::Button; +using cru::ui::controls::MarginContainer; +using cru::ui::controls::TextBox; + +int APIENTRY wWinMain( + HINSTANCE hInstance, + HINSTANCE hPrevInstance, + LPWSTR lpCmdLine, + int nCmdShow) { + + Application application(hInstance); + Window window; + /* + 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 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 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.Repaint(); + + auto task = cru::SetInterval(0.5, [&window]() { + window.Repaint(); + }); + + cru::SetTimeout(4, [task]() { + task->Cancel(); + task->Cancel(); // test for idempotency. + }); + }); + */ + + /* + //test 2 + + const auto layout = CreateWithLayout(LayoutSideParams::Exactly(500), LayoutSideParams::Content()); + + layout->mouse_click_event.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(LayoutSideParams::Content(Alignment::End), LayoutSideParams::Content(), LinearLayout::Orientation::Horizontal); + + inner_layout->AddChild(TextBlock::Create(L"Toggle debug border")); + + const auto toggle_button = ToggleButton::Create(); + toggle_button->toggle_event.AddHandler([&window](cru::ui::events::ToggleEventArgs& args) + { + window.SetDebugDrawControlBorder(args.GetNewState()); + }); + + inner_layout->AddChild(toggle_button); + layout->AddChild(inner_layout); + } + + { + const auto button = Button::Create(); + button->AddChild(MarginContainer::Create(Thickness(20, 5), { TextBlock::Create(L"button") })); + layout->AddChild(button); + } + + { + const auto text_block = CreateWithLayout(LayoutSideParams::Exactly(200), LayoutSideParams::Exactly(80), L"Hello World!!!"); + + text_block->mouse_click_event.AddHandler([layout](cru::ui::events::MouseButtonEventArgs& args) + { + layout->AddChild(TextBlock::Create(L"Hello world is clicked!")); + }); + + layout->AddChild(text_block); + } + + { + const auto text_block = CreateWithLayout(LayoutSideParams::Stretch(), LayoutSideParams::Stretch(), L"This is a very very very very very long sentence!!!"); + text_block->SetSelectable(true); + layout->AddChild(text_block); + } + + layout->AddChild(CreateWithLayout(LayoutSideParams::Content(Alignment::Start), LayoutSideParams::Content(), L"This is a little short sentence!!!")); + layout->AddChild(CreateWithLayout(LayoutSideParams::Content(Alignment::End), LayoutSideParams::Stretch(), L"By crupest!!!")); + + + window.AddChild(layout); + */ + + window.AddChild(CreateWithLayout(LayoutSideParams::Stretch(), LayoutSideParams::Stretch())); + + window.Show(); + + return application.Run(); +} diff --git a/src/system_headers.h b/src/system_headers.h new file mode 100644 index 00000000..99c091e1 --- /dev/null +++ b/src/system_headers.h @@ -0,0 +1,21 @@ +#pragma once + + +//include system headers + +#define NOMINMAX +#define WIN32_LEAN_AND_MEAN +#include +#include + +#pragma comment(lib, "D3D11.lib") +#include + +#pragma comment(lib, "D2d1.lib") +#include + +#pragma comment(lib, "DWrite.lib") +#include + +#include +#include diff --git a/src/timer.cpp b/src/timer.cpp new file mode 100644 index 00000000..ce800eee --- /dev/null +++ b/src/timer.cpp @@ -0,0 +1,79 @@ +#include "timer.h" + +namespace cru +{ + UINT_PTR TimerManager::CreateTimer(const UINT milliseconds, const bool loop, ActionPtr action) + { + const auto id = current_count_++; + ::SetTimer(Application::GetInstance()->GetGodWindow()->GetHandle(), id, milliseconds, nullptr); + if (loop) + map_[id] = std::move(action); + else + map_[id] = CreateActionPtr([this, action, id]() mutable { + (*action)(); + this->KillTimer(id); + }); + 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); + // When timer is killed in tick action, we need to retain the action itself until the action finishes, so InvokeLater! + InvokeLater([=] + { + map_.erase(find_result); + }); + } + } + + ActionPtr TimerManager::GetAction(const UINT_PTR id) + { + const auto find_result = map_.find(id); + if (find_result == map_.cend()) + return nullptr; + return find_result->second; + } + + class TimerTaskImpl : public virtual ICancelable + { + public: + explicit TimerTaskImpl(UINT_PTR id); + TimerTaskImpl(const TimerTaskImpl& other) = delete; + TimerTaskImpl(TimerTaskImpl&& other) = delete; + TimerTaskImpl& operator=(const TimerTaskImpl& other) = delete; + TimerTaskImpl& operator=(TimerTaskImpl&& other) = delete; + ~TimerTaskImpl() override = default; + + void Cancel() override; + + private: + UINT_PTR id_; + }; + + TimerTaskImpl::TimerTaskImpl(const UINT_PTR id) + : id_(id) + { + + } + + void TimerTaskImpl::Cancel() + { + TimerManager::GetInstance()->KillTimer(id_); + } + + TimerTask SetTimeout(std::chrono::milliseconds milliseconds, ActionPtr action) + { + auto id = TimerManager::GetInstance()->CreateTimer(static_cast(milliseconds.count()), false, std::move(action)); + return std::make_shared(id); + } + + TimerTask SetInterval(std::chrono::milliseconds milliseconds, ActionPtr action) + { + auto id = TimerManager::GetInstance()->CreateTimer(static_cast(milliseconds.count()), true, std::move(action)); + return std::make_shared(id); + } +} diff --git a/src/timer.h b/src/timer.h new file mode 100644 index 00000000..9884e46a --- /dev/null +++ b/src/timer.h @@ -0,0 +1,43 @@ +#pragma once + + +#include "system_headers.h" +#include +#include +#include + +#include "base.h" +#include "application.h" + +namespace cru +{ + class TimerManager : public Object + { + public: + static TimerManager* GetInstance() + { + return Application::GetInstance()->GetTimerManager(); + } + + public: + TimerManager() = default; + 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, ActionPtr action); + void KillTimer(UINT_PTR id); + ActionPtr GetAction(UINT_PTR id); + + private: + std::map map_{}; + UINT_PTR current_count_ = 0; + }; + + using TimerTask = CancelablePtr; + + TimerTask SetTimeout(std::chrono::milliseconds milliseconds, ActionPtr action); + TimerTask SetInterval(std::chrono::milliseconds milliseconds, ActionPtr action); +} diff --git a/src/ui/animations/animation.cpp b/src/ui/animations/animation.cpp new file mode 100644 index 00000000..9d05860a --- /dev/null +++ b/src/ui/animations/animation.cpp @@ -0,0 +1,190 @@ +#include "animation.h" + +#include +#include + +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( + String tag, + AnimationTimeUnit duration, + Vector step_handlers, + Vector start_handlers, + Vector finish_handlers, + Vector cancel_handlers, + AnimationDelegatePtr 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 tag_; + } + + private: + const String tag_; + const AnimationTimeUnit duration_; + Vector step_handlers_; + Vector start_handlers_; + Vector finish_handlers_; + Vector cancel_handlers_; + AnimationDelegatePtr delegate_; + + AnimationTimeUnit current_time_ = AnimationTimeUnit::zero(); + }; + + AnimationManager::AnimationManager() + : timer_action_(CreateActionPtr([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(); + })) + { + + } + + AnimationManager::~AnimationManager() + { + KillTimer(); + } + + AnimationDelegatePtr AnimationManager::CreateAnimation(String tag, AnimationTimeUnit duration, + Vector step_handlers, Vector start_handlers, + Vector finish_handlers, Vector cancel_handlers) + { + if (animations_.empty()) + SetTimer(); + + auto delegate = std::make_shared(tag); + + animations_[tag] = std::make_unique(tag, duration, std::move(step_handlers), std::move(start_handlers), std::move(finish_handlers), std::move(cancel_handlers), 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_ == nullptr) + timer_ = SetInterval(std::chrono::duration_cast(frame_step_time), timer_action_); + } + + void AnimationManager::KillTimer() + { + if (timer_ != nullptr) + { + timer_->Cancel(); + timer_ = nullptr; + } + } + + Animation::Animation( + String tag, + AnimationTimeUnit duration, + Vector step_handlers, + Vector start_handlers, + Vector finish_handlers, + Vector cancel_handlers, + AnimationDelegatePtr delegate + ) : tag_(std::move(tag)), duration_(duration), + step_handlers_(std::move(step_handlers)), + start_handlers_(std::move(start_handlers)), + finish_handlers_(std::move(finish_handlers)), + cancel_handlers_(std::move(cancel_handlers)), + delegate_(std::move(delegate)) + { + + } + + Animation::~Animation() + { + if (current_time_ < duration_) + for (auto& handler : cancel_handlers_) + (*handler)(); + } + + bool Animation::Step(const AnimationTimeUnit time) + { + current_time_ += time; + if (current_time_ > duration_) + { + for (auto& handler : step_handlers_) + (*handler)(delegate_, 1); + for (auto& handler : finish_handlers_) + (*handler)(); + return true; + } + else + { + for (auto& handler : step_handlers_) + (*handler)(delegate_, current_time_ / duration_); + return false; + } + } + + } + + AnimationDelegatePtr AnimationBuilder::Start() const + { + return details::AnimationManager::GetInstance()->CreateAnimation(tag, duration, step_handlers_, start_handlers_, finish_handlers_, cancel_handlers_); + } +} diff --git a/src/ui/animations/animation.h b/src/ui/animations/animation.h new file mode 100644 index 00000000..69b08b0c --- /dev/null +++ b/src/ui/animations/animation.h @@ -0,0 +1,107 @@ +#pragma once + +#include + +#include "base.h" +#include "application.h" +#include "timer.h" + +namespace cru::ui::animations +{ + using AnimationTimeUnit = FloatSecond; + + + using IAnimationDelegate = ICancelable; + using AnimationDelegatePtr = CancelablePtr; + + using AnimationStepHandlerPtr = FunctionPtr; + using AnimationStartHandlerPtr = FunctionPtr; + + + namespace details + { + class Animation; + using AnimationPtr = std::unique_ptr; + + class AnimationManager : public Object + { + public: + static AnimationManager* GetInstance() + { + return Application::GetInstance()->GetAnimationManager(); + } + + public: + AnimationManager(); + AnimationManager(const AnimationManager& other) = delete; + AnimationManager(AnimationManager&& other) = delete; + AnimationManager& operator=(const AnimationManager& other) = delete; + AnimationManager& operator=(AnimationManager&& other) = delete; + ~AnimationManager() override; + + AnimationDelegatePtr CreateAnimation( + String tag, + AnimationTimeUnit duration, + Vector step_handlers, + Vector start_handlers, + Vector finish_handlers, + Vector cancel_handlers + ); + void RemoveAnimation(const String& tag); + + private: + void SetTimer(); + void KillTimer(); + + private: + std::unordered_map animations_; + std::shared_ptr timer_; + ActionPtr timer_action_; + }; + } + + class AnimationBuilder : public Object + { + public: + AnimationBuilder(String tag, const AnimationTimeUnit duration) + : tag(std::move(tag)), duration(duration) + { + + } + + String tag; + AnimationTimeUnit duration; + + AnimationBuilder& AddStepHandler(AnimationStepHandlerPtr handler) + { + step_handlers_.push_back(std::move(handler)); + return *this; + } + + AnimationBuilder& AddStartHandler(AnimationStartHandlerPtr handler) + { + start_handlers_.push_back(std::move(handler)); + return *this; + } + + AnimationBuilder& AddFinishHandler(ActionPtr handler) + { + finish_handlers_.push_back(std::move(handler)); + return *this; + } + + AnimationBuilder& AddCancelHandler(ActionPtr handler) + { + cancel_handlers_.push_back(std::move(handler)); + return *this; + } + + AnimationDelegatePtr Start() const; + + private: + Vector step_handlers_; + Vector start_handlers_; + Vector finish_handlers_; + Vector cancel_handlers_; + }; +} diff --git a/src/ui/control.cpp b/src/ui/control.cpp new file mode 100644 index 00000000..8aec8640 --- /dev/null +++ b/src/ui/control.cpp @@ -0,0 +1,712 @@ +#include "control.h" + +#include +#include +#include + +#include "window.h" +#include "timer.h" +#include "debug_base.h" + +namespace cru { + namespace ui { + using namespace events; + + Control::Control(const bool container) : + is_container_(container) + { + + } + + Control::Control(WindowConstructorTag, Window* window) : Control(true) + { + window_ = window; + } + + Control::~Control() + { + ForeachChild([](auto control) + { + delete control; + }); + } + + void Control::ForeachChild(Function&& predicate) const + { + if (is_container_) + for (const auto child : children_) + predicate(child); + } + + void Control::ForeachChild(Function&& predicate) const + { + if (is_container_) + for (const auto child : children_) + { + if (predicate(child) == FlowControl::Break) + break; + } + } + + void AddChildCheck(Control* control) + { + if (control->GetParent() != nullptr) + throw std::invalid_argument("The control already has a parent."); + + if (dynamic_cast(control)) + throw std::invalid_argument("Can't add a window as child."); + } + + void Control::AddChild(Control* control) + { + ThrowIfNotContainer(); + AddChildCheck(control); + + this->children_.push_back(control); + + control->parent_ = this; + + this->OnAddChild(control); + } + + void Control::AddChild(Control* control, int position) + { + ThrowIfNotContainer(); + AddChildCheck(control); + + if (position < 0 || static_castchildren_.size())>(position) > this->children_.size()) + throw std::invalid_argument("The position is out of range."); + + this->children_.insert(this->children_.cbegin() + position, control); + + control->parent_ = this; + + this->OnAddChild(this); + } + + void Control::RemoveChild(Control* child) + { + ThrowIfNotContainer(); + 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."); + + this->children_.erase(i); + + child->parent_ = nullptr; + + this->OnRemoveChild(this); + } + + void Control::RemoveChild(const int position) + { + ThrowIfNotContainer(); + if (position < 0 || static_castchildren_.size())>(position) >= this->children_.size()) + throw std::invalid_argument("The position is out of range."); + + const auto p = children_.cbegin() + position; + const auto child = *p; + children_.erase(p); + + child->parent_ = nullptr; + + this->OnRemoveChild(child); + } + + Control* Control::GetAncestor() + { + // if attached to window, the window is the ancestor. + if (window_) + return window_; + + // otherwise find the ancestor + auto ancestor = this; + while (const auto parent = ancestor->GetParent()) + ancestor = parent; + return ancestor; + } + + void TraverseDescendantsInternal(Control* control, Function& predicate) + { + predicate(control); + control->ForeachChild([&predicate](Control* c) { + TraverseDescendantsInternal(c, predicate); + }); + } + + void Control::TraverseDescendants(Function&& predicate) + { + if (is_container_) + TraverseDescendantsInternal(this, predicate); + else + predicate(this); + } + + Point Control::GetPositionRelative() + { + return position_; + } + + void Control::SetPositionRelative(const Point & position) + { + if (position != position_) + { + if (old_position_ == position) // if cache has been refreshed and no pending notify + old_position_ = position_; + position_ = position; + LayoutManager::GetInstance()->InvalidateControlPositionCache(this); + if (auto window = GetWindow()) + { + window->Repaint(); + } + } + } + + Size Control::GetSize() + { + return size_; + } + + void Control::SetSize(const Size & size) + { + const auto old_size = size_; + size_ = size; + SizeChangedEventArgs args(this, this, old_size, size); + RaiseSizeChangedEvent(args); + if (auto window = GetWindow()) + window->Repaint(); + } + + Point Control::GetPositionAbsolute() const + { + return position_cache_.lefttop_position_absolute; + } + + Point Control::LocalToAbsolute(const Point& point) const + { + return Point(point.x + position_cache_.lefttop_position_absolute.x, + point.y + position_cache_.lefttop_position_absolute.y); + } + + Point Control::AbsoluteToLocal(const Point & point) const + { + return Point(point.x - position_cache_.lefttop_position_absolute.x, + point.y - position_cache_.lefttop_position_absolute.y); + } + + bool Control::IsPointInside(const Point & point) + { + const auto size = GetSize(); + return point.x >= 0.0f && point.x < size.width && point.y >= 0.0f && point.y < size.height; + } + + void Control::Draw(ID2D1DeviceContext* device_context) + { + D2D1::Matrix3x2F old_transform; + device_context->GetTransform(&old_transform); + + const auto position = GetPositionRelative(); + device_context->SetTransform(old_transform * D2D1::Matrix3x2F::Translation(position.x, position.y)); + + OnDraw(device_context); + DrawEventArgs args(this, this, device_context); + draw_event.Raise(args); + + for (auto child : GetChildren()) + child->Draw(device_context); + + device_context->SetTransform(old_transform); + } + + void Control::Repaint() + { + if (window_ != nullptr) + window_->Repaint(); + } + + 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::Relayout() + { + OnMeasure(GetSize()); + OnLayout(Rect(GetPositionRelative(), GetSize())); + } + + void Control::Measure(const Size& available_size) + { + SetDesiredSize(OnMeasure(available_size)); + } + + void Control::Layout(const Rect& rect) + { + SetPositionRelative(rect.GetLeftTop()); + SetSize(rect.GetSize()); + OnLayout(rect); + } + + Size Control::GetDesiredSize() const + { + return desired_size_; + } + + void Control::SetDesiredSize(const Size& desired_size) + { + desired_size_ = desired_size; + } + + void Control::OnAddChild(Control* child) + { + if (auto window = GetWindow()) + { + child->TraverseDescendants([window](Control* control) { + control->OnAttachToWindow(window); + }); + window->RefreshControlList(); + } + Relayout(); + } + + void Control::OnRemoveChild(Control* child) + { + if (auto window = GetWindow()) + { + child->TraverseDescendants([window](Control* control) { + control->OnDetachToWindow(window); + }); + window->RefreshControlList(); + } + Relayout(); + } + + void Control::OnAttachToWindow(Window* window) + { + window_ = window; + } + + void Control::OnDetachToWindow(Window * window) + { + window_ = nullptr; + } + + void Control::OnDraw(ID2D1DeviceContext * device_context) + { +#ifdef CRU_DEBUG_DRAW_CONTROL_BORDER + if (GetWindow()->GetDebugDrawControlBorder()) + { + auto brush = Application::GetInstance()->GetDebugBorderBrush(); + const auto size = GetSize(); + device_context->DrawRectangle(D2D1::RectF(0, 0, size.width, size.height), brush.Get()); + } +#endif + } + + void Control::OnPositionChanged(PositionChangedEventArgs & args) + { + + } + + void Control::OnSizeChanged(SizeChangedEventArgs & args) + { + } + + void Control::OnPositionChangedCore(PositionChangedEventArgs & args) + { + + } + + void Control::OnSizeChangedCore(SizeChangedEventArgs & args) + { + + } + + void Control::RaisePositionChangedEvent(PositionChangedEventArgs& args) + { + OnPositionChangedCore(args); + OnPositionChanged(args); + position_changed_event.Raise(args); + } + + void Control::RaiseSizeChangedEvent(SizeChangedEventArgs& args) + { + OnSizeChangedCore(args); + OnSizeChanged(args); + size_changed_event.Raise(args); + } + + void Control::OnMouseEnter(MouseEventArgs & args) + { + } + + void Control::OnMouseLeave(MouseEventArgs & args) + { + } + + void Control::OnMouseMove(MouseEventArgs & args) + { + } + + void Control::OnMouseDown(MouseButtonEventArgs & args) + { + } + + void Control::OnMouseUp(MouseButtonEventArgs & args) + { + } + + void Control::OnMouseClick(MouseButtonEventArgs& args) + { + + } + + void Control::OnMouseEnterCore(MouseEventArgs & args) + { + is_mouse_inside_ = true; + } + + void Control::OnMouseLeaveCore(MouseEventArgs & args) + { + is_mouse_inside_ = false; + 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); + } + } + } + + void Control::OnMouseMoveCore(MouseEventArgs & args) + { + + } + + void Control::OnMouseDownCore(MouseButtonEventArgs & args) + { + if (is_focus_on_pressed_ && args.GetSender() == args.GetOriginalSender()) + RequestFocus(); + is_mouse_click_valid_map_[args.GetMouseButton()] = true; + OnMouseClickBegin(args.GetMouseButton()); + } + + void Control::OnMouseUpCore(MouseButtonEventArgs & args) + { + if (is_mouse_click_valid_map_[args.GetMouseButton()]) + { + is_mouse_click_valid_map_[args.GetMouseButton()] = false; + RaiseMouseClickEvent(args); + OnMouseClickEnd(args.GetMouseButton()); + } + } + + void Control::OnMouseClickCore(MouseButtonEventArgs& args) + { + + } + + void Control::RaiseMouseEnterEvent(MouseEventArgs& args) + { + OnMouseEnterCore(args); + OnMouseEnter(args); + mouse_enter_event.Raise(args); + } + + void Control::RaiseMouseLeaveEvent(MouseEventArgs& args) + { + OnMouseLeaveCore(args); + OnMouseLeave(args); + mouse_leave_event.Raise(args); + } + + void Control::RaiseMouseMoveEvent(MouseEventArgs& args) + { + OnMouseMoveCore(args); + OnMouseMove(args); + mouse_move_event.Raise(args); + } + + void Control::RaiseMouseDownEvent(MouseButtonEventArgs& args) + { + OnMouseDownCore(args); + OnMouseDown(args); + mouse_down_event.Raise(args); + } + + void Control::RaiseMouseUpEvent(MouseButtonEventArgs& args) + { + OnMouseUpCore(args); + OnMouseUp(args); + mouse_up_event.Raise(args); + } + + void Control::RaiseMouseClickEvent(MouseButtonEventArgs& args) + { + OnMouseClickCore(args); + OnMouseClick(args); + mouse_click_event.Raise(args); + } + + void Control::OnMouseClickBegin(MouseButton button) + { + + } + + void Control::OnMouseClickEnd(MouseButton button) + { + + } + + void Control::OnKeyDown(KeyEventArgs& args) + { + } + + void Control::OnKeyUp(KeyEventArgs& args) + { + } + + void Control::OnChar(CharEventArgs& args) + { + } + + void Control::OnKeyDownCore(KeyEventArgs& args) + { + } + + void Control::OnKeyUpCore(KeyEventArgs& args) + { + } + + void Control::OnCharCore(CharEventArgs& args) + { + } + + void Control::RaiseKeyDownEvent(KeyEventArgs& args) + { + OnKeyDownCore(args); + OnKeyDown(args); + key_down_event.Raise(args); + } + + void Control::RaiseKeyUpEvent(KeyEventArgs& args) + { + OnKeyUpCore(args); + OnKeyUp(args); + key_up_event.Raise(args); + } + + void Control::RaiseCharEvent(CharEventArgs& args) + { + OnCharCore(args); + OnChar(args); + char_event.Raise(args); + } + + void Control::OnGetFocus(FocusChangeEventArgs& args) + { + + } + + void Control::OnLoseFocus(FocusChangeEventArgs& args) + { + + } + + void Control::OnGetFocusCore(FocusChangeEventArgs& args) + { + + } + + void Control::OnLoseFocusCore(FocusChangeEventArgs& args) + { + + } + + void Control::RaiseGetFocusEvent(FocusChangeEventArgs& args) + { + OnGetFocusCore(args); + OnGetFocus(args); + get_focus_event.Raise(args); + } + + void Control::RaiseLoseFocusEvent(FocusChangeEventArgs& args) + { + OnLoseFocusCore(args); + OnLoseFocus(args); + lose_focus_event.Raise(args); + } + + Size Control::OnMeasure(const Size& available_size) + { + const auto layout_params = GetLayoutParams(); + + if (!layout_params->Validate()) + throw std::runtime_error("LayoutParams is not valid. Please check it."); + + auto&& get_available_length_for_child = [](const LayoutSideParams& layout_length, const float available_length) -> float + { + switch (layout_length.mode) + { + case MeasureMode::Exactly: + { + return std::min(layout_length.length, available_length); + } + case MeasureMode::Stretch: + case MeasureMode::Content: + { + return available_length; + } + default: + UnreachableCode(); + } + }; + + const Size size_for_children(get_available_length_for_child(layout_params->width, available_size.width), + get_available_length_for_child(layout_params->height, available_size.height)); + + auto max_child_size = Size::Zero(); + ForeachChild([&](Control* control) + { + control->Measure(size_for_children); + 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; + }); + + auto&& calculate_final_length = [](const LayoutSideParams& layout_length, const float length_for_children, const float max_child_length) -> float + { + switch (layout_length.mode) + { + case MeasureMode::Exactly: + case MeasureMode::Stretch: + return length_for_children; + case MeasureMode::Content: + return max_child_length; + default: + UnreachableCode(); + } + }; + + return Size( + calculate_final_length(layout_params->width, size_for_children.width, max_child_size.width), + calculate_final_length(layout_params->height, size_for_children.height, max_child_size.height) + ); + } + + void Control::OnLayout(const Rect& rect) + { + ForeachChild([rect](Control* control) + { + const auto layout_params = control->GetLayoutParams(); + const auto size = control->GetDesiredSize(); + + auto&& calculate_anchor = [](const Alignment 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(); + } + }; + + control->Layout(Rect(Point( + calculate_anchor(layout_params->width.alignment, rect.width, size.width), + calculate_anchor(layout_params->height.alignment, rect.height, size.height) + ), size)); + }); + } + + void Control::CheckAndNotifyPositionChanged() + { + if (this->old_position_ != this->position_) + { + PositionChangedEventArgs args(this, this, this->old_position_, this->position_); + this->RaisePositionChangedEvent(args); + this->old_position_ = this->position_; + } + } + + std::list GetAncestorList(Control* control) + { + std::list l; + while (control != nullptr) + { + l.push_front(control); + control = control->GetParent(); + } + return l; + } + + 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; + } + } + + Control * IsAncestorOrDescendant(Control * left, Control * right) + { + //Search up along the trunk from "left". Return if find "right". + auto control = left; + while (control != nullptr) + { + if (control == right) + return control; + control = control->GetParent(); + } + //Search up along the trunk from "right". Return if find "left". + control = right; + while (control != nullptr) + { + if (control == left) + return control; + control = control->GetParent(); + } + return nullptr; + } + } +} diff --git a/src/ui/control.h b/src/ui/control.h new file mode 100644 index 00000000..d6cbae40 --- /dev/null +++ b/src/ui/control.h @@ -0,0 +1,380 @@ +#pragma once + +#include "system_headers.h" +#include +#include +#include +#include +#include + +#include "base.h" +#include "ui_base.h" +#include "layout_base.h" +#include "events/ui_event.h" + +namespace cru +{ + namespace ui + { + class Control; + class Window; + + + //the position cache + struct ControlPositionCache + { + //The lefttop relative to the ancestor. + Point lefttop_position_absolute; + }; + + + class Control : public Object + { + friend class Window; + friend class LayoutManager; + + protected: + struct WindowConstructorTag {}; //Used for constructor for class Window. + + explicit Control(bool container = false); + + // Used only for creating Window. It will set window_ as window. + Control(WindowConstructorTag, Window* window); + + public: + Control(const Control& other) = delete; + Control(Control&& other) = delete; + Control& operator=(const Control& other) = delete; + Control& operator=(Control&& other) = delete; + ~Control() override; + + public: + + //*************** region: tree *************** + + bool IsContainer() const + { + return is_container_; + } + + //Get parent of control, return nullptr if it has no parent. + Control* GetParent() const + { + return parent_; + } + + //Traverse the children + void ForeachChild(Function&& predicate) const; + void ForeachChild(Function&& predicate) const; + + //Return a vector of all children. This function will create a + //temporary copy of vector of children. If you just want to + //traverse all children, just call ForeachChild. + Vector 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); + + //Get the ancestor of the control. + Control* GetAncestor(); + + //Get the window if attached, otherwise, return nullptr. + Window* GetWindow() const + { + return window_; + } + + //Traverse the tree rooted the control including itself. + void TraverseDescendants(Function&& predicate); + + //*************** region: position and size *************** + // Position and size part must be isolated from layout part. + // All the operations in this part must be done independently. + // And layout part must use api of this part. + + //Get the lefttop relative to its parent. + virtual Point GetPositionRelative(); + + //Set the lefttop relative to its parent. + virtual void SetPositionRelative(const Point& position); + + //Get the actual size. + virtual Size GetSize(); + + //Set the actual size directly without re-layout. + virtual void SetSize(const Size& size); + + //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 LocalToAbsolute(const Point& point) const; + + //Absolute point to local point. + Point AbsoluteToLocal(const Point& point) const; + + virtual bool IsPointInside(const Point& point); + + + //*************** region: graphic *************** + + //Draw this control and its child controls. + void Draw(ID2D1DeviceContext* device_context); + + virtual void Repaint(); + + //*************** 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 Relayout(); + + void Measure(const Size& available_size); + + void Layout(const Rect& rect); + + Size GetDesiredSize() const; + + void SetDesiredSize(const Size& desired_size); + + BasicLayoutParams* GetLayoutParams() + { + return &layout_params_; + } + + //*************** region: additional properties *************** + template + std::optional GetAdditionalProperty(const String& key) + { + try + { + const auto find_result = additional_properties_.find(key); + if (find_result != additional_properties_.cend()) + return std::any_cast(find_result->second); + else + return std::nullopt; + } + catch (const std::bad_any_cast&) + { + throw std::runtime_error(fmt::format("Key \"{}\" is not of the type {}.", ToUtf8String(key), typeid(T).name())); + } + } + + template + void SetAdditionalProperty(const String& key, const T& value) + { + additional_properties_[key] = std::make_any(value); + } + + template + void SetAdditionalProperty(const String& key, T&& value) + { + additional_properties_[key] = std::make_any(std::move(value)); + } + + //*************** region: events *************** + //Raised when mouse enter the control. + events::MouseEvent mouse_enter_event; + //Raised when mouse is leave the control. + events::MouseEvent mouse_leave_event; + //Raised when mouse is move in the control. + events::MouseEvent mouse_move_event; + //Raised when a mouse button is pressed in the control. + events::MouseButtonEvent mouse_down_event; + //Raised when a mouse button is released in the control. + events::MouseButtonEvent 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::MouseButtonEvent mouse_click_event; + + events::KeyEvent key_down_event; + events::KeyEvent key_up_event; + events::CharEvent char_event; + + events::FocusChangeEvent get_focus_event; + events::FocusChangeEvent lose_focus_event; + + events::DrawEvent draw_event; + + events::PositionChangedEvent position_changed_event; + events::SizeChangedEvent size_changed_event; + + 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); + + //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); + + virtual void OnDraw(ID2D1DeviceContext* device_context); + + + // For a event, the window event system will first dispatch event to core functions. + // Therefore for particular controls, you should do essential actions in core functions, + // and override version should invoke base version. The base core function + // in "Control" class will call corresponding non-core function and call "Raise" on + // event objects. So user custom actions should be done by overriding non-core function + // and calling the base version is optional. + + //*************** region: position and size event *************** + virtual void OnPositionChanged(events::PositionChangedEventArgs& args); + virtual void OnSizeChanged(events::SizeChangedEventArgs& args); + + virtual void OnPositionChangedCore(events::PositionChangedEventArgs& args); + virtual void OnSizeChangedCore(events::SizeChangedEventArgs& args); + + void RaisePositionChangedEvent(events::PositionChangedEventArgs& args); + void RaiseSizeChangedEvent(events::SizeChangedEventArgs& args); + + //*************** region: mouse event *************** + virtual void OnMouseEnter(events::MouseEventArgs& args); + virtual void OnMouseLeave(events::MouseEventArgs& args); + virtual void OnMouseMove(events::MouseEventArgs& args); + virtual void OnMouseDown(events::MouseButtonEventArgs& args); + virtual void OnMouseUp(events::MouseButtonEventArgs& args); + virtual void OnMouseClick(events::MouseButtonEventArgs& args); + + virtual void OnMouseEnterCore(events::MouseEventArgs& args); + virtual void OnMouseLeaveCore(events::MouseEventArgs& args); + virtual void OnMouseMoveCore(events::MouseEventArgs& args); + virtual void OnMouseDownCore(events::MouseButtonEventArgs& args); + virtual void OnMouseUpCore(events::MouseButtonEventArgs& args); + virtual void OnMouseClickCore(events::MouseButtonEventArgs& args); + + void RaiseMouseEnterEvent(events::MouseEventArgs& args); + void RaiseMouseLeaveEvent(events::MouseEventArgs& args); + void RaiseMouseMoveEvent(events::MouseEventArgs& args); + void RaiseMouseDownEvent(events::MouseButtonEventArgs& args); + void RaiseMouseUpEvent(events::MouseButtonEventArgs& args); + void RaiseMouseClickEvent(events::MouseButtonEventArgs& args); + + virtual void OnMouseClickBegin(MouseButton button); + virtual void OnMouseClickEnd(MouseButton button); + + //*************** region: keyboard event *************** + virtual void OnKeyDown(events::KeyEventArgs& args); + virtual void OnKeyUp(events::KeyEventArgs& args); + virtual void OnChar(events::CharEventArgs& args); + + virtual void OnKeyDownCore(events::KeyEventArgs& args); + virtual void OnKeyUpCore(events::KeyEventArgs& args); + virtual void OnCharCore(events::CharEventArgs& args); + + void RaiseKeyDownEvent(events::KeyEventArgs& args); + void RaiseKeyUpEvent(events::KeyEventArgs& args); + void RaiseCharEvent(events::CharEventArgs& args); + + //*************** region: focus event *************** + virtual void OnGetFocus(events::FocusChangeEventArgs& args); + virtual void OnLoseFocus(events::FocusChangeEventArgs& args); + + virtual void OnGetFocusCore(events::FocusChangeEventArgs& args); + virtual void OnLoseFocusCore(events::FocusChangeEventArgs& args); + + void RaiseGetFocusEvent(events::FocusChangeEventArgs& args); + void RaiseLoseFocusEvent(events::FocusChangeEventArgs& args); + + //*************** region: layout *************** + virtual Size OnMeasure(const Size& available_size); + virtual void OnLayout(const Rect& rect); + + private: + // Only for layout manager to use. + // Check if the old position is updated to current position. + // If not, then a notify of position change and update will + // be done. + void CheckAndNotifyPositionChanged(); + + void ThrowIfNotContainer() const + { + if (!is_container_) + throw std::runtime_error("You can't perform such operation on a non-container control."); + } + + private: + bool is_container_; + + protected: + Window * window_ = nullptr; // protected for Window class to write it as itself in constructor. + + private: + Control * parent_ = nullptr; + Vector children_{}; + + // When position is changed and notification hasn't been + // sent, it will be the old position. When position is changed + // more than once, it will be the oldest position since last + // notification. If notification has been sent, it will be updated + // to position_. + Point old_position_ = Point::Zero(); + Point position_ = Point::Zero(); + Size size_ = Size::Zero(); + + ControlPositionCache position_cache_{}; + + bool is_mouse_inside_ = false; + + std::unordered_map 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(); + + std::unordered_map additional_properties_{}; + + bool is_focus_on_pressed_ = true; + }; + + // Find the lowest common ancestor. + // Return nullptr if "left" and "right" are not in the same tree. + Control* FindLowestCommonAncestor(Control* left, Control* right); + + // Return the ancestor if one control is the ancestor of the other one, otherwise nullptr. + Control* IsAncestorOrDescendant(Control* left, Control* right); + + template + TControl* CreateWithLayout(const LayoutSideParams& width, const LayoutSideParams& height, Args&&... args) + { + static_assert(std::is_base_of_v, "TControl is not a control class."); + TControl* control = TControl::Create(std::forward(args)...); + control->GetLayoutParams()->width = width; + control->GetLayoutParams()->height = height; + return control; + } + } +} diff --git a/src/ui/controls/button.cpp b/src/ui/controls/button.cpp new file mode 100644 index 00000000..b7614f93 --- /dev/null +++ b/src/ui/controls/button.cpp @@ -0,0 +1,33 @@ +#include "button.h" + +#include "graph/graph.h" + +namespace cru::ui::controls +{ + using graph::CreateSolidBrush; + + Button::Button() : Control(true) + { + normal_border_brush_ = CreateSolidBrush(D2D1::ColorF(D2D1::ColorF::RoyalBlue)); + pressed_border_brush_ = CreateSolidBrush(D2D1::ColorF(D2D1::ColorF::MediumBlue)); + current_border_brush_ = normal_border_brush_.Get(); + } + + void Button::OnDraw(ID2D1DeviceContext* device_context) + { + Control::OnDraw(device_context); + device_context->DrawRoundedRectangle(D2D1::RoundedRect(D2D1::RectF(0, 0, GetSize().width, GetSize().height), 6, 6), current_border_brush_, 2); + } + + void Button::OnMouseClickBegin(MouseButton button) + { + current_border_brush_ = pressed_border_brush_.Get(); + Repaint(); + } + + void Button::OnMouseClickEnd(MouseButton button) + { + current_border_brush_ = normal_border_brush_.Get(); + Repaint(); + } +} diff --git a/src/ui/controls/button.h b/src/ui/controls/button.h new file mode 100644 index 00000000..bd3f6eb3 --- /dev/null +++ b/src/ui/controls/button.h @@ -0,0 +1,41 @@ +#pragma once + +#include + +#include "ui/control.h" + +namespace cru::ui::controls +{ + class Button : public Control + { + public: + static Button* Create(const std::initializer_list& children = std::initializer_list()) + { + const auto button = new Button(); + for (const auto control : children) + button->AddChild(control); + return button; + } + + protected: + 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: + void OnDraw(ID2D1DeviceContext* device_context) override; + + void OnMouseClickBegin(MouseButton button) override final; + void OnMouseClickEnd(MouseButton button) override final; + + private: + Microsoft::WRL::ComPtr normal_border_brush_; + Microsoft::WRL::ComPtr pressed_border_brush_; + ID2D1Brush* current_border_brush_; + }; +} diff --git a/src/ui/controls/linear_layout.cpp b/src/ui/controls/linear_layout.cpp new file mode 100644 index 00000000..8f537ea8 --- /dev/null +++ b/src/ui/controls/linear_layout.cpp @@ -0,0 +1,178 @@ +#include "linear_layout.h" + +namespace cru::ui::controls +{ + LinearLayout::LinearLayout(const Orientation orientation) + : Control(true), orientation_(orientation) + { + + } + + inline float AtLeast0(const float value) + { + return value < 0 ? 0 : value; + } + + inline Size AtLeast0(const Size& size) + { + return Size(AtLeast0(size.width), AtLeast0(size.height)); + } + + Size LinearLayout::OnMeasure(const Size& available_size) + { + const auto layout_params = GetLayoutParams(); + + if (!layout_params->Validate()) + throw std::runtime_error("LayoutParams is not valid. Please check it."); + + auto&& get_available_length_for_child = [](const LayoutSideParams& layout_length, const float available_length) -> float + { + switch (layout_length.mode) + { + case MeasureMode::Exactly: + { + return std::min(layout_length.length, available_length); + } + case MeasureMode::Stretch: + case MeasureMode::Content: + { + return available_length; + } + default: + UnreachableCode(); + } + }; + + Size total_available_size_for_children( + get_available_length_for_child(layout_params->width, available_size.width), + get_available_length_for_child(layout_params->height, available_size.height) + ); + + auto rest_available_size_for_children = total_available_size_for_children; + + float secondary_side_child_max_length = 0; + + std::list stretch_control_list; + + // First measure Content and Exactly and count Stretch. + if (orientation_ == Orientation::Horizontal) + ForeachChild([&](Control* const control) + { + const auto mode = control->GetLayoutParams()->width.mode; + if (mode == MeasureMode::Content || mode == MeasureMode::Exactly) + { + control->Measure(AtLeast0(rest_available_size_for_children)); + const auto size = control->GetDesiredSize(); + rest_available_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 + ForeachChild([&](Control* const control) + { + const auto mode = control->GetLayoutParams()->height.mode; + if (mode == MeasureMode::Content || mode == MeasureMode::Exactly) + { + control->Measure(AtLeast0(rest_available_size_for_children)); + const auto size = control->GetDesiredSize(); + rest_available_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 = rest_available_size_for_children.width / stretch_control_list.size(); + for (const auto control : stretch_control_list) + { + control->Measure(Size(AtLeast0(available_width), rest_available_size_for_children.height)); + const auto size = control->GetDesiredSize(); + rest_available_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 = rest_available_size_for_children.height / stretch_control_list.size(); + for (const auto control : stretch_control_list) + { + control->Measure(Size(rest_available_size_for_children.width, AtLeast0(available_height))); + const auto size = control->GetDesiredSize(); + rest_available_size_for_children.height -= size.height; + secondary_side_child_max_length = std::max(size.width, secondary_side_child_max_length); + } + } + + auto actual_size_for_children = total_available_size_for_children; + if (orientation_ == Orientation::Horizontal) + { + actual_size_for_children.width -= rest_available_size_for_children.width; + actual_size_for_children.height = secondary_side_child_max_length; + } + else + { + actual_size_for_children.width = secondary_side_child_max_length; + actual_size_for_children.height -= rest_available_size_for_children.height; + } + + auto&& calculate_final_length = [](const LayoutSideParams& layout_length, const float length_for_children, const float max_child_length) -> float + { + switch (layout_length.mode) + { + case MeasureMode::Exactly: + case MeasureMode::Stretch: + return length_for_children; + case MeasureMode::Content: + return max_child_length; + default: + UnreachableCode(); + } + }; + + return Size( + calculate_final_length(layout_params->width, total_available_size_for_children.width, actual_size_for_children.width), + calculate_final_length(layout_params->height, total_available_size_for_children.height, actual_size_for_children.height) + ); + } + + void LinearLayout::OnLayout(const Rect& rect) + { + float current_anchor_length = 0; + ForeachChild([this, ¤t_anchor_length, rect](Control* control) + { + 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_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(); + } + }; + + if (orientation_ == Orientation::Horizontal) + { + control->Layout(Rect(Point(current_anchor_length, calculate_anchor(rect.height, size.height)), size)); + current_anchor_length += size.width; + } + else + { + control->Layout(Rect(Point(calculate_anchor(rect.width, size.width), current_anchor_length), size)); + current_anchor_length += size.height; + } + }); + } +} diff --git a/src/ui/controls/linear_layout.h b/src/ui/controls/linear_layout.h new file mode 100644 index 00000000..369824d4 --- /dev/null +++ b/src/ui/controls/linear_layout.h @@ -0,0 +1,41 @@ +#pragma once + +#include "ui/control.h" + +namespace cru::ui::controls +{ + class LinearLayout : public Control + { + public: + enum class Orientation + { + Horizontal, + Vertical + }; + + static LinearLayout* Create(const Orientation orientation = Orientation::Vertical, const std::initializer_list& children = std::initializer_list()) + { + 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; + + protected: + Size OnMeasure(const Size& available_size) override; + void OnLayout(const Rect& rect) override; + + private: + Orientation orientation_; + }; +} diff --git a/src/ui/controls/margin_container.cpp b/src/ui/controls/margin_container.cpp new file mode 100644 index 00000000..8f9101b2 --- /dev/null +++ b/src/ui/controls/margin_container.cpp @@ -0,0 +1,63 @@ +#include "margin_container.h" + +namespace cru::ui::controls +{ + inline float AtLeast0(const float value) + { + return value < 0 ? 0 : value; + } + + inline Size AtLeast0(const Size& size) + { + return Size(AtLeast0(size.width), AtLeast0(size.height)); + } + + MarginContainer::MarginContainer(const Thickness& margin) + : Control(true), margin_(margin) + { + } + + void MarginContainer::SetMargin(const Thickness& margin) + { + margin_ = margin; + Relayout(); + } + + Size MarginContainer::OnMeasure(const Size& available_size) + { + const auto margin_size = Size(margin_.left + margin_.right, margin_.top + margin_.bottom); + const auto coerced_available_size = AtLeast0(available_size - margin_size); + return Control::OnMeasure(coerced_available_size) + margin_size; + } + + void MarginContainer::OnLayout(const Rect& rect) + { + const auto anchor = Point(margin_.left, margin_.top); + const auto margin_size = Size(margin_.left + margin_.right, margin_.top + margin_.bottom); + ForeachChild([anchor, margin_size, rect](Control* control) + { + 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(anchor.x, layout_params->width.alignment, rect.width - margin_size.width, size.width), + calculate_anchor(anchor.y, layout_params->height.alignment, rect.height - margin_size.height, size.height) + ), size)); + }); + } +} diff --git a/src/ui/controls/margin_container.h b/src/ui/controls/margin_container.h new file mode 100644 index 00000000..0eafc40e --- /dev/null +++ b/src/ui/controls/margin_container.h @@ -0,0 +1,44 @@ +#pragma once + +#include + +#include "ui/control.h" + +namespace cru::ui::controls +{ + class MarginContainer : public Control + { + public: + static MarginContainer* Create(const Thickness& margin = Thickness::Zero(), const std::initializer_list& children = std::initializer_list()) + { + const auto margin_container = new MarginContainer(margin); + for (const auto control : children) + margin_container->AddChild(control); + return margin_container; + } + + protected: + explicit MarginContainer(const Thickness& margin); + + public: + MarginContainer(const MarginContainer& other) = delete; + MarginContainer(MarginContainer&& other) = delete; + MarginContainer& operator=(const MarginContainer& other) = delete; + MarginContainer& operator=(MarginContainer&& other) = delete; + ~MarginContainer() override = default; + + Thickness GetMargin() const + { + return margin_; + } + + void SetMargin(const Thickness& margin); + + protected: + Size OnMeasure(const Size& available_size) override; + void OnLayout(const Rect& rect) override; + + private: + Thickness margin_; + }; +} diff --git a/src/ui/controls/text_block.cpp b/src/ui/controls/text_block.cpp new file mode 100644 index 00000000..93d66ba6 --- /dev/null +++ b/src/ui/controls/text_block.cpp @@ -0,0 +1,283 @@ +#include "text_block.h" + +#include "ui/window.h" +#include "graph/graph.h" +#include "exception.h" + +namespace cru +{ + namespace ui + { + namespace controls + { + using graph::CreateSolidBrush; + + inline Microsoft::WRL::ComPtr GetDWriteFactory() + { + return graph::GraphManager::GetInstance()->GetDWriteFactory(); + } + + TextBlock::TextBlock(const Microsoft::WRL::ComPtr& init_text_format, + const Microsoft::WRL::ComPtr& init_brush) : Control(false) + { + text_format_ = init_text_format == nullptr ? graph::CreateDefaultTextFormat() : init_text_format; + + RecreateTextLayout(); + + brush_ = init_brush == nullptr ? CreateSolidBrush(D2D1::ColorF(D2D1::ColorF::Black)) : init_brush; + + selection_brush_ = CreateSolidBrush(D2D1::ColorF(D2D1::ColorF::LightSkyBlue)); + } + + TextBlock::~TextBlock() = default; + + void TextBlock::SetText(const String& text) + { + if (text_ != text) + { + const auto old_text = text_; + text_ = text; + OnTextChangedCore(old_text, text); + } + } + + void TextBlock::SetBrush(const Microsoft::WRL::ComPtr& brush) + { + brush_ = brush; + Repaint(); + } + + void TextBlock::SetTextFormat(const Microsoft::WRL::ComPtr& text_format) + { + text_format_ = text_format; + RecreateTextLayout(); + Repaint(); + } + + void TextBlock::AddTextLayoutHandler(TextLayoutHandlerPtr handler) + { + text_layout_handlers_.push_back(std::move(handler)); + } + + void TextBlock::RemoveTextLayoutHandler(const TextLayoutHandlerPtr& handler) + { + const auto find_result = std::find(text_layout_handlers_.cbegin(), text_layout_handlers_.cend(), + handler); + if (find_result != text_layout_handlers_.cend()) + text_layout_handlers_.erase(find_result); + } + + void TextBlock::SetSelectable(const bool is_selectable) + { + if (!is_selectable) + { + is_selecting_ = false; + selected_range_ = std::nullopt; + Repaint(); + } + is_selectable_ = is_selectable; + } + + void TextBlock::SetSelectedRange(std::optional text_range) + { + if (is_selectable_) + { + selected_range_ = text_range; + Repaint(); + } + } + + void TextBlock::OnSizeChangedCore(events::SizeChangedEventArgs& args) + { + Control::OnSizeChangedCore(args); + text_layout_->SetMaxWidth(args.GetNewSize().width); + text_layout_->SetMaxHeight(args.GetNewSize().height); + Repaint(); + } + + void TextBlock::OnDraw(ID2D1DeviceContext* device_context) + { + Control::OnDraw(device_context); + if (selected_range_.has_value()) + { + DWRITE_TEXT_METRICS text_metrics{}; + ThrowIfFailed(text_layout_->GetMetrics(&text_metrics)); + const auto metrics_count = text_metrics.lineCount * text_metrics.maxBidiReorderingDepth; + + Vector hit_test_metrics(metrics_count); + UINT32 actual_count; + text_layout_->HitTestTextRange( + selected_range_.value().position, selected_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), selection_brush_.Get()); + } + } + device_context->DrawTextLayout(D2D1::Point2F(), text_layout_.Get(), brush_.Get()); + } + + namespace + { + std::optional TextLayoutHitTest(IDWriteTextLayout* text_layout, const Point& point, const bool test_inside = true) + { + BOOL is_trailing, is_inside; + DWRITE_HIT_TEST_METRICS metrics{}; + text_layout->HitTestPoint(point.x, point.y, &is_trailing, &is_inside, &metrics); + if (!test_inside || is_inside) + return is_trailing == 0 ? metrics.textPosition : metrics.textPosition + 1; + else + return std::nullopt; + } + } + + void TextBlock::OnMouseDownCore(events::MouseButtonEventArgs& args) + { + Control::OnMouseDownCore(args); + if (is_selectable_ && args.GetMouseButton() == MouseButton::Left) + { + selected_range_ = std::nullopt; + const auto hit_test_result = TextLayoutHitTest(text_layout_.Get(), args.GetPoint(this), true); + if (hit_test_result.has_value()) + { + mouse_down_position_ = hit_test_result.value(); + is_selecting_ = true; + GetWindow()->CaptureMouseFor(this); + } + Repaint(); + } + } + + void TextBlock::OnMouseMoveCore(events::MouseEventArgs& args) + { + Control::OnMouseMoveCore(args); + if (is_selecting_) + { + const auto hit_test_result = TextLayoutHitTest(text_layout_.Get(), args.GetPoint(this), false).value(); + if (hit_test_result > mouse_down_position_) + selected_range_ = TextRange(mouse_down_position_, hit_test_result - mouse_down_position_); + else if (hit_test_result < mouse_down_position_) + selected_range_ = TextRange(hit_test_result, mouse_down_position_ - hit_test_result); + else + selected_range_ = std::nullopt; + Repaint(); + } + } + + void TextBlock::OnMouseUpCore(events::MouseButtonEventArgs& args) + { + Control::OnMouseUpCore(args); + if (args.GetMouseButton() == MouseButton::Left) + { + if (is_selecting_) + { + is_selecting_ = false; + GetWindow()->ReleaseCurrentMouseCapture(); + } + } + } + + void TextBlock::OnLoseFocusCore(events::FocusChangeEventArgs& args) + { + Control::OnLoseFocusCore(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; + Repaint(); + } + } + + Size TextBlock::OnMeasure(const Size& available_size) + { + const auto layout_params = GetLayoutParams(); + + if (layout_params->width.mode == MeasureMode::Stretch && layout_params->height.mode == MeasureMode::Stretch) + return available_size; + + auto&& get_measure_length = [](const LayoutSideParams& layout_length, const float available_length) -> float + { + switch (layout_length.mode) + { + case MeasureMode::Exactly: + { + return std::min(layout_length.length, available_length); + } + case MeasureMode::Stretch: + case MeasureMode::Content: + { + return available_length; + } + default: + UnreachableCode(); + } + }; + + const Size measure_size(get_measure_length(layout_params->width, available_size.width), + get_measure_length(layout_params->height, available_size.height)); + + ThrowIfFailed(text_layout_->SetMaxWidth(measure_size.width)); + ThrowIfFailed(text_layout_->SetMaxHeight(measure_size.height)); + + DWRITE_TEXT_METRICS metrics{}; + + ThrowIfFailed(text_layout_->GetMetrics(&metrics)); + + const Size measure_result(metrics.width, metrics.height); + + auto&& calculate_final_length = [](const LayoutSideParams& layout_length, const float measure_length, const float measure_result_length) -> float + { + if ((layout_length.mode == MeasureMode::Stretch || + layout_length.mode == MeasureMode::Exactly) + && measure_result_length < measure_length) + return measure_length; + else + return measure_result_length; + }; + + const Size result_size( + calculate_final_length(layout_params->width, measure_size.width, measure_result.width), + calculate_final_length(layout_params->height, measure_size.height, measure_result.height) + ); + + return result_size; + } + + void TextBlock::OnTextChangedCore(const String& old_text, const String& new_text) + { + RecreateTextLayout(); + Repaint(); + } + + void TextBlock::RecreateTextLayout() + { + assert(text_format_ != nullptr); + + const auto dwrite_factory = GetDWriteFactory(); + + const auto&& size = GetSize(); + + ThrowIfFailed(dwrite_factory->CreateTextLayout( + text_.c_str(), static_cast(text_.size()), + text_format_.Get(), + size.width, size.height, + &text_layout_ + )); + + std::for_each(text_layout_handlers_.cbegin(), text_layout_handlers_.cend(), [this](const TextLayoutHandlerPtr& handler) + { + (*handler)(text_layout_); + }); + } + } + } +} diff --git a/src/ui/controls/text_block.h b/src/ui/controls/text_block.h new file mode 100644 index 00000000..c87ffc51 --- /dev/null +++ b/src/ui/controls/text_block.h @@ -0,0 +1,116 @@ +#pragma once + +#include +#include + +#include "ui/control.h" + +namespace cru +{ + namespace ui + { + namespace controls + { + class TextBlock : public Control + { + public: + using TextLayoutHandlerPtr = FunctionPtr)>; + + static TextBlock* Create( + const String& text = L"", + const Microsoft::WRL::ComPtr& init_text_format = nullptr, + const Microsoft::WRL::ComPtr& init_brush = nullptr) + { + const auto text_block = new TextBlock(init_text_format, init_brush); + text_block->SetText(text); + return text_block; + } + + protected: + explicit TextBlock( + const Microsoft::WRL::ComPtr& init_text_format = nullptr, + const Microsoft::WRL::ComPtr& init_brush = nullptr + ); + public: + TextBlock(const TextBlock& other) = delete; + TextBlock(TextBlock&& other) = delete; + TextBlock& operator=(const TextBlock& other) = delete; + TextBlock& operator=(TextBlock&& other) = delete; + ~TextBlock() override; + + String GetText() const + { + return text_; + } + + void SetText(const String& text); + + Microsoft::WRL::ComPtr GetBrush() const + { + return brush_; + } + + void SetBrush(const Microsoft::WRL::ComPtr& brush); + + Microsoft::WRL::ComPtr GetTextFormat() const + { + return text_format_; + } + + void SetTextFormat(const Microsoft::WRL::ComPtr& text_format); + + + void AddTextLayoutHandler(TextLayoutHandlerPtr handler); + + void RemoveTextLayoutHandler(const TextLayoutHandlerPtr& handler); + + bool IsSelectable() const + { + return is_selectable_; + } + + void SetSelectable(bool is_selectable); + + std::optional GetSelectedRange() const + { + return selected_range_; + } + + void SetSelectedRange(std::optional text_range); + + protected: + void OnSizeChangedCore(events::SizeChangedEventArgs& args) override final; + void OnDraw(ID2D1DeviceContext* device_context) override; + + void OnMouseDownCore(events::MouseButtonEventArgs& args) override final; + void OnMouseMoveCore(events::MouseEventArgs& args) override final; + void OnMouseUpCore(events::MouseButtonEventArgs& args) override final; + + void OnLoseFocusCore(events::FocusChangeEventArgs& args) override final; + + Size OnMeasure(const Size& available_size) override final; + + private: + void OnTextChangedCore(const String& old_text, const String& new_text); + + void RecreateTextLayout(); + + private: + String text_; + + Microsoft::WRL::ComPtr brush_; + Microsoft::WRL::ComPtr selection_brush_; + Microsoft::WRL::ComPtr text_format_; + Microsoft::WRL::ComPtr text_layout_; + + Vector text_layout_handlers_; + + bool is_selectable_ = false; + + bool is_selecting_ = false; + unsigned mouse_down_position_ = 0; + std::optional selected_range_ = std::nullopt; + }; + } + } +} diff --git a/src/ui/controls/text_box.cpp b/src/ui/controls/text_box.cpp new file mode 100644 index 00000000..a8d78398 --- /dev/null +++ b/src/ui/controls/text_box.cpp @@ -0,0 +1,244 @@ +#include "text_box.h" + +#include + +#include "graph/graph.h" +#include "exception.h" + +namespace cru::ui::controls +{ + using graph::CreateSolidBrush; + + inline Microsoft::WRL::ComPtr GetDWriteFactory() + { + return graph::GraphManager::GetInstance()->GetDWriteFactory(); + } + + TextBox::TextBox(const Microsoft::WRL::ComPtr& init_text_format, + const Microsoft::WRL::ComPtr& init_brush) : Control(false) + { + text_format_ = init_text_format == nullptr ? graph::CreateDefaultTextFormat() : init_text_format; + + RecreateTextLayout(); + + brush_ = init_brush == nullptr ? CreateSolidBrush(D2D1::ColorF(D2D1::ColorF::Black)) : init_brush; + + caret_brush_ = CreateSolidBrush(D2D1::ColorF(D2D1::ColorF::Black)); + + caret_action_ = CreateActionPtr([this] + { + is_caret_show_ = !is_caret_show_; + Repaint(); + }); + + //selection_brush_ = CreateSolidBrush(D2D1::ColorF(D2D1::ColorF::LightSkyBlue)); + } + + TextBox::~TextBox() = default; + + void TextBox::SetText(const String& text) + { + if (text_ != text) + { + const auto old_text = text_; + text_ = text; + OnTextChangedCore(old_text, text); + } + } + + void TextBox::SetBrush(const Microsoft::WRL::ComPtr& brush) + { + brush_ = brush; + Repaint(); + } + + void TextBox::SetTextFormat(const Microsoft::WRL::ComPtr& text_format) + { + text_format_ = text_format; + RecreateTextLayout(); + Repaint(); + } + + void TextBox::OnSizeChangedCore(events::SizeChangedEventArgs& args) + { + Control::OnSizeChangedCore(args); + text_layout_->SetMaxWidth(args.GetNewSize().width); + text_layout_->SetMaxHeight(args.GetNewSize().height); + Repaint(); + } + + void TextBox::OnDraw(ID2D1DeviceContext* device_context) + { + Control::OnDraw(device_context); + if (text_layout_ != nullptr) + { + device_context->DrawTextLayout(D2D1::Point2F(), text_layout_.Get(), brush_.Get()); + if (is_caret_show_) + { + const auto caret_half_width = Application::GetInstance()->GetCaretInfo().half_caret_width; + FLOAT x, y; + DWRITE_HIT_TEST_METRICS metrics{}; + ThrowIfFailed(text_layout_->HitTestTextPosition(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()); + } + } + } + + namespace + { + std::optional TextLayoutHitTest(IDWriteTextLayout* text_layout, const Point& point, bool test_inside = true) + { + BOOL is_trailing, is_inside; + DWRITE_HIT_TEST_METRICS metrics{}; + text_layout->HitTestPoint(point.x, point.y, &is_trailing, &is_inside, &metrics); + if (!test_inside || is_inside) + return is_trailing == 0 ? metrics.textPosition : metrics.textPosition + 1; + else + return std::nullopt; + } + } + + void TextBox::OnMouseDownCore(events::MouseButtonEventArgs& args) + { + Control::OnMouseDownCore(args); + if (args.GetMouseButton() == MouseButton::Left) + { + position_ = TextLayoutHitTest(text_layout_.Get(), args.GetPoint(this), false).value(); + + Repaint(); + } + } + + void TextBox::OnGetFocusCore(events::FocusChangeEventArgs& args) + { + Control::OnGetFocusCore(args); + assert(caret_timer_ == nullptr); + is_caret_show_ = true; + caret_timer_ = SetInterval(Application::GetInstance()->GetCaretInfo().caret_blink_duration, caret_action_); + } + + void TextBox::OnLoseFocusCore(events::FocusChangeEventArgs& args) + { + Control::OnLoseFocusCore(args); + assert(caret_timer_ != nullptr); + caret_timer_->Cancel(); + is_caret_show_ = false; + } + + void TextBox::OnKeyDownCore(events::KeyEventArgs& args) + { + Control::OnKeyDownCore(args); + if (args.GetVirtualCode() == VK_LEFT && position_ > 0) + { + position_--; + Repaint(); + } + + if (args.GetVirtualCode() == VK_RIGHT && position_ < GetText().size()) + { + position_++; + Repaint(); + } + } + + void TextBox::OnCharCore(events::CharEventArgs& args) + { + Control::OnCharCore(args); + if (args.GetChar() == L'\b') + { + auto text = GetText(); + if (!text.empty() && position_ > 0) + { + const auto position = --position_; + text.erase(position); + SetText(text); + } + return; + } + + if (std::iswprint(args.GetChar())) + { + const auto position = position_++; + auto text = GetText(); + text.insert(text.cbegin() + position, { args.GetChar() }); + SetText(text); + } + } + + Size TextBox::OnMeasure(const Size& available_size) + { + const auto layout_params = GetLayoutParams(); + + if (layout_params->width.mode == MeasureMode::Stretch && layout_params->height.mode == MeasureMode::Stretch) + return available_size; + + auto&& get_measure_length = [](const LayoutSideParams& layout_length, const float available_length) -> float + { + switch (layout_length.mode) + { + case MeasureMode::Exactly: + { + return std::min(layout_length.length, available_length); + } + case MeasureMode::Stretch: + case MeasureMode::Content: + { + return available_length; + } + default: + UnreachableCode(); + } + }; + + const Size measure_size(get_measure_length(layout_params->width, available_size.width), + get_measure_length(layout_params->height, available_size.height)); + + ThrowIfFailed(text_layout_->SetMaxWidth(measure_size.width)); + ThrowIfFailed(text_layout_->SetMaxHeight(measure_size.height)); + + DWRITE_TEXT_METRICS metrics{}; + + ThrowIfFailed(text_layout_->GetMetrics(&metrics)); + + const Size measure_result(metrics.width, metrics.height); + + auto&& calculate_final_length = [](const LayoutSideParams& layout_length, const float measure_length, const float measure_result_length) -> float + { + if ((layout_length.mode == MeasureMode::Stretch || + layout_length.mode == MeasureMode::Exactly) + && measure_result_length < measure_length) + return measure_length; + else + return measure_result_length; + }; + + const Size result_size( + calculate_final_length(layout_params->width, measure_size.width, measure_result.width), + calculate_final_length(layout_params->height, measure_size.height, measure_result.height) + ); + + return result_size; + } + + void TextBox::OnTextChangedCore(const String& old_text, const String& new_text) + { + RecreateTextLayout(); + Repaint(); + } + + void TextBox::RecreateTextLayout() + { + assert(text_format_ != nullptr); + + const auto dwrite_factory = GetDWriteFactory(); + + const auto&& size = GetSize(); + + ThrowIfFailed(dwrite_factory->CreateTextLayout( + text_.c_str(), static_cast(text_.size()), + text_format_.Get(), + size.width, size.height, + &text_layout_ + )); + } +} diff --git a/src/ui/controls/text_box.h b/src/ui/controls/text_box.h new file mode 100644 index 00000000..b815ed1f --- /dev/null +++ b/src/ui/controls/text_box.h @@ -0,0 +1,85 @@ +#pragma once + +#include "ui/control.h" +#include "timer.h" + +namespace cru::ui::controls +{ + class TextBox : public Control + { + public: + static TextBox* Create( + const Microsoft::WRL::ComPtr& init_text_format = nullptr, + const Microsoft::WRL::ComPtr& init_brush = nullptr) + { + return new TextBox(init_text_format, init_brush); + } + + protected: + explicit TextBox( + const Microsoft::WRL::ComPtr& init_text_format = nullptr, + const Microsoft::WRL::ComPtr& init_brush = nullptr + ); + public: + TextBox(const TextBox& other) = delete; + TextBox(TextBox&& other) = delete; + TextBox& operator=(const TextBox& other) = delete; + TextBox& operator=(TextBox&& other) = delete; + ~TextBox() override; + + String GetText() const + { + return text_; + } + + void SetText(const String& text); + + Microsoft::WRL::ComPtr GetBrush() const + { + return brush_; + } + + void SetBrush(const Microsoft::WRL::ComPtr& brush); + + Microsoft::WRL::ComPtr GetTextFormat() const + { + return text_format_; + } + + void SetTextFormat(const Microsoft::WRL::ComPtr& text_format); + + protected: + void OnSizeChangedCore(events::SizeChangedEventArgs& args) override final; + void OnDraw(ID2D1DeviceContext* device_context) override; + + void OnMouseDownCore(events::MouseButtonEventArgs& args) override final; + + void OnGetFocusCore(events::FocusChangeEventArgs& args) override final; + void OnLoseFocusCore(events::FocusChangeEventArgs& args) override final; + + void OnKeyDownCore(events::KeyEventArgs& args) override final; + void OnCharCore(events::CharEventArgs& args) override final; + + Size OnMeasure(const Size& available_size) override final; + + private: + void OnTextChangedCore(const String& old_text, const String& new_text); + + void RecreateTextLayout(); + + private: + String text_; + + Microsoft::WRL::ComPtr brush_; + Microsoft::WRL::ComPtr caret_brush_; + //Microsoft::WRL::ComPtr selection_brush_; + Microsoft::WRL::ComPtr text_format_; + Microsoft::WRL::ComPtr text_layout_; + + unsigned position_ = 0; + + TimerTask caret_timer_; + ActionPtr caret_action_; + bool is_caret_show_; + }; +} diff --git a/src/ui/controls/toggle_button.cpp b/src/ui/controls/toggle_button.cpp new file mode 100644 index 00000000..68bd0fc9 --- /dev/null +++ b/src/ui/controls/toggle_button.cpp @@ -0,0 +1,150 @@ +#include "toggle_button.h" + +#include + +#include "graph/graph.h" +#include "ui/animations/animation.h" + +namespace cru::ui::controls +{ + using graph::CreateSolidBrush; + 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_ = CreateSolidBrush(D2D1::ColorF(D2D1::ColorF::DeepSkyBlue)); + off_brush_ = CreateSolidBrush(D2D1::ColorF(D2D1::ColorF::LightGray)); + } + + inline D2D1_POINT_2F ConvertPoint(const Point& point) + { + return D2D1::Point2F(point.x, point.y); + } + + 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(ConvertPoint(point), transform, &contains); + if (!contains) + frame_path_->StrokeContainsPoint(ConvertPoint(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(fmt::format(L"ToggleButton {}", reinterpret_cast(this)), time) + .AddStepHandler(CreatePtr([=](animations::AnimationDelegatePtr, const double percentage) + { + current_circle_position_ = static_cast(previous_position + delta * percentage); + Repaint(); + })).Start(); + + RaiseToggleEvent(state); + Repaint(); + } + } + + void ToggleButton::Toggle() + { + SetState(!GetState()); + } + + void ToggleButton::OnToggle(events::ToggleEventArgs& args) + { + + } + + void ToggleButton::OnDraw(ID2D1DeviceContext* device_context) + { + Control::OnDraw(device_context); + 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()); + } + }); + } + + void ToggleButton::OnMouseClickCore(events::MouseButtonEventArgs& args) + { + Control::OnMouseClickCore(args); + Toggle(); + } + + Size ToggleButton::OnMeasure(const Size& available_size) + { + const auto layout_params = GetLayoutParams(); + + auto&& get_measure_length = [](const LayoutSideParams& layout_length, const float available_length, const float fix_length) -> float + { + switch (layout_length.mode) + { + case MeasureMode::Exactly: + { + return std::max(std::min(layout_length.length, available_length), fix_length); + } + case MeasureMode::Stretch: + { + return std::max(available_length, fix_length); + } + case MeasureMode::Content: + { + return fix_length; + } + default: + UnreachableCode(); + } + }; + + const Size result_size( + get_measure_length(layout_params->width, available_size.width, half_width * 2 + stroke_width), + get_measure_length(layout_params->height, available_size.height, half_height * 2 + stroke_width) + ); + + return result_size; + } + + void ToggleButton::RaiseToggleEvent(bool new_state) + { + events::ToggleEventArgs args(this, this, new_state); + OnToggle(args); + toggle_event.Raise(args); + } +} diff --git a/src/ui/controls/toggle_button.h b/src/ui/controls/toggle_button.h new file mode 100644 index 00000000..d496f21a --- /dev/null +++ b/src/ui/controls/toggle_button.h @@ -0,0 +1,61 @@ +#pragma once + +#include "ui/control.h" + +namespace cru::ui::controls +{ + class ToggleButton : public Control + { + public: + 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; + + bool IsPointInside(const Point& point) override; + + bool GetState() const + { + return state_; + } + + void SetState(bool state); + + void Toggle(); + + public: + events::ToggleEvent toggle_event; + + protected: + virtual void OnToggle(events::ToggleEventArgs& args); + + protected: + void OnDraw(ID2D1DeviceContext* device_context) override; + + void OnMouseClickCore(events::MouseButtonEventArgs& args) override; + + Size OnMeasure(const Size& available_size) override; + + private: + void RaiseToggleEvent(bool new_state); + + private: + bool state_ = false; + + float current_circle_position_; + + Microsoft::WRL::ComPtr frame_path_; + Microsoft::WRL::ComPtr on_brush_; + Microsoft::WRL::ComPtr off_brush_; + }; +} diff --git a/src/ui/events/ui_event.cpp b/src/ui/events/ui_event.cpp new file mode 100644 index 00000000..59623bab --- /dev/null +++ b/src/ui/events/ui_event.cpp @@ -0,0 +1,19 @@ +#include "ui_event.h" + +#include "ui/control.h" + +namespace cru +{ + namespace ui + { + namespace events + { + Point MouseEventArgs::GetPoint(Control* control) const + { + if (point_.has_value()) + return control->AbsoluteToLocal(point_.value()); + return Point(); + } + } + } +} diff --git a/src/ui/events/ui_event.h b/src/ui/events/ui_event.h new file mode 100644 index 00000000..b042b706 --- /dev/null +++ b/src/ui/events/ui_event.h @@ -0,0 +1,317 @@ +#pragma once + +#include "system_headers.h" +#include + +#include "base.h" +#include "cru_event.h" +#include "ui/ui_base.h" + +namespace cru +{ + namespace ui + { + class Control; + + namespace events + { + class UiEventArgs : public BasicEventArgs + { + public: + UiEventArgs(Object* sender, Object* original_sender) + : BasicEventArgs(sender), original_sender_(original_sender) + { + + } + + 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_; + } + + private: + Object* original_sender_; + }; + + + class MouseEventArgs : public UiEventArgs + { + public: + MouseEventArgs(Object* sender, Object* original_sender, const std::optional& 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) const; + + private: + std::optional 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 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 PositionChangedEventArgs : public UiEventArgs + { + public: + PositionChangedEventArgs(Object* sender, Object* original_sender, const Point& old_position, const Point& new_position) + : UiEventArgs(sender, original_sender), old_position_(old_position), new_position_(new_position) + { + + } + PositionChangedEventArgs(const PositionChangedEventArgs& other) = default; + PositionChangedEventArgs(PositionChangedEventArgs&& other) = default; + PositionChangedEventArgs& operator=(const PositionChangedEventArgs& other) = default; + PositionChangedEventArgs& operator=(PositionChangedEventArgs&& other) = default; + ~PositionChangedEventArgs() override = default; + + Point GetOldPosition() const + { + return old_position_; + } + + Point GetNewPosition() const + { + return new_position_; + } + + private: + Point old_position_; + Point new_position_; + }; + + + class SizeChangedEventArgs : public UiEventArgs + { + public: + SizeChangedEventArgs(Object* sender, Object* original_sender, const Size& old_size, const Size& new_size) + : UiEventArgs(sender, original_sender), old_size_(old_size), new_size_(new_size) + { + + } + SizeChangedEventArgs(const SizeChangedEventArgs& other) = default; + SizeChangedEventArgs(SizeChangedEventArgs&& other) = default; + SizeChangedEventArgs& operator=(const SizeChangedEventArgs& other) = default; + SizeChangedEventArgs& operator=(SizeChangedEventArgs&& other) = default; + ~SizeChangedEventArgs() override = default; + + Size GetOldSize() const + { + return old_size_; + } + + Size GetNewSize() const + { + return new_size_; + } + + private: + Size old_size_; + Size new_size_; + }; + + 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 GetResult() const + { + return result_; + } + + void SetResult(const std::optional result) + { + result_ = result; + } + + private: + WindowNativeMessage message_; + std::optional 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_; + }; + + using UiEvent = Event; + using MouseEvent = Event; + using MouseButtonEvent = Event; + using DrawEvent = Event; + using PositionChangedEvent = Event; + using SizeChangedEvent = Event; + using FocusChangeEvent = Event; + using ToggleEvent = Event; + using WindowNativeMessageEvent = Event; + using KeyEvent = Event; + using CharEvent = Event; + } + } +} \ No newline at end of file diff --git a/src/ui/layout_base.cpp b/src/ui/layout_base.cpp new file mode 100644 index 00000000..a26379a0 --- /dev/null +++ b/src/ui/layout_base.cpp @@ -0,0 +1,79 @@ +#include "layout_base.h" + +#include "application.h" +#include "control.h" + +namespace cru::ui +{ + LayoutManager* LayoutManager::GetInstance() + { + static LayoutManager layout_manager; + return &layout_manager; + } + + void LayoutManager::InvalidateControlPositionCache(Control * control) + { + if (cache_invalid_controls_.count(control) == 1) + return; + + // find descendant then erase it; find ancestor then just return. + auto i = cache_invalid_controls_.cbegin(); + while (i != cache_invalid_controls_.cend()) + { + auto current_i = i++; + const auto result = IsAncestorOrDescendant(*current_i, control); + if (result == control) + cache_invalid_controls_.erase(current_i); + else if (result != nullptr) + return; // find a ancestor of "control", just return + } + + cache_invalid_controls_.insert(control); + + if (cache_invalid_controls_.size() == 1) // when insert just now and not repeat to "InvokeLater". + { + InvokeLater([this] { + + RefreshInvalidControlPositionCache(); // first refresh position cache. + for (const auto i : cache_invalid_controls_) // traverse all descendants of position-invalid controls and notify position change event + i->TraverseDescendants([](Control* control) + { + control->CheckAndNotifyPositionChanged(); + }); + cache_invalid_controls_.clear(); // after update and notify, clear the set. + + }); + } + } + + void LayoutManager::RefreshInvalidControlPositionCache() + { + for (const auto i : cache_invalid_controls_) + RefreshControlPositionCache(i); + } + + void LayoutManager::RefreshControlPositionCache(Control * control) + { + auto point = Point::Zero(); + auto parent = control; + while ((parent = parent->GetParent())) { + const auto p = parent->GetPositionRelative(); + point.x += p.x; + point.y += p.y; + } + RefreshControlPositionCacheInternal(control, point); + } + + void LayoutManager::RefreshControlPositionCacheInternal(Control * control, const Point & parent_lefttop_absolute) + { + const auto position = control->GetPositionRelative(); + Point lefttop( + parent_lefttop_absolute.x + position.x, + parent_lefttop_absolute.y + position.y + ); + control->position_cache_.lefttop_position_absolute = lefttop; + control->ForeachChild([lefttop](Control* c) { + RefreshControlPositionCacheInternal(c, lefttop); + }); + } +} diff --git a/src/ui/layout_base.h b/src/ui/layout_base.h new file mode 100644 index 00000000..163b99b2 --- /dev/null +++ b/src/ui/layout_base.h @@ -0,0 +1,136 @@ +#pragma once + +#include "system_headers.h" +#include + +#include "base.h" +#include "ui_base.h" + +namespace cru +{ + namespace ui + { + class Control; + + enum class Alignment + { + Center, + Start, + End + }; + + enum class MeasureMode + { + Exactly, + Content, + Stretch + }; + + 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 (mode == MeasureMode::Exactly && length < 0.0) + { +#ifdef CRU_DEBUG + ::OutputDebugStringW(L"LayoutSideParams validation error: mode is Exactly but length is less than 0.\n"); +#endif + return false; + } + return true; + } + + float length = 0.0; + MeasureMode mode = MeasureMode::Content; + Alignment alignment = Alignment::Center; + }; + + 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 + { + if (!width.Validate()) + { +#ifdef CRU_DEBUG + ::OutputDebugStringW(L"Width(LayoutSideParams) is not valid."); +#endif + return false; + } + if (!height.Validate()) + { +#ifdef CRU_DEBUG + ::OutputDebugStringW(L"Height(LayoutSideParams) is not valid."); +#endif + return false; + } + return true; + } + + LayoutSideParams width; + LayoutSideParams height; + }; + + + class LayoutManager : public Object + { + public: + static LayoutManager* GetInstance(); + + public: + LayoutManager() = default; + LayoutManager(const LayoutManager& other) = delete; + LayoutManager(LayoutManager&& other) = delete; + LayoutManager& operator=(const LayoutManager& other) = delete; + LayoutManager& operator=(LayoutManager&& other) = delete; + ~LayoutManager() override = default; + + //Mark position cache of the control and its descendants invalid, + //(which is saved as an auto-managed list internal) + //and send a message to refresh them. + void InvalidateControlPositionCache(Control* control); + + //Refresh position cache of the control and its descendants whose cache + //has been marked as invalid. + void RefreshInvalidControlPositionCache(); + + //Refresh position cache of the control and its descendants immediately. + static void RefreshControlPositionCache(Control* control); + + private: + static void RefreshControlPositionCacheInternal(Control* control, const Point& parent_lefttop_absolute); + + private: + std::unordered_set cache_invalid_controls_; + }; + } +} diff --git a/src/ui/ui_base.cpp b/src/ui/ui_base.cpp new file mode 100644 index 00000000..550432e4 --- /dev/null +++ b/src/ui/ui_base.cpp @@ -0,0 +1,6 @@ +#include "ui_base.h" + +namespace cru { + namespace ui { + } +} diff --git a/src/ui/ui_base.h b/src/ui/ui_base.h new file mode 100644 index 00000000..43f3c498 --- /dev/null +++ b/src/ui/ui_base.h @@ -0,0 +1,154 @@ +#pragma once + + +namespace cru +{ + namespace 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); + } + + 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 Size GetSize() const + { + return Size(width, height); + } + + 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; + }; + + 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 left; + float top; + float right; + float bottom; + }; + + enum class MouseButton + { + Left, + Right, + Middle + }; + + struct TextRange + { + constexpr TextRange() = default; + constexpr TextRange(const int position, const int count) + : position(position), count(count) + { + + } + + unsigned position = 0; + unsigned count = 0; + }; + } +} diff --git a/src/ui/window.cpp b/src/ui/window.cpp new file mode 100644 index 00000000..34a54512 --- /dev/null +++ b/src/ui/window.cpp @@ -0,0 +1,606 @@ +#include "window.h" + +#include + +#include "application.h" +#include "graph/graph.h" +#include "exception.h" + +namespace cru +{ + namespace 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 = Application::GetInstance()->GetWindowManager()->FromHandle(hWnd); + + LRESULT result; + if (window != nullptr && window->HandleWindowMessage(hWnd, Msg, wParam, lParam, result)) + return result; + + return DefWindowProc(hWnd, Msg, wParam, lParam); + } + + WindowManager::WindowManager() { + general_window_class_ = std::make_unique( + 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; + } + + Vector WindowManager::GetAllWindows() const + { + Vector windows; + for (auto [key, value] : window_map_) + windows.push_back(value); + return windows; + } + + inline Point PiToDip(const POINT& pi_point) + { + return Point( + graph::PixelToDipX(pi_point.x), + graph::PixelToDipY(pi_point.y) + ); + } + + Window::Window() : Control(WindowConstructorTag{}, this), control_list_({ this }) { + const auto app = Application::GetInstance(); + hwnd_ = CreateWindowEx(0, + app->GetWindowManager()->GetGeneralWindowClass()->GetName(), + L"", WS_OVERLAPPEDWINDOW, + CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, + nullptr, nullptr, app->GetInstanceHandle(), nullptr + ); + + if (hwnd_ == nullptr) + throw std::runtime_error("Failed to create window."); + + app->GetWindowManager()->RegisterWindow(hwnd_, this); + + render_target_ = app->GetGraphManager()->CreateWindowRenderTarget(hwnd_); + } + + Window::~Window() { + Close(); + TraverseDescendants([this](Control* control) { + control->OnDetachToWindow(this); + }); + } + + void Window::Close() { + if (IsWindowValid()) + DestroyWindow(hwnd_); + } + + void Window::Repaint() { + if (IsWindowValid()) { + InvalidateRect(hwnd_, nullptr, false); + } + } + + void Window::Show() { + if (IsWindowValid()) { + ShowWindow(hwnd_, SW_SHOWNORMAL); + } + } + + void Window::Hide() { + if (IsWindowValid()) { + ShowWindow(hwnd_, SW_HIDE); + } + } + + Size Window::GetClientSize() { + if (!IsWindowValid()) + return Size(); + + const auto pixel_rect = GetClientRectPixel(); + return Size( + graph::PixelToDipX(pixel_rect.right), + graph::PixelToDipY(pixel_rect.bottom) + ); + } + + void Window::SetClientSize(const Size & size) { + if (IsWindowValid()) { + const auto window_style = static_cast(GetWindowLongPtr(hwnd_, GWL_STYLE)); + const auto window_ex_style = static_cast(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 + ); + } + } + + Rect Window::GetWindowRect() { + if (!IsWindowValid()) + return Rect(); + + RECT rect; + ::GetWindowRect(hwnd_, &rect); + + return Rect::FromVertices( + graph::PixelToDipX(rect.left), + graph::PixelToDipY(rect.top), + graph::PixelToDipX(rect.right), + graph::PixelToDipY(rect.bottom) + ); + } + + 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 + ); + } + } + + bool Window::HandleWindowMessage(HWND hwnd, int msg, WPARAM w_param, LPARAM l_param, LRESULT & result) { + + if (!native_message_event.IsNoHandler()) + { + 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_KEYDOWN: + OnKeyDownInternal(static_cast(w_param)); + result = 0; + return true; + case WM_KEYUP: + OnKeyUpInternal(static_cast(w_param)); + result = 0; + return true; + case WM_CHAR: + OnCharInternal(static_cast(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; + } + } + + Point Window::GetMousePosition() + { + POINT point; + ::GetCursorPos(&point); + ::ScreenToClient(hwnd_, &point); + return PiToDip(point); + } + + Point Window::GetPositionRelative() + { + return Point(); + } + + void Window::SetPositionRelative(const Point & position) + { + + } + + Size Window::GetSize() + { + return GetClientSize(); + } + + void Window::SetSize(const Size & size) + { + + } + + void Window::RefreshControlList() { + control_list_.clear(); + TraverseDescendants([this](Control* control) { + this->control_list_.push_back(control); + }); + } + + Control * Window::HitTest(const Point & point) + { + for (auto i = control_list_.crbegin(); i != control_list_.crend(); ++i) { + auto control = *i; + if (control->IsPointInside(control->AbsoluteToLocal(point))) { + return control; + } + } + return nullptr; + } + + 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."); + + if (!IsWindowValid()) + return false; + + if (!window_focus_) + { + focus_control_ = control; + ::SetFocus(hwnd_); + return true; // event dispatch will be done in window message handling function "OnSetFocusInternal". + } + + if (focus_control_ == control) + return true; + + DispatchEvent(focus_control_, &Control::RaiseLoseFocusEvent, nullptr, false); + + focus_control_ = control; + + DispatchEvent(control, &Control::RaiseGetFocusEvent, nullptr, false); + + return true; + } + + 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(); + } + } + + 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_DRAW_CONTROL_BORDER + void Window::SetDebugDrawControlBorder(const bool value) + { + if (debug_draw_control_border_ != value) + { + debug_draw_control_border_ = value; + Repaint(); + } + } +#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::OnDestroyInternal() { + Application::GetInstance()->GetWindowManager()->UnregisterWindow(hwnd_); + hwnd_ = nullptr; + } + + void Window::OnPaintInternal() { + render_target_->SetAsTarget(); + + auto device_context = render_target_->GetD2DDeviceContext(); + + device_context->BeginDraw(); + + //Clear the background. + device_context->Clear(D2D1::ColorF(D2D1::ColorF::White)); + + Draw(device_context.Get()); + + ThrowIfFailed( + device_context->EndDraw(), "Failed to draw window." + ); + + render_target_->Present(); + + ValidateRect(hwnd_, nullptr); + } + + void Window::OnResizeInternal(int new_width, int new_height) { + render_target_->ResizeBuffer(new_width, new_height); + Relayout(); + } + + void Window::OnSetFocusInternal() + { + window_focus_ = true; + DispatchEvent(focus_control_, &Control::RaiseGetFocusEvent, nullptr, true); + } + + void Window::OnKillFocusInternal() + { + window_focus_ = false; + DispatchEvent(focus_control_, &Control::RaiseLoseFocusEvent, 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::RaiseMouseMoveEvent, nullptr, dip_point); + } + else + { + DispatchMouseHoverControlChangeEvent(old_control_mouse_hover, new_control_mouse_hover, dip_point); + DispatchEvent(new_control_mouse_hover, &Control::RaiseMouseMoveEvent, nullptr, dip_point); + } + } + + void Window::OnMouseLeaveInternal() + { + DispatchEvent(mouse_hover_control_, &Control::RaiseMouseLeaveEvent, nullptr); + mouse_hover_control_ = nullptr; + } + + void Window::OnMouseDownInternal(MouseButton button, POINT point) + { + const auto dip_point = PiToDip(point); + + Control* control; + + if (mouse_capture_control_) + control = mouse_capture_control_; + else + control = HitTest(dip_point); + + DispatchEvent(control, &Control::RaiseMouseDownEvent, nullptr, dip_point, button); + } + + void Window::OnMouseUpInternal(MouseButton button, POINT point) + { + const auto dip_point = PiToDip(point); + + Control* control; + + if (mouse_capture_control_) + control = mouse_capture_control_; + else + control = HitTest(dip_point); + + DispatchEvent(control, &Control::RaiseMouseUpEvent, nullptr, dip_point, button); + } + + void Window::OnKeyDownInternal(int virtual_code) + { + DispatchEvent(focus_control_, &Control::RaiseKeyDownEvent, nullptr, virtual_code); + } + + void Window::OnKeyUpInternal(int virtual_code) + { + DispatchEvent(focus_control_, &Control::RaiseKeyUpEvent, nullptr, virtual_code); + } + + void Window::OnCharInternal(wchar_t c) + { + DispatchEvent(focus_control_, &Control::RaiseCharEvent, nullptr, c); + } + + 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::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::RaiseMouseLeaveEvent, lowest_common_ancestor); // dispatch mouse leave event. + if (new_control != nullptr) + DispatchEvent(new_control, &Control::RaiseMouseEnterEvent, lowest_common_ancestor, point); // dispatch mouse enter event. + } + } + } +} diff --git a/src/ui/window.h b/src/ui/window.h new file mode 100644 index 00000000..40d81a06 --- /dev/null +++ b/src/ui/window.h @@ -0,0 +1,276 @@ +#pragma once + +#include "system_headers.h" +#include +#include +#include + +#include "control.h" +#include "events/ui_event.h" + +namespace cru { + namespace graph { + class WindowRenderTarget; + } + + namespace 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: + WindowManager(); + WindowManager(const WindowManager& other) = delete; + WindowManager(WindowManager&& other) = delete; + WindowManager& operator=(const WindowManager& other) = delete; + WindowManager& operator=(WindowManager&& other) = delete; + ~WindowManager() override = default; + + + //Get the general window class for creating ordinary window. + WindowClass* GetGeneralWindowClass() const + { + return general_window_class_.get(); + } + + //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); + + //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); + + //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); + + Vector GetAllWindows() const; + + private: + std::unique_ptr general_window_class_; + std::map window_map_; + }; + + + + class Window : public Control + { + friend class WindowManager; + public: + Window(); + Window(const Window& other) = delete; + Window(Window&& other) = delete; + Window& operator=(const Window& other) = delete; + Window& operator=(Window&& other) = delete; + ~Window() override; + + public: + //*************** region: handle *************** + + //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 *************** + + //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 Repaint() override; + + //Show the window. + void Show(); + + //Hide thw window. + void Hide(); + + //Get the client size. + Size GetClientSize(); + + //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(); + + //Set the rect of the window containing frame. + //The lefttop of the rect is relative to screen lefttop. + void SetWindowRect(const Rect& rect); + + //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); + + Point GetMousePosition(); + + //*************** region: position and size *************** + + //Always return (0, 0) for a window. + Point GetPositionRelative() override final; + + //This method has no effect for a window. + void SetPositionRelative(const Point& position) override final; + + //Get the size of client area for a window. + Size GetSize() override final; + + //This method has no effect for a window. Use SetClientSize instead. + void SetSize(const Size& size) override final; + + + //*************** region: features *************** + + //Refresh control list. + //It should be invoked every time a control is added or removed from the tree. + void RefreshControlList(); + + //Get the most top control at "point". + Control* HitTest(const Point& point); + + + //*************** region: focus *************** + + //Request focus for specified control. + bool RequestFocusFor(Control* control); + + //Get the control that has focus. + Control* GetFocusControl(); + + + //*************** region: mouse capture *************** + + Control* CaptureMouseFor(Control* control); + Control* ReleaseCurrentMouseCapture(); + + //*************** region: debug *************** +#ifdef CRU_DEBUG_DRAW_CONTROL_BORDER + bool GetDebugDrawControlBorder() const + { + return debug_draw_control_border_; + } + + void SetDebugDrawControlBorder(bool value); +#endif + + public: + //*************** region: events *************** + events::UiEvent activated_event; + events::UiEvent deactivated_event; + + events::WindowNativeMessageEvent native_message_event; + + private: + //*************** region: native operations *************** + + //Get the client rect in pixel. + RECT GetClientRectPixel(); + + bool IsMessageInQueue(UINT message); + + + //*************** 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 OnKeyDownInternal(int virtual_code); + void OnKeyUpInternal(int virtual_code); + void OnCharInternal(wchar_t c); + + void OnActivatedInternal(); + void OnDeactivatedInternal(); + + //*************** region: event dispatcher helper *************** + + template + using EventMethod = void (Control::*)(EventArgs&); + + // Dispatch the event. + // + // This will invoke the "event_method" of the control and its parent and parent's + // parent ... (until "last_receiver" if it's not nullptr) with appropriate args. + // + // 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 + void DispatchEvent(Control* original_sender, EventMethod event_method, Control* last_receiver, Args&&... args) + { + auto control = original_sender; + while (control != nullptr && control != last_receiver) + { + EventArgs event_args(control, original_sender, std::forward(args)...); + (control->*event_method)(event_args); + control = control->GetParent(); + } + } + + void DispatchMouseHoverControlChangeEvent(Control* old_control, Control * new_control, const Point& point); + + private: + HWND hwnd_ = nullptr; + std::shared_ptr render_target_{}; + + std::list control_list_{}; + + Control* mouse_hover_control_ = nullptr; + + bool window_focus_ = false; + Control* focus_control_ = this; // "focus_control_" can't be nullptr + + Control* mouse_capture_control_ = nullptr; + +#ifdef CRU_DEBUG_DRAW_CONTROL_BORDER + bool debug_draw_control_border_ = false; +#endif + }; + } +} -- cgit v1.2.3 From 03d078796ad31eb55d70ad0aa61254b301e989a6 Mon Sep 17 00:00:00 2001 From: crupest Date: Tue, 25 Sep 2018 13:26:20 +0800 Subject: ... --- script/build.ps1 | 3 +++ 1 file changed, 3 insertions(+) (limited to 'script/build.ps1') diff --git a/script/build.ps1 b/script/build.ps1 index 0a72d30c..d236421c 100644 --- a/script/build.ps1 +++ b/script/build.ps1 @@ -8,5 +8,8 @@ function CheckLastExitCode { vcpkg.exe install folly:x64-windows fmt:x64-windows & CheckLastExitCode +vcpkg.exe integrate install +& CheckLastExitCode + & "C:\Program Files (x86)\Microsoft Visual Studio\2017\Community\MSBuild\15.0\Bin\MSBuild.exe" CruUI.sln /target:Rebuild /p:Platform=x64 /p:Configuration=Debug & CheckLastExitCode -- cgit v1.2.3