diff options
author | crupest <crupest@outlook.com> | 2019-03-19 16:21:54 +0800 |
---|---|---|
committer | crupest <crupest@outlook.com> | 2019-03-19 16:21:54 +0800 |
commit | 5dc738a57930271194bd86673eb86f149096a7b2 (patch) | |
tree | 71174aba0d1c0918cc7d7a1be0b86ec0d5c20401 | |
parent | 06edefebe8dfb138404397fb2c46732da6cd733a (diff) | |
download | cru-5dc738a57930271194bd86673eb86f149096a7b2.tar.gz cru-5dc738a57930271194bd86673eb86f149096a7b2.tar.bz2 cru-5dc738a57930271194bd86673eb86f149096a7b2.zip |
...
41 files changed, 1260 insertions, 2221 deletions
diff --git a/.clang-format b/.clang-format new file mode 100644 index 00000000..f6cb8ad9 --- /dev/null +++ b/.clang-format @@ -0,0 +1 @@ +BasedOnStyle: Google diff --git a/CruUI.vcxproj b/CruUI.vcxproj index ee2c7eb1..9bedd3f8 100644 --- a/CruUI.vcxproj +++ b/CruUI.vcxproj @@ -117,10 +117,10 @@ </ClCompile> </ItemDefinitionGroup> <ItemGroup> - <ClCompile Include="src\any_map.cpp" /> + <ClCompile Include="src\ui\window_class.cpp" /> + <ClCompile Include="src\util\any_map.cpp" /> <ClCompile Include="src\cru_debug.cpp" /> <ClCompile Include="src\application.cpp" /> - <ClCompile Include="src\base.cpp" /> <ClCompile Include="src\exception.cpp" /> <ClCompile Include="src\graph\graph.cpp" /> <ClCompile Include="src\main.cpp" /> @@ -136,9 +136,11 @@ <ClCompile Include="src\ui\controls\scroll_control.cpp" /> <ClCompile Include="src\ui\controls\text_block.cpp" /> <ClCompile Include="src\ui\controls\text_box.cpp" /> - <ClInclude Include="src\any_map.hpp" /> - <ClInclude Include="src\format.hpp" /> - <ClInclude Include="src\math_util.hpp" /> + <ClCompile Include="src\util\string_util.cpp" /> + <ClInclude Include="src\ui\window_class.hpp" /> + <ClInclude Include="src\util\any_map.hpp" /> + <ClInclude Include="src\util\format.hpp" /> + <ClInclude Include="src\util\math_util.hpp" /> <ClInclude Include="src\ui\border_property.hpp" /> <ClInclude Include="src\ui\controls\frame_layout.hpp" /> <ClInclude Include="src\ui\controls\list_item.hpp" /> @@ -150,10 +152,12 @@ <ClCompile Include="src\ui\events\ui_event.cpp" /> <ClCompile Include="src\ui\input_util.cpp" /> <ClCompile Include="src\ui\layout_base.cpp" /> + <ClCompile Include="src\ui\render\linear_layout_render_object.cpp" /> <ClCompile Include="src\ui\render\render_object.cpp" /> <ClCompile Include="src\ui\ui_manager.cpp" /> <ClCompile Include="src\ui\ui_base.cpp" /> <ClCompile Include="src\ui\window.cpp" /> + <ClInclude Include="src\util\string_util.hpp" /> </ItemGroup> <ItemGroup> <ClInclude Include="src\application.hpp" /> @@ -178,6 +182,7 @@ <ClInclude Include="src\ui\events\ui_event.hpp" /> <ClInclude Include="src\ui\input_util.hpp" /> <ClInclude Include="src\ui\layout_base.hpp" /> + <ClInclude Include="src\ui\render\linear_layout_render_object.hpp" /> <ClInclude Include="src\ui\render\render_object.hpp" /> <ClInclude Include="src\ui\ui_manager.hpp" /> <ClInclude Include="src\ui\ui_base.hpp" /> @@ -186,6 +191,9 @@ <ItemGroup> <Xml Include="snippets\vc++snippets.snippet" /> </ItemGroup> + <ItemGroup> + <None Include=".clang-format" /> + </ItemGroup> <Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" /> <ImportGroup Label="ExtensionTargets"> </ImportGroup> diff --git a/CruUI.vcxproj.filters b/CruUI.vcxproj.filters index 13976a7d..9a85da3f 100644 --- a/CruUI.vcxproj.filters +++ b/CruUI.vcxproj.filters @@ -18,9 +18,6 @@ <ClCompile Include="src\application.cpp"> <Filter>Source Files</Filter> </ClCompile> - <ClCompile Include="src\base.cpp"> - <Filter>Source Files</Filter> - </ClCompile> <ClCompile Include="src\exception.cpp"> <Filter>Source Files</Filter> </ClCompile> @@ -75,9 +72,6 @@ <ClCompile Include="src\cru_debug.cpp"> <Filter>Source Files</Filter> </ClCompile> - <ClCompile Include="src\any_map.cpp"> - <Filter>Source Files</Filter> - </ClCompile> <ClCompile Include="src\ui\ui_manager.cpp"> <Filter>Source Files</Filter> </ClCompile> @@ -96,6 +90,21 @@ <ClCompile Include="src\ui\render\render_object.cpp"> <Filter>Source Files</Filter> </ClCompile> + <ClCompile Include="src\ui\input_util.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="src\ui\render\linear_layout_render_object.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="src\util\string_util.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="src\util\any_map.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="src\ui\window_class.cpp"> + <Filter>Source Files</Filter> + </ClCompile> </ItemGroup> <ItemGroup> <ClInclude Include="src\graph\graph.hpp"> @@ -158,9 +167,6 @@ <ClInclude Include="src\exception.hpp"> <Filter>Header Files</Filter> </ClInclude> - <ClInclude Include="src\format.hpp"> - <Filter>Header Files</Filter> - </ClInclude> <ClInclude Include="src\system_headers.hpp"> <Filter>Header Files</Filter> </ClInclude> @@ -170,9 +176,6 @@ <ClInclude Include="src\ui\ui_manager.hpp"> <Filter>Header Files</Filter> </ClInclude> - <ClInclude Include="src\any_map.hpp"> - <Filter>Header Files</Filter> - </ClInclude> <ClInclude Include="src\ui\controls\list_item.hpp"> <Filter>Header Files</Filter> </ClInclude> @@ -185,9 +188,6 @@ <ClInclude Include="src\ui\controls\scroll_control.hpp"> <Filter>Header Files</Filter> </ClInclude> - <ClInclude Include="src\math_util.hpp"> - <Filter>Header Files</Filter> - </ClInclude> <ClInclude Include="src\pre.hpp"> <Filter>Header Files</Filter> </ClInclude> @@ -197,6 +197,27 @@ <ClInclude Include="src\ui\d2d_util.hpp"> <Filter>Header Files</Filter> </ClInclude> + <ClInclude Include="src\ui\input_util.hpp"> + <Filter>Header Files</Filter> + </ClInclude> + <ClInclude Include="src\ui\render\linear_layout_render_object.hpp"> + <Filter>Header Files</Filter> + </ClInclude> + <ClInclude Include="src\util\format.hpp"> + <Filter>Header Files</Filter> + </ClInclude> + <ClInclude Include="src\util\math_util.hpp"> + <Filter>Header Files</Filter> + </ClInclude> + <ClInclude Include="src\util\string_util.hpp"> + <Filter>Header Files</Filter> + </ClInclude> + <ClInclude Include="src\util\any_map.hpp"> + <Filter>Header Files</Filter> + </ClInclude> + <ClInclude Include="src\ui\window_class.hpp"> + <Filter>Header Files</Filter> + </ClInclude> </ItemGroup> <ItemGroup> <ClCompile Include="src\application.cpp"> @@ -257,4 +278,7 @@ <ItemGroup> <Xml Include="snippets\vc++snippets.snippet" /> </ItemGroup> + <ItemGroup> + <None Include=".clang-format" /> + </ItemGroup> </Project>
\ No newline at end of file diff --git a/snippets/vc++snippets.snippet b/snippets/vc++snippets.snippet index 1a5f7666..ebd72463 100644 --- a/snippets/vc++snippets.snippet +++ b/snippets/vc++snippets.snippet @@ -5,7 +5,7 @@ <Title>Deleted Copy Constructor/Assignment</Title> <Author>crupest</Author> <Description>Declare a deleted copy constructor and a deleted copy assignment operator for a class.</Description> - <Shortcut>dcopy</Shortcut> + <Shortcut>deletecopy</Shortcut> <SnippetTypes> <SnippetType>Expansion</SnippetType> </SnippetTypes> @@ -28,10 +28,36 @@ </CodeSnippet> <CodeSnippet Format="1.0.0"> <Header> + <Title>Default Copy Constructor/Assignment</Title> + <Author>crupest</Author> + <Description>Declare a default copy constructor and a default copy assignment operator for a class.</Description> + <Shortcut>defaultcopy</Shortcut> + <SnippetTypes> + <SnippetType>Expansion</SnippetType> + </SnippetTypes> + </Header> + <Snippet> + <Declarations> + <Literal> + <ID>classname</ID> + <Default>class_name</Default> + <ToolTip>The name of the class.</ToolTip> + </Literal> + </Declarations> + <Code Language="CPP" Kind="method decl"> + <![CDATA[ + $classname$(const $classname$& other) = default; + $classname$& operator=(const $classname$& other) = default; + ]]> + </Code> + </Snippet> + </CodeSnippet> + <CodeSnippet Format="1.0.0"> + <Header> <Title>Deleted Move Constructor/Assignment</Title> <Author>crupest</Author> <Description>Declare a deleted move constructor and a deleted move assignment operator for a class.</Description> - <Shortcut>dmove</Shortcut> + <Shortcut>deletemove</Shortcut> <SnippetTypes> <SnippetType>Expansion</SnippetType> </SnippetTypes> @@ -52,4 +78,98 @@ </Code> </Snippet> </CodeSnippet> + <CodeSnippet Format="1.0.0"> + <Header> + <Title>Default Move Constructor/Assignment</Title> + <Author>crupest</Author> + <Description>Declare a default move constructor and a default move assignment operator for a class.</Description> + <Shortcut>defaultmove</Shortcut> + <SnippetTypes> + <SnippetType>Expansion</SnippetType> + </SnippetTypes> + </Header> + <Snippet> + <Declarations> + <Literal> + <ID>classname</ID> + <Default>class_name</Default> + <ToolTip>The name of the class.</ToolTip> + </Literal> + </Declarations> + <Code Language="CPP" Kind="method decl"> + <![CDATA[ + $classname$($classname$&& other) = default; + $classname$& operator=($classname$&& other) = default; + ]]> + </Code> + </Snippet> + </CodeSnippet> + <CodeSnippet Format="1.0.0"> + <Header> + <Title>Default Copyable/Movable Class</Title> + <Author>crupest</Author> + <Description>Define a class with default constructor, destructor, move/copy constructor/assignment-operator.</Description> + <Shortcut>defaultclass</Shortcut> + <SnippetTypes> + <SnippetType>Expansion</SnippetType> + </SnippetTypes> + </Header> + <Snippet> + <Declarations> + <Literal> + <ID>classname</ID> + <Default>class_name</Default> + <ToolTip>The name of the class.</ToolTip> + </Literal> + </Declarations> + <Code Language="CPP" Kind="type decl"> + <![CDATA[ + class $classname$ + { + public: + $classname$() = default; + $classname$(const $classname$& other) = default; + $classname$& operator=(const $classname$& other) = default; + $classname$($classname$&& other) = default; + $classname$& operator=($classname$&& other) = default; + ~$classname$() = default; + }; + ]]> + </Code> + </Snippet> + </CodeSnippet> + <CodeSnippet Format="1.0.0"> + <Header> + <Title>Default Noncopyable/Nonmovable Class</Title> + <Author>crupest</Author> + <Description>Define a class with default constructor, destructor and deleted move/copy constructor/assignment-operator.</Description> + <Shortcut>objectclass</Shortcut> + <SnippetTypes> + <SnippetType>Expansion</SnippetType> + </SnippetTypes> + </Header> + <Snippet> + <Declarations> + <Literal> + <ID>classname</ID> + <Default>class_name</Default> + <ToolTip>The name of the class.</ToolTip> + </Literal> + </Declarations> + <Code Language="CPP" Kind="type decl"> + <![CDATA[ + class $classname$ + { + public: + $classname$() = default; + $classname$(const $classname$& other) = delete; + $classname$& operator=(const $classname$& other) = delete; + $classname$($classname$&& other) = delete; + $classname$& operator=($classname$&& other) = delete; + ~$classname$() = default; + }; + ]]> + </Code> + </Snippet> + </CodeSnippet> </CodeSnippets>
\ No newline at end of file diff --git a/src/any_map.cpp b/src/any_map.cpp deleted file mode 100644 index de13f85e..00000000 --- a/src/any_map.cpp +++ /dev/null @@ -1,33 +0,0 @@ -#include "any_map.hpp" - -namespace cru -{ - AnyMap::ListenerToken AnyMap::RegisterValueChangeListener(const String& key, const Listener& listener) - { - const auto token = current_listener_token_++; - map_[key].second.push_back(token); - listeners_.emplace(token, listener); - return token; - } - - void AnyMap::UnregisterValueChangeListener(const ListenerToken token) - { - const auto find_result = listeners_.find(token); - if (find_result != listeners_.cend()) - listeners_.erase(find_result); - } - - void AnyMap::InvokeListeners(std::list<ListenerToken>& listener_list, const std::any& value) - { - auto i = listener_list.cbegin(); - while (i != listener_list.cend()) - { - auto current_i = i++; - const auto find_result = listeners_.find(*current_i); - if (find_result != listeners_.cend()) - find_result->second(value); - else - listener_list.erase(current_i); // otherwise remove the invalid listener token. - } - } -} diff --git a/src/any_map.hpp b/src/any_map.hpp deleted file mode 100644 index dfc54f3f..00000000 --- a/src/any_map.hpp +++ /dev/null @@ -1,103 +0,0 @@ -#pragma once - -// ReSharper disable once CppUnusedIncludeDirective -#include "pre.hpp" - -#include <any> -#include <unordered_map> -#include <functional> -#include <optional> -#include <typeinfo> - -#include "base.hpp" -#include "format.hpp" - - -namespace cru -{ - // A map with String as key and any type as value. - // It also has notification when value with specified key changed. - class AnyMap : public Object - { - public: - using ListenerToken = long; - using Listener = std::function<void(const std::any&)>; - - AnyMap() = default; - AnyMap(const AnyMap& other) = delete; - AnyMap(AnyMap&& other) = delete; - AnyMap& operator=(const AnyMap& other) = delete; - AnyMap& operator=(AnyMap&& other) = delete; - ~AnyMap() override = default; - - - // return the value if the value exists and the type of value is T. - // return a null optional if value doesn't exists. - // throw std::runtime_error if type is mismatch. - template <typename T> - std::optional<T> GetOptionalValue(const String& key) const - { - try - { - const auto find_result = map_.find(key); - if (find_result != map_.cend()) - { - const auto& value = find_result->second.first; - if (value.has_value()) - return std::any_cast<T>(value); - return std::nullopt; - } - return std::nullopt; - } - catch (const std::bad_any_cast&) - { - throw std::runtime_error(Format("Value of key \"{}\" in AnyMap is not of the type {}.", ToUtf8String(key), typeid(T).name())); - } - } - - // return the value if the value exists and the type of value is T. - // throw if value doesn't exists. (different from "GetOptionalValue"). - // throw std::runtime_error if type is mismatch. - template <typename T> - T GetValue(const String& key) const - { - const auto optional_value = GetOptionalValue<T>(key); - if (optional_value.has_value()) - return optional_value.value(); - else - throw std::runtime_error(Format("Key \"{}\" does not exists in AnyMap.", ToUtf8String(key))); - } - - // Set the value of key, and trigger all related listeners. - template <typename T> - void SetValue(const String& key, T&& value) - { - auto& p = map_[key]; - p.first = std::make_any<T>(std::forward<T>(value)); - InvokeListeners(p.second, p.first); - } - - // Remove the value of the key. - void ClearValue(const String& key) - { - auto& p = map_[key]; - p.first = std::any{}; - InvokeListeners(p.second, std::any{}); - } - - // Add a listener which is called when value of key is changed. - // Return a token used to remove the listener. - ListenerToken RegisterValueChangeListener(const String& key, const Listener& listener); - - // Remove a listener by token. - void UnregisterValueChangeListener(ListenerToken token); - - private: - void InvokeListeners(std::list<ListenerToken>& listener_list, const std::any& value); - - private: - std::unordered_map<String, std::pair<std::any, std::list<ListenerToken>>> map_{}; - std::unordered_map<ListenerToken, Listener> listeners_{}; - ListenerToken current_listener_token_ = 0; - }; -} diff --git a/src/application.cpp b/src/application.cpp index c3669f72..e580b56b 100644 --- a/src/application.cpp +++ b/src/application.cpp @@ -2,135 +2,113 @@ #include "exception.hpp" #include "timer.hpp" -#include "ui/window.hpp" #include "ui/cursor.hpp" +#include "ui/window_class.hpp" namespace cru { - constexpr auto god_window_class_name = L"GodWindowClass"; - constexpr int invoke_later_message_id = WM_USER + 2000; - - - LRESULT CALLBACK GodWndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) - { - const auto app = Application::GetInstance(); - - if (app) - { - const auto result = app->GetGodWindow()->HandleGodWindowMessage(hWnd, uMsg, wParam, lParam); - if (result.has_value()) - return result.value(); - else - return DefWindowProc(hWnd, uMsg, wParam, lParam); - } - else - return DefWindowProc(hWnd, uMsg, wParam, lParam); - } +constexpr auto god_window_class_name = L"GodWindowClass"; +constexpr int invoke_later_message_id = WM_USER + 2000; + +LRESULT CALLBACK GodWndProc(HWND hWnd, UINT uMsg, WPARAM wParam, + LPARAM lParam) { + const auto app = Application::GetInstance(); + + if (app) { + const auto result = + app->GetGodWindow()->HandleGodWindowMessage(hWnd, uMsg, wParam, lParam); + if (result.has_value()) + return result.value(); + else + return DefWindowProc(hWnd, uMsg, wParam, lParam); + } else + return DefWindowProc(hWnd, uMsg, wParam, lParam); +} - GodWindow::GodWindow(Application* application) - { - const auto h_instance = application->GetInstanceHandle(); +GodWindow::GodWindow(Application* application) { + const auto h_instance = application->GetInstanceHandle(); - god_window_class_ = std::make_unique<ui::WindowClass>(god_window_class_name, GodWndProc, h_instance); + god_window_class_ = std::make_unique<ui::WindowClass>(god_window_class_name, + GodWndProc, h_instance); - hwnd_ = CreateWindowEx(0, - god_window_class_name, - L"", 0, - CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, - HWND_MESSAGE, nullptr, h_instance, nullptr - ); + hwnd_ = CreateWindowEx(0, god_window_class_name, L"", 0, CW_USEDEFAULT, + CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, + HWND_MESSAGE, nullptr, h_instance, nullptr); - if (hwnd_ == nullptr) - throw std::runtime_error("Failed to create window."); - } + if (hwnd_ == nullptr) throw std::runtime_error("Failed to create window."); +} - GodWindow::~GodWindow() - { - ::DestroyWindow(hwnd_); +GodWindow::~GodWindow() { ::DestroyWindow(hwnd_); } + +std::optional<LRESULT> GodWindow::HandleGodWindowMessage(HWND hwnd, int msg, + WPARAM w_param, + LPARAM l_param) { + switch (msg) { + case invoke_later_message_id: { + const auto p_action = reinterpret_cast<std::function<void()>*>(w_param); + (*p_action)(); + delete p_action; + return 0; } - - std::optional<LRESULT> GodWindow::HandleGodWindowMessage(HWND hwnd, int msg, WPARAM w_param, LPARAM l_param) - { - switch (msg) - { - case invoke_later_message_id: - { - const auto p_action = reinterpret_cast<std::function<void()>*>(w_param); - (*p_action)(); - delete p_action; - return 0; - } - case WM_TIMER: - { - const auto id = static_cast<UINT_PTR>(w_param); - const auto action = TimerManager::GetInstance()->GetAction(id); - if (action.has_value()) - { - (action.value().second)(); - if (!action.value().first) - TimerManager::GetInstance()->KillTimer(id); - return 0; - } - break; - } - default: - return std::nullopt; - } - return std::nullopt; + case WM_TIMER: { + const auto id = static_cast<UINT_PTR>(w_param); + const auto action = TimerManager::GetInstance()->GetAction(id); + if (action.has_value()) { + (action.value().second)(); + if (!action.value().first) TimerManager::GetInstance()->KillTimer(id); + return 0; + } + break; } + default: + return std::nullopt; + } + return std::nullopt; +} +Application* Application::instance_ = nullptr; +Application* Application::GetInstance() { return instance_; } - 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."); +Application::Application(HINSTANCE h_instance) : h_instance_(h_instance) { + if (instance_) + throw std::runtime_error("A application instance already exists."); - instance_ = this; + instance_ = this; - if (!::IsWindows8OrGreater()) - throw std::runtime_error("Must run on Windows 8 or later."); + if (!::IsWindows8OrGreater()) + throw std::runtime_error("Must run on Windows 8 or later."); - god_window_ = std::make_unique<GodWindow>(this); + god_window_ = std::make_unique<GodWindow>(this); - ui::cursors::LoadSystemCursors(); - } + ui::cursors::LoadSystemCursors(); +} - Application::~Application() - { - for (auto i = singleton_list_.crbegin(); i != singleton_list_.crend(); ++i) - delete *i; - instance_ = nullptr; - } +Application::~Application() { + for (auto i = singleton_list_.crbegin(); i != singleton_list_.crend(); ++i) + delete *i; + instance_ = nullptr; +} - int Application::Run() - { - MSG msg; +int Application::Run() { + MSG msg; - while (GetMessage(&msg, nullptr, 0, 0)) - { - TranslateMessage(&msg); - DispatchMessage(&msg); - } + while (GetMessage(&msg, nullptr, 0, 0)) { + TranslateMessage(&msg); + DispatchMessage(&msg); + } - return static_cast<int>(msg.wParam); - } + return static_cast<int>(msg.wParam); +} - void Application::Quit(const int quit_code) { - ::PostQuitMessage(quit_code); - } +void Application::Quit(const int quit_code) { ::PostQuitMessage(quit_code); } - void InvokeLater(const std::function<void()>& action) { - //copy the action to a safe place - auto p_action_copy = new std::function<void()>(action); +void InvokeLater(const std::function<void()>& action) { + // copy the action to a safe place + auto p_action_copy = new std::function<void()>(action); - if (PostMessageW(Application::GetInstance()->GetGodWindow()->GetHandle(), invoke_later_message_id, reinterpret_cast<WPARAM>(p_action_copy), 0) == 0) - throw Win32Error(::GetLastError(), "InvokeLater failed to post message."); - } + if (PostMessageW(Application::GetInstance()->GetGodWindow()->GetHandle(), + invoke_later_message_id, + reinterpret_cast<WPARAM>(p_action_copy), 0) == 0) + throw Win32Error(::GetLastError(), "InvokeLater failed to post message."); } +} // namespace cru diff --git a/src/application.hpp b/src/application.hpp index 8d739938..acf264c3 100644 --- a/src/application.hpp +++ b/src/application.hpp @@ -1,14 +1,12 @@ #pragma once - -// ReSharper disable once CppUnusedIncludeDirective #include "pre.hpp" -#include "system_headers.hpp" +#include <functional> #include <memory> #include <optional> -#include <functional> -#include <typeindex> #include <type_traits> +#include <typeindex> +#include "system_headers.hpp" #include "base.hpp" @@ -16,103 +14,92 @@ #include <unordered_set> #endif -namespace cru -{ - class Application; - - namespace ui - { - class WindowClass; - } - - - class GodWindow : public Object - { - public: - explicit GodWindow(Application* application); - GodWindow(const GodWindow& other) = delete; - GodWindow(GodWindow&& other) = delete; - GodWindow& operator=(const GodWindow& other) = delete; - GodWindow& operator=(GodWindow&& other) = delete; - ~GodWindow() override; - - HWND GetHandle() const - { - return hwnd_; - } - - std::optional<LRESULT> HandleGodWindowMessage(HWND hwnd, int msg, WPARAM w_param, LPARAM l_param); - - private: - std::unique_ptr<ui::WindowClass> god_window_class_; - HWND hwnd_; - }; - - class Application : public Object - { - public: - static Application* GetInstance(); - private: - static Application* instance_; - - public: - explicit Application(HINSTANCE h_instance); - Application(const Application&) = delete; - Application(Application&&) = delete; - Application& operator = (const Application&) = delete; - Application& operator = (Application&&) = delete; - ~Application() override; - - public: - int Run(); - void Quit(int quit_code); - - - HINSTANCE GetInstanceHandle() const - { - return h_instance_; - } - - GodWindow* GetGodWindow() const - { - return god_window_.get(); - } - - // Resolve a singleton. - // All singletons will be delete in reverse order of resolve. - template<typename T, typename = std::enable_if_t<std::is_base_of_v<Object, T>>> - T* ResolveSingleton(const std::function<T*(Application*)>& creator) - { - const auto& index = std::type_index{typeid(T)}; - const auto find_result = singleton_map_.find(index); - if (find_result != singleton_map_.cend()) - return static_cast<T*>(find_result->second); +namespace cru { +class Application; + +namespace ui { +class WindowClass; +} + +class GodWindow : public Object { + public: + explicit GodWindow(Application* application); + GodWindow(const GodWindow& other) = delete; + GodWindow(GodWindow&& other) = delete; + GodWindow& operator=(const GodWindow& other) = delete; + GodWindow& operator=(GodWindow&& other) = delete; + ~GodWindow() override; + + HWND GetHandle() const { return hwnd_; } + + std::optional<LRESULT> HandleGodWindowMessage(HWND hwnd, int msg, + WPARAM w_param, LPARAM l_param); + + private: + std::unique_ptr<ui::WindowClass> god_window_class_; + HWND hwnd_; +}; + +class Application : public Object { + public: + static Application* GetInstance(); + + private: + static Application* instance_; + + public: + explicit Application(HINSTANCE h_instance); + Application(const Application&) = delete; + Application(Application&&) = delete; + Application& operator=(const Application&) = delete; + Application& operator=(Application&&) = delete; + ~Application() override; + + public: + int Run(); + void Quit(int quit_code); + + HINSTANCE GetInstanceHandle() const { return h_instance_; } + + GodWindow* GetGodWindow() const { return god_window_.get(); } + + // Resolve a singleton. + // All singletons will be delete in reverse order of resolve. + template <typename T, + typename = std::enable_if_t<std::is_base_of_v<Object, T>>> + T* ResolveSingleton(const std::function<T*(Application*)>& creator) { + const auto& index = std::type_index{typeid(T)}; + const auto find_result = singleton_map_.find(index); + if (find_result != singleton_map_.cend()) + return static_cast<T*>(find_result->second); #ifdef CRU_DEBUG - const auto type_find_result = singleton_type_set_.find(index); - if (type_find_result != singleton_type_set_.cend()) - throw std::logic_error("The singleton of that type is being constructed. This may cause a dead recursion."); - singleton_type_set_.insert(index); + const auto type_find_result = singleton_type_set_.find(index); + if (type_find_result != singleton_type_set_.cend()) + throw std::logic_error( + "The singleton of that type is being constructed. This may cause a " + "dead recursion."); + singleton_type_set_.insert(index); #endif - auto singleton = creator(this); - singleton_map_.emplace(index, static_cast<Object*>(singleton)); - singleton_list_.push_back(singleton); - return singleton; - } + auto singleton = creator(this); + singleton_map_.emplace(index, static_cast<Object*>(singleton)); + singleton_list_.push_back(singleton); + return singleton; + } - private: - HINSTANCE h_instance_; + private: + HINSTANCE h_instance_; - std::unique_ptr<GodWindow> god_window_; + std::unique_ptr<GodWindow> god_window_; - std::unordered_map<std::type_index, Object*> singleton_map_; - std::list<Object*> singleton_list_; // used for reverse destroy. + std::unordered_map<std::type_index, Object*> singleton_map_; + std::list<Object*> singleton_list_; // used for reverse destroy. #ifdef CRU_DEBUG - std::unordered_set<std::type_index> singleton_type_set_; // used for detecting dead recursion. + std::unordered_set<std::type_index> + singleton_type_set_; // used for detecting dead recursion. #endif - }; - +}; - void InvokeLater(const std::function<void()>& action); -} +void InvokeLater(const std::function<void()>& action); +} // namespace cru diff --git a/src/base.cpp b/src/base.cpp deleted file mode 100644 index a2d20fc4..00000000 --- a/src/base.cpp +++ /dev/null @@ -1,20 +0,0 @@ -#include "base.hpp" - -#include "system_headers.hpp" -#include "exception.hpp" - -namespace cru -{ - MultiByteString ToUtf8String(const StringView& string) - { - if (string.empty()) - return MultiByteString(); - - const auto length = ::WideCharToMultiByte(CP_UTF8, 0, string.data(), -1, nullptr, 0, nullptr, nullptr); - MultiByteString result; - result.reserve(length); - if (::WideCharToMultiByte(CP_UTF8, 0, string.data(), -1, result.data(), static_cast<int>(result.capacity()), nullptr, nullptr) == 0) - throw Win32Error(::GetLastError(), "Failed to convert wide string to UTF-8."); - return result; - } -} diff --git a/src/base.hpp b/src/base.hpp index 64ce7f6e..a62a745b 100644 --- a/src/base.hpp +++ b/src/base.hpp @@ -1,59 +1,46 @@ #pragma once - -// ReSharper disable once CppUnusedIncludeDirective #include "pre.hpp" -#include <string> +#include <chrono> #include <stdexcept> +#include <string> #include <string_view> -#include <chrono> -namespace cru -{ - template<typename T> struct type_tag {}; - - //typedefs - using String = std::wstring; - using MultiByteString = std::string; - - using StringView = std::wstring_view; - using MultiByteStringView = std::string_view; - - using FloatSecond = std::chrono::duration<double, std::chrono::seconds::period>; - - enum class FlowControl - { - Continue, - Break - }; - - - class Object - { - public: - Object() = default; - Object(const Object&) = default; - Object& operator = (const Object&) = default; - Object(Object&&) = default; - Object& operator = (Object&&) = default; - virtual ~Object() = default; - }; - - struct Interface - { - virtual ~Interface() = default; - }; - - [[noreturn]] inline void UnreachableCode() - { - throw std::logic_error("Unreachable code."); - } - - MultiByteString ToUtf8String(const StringView& string); - - inline void Require(const bool condition, const MultiByteStringView& error_message) - { - if (!condition) - throw std::invalid_argument(error_message.data()); - } +namespace cru { +template <typename T> +struct type_tag {}; + +// typedefs +using String = std::wstring; +using MultiByteString = std::string; + +using StringView = std::wstring_view; +using MultiByteStringView = std::string_view; + +using FloatSecond = std::chrono::duration<double, std::chrono::seconds::period>; + +enum class FlowControl { Continue, Break }; + +class Object { + public: + Object() = default; + Object(const Object&) = default; + Object& operator=(const Object&) = default; + Object(Object&&) = default; + Object& operator=(Object&&) = default; + virtual ~Object() = default; +}; + +struct Interface { + virtual ~Interface() = default; +}; + +[[noreturn]] inline void UnreachableCode() { + throw std::logic_error("Unreachable code."); +} + +inline void Require(const bool condition, + const MultiByteStringView& error_message) { + if (!condition) throw std::invalid_argument(error_message.data()); } +} // namespace cru diff --git a/src/cru_debug.cpp b/src/cru_debug.cpp index 9c61d052..b9226132 100644 --- a/src/cru_debug.cpp +++ b/src/cru_debug.cpp @@ -2,10 +2,8 @@ #include "system_headers.hpp" -namespace cru::debug -{ - void DebugMessage(const StringView& message) - { - ::OutputDebugStringW(message.data()); - } +namespace cru::debug { +void DebugMessage(const StringView& message) { + ::OutputDebugStringW(message.data()); } +} // namespace cru::debug diff --git a/src/cru_debug.hpp b/src/cru_debug.hpp index 17cc7b53..9c22a24f 100644 --- a/src/cru_debug.hpp +++ b/src/cru_debug.hpp @@ -1,47 +1,46 @@ #pragma once - -// ReSharper disable once CppUnusedIncludeDirective #include "pre.hpp" #include <functional> #include "base.hpp" -#include "format.hpp" +#include "util/format.hpp" -namespace cru::debug -{ - void DebugMessage(const StringView& message); +namespace cru::debug { +void DebugMessage(const StringView& message); #ifdef CRU_DEBUG - inline void DebugTime(const std::function<void()>& action, const StringView& hint_message) - { - const auto before = std::chrono::steady_clock::now(); - action(); - const auto after = std::chrono::steady_clock::now(); - const auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(after - before); - DebugMessage(Format(L"{}: {}ms.\n", hint_message, duration.count())); - } +inline void DebugTime(const std::function<void()>& action, + const StringView& hint_message) { + const auto before = std::chrono::steady_clock::now(); + action(); + const auto after = std::chrono::steady_clock::now(); + const auto duration = + std::chrono::duration_cast<std::chrono::milliseconds>(after - before); + DebugMessage(util::Format(L"{}: {}ms.\n", hint_message, duration.count())); +} - template<typename TReturn> - TReturn DebugTime(const std::function<TReturn()>& action, const StringView& hint_message) - { - const auto before = std::chrono::steady_clock::now(); - auto&& result = action(); - const auto after = std::chrono::steady_clock::now(); - const auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(after - before); - DebugMessage(Format(L"{}: {}ms.\n", hint_message, duration.count())); - return std::move(result); - } +template <typename TReturn> +TReturn DebugTime(const std::function<TReturn()>& action, + const StringView& hint_message) { + const auto before = std::chrono::steady_clock::now(); + auto&& result = action(); + const auto after = std::chrono::steady_clock::now(); + const auto duration = + std::chrono::duration_cast<std::chrono::milliseconds>(after - before); + DebugMessage(util::Format(L"{}: {}ms.\n", hint_message, duration.count())); + return std::move(result); +} #else - inline void DebugTime(const std::function<void()>& action, const StringView& hint_message) - { - action(); - } +inline void DebugTime(const std::function<void()>& action, + const StringView& hint_message) { + action(); +} - template<typename TReturn> - TReturn DebugTime(const std::function<TReturn()>& action, const StringView& hint_message) - { - return action(); - } -#endif +template <typename TReturn> +TReturn DebugTime(const std::function<TReturn()>& action, + const StringView& hint_message) { + return action(); } +#endif +} // namespace cru::debug diff --git a/src/cru_event.hpp b/src/cru_event.hpp index fa41d646..a669695f 100644 --- a/src/cru_event.hpp +++ b/src/cru_event.hpp @@ -1,84 +1,68 @@ #pragma once - -// ReSharper disable once CppUnusedIncludeDirective #include "pre.hpp" -#include <type_traits> #include <functional> #include <map> +#include <type_traits> #include "base.hpp" namespace cru { - //Base class of all event args. - class BasicEventArgs : public Object - { - public: - explicit BasicEventArgs(Object* sender) - : sender_(sender) - { - - } - BasicEventArgs(const BasicEventArgs& other) = default; - BasicEventArgs(BasicEventArgs&& other) = default; - BasicEventArgs& operator=(const BasicEventArgs& other) = default; - BasicEventArgs& operator=(BasicEventArgs&& other) = default; - ~BasicEventArgs() override = default; - - //Get the sender of the event. - Object* GetSender() const - { - return sender_; - } - - private: - Object* sender_; - }; - - - //A non-copyable non-movable Event class. - //It stores a list of event handlers. - //TArgsType must be subclass of BasicEventArgs. - template<typename TArgsType> - class Event - { - public: - static_assert(std::is_base_of_v<BasicEventArgs, TArgsType>, - "TArgsType must be subclass of BasicEventArgs."); - - - using ArgsType = TArgsType; - using EventHandler = std::function<void(ArgsType&)>; - using EventHandlerToken = long; - - Event() = default; - Event(const Event&) = delete; - Event& operator = (const Event&) = delete; - Event(Event&&) = delete; - Event& operator = (Event&&) = delete; - ~Event() = default; - - EventHandlerToken AddHandler(const EventHandler& handler) - { - const auto token = current_token_++; - handlers_.emplace(token, handler); - return token; - } - - void RemoveHandler(const EventHandlerToken token) { - auto find_result = handlers_.find(token); - if (find_result != handlers_.cend()) - handlers_.erase(find_result); - } - - void Raise(ArgsType& args) { - for (const auto& handler : handlers_) - (handler.second)(args); - } - - private: - std::map<EventHandlerToken, EventHandler> handlers_; - - EventHandlerToken current_token_ = 0; - }; -} +// Base class of all event args. +class BasicEventArgs : public Object { + public: + explicit BasicEventArgs(Object* sender) : sender_(sender) {} + BasicEventArgs(const BasicEventArgs& other) = default; + BasicEventArgs(BasicEventArgs&& other) = default; + BasicEventArgs& operator=(const BasicEventArgs& other) = default; + BasicEventArgs& operator=(BasicEventArgs&& other) = default; + ~BasicEventArgs() override = default; + + // Get the sender of the event. + Object* GetSender() const { return sender_; } + + private: + Object* sender_; +}; + +// A non-copyable non-movable Event class. +// It stores a list of event handlers. +// TArgsType must be subclass of BasicEventArgs. +template <typename TArgsType> +class Event { + public: + static_assert(std::is_base_of_v<BasicEventArgs, TArgsType>, + "TArgsType must be subclass of BasicEventArgs."); + + using ArgsType = TArgsType; + using EventHandler = std::function<void(ArgsType&)>; + using EventHandlerToken = long; + + Event() = default; + Event(const Event&) = delete; + Event& operator=(const Event&) = delete; + Event(Event&&) = delete; + Event& operator=(Event&&) = delete; + ~Event() = default; + + EventHandlerToken AddHandler(const EventHandler& handler) { + const auto token = current_token_++; + handlers_.emplace(token, handler); + return token; + } + + void RemoveHandler(const EventHandlerToken token) { + auto find_result = handlers_.find(token); + if (find_result != handlers_.cend()) handlers_.erase(find_result); + } + + void Raise(ArgsType& args) { + for (const auto& handler : handlers_) (handler.second)(args); + } + + private: + std::map<EventHandlerToken, EventHandler> handlers_; + + EventHandlerToken current_token_ = 0; +}; +} // namespace cru diff --git a/src/exception.cpp b/src/exception.cpp index cb1ca2c7..dbc98453 100644 --- a/src/exception.cpp +++ b/src/exception.cpp @@ -1,40 +1,42 @@ #include "exception.hpp" -#include "format.hpp" - -namespace cru -{ - inline std::string HResultMakeMessage(HRESULT h_result, std::optional<MultiByteStringView> message) - { - char buffer[10]; - sprintf_s(buffer, "%#08x", h_result); - - if (message.has_value()) - return Format("An HResultError is thrown. HRESULT: {}.\nAdditional message: {}\n", buffer, message.value()); - else - return Format("An HResultError is thrown. HRESULT: {}.\n", buffer); - } - - HResultError::HResultError(HRESULT h_result, std::optional<MultiByteStringView> additional_message) - : runtime_error(HResultMakeMessage(h_result, std::nullopt)), h_result_(h_result) - { - - } - - inline std::string Win32MakeMessage(DWORD error_code, std::optional<MultiByteStringView> message) - { - char buffer[10]; - sprintf_s(buffer, "%#04x", error_code); - - if (message.has_value()) - return Format("Last error code: {}.\nAdditional message: {}\n", buffer, message.value()); - else - return Format("Last error code: {}.\n", buffer); - } - - Win32Error::Win32Error(DWORD error_code, std::optional<MultiByteStringView> additional_message) - : runtime_error(Win32MakeMessage(error_code, std::nullopt)), error_code_(error_code) - { - - } +#include "util/format.hpp" + +namespace cru { +using util::Format; + +inline std::string HResultMakeMessage( + HRESULT h_result, std::optional<MultiByteStringView> message) { + char buffer[10]; + sprintf_s(buffer, "%#08x", h_result); + + if (message.has_value()) + return Format( + "An HResultError is thrown. HRESULT: {}.\nAdditional message: {}\n", + buffer, message.value()); + else + return Format("An HResultError is thrown. HRESULT: {}.\n", buffer); } + +HResultError::HResultError( + HRESULT h_result, std::optional<MultiByteStringView> additional_message) + : runtime_error(HResultMakeMessage(h_result, std::nullopt)), + h_result_(h_result) {} + +inline std::string Win32MakeMessage( + DWORD error_code, std::optional<MultiByteStringView> message) { + char buffer[10]; + sprintf_s(buffer, "%#04x", error_code); + + if (message.has_value()) + return Format("Last error code: {}.\nAdditional message: {}\n", buffer, + message.value()); + else + return Format("Last error code: {}.\n", buffer); +} + +Win32Error::Win32Error(DWORD error_code, + std::optional<MultiByteStringView> additional_message) + : runtime_error(Win32MakeMessage(error_code, std::nullopt)), + error_code_(error_code) {} +} // namespace cru diff --git a/src/exception.hpp b/src/exception.hpp index b8cef604..db2572f1 100644 --- a/src/exception.hpp +++ b/src/exception.hpp @@ -1,60 +1,52 @@ #pragma once - -// ReSharper disable once CppUnusedIncludeDirective #include "pre.hpp" -#include "system_headers.hpp" #include <optional> +#include "system_headers.hpp" #include "base.hpp" - namespace cru { - class HResultError : public std::runtime_error - { - public: - explicit HResultError(HRESULT h_result, std::optional<MultiByteStringView> additional_message = std::nullopt); - HResultError(const HResultError& other) = default; - HResultError(HResultError&& other) = default; - HResultError& operator=(const HResultError& other) = default; - HResultError& operator=(HResultError&& other) = default; - ~HResultError() override = default; - - HRESULT GetHResult() const - { - return h_result_; - } - - private: - HRESULT h_result_; - }; - - inline void ThrowIfFailed(const HRESULT h_result) { - if (FAILED(h_result)) - throw HResultError(h_result); - } - - inline void ThrowIfFailed(const HRESULT h_result, const MultiByteStringView& message) { - if (FAILED(h_result)) - throw HResultError(h_result, message); - } - - class Win32Error : public std::runtime_error - { - public: - explicit Win32Error(DWORD error_code, std::optional<MultiByteStringView> additional_message = std::nullopt); - Win32Error(const Win32Error& other) = default; - Win32Error(Win32Error&& other) = default; - Win32Error& operator=(const Win32Error& other) = default; - Win32Error& operator=(Win32Error&& other) = default; - ~Win32Error() override = default; - - HRESULT GetErrorCode() const - { - return error_code_; - } - - private: - DWORD error_code_; - }; +class HResultError : public std::runtime_error { + public: + explicit HResultError( + HRESULT h_result, + std::optional<MultiByteStringView> additional_message = std::nullopt); + HResultError(const HResultError& other) = default; + HResultError(HResultError&& other) = default; + HResultError& operator=(const HResultError& other) = default; + HResultError& operator=(HResultError&& other) = default; + ~HResultError() override = default; + + HRESULT GetHResult() const { return h_result_; } + + private: + HRESULT h_result_; +}; + +inline void ThrowIfFailed(const HRESULT h_result) { + if (FAILED(h_result)) throw HResultError(h_result); } + +inline void ThrowIfFailed(const HRESULT h_result, + const MultiByteStringView& message) { + if (FAILED(h_result)) throw HResultError(h_result, message); +} + +class Win32Error : public std::runtime_error { + public: + explicit Win32Error( + DWORD error_code, + std::optional<MultiByteStringView> additional_message = std::nullopt); + Win32Error(const Win32Error& other) = default; + Win32Error(Win32Error&& other) = default; + Win32Error& operator=(const Win32Error& other) = default; + Win32Error& operator=(Win32Error&& other) = default; + ~Win32Error() override = default; + + HRESULT GetErrorCode() const { return error_code_; } + + private: + DWORD error_code_; +}; +} // namespace cru diff --git a/src/format.hpp b/src/format.hpp deleted file mode 100644 index efd25f89..00000000 --- a/src/format.hpp +++ /dev/null @@ -1,110 +0,0 @@ -#pragma once - -// ReSharper disable once CppUnusedIncludeDirective -#include "pre.hpp" - -#include "base.hpp" - -namespace cru -{ - namespace details - { - constexpr StringView PlaceHolder(type_tag<String>) - { - return StringView(L"{}"); - } - - constexpr MultiByteStringView PlaceHolder(type_tag<MultiByteString>) - { - return MultiByteStringView("{}"); - } - - template<typename TString> - void FormatInternal(TString& string) - { - const auto find_result = string.find(PlaceHolder(type_tag<TString>{})); - if (find_result != TString::npos) - throw std::invalid_argument("There is more placeholders than args."); - } - - template<typename TString, typename T, typename... TRest> - void FormatInternal(TString& string, const T& arg, const TRest&... args) - { - const auto find_result = string.find(PlaceHolder(type_tag<TString>{})); - if (find_result == TString::npos) - throw std::invalid_argument("There is less placeholders than args."); - - string.replace(find_result, 2, FormatToString(arg, type_tag<TString>{})); - FormatInternal<TString>(string, args...); - } - } - - template<typename... T> - String Format(const StringView& format, const T&... args) - { - String result(format); - details::FormatInternal<String>(result, args...); - return result; - } - - template<typename... T> - MultiByteString Format(const MultiByteStringView& format, const T&... args) - { - MultiByteString result(format); - details::FormatInternal<MultiByteString>(result, args...); - return result; - } - -#define CRU_FORMAT_NUMBER(type) \ - inline String FormatToString(const type number, type_tag<String>) \ - { \ - return std::to_wstring(number); \ - } \ - inline MultiByteString FormatToString(const type number, type_tag<MultiByteString>) \ - { \ - return std::to_string(number); \ - } - - CRU_FORMAT_NUMBER(int) - CRU_FORMAT_NUMBER(short) - CRU_FORMAT_NUMBER(long) - CRU_FORMAT_NUMBER(long long) - CRU_FORMAT_NUMBER(unsigned int) - CRU_FORMAT_NUMBER(unsigned short) - CRU_FORMAT_NUMBER(unsigned long) - CRU_FORMAT_NUMBER(unsigned long long) - CRU_FORMAT_NUMBER(float) - CRU_FORMAT_NUMBER(double) - -#undef CRU_FORMAT_NUMBER - - inline StringView FormatToString(const String& string, type_tag<String>) - { - return string; - } - - inline MultiByteString FormatToString(const MultiByteString& string, type_tag<MultiByteString>) - { - return string; - } - - inline StringView FormatToString(const StringView& string, type_tag<String>) - { - return string; - } - - inline MultiByteStringView FormatToString(const MultiByteStringView& string, type_tag<MultiByteString>) - { - return string; - } - - inline StringView FormatToString(const wchar_t* string, type_tag<String>) - { - return StringView(string); - } - - inline MultiByteStringView FormatToString(const char* string, type_tag<MultiByteString>) - { - return MultiByteString(string); - } -} diff --git a/src/math_util.hpp b/src/math_util.hpp deleted file mode 100644 index 8f0741b8..00000000 --- a/src/math_util.hpp +++ /dev/null @@ -1,69 +0,0 @@ -#pragma once - -// ReSharper disable once CppUnusedIncludeDirective -#include "pre.hpp" - -// ReSharper disable once CppUnusedIncludeDirective -#include <type_traits> -#include <optional> - -namespace cru -{ - template <typename T, typename = std::enable_if_t<std::is_arithmetic_v<T>>> - float Coerce(const T n, const std::optional<T> min, const std::optional<T> max) - { - if (min.has_value() && n < min.value()) - return min.value(); - if (max.has_value() && n > max.value()) - return max.value(); - return n; - } - - template <typename T, typename = std::enable_if_t<std::is_arithmetic_v<T>>> - float Coerce(const T n, const T min, const T max) - { - if (n < min) - return min; - if (n > max) - return max; - return n; - } - - template <typename T, typename = std::enable_if_t<std::is_arithmetic_v<T>>> - float Coerce(const T n, const std::nullopt_t, const std::optional<T> max) - { - if (max.has_value() && n > max.value()) - return max.value(); - return n; - } - - template <typename T, typename = std::enable_if_t<std::is_arithmetic_v<T>>> - float Coerce(const T n, const std::optional<T> min, const std::nullopt_t) - { - if (min.has_value() && n < min.value()) - return min.value(); - return n; - } - - template <typename T, typename = std::enable_if_t<std::is_arithmetic_v<T>>> - float Coerce(const T n, const std::nullopt_t, const T max) - { - if (n > max) - return max; - return n; - } - - template <typename T, typename = std::enable_if_t<std::is_arithmetic_v<T>>> - float Coerce(const T n, const T min, const std::nullopt_t) - { - if (n < min) - return min; - return n; - } - - template <typename T, typename = std::enable_if_t<std::is_arithmetic_v<T>>> - T AtLeast0(const T value) - { - return value < static_cast<T>(0) ? static_cast<T>(0) : value; - } -} diff --git a/src/pre.hpp b/src/pre.hpp index 03c51a94..ba47dc6c 100644 --- a/src/pre.hpp +++ b/src/pre.hpp @@ -8,11 +8,8 @@ #define CRU_DEBUG_LAYOUT #endif - #ifdef CRU_DEBUG -// ReSharper disable once IdentifierTypo -// ReSharper disable once CppInconsistentNaming #define _CRTDBG_MAP_ALLOC -#include <cstdlib> #include <crtdbg.h> +#include <cstdlib> #endif diff --git a/src/system_headers.hpp b/src/system_headers.hpp index eabc7c25..6cbad697 100644 --- a/src/system_headers.hpp +++ b/src/system_headers.hpp @@ -1,6 +1,4 @@ #pragma once - -// ReSharper disable once CppUnusedIncludeDirective #include "pre.hpp" //include system headers diff --git a/src/timer.cpp b/src/timer.cpp index c839a48d..40e32640 100644 --- a/src/timer.cpp +++ b/src/timer.cpp @@ -2,62 +2,51 @@ #include "application.hpp" -namespace cru -{ - TimerManager* TimerManager::GetInstance() - { - return Application::GetInstance()->ResolveSingleton<TimerManager>([](auto) - { - return new TimerManager{}; - }); - } - - UINT_PTR TimerManager::CreateTimer(const UINT milliseconds, const bool loop, const TimerAction& action) - { - const auto id = current_count_++; - ::SetTimer(Application::GetInstance()->GetGodWindow()->GetHandle(), id, milliseconds, nullptr); - map_.emplace(id, std::make_pair(loop, action)); - return id; - } - - void TimerManager::KillTimer(const UINT_PTR id) - { - const auto find_result = map_.find(id); - if (find_result != map_.cend()) - { - ::KillTimer(Application::GetInstance()->GetGodWindow()->GetHandle(), id); - map_.erase(find_result); - } - } - - std::optional<std::pair<bool, TimerAction>> TimerManager::GetAction(const UINT_PTR id) - { - const auto find_result = map_.find(id); - if (find_result == map_.cend()) - return std::nullopt; - return find_result->second; - } - - TimerTask::TimerTask(const UINT_PTR id) - : id_(id) - { - - } - - void TimerTask::Cancel() const - { - TimerManager::GetInstance()->KillTimer(id_); - } - - TimerTask SetTimeout(std::chrono::milliseconds milliseconds, const TimerAction& action) - { - const auto id = TimerManager::GetInstance()->CreateTimer(static_cast<UINT>(milliseconds.count()), false, action); - return TimerTask(id); - } - - TimerTask SetInterval(std::chrono::milliseconds milliseconds, const TimerAction& action) - { - const auto id = TimerManager::GetInstance()->CreateTimer(static_cast<UINT>(milliseconds.count()), true, action); - return TimerTask(id); - } +namespace cru { +TimerManager* TimerManager::GetInstance() { + return Application::GetInstance()->ResolveSingleton<TimerManager>( + [](auto) { return new TimerManager{}; }); } + +UINT_PTR TimerManager::CreateTimer(const UINT milliseconds, const bool loop, + const TimerAction& action) { + const auto id = current_count_++; + ::SetTimer(Application::GetInstance()->GetGodWindow()->GetHandle(), id, + milliseconds, nullptr); + map_.emplace(id, std::make_pair(loop, action)); + return id; +} + +void TimerManager::KillTimer(const UINT_PTR id) { + const auto find_result = map_.find(id); + if (find_result != map_.cend()) { + ::KillTimer(Application::GetInstance()->GetGodWindow()->GetHandle(), id); + map_.erase(find_result); + } +} + +std::optional<std::pair<bool, TimerAction>> TimerManager::GetAction( + const UINT_PTR id) { + const auto find_result = map_.find(id); + if (find_result == map_.cend()) return std::nullopt; + return find_result->second; +} + +TimerTask::TimerTask(const UINT_PTR id) : id_(id) {} + +void TimerTask::Cancel() const { TimerManager::GetInstance()->KillTimer(id_); } + +TimerTask SetTimeout(std::chrono::milliseconds milliseconds, + const TimerAction& action) { + const auto id = TimerManager::GetInstance()->CreateTimer( + static_cast<UINT>(milliseconds.count()), false, action); + return TimerTask(id); +} + +TimerTask SetInterval(std::chrono::milliseconds milliseconds, + const TimerAction& action) { + const auto id = TimerManager::GetInstance()->CreateTimer( + static_cast<UINT>(milliseconds.count()), true, action); + return TimerTask(id); +} +} // namespace cru diff --git a/src/timer.hpp b/src/timer.hpp index 5055a3d8..685e83b9 100644 --- a/src/timer.hpp +++ b/src/timer.hpp @@ -1,64 +1,64 @@ #pragma once - -// ReSharper disable once CppUnusedIncludeDirective #include "pre.hpp" -#include "system_headers.hpp" -#include <map> #include <chrono> #include <functional> +#include <map> #include <optional> +#include "system_headers.hpp" #include "base.hpp" -namespace cru -{ - using TimerAction = std::function<void()>; +namespace cru { +using TimerAction = std::function<void()>; + +class TimerManager : public Object { + public: + static TimerManager* GetInstance(); - class TimerManager : public Object - { - public: - static TimerManager* GetInstance(); + private: + TimerManager() = default; - private: - TimerManager() = default; - public: - TimerManager(const TimerManager& other) = delete; - TimerManager(TimerManager&& other) = delete; - TimerManager& operator=(const TimerManager& other) = delete; - TimerManager& operator=(TimerManager&& other) = delete; - ~TimerManager() override = default; + public: + TimerManager(const TimerManager& other) = delete; + TimerManager(TimerManager&& other) = delete; + TimerManager& operator=(const TimerManager& other) = delete; + TimerManager& operator=(TimerManager&& other) = delete; + ~TimerManager() override = default; - UINT_PTR CreateTimer(UINT milliseconds, bool loop, const TimerAction& action); - void KillTimer(UINT_PTR id); - std::optional<std::pair<bool, TimerAction>> GetAction(UINT_PTR id); + UINT_PTR CreateTimer(UINT milliseconds, bool loop, const TimerAction& action); + void KillTimer(UINT_PTR id); + std::optional<std::pair<bool, TimerAction>> GetAction(UINT_PTR id); - private: - std::map<UINT_PTR, std::pair<bool, TimerAction>> map_{}; - UINT_PTR current_count_ = 0; - }; + private: + std::map<UINT_PTR, std::pair<bool, TimerAction>> map_{}; + UINT_PTR current_count_ = 0; +}; - class TimerTask - { - friend TimerTask SetTimeout(std::chrono::milliseconds milliseconds, const TimerAction& action); - friend TimerTask SetInterval(std::chrono::milliseconds milliseconds, const TimerAction& action); +class TimerTask { + friend TimerTask SetTimeout(std::chrono::milliseconds milliseconds, + const TimerAction& action); + friend TimerTask SetInterval(std::chrono::milliseconds milliseconds, + const TimerAction& action); - private: - explicit TimerTask(UINT_PTR id); + private: + explicit TimerTask(UINT_PTR id); - public: - TimerTask(const TimerTask& other) = default; - TimerTask(TimerTask&& other) = default; - TimerTask& operator=(const TimerTask& other) = default; - TimerTask& operator=(TimerTask&& other) = default; - ~TimerTask() = default; + public: + TimerTask(const TimerTask& other) = default; + TimerTask(TimerTask&& other) = default; + TimerTask& operator=(const TimerTask& other) = default; + TimerTask& operator=(TimerTask&& other) = default; + ~TimerTask() = default; - void Cancel() const; + void Cancel() const; - private: - UINT_PTR id_; - }; + private: + UINT_PTR id_; +}; - TimerTask SetTimeout(std::chrono::milliseconds milliseconds, const TimerAction& action); - TimerTask SetInterval(std::chrono::milliseconds milliseconds, const TimerAction& action); -} +TimerTask SetTimeout(std::chrono::milliseconds milliseconds, + const TimerAction& action); +TimerTask SetInterval(std::chrono::milliseconds milliseconds, + const TimerAction& action); +} // namespace cru diff --git a/src/ui/control.cpp b/src/ui/control.cpp index 9388c719..617c50c7 100644 --- a/src/ui/control.cpp +++ b/src/ui/control.cpp @@ -17,53 +17,6 @@ namespace cru::ui { - Control::Control() - { - mouse_leave_event.bubble.AddHandler([this](events::MouseEventArgs& args) - { - if (args.GetOriginalSender() != this) - return; - for (auto& is_mouse_click_valid : is_mouse_click_valid_map_) - { - if (is_mouse_click_valid.second) - { - is_mouse_click_valid.second = false; - OnMouseClickEnd(is_mouse_click_valid.first); - } - } - }); - - mouse_down_event.bubble.AddHandler([this](events::MouseButtonEventArgs& args) - { - if (args.GetOriginalSender() != this) - return; - - if (is_focus_on_pressed_ && args.GetSender() == args.GetOriginalSender()) - RequestFocus(); - const auto button = args.GetMouseButton(); - is_mouse_click_valid_map_[button] = true; - OnMouseClickBegin(button); - }); - - mouse_up_event.bubble.AddHandler([this](events::MouseButtonEventArgs& args) - { - if (args.GetOriginalSender() != this) - return; - - const auto button = args.GetMouseButton(); - if (is_mouse_click_valid_map_[button]) - { - is_mouse_click_valid_map_[button] = false; - OnMouseClickEnd(button); - const auto point = args.GetPoint(GetWindow()); - InvokeLater([this, button, point] - { - DispatchEvent(this, &Control::mouse_click_event, nullptr, point, button); - }); - } - }); - } - void Control::SetParent(Control* parent) { @@ -74,19 +27,6 @@ namespace cru::ui OnParentChanged(old_parent, new_parent); } - void Control::SetInternalParent(Control* internal_parent) - { - const auto old_internal_parent = GetInternalParent(); - const auto old_parent = GetParent(); - internal_parent_ = internal_parent; - const auto new_internal_parent = GetInternalParent(); - const auto new_parent = GetParent(); - if (old_parent != new_parent) - OnParentChanged(old_parent, new_parent); - if (old_internal_parent != new_internal_parent) - OnInternalParentChanged(old_internal_parent, new_internal_parent); - } - void Control::SetDescendantWindow(Window* window) { if (window == nullptr && window_ == nullptr) @@ -112,199 +52,29 @@ namespace cru::ui }); } - - void TraverseDescendantsInternal(Control* control, const std::function<void(Control*)>& predicate) - { - predicate(control); - for (auto c: control->GetInternalChildren()) - TraverseDescendantsInternal(c, predicate); - } - void Control::TraverseDescendants(const std::function<void(Control*)>& predicate) { TraverseDescendantsInternal(this, predicate); } - Point Control::GetOffset() - { - return rect_.GetLeftTop(); - } - - Size Control::GetSize() - { - return rect_.GetSize(); - } - - void Control::SetRect(const Rect& rect) - { - const auto old_rect = rect_; - rect_ = rect; - - RegenerateGeometryInfo(); - - OnRectChange(old_rect, rect); - - if (auto window = GetWindow()) - window->InvalidateDraw(); - } - - namespace + void Control::TraverseDescendantsInternal(Control * control, const std::function<void(Control*)>& predicate) { -#ifdef CRU_DEBUG_LAYOUT - Microsoft::WRL::ComPtr<ID2D1Geometry> CalculateSquareRingGeometry(const Rect& out, const Rect& in) - { - const auto d2d1_factory = graph::GraphManager::GetInstance()->GetD2D1Factory(); - Microsoft::WRL::ComPtr<ID2D1RectangleGeometry> out_geometry; - ThrowIfFailed(d2d1_factory->CreateRectangleGeometry(Convert(out), &out_geometry)); - Microsoft::WRL::ComPtr<ID2D1RectangleGeometry> in_geometry; - ThrowIfFailed(d2d1_factory->CreateRectangleGeometry(Convert(in), &in_geometry)); - Microsoft::WRL::ComPtr<ID2D1PathGeometry> result_geometry; - ThrowIfFailed(d2d1_factory->CreatePathGeometry(&result_geometry)); - Microsoft::WRL::ComPtr<ID2D1GeometrySink> sink; - ThrowIfFailed(result_geometry->Open(&sink)); - ThrowIfFailed(out_geometry->CombineWithGeometry(in_geometry.Get(), D2D1_COMBINE_MODE_EXCLUDE, D2D1::Matrix3x2F::Identity(), sink.Get())); - ThrowIfFailed(sink->Close()); - return result_geometry; - } -#endif + predicate(control); + for (auto c: control->GetInternalChildren()) + TraverseDescendantsInternal(c, predicate); } - Point Control::GetPositionAbsolute() const - { - return position_cache_.lefttop_position_absolute; - } Point Control::ControlToWindow(const Point& point) const { - return Point(point.x + position_cache_.lefttop_position_absolute.x, - point.y + position_cache_.lefttop_position_absolute.y); + const auto position = GetPositionInWindow(); + return Point(point.x + position.x, point.y + position.y); } Point Control::WindowToControl(const Point & point) const { - return Point(point.x - position_cache_.lefttop_position_absolute.x, - point.y - position_cache_.lefttop_position_absolute.y); - } - - void Control::RefreshDescendantPositionCache() - { - auto point = Point::Zero(); - auto parent = this; - while ((parent = parent->GetParent())) - { - const auto p = parent->GetOffset(); - point.x += p.x; - point.y += p.y; - } - RefreshControlPositionCacheInternal(this, point); - } - - void Control::RefreshControlPositionCacheInternal(Control* control, const Point& parent_lefttop_absolute) - { - const auto position = control->GetOffset(); - const Point lefttop( - parent_lefttop_absolute.x + position.x, - parent_lefttop_absolute.y + position.y - ); - control->position_cache_.lefttop_position_absolute = lefttop; - for(auto c : control->GetInternalChildren()) - { - RefreshControlPositionCacheInternal(c, lefttop); - } - } - - bool Control::IsPointInside(const Point & point) - { - const auto border_geometry = geometry_info_.border_geometry; - if (border_geometry != nullptr) - { - if (IsBordered()) - { - BOOL contains; - border_geometry->FillContainsPoint(Convert(point), D2D1::Matrix3x2F::Identity(), &contains); - if (!contains) - border_geometry->StrokeContainsPoint(Convert(point), GetBorderProperty().GetStrokeWidth(), nullptr, D2D1::Matrix3x2F::Identity(), &contains); - return contains != 0; - } - else - { - BOOL contains; - border_geometry->FillContainsPoint(Convert(point), D2D1::Matrix3x2F::Identity(), &contains); - return contains != 0; - } - } - return false; - } - - Control* Control::HitTest(const Point& point) - { - const auto point_inside = IsPointInside(point); - - if (IsClipContent()) - { - if (!point_inside) - return nullptr; - if (geometry_info_.content_geometry != nullptr) - { - BOOL contains; - ThrowIfFailed(geometry_info_.content_geometry->FillContainsPoint(Convert(point), D2D1::Matrix3x2F::Identity(), &contains)); - if (contains == 0) - return this; - } - } - - const auto& children = GetInternalChildren(); - - for (auto i = children.crbegin(); i != children.crend(); ++i) - { - const auto&& lefttop = (*i)->GetOffset(); - const auto&& coerced_point = Point(point.x - lefttop.x, point.y - lefttop.y); - const auto child_hit_test_result = (*i)->HitTest(coerced_point); - if (child_hit_test_result != nullptr) - return child_hit_test_result; - } - - return point_inside ? this : nullptr; - } - - void Control::SetClipContent(const bool clip) - { - if (clip_content_ == clip) - return; - - clip_content_ = clip; - InvalidateDraw(); - } - - void Control::Draw(ID2D1DeviceContext* device_context) - { - D2D1::Matrix3x2F old_transform; - device_context->GetTransform(&old_transform); - - const auto position = GetOffset(); - device_context->SetTransform(old_transform * D2D1::Matrix3x2F::Translation(position.x, position.y)); - - OnDrawDecoration(device_context); - - const auto set_layer = geometry_info_.content_geometry != nullptr && IsClipContent(); - if (set_layer) - device_context->PushLayer(D2D1::LayerParameters(D2D1::InfiniteRect(), geometry_info_.content_geometry.Get()), nullptr); - - OnDrawCore(device_context); - - for (auto child : GetInternalChildren()) - child->Draw(device_context); - - if (set_layer) - device_context->PopLayer(); - - device_context->SetTransform(old_transform); - } - - void Control::InvalidateDraw() - { - if (window_ != nullptr) - window_->InvalidateDraw(); + const auto position = GetPositionInWindow(); + return Point(point.x - position.x, point.y - position.y); } bool Control::RequestFocus() @@ -325,109 +95,6 @@ namespace cru::ui return window->GetFocusControl() == this; } - void Control::InvalidateLayout() - { - if (const auto window = GetWindow()) - window->WindowInvalidateLayout(); - } - - void Control::Measure(const Size& available_size, const AdditionalMeasureInfo& additional_info) - { - SetDesiredSize(OnMeasureCore(available_size, additional_info)); - } - - void Control::Layout(const Rect& rect, const AdditionalLayoutInfo& additional_info) - { - auto my_additional_info = additional_info; - my_additional_info.total_offset.x += rect.left; - my_additional_info.total_offset.y += rect.top; - position_cache_.lefttop_position_absolute.x = my_additional_info.total_offset.x; - position_cache_.lefttop_position_absolute.y = my_additional_info.total_offset.y; - - SetRect(rect); - OnLayoutCore(Rect(Point::Zero(), rect.GetSize()), my_additional_info); - } - - Size Control::GetDesiredSize() const - { - return desired_size_; - } - - void Control::SetDesiredSize(const Size& desired_size) - { - desired_size_ = desired_size; - } - - inline void Shrink(Rect& rect, const Thickness& thickness) - { - rect.left += thickness.left; - rect.top += thickness.top; - rect.width -= thickness.GetHorizontalTotal(); - rect.height -= thickness.GetVerticalTotal(); - } - - Rect Control::GetRect(const RectRange range) - { - if (GetSize() == Size::Zero()) - return Rect(); - - const auto layout_params = GetLayoutParams(); - - auto result = Rect(Point::Zero(), GetSize()); - - if (range == RectRange::Margin) - return result; - - Shrink(result, layout_params->margin); - - if (range == RectRange::FullBorder) - return result; - - if (is_bordered_) - Shrink(result, Thickness(GetBorderProperty().GetStrokeWidth() / 2.0f)); - - if (range == RectRange::HalfBorder) - return result; - - if (is_bordered_) - Shrink(result, Thickness(GetBorderProperty().GetStrokeWidth() / 2.0f)); - - if (range == RectRange::Padding) - return result; - - Shrink(result, layout_params->padding); - - return result; - } - - Point Control::TransformPoint(const Point& point, const RectRange from, const RectRange to) - { - const auto rect_from = GetRect(from); - const auto rect_to = GetRect(to); - auto p = point; - p.x += rect_from.left; - p.y += rect_from.top; - p.x -= rect_to.left; - p.y -= rect_to.top; - return p; - } - - void Control::UpdateBorder() - { - RegenerateGeometryInfo(); - InvalidateLayout(); - InvalidateDraw(); - } - - void Control::SetBordered(const bool bordered) - { - if (bordered != is_bordered_) - { - is_bordered_ = bordered; - UpdateBorder(); - } - } - void Control::SetCursor(const Cursor::Ptr& cursor) { if (cursor != cursor_) @@ -444,289 +111,41 @@ namespace cru::ui } - void Control::OnInternalParentChanged(Control* old_internal_parent, Control* new_internal_parent) - { - - } - void Control::OnAttachToWindow(Window* window) { - window_ = window; - } - - void Control::OnDetachToWindow(Window * window) - { - window_ = nullptr; - } - - void Control::OnDrawDecoration(ID2D1DeviceContext* device_context) - { -#ifdef CRU_DEBUG_LAYOUT - if (GetWindow()->IsDebugLayout()) - { - if (padding_geometry_ != nullptr) - device_context->FillGeometry(padding_geometry_.Get(), UiManager::GetInstance()->GetPredefineResources()->debug_layout_padding_brush.Get()); - if (margin_geometry_ != nullptr) - device_context->FillGeometry(margin_geometry_.Get(), UiManager::GetInstance()->GetPredefineResources()->debug_layout_margin_brush.Get()); - device_context->DrawRectangle(Convert(GetRect(RectRange::Margin)), UiManager::GetInstance()->GetPredefineResources()->debug_layout_out_border_brush.Get()); - } -#endif - - if (is_bordered_ && geometry_info_.border_geometry != nullptr) - device_context->DrawGeometry( - geometry_info_.border_geometry.Get(), - GetBorderProperty().GetBrush().Get(), - GetBorderProperty().GetStrokeWidth(), - GetBorderProperty().GetStrokeStyle().Get() - ); - } - - void Control::OnDrawCore(ID2D1DeviceContext* device_context) - { - const auto ground_geometry = geometry_info_.padding_content_geometry; - //draw background. - if (ground_geometry != nullptr && background_brush_ != nullptr) - device_context->FillGeometry(ground_geometry.Get(), background_brush_.Get()); - const auto padding_rect = GetRect(RectRange::Padding); - graph::WithTransform(device_context, D2D1::Matrix3x2F::Translation(padding_rect.left, padding_rect.top), - [this](ID2D1DeviceContext* device_context) - { - events::DrawEventArgs args(this, this, device_context); - draw_background_event.Raise(args); - }); - - - const auto rect = GetRect(RectRange::Content); - graph::WithTransform(device_context, D2D1::Matrix3x2F::Translation(rect.left, rect.top), - [this](ID2D1DeviceContext* device_context) - { - events::DrawEventArgs args(this, this, device_context); - draw_content_event.Raise(args); - }); - - - //draw foreground. - if (ground_geometry != nullptr && foreground_brush_ != nullptr) - device_context->FillGeometry(ground_geometry.Get(), foreground_brush_.Get()); - graph::WithTransform(device_context, D2D1::Matrix3x2F::Translation(padding_rect.left, padding_rect.top), - [this](ID2D1DeviceContext* device_context) - { - events::DrawEventArgs args(this, this, device_context); - draw_foreground_event.Raise(args); - }); - } - - void Control::OnRectChange(const Rect& old_rect, const Rect& new_rect) - { } - void Control::RegenerateGeometryInfo() + void Control::OnDetachToWindow(Window * window) { - if (IsBordered()) - { - const auto bound_rect = GetRect(RectRange::HalfBorder); - const auto bound_rounded_rect = D2D1::RoundedRect(Convert(bound_rect), - GetBorderProperty().GetRadiusX(), - GetBorderProperty().GetRadiusY()); - - Microsoft::WRL::ComPtr<ID2D1RoundedRectangleGeometry> geometry; - ThrowIfFailed( - graph::GraphManager::GetInstance()->GetD2D1Factory()->CreateRoundedRectangleGeometry(bound_rounded_rect, &geometry) - ); - geometry_info_.border_geometry = std::move(geometry); - - const auto padding_rect = GetRect(RectRange::Padding); - const auto in_border_rounded_rect = D2D1::RoundedRect(Convert(padding_rect), - GetBorderProperty().GetRadiusX() - GetBorderProperty().GetStrokeWidth() / 2.0f, - GetBorderProperty().GetRadiusY() - GetBorderProperty().GetStrokeWidth() / 2.0f); - - Microsoft::WRL::ComPtr<ID2D1RoundedRectangleGeometry> geometry2; - ThrowIfFailed( - graph::GraphManager::GetInstance()->GetD2D1Factory()->CreateRoundedRectangleGeometry(in_border_rounded_rect, &geometry2) - ); - geometry_info_.padding_content_geometry = geometry2; - - - Microsoft::WRL::ComPtr<ID2D1RectangleGeometry> geometry3; - ThrowIfFailed( - graph::GraphManager::GetInstance()->GetD2D1Factory()->CreateRectangleGeometry(Convert(GetRect(RectRange::Content)), &geometry3) - ); - Microsoft::WRL::ComPtr<ID2D1PathGeometry> geometry4; - ThrowIfFailed( - graph::GraphManager::GetInstance()->GetD2D1Factory()->CreatePathGeometry(&geometry4) - ); - Microsoft::WRL::ComPtr<ID2D1GeometrySink> sink; - geometry4->Open(&sink); - ThrowIfFailed( - geometry3->CombineWithGeometry(geometry2.Get(), D2D1_COMBINE_MODE_INTERSECT, D2D1::Matrix3x2F::Identity(), sink.Get()) - ); - sink->Close(); - geometry_info_.content_geometry = std::move(geometry4); - } - else - { - const auto bound_rect = GetRect(RectRange::Padding); - Microsoft::WRL::ComPtr<ID2D1RectangleGeometry> geometry; - ThrowIfFailed( - graph::GraphManager::GetInstance()->GetD2D1Factory()->CreateRectangleGeometry(Convert(bound_rect), &geometry) - ); - geometry_info_.border_geometry = geometry; - geometry_info_.padding_content_geometry = std::move(geometry); - - Microsoft::WRL::ComPtr<ID2D1RectangleGeometry> geometry2; - ThrowIfFailed( - graph::GraphManager::GetInstance()->GetD2D1Factory()->CreateRectangleGeometry(Convert(GetRect(RectRange::Content)), &geometry2) - ); - geometry_info_.content_geometry = std::move(geometry2); - } - //TODO: generate debug geometry -#ifdef CRU_DEBUG_LAYOUT - margin_geometry_ = CalculateSquareRingGeometry(GetRect(RectRange::Margin), GetRect(RectRange::FullBorder)); - padding_geometry_ = CalculateSquareRingGeometry(GetRect(RectRange::Padding), GetRect(RectRange::Content)); -#endif } void Control::OnMouseClickBegin(MouseButton button) { - } - void Control::OnMouseClickEnd(MouseButton button) - { } - inline Size ThicknessToSize(const Thickness& thickness) - { - return Size(thickness.left + thickness.right, thickness.top + thickness.bottom); - } - - Size Control::OnMeasureCore(const Size& available_size, const AdditionalMeasureInfo& additional_info) + void Control::OnMouseClickEnd(MouseButton button) { - const auto layout_params = GetLayoutParams(); - - if (!layout_params->Validate()) - throw std::runtime_error("LayoutParams is not valid. Please check it."); - - auto my_additional_info = additional_info; - - if (layout_params->width.mode == MeasureMode::Content) - my_additional_info.horizontal_stretchable = false; - else if (layout_params->width.mode == MeasureMode::Exactly) - my_additional_info.horizontal_stretchable = true; - // if stretch, then inherent parent's value - - if (layout_params->height.mode == MeasureMode::Content) - my_additional_info.vertical_stretchable = false; - else if (layout_params->height.mode == MeasureMode::Exactly) - my_additional_info.vertical_stretchable = true; - // if stretch, then inherent parent's value - - auto border_size = Size::Zero(); - if (is_bordered_) - { - const auto border_width = GetBorderProperty().GetStrokeWidth(); - border_size = Size(border_width * 2.0f, border_width * 2.0f); - } - - // the total size of padding, border and margin - const auto outer_size = ThicknessToSize(layout_params->padding) + - ThicknessToSize(layout_params->margin) + border_size; - - - auto&& get_content_measure_length = [](const LayoutSideParams& layout_length, const float available_length, const float outer_length) -> float - { - float length; - if (layout_length.mode == MeasureMode::Exactly) - length = layout_length.length; - else if (available_length > outer_length) - length = available_length - outer_length; - else - length = 0; - return Coerce(length, layout_length.min, layout_length.max); - }; - - // if padding, margin and border exceeded, then content size is 0. - const auto content_measure_size = Size( - get_content_measure_length(layout_params->width, available_size.width, outer_size.width), - get_content_measure_length(layout_params->height, available_size.height, outer_size.height) - ); - - const auto content_actual_size = OnMeasureContent(content_measure_size, my_additional_info); - - - - auto&& calculate_final_length = [](const bool stretch, const std::optional<float> min_length, const float measure_length, const float actual_length) -> float - { - // only use measure length when stretch and actual length is smaller than measure length, that is "stretch" - if (stretch && actual_length < measure_length) - return measure_length; - return Coerce(actual_length, min_length, std::nullopt); - }; - - const auto final_size = Size( - calculate_final_length(my_additional_info.horizontal_stretchable, layout_params->width.min, content_measure_size.width, content_actual_size.width), - calculate_final_length(my_additional_info.vertical_stretchable, layout_params->height.min, content_measure_size.height, content_actual_size.height) - ) + outer_size; - - return final_size; } - void Control::OnLayoutCore(const Rect& rect, const AdditionalLayoutInfo& additional_info) - { - const auto layout_params = GetLayoutParams(); - - auto border_width = 0.0f; - if (is_bordered_) - { - border_width = GetBorderProperty().GetStrokeWidth(); - } - - const Rect content_rect( - rect.left + layout_params->padding.left + layout_params->margin.right + border_width, - rect.top + layout_params->padding.top + layout_params->margin.top + border_width, - rect.width - layout_params->padding.GetHorizontalTotal() - layout_params->margin.GetHorizontalTotal() - border_width * 2.0f, - rect.height - layout_params->padding.GetVerticalTotal() - layout_params->margin.GetVerticalTotal() - border_width * 2.0f - ); - - if (content_rect.width < 0.0) - throw std::runtime_error(Format("Width to layout must sufficient. But in {}, width for content is {}.", ToUtf8String(GetControlType()), content_rect.width)); - if (content_rect.height < 0.0) - throw std::runtime_error(Format("Height to layout must sufficient. But in {}, height for content is {}.", ToUtf8String(GetControlType()), content_rect.height)); - - OnLayoutContent(content_rect, additional_info); - } const std::vector<Control*> NoChildControl::empty_control_vector{}; - std::list<Control*> GetAncestorList(Control* control) - { - std::list<Control*> l; - while (control != nullptr) - { - l.push_front(control); - control = control->GetInternalParent(); - } - return l; - } - - void NoChildControl::OnLayoutContent(const Rect& rect, const AdditionalLayoutInfo& additional_info) - { - } - - SingleChildControl::SingleChildControl() : child_vector_{nullptr}, child_(child_vector_[0]) + ContentControl::ContentControl() : child_vector_{nullptr}, child_(child_vector_[0]) { } - SingleChildControl::~SingleChildControl() + ContentControl::~ContentControl() { delete child_; } - void SingleChildControl::SetChild(Control* child) + void ContentControl::SetChild(Control* child) { if (child == child_) return; @@ -736,66 +155,25 @@ namespace cru::ui child_ = child; if (old_child) { - old_child->SetInternalParent(nullptr); + old_child->SetParent(nullptr); old_child->SetDescendantWindow(nullptr); } if (child) { - child->SetInternalParent(this); + child->SetParent(this); child->SetDescendantWindow(window); } OnChildChanged(old_child, child); } - void SingleChildControl::OnChildChanged(Control* old_child, Control* new_child) + void ContentControl::OnChildChanged(Control* old_child, Control* new_child) { } - Size SingleChildControl::OnMeasureContent(const Size& available_size, const AdditionalMeasureInfo& additional_info) + void ControlAddChildCheck(Control* control) { - auto child_size = Size::Zero(); - if (child_) - { - child_->Measure(available_size, additional_info); - child_size = child_->GetDesiredSize(); - } - - return child_size; - } - - void SingleChildControl::OnLayoutContent(const Rect& rect, const AdditionalLayoutInfo& additional_info) - { - if (child_) - { - const auto layout_params = child_->GetLayoutParams(); - const auto size = child_->GetDesiredSize(); - - auto&& calculate_anchor = [](const float anchor, const Alignment alignment, const float layout_length, const float control_length) -> float - { - switch (alignment) - { - case Alignment::Center: - return anchor + (layout_length - control_length) / 2; - case Alignment::Start: - return anchor; - case Alignment::End: - return anchor + layout_length - control_length; - default: - UnreachableCode(); - } - }; - - child_->Layout(Rect(Point( - calculate_anchor(rect.left, layout_params->width.alignment, rect.width, size.width), - calculate_anchor(rect.top, layout_params->height.alignment, rect.height, size.height) - ), size), additional_info); - } - } - - void AddChildCheck(Control* control) - { - if (control->GetInternalParent() != nullptr) + if (control->GetParent() != nullptr) throw std::invalid_argument("The control already has a parent."); if (dynamic_cast<Window*>(control)) @@ -810,11 +188,11 @@ namespace cru::ui void MultiChildControl::AddChild(Control* control) { - AddChildCheck(control); + ControlAddChildCheck(control); children_.push_back(control); - control->SetInternalParent(this); + control->SetParent(this); control->SetDescendantWindow(GetWindow()); OnAddChild(control); @@ -822,14 +200,14 @@ namespace cru::ui void MultiChildControl::AddChild(Control* control, const int position) { - AddChildCheck(control); + ControlAddChildCheck(control); if (position < 0 || static_cast<decltype(children_.size())>(position) > this->children_.size()) throw std::invalid_argument("The position is out of range."); children_.insert(this->children_.cbegin() + position, control); - control->SetInternalParent(this); + control->SetParent(this); control->SetDescendantWindow(GetWindow()); OnAddChild(control); @@ -843,7 +221,7 @@ namespace cru::ui children_.erase(i); - child->SetInternalParent(nullptr); + child->SetParent(nullptr); child->SetDescendantWindow(nullptr); OnRemoveChild(child); @@ -859,7 +237,7 @@ namespace cru::ui children_.erase(i); - child->SetInternalParent(nullptr); + child->SetParent(nullptr); child->SetDescendantWindow(nullptr); OnRemoveChild(child); @@ -875,6 +253,17 @@ namespace cru::ui } + std::list<Control*> GetAncestorList(Control* control) + { + std::list<Control*> l; + while (control != nullptr) + { + l.push_front(control); + control = control->GetParent(); + } + return l; + } + Control* FindLowestCommonAncestor(Control * left, Control * right) { if (left == nullptr || right == nullptr) diff --git a/src/ui/control.hpp b/src/ui/control.hpp index 6abcc365..8e69fb07 100644 --- a/src/ui/control.hpp +++ b/src/ui/control.hpp @@ -12,49 +12,21 @@ #include "ui_base.hpp" #include "layout_base.hpp" #include "events/ui_event.hpp" -#include "border_property.hpp" #include "cursor.hpp" #include "any_map.hpp" +#include "input_util.hpp" namespace cru::ui { class Window; - struct AdditionalMeasureInfo - { - bool horizontal_stretchable = true; - bool vertical_stretchable = true; - }; - - struct AdditionalLayoutInfo - { - Point total_offset = Point::Zero(); - }; - - //the position cache - struct ControlPositionCache - { - //The lefttop relative to the ancestor. - Point lefttop_position_absolute = Point::Zero(); - }; - - class Control : public Object { friend class Window; protected: - struct GeometryInfo - { - Microsoft::WRL::ComPtr<ID2D1Geometry> border_geometry = nullptr; - Microsoft::WRL::ComPtr<ID2D1Geometry> padding_content_geometry = nullptr; - Microsoft::WRL::ComPtr<ID2D1Geometry> content_geometry = nullptr; - }; - - - protected: - Control(); + Control() = default; public: Control(const Control& other) = delete; Control(Control&& other) = delete; @@ -63,53 +35,35 @@ namespace cru::ui ~Control() override = default; public: - - //*************** region: tree *************** virtual StringView GetControlType() const = 0; - virtual const std::vector<Control*>& GetInternalChildren() const = 0; - - Control* GetParent() const - { - return parent_ == nullptr ? internal_parent_ : parent_; - } - - Control* GetInternalParent() const - { - return internal_parent_; - } + //*************** region: tree *************** + public: //Get the window if attached, otherwise, return nullptr. Window* GetWindow() const { return window_; } - void SetParent(Control* parent); + Control* GetParent() const + { + return parent_; + } - void SetInternalParent(Control* internal_parent); + void SetParent(Control* parent); void SetDescendantWindow(Window* window); - //Traverse the tree rooted the control including itself. void TraverseDescendants(const std::function<void(Control*)>& predicate); - //*************** region: position and size *************** - - //Get the lefttop relative to its parent. - virtual Point GetOffset(); - - //Get the actual size. - virtual Size GetSize(); - - // If offset changes, call RefreshDescendantPositionCache. - virtual void SetRect(const Rect& rect); + private: + static void TraverseDescendantsInternal(Control* control, const std::function<void(Control*)>& predicate); - //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; + //*************** region: position *************** + public: + virtual Point GetPositionInWindow() const = 0; //Local point to absolute point. Point ControlToWindow(const Point& point) const; @@ -117,122 +71,19 @@ namespace cru::ui //Absolute point to local point. Point WindowToControl(const Point& point) const; - void RefreshDescendantPositionCache(); - - private: - static void RefreshControlPositionCacheInternal(Control* control, const Point& parent_lefttop_absolute); - - public: - - // Default implement in Control is test point in border geometry's - // fill and stroke with width of border. - virtual bool IsPointInside(const Point& point); - - // Get the top control among all descendants (including self) in local coordinate. - virtual Control* HitTest(const Point& point); - - //*************** region: graphic *************** - - bool IsClipContent() const - { - return clip_content_; - } - - void SetClipContent(bool clip); - - //Draw this control and its child controls. - void Draw(ID2D1DeviceContext* device_context); - - virtual void InvalidateDraw(); - - Microsoft::WRL::ComPtr<ID2D1Brush> GetForegroundBrush() const - { - return foreground_brush_; - } - - void SetForegroundBrush(Microsoft::WRL::ComPtr<ID2D1Brush> foreground_brush) - { - foreground_brush_ = std::move(foreground_brush); - InvalidateDraw(); - } - - Microsoft::WRL::ComPtr<ID2D1Brush> GetBackgroundBrush() const - { - return background_brush_; - } - - void SetBackgroundBrush(Microsoft::WRL::ComPtr<ID2D1Brush> background_brush) - { - background_brush_ = std::move(background_brush); - InvalidateDraw(); - } //*************** region: focus *************** - + public: bool RequestFocus(); bool HasFocus(); - bool IsFocusOnPressed() const - { - return is_focus_on_pressed_; - } - - void SetFocusOnPressed(const bool value) - { - is_focus_on_pressed_ = value; - } - - //*************** region: layout *************** - - void InvalidateLayout(); - - void Measure(const Size& available_size, const AdditionalMeasureInfo& additional_info); - - void Layout(const Rect& rect, const AdditionalLayoutInfo& additional_info); - - Size GetDesiredSize() const; - - void SetDesiredSize(const Size& desired_size); - - BasicLayoutParams* GetLayoutParams() - { - return &layout_params_; - } - - Rect GetRect(RectRange range); - - Point TransformPoint(const Point& point, RectRange from = RectRange::Margin, RectRange to = RectRange::Content); - - //*************** region: border *************** - - BorderProperty& GetBorderProperty() - { - return border_property_; - } - - void UpdateBorder(); - - bool IsBordered() const - { - return is_bordered_; - } - - void SetBordered(bool bordered); - - - //*************** region: additional properties *************** - AnyMap* GetAdditionalPropertyMap() - { - return &additional_property_map_; - } - //*************** region: cursor *************** // If cursor is set to null, then it uses parent's cursor. // Window's cursor can't be null. - + public: Cursor::Ptr GetCursor() const { return cursor_; @@ -241,7 +92,16 @@ namespace cru::ui void SetCursor(const Cursor::Ptr& cursor); + //*************** region: additional properties *************** + public: + AnyMap* GetAdditionalPropertyMap() + { + return &additional_property_map_; + } + + //*************** region: events *************** + public: //Raised when mouse enter the control. events::RoutedEvent<events::MouseEventArgs> mouse_enter_event; //Raised when mouse is leave the control. @@ -264,95 +124,29 @@ namespace cru::ui events::RoutedEvent<events::FocusChangeEventArgs> get_focus_event; events::RoutedEvent<events::FocusChangeEventArgs> lose_focus_event; - Event<events::DrawEventArgs> draw_content_event; - Event<events::DrawEventArgs> draw_background_event; - Event<events::DrawEventArgs> draw_foreground_event; - - //*************** region: tree event *************** + //*************** region: tree *************** protected: - virtual void OnParentChanged(Control* old_parent, Control* new_parent); - - virtual void OnInternalParentChanged(Control* old_internal_parent, Control* new_internal_parent); + virtual const std::vector<Control*>& GetInternalChildren() const = 0; - //Invoked when the control is attached to a window. Overrides should invoke base. + virtual void OnParentChanged(Control* old_parent, Control* new_parent); virtual void OnAttachToWindow(Window* window); - //Invoked when the control is detached to a window. Overrides should invoke base. virtual void OnDetachToWindow(Window* window); - //*************** region: graphic event *************** - private: - void OnDrawDecoration(ID2D1DeviceContext* device_context); - void OnDrawCore(ID2D1DeviceContext* device_context); - - - //*************** region: position and size event *************** - protected: - virtual void OnRectChange(const Rect& old_rect, const Rect& new_rect); - - void RegenerateGeometryInfo(); - - const GeometryInfo& GetGeometryInfo() const - { - return geometry_info_; - } - - - //*************** region: mouse event *************** + //*************** region: additional mouse event *************** protected: virtual void OnMouseClickBegin(MouseButton button); virtual void OnMouseClickEnd(MouseButton button); - //*************** region: layout *************** - private: - Size OnMeasureCore(const Size& available_size, const AdditionalMeasureInfo& additional_info); - void OnLayoutCore(const Rect& rect, const AdditionalLayoutInfo& additional_info); - - protected: - virtual Size OnMeasureContent(const Size& available_size, const AdditionalMeasureInfo& additional_info) = 0; - virtual void OnLayoutContent(const Rect& rect, const AdditionalLayoutInfo& additional_info) = 0; - private: Window * window_ = nullptr; - Control* parent_ = nullptr; // when parent and internal parent are the same, parent_ is nullptr. - Control * internal_parent_ = nullptr; - - Rect rect_{}; - - ControlPositionCache position_cache_{}; - - std::unordered_map<MouseButton, bool> is_mouse_click_valid_map_ - { - { MouseButton::Left, true }, - { MouseButton::Middle, true }, - { MouseButton::Right, true } - }; // used for clicking determination - - BasicLayoutParams layout_params_{}; - Size desired_size_ = Size::Zero(); - - bool is_bordered_ = false; - BorderProperty border_property_; - - GeometryInfo geometry_info_{}; - - bool clip_content_ = false; + Control* parent_ = nullptr; - Microsoft::WRL::ComPtr<ID2D1Brush> foreground_brush_ = nullptr; - Microsoft::WRL::ComPtr<ID2D1Brush> background_brush_ = nullptr; + Cursor::Ptr cursor_{}; AnyMap additional_property_map_{}; - - bool is_focus_on_pressed_ = true; - -#ifdef CRU_DEBUG_LAYOUT - Microsoft::WRL::ComPtr<ID2D1Geometry> margin_geometry_; - Microsoft::WRL::ComPtr<ID2D1Geometry> padding_geometry_; -#endif - - Cursor::Ptr cursor_{}; }; @@ -372,31 +166,24 @@ namespace cru::ui NoChildControl& operator=(NoChildControl&& other) = delete; ~NoChildControl() override = default; + protected: const std::vector<Control*>& GetInternalChildren() const override final { return empty_control_vector; } - - protected: - void OnLayoutContent(const Rect& rect, const AdditionalLayoutInfo& additional_info) override; }; - class SingleChildControl : public Control + class ContentControl : public Control { protected: - SingleChildControl(); + ContentControl(); public: - SingleChildControl(const SingleChildControl& other) = delete; - SingleChildControl(SingleChildControl&& other) = delete; - SingleChildControl& operator=(const SingleChildControl& other) = delete; - SingleChildControl& operator=(SingleChildControl&& other) = delete; - ~SingleChildControl() override; - - const std::vector<Control*>& GetInternalChildren() const override final - { - return child_vector_; - } + ContentControl(const ContentControl& other) = delete; + ContentControl(ContentControl&& other) = delete; + ContentControl& operator=(const ContentControl& other) = delete; + ContentControl& operator=(ContentControl&& other) = delete; + ~ContentControl() override; Control* GetChild() const { @@ -406,12 +193,14 @@ namespace cru::ui void SetChild(Control* child); protected: + const std::vector<Control*>& GetInternalChildren() const override final + { + return child_vector_; + } + // Override should call base. virtual void OnChildChanged(Control* old_child, Control* new_child); - Size OnMeasureContent(const Size& available_size, const AdditionalMeasureInfo& additional_info) override; - void OnLayoutContent(const Rect& rect, const AdditionalLayoutInfo& additional_info) override; - private: std::vector<Control*> child_vector_; Control*& child_; @@ -452,9 +241,7 @@ namespace cru::ui void RemoveChild(int position); protected: - //Invoked when a child is added. Overrides should invoke base. virtual void OnAddChild(Control* child); - //Invoked when a child is removed. Overrides should invoke base. virtual void OnRemoveChild(Control* child); private: diff --git a/src/ui/controls/button.hpp b/src/ui/controls/button.hpp index 82694fe8..6436f7c0 100644 --- a/src/ui/controls/button.hpp +++ b/src/ui/controls/button.hpp @@ -9,7 +9,7 @@ namespace cru::ui::controls { - class Button : public SingleChildControl + class Button : public ContentControl { public: static constexpr auto control_type = L"Button"; diff --git a/src/ui/controls/list_item.hpp b/src/ui/controls/list_item.hpp index a50b2496..bf8f8d8e 100644 --- a/src/ui/controls/list_item.hpp +++ b/src/ui/controls/list_item.hpp @@ -10,7 +10,7 @@ namespace cru::ui::controls { - class ListItem : public SingleChildControl + class ListItem : public ContentControl { public: static constexpr auto control_type = L"ListItem"; diff --git a/src/ui/controls/scroll_control.hpp b/src/ui/controls/scroll_control.hpp index 7138add6..84ebca30 100644 --- a/src/ui/controls/scroll_control.hpp +++ b/src/ui/controls/scroll_control.hpp @@ -17,7 +17,7 @@ namespace cru::ui::controls // Done: API // Done: ScrollBar // Done: MouseEvent - class ScrollControl : public SingleChildControl + class ScrollControl : public ContentControl { private: struct ScrollBarInfo diff --git a/src/ui/render/linear_layout_render_object.cpp b/src/ui/render/linear_layout_render_object.cpp new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/src/ui/render/linear_layout_render_object.cpp diff --git a/src/ui/render/linear_layout_render_object.hpp b/src/ui/render/linear_layout_render_object.hpp new file mode 100644 index 00000000..6f70f09b --- /dev/null +++ b/src/ui/render/linear_layout_render_object.hpp @@ -0,0 +1 @@ +#pragma once diff --git a/src/ui/render/render_object.cpp b/src/ui/render/render_object.cpp index 0828fc9c..c2aaeb62 100644 --- a/src/ui/render/render_object.cpp +++ b/src/ui/render/render_object.cpp @@ -3,96 +3,88 @@ namespace cru::ui::render { - void RenderObject::SetRenderHost(IRenderHost* new_render_host) - { - if (new_render_host == render_host_) - return; - - const auto old = render_host_; - render_host_ = new_render_host; - OnRenderHostChanged(old, new_render_host); - } - - void RenderObject::OnRenderHostChanged(IRenderHost* old_render_host, IRenderHost* new_render_host) - { - - } - - void RenderObject::InvalidateRenderHost() - { - if (render_host_ != nullptr) - render_host_->InvalidateRender(); - } - - void StrokeRenderObject::SetStrokeWidth(const float new_stroke_width) - { - if (stroke_width_ == new_stroke_width) - return; - - stroke_width_ = new_stroke_width; - InvalidateRenderHost(); - } - - void StrokeRenderObject::SetBrush(Microsoft::WRL::ComPtr<ID2D1Brush> new_brush) - { - if (brush_ == new_brush) - return; - - brush_ = std::move(new_brush); - InvalidateRenderHost(); - } - - void StrokeRenderObject::SetStrokeStyle(Microsoft::WRL::ComPtr<ID2D1StrokeStyle> new_stroke_style) - { - if (stroke_style_ == new_stroke_style) - return; - - stroke_style_ = std::move(new_stroke_style); - InvalidateRenderHost(); - } - - void FillRenderObject::SetBrush(Microsoft::WRL::ComPtr<ID2D1Brush> new_brush) - { - if (brush_ == new_brush) - return; - - brush_ = std::move(new_brush); - InvalidateRenderHost(); - } - - namespace details - { - template class ShapeRenderObject<Rect>; - template class ShapeRenderObject<RoundedRect>; - template class ShapeRenderObject<Ellipse>; - } - - namespace details - { - template ShapeStrokeRenderObject<Rect, D2D1_RECT_F, &ID2D1RenderTarget::DrawRectangle>; - template ShapeStrokeRenderObject<RoundedRect, D2D1_ROUNDED_RECT, &ID2D1RenderTarget::DrawRoundedRectangle>; - template ShapeStrokeRenderObject<Ellipse, D2D1_ELLIPSE, &ID2D1RenderTarget::DrawEllipse>; - } - - namespace details - { - template ShapeFillRenderObject<Rect, D2D1_RECT_F, &ID2D1RenderTarget::FillRectangle>; - template ShapeFillRenderObject<RoundedRect, D2D1_ROUNDED_RECT, &ID2D1RenderTarget::FillRoundedRectangle>; - template ShapeFillRenderObject<Ellipse, D2D1_ELLIPSE, &ID2D1RenderTarget::FillEllipse>; - } - - void CustomDrawHandlerRenderObject::SetDrawHandler(DrawHandler new_draw_handler) - { - if (draw_handler_ == nullptr && new_draw_handler == nullptr) - return; - - draw_handler_ = std::move(new_draw_handler); - InvalidateRenderHost(); - } - - void CustomDrawHandlerRenderObject::Draw(ID2D1RenderTarget* render_target) - { - if (draw_handler_ != nullptr) - draw_handler_(render_target); - } +void RenderObject::SetRenderHost(IRenderHost* new_render_host) +{ + if (new_render_host == render_host_) return; + + const auto old = render_host_; + render_host_ = new_render_host; + OnRenderHostChanged(old, new_render_host); +} + +void RenderObject::AddChild(RenderObject* render_object, const int position) +{ + if (render_object->GetParent() != nullptr) + throw std::invalid_argument("Render object already has a parent."); + + if (position < 0) + throw std::invalid_argument("Position index is less than 0."); + + if (static_cast<std::vector<RenderObject*>::size_type>(position) > + children_.size()) + throw std::invalid_argument("Position index is out of bound."); + + children_.insert(children_.cbegin() + position, render_object); + render_object->SetParent(this); + OnAddChild(render_object, position); +} + +void RenderObject::RemoveChild(const int position) +{ + if (position < 0) + throw std::invalid_argument("Position index is less than 0."); + + if (static_cast<std::vector<RenderObject*>::size_type>(position) >= + children_.size()) + throw std::invalid_argument("Position index is out of bound."); + + const auto i = children_.cbegin() + position; + const auto removed_child = *i; + children_.erase(i); + removed_child->SetParent(nullptr); + OnRemoveChild(removed_child, position); +} + + +void RenderObject::OnRenderHostChanged(IRenderHost* old_render_host, + IRenderHost* new_render_host) +{ +} + +void RenderObject::InvalidateRenderHostPaint() const +{ + if (render_host_ != nullptr) render_host_->InvalidatePaint(); +} + +void RenderObject::InvalidateRenderHostLayout() const +{ + if (render_host_ != nullptr) render_host_->InvalidateLayout(); +} + +void RenderObject::OnParentChanged(RenderObject* old_parent, + RenderObject* new_parent) +{ +} + + +void RenderObject::OnAddChild(RenderObject* new_child, int position) +{ +} + +void RenderObject::OnRemoveChild(RenderObject* removed_child, int position) +{ +} + +void RenderObject::SetParent(RenderObject* new_parent) +{ + const auto old_parent = parent_; + parent_ = new_parent; + OnParentChanged(old_parent, new_parent); +} + + +void LinearLayoutRenderObject::Measure(const MeasureConstraint& constraint) +{ + } +} // namespace cru::ui::render diff --git a/src/ui/render/render_object.hpp b/src/ui/render/render_object.hpp index 31745be5..00f761d1 100644 --- a/src/ui/render/render_object.hpp +++ b/src/ui/render/render_object.hpp @@ -2,256 +2,108 @@ #include "pre.hpp" +#include <optional> +#include <vector> #include "system_headers.hpp" -#include <functional> -#include <cassert> #include "base.hpp" #include "ui/ui_base.hpp" -#include "ui/d2d_util.hpp" namespace cru::ui::render { - /* About Render Object - * - * Render object is a concrete subclass of RenderObject class. - * It represents a painting action on a d2d render target. By - * overriding "Draw" virtual method, it can customize its painting - * action. - */ - - - struct IRenderHost : Interface - { - virtual void InvalidateRender() = 0; - }; - - - class RenderObject : public Object - { - protected: - RenderObject() = default; - public: - RenderObject(const RenderObject& other) = delete; - RenderObject(RenderObject&& other) = delete; - RenderObject& operator=(const RenderObject& other) = delete; - RenderObject& operator=(RenderObject&& other) = delete; - ~RenderObject() override = default; - - virtual void Draw(ID2D1RenderTarget* render_target) = 0; - - IRenderHost* GetRenderHost() const - { - return render_host_; - } - - void SetRenderHost(IRenderHost* new_render_host); - - protected: - virtual void OnRenderHostChanged(IRenderHost* old_render_host, IRenderHost* new_render_host); - - void InvalidateRenderHost(); - - private: - IRenderHost* render_host_ = nullptr; - }; - - - class StrokeRenderObject : public virtual RenderObject - { - protected: - StrokeRenderObject() = default; - public: - StrokeRenderObject(const StrokeRenderObject& other) = delete; - StrokeRenderObject(StrokeRenderObject&& other) = delete; - StrokeRenderObject& operator=(const StrokeRenderObject& other) = delete; - StrokeRenderObject& operator=(StrokeRenderObject&& other) = delete; - ~StrokeRenderObject() override = default; - - float GetStrokeWidth() const - { - return stroke_width_; - } - - void SetStrokeWidth(float new_stroke_width); - - Microsoft::WRL::ComPtr<ID2D1Brush> GetBrush() const - { - return brush_; - } - - void SetBrush(Microsoft::WRL::ComPtr<ID2D1Brush> new_brush); - - Microsoft::WRL::ComPtr<ID2D1StrokeStyle> GetStrokeStyle() const - { - return stroke_style_; - } - - void SetStrokeStyle(Microsoft::WRL::ComPtr<ID2D1StrokeStyle> new_stroke_style); - - private: - float stroke_width_ = 1.0f; - Microsoft::WRL::ComPtr<ID2D1Brush> brush_ = nullptr; - Microsoft::WRL::ComPtr<ID2D1StrokeStyle> stroke_style_ = nullptr; - }; - - - class FillRenderObject : public virtual RenderObject - { - protected: - FillRenderObject() = default; - public: - FillRenderObject(const FillRenderObject& other) = delete; - FillRenderObject(FillRenderObject&& other) = delete; - FillRenderObject& operator=(const FillRenderObject& other) = delete; - FillRenderObject& operator=(FillRenderObject&& other) = delete; - ~FillRenderObject() override = default; - - Microsoft::WRL::ComPtr<ID2D1Brush> GetBrush() const - { - return brush_; - } - - void SetBrush(Microsoft::WRL::ComPtr<ID2D1Brush> new_brush); - - private: - Microsoft::WRL::ComPtr<ID2D1Brush> brush_ = nullptr; - }; - - - namespace details - { - template <typename TShapeType> - class ShapeRenderObject : public virtual RenderObject - { - public: - using ShapeType = TShapeType; - protected: - ShapeRenderObject() = default; - public: - ShapeRenderObject(const ShapeRenderObject& other) = delete; - ShapeRenderObject& operator=(const ShapeRenderObject& other) = delete; - ShapeRenderObject(ShapeRenderObject&& other) = delete; - ShapeRenderObject& operator=(ShapeRenderObject&& other) = delete; - ~ShapeRenderObject() override = default; - - ShapeType GetShape() const - { - return shape_; - } - - void SetShape(const ShapeType& new_shape) - { - if (new_shape == shape_) - return; - - shape_ = new_shape; - InvalidateRenderHost(); - } - - private: - ShapeType shape_; - }; +struct MeasureConstraint +{ + std::optional<float> min_width; + std::optional<float> min_height; + std::optional<float> max_width; + std::optional<float> max_height; +}; - extern template class ShapeRenderObject<Rect>; - extern template class ShapeRenderObject<RoundedRect>; - extern template class ShapeRenderObject<Ellipse>; - } +struct LayoutConstraint +{ + float preferred_width; + float preferred_height; +}; - using RectangleRenderObject = details::ShapeRenderObject<Rect>; - using RoundedRectangleRenderObject = details::ShapeRenderObject<RoundedRect>; - using EllipseRenderObject = details::ShapeRenderObject<Ellipse>; +struct IRenderHost : Interface +{ + virtual void InvalidatePaint() = 0; + virtual void InvalidateLayout() = 0; +}; - namespace details - { - template<typename TShapeType, typename TD2D1ShapeType, void (ID2D1RenderTarget::*draw_function)(const TD2D1ShapeType&, ID2D1Brush*, float, ID2D1StrokeStyle*)> - class ShapeStrokeRenderObject : public ShapeRenderObject<TShapeType>, public StrokeRenderObject - { - public: - ShapeStrokeRenderObject() = default; - ShapeStrokeRenderObject(const ShapeStrokeRenderObject& other) = delete; - ShapeStrokeRenderObject& operator=(const ShapeStrokeRenderObject& other) = delete; - ShapeStrokeRenderObject(ShapeStrokeRenderObject&& other) = delete; - ShapeStrokeRenderObject& operator=(ShapeStrokeRenderObject&& other) = delete; - ~ShapeStrokeRenderObject() = default; +// features: +// 1. tree +// 2. layout +// 3. paint +// 3. hit test +class RenderObject : public Object +{ +protected: + RenderObject() = default; - protected: - void Draw(ID2D1RenderTarget* render_target) override - { - const auto brush = GetBrush(); - if (brush != nullptr) - (render_target->*draw_function)(Convert(GetShape()), brush.Get(), GetStrokeWidth(), GetStrokeStyle().Get()); - } - }; +public: + RenderObject(const RenderObject& other) = delete; + RenderObject(RenderObject&& other) = delete; + RenderObject& operator=(const RenderObject& other) = delete; + RenderObject& operator=(RenderObject&& other) = delete; + ~RenderObject() override = default; - extern template ShapeStrokeRenderObject<Rect, D2D1_RECT_F, &ID2D1RenderTarget::DrawRectangle>; - extern template ShapeStrokeRenderObject<RoundedRect, D2D1_ROUNDED_RECT, &ID2D1RenderTarget::DrawRoundedRectangle>; - extern template ShapeStrokeRenderObject<Ellipse, D2D1_ELLIPSE, &ID2D1RenderTarget::DrawEllipse>; - } + IRenderHost* GetRenderHost() const { return render_host_; } + void SetRenderHost(IRenderHost* new_render_host); - using RectangleStrokeRenderObject = details::ShapeStrokeRenderObject<Rect, D2D1_RECT_F, &ID2D1RenderTarget::DrawRectangle>; - using RoundedRectangleStrokeRenderObject = details::ShapeStrokeRenderObject<RoundedRect, D2D1_ROUNDED_RECT, &ID2D1RenderTarget::DrawRoundedRectangle>; - using EllipseStrokeRenderObject = details::ShapeStrokeRenderObject<Ellipse, D2D1_ELLIPSE, &ID2D1RenderTarget::DrawEllipse>; + RenderObject* GetParent() const { return parent_; } + const std::vector<RenderObject*>& GetChildren() const { return children_; } + void AddChild(RenderObject* render_object, int position); + void RemoveChild(int position); - namespace details - { - template<typename TShapeType, typename TD2D1ShapeType, void (ID2D1RenderTarget::*fill_function)(const TD2D1ShapeType&, ID2D1Brush*)> - class ShapeFillRenderObject : public ShapeRenderObject<TShapeType>, public StrokeRenderObject - { - public: - ShapeFillRenderObject() = default; - ShapeFillRenderObject(const ShapeFillRenderObject& other) = delete; - ShapeFillRenderObject& operator=(const ShapeFillRenderObject& other) = delete; - ShapeFillRenderObject(ShapeFillRenderObject&& other) = delete; - ShapeFillRenderObject& operator=(ShapeFillRenderObject&& other) = delete; - ~ShapeFillRenderObject() = default; + virtual void Measure(const MeasureConstraint& constraint) = 0; + virtual void Layout(const LayoutConstraint& constraint) = 0; - protected: - void Draw(ID2D1RenderTarget* render_target) override - { - const auto brush = GetBrush(); - if (brush != nullptr) - (render_target->*fill_function)(Convert(GetShape()), brush.Get()); - } - }; + virtual void Draw(ID2D1RenderTarget* render_target) = 0; - extern template ShapeFillRenderObject<Rect, D2D1_RECT_F, &ID2D1RenderTarget::FillRectangle>; - extern template ShapeFillRenderObject<RoundedRect, D2D1_ROUNDED_RECT, &ID2D1RenderTarget::FillRoundedRectangle>; - extern template ShapeFillRenderObject<Ellipse, D2D1_ELLIPSE, &ID2D1RenderTarget::FillEllipse>; - } + virtual void HitTest(const Point& point) = 0; - using RectangleFillRenderObject = details::ShapeFillRenderObject<Rect, D2D1_RECT_F, &ID2D1RenderTarget::FillRectangle>; - using RoundedRectangleFillRenderObject = details::ShapeFillRenderObject<RoundedRect, D2D1_ROUNDED_RECT, &ID2D1RenderTarget::FillRoundedRectangle>; - using EllipseFillRenderObject = details::ShapeFillRenderObject<Ellipse, D2D1_ELLIPSE, &ID2D1RenderTarget::FillEllipse>; +protected: + virtual void OnRenderHostChanged(IRenderHost* old_render_host, + IRenderHost* new_render_host); + void InvalidateRenderHostPaint() const; + void InvalidateRenderHostLayout() const; - class CustomDrawHandlerRenderObject : public RenderObject - { - public: - using DrawHandler = std::function<void(ID2D1RenderTarget*)>; + virtual void OnParentChanged(RenderObject* old_parent, + RenderObject* new_parent); - CustomDrawHandlerRenderObject() = default; - CustomDrawHandlerRenderObject(const CustomDrawHandlerRenderObject& other) = delete; - CustomDrawHandlerRenderObject& operator=(const CustomDrawHandlerRenderObject& other) = delete; - CustomDrawHandlerRenderObject(CustomDrawHandlerRenderObject&& other) = delete; - CustomDrawHandlerRenderObject& operator=(CustomDrawHandlerRenderObject&& other) = delete; - ~CustomDrawHandlerRenderObject() override = default; + virtual void OnAddChild(RenderObject* new_child, int position); + virtual void OnRemoveChild(RenderObject* removed_child, int position); - DrawHandler GetDrawHandler() const - { - return draw_handler_; - } +private: + void SetParent(RenderObject* new_parent); - void SetDrawHandler(DrawHandler new_draw_handler); +private: + IRenderHost* render_host_ = nullptr; + RenderObject* parent_ = nullptr; + std::vector<RenderObject*> children_; +}; - protected: - void Draw(ID2D1RenderTarget* render_target) override; - private: - DrawHandler draw_handler_{}; - }; -} +class LinearLayoutRenderObject : public RenderObject +{ +public: + LinearLayoutRenderObject() = default; + LinearLayoutRenderObject(const LinearLayoutRenderObject& other) = delete; + LinearLayoutRenderObject& operator=(const LinearLayoutRenderObject& other) = + delete; + LinearLayoutRenderObject(LinearLayoutRenderObject&& other) = delete; + LinearLayoutRenderObject& operator=(LinearLayoutRenderObject&& other) = + delete; + ~LinearLayoutRenderObject() = default; + + void Measure(const MeasureConstraint& constraint) override; + +private: +}; +} // namespace cru::ui::render diff --git a/src/ui/ui_base.hpp b/src/ui/ui_base.hpp index c26bfe0e..17cb9acb 100644 --- a/src/ui/ui_base.hpp +++ b/src/ui/ui_base.hpp @@ -8,7 +8,7 @@ namespace cru::ui { - struct Point + struct Point final { constexpr static Point Zero() { @@ -33,7 +33,7 @@ namespace cru::ui } - struct Size + struct Size final { constexpr static Size Zero() { @@ -68,7 +68,7 @@ namespace cru::ui } - struct Thickness + struct Thickness final { constexpr static Thickness Zero() { @@ -122,7 +122,7 @@ namespace cru::ui float bottom; }; - constexpr bool operator == (const Thickness& left, const Thickness& right) + constexpr bool operator==(const Thickness& left, const Thickness& right) { return left.left == right.left && left.top == right.top && @@ -130,13 +130,13 @@ namespace cru::ui left.bottom == right.bottom; } - constexpr bool operator != (const Thickness& left, const Thickness& right) + constexpr bool operator!=(const Thickness& left, const Thickness& right) { return !(left == right); } - struct Rect + struct Rect final { constexpr Rect() = default; constexpr Rect(const float left, const float top, const float width, const float height) @@ -228,7 +228,7 @@ namespace cru::ui } - struct RoundedRect + struct RoundedRect final { constexpr RoundedRect() = default; constexpr RoundedRect(const Rect& rect, const float radius_x, const float radius_y) @@ -239,17 +239,18 @@ namespace cru::ui float radius_y = 0.0f; }; - constexpr bool operator == (const RoundedRect& left, const RoundedRect& right) + constexpr bool operator==(const RoundedRect& left, const RoundedRect& right) { return left.rect == right.rect && left.radius_x == right.radius_x && left.radius_y == right.radius_y; } - constexpr bool operator != (const RoundedRect& left, const RoundedRect& right) + constexpr bool operator!=(const RoundedRect& left, const RoundedRect& right) { return !(left == right); } - struct Ellipse + + struct Ellipse final { constexpr Ellipse() = default; constexpr Ellipse(const Point& center, const float radius_x, const float radius_y) @@ -270,26 +271,18 @@ namespace cru::ui float radius_y = 0.0f; }; - constexpr bool operator == (const Ellipse& left, const Ellipse& right) + constexpr bool operator==(const Ellipse& left, const Ellipse& right) { return left.center == right.center && left.radius_x == right.radius_x && left.radius_y == right.radius_y; } - constexpr bool operator != (const Ellipse& left, const Ellipse& right) + constexpr bool operator!=(const Ellipse& left, const Ellipse& right) { return !(left == right); } - enum class MouseButton - { - Left, - Right, - Middle - }; - - - struct TextRange + struct TextRange final { constexpr static std::optional<TextRange> FromTwoSides(unsigned first, unsigned second) { @@ -317,8 +310,4 @@ namespace cru::ui unsigned position = 0; unsigned count = 0; }; - - bool IsKeyDown(int virtual_code); - bool IsKeyToggled(int virtual_code); - bool IsAnyMouseButtonDown(); } diff --git a/src/ui/window.cpp b/src/ui/window.cpp index 86fa4436..51b3f628 100644 --- a/src/ui/window.cpp +++ b/src/ui/window.cpp @@ -7,29 +7,6 @@ namespace cru::ui { - WindowClass::WindowClass(const String& name, WNDPROC window_proc, HINSTANCE h_instance) - : name_(name) - { - WNDCLASSEX window_class; - window_class.cbSize = sizeof(WNDCLASSEX); - - window_class.style = CS_HREDRAW | CS_VREDRAW; - window_class.lpfnWndProc = window_proc; - window_class.cbClsExtra = 0; - window_class.cbWndExtra = 0; - window_class.hInstance = h_instance; - window_class.hIcon = LoadIcon(NULL, IDI_APPLICATION); - window_class.hCursor = LoadCursor(NULL, IDC_ARROW); - window_class.hbrBackground = GetSysColorBrush(COLOR_BTNFACE); - window_class.lpszMenuName = NULL; - window_class.lpszClassName = name.c_str(); - window_class.hIconSm = NULL; - - atom_ = RegisterClassEx(&window_class); - if (atom_ == 0) - throw std::runtime_error("Failed to create window class."); - } - LRESULT __stdcall GeneralWndProc(HWND hWnd, UINT Msg, WPARAM wParam, LPARAM lParam) { auto window = WindowManager::GetInstance()->FromHandle(hWnd); diff --git a/src/ui/window.hpp b/src/ui/window.hpp index e96d4d92..d3374684 100644 --- a/src/ui/window.hpp +++ b/src/ui/window.hpp @@ -17,32 +17,6 @@ namespace cru::graph namespace cru::ui { - class WindowClass : public Object - { - public: - WindowClass(const String& name, WNDPROC window_proc, HINSTANCE h_instance); - WindowClass(const WindowClass& other) = delete; - WindowClass(WindowClass&& other) = delete; - WindowClass& operator=(const WindowClass& other) = delete; - WindowClass& operator=(WindowClass&& other) = delete; - ~WindowClass() override = default; - - - const wchar_t* GetName() const - { - return name_.c_str(); - } - - ATOM GetAtom() const - { - return atom_; - } - - private: - String name_; - ATOM atom_; - }; - class WindowManager : public Object { public: @@ -85,7 +59,7 @@ namespace cru::ui - class Window final : public SingleChildControl + class Window final : public ContentControl { friend class WindowManager; public: diff --git a/src/ui/window_class.cpp b/src/ui/window_class.cpp new file mode 100644 index 00000000..456d9492 --- /dev/null +++ b/src/ui/window_class.cpp @@ -0,0 +1,25 @@ +#include "window_class.hpp" + +namespace cru::ui { +WindowClass::WindowClass(const String& name, WNDPROC window_proc, + HINSTANCE h_instance) + : name_(name) { + WNDCLASSEX window_class; + window_class.cbSize = sizeof(WNDCLASSEX); + + window_class.style = CS_HREDRAW | CS_VREDRAW; + window_class.lpfnWndProc = window_proc; + window_class.cbClsExtra = 0; + window_class.cbWndExtra = 0; + window_class.hInstance = h_instance; + window_class.hIcon = LoadIcon(NULL, IDI_APPLICATION); + window_class.hCursor = LoadCursor(NULL, IDC_ARROW); + window_class.hbrBackground = GetSysColorBrush(COLOR_BTNFACE); + window_class.lpszMenuName = NULL; + window_class.lpszClassName = name.c_str(); + window_class.hIconSm = NULL; + + atom_ = ::RegisterClassExW(&window_class); + if (atom_ == 0) throw std::runtime_error("Failed to create window class."); +} +} // namespace cru::ui diff --git a/src/ui/window_class.hpp b/src/ui/window_class.hpp new file mode 100644 index 00000000..66babd94 --- /dev/null +++ b/src/ui/window_class.hpp @@ -0,0 +1,26 @@ +#pragma once +#include "pre.hpp" + +#include "system_headers.hpp" + +#include "base.hpp" + +namespace cru::ui { +class WindowClass : public Object { + public: + WindowClass(const String& name, WNDPROC window_proc, HINSTANCE h_instance); + WindowClass(const WindowClass& other) = delete; + WindowClass(WindowClass&& other) = delete; + WindowClass& operator=(const WindowClass& other) = delete; + WindowClass& operator=(WindowClass&& other) = delete; + ~WindowClass() override = default; + + const wchar_t* GetName() const { return name_.c_str(); } + + ATOM GetAtom() const { return atom_; } + + private: + String name_; + ATOM atom_; +}; +} // namespace cru::ui diff --git a/src/util/any_map.cpp b/src/util/any_map.cpp new file mode 100644 index 00000000..c49464d3 --- /dev/null +++ b/src/util/any_map.cpp @@ -0,0 +1,30 @@ +#include "any_map.hpp" + +namespace cru::util { +AnyMap::ListenerToken AnyMap::RegisterValueChangeListener( + const String& key, const Listener& listener) { + const auto token = current_listener_token_++; + map_[key].second.push_back(token); + listeners_.emplace(token, listener); + return token; +} + +void AnyMap::UnregisterValueChangeListener(const ListenerToken token) { + const auto find_result = listeners_.find(token); + if (find_result != listeners_.cend()) listeners_.erase(find_result); +} + +void AnyMap::InvokeListeners(std::list<ListenerToken>& listener_list, + const std::any& value) { + auto i = listener_list.cbegin(); + while (i != listener_list.cend()) { + auto current_i = i++; + const auto find_result = listeners_.find(*current_i); + if (find_result != listeners_.cend()) + find_result->second(value); + else + listener_list.erase( + current_i); // otherwise remove the invalid listener token. + } +} +} // namespace cru::util diff --git a/src/util/any_map.hpp b/src/util/any_map.hpp new file mode 100644 index 00000000..d82167d2 --- /dev/null +++ b/src/util/any_map.hpp @@ -0,0 +1,94 @@ +#pragma once +#include "pre.hpp" + +#include <any> +#include <functional> +#include <optional> +#include <typeinfo> +#include <unordered_map> + +#include "base.hpp" +#include "format.hpp" + +namespace cru::util { +// A map with String as key and any type as value. +// It also has notification when value with specified key changed. +class AnyMap : public Object { + public: + using ListenerToken = long; + using Listener = std::function<void(const std::any&)>; + + AnyMap() = default; + AnyMap(const AnyMap& other) = delete; + AnyMap(AnyMap&& other) = delete; + AnyMap& operator=(const AnyMap& other) = delete; + AnyMap& operator=(AnyMap&& other) = delete; + ~AnyMap() override = default; + + // return the value if the value exists and the type of value is T. + // return a null optional if value doesn't exists. + // throw std::runtime_error if type is mismatch. + template <typename T> + std::optional<T> GetOptionalValue(const String& key) const { + try { + const auto find_result = map_.find(key); + if (find_result != map_.cend()) { + const auto& value = find_result->second.first; + if (value.has_value()) return std::any_cast<T>(value); + return std::nullopt; + } + return std::nullopt; + } catch (const std::bad_any_cast&) { + throw std::runtime_error( + Format("Value of key \"{}\" in AnyMap is not of the type {}.", + ToUtf8String(key), typeid(T).name())); + } + } + + // return the value if the value exists and the type of value is T. + // throw if value doesn't exists. (different from "GetOptionalValue"). + // throw std::runtime_error if type is mismatch. + template <typename T> + T GetValue(const String& key) const { + const auto optional_value = GetOptionalValue<T>(key); + if (optional_value.has_value()) + return optional_value.value(); + else + throw std::runtime_error( + Format("Key \"{}\" does not exists in AnyMap.", ToUtf8String(key))); + } + + // Set the value of key, and trigger all related listeners. + template <typename T> + void SetValue(const String& key, T&& value) { + auto& p = map_[key]; + p.first = std::make_any<T>(std::forward<T>(value)); + InvokeListeners(p.second, p.first); + } + + // Remove the value of the key. + void ClearValue(const String& key) { + auto& p = map_[key]; + p.first = std::any{}; + InvokeListeners(p.second, std::any{}); + } + + // Add a listener which is called when value of key is changed. + // Return a token used to remove the listener. + ListenerToken RegisterValueChangeListener(const String& key, + const Listener& listener); + + // Remove a listener by token. + void UnregisterValueChangeListener(ListenerToken token); + + private: + void InvokeListeners(std::list<ListenerToken>& listener_list, + const std::any& value); + + private: + std::unordered_map<String, std::pair<std::any, std::list<ListenerToken>>> + map_{}; + std::unordered_map<ListenerToken, Listener> listeners_{}; + ListenerToken current_listener_token_ = 0; +}; +} // namespace cru::util diff --git a/src/util/format.hpp b/src/util/format.hpp new file mode 100644 index 00000000..874c5b43 --- /dev/null +++ b/src/util/format.hpp @@ -0,0 +1,94 @@ +#pragma once +#include "pre.hpp" + +#include "base.hpp" + +namespace cru::util { +namespace details { +constexpr StringView PlaceHolder(type_tag<String>) { return StringView(L"{}"); } + +constexpr MultiByteStringView PlaceHolder(type_tag<MultiByteString>) { + return MultiByteStringView("{}"); +} + +template <typename TString> +void FormatInternal(TString& string) { + const auto find_result = string.find(PlaceHolder(type_tag<TString>{})); + if (find_result != TString::npos) + throw std::invalid_argument("There is more placeholders than args."); +} + +template <typename TString, typename T, typename... TRest> +void FormatInternal(TString& string, const T& arg, const TRest&... args) { + const auto find_result = string.find(PlaceHolder(type_tag<TString>{})); + if (find_result == TString::npos) + throw std::invalid_argument("There is less placeholders than args."); + + string.replace(find_result, 2, FormatToString(arg, type_tag<TString>{})); + FormatInternal<TString>(string, args...); +} +} // namespace details + +template <typename... T> +String Format(const StringView& format, const T&... args) { + String result(format); + details::FormatInternal<String>(result, args...); + return result; +} + +template <typename... T> +MultiByteString Format(const MultiByteStringView& format, const T&... args) { + MultiByteString result(format); + details::FormatInternal<MultiByteString>(result, args...); + return result; +} + +#define CRU_FORMAT_NUMBER(type) \ + inline String FormatToString(const type number, type_tag<String>) { \ + return std::to_wstring(number); \ + } \ + inline MultiByteString FormatToString(const type number, \ + type_tag<MultiByteString>) { \ + return std::to_string(number); \ + } + +CRU_FORMAT_NUMBER(int) +CRU_FORMAT_NUMBER(short) +CRU_FORMAT_NUMBER(long) +CRU_FORMAT_NUMBER(long long) +CRU_FORMAT_NUMBER(unsigned int) +CRU_FORMAT_NUMBER(unsigned short) +CRU_FORMAT_NUMBER(unsigned long) +CRU_FORMAT_NUMBER(unsigned long long) +CRU_FORMAT_NUMBER(float) +CRU_FORMAT_NUMBER(double) + +#undef CRU_FORMAT_NUMBER + +inline StringView FormatToString(const String& string, type_tag<String>) { + return string; +} + +inline MultiByteString FormatToString(const MultiByteString& string, + type_tag<MultiByteString>) { + return string; +} + +inline StringView FormatToString(const StringView& string, type_tag<String>) { + return string; +} + +inline MultiByteStringView FormatToString(const MultiByteStringView& string, + type_tag<MultiByteString>) { + return string; +} + +inline StringView FormatToString(const wchar_t* string, type_tag<String>) { + return StringView(string); +} + +inline MultiByteStringView FormatToString(const char* string, + type_tag<MultiByteString>) { + return MultiByteString(string); +} +} // namespace cru::util diff --git a/src/util/math_util.hpp b/src/util/math_util.hpp new file mode 100644 index 00000000..01348641 --- /dev/null +++ b/src/util/math_util.hpp @@ -0,0 +1,51 @@ +#pragma once +#include "pre.hpp" + +#include <optional> +#include <type_traits> + +namespace cru::util { +template <typename T, typename = std::enable_if_t<std::is_arithmetic_v<T>>> +float Coerce(const T n, const std::optional<T> min, + const std::optional<T> max) { + if (min.has_value() && n < min.value()) return min.value(); + if (max.has_value() && n > max.value()) return max.value(); + return n; +} + +template <typename T, typename = std::enable_if_t<std::is_arithmetic_v<T>>> +float Coerce(const T n, const T min, const T max) { + if (n < min) return min; + if (n > max) return max; + return n; +} + +template <typename T, typename = std::enable_if_t<std::is_arithmetic_v<T>>> +float Coerce(const T n, const std::nullopt_t, const std::optional<T> max) { + if (max.has_value() && n > max.value()) return max.value(); + return n; +} + +template <typename T, typename = std::enable_if_t<std::is_arithmetic_v<T>>> +float Coerce(const T n, const std::optional<T> min, const std::nullopt_t) { + if (min.has_value() && n < min.value()) return min.value(); + return n; +} + +template <typename T, typename = std::enable_if_t<std::is_arithmetic_v<T>>> +float Coerce(const T n, const std::nullopt_t, const T max) { + if (n > max) return max; + return n; +} + +template <typename T, typename = std::enable_if_t<std::is_arithmetic_v<T>>> +float Coerce(const T n, const T min, const std::nullopt_t) { + if (n < min) return min; + return n; +} + +template <typename T, typename = std::enable_if_t<std::is_arithmetic_v<T>>> +T AtLeast0(const T value) { + return value < static_cast<T>(0) ? static_cast<T>(0) : value; +} +} // namespace cru::util diff --git a/src/util/string_util.cpp b/src/util/string_util.cpp new file mode 100644 index 00000000..3c765259 --- /dev/null +++ b/src/util/string_util.cpp @@ -0,0 +1,21 @@ +#include "string_util.hpp" + +#include "system_headers.hpp" +#include "exception.hpp" + +namespace cru::util { +MultiByteString ToUtf8String(const StringView& string) { + if (string.empty()) return MultiByteString(); + + const auto length = ::WideCharToMultiByte(CP_UTF8, 0, string.data(), -1, + nullptr, 0, nullptr, nullptr); + MultiByteString result; + result.reserve(length); + if (::WideCharToMultiByte(CP_UTF8, 0, string.data(), -1, result.data(), + static_cast<int>(result.capacity()), nullptr, + nullptr) == 0) + throw Win32Error(::GetLastError(), + "Failed to convert wide string to UTF-8."); + return result; +} +} diff --git a/src/util/string_util.hpp b/src/util/string_util.hpp new file mode 100644 index 00000000..6d060089 --- /dev/null +++ b/src/util/string_util.hpp @@ -0,0 +1,8 @@ +#pragma once +#include "pre.hpp" + +#include "base.hpp" + +namespace cru::util { +MultiByteString ToUtf8String(const StringView& string); +} |