From d86a71f79afe0e4dac768f61d6bff690567aca5b Mon Sep 17 00:00:00 2001 From: crupest Date: Sun, 24 May 2020 01:40:02 +0800 Subject: ... --- demos/input_method/main.cpp | 12 +- demos/main/main.cpp | 20 +- include/cru/common/PreConfig.hpp | 8 + include/cru/common/SelfResolvable.hpp | 51 +++ include/cru/common/base.hpp | 2 +- include/cru/common/bitmask.hpp | 2 +- include/cru/common/event.hpp | 4 +- include/cru/common/logger.hpp | 2 +- include/cru/common/pre_config.hpp | 8 - include/cru/common/self_resolvable.hpp | 51 --- include/cru/platform/GraphBase.hpp | 419 +++++++++++++++++++++ include/cru/platform/HeapDebug.hpp | 7 + include/cru/platform/check.hpp | 4 +- include/cru/platform/exception.hpp | 2 +- include/cru/platform/graph/TextLayout.hpp | 23 ++ include/cru/platform/graph/base.hpp | 6 +- include/cru/platform/graph/brush.hpp | 2 +- include/cru/platform/graph/factory.hpp | 10 +- include/cru/platform/graph/font.hpp | 2 +- include/cru/platform/graph/geometry.hpp | 2 +- include/cru/platform/graph/painter.hpp | 2 +- include/cru/platform/graph/resource.hpp | 2 +- include/cru/platform/graph/text_layout.hpp | 23 -- include/cru/platform/graph/util/painter.hpp | 2 +- include/cru/platform/graph_base.hpp | 419 --------------------- include/cru/platform/heap_debug.hpp | 7 - include/cru/platform/matrix.hpp | 2 +- include/cru/platform/native/InputMethod.hpp | 77 ++++ include/cru/platform/native/UiApplication.hpp | 58 +++ include/cru/platform/native/base.hpp | 8 +- include/cru/platform/native/cursor.hpp | 4 +- include/cru/platform/native/input_method.hpp | 77 ---- include/cru/platform/native/keyboard.hpp | 2 +- include/cru/platform/native/ui_application.hpp | 58 --- include/cru/platform/native/window.hpp | 6 +- include/cru/platform/resource.hpp | 2 +- include/cru/ui/ClickDetector.hpp | 86 +++++ include/cru/ui/ContentControl.hpp | 29 ++ include/cru/ui/LayoutControl.hpp | 31 ++ include/cru/ui/NoChildControl.hpp | 24 ++ include/cru/ui/UiEvent.hpp | 229 +++++++++++ include/cru/ui/UiHost.hpp | 170 +++++++++ include/cru/ui/UiManager.hpp | 35 ++ include/cru/ui/base.hpp | 6 +- include/cru/ui/click_detector.hpp | 86 ----- include/cru/ui/content_control.hpp | 29 -- include/cru/ui/control.hpp | 8 +- include/cru/ui/controls/FlexLayout.hpp | 41 ++ include/cru/ui/controls/StackLayout.hpp | 31 ++ include/cru/ui/controls/TextBlock.hpp | 38 ++ include/cru/ui/controls/TextBox.hpp | 49 +++ include/cru/ui/controls/base.hpp | 2 +- include/cru/ui/controls/button.hpp | 6 +- include/cru/ui/controls/container.hpp | 2 +- include/cru/ui/controls/flex_layout.hpp | 41 -- include/cru/ui/controls/stack_layout.hpp | 31 -- include/cru/ui/controls/text_block.hpp | 38 -- include/cru/ui/controls/text_box.hpp | 49 --- include/cru/ui/layout_control.hpp | 31 -- include/cru/ui/no_child_control.hpp | 24 -- include/cru/ui/render/BorderRenderObject.hpp | 102 +++++ include/cru/ui/render/CanvasRenderObject.hpp | 39 ++ include/cru/ui/render/FlexLayoutRenderObject.hpp | 42 +++ include/cru/ui/render/LayoutRenderObject.hpp | 88 +++++ include/cru/ui/render/LayoutUtility.hpp | 7 + include/cru/ui/render/RenderObject.hpp | 118 ++++++ include/cru/ui/render/ScrollRenderObject.hpp | 30 ++ include/cru/ui/render/StackLayoutRenderObject.hpp | 17 + include/cru/ui/render/TextRenderObject.hpp | 85 +++++ include/cru/ui/render/WindowRenderObject.hpp | 30 ++ include/cru/ui/render/base.hpp | 2 +- include/cru/ui/render/border_render_object.hpp | 102 ----- include/cru/ui/render/canvas_render_object.hpp | 39 -- .../cru/ui/render/flex_layout_render_object.hpp | 42 --- include/cru/ui/render/layout_render_object.hpp | 88 ----- include/cru/ui/render/layout_utility.hpp | 7 - include/cru/ui/render/render_object.hpp | 118 ------ include/cru/ui/render/scroll_render_object.hpp | 30 -- .../cru/ui/render/stack_layout_render_object.hpp | 17 - include/cru/ui/render/text_render_object.hpp | 85 ----- include/cru/ui/render/window_render_object.hpp | 30 -- include/cru/ui/ui_event.hpp | 229 ----------- include/cru/ui/ui_host.hpp | 170 --------- include/cru/ui/ui_manager.hpp | 35 -- include/cru/ui/window.hpp | 2 +- include/cru/win/WinPreConfig.hpp | 16 + include/cru/win/exception.hpp | 4 +- include/cru/win/graph/direct/ComResource.hpp | 11 + include/cru/win/graph/direct/ConvertUtil.hpp | 107 ++++++ include/cru/win/graph/direct/TextLayout.hpp | 54 +++ include/cru/win/graph/direct/brush.hpp | 6 +- include/cru/win/graph/direct/com_resource.hpp | 11 - include/cru/win/graph/direct/convert_util.hpp | 107 ------ include/cru/win/graph/direct/exception.hpp | 2 +- include/cru/win/graph/direct/factory.hpp | 4 +- include/cru/win/graph/direct/font.hpp | 6 +- include/cru/win/graph/direct/geometry.hpp | 6 +- include/cru/win/graph/direct/painter.hpp | 6 +- include/cru/win/graph/direct/resource.hpp | 4 +- include/cru/win/graph/direct/text_layout.hpp | 54 --- include/cru/win/native/GodWindow.hpp | 27 ++ include/cru/win/native/InputMethod.hpp | 98 +++++ include/cru/win/native/UiApplication.hpp | 76 ++++ include/cru/win/native/WindowClass.hpp | 24 ++ .../win/native/WindowNativeMessageEventArgs.hpp | 40 ++ include/cru/win/native/WindowRenderTarget.hpp | 47 +++ include/cru/win/native/base.hpp | 4 +- include/cru/win/native/cursor.hpp | 4 +- include/cru/win/native/exception.hpp | 2 +- include/cru/win/native/god_window.hpp | 27 -- include/cru/win/native/input_method.hpp | 98 ----- include/cru/win/native/keyboard.hpp | 4 +- include/cru/win/native/resource.hpp | 4 +- include/cru/win/native/ui_application.hpp | 76 ---- include/cru/win/native/window.hpp | 6 +- include/cru/win/native/window_class.hpp | 24 -- .../native/window_native_message_event_args.hpp | 40 -- include/cru/win/native/window_render_target.hpp | 47 --- include/cru/win/string.hpp | 4 +- include/cru/win/win_pre_config.hpp | 16 - src/common/CMakeLists.txt | 14 +- src/common/logger.cpp | 2 +- src/platform/CMakeLists.txt | 12 +- src/platform/graph/CMakeLists.txt | 18 +- src/platform/native/CMakeLists.txt | 14 +- src/platform/native/UiApplication.cpp | 15 + src/platform/native/ui_application.cpp | 15 - src/ui/CMakeLists.txt | 110 +++--- src/ui/ClickDetector.cpp | 129 +++++++ src/ui/ContentControl.cpp | 33 ++ src/ui/LayoutControl.cpp | 53 +++ src/ui/NoChildControl.cpp | 5 + src/ui/RoutedEventDispatch.hpp | 134 +++++++ src/ui/UiHost.cpp | 368 ++++++++++++++++++ src/ui/UiManager.cpp | 81 ++++ src/ui/click_detector.cpp | 129 ------- src/ui/content_control.cpp | 33 -- src/ui/control.cpp | 12 +- src/ui/controls/FlexLayout.cpp | 71 ++++ src/ui/controls/StackLayout.cpp | 27 ++ src/ui/controls/TextBlock.cpp | 44 +++ src/ui/controls/TextBox.cpp | 70 ++++ src/ui/controls/TextControlService.hpp | 227 +++++++++++ src/ui/controls/button.cpp | 16 +- src/ui/controls/container.cpp | 6 +- src/ui/controls/flex_layout.cpp | 71 ---- src/ui/controls/stack_layout.cpp | 27 -- src/ui/controls/text_block.cpp | 44 --- src/ui/controls/text_box.cpp | 70 ---- src/ui/controls/text_control_service.hpp | 227 ----------- src/ui/helper.cpp | 6 +- src/ui/helper.hpp | 2 +- src/ui/layout_control.cpp | 53 --- src/ui/no_child_control.cpp | 5 - src/ui/render/BorderRenderObject.cpp | 236 ++++++++++++ src/ui/render/CanvasRenderObject.cpp | 28 ++ src/ui/render/FlexLayoutRenderObject.cpp | 197 ++++++++++ src/ui/render/LayoutUtility.cpp | 15 + src/ui/render/RenderObject.cpp | 190 ++++++++++ src/ui/render/ScrollRenderObject.cpp | 1 + src/ui/render/StackLayoutRenderObject.cpp | 49 +++ src/ui/render/TextRenderObject.cpp | 177 +++++++++ src/ui/render/WindowRenderObject.cpp | 45 +++ src/ui/render/border_render_object.cpp | 236 ------------ src/ui/render/canvas_render_object.cpp | 28 -- src/ui/render/flex_layout_render_object.cpp | 197 ---------- src/ui/render/layout_utility.cpp | 15 - src/ui/render/render_object.cpp | 190 ---------- src/ui/render/scroll_render_object.cpp | 1 - src/ui/render/stack_layout_render_object.cpp | 49 --- src/ui/render/text_render_object.cpp | 177 --------- src/ui/render/window_render_object.cpp | 45 --- src/ui/routed_event_dispatch.hpp | 134 ------- src/ui/ui_host.cpp | 368 ------------------ src/ui/ui_manager.cpp | 81 ---- src/ui/window.cpp | 6 +- src/win/CMakeLists.txt | 14 +- src/win/DebugLogger.hpp | 24 ++ src/win/HeapDebug.cpp | 11 + src/win/debug_logger.hpp | 24 -- src/win/exception.cpp | 2 +- src/win/graph/direct/CMakeLists.txt | 34 +- src/win/graph/direct/TextLayout.cpp | 137 +++++++ src/win/graph/direct/brush.cpp | 8 +- src/win/graph/direct/factory.cpp | 16 +- src/win/graph/direct/font.cpp | 8 +- src/win/graph/direct/geometry.cpp | 8 +- src/win/graph/direct/painter.cpp | 16 +- src/win/graph/direct/resource.cpp | 4 +- src/win/graph/direct/text_layout.cpp | 137 ------- src/win/heap_debug.cpp | 11 - src/win/native/CMakeLists.txt | 56 +-- src/win/native/DpiUtil.hpp | 46 +++ src/win/native/GodWindow.cpp | 82 ++++ src/win/native/GodWindowMessage.hpp | 6 + src/win/native/InputMethod.cpp | 324 ++++++++++++++++ src/win/native/UiApplication.cpp | 124 ++++++ src/win/native/WindowClass.cpp | 28 ++ src/win/native/WindowD2DPainter.cpp | 22 ++ src/win/native/WindowD2DPainter.hpp | 21 ++ src/win/native/WindowManager.cpp | 56 +++ src/win/native/WindowManager.hpp | 51 +++ src/win/native/WindowRenderTarget.cpp | 78 ++++ src/win/native/cursor.cpp | 6 +- src/win/native/dpi_util.hpp | 46 --- src/win/native/god_window.cpp | 82 ---- src/win/native/god_window_message.hpp | 6 - src/win/native/input_method.cpp | 324 ---------------- src/win/native/keyboard.cpp | 2 +- src/win/native/timer.cpp | 2 +- src/win/native/timer.hpp | 6 +- src/win/native/ui_application.cpp | 124 ------ src/win/native/window.cpp | 28 +- src/win/native/window_class.cpp | 28 -- src/win/native/window_d2d_painter.cpp | 22 -- src/win/native/window_d2d_painter.hpp | 21 -- src/win/native/window_manager.cpp | 56 --- src/win/native/window_manager.hpp | 51 --- src/win/native/window_render_target.cpp | 78 ---- src/win/string.cpp | 4 +- test/win/CMakeLists.txt | 2 +- test/win/string.cpp | 2 +- tools/migrate-1/migrate-cmake.py | 26 ++ tools/migrate-1/migrate-files.py | 43 +++ tools/migrate-files.py | 0 225 files changed, 6053 insertions(+), 5984 deletions(-) create mode 100644 include/cru/common/PreConfig.hpp create mode 100644 include/cru/common/SelfResolvable.hpp delete mode 100644 include/cru/common/pre_config.hpp delete mode 100644 include/cru/common/self_resolvable.hpp create mode 100644 include/cru/platform/GraphBase.hpp create mode 100644 include/cru/platform/HeapDebug.hpp create mode 100644 include/cru/platform/graph/TextLayout.hpp delete mode 100644 include/cru/platform/graph/text_layout.hpp delete mode 100644 include/cru/platform/graph_base.hpp delete mode 100644 include/cru/platform/heap_debug.hpp create mode 100644 include/cru/platform/native/InputMethod.hpp create mode 100644 include/cru/platform/native/UiApplication.hpp delete mode 100644 include/cru/platform/native/input_method.hpp delete mode 100644 include/cru/platform/native/ui_application.hpp create mode 100644 include/cru/ui/ClickDetector.hpp create mode 100644 include/cru/ui/ContentControl.hpp create mode 100644 include/cru/ui/LayoutControl.hpp create mode 100644 include/cru/ui/NoChildControl.hpp create mode 100644 include/cru/ui/UiEvent.hpp create mode 100644 include/cru/ui/UiHost.hpp create mode 100644 include/cru/ui/UiManager.hpp delete mode 100644 include/cru/ui/click_detector.hpp delete mode 100644 include/cru/ui/content_control.hpp create mode 100644 include/cru/ui/controls/FlexLayout.hpp create mode 100644 include/cru/ui/controls/StackLayout.hpp create mode 100644 include/cru/ui/controls/TextBlock.hpp create mode 100644 include/cru/ui/controls/TextBox.hpp delete mode 100644 include/cru/ui/controls/flex_layout.hpp delete mode 100644 include/cru/ui/controls/stack_layout.hpp delete mode 100644 include/cru/ui/controls/text_block.hpp delete mode 100644 include/cru/ui/controls/text_box.hpp delete mode 100644 include/cru/ui/layout_control.hpp delete mode 100644 include/cru/ui/no_child_control.hpp create mode 100644 include/cru/ui/render/BorderRenderObject.hpp create mode 100644 include/cru/ui/render/CanvasRenderObject.hpp create mode 100644 include/cru/ui/render/FlexLayoutRenderObject.hpp create mode 100644 include/cru/ui/render/LayoutRenderObject.hpp create mode 100644 include/cru/ui/render/LayoutUtility.hpp create mode 100644 include/cru/ui/render/RenderObject.hpp create mode 100644 include/cru/ui/render/ScrollRenderObject.hpp create mode 100644 include/cru/ui/render/StackLayoutRenderObject.hpp create mode 100644 include/cru/ui/render/TextRenderObject.hpp create mode 100644 include/cru/ui/render/WindowRenderObject.hpp delete mode 100644 include/cru/ui/render/border_render_object.hpp delete mode 100644 include/cru/ui/render/canvas_render_object.hpp delete mode 100644 include/cru/ui/render/flex_layout_render_object.hpp delete mode 100644 include/cru/ui/render/layout_render_object.hpp delete mode 100644 include/cru/ui/render/layout_utility.hpp delete mode 100644 include/cru/ui/render/render_object.hpp delete mode 100644 include/cru/ui/render/scroll_render_object.hpp delete mode 100644 include/cru/ui/render/stack_layout_render_object.hpp delete mode 100644 include/cru/ui/render/text_render_object.hpp delete mode 100644 include/cru/ui/render/window_render_object.hpp delete mode 100644 include/cru/ui/ui_event.hpp delete mode 100644 include/cru/ui/ui_host.hpp delete mode 100644 include/cru/ui/ui_manager.hpp create mode 100644 include/cru/win/WinPreConfig.hpp create mode 100644 include/cru/win/graph/direct/ComResource.hpp create mode 100644 include/cru/win/graph/direct/ConvertUtil.hpp create mode 100644 include/cru/win/graph/direct/TextLayout.hpp delete mode 100644 include/cru/win/graph/direct/com_resource.hpp delete mode 100644 include/cru/win/graph/direct/convert_util.hpp delete mode 100644 include/cru/win/graph/direct/text_layout.hpp create mode 100644 include/cru/win/native/GodWindow.hpp create mode 100644 include/cru/win/native/InputMethod.hpp create mode 100644 include/cru/win/native/UiApplication.hpp create mode 100644 include/cru/win/native/WindowClass.hpp create mode 100644 include/cru/win/native/WindowNativeMessageEventArgs.hpp create mode 100644 include/cru/win/native/WindowRenderTarget.hpp delete mode 100644 include/cru/win/native/god_window.hpp delete mode 100644 include/cru/win/native/input_method.hpp delete mode 100644 include/cru/win/native/ui_application.hpp delete mode 100644 include/cru/win/native/window_class.hpp delete mode 100644 include/cru/win/native/window_native_message_event_args.hpp delete mode 100644 include/cru/win/native/window_render_target.hpp delete mode 100644 include/cru/win/win_pre_config.hpp create mode 100644 src/platform/native/UiApplication.cpp delete mode 100644 src/platform/native/ui_application.cpp create mode 100644 src/ui/ClickDetector.cpp create mode 100644 src/ui/ContentControl.cpp create mode 100644 src/ui/LayoutControl.cpp create mode 100644 src/ui/NoChildControl.cpp create mode 100644 src/ui/RoutedEventDispatch.hpp create mode 100644 src/ui/UiHost.cpp create mode 100644 src/ui/UiManager.cpp delete mode 100644 src/ui/click_detector.cpp delete mode 100644 src/ui/content_control.cpp create mode 100644 src/ui/controls/FlexLayout.cpp create mode 100644 src/ui/controls/StackLayout.cpp create mode 100644 src/ui/controls/TextBlock.cpp create mode 100644 src/ui/controls/TextBox.cpp create mode 100644 src/ui/controls/TextControlService.hpp delete mode 100644 src/ui/controls/flex_layout.cpp delete mode 100644 src/ui/controls/stack_layout.cpp delete mode 100644 src/ui/controls/text_block.cpp delete mode 100644 src/ui/controls/text_box.cpp delete mode 100644 src/ui/controls/text_control_service.hpp delete mode 100644 src/ui/layout_control.cpp delete mode 100644 src/ui/no_child_control.cpp create mode 100644 src/ui/render/BorderRenderObject.cpp create mode 100644 src/ui/render/CanvasRenderObject.cpp create mode 100644 src/ui/render/FlexLayoutRenderObject.cpp create mode 100644 src/ui/render/LayoutUtility.cpp create mode 100644 src/ui/render/RenderObject.cpp create mode 100644 src/ui/render/ScrollRenderObject.cpp create mode 100644 src/ui/render/StackLayoutRenderObject.cpp create mode 100644 src/ui/render/TextRenderObject.cpp create mode 100644 src/ui/render/WindowRenderObject.cpp delete mode 100644 src/ui/render/border_render_object.cpp delete mode 100644 src/ui/render/canvas_render_object.cpp delete mode 100644 src/ui/render/flex_layout_render_object.cpp delete mode 100644 src/ui/render/layout_utility.cpp delete mode 100644 src/ui/render/render_object.cpp delete mode 100644 src/ui/render/scroll_render_object.cpp delete mode 100644 src/ui/render/stack_layout_render_object.cpp delete mode 100644 src/ui/render/text_render_object.cpp delete mode 100644 src/ui/render/window_render_object.cpp delete mode 100644 src/ui/routed_event_dispatch.hpp delete mode 100644 src/ui/ui_host.cpp delete mode 100644 src/ui/ui_manager.cpp create mode 100644 src/win/DebugLogger.hpp create mode 100644 src/win/HeapDebug.cpp delete mode 100644 src/win/debug_logger.hpp create mode 100644 src/win/graph/direct/TextLayout.cpp delete mode 100644 src/win/graph/direct/text_layout.cpp delete mode 100644 src/win/heap_debug.cpp create mode 100644 src/win/native/DpiUtil.hpp create mode 100644 src/win/native/GodWindow.cpp create mode 100644 src/win/native/GodWindowMessage.hpp create mode 100644 src/win/native/InputMethod.cpp create mode 100644 src/win/native/UiApplication.cpp create mode 100644 src/win/native/WindowClass.cpp create mode 100644 src/win/native/WindowD2DPainter.cpp create mode 100644 src/win/native/WindowD2DPainter.hpp create mode 100644 src/win/native/WindowManager.cpp create mode 100644 src/win/native/WindowManager.hpp create mode 100644 src/win/native/WindowRenderTarget.cpp delete mode 100644 src/win/native/dpi_util.hpp delete mode 100644 src/win/native/god_window.cpp delete mode 100644 src/win/native/god_window_message.hpp delete mode 100644 src/win/native/input_method.cpp delete mode 100644 src/win/native/ui_application.cpp delete mode 100644 src/win/native/window_class.cpp delete mode 100644 src/win/native/window_d2d_painter.cpp delete mode 100644 src/win/native/window_d2d_painter.hpp delete mode 100644 src/win/native/window_manager.cpp delete mode 100644 src/win/native/window_manager.hpp delete mode 100644 src/win/native/window_render_target.cpp create mode 100644 tools/migrate-1/migrate-cmake.py create mode 100644 tools/migrate-1/migrate-files.py delete mode 100644 tools/migrate-files.py diff --git a/demos/input_method/main.cpp b/demos/input_method/main.cpp index 74654a2d..45924c96 100644 --- a/demos/input_method/main.cpp +++ b/demos/input_method/main.cpp @@ -1,9 +1,9 @@ -#include "cru/platform/graph/factory.hpp" -#include "cru/platform/graph/font.hpp" -#include "cru/platform/graph/painter.hpp" -#include "cru/platform/native/input_method.hpp" -#include "cru/platform/native/ui_application.hpp" -#include "cru/platform/native/window.hpp" +#include "cru/platform/graph/Factory.hpp" +#include "cru/platform/graph/Font.hpp" +#include "cru/platform/graph/Painter.hpp" +#include "cru/platform/native/InputMethod.hpp" +#include "cru/platform/native/UiApplication.hpp" +#include "cru/platform/native/Window.hpp" int main() { using namespace cru::platform; diff --git a/demos/main/main.cpp b/demos/main/main.cpp index d39aef88..832ef75e 100644 --- a/demos/main/main.cpp +++ b/demos/main/main.cpp @@ -1,13 +1,13 @@ -#include "cru/platform/heap_debug.hpp" -#include "cru/platform/native/ui_application.hpp" -#include "cru/platform/native/window.hpp" -#include "cru/ui/controls/button.hpp" -#include "cru/ui/controls/flex_layout.hpp" -#include "cru/ui/controls/stack_layout.hpp" -#include "cru/ui/controls/text_block.hpp" -#include "cru/ui/controls/text_box.hpp" -#include "cru/ui/ui_host.hpp" -#include "cru/ui/window.hpp" +#include "cru/platform/HeapDebug.hpp" +#include "cru/platform/native/UiApplication.hpp" +#include "cru/platform/native/Window.hpp" +#include "cru/ui/UiHost.hpp" +#include "cru/ui/Window.hpp" +#include "cru/ui/controls/Button.hpp" +#include "cru/ui/controls/FlexLayout.hpp" +#include "cru/ui/controls/StackLayout.hpp" +#include "cru/ui/controls/TextBlock.hpp" +#include "cru/ui/controls/TextBox.hpp" using cru::platform::native::CreateUiApplication; using cru::ui::Rect; diff --git a/include/cru/common/PreConfig.hpp b/include/cru/common/PreConfig.hpp new file mode 100644 index 00000000..4bccef1d --- /dev/null +++ b/include/cru/common/PreConfig.hpp @@ -0,0 +1,8 @@ +#pragma once + +#ifdef _MSC_VER +// disable the unnecessary warning about multi-inheritance +#pragma warning(disable : 4250) +#endif + +#define _CRT_SECURE_NO_WARNINGS diff --git a/include/cru/common/SelfResolvable.hpp b/include/cru/common/SelfResolvable.hpp new file mode 100644 index 00000000..94f3ae87 --- /dev/null +++ b/include/cru/common/SelfResolvable.hpp @@ -0,0 +1,51 @@ +#pragma once +#include "PreConfig.hpp" + +#include +#include + +namespace cru { +template +class SelfResolvable; + +template +class ObjectResolver { + friend SelfResolvable; + + private: + ObjectResolver(const std::shared_ptr& resolver) : resolver_(resolver) {} + + public: + ObjectResolver(const ObjectResolver&) = default; + ObjectResolver& operator=(const ObjectResolver&) = default; + ObjectResolver(ObjectResolver&&) = default; + ObjectResolver& operator=(ObjectResolver&&) = default; + ~ObjectResolver() = default; + + T* Resolve() const { + // resolver_ is null only when this has been moved. + // You shouldn't resolve a moved resolver. So assert it. + Expects(resolver_); + return *resolver_; + } + + private: + std::shared_ptr resolver_; +}; + +template +class SelfResolvable { + public: + SelfResolvable() : resolver_(new T*(static_cast(this))) {} + SelfResolvable(const SelfResolvable&) = delete; + SelfResolvable& operator=(const SelfResolvable&) = delete; + SelfResolvable(SelfResolvable&&) = delete; + SelfResolvable& operator=(SelfResolvable&&) = delete; + virtual ~SelfResolvable() { (*resolver_) = nullptr; } + + ObjectResolver CreateResolver() { return ObjectResolver(resolver_); } + + private: + std::shared_ptr resolver_; +}; +} // namespace cru diff --git a/include/cru/common/base.hpp b/include/cru/common/base.hpp index 20dd811d..ff7ab31e 100644 --- a/include/cru/common/base.hpp +++ b/include/cru/common/base.hpp @@ -1,5 +1,5 @@ #pragma once -#include "pre_config.hpp" +#include "PreConfig.hpp" #include diff --git a/include/cru/common/bitmask.hpp b/include/cru/common/bitmask.hpp index 95edee13..ddfdc86b 100644 --- a/include/cru/common/bitmask.hpp +++ b/include/cru/common/bitmask.hpp @@ -1,5 +1,5 @@ #pragma once -#include "base.hpp" +#include "Base.hpp" namespace cru { template diff --git a/include/cru/common/event.hpp b/include/cru/common/event.hpp index 8389b38e..377ca7f3 100644 --- a/include/cru/common/event.hpp +++ b/include/cru/common/event.hpp @@ -1,7 +1,7 @@ #pragma once -#include "base.hpp" +#include "Base.hpp" -#include "self_resolvable.hpp" +#include "SelfResolvable.hpp" #include #include diff --git a/include/cru/common/logger.hpp b/include/cru/common/logger.hpp index ab3f2250..bd16678b 100644 --- a/include/cru/common/logger.hpp +++ b/include/cru/common/logger.hpp @@ -1,5 +1,5 @@ #pragma once -#include "cru/common/base.hpp" +#include "cru/common/Base.hpp" #include #include diff --git a/include/cru/common/pre_config.hpp b/include/cru/common/pre_config.hpp deleted file mode 100644 index 4bccef1d..00000000 --- a/include/cru/common/pre_config.hpp +++ /dev/null @@ -1,8 +0,0 @@ -#pragma once - -#ifdef _MSC_VER -// disable the unnecessary warning about multi-inheritance -#pragma warning(disable : 4250) -#endif - -#define _CRT_SECURE_NO_WARNINGS diff --git a/include/cru/common/self_resolvable.hpp b/include/cru/common/self_resolvable.hpp deleted file mode 100644 index 19c06a1b..00000000 --- a/include/cru/common/self_resolvable.hpp +++ /dev/null @@ -1,51 +0,0 @@ -#pragma once -#include "pre_config.hpp" - -#include -#include - -namespace cru { -template -class SelfResolvable; - -template -class ObjectResolver { - friend SelfResolvable; - - private: - ObjectResolver(const std::shared_ptr& resolver) : resolver_(resolver) {} - - public: - ObjectResolver(const ObjectResolver&) = default; - ObjectResolver& operator=(const ObjectResolver&) = default; - ObjectResolver(ObjectResolver&&) = default; - ObjectResolver& operator=(ObjectResolver&&) = default; - ~ObjectResolver() = default; - - T* Resolve() const { - // resolver_ is null only when this has been moved. - // You shouldn't resolve a moved resolver. So assert it. - Expects(resolver_); - return *resolver_; - } - - private: - std::shared_ptr resolver_; -}; - -template -class SelfResolvable { - public: - SelfResolvable() : resolver_(new T*(static_cast(this))) {} - SelfResolvable(const SelfResolvable&) = delete; - SelfResolvable& operator=(const SelfResolvable&) = delete; - SelfResolvable(SelfResolvable&&) = delete; - SelfResolvable& operator=(SelfResolvable&&) = delete; - virtual ~SelfResolvable() { (*resolver_) = nullptr; } - - ObjectResolver CreateResolver() { return ObjectResolver(resolver_); } - - private: - std::shared_ptr resolver_; -}; -} // namespace cru diff --git a/include/cru/platform/GraphBase.hpp b/include/cru/platform/GraphBase.hpp new file mode 100644 index 00000000..af61eba3 --- /dev/null +++ b/include/cru/platform/GraphBase.hpp @@ -0,0 +1,419 @@ +#pragma once +#include "cru/common/Base.hpp" + +#include +#include +#include + +namespace cru::platform { +struct Point final { + constexpr Point() = default; + constexpr Point(const float x, const float y) : x(x), y(y) {} + + float x = 0; + float y = 0; +}; + +constexpr Point operator+(const Point& left, const Point& right) { + return Point(left.x + right.x, left.y + right.y); +} + +constexpr Point operator-(const Point& left, const Point& right) { + return Point(left.x - right.x, left.y - right.y); +} + +constexpr bool operator==(const Point& left, const Point& right) { + return left.x == right.x && left.y == right.y; +} + +constexpr bool operator!=(const Point& left, const Point& right) { + return !(left == right); +} + +struct Size final { + constexpr Size() = default; + constexpr Size(const float width, const float height) + : width(width), height(height) {} + + float width = 0; + float height = 0; +}; + +constexpr Size operator+(const Size& left, const Size& right) { + return Size(left.width + right.width, left.height + right.height); +} + +constexpr Size operator-(const Size& left, const Size& right) { + return Size(left.width - right.width, left.height - right.height); +} + +constexpr bool operator==(const Size& left, const Size& right) { + return left.width == right.width && left.height == right.height; +} + +constexpr bool operator!=(const Size& left, const Size& right) { + return !(left == right); +} + +struct Thickness final { + constexpr Thickness() : Thickness(0) {} + + constexpr explicit Thickness(const float width) + : left(width), top(width), right(width), bottom(width) {} + + constexpr explicit Thickness(const float horizontal, const float vertical) + : left(horizontal), top(vertical), right(horizontal), bottom(vertical) {} + + constexpr Thickness(const float left, const float top, const float right, + const float bottom) + : left(left), top(top), right(right), bottom(bottom) {} + + constexpr float GetHorizontalTotal() const { return left + right; } + + constexpr float GetVerticalTotal() const { return top + bottom; } + + void SetLeftRight(const float value) { left = right = value; } + + void SetTopBottom(const float value) { top = bottom = value; } + + void SetAll(const float value) { left = top = right = bottom = value; } + + constexpr float Validate() const { + return left >= 0.0 && top >= 0.0 && right >= 0.0 && bottom >= 0.0; + } + + float left; + float top; + float right; + float bottom; +}; + +constexpr bool operator==(const Thickness& left, const Thickness& right) { + return left.left == right.left && left.top == right.top && + left.right == right.right && left.bottom == right.bottom; +} + +constexpr bool operator!=(const Thickness& left, const Thickness& right) { + return !(left == right); +} + +struct Rect final { + constexpr Rect() = default; + constexpr Rect(const float left, const float top, const float width, + const float height) + : left(left), top(top), width(width), height(height) {} + constexpr Rect(const Point& lefttop, const Size& size) + : left(lefttop.x), + top(lefttop.y), + width(size.width), + height(size.height) {} + + constexpr static Rect FromVertices(const float left, const float top, + const float right, const float bottom) { + return Rect(left, top, right - left, bottom - top); + } + + constexpr static Rect FromCenter(const Point& center, const float width, + const float height) { + return Rect(center.x - width / 2.0f, center.y - height / 2.0f, width, + height); + } + + constexpr float GetRight() const { return left + width; } + + constexpr float GetBottom() const { return top + height; } + + constexpr Point GetLeftTop() const { return Point(left, top); } + + constexpr Point GetRightBottom() const { + return Point(left + width, top + height); + } + + constexpr Point GetLeftBottom() const { return Point(left, top + height); } + + constexpr Point GetRightTop() const { return Point(left + width, top); } + + constexpr Point GetCenter() const { + return Point(left + width / 2.0f, top + height / 2.0f); + } + + constexpr Size GetSize() const { return Size(width, height); } + + constexpr Rect Shrink(const Thickness& thickness) const { + return Rect(left + thickness.left, top + thickness.top, + width - thickness.GetHorizontalTotal(), + height - thickness.GetVerticalTotal()); + } + + constexpr bool IsPointInside(const Point& point) const { + return point.x >= left && point.x < GetRight() && point.y >= top && + point.y < GetBottom(); + } + + float left = 0.0f; + float top = 0.0f; + float width = 0.0f; + float height = 0.0f; +}; + +constexpr bool operator==(const Rect& left, const Rect& right) { + return left.left == right.left && left.top == right.top && + left.width == right.width && left.height == right.height; +} + +constexpr bool operator!=(const Rect& left, const Rect& right) { + return !(left == right); +} + +struct RoundedRect final { + constexpr RoundedRect() = default; + constexpr RoundedRect(const Rect& rect, const float radius_x, + const float radius_y) + : rect(rect), radius_x(radius_x), radius_y(radius_y) {} + + Rect rect{}; + float radius_x = 0.0f; + float radius_y = 0.0f; +}; + +constexpr bool operator==(const RoundedRect& left, const RoundedRect& right) { + return left.rect == right.rect && left.radius_x == right.radius_x && + left.radius_y == right.radius_y; +} + +constexpr bool operator!=(const RoundedRect& left, const RoundedRect& right) { + return !(left == right); +} + +struct Ellipse final { + constexpr Ellipse() = default; + constexpr Ellipse(const Point& center, const float radius_x, + const float radius_y) + : center(center), radius_x(radius_x), radius_y(radius_y) {} + + constexpr static Ellipse FromRect(const Rect& rect) { + return Ellipse(rect.GetCenter(), rect.width / 2.0f, rect.height / 2.0f); + } + + constexpr Rect GetBoundRect() const { + return Rect::FromCenter(center, radius_x * 2.0f, radius_y * 2.0f); + } + + Point center{}; + float radius_x = 0.0f; + float radius_y = 0.0f; +}; + +constexpr bool operator==(const Ellipse& left, const Ellipse& right) { + return left.center == right.center && left.radius_x == right.radius_x && + left.radius_y == right.radius_y; +} + +constexpr bool operator!=(const Ellipse& left, const Ellipse& right) { + return !(left == right); +} + +struct TextRange final { + constexpr static TextRange FromTwoSides(gsl::index start, gsl::index end) { + return TextRange(start, end - start); + } + + constexpr static TextRange FromTwoSides(gsl::index start, gsl::index end, + gsl::index offset) { + return TextRange(start + offset, end - start); + } + + constexpr TextRange() = default; + constexpr TextRange(const gsl::index position, const gsl::index count = 0) + : position(position), count(count) {} + + gsl::index GetEnd() const { return position + count; } + + TextRange Normalize() const { + auto result = *this; + if (result.count < 0) { + result.position += result.count; + result.count = -result.count; + } + return result; + } + + gsl::index position = 0; + gsl::index count = 0; +}; + +struct Color { + constexpr Color() : Color(0, 0, 0, 255) {} + constexpr Color(std::uint8_t red, std::uint8_t green, std::uint8_t blue, + std::uint8_t alpha = 255) + : red(red), green(green), blue(blue), alpha(alpha) {} + + constexpr static Color FromHex(std::uint32_t hex) { + const std::uint32_t mask = 0b11111111; + return Color((hex >> 16) & mask, (hex >> 8) & mask, hex & mask, 255); + } + + constexpr static Color FromHexAlpha(std::uint32_t hex) { + const std::uint32_t mask = 0b11111111; + return Color((hex >> 16) & mask, (hex >> 8) & mask, hex & mask, + (hex >> 24) & mask); + } + + std::uint8_t red; + std::uint8_t green; + std::uint8_t blue; + std::uint8_t alpha; +}; + +namespace colors { +constexpr Color transparent = Color::FromHexAlpha(0x00000000); +constexpr Color black = Color::FromHex(0x000000); +constexpr Color silver = Color::FromHex(0xc0c0c0); +constexpr Color gray = Color::FromHex(0x808080); +constexpr Color white = Color::FromHex(0xffffff); +constexpr Color maroon = Color::FromHex(0x800000); +constexpr Color red = Color::FromHex(0xff0000); +constexpr Color purple = Color::FromHex(0x800080); +constexpr Color fuchsia = Color::FromHex(0xff00ff); +constexpr Color green = Color::FromHex(0x008000); +constexpr Color lime = Color::FromHex(0x00ff00); +constexpr Color olive = Color::FromHex(0x808000); +constexpr Color yellow = Color::FromHex(0xffff00); +constexpr Color navy = Color::FromHex(0x000080); +constexpr Color blue = Color::FromHex(0x0000ff); +constexpr Color teal = Color::FromHex(0x008080); +constexpr Color aqua = Color::FromHex(0x00ffff); +constexpr Color orange = Color::FromHex(0xffa500); +constexpr Color aliceblue = Color::FromHex(0xf0f8ff); +constexpr Color antiquewhite = Color::FromHex(0xfaebd7); +constexpr Color aquamarine = Color::FromHex(0x7fffd4); +constexpr Color azure = Color::FromHex(0xf0ffff); +constexpr Color beige = Color::FromHex(0xf5f5dc); +constexpr Color bisque = Color::FromHex(0xffe4c4); +constexpr Color blanchedalmond = Color::FromHex(0xffebcd); +constexpr Color blueviolet = Color::FromHex(0x8a2be2); +constexpr Color brown = Color::FromHex(0xa52a2a); +constexpr Color burlywood = Color::FromHex(0xdeb887); +constexpr Color cadetblue = Color::FromHex(0x5f9ea0); +constexpr Color chartreuse = Color::FromHex(0x7fff00); +constexpr Color chocolate = Color::FromHex(0xd2691e); +constexpr Color coral = Color::FromHex(0xff7f50); +constexpr Color cornflowerblue = Color::FromHex(0x6495ed); +constexpr Color cornsilk = Color::FromHex(0xfff8dc); +constexpr Color crimson = Color::FromHex(0xdc143c); +constexpr Color cyan = aqua; +constexpr Color darkblue = Color::FromHex(0x00008b); +constexpr Color darkcyan = Color::FromHex(0x008b8b); +constexpr Color darkgoldenrod = Color::FromHex(0xb8860b); +constexpr Color darkgray = Color::FromHex(0xa9a9a9); +constexpr Color darkgreen = Color::FromHex(0x006400); +constexpr Color darkgrey = Color::FromHex(0xa9a9a9); +constexpr Color darkkhaki = Color::FromHex(0xbdb76b); +constexpr Color darkmagenta = Color::FromHex(0x8b008b); +constexpr Color darkolivegreen = Color::FromHex(0x556b2f); +constexpr Color darkorange = Color::FromHex(0xff8c00); +constexpr Color darkorchid = Color::FromHex(0x9932cc); +constexpr Color darkred = Color::FromHex(0x8b0000); +constexpr Color darksalmon = Color::FromHex(0xe9967a); +constexpr Color darkseagreen = Color::FromHex(0x8fbc8f); +constexpr Color darkslateblue = Color::FromHex(0x483d8b); +constexpr Color darkslategray = Color::FromHex(0x2f4f4f); +constexpr Color darkslategrey = Color::FromHex(0x2f4f4f); +constexpr Color darkturquoise = Color::FromHex(0x00ced1); +constexpr Color darkviolet = Color::FromHex(0x9400d3); +constexpr Color deeppink = Color::FromHex(0xff1493); +constexpr Color deepskyblue = Color::FromHex(0x00bfff); +constexpr Color dimgray = Color::FromHex(0x696969); +constexpr Color dimgrey = Color::FromHex(0x696969); +constexpr Color dodgerblue = Color::FromHex(0x1e90ff); +constexpr Color firebrick = Color::FromHex(0xb22222); +constexpr Color floralwhite = Color::FromHex(0xfffaf0); +constexpr Color forestgreen = Color::FromHex(0x228b22); +constexpr Color gainsboro = Color::FromHex(0xdcdcdc); +constexpr Color ghostwhite = Color::FromHex(0xf8f8ff); +constexpr Color gold = Color::FromHex(0xffd700); +constexpr Color goldenrod = Color::FromHex(0xdaa520); +constexpr Color greenyellow = Color::FromHex(0xadff2f); +constexpr Color grey = Color::FromHex(0x808080); +constexpr Color honeydew = Color::FromHex(0xf0fff0); +constexpr Color hotpink = Color::FromHex(0xff69b4); +constexpr Color indianred = Color::FromHex(0xcd5c5c); +constexpr Color indigo = Color::FromHex(0x4b0082); +constexpr Color ivory = Color::FromHex(0xfffff0); +constexpr Color khaki = Color::FromHex(0xf0e68c); +constexpr Color lavender = Color::FromHex(0xe6e6fa); +constexpr Color lavenderblush = Color::FromHex(0xfff0f5); +constexpr Color lawngreen = Color::FromHex(0x7cfc00); +constexpr Color lemonchiffon = Color::FromHex(0xfffacd); +constexpr Color lightblue = Color::FromHex(0xadd8e6); +constexpr Color lightcoral = Color::FromHex(0xf08080); +constexpr Color lightcyan = Color::FromHex(0xe0ffff); +constexpr Color lightgoldenrodyellow = Color::FromHex(0xfafad2); +constexpr Color lightgray = Color::FromHex(0xd3d3d3); +constexpr Color lightgreen = Color::FromHex(0x90ee90); +constexpr Color lightgrey = Color::FromHex(0xd3d3d3); +constexpr Color lightpink = Color::FromHex(0xffb6c1); +constexpr Color lightsalmon = Color::FromHex(0xffa07a); +constexpr Color lightseagreen = Color::FromHex(0x20b2aa); +constexpr Color lightskyblue = Color::FromHex(0x87cefa); +constexpr Color lightslategray = Color::FromHex(0x778899); +constexpr Color lightslategrey = Color::FromHex(0x778899); +constexpr Color lightsteelblue = Color::FromHex(0xb0c4de); +constexpr Color lightyellow = Color::FromHex(0xffffe0); +constexpr Color limegreen = Color::FromHex(0x32cd32); +constexpr Color linen = Color::FromHex(0xfaf0e6); +constexpr Color magenta = fuchsia; +constexpr Color mediumaquamarine = Color::FromHex(0x66cdaa); +constexpr Color mediumblue = Color::FromHex(0x0000cd); +constexpr Color mediumorchid = Color::FromHex(0xba55d3); +constexpr Color mediumpurple = Color::FromHex(0x9370db); +constexpr Color mediumseagreen = Color::FromHex(0x3cb371); +constexpr Color mediumslateblue = Color::FromHex(0x7b68ee); +constexpr Color mediumspringgreen = Color::FromHex(0x00fa9a); +constexpr Color mediumturquoise = Color::FromHex(0x48d1cc); +constexpr Color mediumvioletred = Color::FromHex(0xc71585); +constexpr Color midnightblue = Color::FromHex(0x191970); +constexpr Color mintcream = Color::FromHex(0xf5fffa); +constexpr Color mistyrose = Color::FromHex(0xffe4e1); +constexpr Color moccasin = Color::FromHex(0xffe4b5); +constexpr Color navajowhite = Color::FromHex(0xffdead); +constexpr Color oldlace = Color::FromHex(0xfdf5e6); +constexpr Color olivedrab = Color::FromHex(0x6b8e23); +constexpr Color orangered = Color::FromHex(0xff4500); +constexpr Color orchid = Color::FromHex(0xda70d6); +constexpr Color palegoldenrod = Color::FromHex(0xeee8aa); +constexpr Color palegreen = Color::FromHex(0x98fb98); +constexpr Color paleturquoise = Color::FromHex(0xafeeee); +constexpr Color palevioletred = Color::FromHex(0xdb7093); +constexpr Color papayawhip = Color::FromHex(0xffefd5); +constexpr Color peachpuff = Color::FromHex(0xffdab9); +constexpr Color peru = Color::FromHex(0xcd853f); +constexpr Color pink = Color::FromHex(0xffc0cb); +constexpr Color plum = Color::FromHex(0xdda0dd); +constexpr Color powderblue = Color::FromHex(0xb0e0e6); +constexpr Color rosybrown = Color::FromHex(0xbc8f8f); +constexpr Color royalblue = Color::FromHex(0x4169e1); +constexpr Color saddlebrown = Color::FromHex(0x8b4513); +constexpr Color salmon = Color::FromHex(0xfa8072); +constexpr Color sandybrown = Color::FromHex(0xf4a460); +constexpr Color seagreen = Color::FromHex(0x2e8b57); +constexpr Color seashell = Color::FromHex(0xfff5ee); +constexpr Color sienna = Color::FromHex(0xa0522d); +constexpr Color skyblue = Color::FromHex(0x87ceeb); +constexpr Color slateblue = Color::FromHex(0x6a5acd); +constexpr Color slategray = Color::FromHex(0x708090); +constexpr Color slategrey = Color::FromHex(0x708090); +constexpr Color snow = Color::FromHex(0xfffafa); +constexpr Color springgreen = Color::FromHex(0x00ff7f); +constexpr Color steelblue = Color::FromHex(0x4682b4); +constexpr Color tan = Color::FromHex(0xd2b48c); +constexpr Color thistle = Color::FromHex(0xd8bfd8); +constexpr Color tomato = Color::FromHex(0xff6347); +constexpr Color turquoise = Color::FromHex(0x40e0d0); +constexpr Color violet = Color::FromHex(0xee82ee); +constexpr Color wheat = Color::FromHex(0xf5deb3); +constexpr Color whitesmoke = Color::FromHex(0xf5f5f5); +constexpr Color yellowgreen = Color::FromHex(0x9acd32); +constexpr Color rebeccapurple = Color::FromHex(0x663399); +} // namespace colors +} // namespace cru::platform diff --git a/include/cru/platform/HeapDebug.hpp b/include/cru/platform/HeapDebug.hpp new file mode 100644 index 00000000..10ebfd2c --- /dev/null +++ b/include/cru/platform/HeapDebug.hpp @@ -0,0 +1,7 @@ +#pragma once +#include "cru/common/PreConfig.hpp" + +namespace cru::platform { +// Setup the heap debug function. Currently I only use this on Windows... +void SetupHeapDebug(); +} // namespace cru::platform diff --git a/include/cru/platform/check.hpp b/include/cru/platform/check.hpp index 6e353afb..f4bbcfe8 100644 --- a/include/cru/platform/check.hpp +++ b/include/cru/platform/check.hpp @@ -1,6 +1,6 @@ #pragma once -#include "exception.hpp" -#include "resource.hpp" +#include "Exception.hpp" +#include "Resource.hpp" #include #include diff --git a/include/cru/platform/exception.hpp b/include/cru/platform/exception.hpp index afe11c03..8b958a1d 100644 --- a/include/cru/platform/exception.hpp +++ b/include/cru/platform/exception.hpp @@ -1,5 +1,5 @@ #pragma once -#include "cru/common/base.hpp" +#include "cru/common/Base.hpp" #include diff --git a/include/cru/platform/graph/TextLayout.hpp b/include/cru/platform/graph/TextLayout.hpp new file mode 100644 index 00000000..4086ac56 --- /dev/null +++ b/include/cru/platform/graph/TextLayout.hpp @@ -0,0 +1,23 @@ +#pragma once +#include "Resource.hpp" + +#include +#include + +namespace cru::platform::graph { +struct ITextLayout : virtual IGraphResource { + virtual std::string GetText() = 0; + virtual void SetText(std::string new_text) = 0; + + virtual std::shared_ptr GetFont() = 0; + virtual void SetFont(std::shared_ptr font) = 0; + + virtual void SetMaxWidth(float max_width) = 0; + virtual void SetMaxHeight(float max_height) = 0; + + virtual Rect GetTextBounds() = 0; + virtual std::vector TextRangeRect(const TextRange& text_range) = 0; + virtual Point TextSinglePoint(gsl::index position, bool trailing) = 0; + virtual TextHitTestResult HitTest(const Point& point) = 0; +}; +} // namespace cru::platform::graph diff --git a/include/cru/platform/graph/base.hpp b/include/cru/platform/graph/base.hpp index 002c2f51..61cfc5ef 100644 --- a/include/cru/platform/graph/base.hpp +++ b/include/cru/platform/graph/base.hpp @@ -1,7 +1,7 @@ #pragma once -#include "../graph_base.hpp" -#include "../matrix.hpp" -#include "../resource.hpp" +#include "../GraphBase.hpp" +#include "../Matrix.hpp" +#include "../Resource.hpp" #include diff --git a/include/cru/platform/graph/brush.hpp b/include/cru/platform/graph/brush.hpp index af7a1dec..e67384de 100644 --- a/include/cru/platform/graph/brush.hpp +++ b/include/cru/platform/graph/brush.hpp @@ -1,5 +1,5 @@ #pragma once -#include "resource.hpp" +#include "Resource.hpp" namespace cru::platform::graph { struct IBrush : virtual IGraphResource {}; diff --git a/include/cru/platform/graph/factory.hpp b/include/cru/platform/graph/factory.hpp index 0ed45161..0a425d15 100644 --- a/include/cru/platform/graph/factory.hpp +++ b/include/cru/platform/graph/factory.hpp @@ -1,10 +1,10 @@ #pragma once -#include "resource.hpp" +#include "Resource.hpp" -#include "brush.hpp" -#include "font.hpp" -#include "geometry.hpp" -#include "text_layout.hpp" +#include "Brush.hpp" +#include "Font.hpp" +#include "Geometry.hpp" +#include "TextLayout.hpp" #include #include diff --git a/include/cru/platform/graph/font.hpp b/include/cru/platform/graph/font.hpp index d0aa2d28..182cc15b 100644 --- a/include/cru/platform/graph/font.hpp +++ b/include/cru/platform/graph/font.hpp @@ -1,5 +1,5 @@ #pragma once -#include "resource.hpp" +#include "Resource.hpp" namespace cru::platform::graph { struct IFont : virtual IGraphResource { diff --git a/include/cru/platform/graph/geometry.hpp b/include/cru/platform/graph/geometry.hpp index 85ffd3f6..354efd97 100644 --- a/include/cru/platform/graph/geometry.hpp +++ b/include/cru/platform/graph/geometry.hpp @@ -1,5 +1,5 @@ #pragma once -#include "resource.hpp" +#include "Resource.hpp" namespace cru::platform::graph { struct IGeometry : virtual IGraphResource { diff --git a/include/cru/platform/graph/painter.hpp b/include/cru/platform/graph/painter.hpp index b6eb5452..27ae420b 100644 --- a/include/cru/platform/graph/painter.hpp +++ b/include/cru/platform/graph/painter.hpp @@ -1,5 +1,5 @@ #pragma once -#include "resource.hpp" +#include "Resource.hpp" namespace cru::platform::graph { diff --git a/include/cru/platform/graph/resource.hpp b/include/cru/platform/graph/resource.hpp index 255865eb..8859360c 100644 --- a/include/cru/platform/graph/resource.hpp +++ b/include/cru/platform/graph/resource.hpp @@ -1,5 +1,5 @@ #pragma once -#include "base.hpp" +#include "Base.hpp" namespace cru::platform::graph { struct IGraphFactory; diff --git a/include/cru/platform/graph/text_layout.hpp b/include/cru/platform/graph/text_layout.hpp deleted file mode 100644 index d91834c0..00000000 --- a/include/cru/platform/graph/text_layout.hpp +++ /dev/null @@ -1,23 +0,0 @@ -#pragma once -#include "resource.hpp" - -#include -#include - -namespace cru::platform::graph { -struct ITextLayout : virtual IGraphResource { - virtual std::string GetText() = 0; - virtual void SetText(std::string new_text) = 0; - - virtual std::shared_ptr GetFont() = 0; - virtual void SetFont(std::shared_ptr font) = 0; - - virtual void SetMaxWidth(float max_width) = 0; - virtual void SetMaxHeight(float max_height) = 0; - - virtual Rect GetTextBounds() = 0; - virtual std::vector TextRangeRect(const TextRange& text_range) = 0; - virtual Point TextSinglePoint(gsl::index position, bool trailing) = 0; - virtual TextHitTestResult HitTest(const Point& point) = 0; -}; -} // namespace cru::platform::graph diff --git a/include/cru/platform/graph/util/painter.hpp b/include/cru/platform/graph/util/painter.hpp index 72d96bc1..f9aec027 100644 --- a/include/cru/platform/graph/util/painter.hpp +++ b/include/cru/platform/graph/util/painter.hpp @@ -1,5 +1,5 @@ #pragma once -#include "../painter.hpp" +#include "../Painter.hpp" #include #include diff --git a/include/cru/platform/graph_base.hpp b/include/cru/platform/graph_base.hpp deleted file mode 100644 index c880c7a2..00000000 --- a/include/cru/platform/graph_base.hpp +++ /dev/null @@ -1,419 +0,0 @@ -#pragma once -#include "cru/common/base.hpp" - -#include -#include -#include - -namespace cru::platform { -struct Point final { - constexpr Point() = default; - constexpr Point(const float x, const float y) : x(x), y(y) {} - - float x = 0; - float y = 0; -}; - -constexpr Point operator+(const Point& left, const Point& right) { - return Point(left.x + right.x, left.y + right.y); -} - -constexpr Point operator-(const Point& left, const Point& right) { - return Point(left.x - right.x, left.y - right.y); -} - -constexpr bool operator==(const Point& left, const Point& right) { - return left.x == right.x && left.y == right.y; -} - -constexpr bool operator!=(const Point& left, const Point& right) { - return !(left == right); -} - -struct Size final { - constexpr Size() = default; - constexpr Size(const float width, const float height) - : width(width), height(height) {} - - float width = 0; - float height = 0; -}; - -constexpr Size operator+(const Size& left, const Size& right) { - return Size(left.width + right.width, left.height + right.height); -} - -constexpr Size operator-(const Size& left, const Size& right) { - return Size(left.width - right.width, left.height - right.height); -} - -constexpr bool operator==(const Size& left, const Size& right) { - return left.width == right.width && left.height == right.height; -} - -constexpr bool operator!=(const Size& left, const Size& right) { - return !(left == right); -} - -struct Thickness final { - constexpr Thickness() : Thickness(0) {} - - constexpr explicit Thickness(const float width) - : left(width), top(width), right(width), bottom(width) {} - - constexpr explicit Thickness(const float horizontal, const float vertical) - : left(horizontal), top(vertical), right(horizontal), bottom(vertical) {} - - constexpr Thickness(const float left, const float top, const float right, - const float bottom) - : left(left), top(top), right(right), bottom(bottom) {} - - constexpr float GetHorizontalTotal() const { return left + right; } - - constexpr float GetVerticalTotal() const { return top + bottom; } - - void SetLeftRight(const float value) { left = right = value; } - - void SetTopBottom(const float value) { top = bottom = value; } - - void SetAll(const float value) { left = top = right = bottom = value; } - - constexpr float Validate() const { - return left >= 0.0 && top >= 0.0 && right >= 0.0 && bottom >= 0.0; - } - - float left; - float top; - float right; - float bottom; -}; - -constexpr bool operator==(const Thickness& left, const Thickness& right) { - return left.left == right.left && left.top == right.top && - left.right == right.right && left.bottom == right.bottom; -} - -constexpr bool operator!=(const Thickness& left, const Thickness& right) { - return !(left == right); -} - -struct Rect final { - constexpr Rect() = default; - constexpr Rect(const float left, const float top, const float width, - const float height) - : left(left), top(top), width(width), height(height) {} - constexpr Rect(const Point& lefttop, const Size& size) - : left(lefttop.x), - top(lefttop.y), - width(size.width), - height(size.height) {} - - constexpr static Rect FromVertices(const float left, const float top, - const float right, const float bottom) { - return Rect(left, top, right - left, bottom - top); - } - - constexpr static Rect FromCenter(const Point& center, const float width, - const float height) { - return Rect(center.x - width / 2.0f, center.y - height / 2.0f, width, - height); - } - - constexpr float GetRight() const { return left + width; } - - constexpr float GetBottom() const { return top + height; } - - constexpr Point GetLeftTop() const { return Point(left, top); } - - constexpr Point GetRightBottom() const { - return Point(left + width, top + height); - } - - constexpr Point GetLeftBottom() const { return Point(left, top + height); } - - constexpr Point GetRightTop() const { return Point(left + width, top); } - - constexpr Point GetCenter() const { - return Point(left + width / 2.0f, top + height / 2.0f); - } - - constexpr Size GetSize() const { return Size(width, height); } - - constexpr Rect Shrink(const Thickness& thickness) const { - return Rect(left + thickness.left, top + thickness.top, - width - thickness.GetHorizontalTotal(), - height - thickness.GetVerticalTotal()); - } - - constexpr bool IsPointInside(const Point& point) const { - return point.x >= left && point.x < GetRight() && point.y >= top && - point.y < GetBottom(); - } - - float left = 0.0f; - float top = 0.0f; - float width = 0.0f; - float height = 0.0f; -}; - -constexpr bool operator==(const Rect& left, const Rect& right) { - return left.left == right.left && left.top == right.top && - left.width == right.width && left.height == right.height; -} - -constexpr bool operator!=(const Rect& left, const Rect& right) { - return !(left == right); -} - -struct RoundedRect final { - constexpr RoundedRect() = default; - constexpr RoundedRect(const Rect& rect, const float radius_x, - const float radius_y) - : rect(rect), radius_x(radius_x), radius_y(radius_y) {} - - Rect rect{}; - float radius_x = 0.0f; - float radius_y = 0.0f; -}; - -constexpr bool operator==(const RoundedRect& left, const RoundedRect& right) { - return left.rect == right.rect && left.radius_x == right.radius_x && - left.radius_y == right.radius_y; -} - -constexpr bool operator!=(const RoundedRect& left, const RoundedRect& right) { - return !(left == right); -} - -struct Ellipse final { - constexpr Ellipse() = default; - constexpr Ellipse(const Point& center, const float radius_x, - const float radius_y) - : center(center), radius_x(radius_x), radius_y(radius_y) {} - - constexpr static Ellipse FromRect(const Rect& rect) { - return Ellipse(rect.GetCenter(), rect.width / 2.0f, rect.height / 2.0f); - } - - constexpr Rect GetBoundRect() const { - return Rect::FromCenter(center, radius_x * 2.0f, radius_y * 2.0f); - } - - Point center{}; - float radius_x = 0.0f; - float radius_y = 0.0f; -}; - -constexpr bool operator==(const Ellipse& left, const Ellipse& right) { - return left.center == right.center && left.radius_x == right.radius_x && - left.radius_y == right.radius_y; -} - -constexpr bool operator!=(const Ellipse& left, const Ellipse& right) { - return !(left == right); -} - -struct TextRange final { - constexpr static TextRange FromTwoSides(gsl::index start, gsl::index end) { - return TextRange(start, end - start); - } - - constexpr static TextRange FromTwoSides(gsl::index start, gsl::index end, - gsl::index offset) { - return TextRange(start + offset, end - start); - } - - constexpr TextRange() = default; - constexpr TextRange(const gsl::index position, const gsl::index count = 0) - : position(position), count(count) {} - - gsl::index GetEnd() const { return position + count; } - - TextRange Normalize() const { - auto result = *this; - if (result.count < 0) { - result.position += result.count; - result.count = -result.count; - } - return result; - } - - gsl::index position = 0; - gsl::index count = 0; -}; - -struct Color { - constexpr Color() : Color(0, 0, 0, 255) {} - constexpr Color(std::uint8_t red, std::uint8_t green, std::uint8_t blue, - std::uint8_t alpha = 255) - : red(red), green(green), blue(blue), alpha(alpha) {} - - constexpr static Color FromHex(std::uint32_t hex) { - const std::uint32_t mask = 0b11111111; - return Color((hex >> 16) & mask, (hex >> 8) & mask, hex & mask, 255); - } - - constexpr static Color FromHexAlpha(std::uint32_t hex) { - const std::uint32_t mask = 0b11111111; - return Color((hex >> 16) & mask, (hex >> 8) & mask, hex & mask, - (hex >> 24) & mask); - } - - std::uint8_t red; - std::uint8_t green; - std::uint8_t blue; - std::uint8_t alpha; -}; - -namespace colors { -constexpr Color transparent = Color::FromHexAlpha(0x00000000); -constexpr Color black = Color::FromHex(0x000000); -constexpr Color silver = Color::FromHex(0xc0c0c0); -constexpr Color gray = Color::FromHex(0x808080); -constexpr Color white = Color::FromHex(0xffffff); -constexpr Color maroon = Color::FromHex(0x800000); -constexpr Color red = Color::FromHex(0xff0000); -constexpr Color purple = Color::FromHex(0x800080); -constexpr Color fuchsia = Color::FromHex(0xff00ff); -constexpr Color green = Color::FromHex(0x008000); -constexpr Color lime = Color::FromHex(0x00ff00); -constexpr Color olive = Color::FromHex(0x808000); -constexpr Color yellow = Color::FromHex(0xffff00); -constexpr Color navy = Color::FromHex(0x000080); -constexpr Color blue = Color::FromHex(0x0000ff); -constexpr Color teal = Color::FromHex(0x008080); -constexpr Color aqua = Color::FromHex(0x00ffff); -constexpr Color orange = Color::FromHex(0xffa500); -constexpr Color aliceblue = Color::FromHex(0xf0f8ff); -constexpr Color antiquewhite = Color::FromHex(0xfaebd7); -constexpr Color aquamarine = Color::FromHex(0x7fffd4); -constexpr Color azure = Color::FromHex(0xf0ffff); -constexpr Color beige = Color::FromHex(0xf5f5dc); -constexpr Color bisque = Color::FromHex(0xffe4c4); -constexpr Color blanchedalmond = Color::FromHex(0xffebcd); -constexpr Color blueviolet = Color::FromHex(0x8a2be2); -constexpr Color brown = Color::FromHex(0xa52a2a); -constexpr Color burlywood = Color::FromHex(0xdeb887); -constexpr Color cadetblue = Color::FromHex(0x5f9ea0); -constexpr Color chartreuse = Color::FromHex(0x7fff00); -constexpr Color chocolate = Color::FromHex(0xd2691e); -constexpr Color coral = Color::FromHex(0xff7f50); -constexpr Color cornflowerblue = Color::FromHex(0x6495ed); -constexpr Color cornsilk = Color::FromHex(0xfff8dc); -constexpr Color crimson = Color::FromHex(0xdc143c); -constexpr Color cyan = aqua; -constexpr Color darkblue = Color::FromHex(0x00008b); -constexpr Color darkcyan = Color::FromHex(0x008b8b); -constexpr Color darkgoldenrod = Color::FromHex(0xb8860b); -constexpr Color darkgray = Color::FromHex(0xa9a9a9); -constexpr Color darkgreen = Color::FromHex(0x006400); -constexpr Color darkgrey = Color::FromHex(0xa9a9a9); -constexpr Color darkkhaki = Color::FromHex(0xbdb76b); -constexpr Color darkmagenta = Color::FromHex(0x8b008b); -constexpr Color darkolivegreen = Color::FromHex(0x556b2f); -constexpr Color darkorange = Color::FromHex(0xff8c00); -constexpr Color darkorchid = Color::FromHex(0x9932cc); -constexpr Color darkred = Color::FromHex(0x8b0000); -constexpr Color darksalmon = Color::FromHex(0xe9967a); -constexpr Color darkseagreen = Color::FromHex(0x8fbc8f); -constexpr Color darkslateblue = Color::FromHex(0x483d8b); -constexpr Color darkslategray = Color::FromHex(0x2f4f4f); -constexpr Color darkslategrey = Color::FromHex(0x2f4f4f); -constexpr Color darkturquoise = Color::FromHex(0x00ced1); -constexpr Color darkviolet = Color::FromHex(0x9400d3); -constexpr Color deeppink = Color::FromHex(0xff1493); -constexpr Color deepskyblue = Color::FromHex(0x00bfff); -constexpr Color dimgray = Color::FromHex(0x696969); -constexpr Color dimgrey = Color::FromHex(0x696969); -constexpr Color dodgerblue = Color::FromHex(0x1e90ff); -constexpr Color firebrick = Color::FromHex(0xb22222); -constexpr Color floralwhite = Color::FromHex(0xfffaf0); -constexpr Color forestgreen = Color::FromHex(0x228b22); -constexpr Color gainsboro = Color::FromHex(0xdcdcdc); -constexpr Color ghostwhite = Color::FromHex(0xf8f8ff); -constexpr Color gold = Color::FromHex(0xffd700); -constexpr Color goldenrod = Color::FromHex(0xdaa520); -constexpr Color greenyellow = Color::FromHex(0xadff2f); -constexpr Color grey = Color::FromHex(0x808080); -constexpr Color honeydew = Color::FromHex(0xf0fff0); -constexpr Color hotpink = Color::FromHex(0xff69b4); -constexpr Color indianred = Color::FromHex(0xcd5c5c); -constexpr Color indigo = Color::FromHex(0x4b0082); -constexpr Color ivory = Color::FromHex(0xfffff0); -constexpr Color khaki = Color::FromHex(0xf0e68c); -constexpr Color lavender = Color::FromHex(0xe6e6fa); -constexpr Color lavenderblush = Color::FromHex(0xfff0f5); -constexpr Color lawngreen = Color::FromHex(0x7cfc00); -constexpr Color lemonchiffon = Color::FromHex(0xfffacd); -constexpr Color lightblue = Color::FromHex(0xadd8e6); -constexpr Color lightcoral = Color::FromHex(0xf08080); -constexpr Color lightcyan = Color::FromHex(0xe0ffff); -constexpr Color lightgoldenrodyellow = Color::FromHex(0xfafad2); -constexpr Color lightgray = Color::FromHex(0xd3d3d3); -constexpr Color lightgreen = Color::FromHex(0x90ee90); -constexpr Color lightgrey = Color::FromHex(0xd3d3d3); -constexpr Color lightpink = Color::FromHex(0xffb6c1); -constexpr Color lightsalmon = Color::FromHex(0xffa07a); -constexpr Color lightseagreen = Color::FromHex(0x20b2aa); -constexpr Color lightskyblue = Color::FromHex(0x87cefa); -constexpr Color lightslategray = Color::FromHex(0x778899); -constexpr Color lightslategrey = Color::FromHex(0x778899); -constexpr Color lightsteelblue = Color::FromHex(0xb0c4de); -constexpr Color lightyellow = Color::FromHex(0xffffe0); -constexpr Color limegreen = Color::FromHex(0x32cd32); -constexpr Color linen = Color::FromHex(0xfaf0e6); -constexpr Color magenta = fuchsia; -constexpr Color mediumaquamarine = Color::FromHex(0x66cdaa); -constexpr Color mediumblue = Color::FromHex(0x0000cd); -constexpr Color mediumorchid = Color::FromHex(0xba55d3); -constexpr Color mediumpurple = Color::FromHex(0x9370db); -constexpr Color mediumseagreen = Color::FromHex(0x3cb371); -constexpr Color mediumslateblue = Color::FromHex(0x7b68ee); -constexpr Color mediumspringgreen = Color::FromHex(0x00fa9a); -constexpr Color mediumturquoise = Color::FromHex(0x48d1cc); -constexpr Color mediumvioletred = Color::FromHex(0xc71585); -constexpr Color midnightblue = Color::FromHex(0x191970); -constexpr Color mintcream = Color::FromHex(0xf5fffa); -constexpr Color mistyrose = Color::FromHex(0xffe4e1); -constexpr Color moccasin = Color::FromHex(0xffe4b5); -constexpr Color navajowhite = Color::FromHex(0xffdead); -constexpr Color oldlace = Color::FromHex(0xfdf5e6); -constexpr Color olivedrab = Color::FromHex(0x6b8e23); -constexpr Color orangered = Color::FromHex(0xff4500); -constexpr Color orchid = Color::FromHex(0xda70d6); -constexpr Color palegoldenrod = Color::FromHex(0xeee8aa); -constexpr Color palegreen = Color::FromHex(0x98fb98); -constexpr Color paleturquoise = Color::FromHex(0xafeeee); -constexpr Color palevioletred = Color::FromHex(0xdb7093); -constexpr Color papayawhip = Color::FromHex(0xffefd5); -constexpr Color peachpuff = Color::FromHex(0xffdab9); -constexpr Color peru = Color::FromHex(0xcd853f); -constexpr Color pink = Color::FromHex(0xffc0cb); -constexpr Color plum = Color::FromHex(0xdda0dd); -constexpr Color powderblue = Color::FromHex(0xb0e0e6); -constexpr Color rosybrown = Color::FromHex(0xbc8f8f); -constexpr Color royalblue = Color::FromHex(0x4169e1); -constexpr Color saddlebrown = Color::FromHex(0x8b4513); -constexpr Color salmon = Color::FromHex(0xfa8072); -constexpr Color sandybrown = Color::FromHex(0xf4a460); -constexpr Color seagreen = Color::FromHex(0x2e8b57); -constexpr Color seashell = Color::FromHex(0xfff5ee); -constexpr Color sienna = Color::FromHex(0xa0522d); -constexpr Color skyblue = Color::FromHex(0x87ceeb); -constexpr Color slateblue = Color::FromHex(0x6a5acd); -constexpr Color slategray = Color::FromHex(0x708090); -constexpr Color slategrey = Color::FromHex(0x708090); -constexpr Color snow = Color::FromHex(0xfffafa); -constexpr Color springgreen = Color::FromHex(0x00ff7f); -constexpr Color steelblue = Color::FromHex(0x4682b4); -constexpr Color tan = Color::FromHex(0xd2b48c); -constexpr Color thistle = Color::FromHex(0xd8bfd8); -constexpr Color tomato = Color::FromHex(0xff6347); -constexpr Color turquoise = Color::FromHex(0x40e0d0); -constexpr Color violet = Color::FromHex(0xee82ee); -constexpr Color wheat = Color::FromHex(0xf5deb3); -constexpr Color whitesmoke = Color::FromHex(0xf5f5f5); -constexpr Color yellowgreen = Color::FromHex(0x9acd32); -constexpr Color rebeccapurple = Color::FromHex(0x663399); -} // namespace colors -} // namespace cru::platform diff --git a/include/cru/platform/heap_debug.hpp b/include/cru/platform/heap_debug.hpp deleted file mode 100644 index 9e3ae368..00000000 --- a/include/cru/platform/heap_debug.hpp +++ /dev/null @@ -1,7 +0,0 @@ -#pragma once -#include "cru/common/pre_config.hpp" - -namespace cru::platform { -// Setup the heap debug function. Currently I only use this on Windows... -void SetupHeapDebug(); -} // namespace cru::platform diff --git a/include/cru/platform/matrix.hpp b/include/cru/platform/matrix.hpp index 030e1378..cea5198b 100644 --- a/include/cru/platform/matrix.hpp +++ b/include/cru/platform/matrix.hpp @@ -1,5 +1,5 @@ #pragma once -#include "graph_base.hpp" +#include "GraphBase.hpp" #include diff --git a/include/cru/platform/native/InputMethod.hpp b/include/cru/platform/native/InputMethod.hpp new file mode 100644 index 00000000..1ede15b2 --- /dev/null +++ b/include/cru/platform/native/InputMethod.hpp @@ -0,0 +1,77 @@ +#pragma once +#include "../Resource.hpp" +#include "Base.hpp" + +#include "cru/common/Event.hpp" + +#include +#include +#include + +namespace cru::platform::native { +struct CompositionClause { + int start; + int end; + bool target; +}; + +using CompositionClauses = std::vector; + +struct CompositionText { + std::string text; + CompositionClauses clauses; + TextRange selection; +}; + +inline std::ostream& operator<<(std::ostream& stream, + const CompositionText& composition_text) { + stream << "text: " << composition_text.text << "\n" + << "clauses:\n"; + for (int i = 0; i < static_cast(composition_text.clauses.size()); i++) { + const auto& clause = composition_text.clauses[i]; + stream << "\t" << i << ". start:" << clause.start << " end:" << clause.end; + if (clause.target) { + stream << " target"; + } + stream << "\n"; + } + stream << "selection: position:" << composition_text.selection.position + << " count:" << composition_text.selection.count; + return stream; +} + +struct IInputMethodContext : virtual INativeResource { + // Return true if you should draw composition text manually. Return false if + // system will take care of that for you. + virtual bool ShouldManuallyDrawCompositionText() = 0; + + virtual void EnableIME() = 0; + + virtual void DisableIME() = 0; + + virtual void CompleteComposition() = 0; + + virtual void CancelComposition() = 0; + + virtual CompositionText GetCompositionText() = 0; + + // Set the candidate window lefttop. Use this method to prepare typing. + virtual void SetCandidateWindowPosition(const Point& point) = 0; + + // Triggered when user starts composition. + virtual IEvent* CompositionStartEvent() = 0; + + // Triggered when user stops composition. + virtual IEvent* CompositionEndEvent() = 0; + + // Triggered every time composition text changes. + virtual IEvent* CompositionEvent() = 0; + + virtual IEvent* TextEvent() = 0; +}; + +struct IInputMethodManager : virtual INativeResource { + virtual std::unique_ptr GetContext( + INativeWindow* window) = 0; +}; +} // namespace cru::platform::native diff --git a/include/cru/platform/native/UiApplication.hpp b/include/cru/platform/native/UiApplication.hpp new file mode 100644 index 00000000..1aa4df57 --- /dev/null +++ b/include/cru/platform/native/UiApplication.hpp @@ -0,0 +1,58 @@ +#pragma once +#include "../Resource.hpp" +#include "Base.hpp" + +#include +#include +#include +#include + +namespace cru::platform::native { +// The entry point of a ui application. +struct IUiApplication : public virtual INativeResource { + public: + static IUiApplication* GetInstance() { return instance; } + + private: + static IUiApplication* instance; + + protected: + IUiApplication(); + + public: + ~IUiApplication() override; + + // Block current thread and run the message loop. Return the exit code when + // message loop gets a quit message (possibly posted by method RequestQuit). + virtual int Run() = 0; + + // Post a quit message with given quit code. + virtual void RequestQuit(int quit_code) = 0; + + virtual void AddOnQuitHandler(std::function handler) = 0; + + virtual void InvokeLater(std::function action) = 0; + // Timer id should always be positive and never the same. So it's ok to use + // negative value to represent no timer. + virtual long long SetTimeout(std::chrono::milliseconds milliseconds, + std::function action) = 0; + virtual long long SetInterval(std::chrono::milliseconds milliseconds, + std::function action) = 0; + // Implementation should guarantee calls on timer id already canceled have no + // effects and do not crash. Also canceling negative id should always result + // in no-op. + virtual void CancelTimer(long long id) = 0; + + virtual std::vector GetAllWindow() = 0; + virtual std::shared_ptr CreateWindow( + INativeWindow* parent) = 0; + + virtual cru::platform::graph::IGraphFactory* GetGraphFactory() = 0; + + virtual ICursorManager* GetCursorManager() = 0; + virtual IInputMethodManager* GetInputMethodManager() = 0; +}; + +// Bootstrap from this. +std::unique_ptr CreateUiApplication(); +} // namespace cru::platform::native diff --git a/include/cru/platform/native/base.hpp b/include/cru/platform/native/base.hpp index e0ecbda7..bba7b960 100644 --- a/include/cru/platform/native/base.hpp +++ b/include/cru/platform/native/base.hpp @@ -1,8 +1,8 @@ #pragma once -#include "cru/common/base.hpp" -#include "cru/common/bitmask.hpp" -#include "cru/platform/graph/base.hpp" -#include "keyboard.hpp" +#include "cru/common/Base.hpp" +#include "cru/common/Bitmask.hpp" +#include "cru/platform/graph/Base.hpp" +#include "Keyboard.hpp" namespace cru::platform::native { struct ICursor; diff --git a/include/cru/platform/native/cursor.hpp b/include/cru/platform/native/cursor.hpp index eae51ffe..6c8f8068 100644 --- a/include/cru/platform/native/cursor.hpp +++ b/include/cru/platform/native/cursor.hpp @@ -1,6 +1,6 @@ #pragma once -#include "../resource.hpp" -#include "base.hpp" +#include "../Resource.hpp" +#include "Base.hpp" #include diff --git a/include/cru/platform/native/input_method.hpp b/include/cru/platform/native/input_method.hpp deleted file mode 100644 index bcf030b4..00000000 --- a/include/cru/platform/native/input_method.hpp +++ /dev/null @@ -1,77 +0,0 @@ -#pragma once -#include "../resource.hpp" -#include "base.hpp" - -#include "cru/common/event.hpp" - -#include -#include -#include - -namespace cru::platform::native { -struct CompositionClause { - int start; - int end; - bool target; -}; - -using CompositionClauses = std::vector; - -struct CompositionText { - std::string text; - CompositionClauses clauses; - TextRange selection; -}; - -inline std::ostream& operator<<(std::ostream& stream, - const CompositionText& composition_text) { - stream << "text: " << composition_text.text << "\n" - << "clauses:\n"; - for (int i = 0; i < static_cast(composition_text.clauses.size()); i++) { - const auto& clause = composition_text.clauses[i]; - stream << "\t" << i << ". start:" << clause.start << " end:" << clause.end; - if (clause.target) { - stream << " target"; - } - stream << "\n"; - } - stream << "selection: position:" << composition_text.selection.position - << " count:" << composition_text.selection.count; - return stream; -} - -struct IInputMethodContext : virtual INativeResource { - // Return true if you should draw composition text manually. Return false if - // system will take care of that for you. - virtual bool ShouldManuallyDrawCompositionText() = 0; - - virtual void EnableIME() = 0; - - virtual void DisableIME() = 0; - - virtual void CompleteComposition() = 0; - - virtual void CancelComposition() = 0; - - virtual CompositionText GetCompositionText() = 0; - - // Set the candidate window lefttop. Use this method to prepare typing. - virtual void SetCandidateWindowPosition(const Point& point) = 0; - - // Triggered when user starts composition. - virtual IEvent* CompositionStartEvent() = 0; - - // Triggered when user stops composition. - virtual IEvent* CompositionEndEvent() = 0; - - // Triggered every time composition text changes. - virtual IEvent* CompositionEvent() = 0; - - virtual IEvent* TextEvent() = 0; -}; - -struct IInputMethodManager : virtual INativeResource { - virtual std::unique_ptr GetContext( - INativeWindow* window) = 0; -}; -} // namespace cru::platform::native diff --git a/include/cru/platform/native/keyboard.hpp b/include/cru/platform/native/keyboard.hpp index 8b5e6162..26a1409d 100644 --- a/include/cru/platform/native/keyboard.hpp +++ b/include/cru/platform/native/keyboard.hpp @@ -1,5 +1,5 @@ #pragma once -#include "cru/common/bitmask.hpp" +#include "cru/common/Bitmask.hpp" namespace cru::platform::native { // Because of the complexity of keyboard layout, I only add code in US keyboard diff --git a/include/cru/platform/native/ui_application.hpp b/include/cru/platform/native/ui_application.hpp deleted file mode 100644 index afcc7117..00000000 --- a/include/cru/platform/native/ui_application.hpp +++ /dev/null @@ -1,58 +0,0 @@ -#pragma once -#include "../resource.hpp" -#include "base.hpp" - -#include -#include -#include -#include - -namespace cru::platform::native { -// The entry point of a ui application. -struct IUiApplication : public virtual INativeResource { - public: - static IUiApplication* GetInstance() { return instance; } - - private: - static IUiApplication* instance; - - protected: - IUiApplication(); - - public: - ~IUiApplication() override; - - // Block current thread and run the message loop. Return the exit code when - // message loop gets a quit message (possibly posted by method RequestQuit). - virtual int Run() = 0; - - // Post a quit message with given quit code. - virtual void RequestQuit(int quit_code) = 0; - - virtual void AddOnQuitHandler(std::function handler) = 0; - - virtual void InvokeLater(std::function action) = 0; - // Timer id should always be positive and never the same. So it's ok to use - // negative value to represent no timer. - virtual long long SetTimeout(std::chrono::milliseconds milliseconds, - std::function action) = 0; - virtual long long SetInterval(std::chrono::milliseconds milliseconds, - std::function action) = 0; - // Implementation should guarantee calls on timer id already canceled have no - // effects and do not crash. Also canceling negative id should always result - // in no-op. - virtual void CancelTimer(long long id) = 0; - - virtual std::vector GetAllWindow() = 0; - virtual std::shared_ptr CreateWindow( - INativeWindow* parent) = 0; - - virtual cru::platform::graph::IGraphFactory* GetGraphFactory() = 0; - - virtual ICursorManager* GetCursorManager() = 0; - virtual IInputMethodManager* GetInputMethodManager() = 0; -}; - -// Bootstrap from this. -std::unique_ptr CreateUiApplication(); -} // namespace cru::platform::native diff --git a/include/cru/platform/native/window.hpp b/include/cru/platform/native/window.hpp index 57363a3b..1fcac1fc 100644 --- a/include/cru/platform/native/window.hpp +++ b/include/cru/platform/native/window.hpp @@ -1,7 +1,7 @@ #pragma once -#include "../resource.hpp" -#include "base.hpp" -#include "cru/common/event.hpp" +#include "../Resource.hpp" +#include "Base.hpp" +#include "cru/common/Event.hpp" #include diff --git a/include/cru/platform/resource.hpp b/include/cru/platform/resource.hpp index 6b315527..72cfaf52 100644 --- a/include/cru/platform/resource.hpp +++ b/include/cru/platform/resource.hpp @@ -1,5 +1,5 @@ #pragma once -#include "cru/common/base.hpp" +#include "cru/common/Base.hpp" #include diff --git a/include/cru/ui/ClickDetector.hpp b/include/cru/ui/ClickDetector.hpp new file mode 100644 index 00000000..f5fd3d8f --- /dev/null +++ b/include/cru/ui/ClickDetector.hpp @@ -0,0 +1,86 @@ +#pragma once +#include "Control.hpp" + + +namespace cru::ui { +class ClickEventArgs : Object { + public: + ClickEventArgs(Control* sender, const Point& down_point, + const Point& up_point, MouseButton button) + : sender_(sender), + down_point_(down_point), + up_point_(up_point), + button_(button) {} + + CRU_DEFAULT_COPY(ClickEventArgs) + CRU_DEFAULT_MOVE(ClickEventArgs) + + ~ClickEventArgs() override = default; + + Control* GetSender() const { return sender_; } + Point GetDownPoint() const { return down_point_; } + Point GetUpPoint() const { return up_point_; } + MouseButton GetButton() const { return button_; } + + private: + Control* sender_; + Point down_point_; + Point up_point_; + MouseButton button_; +}; + +enum class ClickState { + None, // Mouse is outside the control. + Hover, // Mouse hovers on the control but not pressed + Press, // Mouse is pressed and if released click is done. + PressInactive // Mouse is pressed but if released click is canceled. +}; + +class ClickDetector : public Object { + public: + explicit ClickDetector(Control* control); + + CRU_DELETE_COPY(ClickDetector) + CRU_DELETE_MOVE(ClickDetector) + + ~ClickDetector() override = default; + + Control* GetControl() const { return control_; } + + ClickState GetState() const { return state_; } + + // Default is enable. + bool IsEnabled() const { return enable_; } + // If disable when user is pressing, the pressing is deactivated. + void SetEnabled(bool enable); + + // Default is left and right. + MouseButton GetTriggerButton() const { return trigger_button_; } + // If unset the trigger button when user is pressing, the pressing is + // deactivated. + void SetTriggerButton(MouseButton trigger_button); + + IEvent* ClickEvent() { return &event_; } + + IEvent* StateChangeEvent() { return &state_change_event_; } + + private: + void SetState(ClickState state); + + private: + Control* control_; + + ClickState state_; + + bool enable_ = true; + MouseButton trigger_button_ = mouse_buttons::left | mouse_buttons::right; + + Event event_; + Event state_change_event_; + + std::vector event_rovoker_guards_; + + Point down_point_; + MouseButton button_; +}; +} // namespace cru::ui diff --git a/include/cru/ui/ContentControl.hpp b/include/cru/ui/ContentControl.hpp new file mode 100644 index 00000000..19f13a1d --- /dev/null +++ b/include/cru/ui/ContentControl.hpp @@ -0,0 +1,29 @@ +#pragma once +#include "Control.hpp" + +namespace cru::ui { +class ContentControl : public Control { + protected: + ContentControl(); + + public: + ContentControl(const ContentControl& other) = delete; + ContentControl(ContentControl&& other) = delete; + ContentControl& operator=(const ContentControl& other) = delete; + ContentControl& operator=(ContentControl&& other) = delete; + ~ContentControl() override; + + const std::vector& GetChildren() const override final { + return child_vector_; + } + Control* GetChild() const { return child_; } + void SetChild(Control* child); + + protected: + virtual void OnChildChanged(Control* old_child, Control* new_child); + + private: + std::vector child_vector_; + Control*& child_; +}; +} // namespace cru::ui diff --git a/include/cru/ui/LayoutControl.hpp b/include/cru/ui/LayoutControl.hpp new file mode 100644 index 00000000..7997b37e --- /dev/null +++ b/include/cru/ui/LayoutControl.hpp @@ -0,0 +1,31 @@ +#pragma once +#include "Control.hpp" + +namespace cru::ui { +class LayoutControl : public Control { + protected: + LayoutControl() = default; + + public: + LayoutControl(const LayoutControl& other) = delete; + LayoutControl(LayoutControl&& other) = delete; + LayoutControl& operator=(const LayoutControl& other) = delete; + LayoutControl& operator=(LayoutControl&& other) = delete; + ~LayoutControl() override; + + const std::vector& GetChildren() const override final { + return children_; + } + + void AddChild(Control* control, Index position); + + void RemoveChild(Index position); + + protected: + virtual void OnAddChild(Control* child, Index position); + virtual void OnRemoveChild(Control* child, Index position); + + private: + std::vector children_; +}; +} // namespace cru::ui diff --git a/include/cru/ui/NoChildControl.hpp b/include/cru/ui/NoChildControl.hpp new file mode 100644 index 00000000..1a31ae7e --- /dev/null +++ b/include/cru/ui/NoChildControl.hpp @@ -0,0 +1,24 @@ +#pragma once +#include "Control.hpp" + +namespace cru::ui { +class NoChildControl : public Control { + private: + static const std::vector empty_control_vector; + + protected: + NoChildControl() = default; + + public: + NoChildControl(const NoChildControl& other) = delete; + NoChildControl(NoChildControl&& other) = delete; + NoChildControl& operator=(const NoChildControl& other) = delete; + NoChildControl& operator=(NoChildControl&& other) = delete; + ~NoChildControl() override = default; + + protected: + const std::vector& GetChildren() const override final { + return empty_control_vector; + } +}; +} // namespace cru::ui diff --git a/include/cru/ui/UiEvent.hpp b/include/cru/ui/UiEvent.hpp new file mode 100644 index 00000000..29292d75 --- /dev/null +++ b/include/cru/ui/UiEvent.hpp @@ -0,0 +1,229 @@ +#pragma once +#include "Base.hpp" + +#include "cru/common/Event.hpp" +#include "cru/platform/native/Keyboard.hpp" + +#include +#include +#include +#include + +namespace cru::platform::graph { +struct IPainter; +} + +namespace cru::ui { +class Control; +} + +namespace cru::ui::event { +class UiEventArgs : public Object { + public: + UiEventArgs(Object* sender, Object* original_sender) + : sender_(sender), original_sender_(original_sender), handled_(false) {} + + UiEventArgs(const UiEventArgs& other) = default; + UiEventArgs(UiEventArgs&& other) = default; + UiEventArgs& operator=(const UiEventArgs& other) = default; + UiEventArgs& operator=(UiEventArgs&& other) = default; + ~UiEventArgs() override = default; + + Object* GetSender() const { return sender_; } + + Object* GetOriginalSender() const { return original_sender_; } + + bool IsHandled() const { return handled_; } + void SetHandled(const bool handled = true) { handled_ = handled; } + + private: + Object* sender_; + Object* original_sender_; + bool handled_; +}; + +// TEventArgs must not be a reference type. This class help add reference. +// EventArgs must be reference because the IsHandled property must be settable. +template +class RoutedEvent { + public: + static_assert(std::is_base_of_v, + "TEventArgs must be subclass of UiEventArgs."); + static_assert(!std::is_reference_v, + "TEventArgs must not be reference."); + + using EventArgs = TEventArgs; + + RoutedEvent() = default; + RoutedEvent(const RoutedEvent& other) = delete; + RoutedEvent(RoutedEvent&& other) = delete; + RoutedEvent& operator=(const RoutedEvent& other) = delete; + RoutedEvent& operator=(RoutedEvent&& other) = delete; + ~RoutedEvent() = default; + + IEvent* Direct() { return &direct_; } + + IEvent* Bubble() { return &bubble_; } + + IEvent* Tunnel() { return &tunnel_; } + + private: + Event direct_; + Event bubble_; + Event tunnel_; +}; + +class MouseEventArgs : public UiEventArgs { + public: + MouseEventArgs(Object* sender, Object* original_sender, + const std::optional& point = std::nullopt) + : UiEventArgs(sender, original_sender), point_(point) {} + MouseEventArgs(const MouseEventArgs& other) = default; + MouseEventArgs(MouseEventArgs&& other) = default; + MouseEventArgs& operator=(const MouseEventArgs& other) = default; + MouseEventArgs& operator=(MouseEventArgs&& other) = default; + ~MouseEventArgs() override = default; + + // This point is relative to window client lefttop. + Point GetPoint() const { return point_.value_or(Point{}); } + + private: + std::optional point_; +}; + +class MouseButtonEventArgs : public MouseEventArgs { + public: + MouseButtonEventArgs(Object* sender, Object* original_sender, + const Point& point, const MouseButton button, + platform::native::KeyModifier key_modifier) + : MouseEventArgs(sender, original_sender, point), + button_(button), + key_modifier_(key_modifier) {} + MouseButtonEventArgs(Object* sender, Object* original_sender, + const MouseButton button, + platform::native::KeyModifier key_modifier) + : MouseEventArgs(sender, original_sender), + button_(button), + key_modifier_(key_modifier) {} + MouseButtonEventArgs(const MouseButtonEventArgs& other) = default; + MouseButtonEventArgs(MouseButtonEventArgs&& other) = default; + MouseButtonEventArgs& operator=(const MouseButtonEventArgs& other) = default; + MouseButtonEventArgs& operator=(MouseButtonEventArgs&& other) = default; + ~MouseButtonEventArgs() override = default; + + MouseButton GetButton() const { return button_; } + platform::native::KeyModifier GetKeyModifier() const { return key_modifier_; } + + private: + MouseButton button_; + platform::native::KeyModifier key_modifier_; +}; + +class MouseWheelEventArgs : public MouseEventArgs { + public: + MouseWheelEventArgs(Object* sender, Object* original_sender, + const Point& point, const float delta) + : MouseEventArgs(sender, original_sender, point), delta_(delta) {} + MouseWheelEventArgs(const MouseWheelEventArgs& other) = default; + MouseWheelEventArgs(MouseWheelEventArgs&& other) = default; + MouseWheelEventArgs& operator=(const MouseWheelEventArgs& other) = default; + MouseWheelEventArgs& operator=(MouseWheelEventArgs&& other) = default; + ~MouseWheelEventArgs() override = default; + + float GetDelta() const { return delta_; } + + private: + float delta_; +}; + +class PaintEventArgs : public UiEventArgs { + public: + PaintEventArgs(Object* sender, Object* original_sender, + platform::graph::IPainter* painter) + : UiEventArgs(sender, original_sender), painter_(painter) {} + PaintEventArgs(const PaintEventArgs& other) = default; + PaintEventArgs(PaintEventArgs&& other) = default; + PaintEventArgs& operator=(const PaintEventArgs& other) = default; + PaintEventArgs& operator=(PaintEventArgs&& other) = default; + ~PaintEventArgs() = default; + + platform::graph::IPainter* GetPainter() const { return painter_; } + + private: + platform::graph::IPainter* painter_; +}; + +class FocusChangeEventArgs : public UiEventArgs { + public: + FocusChangeEventArgs(Object* sender, Object* original_sender, + const bool is_window = false) + : UiEventArgs(sender, original_sender), is_window_(is_window) {} + FocusChangeEventArgs(const FocusChangeEventArgs& other) = default; + FocusChangeEventArgs(FocusChangeEventArgs&& other) = default; + FocusChangeEventArgs& operator=(const FocusChangeEventArgs& other) = default; + FocusChangeEventArgs& operator=(FocusChangeEventArgs&& other) = default; + ~FocusChangeEventArgs() override = default; + + // Return whether the focus change is caused by the window-wide focus change. + bool IsWindow() const { return is_window_; } + + private: + bool is_window_; +}; + +/* +class ToggleEventArgs : public UiEventArgs { + public: + ToggleEventArgs(Object* sender, Object* original_sender, bool new_state) + : UiEventArgs(sender, original_sender), new_state_(new_state) {} + ToggleEventArgs(const ToggleEventArgs& other) = default; + ToggleEventArgs(ToggleEventArgs&& other) = default; + ToggleEventArgs& operator=(const ToggleEventArgs& other) = default; + ToggleEventArgs& operator=(ToggleEventArgs&& other) = default; + ~ToggleEventArgs() override = default; + + bool GetNewState() const { return new_state_; } + + private: + bool new_state_; +}; +*/ + +class KeyEventArgs : public UiEventArgs { + public: + KeyEventArgs(Object* sender, Object* original_sender, + platform::native::KeyCode key_code, + platform::native::KeyModifier key_modifier) + : UiEventArgs(sender, original_sender), + key_code_(key_code), + key_modifier_(key_modifier) {} + KeyEventArgs(const KeyEventArgs& other) = default; + KeyEventArgs(KeyEventArgs&& other) = default; + KeyEventArgs& operator=(const KeyEventArgs& other) = default; + KeyEventArgs& operator=(KeyEventArgs&& other) = default; + ~KeyEventArgs() override = default; + + platform::native::KeyCode GetKeyCode() const { return key_code_; } + platform::native::KeyModifier GetKeyModifier() const { return key_modifier_; } + + private: + platform::native::KeyCode key_code_; + platform::native::KeyModifier key_modifier_; +}; + +class CharEventArgs : public UiEventArgs { + public: + CharEventArgs(Object* sender, Object* original_sender, std::string c) + : UiEventArgs(sender, original_sender), c_(std::move(c)) {} + CharEventArgs(const CharEventArgs& other) = default; + CharEventArgs(CharEventArgs&& other) = default; + CharEventArgs& operator=(const CharEventArgs& other) = default; + CharEventArgs& operator=(CharEventArgs&& other) = default; + ~CharEventArgs() override = default; + + std::string GetChar() const { return c_; } + + private: + std::string c_; +}; +} // namespace cru::ui::event diff --git a/include/cru/ui/UiHost.hpp b/include/cru/ui/UiHost.hpp new file mode 100644 index 00000000..651dab81 --- /dev/null +++ b/include/cru/ui/UiHost.hpp @@ -0,0 +1,170 @@ +#pragma once +#include "Base.hpp" + +#include "cru/common/Event.hpp" +#include "cru/common/SelfResolvable.hpp" +#include "render/Base.hpp" + +namespace cru::ui { +struct AfterLayoutEventArgs {}; + +// The host of all controls and render objects. +// +// 3 situations on destroy: +// 1. Native window destroyed, IsRetainAfterDestroy: false: +// OnNativeDestroy(set native_window_destroyed_ to true, call ~Window due to +// deleting_ is false and IsRetainAfterDestroy is false) -> ~Window -> +// ~UiHost(not destroy native window repeatedly due to native_window_destroyed_ +// is true) +// 2. Native window destroyed, IsRetainAfterDestroy: true: +// OnNativeDestroy(set native_window_destroyed_ to true, not call ~Window +// because deleting_ is false and IsRetainAfterDestroy is true) +// then, ~Window -> ~UiHost(not destroy native window repeatedly due to +// native_window_destroyed_ is true) +// 3. Native window not destroyed, ~Window is called: +// ~Window -> ~UiHost(set deleting_ to true, destroy native window +// due to native_window_destroyed is false) -> OnNativeDestroy(not call ~Window +// due to deleting_ is true and IsRetainAfterDestroy is whatever) +// In conclusion: +// 1. Set native_window_destroyed_ to true at the beginning of OnNativeDestroy. +// 2. Set deleting_ to true at the beginning of ~UiHost. +// 3. Destroy native window when native_window_destroy_ is false in ~Window. +// 4. Delete Window when deleting_ is false and IsRetainAfterDestroy is false in +// OnNativeDestroy. +class UiHost : public Object, public SelfResolvable { + public: + // This will create root window render object and attach it to window. + // It will also create and manage a native window. + UiHost(Window* window); + + CRU_DELETE_COPY(UiHost) + CRU_DELETE_MOVE(UiHost) + + ~UiHost() override; + + public: + // Mark the layout as invalid, and arrange a re-layout later. + // This method could be called more than one times in a message cycle. But + // layout only takes place once. + void InvalidateLayout(); + + // Mark the paint as invalid, and arrange a re-paint later. + // This method could be called more than one times in a message cycle. But + // paint only takes place once. + void InvalidatePaint(); + + IEvent* AfterLayoutEvent() { + return &after_layout_event_; + } + + void Relayout(); + + // Get current control that mouse hovers on. This ignores the mouse-capture + // control. Even when mouse is captured by another control, this function + // return the control under cursor. You can use `GetMouseCaptureControl` to + // get more info. + Control* GetMouseHoverControl() const { return mouse_hover_control_; } + + //*************** region: focus *************** + + // Request focus for specified control. + bool RequestFocusFor(Control* control); + + // Get the control that has focus. + Control* GetFocusControl(); + + //*************** region: focus *************** + + // Pass nullptr to release capture. If mouse is already capture by a control, + // this capture will fail and return false. If control is identical to the + // capturing control, capture is not changed and this function will return + // true. + // + // When capturing control changes, + // appropriate event will be sent. If mouse is not on the capturing control + // and capture is released, mouse enter event will be sent to the mouse-hover + // control. If mouse is not on the capturing control and capture is set, mouse + // leave event will be sent to the mouse-hover control. + bool CaptureMouseFor(Control* control); + + // Return null if not captured. + Control* GetMouseCaptureControl(); + + Control* HitTest(const Point& point); + + void UpdateCursor(); + + std::shared_ptr + GetNativeWindowResolver() { + return native_window_resolver_; + } + + bool IsRetainAfterDestroy() { return retain_after_destroy_; } + + void SetRetainAfterDestroy(bool destroy) { retain_after_destroy_ = destroy; } + + private: + //*************** region: native messages *************** + void OnNativeDestroy(platform::native::INativeWindow* window, std::nullptr_t); + void OnNativePaint(platform::native::INativeWindow* window, std::nullptr_t); + void OnNativeResize(platform::native::INativeWindow* window, + const Size& size); + + void OnNativeFocus(platform::native::INativeWindow* window, + cru::platform::native::FocusChangeType focus); + + void OnNativeMouseEnterLeave( + platform::native::INativeWindow* window, + cru::platform::native::MouseEnterLeaveType enter); + void OnNativeMouseMove(platform::native::INativeWindow* window, + const Point& point); + void OnNativeMouseDown( + platform::native::INativeWindow* window, + const platform::native::NativeMouseButtonEventArgs& args); + void OnNativeMouseUp( + platform::native::INativeWindow* window, + const platform::native::NativeMouseButtonEventArgs& args); + + void OnNativeKeyDown(platform::native::INativeWindow* window, + const platform::native::NativeKeyEventArgs& args); + void OnNativeKeyUp(platform::native::INativeWindow* window, + const platform::native::NativeKeyEventArgs& args); + + //*************** region: event dispatcher helper *************** + + void DispatchMouseHoverControlChangeEvent(Control* old_control, + Control* new_control, + const Point& point, bool no_leave, + bool no_enter); + + private: + bool need_layout_ = false; + + Event after_layout_event_; + + std::shared_ptr + native_window_resolver_; + + // See remarks of UiHost. + bool retain_after_destroy_ = false; + // See remarks of UiHost. + bool deleting_ = false; + + // We need this because calling Resolve on resolver in handler of destroy + // event is bad and will always get the dying window. But we need to label the + // window as destroyed so the destructor will not destroy native window + // repeatedly. See remarks of UiHost. + bool native_window_destroyed_ = false; + + std::vector event_revoker_guards_; + + Window* window_control_; + std::unique_ptr root_render_object_; + + Control* mouse_hover_control_; + + Control* focus_control_; // "focus_control_" can't be nullptr + + Control* mouse_captured_control_; +}; +} // namespace cru::ui diff --git a/include/cru/ui/UiManager.hpp b/include/cru/ui/UiManager.hpp new file mode 100644 index 00000000..e6facdbd --- /dev/null +++ b/include/cru/ui/UiManager.hpp @@ -0,0 +1,35 @@ +#pragma once +#include "Base.hpp" + +#include "controls/Base.hpp" + +namespace cru::ui { +struct ThemeResources { + std::shared_ptr default_font; + std::shared_ptr text_brush; + std::shared_ptr text_selection_brush; + std::shared_ptr caret_brush; + controls::ButtonStyle button_style; + controls::TextBoxBorderStyle text_box_border_style; +}; + +class UiManager : public Object { + public: + static UiManager* GetInstance(); + + private: + UiManager(); + + public: + UiManager(const UiManager& other) = delete; + UiManager(UiManager&& other) = delete; + UiManager& operator=(const UiManager& other) = delete; + UiManager& operator=(UiManager&& other) = delete; + ~UiManager() override; + + ThemeResources* GetThemeResources() { return &theme_resource_; } + + private: + ThemeResources theme_resource_; +}; +} // namespace cru::ui diff --git a/include/cru/ui/base.hpp b/include/cru/ui/base.hpp index a1335f90..97b0dbff 100644 --- a/include/cru/ui/base.hpp +++ b/include/cru/ui/base.hpp @@ -1,7 +1,7 @@ #pragma once -#include "cru/common/base.hpp" -#include "cru/platform/graph/base.hpp" -#include "cru/platform/native/base.hpp" +#include "cru/common/Base.hpp" +#include "cru/platform/graph/Base.hpp" +#include "cru/platform/native/Base.hpp" #include #include diff --git a/include/cru/ui/click_detector.hpp b/include/cru/ui/click_detector.hpp deleted file mode 100644 index 6c4761e7..00000000 --- a/include/cru/ui/click_detector.hpp +++ /dev/null @@ -1,86 +0,0 @@ -#pragma once -#include "control.hpp" - - -namespace cru::ui { -class ClickEventArgs : Object { - public: - ClickEventArgs(Control* sender, const Point& down_point, - const Point& up_point, MouseButton button) - : sender_(sender), - down_point_(down_point), - up_point_(up_point), - button_(button) {} - - CRU_DEFAULT_COPY(ClickEventArgs) - CRU_DEFAULT_MOVE(ClickEventArgs) - - ~ClickEventArgs() override = default; - - Control* GetSender() const { return sender_; } - Point GetDownPoint() const { return down_point_; } - Point GetUpPoint() const { return up_point_; } - MouseButton GetButton() const { return button_; } - - private: - Control* sender_; - Point down_point_; - Point up_point_; - MouseButton button_; -}; - -enum class ClickState { - None, // Mouse is outside the control. - Hover, // Mouse hovers on the control but not pressed - Press, // Mouse is pressed and if released click is done. - PressInactive // Mouse is pressed but if released click is canceled. -}; - -class ClickDetector : public Object { - public: - explicit ClickDetector(Control* control); - - CRU_DELETE_COPY(ClickDetector) - CRU_DELETE_MOVE(ClickDetector) - - ~ClickDetector() override = default; - - Control* GetControl() const { return control_; } - - ClickState GetState() const { return state_; } - - // Default is enable. - bool IsEnabled() const { return enable_; } - // If disable when user is pressing, the pressing is deactivated. - void SetEnabled(bool enable); - - // Default is left and right. - MouseButton GetTriggerButton() const { return trigger_button_; } - // If unset the trigger button when user is pressing, the pressing is - // deactivated. - void SetTriggerButton(MouseButton trigger_button); - - IEvent* ClickEvent() { return &event_; } - - IEvent* StateChangeEvent() { return &state_change_event_; } - - private: - void SetState(ClickState state); - - private: - Control* control_; - - ClickState state_; - - bool enable_ = true; - MouseButton trigger_button_ = mouse_buttons::left | mouse_buttons::right; - - Event event_; - Event state_change_event_; - - std::vector event_rovoker_guards_; - - Point down_point_; - MouseButton button_; -}; -} // namespace cru::ui diff --git a/include/cru/ui/content_control.hpp b/include/cru/ui/content_control.hpp deleted file mode 100644 index f88ec157..00000000 --- a/include/cru/ui/content_control.hpp +++ /dev/null @@ -1,29 +0,0 @@ -#pragma once -#include "control.hpp" - -namespace cru::ui { -class ContentControl : public Control { - protected: - ContentControl(); - - public: - ContentControl(const ContentControl& other) = delete; - ContentControl(ContentControl&& other) = delete; - ContentControl& operator=(const ContentControl& other) = delete; - ContentControl& operator=(ContentControl&& other) = delete; - ~ContentControl() override; - - const std::vector& GetChildren() const override final { - return child_vector_; - } - Control* GetChild() const { return child_; } - void SetChild(Control* child); - - protected: - virtual void OnChildChanged(Control* old_child, Control* new_child); - - private: - std::vector child_vector_; - Control*& child_; -}; -} // namespace cru::ui diff --git a/include/cru/ui/control.hpp b/include/cru/ui/control.hpp index d66405e6..347163be 100644 --- a/include/cru/ui/control.hpp +++ b/include/cru/ui/control.hpp @@ -1,9 +1,9 @@ #pragma once -#include "base.hpp" +#include "Base.hpp" -#include "cru/common/event.hpp" -#include "render/base.hpp" -#include "ui_event.hpp" +#include "cru/common/Event.hpp" +#include "render/Base.hpp" +#include "UiEvent.hpp" #include diff --git a/include/cru/ui/controls/FlexLayout.hpp b/include/cru/ui/controls/FlexLayout.hpp new file mode 100644 index 00000000..beacd1f9 --- /dev/null +++ b/include/cru/ui/controls/FlexLayout.hpp @@ -0,0 +1,41 @@ +#pragma once +#include "../LayoutControl.hpp" + +namespace cru::ui::controls { +class FlexLayout : public LayoutControl { + public: + static constexpr std::string_view control_type = "FlexLayout"; + + static FlexLayout* Create() { return new FlexLayout(); } + + protected: + FlexLayout(); + + public: + FlexLayout(const FlexLayout& other) = delete; + FlexLayout(FlexLayout&& other) = delete; + FlexLayout& operator=(const FlexLayout& other) = delete; + FlexLayout& operator=(FlexLayout&& other) = delete; + ~FlexLayout() override; + + std::string_view GetControlType() const final { return control_type; } + + render::RenderObject* GetRenderObject() const override; + + FlexMainAlignment GetContentMainAlign() const; + void SetContentMainAlign(FlexMainAlignment value); + + FlexDirection GetFlexDirection() const; + void SetFlexDirection(FlexDirection direction); + + FlexChildLayoutData GetChildLayoutData(Control* control); + void SetChildLayoutData(Control* control, const FlexChildLayoutData& data); + + protected: + void OnAddChild(Control* child, Index position) override; + void OnRemoveChild(Control* child, Index position) override; + + private: + std::shared_ptr render_object_; +}; +} // namespace cru::ui::controls diff --git a/include/cru/ui/controls/StackLayout.hpp b/include/cru/ui/controls/StackLayout.hpp new file mode 100644 index 00000000..d5998cc4 --- /dev/null +++ b/include/cru/ui/controls/StackLayout.hpp @@ -0,0 +1,31 @@ +#pragma once +#include "../LayoutControl.hpp" + +namespace cru::ui::controls { +class StackLayout : public LayoutControl { + public: + static constexpr std::string_view control_type = "StackLayout"; + + static StackLayout* Create() { return new StackLayout(); } + + protected: + StackLayout(); + + public: + CRU_DELETE_COPY(StackLayout) + CRU_DELETE_MOVE(StackLayout) + + ~StackLayout() override; + + std::string_view GetControlType() const final { return control_type; } + + render::RenderObject* GetRenderObject() const override; + + protected: + void OnAddChild(Control* child, Index position) override; + void OnRemoveChild(Control* child, Index position) override; + + private: + std::shared_ptr render_object_; +}; +} // namespace cru::ui::controls diff --git a/include/cru/ui/controls/TextBlock.hpp b/include/cru/ui/controls/TextBlock.hpp new file mode 100644 index 00000000..dd8b40b4 --- /dev/null +++ b/include/cru/ui/controls/TextBlock.hpp @@ -0,0 +1,38 @@ +#pragma once +#include "../NoChildControl.hpp" + +namespace cru::ui::controls { +template +class TextControlService; + +class TextBlock : public NoChildControl { + public: + static constexpr std::string_view control_type = "TextBlock"; + + static TextBlock* Create() { return new TextBlock(); } + + protected: + TextBlock(); + + public: + TextBlock(const TextBlock& other) = delete; + TextBlock(TextBlock&& other) = delete; + TextBlock& operator=(const TextBlock& other) = delete; + TextBlock& operator=(TextBlock&& other) = delete; + ~TextBlock() override; + + std::string_view GetControlType() const final { return control_type; } + + render::RenderObject* GetRenderObject() const override; + + std::string GetText() const; + void SetText(std::string text); + + render::TextRenderObject* GetTextRenderObject(); + + private: + std::unique_ptr text_render_object_; + + std::unique_ptr> service_; +}; +} // namespace cru::ui::controls diff --git a/include/cru/ui/controls/TextBox.hpp b/include/cru/ui/controls/TextBox.hpp new file mode 100644 index 00000000..2f7a12b6 --- /dev/null +++ b/include/cru/ui/controls/TextBox.hpp @@ -0,0 +1,49 @@ +#pragma once +#include "../NoChildControl.hpp" +#include "Base.hpp" + +#include + +namespace cru::ui::controls { +template +class TextControlService; + +class TextBox : public NoChildControl { + public: + static constexpr std::string_view control_type = "TextBox"; + + static TextBox* Create() { return new TextBox(); } + + protected: + TextBox(); + + public: + CRU_DELETE_COPY(TextBox) + CRU_DELETE_MOVE(TextBox) + + ~TextBox() override; + + std::string_view GetControlType() const final { return control_type; } + + render::RenderObject* GetRenderObject() const override; + + render::TextRenderObject* GetTextRenderObject(); + + const TextBoxBorderStyle& GetBorderStyle(); + void SetBorderStyle(TextBoxBorderStyle border_style); + + protected: + void OnMouseHoverChange(bool newHover) override; + + private: + void UpdateBorderStyle(); + + private: + std::unique_ptr border_render_object_; + std::unique_ptr text_render_object_; + + TextBoxBorderStyle border_style_; + + std::unique_ptr> service_; +}; +} // namespace cru::ui::controls diff --git a/include/cru/ui/controls/base.hpp b/include/cru/ui/controls/base.hpp index ebe9cdda..b550601b 100644 --- a/include/cru/ui/controls/base.hpp +++ b/include/cru/ui/controls/base.hpp @@ -1,5 +1,5 @@ #pragma once -#include "../base.hpp" +#include "../Base.hpp" namespace cru::ui::controls { using ButtonStateStyle = BorderStyle; diff --git a/include/cru/ui/controls/button.hpp b/include/cru/ui/controls/button.hpp index fb636a33..8a11409c 100644 --- a/include/cru/ui/controls/button.hpp +++ b/include/cru/ui/controls/button.hpp @@ -1,8 +1,8 @@ #pragma once -#include "../content_control.hpp" -#include "base.hpp" +#include "../ContentControl.hpp" +#include "Base.hpp" -#include "../click_detector.hpp" +#include "../ClickDetector.hpp" namespace cru::ui::controls { class Button : public ContentControl { diff --git a/include/cru/ui/controls/container.hpp b/include/cru/ui/controls/container.hpp index 7d4c0d23..e3d78365 100644 --- a/include/cru/ui/controls/container.hpp +++ b/include/cru/ui/controls/container.hpp @@ -1,5 +1,5 @@ #pragma once -#include "../content_control.hpp" +#include "../ContentControl.hpp" namespace cru::ui::controls { class Container : public ContentControl { diff --git a/include/cru/ui/controls/flex_layout.hpp b/include/cru/ui/controls/flex_layout.hpp deleted file mode 100644 index ab08a80b..00000000 --- a/include/cru/ui/controls/flex_layout.hpp +++ /dev/null @@ -1,41 +0,0 @@ -#pragma once -#include "../layout_control.hpp" - -namespace cru::ui::controls { -class FlexLayout : public LayoutControl { - public: - static constexpr std::string_view control_type = "FlexLayout"; - - static FlexLayout* Create() { return new FlexLayout(); } - - protected: - FlexLayout(); - - public: - FlexLayout(const FlexLayout& other) = delete; - FlexLayout(FlexLayout&& other) = delete; - FlexLayout& operator=(const FlexLayout& other) = delete; - FlexLayout& operator=(FlexLayout&& other) = delete; - ~FlexLayout() override; - - std::string_view GetControlType() const final { return control_type; } - - render::RenderObject* GetRenderObject() const override; - - FlexMainAlignment GetContentMainAlign() const; - void SetContentMainAlign(FlexMainAlignment value); - - FlexDirection GetFlexDirection() const; - void SetFlexDirection(FlexDirection direction); - - FlexChildLayoutData GetChildLayoutData(Control* control); - void SetChildLayoutData(Control* control, const FlexChildLayoutData& data); - - protected: - void OnAddChild(Control* child, Index position) override; - void OnRemoveChild(Control* child, Index position) override; - - private: - std::shared_ptr render_object_; -}; -} // namespace cru::ui::controls diff --git a/include/cru/ui/controls/stack_layout.hpp b/include/cru/ui/controls/stack_layout.hpp deleted file mode 100644 index 20da0e82..00000000 --- a/include/cru/ui/controls/stack_layout.hpp +++ /dev/null @@ -1,31 +0,0 @@ -#pragma once -#include "../layout_control.hpp" - -namespace cru::ui::controls { -class StackLayout : public LayoutControl { - public: - static constexpr std::string_view control_type = "StackLayout"; - - static StackLayout* Create() { return new StackLayout(); } - - protected: - StackLayout(); - - public: - CRU_DELETE_COPY(StackLayout) - CRU_DELETE_MOVE(StackLayout) - - ~StackLayout() override; - - std::string_view GetControlType() const final { return control_type; } - - render::RenderObject* GetRenderObject() const override; - - protected: - void OnAddChild(Control* child, Index position) override; - void OnRemoveChild(Control* child, Index position) override; - - private: - std::shared_ptr render_object_; -}; -} // namespace cru::ui::controls diff --git a/include/cru/ui/controls/text_block.hpp b/include/cru/ui/controls/text_block.hpp deleted file mode 100644 index 61f568c4..00000000 --- a/include/cru/ui/controls/text_block.hpp +++ /dev/null @@ -1,38 +0,0 @@ -#pragma once -#include "../no_child_control.hpp" - -namespace cru::ui::controls { -template -class TextControlService; - -class TextBlock : public NoChildControl { - public: - static constexpr std::string_view control_type = "TextBlock"; - - static TextBlock* Create() { return new TextBlock(); } - - protected: - TextBlock(); - - public: - TextBlock(const TextBlock& other) = delete; - TextBlock(TextBlock&& other) = delete; - TextBlock& operator=(const TextBlock& other) = delete; - TextBlock& operator=(TextBlock&& other) = delete; - ~TextBlock() override; - - std::string_view GetControlType() const final { return control_type; } - - render::RenderObject* GetRenderObject() const override; - - std::string GetText() const; - void SetText(std::string text); - - render::TextRenderObject* GetTextRenderObject(); - - private: - std::unique_ptr text_render_object_; - - std::unique_ptr> service_; -}; -} // namespace cru::ui::controls diff --git a/include/cru/ui/controls/text_box.hpp b/include/cru/ui/controls/text_box.hpp deleted file mode 100644 index 15fcb734..00000000 --- a/include/cru/ui/controls/text_box.hpp +++ /dev/null @@ -1,49 +0,0 @@ -#pragma once -#include "../no_child_control.hpp" -#include "base.hpp" - -#include - -namespace cru::ui::controls { -template -class TextControlService; - -class TextBox : public NoChildControl { - public: - static constexpr std::string_view control_type = "TextBox"; - - static TextBox* Create() { return new TextBox(); } - - protected: - TextBox(); - - public: - CRU_DELETE_COPY(TextBox) - CRU_DELETE_MOVE(TextBox) - - ~TextBox() override; - - std::string_view GetControlType() const final { return control_type; } - - render::RenderObject* GetRenderObject() const override; - - render::TextRenderObject* GetTextRenderObject(); - - const TextBoxBorderStyle& GetBorderStyle(); - void SetBorderStyle(TextBoxBorderStyle border_style); - - protected: - void OnMouseHoverChange(bool newHover) override; - - private: - void UpdateBorderStyle(); - - private: - std::unique_ptr border_render_object_; - std::unique_ptr text_render_object_; - - TextBoxBorderStyle border_style_; - - std::unique_ptr> service_; -}; -} // namespace cru::ui::controls diff --git a/include/cru/ui/layout_control.hpp b/include/cru/ui/layout_control.hpp deleted file mode 100644 index e1856403..00000000 --- a/include/cru/ui/layout_control.hpp +++ /dev/null @@ -1,31 +0,0 @@ -#pragma once -#include "control.hpp" - -namespace cru::ui { -class LayoutControl : public Control { - protected: - LayoutControl() = default; - - public: - LayoutControl(const LayoutControl& other) = delete; - LayoutControl(LayoutControl&& other) = delete; - LayoutControl& operator=(const LayoutControl& other) = delete; - LayoutControl& operator=(LayoutControl&& other) = delete; - ~LayoutControl() override; - - const std::vector& GetChildren() const override final { - return children_; - } - - void AddChild(Control* control, Index position); - - void RemoveChild(Index position); - - protected: - virtual void OnAddChild(Control* child, Index position); - virtual void OnRemoveChild(Control* child, Index position); - - private: - std::vector children_; -}; -} // namespace cru::ui diff --git a/include/cru/ui/no_child_control.hpp b/include/cru/ui/no_child_control.hpp deleted file mode 100644 index 62a9fa8d..00000000 --- a/include/cru/ui/no_child_control.hpp +++ /dev/null @@ -1,24 +0,0 @@ -#pragma once -#include "control.hpp" - -namespace cru::ui { -class NoChildControl : public Control { - private: - static const std::vector empty_control_vector; - - protected: - NoChildControl() = default; - - public: - NoChildControl(const NoChildControl& other) = delete; - NoChildControl(NoChildControl&& other) = delete; - NoChildControl& operator=(const NoChildControl& other) = delete; - NoChildControl& operator=(NoChildControl&& other) = delete; - ~NoChildControl() override = default; - - protected: - const std::vector& GetChildren() const override final { - return empty_control_vector; - } -}; -} // namespace cru::ui diff --git a/include/cru/ui/render/BorderRenderObject.hpp b/include/cru/ui/render/BorderRenderObject.hpp new file mode 100644 index 00000000..c3031f59 --- /dev/null +++ b/include/cru/ui/render/BorderRenderObject.hpp @@ -0,0 +1,102 @@ +#pragma once +#include "RenderObject.hpp" + +namespace cru::ui::render { +class BorderRenderObject : public RenderObject { + public: + BorderRenderObject(); + BorderRenderObject(const BorderRenderObject& other) = delete; + BorderRenderObject(BorderRenderObject&& other) = delete; + BorderRenderObject& operator=(const BorderRenderObject& other) = delete; + BorderRenderObject& operator=(BorderRenderObject&& other) = delete; + ~BorderRenderObject() override; + + bool IsBorderEnabled() const { return is_border_enabled_; } + void SetBorderEnabled(bool enabled) { is_border_enabled_ = enabled; } + + std::shared_ptr GetBorderBrush() { + return border_brush_; + } + + void SetBorderBrush(std::shared_ptr brush) { + if (brush == border_brush_) return; + border_brush_ = std::move(brush); + InvalidatePaint(); + } + + Thickness GetBorderThickness() { return border_thickness_; } + + void SetBorderThickness(const Thickness thickness) { + if (thickness == border_thickness_) return; + border_thickness_ = thickness; + InvalidateLayout(); + } + + CornerRadius GetBorderRadius() { return border_radius_; } + + void SetBorderRadius(const CornerRadius radius) { + if (radius == border_radius_) return; + border_radius_ = radius; + RecreateGeometry(); + } + + std::shared_ptr GetForegroundBrush() { + return foreground_brush_; + } + + void SetForegroundBrush(std::shared_ptr brush) { + if (brush == foreground_brush_) return; + foreground_brush_ = std::move(brush); + InvalidatePaint(); + } + + std::shared_ptr GetBackgroundBrush() { + return background_brush_; + } + + void SetBackgroundBrush(std::shared_ptr brush) { + if (brush == background_brush_) return; + background_brush_ = std::move(brush); + InvalidatePaint(); + } + + void SetBorderStyle(const BorderStyle& style); + + void Draw(platform::graph::IPainter* painter) override; + + RenderObject* HitTest(const Point& point) override; + + protected: + void OnMeasureCore(const Size& available_size) override; + void OnLayoutCore(const Rect& rect) override; + Size OnMeasureContent(const Size& available_size) override; + void OnLayoutContent(const Rect& content_rect) override; + + void OnAfterLayout() override; + + private: + RenderObject* GetChild() const { + return GetChildren().empty() ? nullptr : GetChildren()[0]; + } + + void RecreateGeometry(); + + private: + bool is_border_enabled_ = false; + + std::shared_ptr border_brush_; + platform::Thickness border_thickness_; + CornerRadius border_radius_; + + std::shared_ptr foreground_brush_; + std::shared_ptr background_brush_; + + // The ring. Used for painting. + std::unique_ptr geometry_; + // Area including inner area of the border. Used for painting foreground and + // background. + std::unique_ptr border_inner_geometry_; + // Area including border ring and inner area. Used for hit test. + std::unique_ptr border_outer_geometry_; +}; +} // namespace cru::ui::render diff --git a/include/cru/ui/render/CanvasRenderObject.hpp b/include/cru/ui/render/CanvasRenderObject.hpp new file mode 100644 index 00000000..ba50a985 --- /dev/null +++ b/include/cru/ui/render/CanvasRenderObject.hpp @@ -0,0 +1,39 @@ +#pragma once +#include "RenderObject.hpp" + +namespace cru::ui::render { +// The measure logic for `CanvasRenderObject` is that you set a desired size by +// `SetDesiredSize` (not `SetPreferredSize`) and it will compare desired size +// and available size and use the smaller one (by `Min`). +class CanvasRenderObject : public RenderObject { + public: + CanvasRenderObject(); + + CRU_DELETE_COPY(CanvasRenderObject) + CRU_DELETE_MOVE(CanvasRenderObject) + + ~CanvasRenderObject(); + + public: + void Draw(platform::graph::IPainter* painter) override; + + RenderObject* HitTest(const Point& point) override; + + Size GetDesiredSize() const { return desired_size_; } + + // Set the desired size. This is the content size excluding padding and + // margin. + void SetDesiredSize(const Size& new_size) { desired_size_ = new_size; } + + IEvent* PaintEvent() { return &paint_event_; } + + protected: + Size OnMeasureContent(const Size& available_size) override; + void OnLayoutContent(const Rect& content_rect) override; + + private: + Size desired_size_{}; + + Event paint_event_; +}; +} // namespace cru::ui::render diff --git a/include/cru/ui/render/FlexLayoutRenderObject.hpp b/include/cru/ui/render/FlexLayoutRenderObject.hpp new file mode 100644 index 00000000..bc43141d --- /dev/null +++ b/include/cru/ui/render/FlexLayoutRenderObject.hpp @@ -0,0 +1,42 @@ +#pragma once +#include "LayoutRenderObject.hpp" + +namespace cru::ui::render { +class FlexLayoutRenderObject : public LayoutRenderObject { + public: + FlexLayoutRenderObject() = default; + FlexLayoutRenderObject(const FlexLayoutRenderObject& other) = delete; + FlexLayoutRenderObject& operator=(const FlexLayoutRenderObject& other) = + delete; + FlexLayoutRenderObject(FlexLayoutRenderObject&& other) = delete; + FlexLayoutRenderObject& operator=(FlexLayoutRenderObject&& other) = delete; + ~FlexLayoutRenderObject() override = default; + + FlexDirection GetFlexDirection() const { return direction_; } + void SetFlexDirection(FlexDirection direction) { + direction_ = direction; + InvalidateLayout(); + } + + FlexMainAlignment GetContentMainAlign() const { return content_main_align_; } + void SetContentMainAlign(FlexMainAlignment align) { + content_main_align_ = align; + InvalidateLayout(); + } + + FlexCrossAlignment GetItemCrossAlign() const { return item_cross_align_; } + void SetItemCrossAlign(FlexCrossAlignment align) { + item_cross_align_ = align; + InvalidateLayout(); + } + + protected: + Size OnMeasureContent(const Size& available_size) override; + void OnLayoutContent(const Rect& content_rect) override; + + private: + FlexDirection direction_ = FlexDirection::Horizontal; + FlexMainAlignment content_main_align_ = FlexMainAlignment::Start; + FlexCrossAlignment item_cross_align_ = FlexCrossAlignment::Center; +}; +} // namespace cru::ui::render diff --git a/include/cru/ui/render/LayoutRenderObject.hpp b/include/cru/ui/render/LayoutRenderObject.hpp new file mode 100644 index 00000000..e6ca9dc0 --- /dev/null +++ b/include/cru/ui/render/LayoutRenderObject.hpp @@ -0,0 +1,88 @@ +#pragma once +#include "RenderObject.hpp" + +#include "cru/platform/graph/util/Painter.hpp" + +namespace cru::ui::render { +template +class LayoutRenderObject : public RenderObject { + public: + using ChildLayoutData = TChildLayoutData; + + protected: + LayoutRenderObject() : RenderObject(ChildMode::Multiple) {} + + public: + CRU_DELETE_COPY(LayoutRenderObject) + CRU_DELETE_MOVE(LayoutRenderObject) + + ~LayoutRenderObject() override = default; + + ChildLayoutData* GetChildLayoutData(Index position) { + Expects(position >= 0 && + position < static_cast(child_layout_data_.size())); + return &child_layout_data_[position]; + } + + void Draw(platform::graph::IPainter* painter) override; + + RenderObject* HitTest(const Point& point) override; + + protected: + void OnAddChild(RenderObject* new_child, Index position) override; + void OnRemoveChild(RenderObject* removed_child, Index position) override; + + private: + std::vector child_layout_data_{}; +}; + +template +void LayoutRenderObject::Draw( + platform::graph::IPainter* painter) { + for (const auto child : GetChildren()) { + auto offset = child->GetOffset(); + platform::graph::util::WithTransform( + painter, platform::Matrix::Translation(offset.x, offset.y), + [child](auto p) { child->Draw(p); }); + } +} + +template +RenderObject* LayoutRenderObject::HitTest( + const Point& point) { + const auto& children = GetChildren(); + for (auto i = children.crbegin(); i != children.crend(); ++i) { + auto offset = (*i)->GetOffset(); + Point p{point.x - offset.x, point.y - offset.y}; + const auto result = (*i)->HitTest(p); + if (result != nullptr) { + return result; + } + } + + const auto margin = GetMargin(); + const auto size = GetSize(); + return Rect{margin.left, margin.top, + std::max(size.width - margin.GetHorizontalTotal(), 0.0f), + std::max(size.height - margin.GetVerticalTotal(), 0.0f)} + .IsPointInside(point) + ? this + : nullptr; +} // namespace cru::ui::render + +template +void LayoutRenderObject::OnAddChild(RenderObject* new_child, + const Index position) { + CRU_UNUSED(new_child) + + child_layout_data_.emplace(child_layout_data_.cbegin() + position); +} + +template +void LayoutRenderObject::OnRemoveChild( + RenderObject* removed_child, const Index position) { + CRU_UNUSED(removed_child) + + child_layout_data_.erase(child_layout_data_.cbegin() + position); +} +} // namespace cru::ui::render diff --git a/include/cru/ui/render/LayoutUtility.hpp b/include/cru/ui/render/LayoutUtility.hpp new file mode 100644 index 00000000..63d13fd3 --- /dev/null +++ b/include/cru/ui/render/LayoutUtility.hpp @@ -0,0 +1,7 @@ +#pragma once +#include "Base.hpp" + +namespace cru::ui::render { +Size Min(const Size& left, const Size& right); +Size Max(const Size& left, const Size& right); +} // namespace cru::ui::render diff --git a/include/cru/ui/render/RenderObject.hpp b/include/cru/ui/render/RenderObject.hpp new file mode 100644 index 00000000..7cfa3883 --- /dev/null +++ b/include/cru/ui/render/RenderObject.hpp @@ -0,0 +1,118 @@ +#pragma once +#include "Base.hpp" + +#include "cru/common/Event.hpp" + +namespace cru::ui::render { +// Render object will not destroy its children when destroyed. Control must +// manage lifecycle of its render objects. Since control will destroy its +// children when destroyed, render objects will be destroyed along with it. +class RenderObject : public Object { + friend WindowRenderObject; + + protected: + enum class ChildMode { + None, + Single, + Multiple, + }; + + RenderObject() = default; + RenderObject(ChildMode child_mode) : RenderObject() { + SetChildMode(child_mode); + } + + public: + RenderObject(const RenderObject& other) = delete; + RenderObject(RenderObject&& other) = delete; + RenderObject& operator=(const RenderObject& other) = delete; + RenderObject& operator=(RenderObject&& other) = delete; + ~RenderObject() override = default; + + Control* GetAttachedControl() const { return control_; } + void SetAttachedControl(Control* new_control) { control_ = new_control; } + + UiHost* GetUiHost() const { return ui_host_; } + + RenderObject* GetParent() const { return parent_; } + + const std::vector& GetChildren() const { return children_; } + Index GetChildCount() const { return static_cast(children_.size()); } + void AddChild(RenderObject* render_object, Index position); + void RemoveChild(Index position); + + Point GetOffset() const { return offset_; } + void SetOffset(const Point& offset) { offset_ = offset; } + Point GetTotalOffset() const; + Point FromRootToContent(const Point& point) const; + Size GetSize() const { return size_; } + void SetSize(const Size& size) { size_ = size; } + + Thickness GetMargin() const { return margin_; } + void SetMargin(const Thickness& margin) { margin_ = margin; } + + Thickness GetPadding() const { return padding_; } + void SetPadding(const Thickness& padding) { padding_ = padding; } + + Size GetPreferredSize() const { return preferred_size_; } + void SetPreferredSize(const Size& preferred_size) { + preferred_size_ = preferred_size; + } + + void Measure(const Size& available_size); + void Layout(const Rect& rect); + + virtual void Draw(platform::graph::IPainter* painter) = 0; + + virtual RenderObject* HitTest(const Point& point) = 0; + + public: + void InvalidateLayout(); + void InvalidatePaint(); + + protected: + void SetChildMode(ChildMode mode) { child_mode_ = mode; } + + protected: + virtual void OnParentChanged(RenderObject* old_parent, + RenderObject* new_parent); + + // default is to invalidate both layout and paint + virtual void OnAddChild(RenderObject* new_child, Index position); + // default is to invalidate both layout and paint + virtual void OnRemoveChild(RenderObject* removed_child, Index position); + + virtual void OnMeasureCore(const Size& available_size); + virtual void OnLayoutCore(const Rect& rect); + virtual Size OnMeasureContent(const Size& available_size) = 0; + virtual void OnLayoutContent(const Rect& content_rect) = 0; + + virtual void OnAfterLayout(); + static void NotifyAfterLayoutRecursive(RenderObject* render_object); + + Rect GetPaddingRect() const; + Rect GetContentRect() const; + + private: + void SetParent(RenderObject* new_parent); + + void SetRenderHostRecursive(UiHost* host); + + private: + Control* control_ = nullptr; + UiHost* ui_host_ = nullptr; + + RenderObject* parent_ = nullptr; + std::vector children_{}; + + ChildMode child_mode_ = ChildMode::None; + + Point offset_{}; + Size size_{}; + + Thickness margin_{}; + Thickness padding_{}; + + Size preferred_size_{}; +}; +} // namespace cru::ui::render diff --git a/include/cru/ui/render/ScrollRenderObject.hpp b/include/cru/ui/render/ScrollRenderObject.hpp new file mode 100644 index 00000000..dcf6dae6 --- /dev/null +++ b/include/cru/ui/render/ScrollRenderObject.hpp @@ -0,0 +1,30 @@ +#pragma once +#include "RenderObject.hpp" + +#include "cru/platform/graph/util/Painter.hpp" + +namespace cru::ui::render { +class ScrollRenderObject : public RenderObject { + public: + ScrollRenderObject() : RenderObject(ChildMode::Single) {} + + CRU_DELETE_COPY(ScrollRenderObject) + CRU_DELETE_MOVE(ScrollRenderObject) + + ~ScrollRenderObject() override = default; + + void Draw(platform::graph::IPainter* painter) override; + + RenderObject* HitTest(const Point& point) override; + + Point GetScrollOffset() { return scroll_offset_; } + void SetScrollOffset(const Point& offset); + + protected: + void OnAddChild(RenderObject* new_child, Index position) override; + void OnRemoveChild(RenderObject* removed_child, Index position) override; + + private: + Point scroll_offset_; +}; +} // namespace cru::ui::render diff --git a/include/cru/ui/render/StackLayoutRenderObject.hpp b/include/cru/ui/render/StackLayoutRenderObject.hpp new file mode 100644 index 00000000..a5bf9335 --- /dev/null +++ b/include/cru/ui/render/StackLayoutRenderObject.hpp @@ -0,0 +1,17 @@ +#pragma once +#include "LayoutRenderObject.hpp" + +namespace cru::ui::render { +class StackLayoutRenderObject + : public LayoutRenderObject { + public: + StackLayoutRenderObject() = default; + CRU_DELETE_COPY(StackLayoutRenderObject) + CRU_DELETE_MOVE(StackLayoutRenderObject) + ~StackLayoutRenderObject() = default; + + protected: + Size OnMeasureContent(const Size& available_size) override; + void OnLayoutContent(const Rect& content_rect) override; +}; +} // namespace cru::ui::render diff --git a/include/cru/ui/render/TextRenderObject.hpp b/include/cru/ui/render/TextRenderObject.hpp new file mode 100644 index 00000000..7a81ba51 --- /dev/null +++ b/include/cru/ui/render/TextRenderObject.hpp @@ -0,0 +1,85 @@ +#pragma once +#include "RenderObject.hpp" + +#include + +namespace cru::ui::render { +class TextRenderObject : public RenderObject { + public: + constexpr static float default_caret_width = 2; + + public: + TextRenderObject(std::shared_ptr brush, + std::shared_ptr font, + std::shared_ptr selection_brush, + std::shared_ptr caret_brush); + TextRenderObject(const TextRenderObject& other) = delete; + TextRenderObject(TextRenderObject&& other) = delete; + TextRenderObject& operator=(const TextRenderObject& other) = delete; + TextRenderObject& operator=(TextRenderObject&& other) = delete; + ~TextRenderObject() override; + + std::string GetText() const; + void SetText(std::string new_text); + + std::shared_ptr GetBrush() const { return brush_; } + void SetBrush(std::shared_ptr new_brush); + + std::shared_ptr GetFont() const; + void SetFont(std::shared_ptr font); + + std::vector TextRangeRect(const TextRange& text_range); + Point TextSinglePoint(gsl::index position, bool trailing); + platform::graph::TextHitTestResult TextHitTest(const Point& point); + + std::optional GetSelectionRange() const { + return selection_range_; + } + void SetSelectionRange(std::optional new_range); + + std::shared_ptr GetSelectionBrush() const { + return selection_brush_; + } + void SetSelectionBrush(std::shared_ptr new_brush); + + bool IsDrawCaret() const { return draw_caret_; } + void SetDrawCaret(bool draw_caret); + void ToggleDrawCaret() { SetDrawCaret(!IsDrawCaret()); } + + // Caret position can be any value. When it is negative, 0 is used. When it + // exceeds the size of the string, the size of the string is used. + gsl::index GetCaretPosition() const { return caret_position_; } + void SetCaretPosition(gsl::index position); + + std::shared_ptr GetCaretBrush() const { + return caret_brush_; + } + void GetCaretBrush(std::shared_ptr brush); + + float GetCaretWidth() const { return caret_width_; } + void SetCaretWidth(float width); + + void Draw(platform::graph::IPainter* painter) override; + + RenderObject* HitTest(const Point& point) override; + + protected: + Size OnMeasureContent(const Size& available_size) override; + void OnLayoutContent(const Rect& content_rect) override; + + void OnAfterLayout() override; + + private: + std::shared_ptr brush_; + std::shared_ptr font_; + std::unique_ptr text_layout_; + + std::optional selection_range_ = std::nullopt; + std::shared_ptr selection_brush_; + + bool draw_caret_ = false; + gsl::index caret_position_ = 0; + std::shared_ptr caret_brush_; + float caret_width_ = default_caret_width; +}; +} // namespace cru::ui::render diff --git a/include/cru/ui/render/WindowRenderObject.hpp b/include/cru/ui/render/WindowRenderObject.hpp new file mode 100644 index 00000000..00bce29b --- /dev/null +++ b/include/cru/ui/render/WindowRenderObject.hpp @@ -0,0 +1,30 @@ +#pragma once +#include "RenderObject.hpp" + +namespace cru::ui::render { +class WindowRenderObject : public RenderObject { + public: + WindowRenderObject(UiHost* host); + WindowRenderObject(const WindowRenderObject& other) = delete; + WindowRenderObject(WindowRenderObject&& other) = delete; + WindowRenderObject& operator=(const WindowRenderObject& other) = delete; + WindowRenderObject& operator=(WindowRenderObject&& other) = delete; + ~WindowRenderObject() override = default; + + void Draw(platform::graph::IPainter* painter) override; + + RenderObject* HitTest(const Point& point) override; + + protected: + Size OnMeasureContent(const Size& available_size) override; + void OnLayoutContent(const Rect& content_rect) override; + + private: + RenderObject* GetChild() const { + return GetChildren().empty() ? nullptr : GetChildren()[0]; + } + + private: + EventRevokerGuard after_layout_event_guard_; +}; +} // namespace cru::ui::render diff --git a/include/cru/ui/render/base.hpp b/include/cru/ui/render/base.hpp index f9d936e0..c2af5e99 100644 --- a/include/cru/ui/render/base.hpp +++ b/include/cru/ui/render/base.hpp @@ -1,5 +1,5 @@ #pragma once -#include "../base.hpp" +#include "../Base.hpp" namespace cru::ui::render { class RenderObject; diff --git a/include/cru/ui/render/border_render_object.hpp b/include/cru/ui/render/border_render_object.hpp deleted file mode 100644 index 02672309..00000000 --- a/include/cru/ui/render/border_render_object.hpp +++ /dev/null @@ -1,102 +0,0 @@ -#pragma once -#include "render_object.hpp" - -namespace cru::ui::render { -class BorderRenderObject : public RenderObject { - public: - BorderRenderObject(); - BorderRenderObject(const BorderRenderObject& other) = delete; - BorderRenderObject(BorderRenderObject&& other) = delete; - BorderRenderObject& operator=(const BorderRenderObject& other) = delete; - BorderRenderObject& operator=(BorderRenderObject&& other) = delete; - ~BorderRenderObject() override; - - bool IsBorderEnabled() const { return is_border_enabled_; } - void SetBorderEnabled(bool enabled) { is_border_enabled_ = enabled; } - - std::shared_ptr GetBorderBrush() { - return border_brush_; - } - - void SetBorderBrush(std::shared_ptr brush) { - if (brush == border_brush_) return; - border_brush_ = std::move(brush); - InvalidatePaint(); - } - - Thickness GetBorderThickness() { return border_thickness_; } - - void SetBorderThickness(const Thickness thickness) { - if (thickness == border_thickness_) return; - border_thickness_ = thickness; - InvalidateLayout(); - } - - CornerRadius GetBorderRadius() { return border_radius_; } - - void SetBorderRadius(const CornerRadius radius) { - if (radius == border_radius_) return; - border_radius_ = radius; - RecreateGeometry(); - } - - std::shared_ptr GetForegroundBrush() { - return foreground_brush_; - } - - void SetForegroundBrush(std::shared_ptr brush) { - if (brush == foreground_brush_) return; - foreground_brush_ = std::move(brush); - InvalidatePaint(); - } - - std::shared_ptr GetBackgroundBrush() { - return background_brush_; - } - - void SetBackgroundBrush(std::shared_ptr brush) { - if (brush == background_brush_) return; - background_brush_ = std::move(brush); - InvalidatePaint(); - } - - void SetBorderStyle(const BorderStyle& style); - - void Draw(platform::graph::IPainter* painter) override; - - RenderObject* HitTest(const Point& point) override; - - protected: - void OnMeasureCore(const Size& available_size) override; - void OnLayoutCore(const Rect& rect) override; - Size OnMeasureContent(const Size& available_size) override; - void OnLayoutContent(const Rect& content_rect) override; - - void OnAfterLayout() override; - - private: - RenderObject* GetChild() const { - return GetChildren().empty() ? nullptr : GetChildren()[0]; - } - - void RecreateGeometry(); - - private: - bool is_border_enabled_ = false; - - std::shared_ptr border_brush_; - platform::Thickness border_thickness_; - CornerRadius border_radius_; - - std::shared_ptr foreground_brush_; - std::shared_ptr background_brush_; - - // The ring. Used for painting. - std::unique_ptr geometry_; - // Area including inner area of the border. Used for painting foreground and - // background. - std::unique_ptr border_inner_geometry_; - // Area including border ring and inner area. Used for hit test. - std::unique_ptr border_outer_geometry_; -}; -} // namespace cru::ui::render diff --git a/include/cru/ui/render/canvas_render_object.hpp b/include/cru/ui/render/canvas_render_object.hpp deleted file mode 100644 index cb3828b6..00000000 --- a/include/cru/ui/render/canvas_render_object.hpp +++ /dev/null @@ -1,39 +0,0 @@ -#pragma once -#include "render_object.hpp" - -namespace cru::ui::render { -// The measure logic for `CanvasRenderObject` is that you set a desired size by -// `SetDesiredSize` (not `SetPreferredSize`) and it will compare desired size -// and available size and use the smaller one (by `Min`). -class CanvasRenderObject : public RenderObject { - public: - CanvasRenderObject(); - - CRU_DELETE_COPY(CanvasRenderObject) - CRU_DELETE_MOVE(CanvasRenderObject) - - ~CanvasRenderObject(); - - public: - void Draw(platform::graph::IPainter* painter) override; - - RenderObject* HitTest(const Point& point) override; - - Size GetDesiredSize() const { return desired_size_; } - - // Set the desired size. This is the content size excluding padding and - // margin. - void SetDesiredSize(const Size& new_size) { desired_size_ = new_size; } - - IEvent* PaintEvent() { return &paint_event_; } - - protected: - Size OnMeasureContent(const Size& available_size) override; - void OnLayoutContent(const Rect& content_rect) override; - - private: - Size desired_size_{}; - - Event paint_event_; -}; -} // namespace cru::ui::render diff --git a/include/cru/ui/render/flex_layout_render_object.hpp b/include/cru/ui/render/flex_layout_render_object.hpp deleted file mode 100644 index 849c1a0d..00000000 --- a/include/cru/ui/render/flex_layout_render_object.hpp +++ /dev/null @@ -1,42 +0,0 @@ -#pragma once -#include "layout_render_object.hpp" - -namespace cru::ui::render { -class FlexLayoutRenderObject : public LayoutRenderObject { - public: - FlexLayoutRenderObject() = default; - FlexLayoutRenderObject(const FlexLayoutRenderObject& other) = delete; - FlexLayoutRenderObject& operator=(const FlexLayoutRenderObject& other) = - delete; - FlexLayoutRenderObject(FlexLayoutRenderObject&& other) = delete; - FlexLayoutRenderObject& operator=(FlexLayoutRenderObject&& other) = delete; - ~FlexLayoutRenderObject() override = default; - - FlexDirection GetFlexDirection() const { return direction_; } - void SetFlexDirection(FlexDirection direction) { - direction_ = direction; - InvalidateLayout(); - } - - FlexMainAlignment GetContentMainAlign() const { return content_main_align_; } - void SetContentMainAlign(FlexMainAlignment align) { - content_main_align_ = align; - InvalidateLayout(); - } - - FlexCrossAlignment GetItemCrossAlign() const { return item_cross_align_; } - void SetItemCrossAlign(FlexCrossAlignment align) { - item_cross_align_ = align; - InvalidateLayout(); - } - - protected: - Size OnMeasureContent(const Size& available_size) override; - void OnLayoutContent(const Rect& content_rect) override; - - private: - FlexDirection direction_ = FlexDirection::Horizontal; - FlexMainAlignment content_main_align_ = FlexMainAlignment::Start; - FlexCrossAlignment item_cross_align_ = FlexCrossAlignment::Center; -}; -} // namespace cru::ui::render diff --git a/include/cru/ui/render/layout_render_object.hpp b/include/cru/ui/render/layout_render_object.hpp deleted file mode 100644 index 5c4c9c5c..00000000 --- a/include/cru/ui/render/layout_render_object.hpp +++ /dev/null @@ -1,88 +0,0 @@ -#pragma once -#include "render_object.hpp" - -#include "cru/platform/graph/util/painter.hpp" - -namespace cru::ui::render { -template -class LayoutRenderObject : public RenderObject { - public: - using ChildLayoutData = TChildLayoutData; - - protected: - LayoutRenderObject() : RenderObject(ChildMode::Multiple) {} - - public: - CRU_DELETE_COPY(LayoutRenderObject) - CRU_DELETE_MOVE(LayoutRenderObject) - - ~LayoutRenderObject() override = default; - - ChildLayoutData* GetChildLayoutData(Index position) { - Expects(position >= 0 && - position < static_cast(child_layout_data_.size())); - return &child_layout_data_[position]; - } - - void Draw(platform::graph::IPainter* painter) override; - - RenderObject* HitTest(const Point& point) override; - - protected: - void OnAddChild(RenderObject* new_child, Index position) override; - void OnRemoveChild(RenderObject* removed_child, Index position) override; - - private: - std::vector child_layout_data_{}; -}; - -template -void LayoutRenderObject::Draw( - platform::graph::IPainter* painter) { - for (const auto child : GetChildren()) { - auto offset = child->GetOffset(); - platform::graph::util::WithTransform( - painter, platform::Matrix::Translation(offset.x, offset.y), - [child](auto p) { child->Draw(p); }); - } -} - -template -RenderObject* LayoutRenderObject::HitTest( - const Point& point) { - const auto& children = GetChildren(); - for (auto i = children.crbegin(); i != children.crend(); ++i) { - auto offset = (*i)->GetOffset(); - Point p{point.x - offset.x, point.y - offset.y}; - const auto result = (*i)->HitTest(p); - if (result != nullptr) { - return result; - } - } - - const auto margin = GetMargin(); - const auto size = GetSize(); - return Rect{margin.left, margin.top, - std::max(size.width - margin.GetHorizontalTotal(), 0.0f), - std::max(size.height - margin.GetVerticalTotal(), 0.0f)} - .IsPointInside(point) - ? this - : nullptr; -} // namespace cru::ui::render - -template -void LayoutRenderObject::OnAddChild(RenderObject* new_child, - const Index position) { - CRU_UNUSED(new_child) - - child_layout_data_.emplace(child_layout_data_.cbegin() + position); -} - -template -void LayoutRenderObject::OnRemoveChild( - RenderObject* removed_child, const Index position) { - CRU_UNUSED(removed_child) - - child_layout_data_.erase(child_layout_data_.cbegin() + position); -} -} // namespace cru::ui::render diff --git a/include/cru/ui/render/layout_utility.hpp b/include/cru/ui/render/layout_utility.hpp deleted file mode 100644 index 16a15d87..00000000 --- a/include/cru/ui/render/layout_utility.hpp +++ /dev/null @@ -1,7 +0,0 @@ -#pragma once -#include "base.hpp" - -namespace cru::ui::render { -Size Min(const Size& left, const Size& right); -Size Max(const Size& left, const Size& right); -} // namespace cru::ui::render diff --git a/include/cru/ui/render/render_object.hpp b/include/cru/ui/render/render_object.hpp deleted file mode 100644 index 6a8db52f..00000000 --- a/include/cru/ui/render/render_object.hpp +++ /dev/null @@ -1,118 +0,0 @@ -#pragma once -#include "base.hpp" - -#include "cru/common/event.hpp" - -namespace cru::ui::render { -// Render object will not destroy its children when destroyed. Control must -// manage lifecycle of its render objects. Since control will destroy its -// children when destroyed, render objects will be destroyed along with it. -class RenderObject : public Object { - friend WindowRenderObject; - - protected: - enum class ChildMode { - None, - Single, - Multiple, - }; - - RenderObject() = default; - RenderObject(ChildMode child_mode) : RenderObject() { - SetChildMode(child_mode); - } - - public: - RenderObject(const RenderObject& other) = delete; - RenderObject(RenderObject&& other) = delete; - RenderObject& operator=(const RenderObject& other) = delete; - RenderObject& operator=(RenderObject&& other) = delete; - ~RenderObject() override = default; - - Control* GetAttachedControl() const { return control_; } - void SetAttachedControl(Control* new_control) { control_ = new_control; } - - UiHost* GetUiHost() const { return ui_host_; } - - RenderObject* GetParent() const { return parent_; } - - const std::vector& GetChildren() const { return children_; } - Index GetChildCount() const { return static_cast(children_.size()); } - void AddChild(RenderObject* render_object, Index position); - void RemoveChild(Index position); - - Point GetOffset() const { return offset_; } - void SetOffset(const Point& offset) { offset_ = offset; } - Point GetTotalOffset() const; - Point FromRootToContent(const Point& point) const; - Size GetSize() const { return size_; } - void SetSize(const Size& size) { size_ = size; } - - Thickness GetMargin() const { return margin_; } - void SetMargin(const Thickness& margin) { margin_ = margin; } - - Thickness GetPadding() const { return padding_; } - void SetPadding(const Thickness& padding) { padding_ = padding; } - - Size GetPreferredSize() const { return preferred_size_; } - void SetPreferredSize(const Size& preferred_size) { - preferred_size_ = preferred_size; - } - - void Measure(const Size& available_size); - void Layout(const Rect& rect); - - virtual void Draw(platform::graph::IPainter* painter) = 0; - - virtual RenderObject* HitTest(const Point& point) = 0; - - public: - void InvalidateLayout(); - void InvalidatePaint(); - - protected: - void SetChildMode(ChildMode mode) { child_mode_ = mode; } - - protected: - virtual void OnParentChanged(RenderObject* old_parent, - RenderObject* new_parent); - - // default is to invalidate both layout and paint - virtual void OnAddChild(RenderObject* new_child, Index position); - // default is to invalidate both layout and paint - virtual void OnRemoveChild(RenderObject* removed_child, Index position); - - virtual void OnMeasureCore(const Size& available_size); - virtual void OnLayoutCore(const Rect& rect); - virtual Size OnMeasureContent(const Size& available_size) = 0; - virtual void OnLayoutContent(const Rect& content_rect) = 0; - - virtual void OnAfterLayout(); - static void NotifyAfterLayoutRecursive(RenderObject* render_object); - - Rect GetPaddingRect() const; - Rect GetContentRect() const; - - private: - void SetParent(RenderObject* new_parent); - - void SetRenderHostRecursive(UiHost* host); - - private: - Control* control_ = nullptr; - UiHost* ui_host_ = nullptr; - - RenderObject* parent_ = nullptr; - std::vector children_{}; - - ChildMode child_mode_ = ChildMode::None; - - Point offset_{}; - Size size_{}; - - Thickness margin_{}; - Thickness padding_{}; - - Size preferred_size_{}; -}; -} // namespace cru::ui::render diff --git a/include/cru/ui/render/scroll_render_object.hpp b/include/cru/ui/render/scroll_render_object.hpp deleted file mode 100644 index 1527db6c..00000000 --- a/include/cru/ui/render/scroll_render_object.hpp +++ /dev/null @@ -1,30 +0,0 @@ -#pragma once -#include "render_object.hpp" - -#include "cru/platform/graph/util/painter.hpp" - -namespace cru::ui::render { -class ScrollRenderObject : public RenderObject { - public: - ScrollRenderObject() : RenderObject(ChildMode::Single) {} - - CRU_DELETE_COPY(ScrollRenderObject) - CRU_DELETE_MOVE(ScrollRenderObject) - - ~ScrollRenderObject() override = default; - - void Draw(platform::graph::IPainter* painter) override; - - RenderObject* HitTest(const Point& point) override; - - Point GetScrollOffset() { return scroll_offset_; } - void SetScrollOffset(const Point& offset); - - protected: - void OnAddChild(RenderObject* new_child, Index position) override; - void OnRemoveChild(RenderObject* removed_child, Index position) override; - - private: - Point scroll_offset_; -}; -} // namespace cru::ui::render diff --git a/include/cru/ui/render/stack_layout_render_object.hpp b/include/cru/ui/render/stack_layout_render_object.hpp deleted file mode 100644 index c259b98d..00000000 --- a/include/cru/ui/render/stack_layout_render_object.hpp +++ /dev/null @@ -1,17 +0,0 @@ -#pragma once -#include "layout_render_object.hpp" - -namespace cru::ui::render { -class StackLayoutRenderObject - : public LayoutRenderObject { - public: - StackLayoutRenderObject() = default; - CRU_DELETE_COPY(StackLayoutRenderObject) - CRU_DELETE_MOVE(StackLayoutRenderObject) - ~StackLayoutRenderObject() = default; - - protected: - Size OnMeasureContent(const Size& available_size) override; - void OnLayoutContent(const Rect& content_rect) override; -}; -} // namespace cru::ui::render diff --git a/include/cru/ui/render/text_render_object.hpp b/include/cru/ui/render/text_render_object.hpp deleted file mode 100644 index 4b1e91e0..00000000 --- a/include/cru/ui/render/text_render_object.hpp +++ /dev/null @@ -1,85 +0,0 @@ -#pragma once -#include "render_object.hpp" - -#include - -namespace cru::ui::render { -class TextRenderObject : public RenderObject { - public: - constexpr static float default_caret_width = 2; - - public: - TextRenderObject(std::shared_ptr brush, - std::shared_ptr font, - std::shared_ptr selection_brush, - std::shared_ptr caret_brush); - TextRenderObject(const TextRenderObject& other) = delete; - TextRenderObject(TextRenderObject&& other) = delete; - TextRenderObject& operator=(const TextRenderObject& other) = delete; - TextRenderObject& operator=(TextRenderObject&& other) = delete; - ~TextRenderObject() override; - - std::string GetText() const; - void SetText(std::string new_text); - - std::shared_ptr GetBrush() const { return brush_; } - void SetBrush(std::shared_ptr new_brush); - - std::shared_ptr GetFont() const; - void SetFont(std::shared_ptr font); - - std::vector TextRangeRect(const TextRange& text_range); - Point TextSinglePoint(gsl::index position, bool trailing); - platform::graph::TextHitTestResult TextHitTest(const Point& point); - - std::optional GetSelectionRange() const { - return selection_range_; - } - void SetSelectionRange(std::optional new_range); - - std::shared_ptr GetSelectionBrush() const { - return selection_brush_; - } - void SetSelectionBrush(std::shared_ptr new_brush); - - bool IsDrawCaret() const { return draw_caret_; } - void SetDrawCaret(bool draw_caret); - void ToggleDrawCaret() { SetDrawCaret(!IsDrawCaret()); } - - // Caret position can be any value. When it is negative, 0 is used. When it - // exceeds the size of the string, the size of the string is used. - gsl::index GetCaretPosition() const { return caret_position_; } - void SetCaretPosition(gsl::index position); - - std::shared_ptr GetCaretBrush() const { - return caret_brush_; - } - void GetCaretBrush(std::shared_ptr brush); - - float GetCaretWidth() const { return caret_width_; } - void SetCaretWidth(float width); - - void Draw(platform::graph::IPainter* painter) override; - - RenderObject* HitTest(const Point& point) override; - - protected: - Size OnMeasureContent(const Size& available_size) override; - void OnLayoutContent(const Rect& content_rect) override; - - void OnAfterLayout() override; - - private: - std::shared_ptr brush_; - std::shared_ptr font_; - std::unique_ptr text_layout_; - - std::optional selection_range_ = std::nullopt; - std::shared_ptr selection_brush_; - - bool draw_caret_ = false; - gsl::index caret_position_ = 0; - std::shared_ptr caret_brush_; - float caret_width_ = default_caret_width; -}; -} // namespace cru::ui::render diff --git a/include/cru/ui/render/window_render_object.hpp b/include/cru/ui/render/window_render_object.hpp deleted file mode 100644 index 76b17b82..00000000 --- a/include/cru/ui/render/window_render_object.hpp +++ /dev/null @@ -1,30 +0,0 @@ -#pragma once -#include "render_object.hpp" - -namespace cru::ui::render { -class WindowRenderObject : public RenderObject { - public: - WindowRenderObject(UiHost* host); - WindowRenderObject(const WindowRenderObject& other) = delete; - WindowRenderObject(WindowRenderObject&& other) = delete; - WindowRenderObject& operator=(const WindowRenderObject& other) = delete; - WindowRenderObject& operator=(WindowRenderObject&& other) = delete; - ~WindowRenderObject() override = default; - - void Draw(platform::graph::IPainter* painter) override; - - RenderObject* HitTest(const Point& point) override; - - protected: - Size OnMeasureContent(const Size& available_size) override; - void OnLayoutContent(const Rect& content_rect) override; - - private: - RenderObject* GetChild() const { - return GetChildren().empty() ? nullptr : GetChildren()[0]; - } - - private: - EventRevokerGuard after_layout_event_guard_; -}; -} // namespace cru::ui::render diff --git a/include/cru/ui/ui_event.hpp b/include/cru/ui/ui_event.hpp deleted file mode 100644 index a9d6028a..00000000 --- a/include/cru/ui/ui_event.hpp +++ /dev/null @@ -1,229 +0,0 @@ -#pragma once -#include "base.hpp" - -#include "cru/common/event.hpp" -#include "cru/platform/native/keyboard.hpp" - -#include -#include -#include -#include - -namespace cru::platform::graph { -struct IPainter; -} - -namespace cru::ui { -class Control; -} - -namespace cru::ui::event { -class UiEventArgs : public Object { - public: - UiEventArgs(Object* sender, Object* original_sender) - : sender_(sender), original_sender_(original_sender), handled_(false) {} - - UiEventArgs(const UiEventArgs& other) = default; - UiEventArgs(UiEventArgs&& other) = default; - UiEventArgs& operator=(const UiEventArgs& other) = default; - UiEventArgs& operator=(UiEventArgs&& other) = default; - ~UiEventArgs() override = default; - - Object* GetSender() const { return sender_; } - - Object* GetOriginalSender() const { return original_sender_; } - - bool IsHandled() const { return handled_; } - void SetHandled(const bool handled = true) { handled_ = handled; } - - private: - Object* sender_; - Object* original_sender_; - bool handled_; -}; - -// TEventArgs must not be a reference type. This class help add reference. -// EventArgs must be reference because the IsHandled property must be settable. -template -class RoutedEvent { - public: - static_assert(std::is_base_of_v, - "TEventArgs must be subclass of UiEventArgs."); - static_assert(!std::is_reference_v, - "TEventArgs must not be reference."); - - using EventArgs = TEventArgs; - - RoutedEvent() = default; - RoutedEvent(const RoutedEvent& other) = delete; - RoutedEvent(RoutedEvent&& other) = delete; - RoutedEvent& operator=(const RoutedEvent& other) = delete; - RoutedEvent& operator=(RoutedEvent&& other) = delete; - ~RoutedEvent() = default; - - IEvent* Direct() { return &direct_; } - - IEvent* Bubble() { return &bubble_; } - - IEvent* Tunnel() { return &tunnel_; } - - private: - Event direct_; - Event bubble_; - Event tunnel_; -}; - -class MouseEventArgs : public UiEventArgs { - public: - MouseEventArgs(Object* sender, Object* original_sender, - const std::optional& point = std::nullopt) - : UiEventArgs(sender, original_sender), point_(point) {} - MouseEventArgs(const MouseEventArgs& other) = default; - MouseEventArgs(MouseEventArgs&& other) = default; - MouseEventArgs& operator=(const MouseEventArgs& other) = default; - MouseEventArgs& operator=(MouseEventArgs&& other) = default; - ~MouseEventArgs() override = default; - - // This point is relative to window client lefttop. - Point GetPoint() const { return point_.value_or(Point{}); } - - private: - std::optional point_; -}; - -class MouseButtonEventArgs : public MouseEventArgs { - public: - MouseButtonEventArgs(Object* sender, Object* original_sender, - const Point& point, const MouseButton button, - platform::native::KeyModifier key_modifier) - : MouseEventArgs(sender, original_sender, point), - button_(button), - key_modifier_(key_modifier) {} - MouseButtonEventArgs(Object* sender, Object* original_sender, - const MouseButton button, - platform::native::KeyModifier key_modifier) - : MouseEventArgs(sender, original_sender), - button_(button), - key_modifier_(key_modifier) {} - MouseButtonEventArgs(const MouseButtonEventArgs& other) = default; - MouseButtonEventArgs(MouseButtonEventArgs&& other) = default; - MouseButtonEventArgs& operator=(const MouseButtonEventArgs& other) = default; - MouseButtonEventArgs& operator=(MouseButtonEventArgs&& other) = default; - ~MouseButtonEventArgs() override = default; - - MouseButton GetButton() const { return button_; } - platform::native::KeyModifier GetKeyModifier() const { return key_modifier_; } - - private: - MouseButton button_; - platform::native::KeyModifier key_modifier_; -}; - -class MouseWheelEventArgs : public MouseEventArgs { - public: - MouseWheelEventArgs(Object* sender, Object* original_sender, - const Point& point, const float delta) - : MouseEventArgs(sender, original_sender, point), delta_(delta) {} - MouseWheelEventArgs(const MouseWheelEventArgs& other) = default; - MouseWheelEventArgs(MouseWheelEventArgs&& other) = default; - MouseWheelEventArgs& operator=(const MouseWheelEventArgs& other) = default; - MouseWheelEventArgs& operator=(MouseWheelEventArgs&& other) = default; - ~MouseWheelEventArgs() override = default; - - float GetDelta() const { return delta_; } - - private: - float delta_; -}; - -class PaintEventArgs : public UiEventArgs { - public: - PaintEventArgs(Object* sender, Object* original_sender, - platform::graph::IPainter* painter) - : UiEventArgs(sender, original_sender), painter_(painter) {} - PaintEventArgs(const PaintEventArgs& other) = default; - PaintEventArgs(PaintEventArgs&& other) = default; - PaintEventArgs& operator=(const PaintEventArgs& other) = default; - PaintEventArgs& operator=(PaintEventArgs&& other) = default; - ~PaintEventArgs() = default; - - platform::graph::IPainter* GetPainter() const { return painter_; } - - private: - platform::graph::IPainter* painter_; -}; - -class FocusChangeEventArgs : public UiEventArgs { - public: - FocusChangeEventArgs(Object* sender, Object* original_sender, - const bool is_window = false) - : UiEventArgs(sender, original_sender), is_window_(is_window) {} - FocusChangeEventArgs(const FocusChangeEventArgs& other) = default; - FocusChangeEventArgs(FocusChangeEventArgs&& other) = default; - FocusChangeEventArgs& operator=(const FocusChangeEventArgs& other) = default; - FocusChangeEventArgs& operator=(FocusChangeEventArgs&& other) = default; - ~FocusChangeEventArgs() override = default; - - // Return whether the focus change is caused by the window-wide focus change. - bool IsWindow() const { return is_window_; } - - private: - bool is_window_; -}; - -/* -class ToggleEventArgs : public UiEventArgs { - public: - ToggleEventArgs(Object* sender, Object* original_sender, bool new_state) - : UiEventArgs(sender, original_sender), new_state_(new_state) {} - ToggleEventArgs(const ToggleEventArgs& other) = default; - ToggleEventArgs(ToggleEventArgs&& other) = default; - ToggleEventArgs& operator=(const ToggleEventArgs& other) = default; - ToggleEventArgs& operator=(ToggleEventArgs&& other) = default; - ~ToggleEventArgs() override = default; - - bool GetNewState() const { return new_state_; } - - private: - bool new_state_; -}; -*/ - -class KeyEventArgs : public UiEventArgs { - public: - KeyEventArgs(Object* sender, Object* original_sender, - platform::native::KeyCode key_code, - platform::native::KeyModifier key_modifier) - : UiEventArgs(sender, original_sender), - key_code_(key_code), - key_modifier_(key_modifier) {} - KeyEventArgs(const KeyEventArgs& other) = default; - KeyEventArgs(KeyEventArgs&& other) = default; - KeyEventArgs& operator=(const KeyEventArgs& other) = default; - KeyEventArgs& operator=(KeyEventArgs&& other) = default; - ~KeyEventArgs() override = default; - - platform::native::KeyCode GetKeyCode() const { return key_code_; } - platform::native::KeyModifier GetKeyModifier() const { return key_modifier_; } - - private: - platform::native::KeyCode key_code_; - platform::native::KeyModifier key_modifier_; -}; - -class CharEventArgs : public UiEventArgs { - public: - CharEventArgs(Object* sender, Object* original_sender, std::string c) - : UiEventArgs(sender, original_sender), c_(std::move(c)) {} - CharEventArgs(const CharEventArgs& other) = default; - CharEventArgs(CharEventArgs&& other) = default; - CharEventArgs& operator=(const CharEventArgs& other) = default; - CharEventArgs& operator=(CharEventArgs&& other) = default; - ~CharEventArgs() override = default; - - std::string GetChar() const { return c_; } - - private: - std::string c_; -}; -} // namespace cru::ui::event diff --git a/include/cru/ui/ui_host.hpp b/include/cru/ui/ui_host.hpp deleted file mode 100644 index ca2b70a4..00000000 --- a/include/cru/ui/ui_host.hpp +++ /dev/null @@ -1,170 +0,0 @@ -#pragma once -#include "base.hpp" - -#include "cru/common/event.hpp" -#include "cru/common/self_resolvable.hpp" -#include "render/base.hpp" - -namespace cru::ui { -struct AfterLayoutEventArgs {}; - -// The host of all controls and render objects. -// -// 3 situations on destroy: -// 1. Native window destroyed, IsRetainAfterDestroy: false: -// OnNativeDestroy(set native_window_destroyed_ to true, call ~Window due to -// deleting_ is false and IsRetainAfterDestroy is false) -> ~Window -> -// ~UiHost(not destroy native window repeatedly due to native_window_destroyed_ -// is true) -// 2. Native window destroyed, IsRetainAfterDestroy: true: -// OnNativeDestroy(set native_window_destroyed_ to true, not call ~Window -// because deleting_ is false and IsRetainAfterDestroy is true) -// then, ~Window -> ~UiHost(not destroy native window repeatedly due to -// native_window_destroyed_ is true) -// 3. Native window not destroyed, ~Window is called: -// ~Window -> ~UiHost(set deleting_ to true, destroy native window -// due to native_window_destroyed is false) -> OnNativeDestroy(not call ~Window -// due to deleting_ is true and IsRetainAfterDestroy is whatever) -// In conclusion: -// 1. Set native_window_destroyed_ to true at the beginning of OnNativeDestroy. -// 2. Set deleting_ to true at the beginning of ~UiHost. -// 3. Destroy native window when native_window_destroy_ is false in ~Window. -// 4. Delete Window when deleting_ is false and IsRetainAfterDestroy is false in -// OnNativeDestroy. -class UiHost : public Object, public SelfResolvable { - public: - // This will create root window render object and attach it to window. - // It will also create and manage a native window. - UiHost(Window* window); - - CRU_DELETE_COPY(UiHost) - CRU_DELETE_MOVE(UiHost) - - ~UiHost() override; - - public: - // Mark the layout as invalid, and arrange a re-layout later. - // This method could be called more than one times in a message cycle. But - // layout only takes place once. - void InvalidateLayout(); - - // Mark the paint as invalid, and arrange a re-paint later. - // This method could be called more than one times in a message cycle. But - // paint only takes place once. - void InvalidatePaint(); - - IEvent* AfterLayoutEvent() { - return &after_layout_event_; - } - - void Relayout(); - - // Get current control that mouse hovers on. This ignores the mouse-capture - // control. Even when mouse is captured by another control, this function - // return the control under cursor. You can use `GetMouseCaptureControl` to - // get more info. - Control* GetMouseHoverControl() const { return mouse_hover_control_; } - - //*************** region: focus *************** - - // Request focus for specified control. - bool RequestFocusFor(Control* control); - - // Get the control that has focus. - Control* GetFocusControl(); - - //*************** region: focus *************** - - // Pass nullptr to release capture. If mouse is already capture by a control, - // this capture will fail and return false. If control is identical to the - // capturing control, capture is not changed and this function will return - // true. - // - // When capturing control changes, - // appropriate event will be sent. If mouse is not on the capturing control - // and capture is released, mouse enter event will be sent to the mouse-hover - // control. If mouse is not on the capturing control and capture is set, mouse - // leave event will be sent to the mouse-hover control. - bool CaptureMouseFor(Control* control); - - // Return null if not captured. - Control* GetMouseCaptureControl(); - - Control* HitTest(const Point& point); - - void UpdateCursor(); - - std::shared_ptr - GetNativeWindowResolver() { - return native_window_resolver_; - } - - bool IsRetainAfterDestroy() { return retain_after_destroy_; } - - void SetRetainAfterDestroy(bool destroy) { retain_after_destroy_ = destroy; } - - private: - //*************** region: native messages *************** - void OnNativeDestroy(platform::native::INativeWindow* window, std::nullptr_t); - void OnNativePaint(platform::native::INativeWindow* window, std::nullptr_t); - void OnNativeResize(platform::native::INativeWindow* window, - const Size& size); - - void OnNativeFocus(platform::native::INativeWindow* window, - cru::platform::native::FocusChangeType focus); - - void OnNativeMouseEnterLeave( - platform::native::INativeWindow* window, - cru::platform::native::MouseEnterLeaveType enter); - void OnNativeMouseMove(platform::native::INativeWindow* window, - const Point& point); - void OnNativeMouseDown( - platform::native::INativeWindow* window, - const platform::native::NativeMouseButtonEventArgs& args); - void OnNativeMouseUp( - platform::native::INativeWindow* window, - const platform::native::NativeMouseButtonEventArgs& args); - - void OnNativeKeyDown(platform::native::INativeWindow* window, - const platform::native::NativeKeyEventArgs& args); - void OnNativeKeyUp(platform::native::INativeWindow* window, - const platform::native::NativeKeyEventArgs& args); - - //*************** region: event dispatcher helper *************** - - void DispatchMouseHoverControlChangeEvent(Control* old_control, - Control* new_control, - const Point& point, bool no_leave, - bool no_enter); - - private: - bool need_layout_ = false; - - Event after_layout_event_; - - std::shared_ptr - native_window_resolver_; - - // See remarks of UiHost. - bool retain_after_destroy_ = false; - // See remarks of UiHost. - bool deleting_ = false; - - // We need this because calling Resolve on resolver in handler of destroy - // event is bad and will always get the dying window. But we need to label the - // window as destroyed so the destructor will not destroy native window - // repeatedly. See remarks of UiHost. - bool native_window_destroyed_ = false; - - std::vector event_revoker_guards_; - - Window* window_control_; - std::unique_ptr root_render_object_; - - Control* mouse_hover_control_; - - Control* focus_control_; // "focus_control_" can't be nullptr - - Control* mouse_captured_control_; -}; -} // namespace cru::ui diff --git a/include/cru/ui/ui_manager.hpp b/include/cru/ui/ui_manager.hpp deleted file mode 100644 index 9ad184c4..00000000 --- a/include/cru/ui/ui_manager.hpp +++ /dev/null @@ -1,35 +0,0 @@ -#pragma once -#include "base.hpp" - -#include "controls/base.hpp" - -namespace cru::ui { -struct ThemeResources { - std::shared_ptr default_font; - std::shared_ptr text_brush; - std::shared_ptr text_selection_brush; - std::shared_ptr caret_brush; - controls::ButtonStyle button_style; - controls::TextBoxBorderStyle text_box_border_style; -}; - -class UiManager : public Object { - public: - static UiManager* GetInstance(); - - private: - UiManager(); - - public: - UiManager(const UiManager& other) = delete; - UiManager(UiManager&& other) = delete; - UiManager& operator=(const UiManager& other) = delete; - UiManager& operator=(UiManager&& other) = delete; - ~UiManager() override; - - ThemeResources* GetThemeResources() { return &theme_resource_; } - - private: - ThemeResources theme_resource_; -}; -} // namespace cru::ui diff --git a/include/cru/ui/window.hpp b/include/cru/ui/window.hpp index 2f5df4da..eb2ecfbb 100644 --- a/include/cru/ui/window.hpp +++ b/include/cru/ui/window.hpp @@ -1,5 +1,5 @@ #pragma once -#include "content_control.hpp" +#include "ContentControl.hpp" namespace cru::ui { class Window final : public ContentControl { diff --git a/include/cru/win/WinPreConfig.hpp b/include/cru/win/WinPreConfig.hpp new file mode 100644 index 00000000..1613da95 --- /dev/null +++ b/include/cru/win/WinPreConfig.hpp @@ -0,0 +1,16 @@ +#pragma once + +#include "cru/common/PreConfig.hpp" + +#define NOMINMAX +#define WIN32_LEAN_AND_MEAN +#include +#undef CreateWindow +#undef DrawText +#undef CreateFont + +#include +#include +#include +#include +#include diff --git a/include/cru/win/exception.hpp b/include/cru/win/exception.hpp index 95eb4079..234aea69 100644 --- a/include/cru/win/exception.hpp +++ b/include/cru/win/exception.hpp @@ -1,7 +1,7 @@ #pragma once -#include "win_pre_config.hpp" +#include "WinPreConfig.hpp" -#include "cru/platform/exception.hpp" +#include "cru/platform/Exception.hpp" #include #include diff --git a/include/cru/win/graph/direct/ComResource.hpp b/include/cru/win/graph/direct/ComResource.hpp new file mode 100644 index 00000000..2ac332cd --- /dev/null +++ b/include/cru/win/graph/direct/ComResource.hpp @@ -0,0 +1,11 @@ +#pragma once +#include "../../WinPreConfig.hpp" + +#include "cru/common/Base.hpp" + +namespace cru::platform::graph::win::direct { +template +struct IComResource : virtual Interface { + virtual TInterface* GetComInterface() const = 0; +}; +} // namespace cru::platform::graph::win::direct diff --git a/include/cru/win/graph/direct/ConvertUtil.hpp b/include/cru/win/graph/direct/ConvertUtil.hpp new file mode 100644 index 00000000..12a04c7b --- /dev/null +++ b/include/cru/win/graph/direct/ConvertUtil.hpp @@ -0,0 +1,107 @@ +#pragma once +#include "../../WinPreConfig.hpp" + +#include "cru/platform/graph/Base.hpp" + +namespace cru::platform::graph::win::direct { +inline D2D1_MATRIX_3X2_F Convert(const platform::Matrix& matrix) { + D2D1_MATRIX_3X2_F m; + m._11 = matrix.m11; + m._12 = matrix.m12; + m._21 = matrix.m21; + m._22 = matrix.m22; + m._31 = matrix.m31; + m._32 = matrix.m32; + return m; +} + +inline D2D1_COLOR_F Convert(const Color& color) { + return D2D1::ColorF(color.red / 255.0f, color.green / 255.0f, + color.blue / 255.0f, color.alpha / 255.0f); +} + +inline D2D1_POINT_2F Convert(const Point& point) { + return D2D1::Point2F(point.x, point.y); +} + +inline D2D1_RECT_F Convert(const Rect& rect) { + return D2D1::RectF(rect.left, rect.top, rect.left + rect.width, + rect.top + rect.height); +} + +inline D2D1_ROUNDED_RECT Convert(const RoundedRect& rounded_rect) { + return D2D1::RoundedRect(Convert(rounded_rect.rect), rounded_rect.radius_x, + rounded_rect.radius_y); +} + +inline D2D1_ELLIPSE Convert(const Ellipse& ellipse) { + return D2D1::Ellipse(Convert(ellipse.center), ellipse.radius_x, + ellipse.radius_y); +} + +inline platform::Matrix Convert(const D2D1_MATRIX_3X2_F& matrix) { + return platform::Matrix{matrix._11, matrix._12, matrix._21, + matrix._22, matrix._31, matrix._32}; +} + +inline Color Convert(const D2D1_COLOR_F& color) { + auto floor = [](float n) { return static_cast(n + 0.5f); }; + return Color{floor(color.r * 255.0f), floor(color.g * 255.0f), + floor(color.b * 255.0f), floor(color.a * 255.0f)}; +} + +inline Point Convert(const D2D1_POINT_2F& point) { + return Point(point.x, point.y); +} + +inline Rect Convert(const D2D1_RECT_F& rect) { + return Rect(rect.left, rect.top, rect.right - rect.left, + rect.bottom - rect.top); +} + +inline RoundedRect Convert(const D2D1_ROUNDED_RECT& rounded_rect) { + return RoundedRect(Convert(rounded_rect.rect), rounded_rect.radiusX, + rounded_rect.radiusY); +} + +inline Ellipse Convert(const D2D1_ELLIPSE& ellipse) { + return Ellipse(Convert(ellipse.point), ellipse.radiusX, ellipse.radiusY); +} + +inline bool operator==(const D2D1_POINT_2F& left, const D2D1_POINT_2F& right) { + return left.x == right.x && left.y == right.y; +} + +inline bool operator!=(const D2D1_POINT_2F& left, const D2D1_POINT_2F& right) { + return !(left == right); +} + +inline bool operator==(const D2D1_RECT_F& left, const D2D1_RECT_F& right) { + return left.left == right.left && left.top == right.top && + left.right == right.right && left.bottom == right.bottom; +} + +inline bool operator!=(const D2D1_RECT_F& left, const D2D1_RECT_F& right) { + return !(left == right); +} + +inline bool operator==(const D2D1_ROUNDED_RECT& left, + const D2D1_ROUNDED_RECT& right) { + return left.rect == right.rect && left.radiusX == right.radiusX && + left.radiusY == right.radiusY; +} + +inline bool operator!=(const D2D1_ROUNDED_RECT& left, + const D2D1_ROUNDED_RECT& right) { + return !(left == right); +} + +inline bool operator==(const D2D1_ELLIPSE& left, const D2D1_ELLIPSE& right) { + return left.point == right.point && left.radiusX == right.radiusX && + left.radiusY == right.radiusY; +} + +inline bool operator!=(const D2D1_ELLIPSE& left, const D2D1_ELLIPSE& right) { + return !(left == right); +} +} // namespace cru::platform::graph::win::direct diff --git a/include/cru/win/graph/direct/TextLayout.hpp b/include/cru/win/graph/direct/TextLayout.hpp new file mode 100644 index 00000000..1a378ed4 --- /dev/null +++ b/include/cru/win/graph/direct/TextLayout.hpp @@ -0,0 +1,54 @@ +#pragma once +#include "ComResource.hpp" +#include "Resource.hpp" + +#include "cru/platform/graph/TextLayout.hpp" + +#include + +namespace cru::platform::graph::win::direct { +class DWriteFont; + +class DWriteTextLayout : public DirectGraphResource, + public virtual ITextLayout, + public virtual IComResource { + public: + DWriteTextLayout(DirectGraphFactory* factory, std::shared_ptr font, + std::string text); + + CRU_DELETE_COPY(DWriteTextLayout) + CRU_DELETE_MOVE(DWriteTextLayout) + + ~DWriteTextLayout() override; + + public: + IDWriteTextLayout* GetComInterface() const override { + return text_layout_.Get(); + } + + public: + std::string GetText() override; + void SetText(std::string new_text) override; + + std::shared_ptr GetFont() override; + void SetFont(std::shared_ptr font) override; + + void SetMaxWidth(float max_width) override; + void SetMaxHeight(float max_height) override; + + Rect GetTextBounds() override; + // Return empty vector if text_range.count is 0. Text range could be in + // reverse direction, it should be normalized first in implementation. + std::vector TextRangeRect(const TextRange& text_range) override; + Point TextSinglePoint(gsl::index position, bool trailing) override; + TextHitTestResult HitTest(const Point& point) override; + + private: + std::string text_; + std::wstring w_text_; + std::shared_ptr font_; + float max_width_ = 10000.0f; + float max_height_ = 10000.0f; + Microsoft::WRL::ComPtr text_layout_; +}; +} // namespace cru::platform::graph::win::direct diff --git a/include/cru/win/graph/direct/brush.hpp b/include/cru/win/graph/direct/brush.hpp index 0726ec3f..df1debe3 100644 --- a/include/cru/win/graph/direct/brush.hpp +++ b/include/cru/win/graph/direct/brush.hpp @@ -1,8 +1,8 @@ #pragma once -#include "com_resource.hpp" -#include "resource.hpp" +#include "ComResource.hpp" +#include "Resource.hpp" -#include "cru/platform/graph/brush.hpp" +#include "cru/platform/graph/Brush.hpp" namespace cru::platform::graph::win::direct { struct ID2DBrush : virtual IBrush { diff --git a/include/cru/win/graph/direct/com_resource.hpp b/include/cru/win/graph/direct/com_resource.hpp deleted file mode 100644 index a6c83434..00000000 --- a/include/cru/win/graph/direct/com_resource.hpp +++ /dev/null @@ -1,11 +0,0 @@ -#pragma once -#include "../../win_pre_config.hpp" - -#include "cru/common/base.hpp" - -namespace cru::platform::graph::win::direct { -template -struct IComResource : virtual Interface { - virtual TInterface* GetComInterface() const = 0; -}; -} // namespace cru::platform::graph::win::direct diff --git a/include/cru/win/graph/direct/convert_util.hpp b/include/cru/win/graph/direct/convert_util.hpp deleted file mode 100644 index ef343a3f..00000000 --- a/include/cru/win/graph/direct/convert_util.hpp +++ /dev/null @@ -1,107 +0,0 @@ -#pragma once -#include "../../win_pre_config.hpp" - -#include "cru/platform/graph/base.hpp" - -namespace cru::platform::graph::win::direct { -inline D2D1_MATRIX_3X2_F Convert(const platform::Matrix& matrix) { - D2D1_MATRIX_3X2_F m; - m._11 = matrix.m11; - m._12 = matrix.m12; - m._21 = matrix.m21; - m._22 = matrix.m22; - m._31 = matrix.m31; - m._32 = matrix.m32; - return m; -} - -inline D2D1_COLOR_F Convert(const Color& color) { - return D2D1::ColorF(color.red / 255.0f, color.green / 255.0f, - color.blue / 255.0f, color.alpha / 255.0f); -} - -inline D2D1_POINT_2F Convert(const Point& point) { - return D2D1::Point2F(point.x, point.y); -} - -inline D2D1_RECT_F Convert(const Rect& rect) { - return D2D1::RectF(rect.left, rect.top, rect.left + rect.width, - rect.top + rect.height); -} - -inline D2D1_ROUNDED_RECT Convert(const RoundedRect& rounded_rect) { - return D2D1::RoundedRect(Convert(rounded_rect.rect), rounded_rect.radius_x, - rounded_rect.radius_y); -} - -inline D2D1_ELLIPSE Convert(const Ellipse& ellipse) { - return D2D1::Ellipse(Convert(ellipse.center), ellipse.radius_x, - ellipse.radius_y); -} - -inline platform::Matrix Convert(const D2D1_MATRIX_3X2_F& matrix) { - return platform::Matrix{matrix._11, matrix._12, matrix._21, - matrix._22, matrix._31, matrix._32}; -} - -inline Color Convert(const D2D1_COLOR_F& color) { - auto floor = [](float n) { return static_cast(n + 0.5f); }; - return Color{floor(color.r * 255.0f), floor(color.g * 255.0f), - floor(color.b * 255.0f), floor(color.a * 255.0f)}; -} - -inline Point Convert(const D2D1_POINT_2F& point) { - return Point(point.x, point.y); -} - -inline Rect Convert(const D2D1_RECT_F& rect) { - return Rect(rect.left, rect.top, rect.right - rect.left, - rect.bottom - rect.top); -} - -inline RoundedRect Convert(const D2D1_ROUNDED_RECT& rounded_rect) { - return RoundedRect(Convert(rounded_rect.rect), rounded_rect.radiusX, - rounded_rect.radiusY); -} - -inline Ellipse Convert(const D2D1_ELLIPSE& ellipse) { - return Ellipse(Convert(ellipse.point), ellipse.radiusX, ellipse.radiusY); -} - -inline bool operator==(const D2D1_POINT_2F& left, const D2D1_POINT_2F& right) { - return left.x == right.x && left.y == right.y; -} - -inline bool operator!=(const D2D1_POINT_2F& left, const D2D1_POINT_2F& right) { - return !(left == right); -} - -inline bool operator==(const D2D1_RECT_F& left, const D2D1_RECT_F& right) { - return left.left == right.left && left.top == right.top && - left.right == right.right && left.bottom == right.bottom; -} - -inline bool operator!=(const D2D1_RECT_F& left, const D2D1_RECT_F& right) { - return !(left == right); -} - -inline bool operator==(const D2D1_ROUNDED_RECT& left, - const D2D1_ROUNDED_RECT& right) { - return left.rect == right.rect && left.radiusX == right.radiusX && - left.radiusY == right.radiusY; -} - -inline bool operator!=(const D2D1_ROUNDED_RECT& left, - const D2D1_ROUNDED_RECT& right) { - return !(left == right); -} - -inline bool operator==(const D2D1_ELLIPSE& left, const D2D1_ELLIPSE& right) { - return left.point == right.point && left.radiusX == right.radiusX && - left.radiusY == right.radiusY; -} - -inline bool operator!=(const D2D1_ELLIPSE& left, const D2D1_ELLIPSE& right) { - return !(left == right); -} -} // namespace cru::platform::graph::win::direct diff --git a/include/cru/win/graph/direct/exception.hpp b/include/cru/win/graph/direct/exception.hpp index 8e955825..8b62e8fa 100644 --- a/include/cru/win/graph/direct/exception.hpp +++ b/include/cru/win/graph/direct/exception.hpp @@ -1,5 +1,5 @@ #pragma once -#include "../../exception.hpp" +#include "../../Exception.hpp" namespace cru::platform::graph::win::direct { using platform::win::HResultError; diff --git a/include/cru/win/graph/direct/factory.hpp b/include/cru/win/graph/direct/factory.hpp index cf5ccaee..763d4b2b 100644 --- a/include/cru/win/graph/direct/factory.hpp +++ b/include/cru/win/graph/direct/factory.hpp @@ -1,7 +1,7 @@ #pragma once -#include "resource.hpp" +#include "Resource.hpp" -#include "cru/platform/graph/factory.hpp" +#include "cru/platform/graph/Factory.hpp" namespace cru::platform::graph::win::direct { class DirectGraphFactory : public DirectResource, public virtual IGraphFactory { diff --git a/include/cru/win/graph/direct/font.hpp b/include/cru/win/graph/direct/font.hpp index ee2e319b..ecf9fd81 100644 --- a/include/cru/win/graph/direct/font.hpp +++ b/include/cru/win/graph/direct/font.hpp @@ -1,8 +1,8 @@ #pragma once -#include "com_resource.hpp" -#include "resource.hpp" +#include "ComResource.hpp" +#include "Resource.hpp" -#include "cru/platform/graph/font.hpp" +#include "cru/platform/graph/Font.hpp" #include diff --git a/include/cru/win/graph/direct/geometry.hpp b/include/cru/win/graph/direct/geometry.hpp index 086b31cc..87987d3e 100644 --- a/include/cru/win/graph/direct/geometry.hpp +++ b/include/cru/win/graph/direct/geometry.hpp @@ -1,8 +1,8 @@ #pragma once -#include "com_resource.hpp" -#include "resource.hpp" +#include "ComResource.hpp" +#include "Resource.hpp" -#include "cru/platform/graph/geometry.hpp" +#include "cru/platform/graph/Geometry.hpp" namespace cru::platform::graph::win::direct { class D2DGeometryBuilder : public DirectGraphResource, diff --git a/include/cru/win/graph/direct/painter.hpp b/include/cru/win/graph/direct/painter.hpp index 4f2164c9..a50f962d 100644 --- a/include/cru/win/graph/direct/painter.hpp +++ b/include/cru/win/graph/direct/painter.hpp @@ -1,8 +1,8 @@ #pragma once -#include "com_resource.hpp" -#include "resource.hpp" +#include "ComResource.hpp" +#include "Resource.hpp" -#include "cru/platform/graph/painter.hpp" +#include "cru/platform/graph/Painter.hpp" #include diff --git a/include/cru/win/graph/direct/resource.hpp b/include/cru/win/graph/direct/resource.hpp index 94a91a40..d0a30dbd 100644 --- a/include/cru/win/graph/direct/resource.hpp +++ b/include/cru/win/graph/direct/resource.hpp @@ -1,7 +1,7 @@ #pragma once -#include "../../win_pre_config.hpp" +#include "../../WinPreConfig.hpp" -#include "cru/platform/graph/resource.hpp" +#include "cru/platform/graph/Resource.hpp" #include diff --git a/include/cru/win/graph/direct/text_layout.hpp b/include/cru/win/graph/direct/text_layout.hpp deleted file mode 100644 index 2870db96..00000000 --- a/include/cru/win/graph/direct/text_layout.hpp +++ /dev/null @@ -1,54 +0,0 @@ -#pragma once -#include "com_resource.hpp" -#include "resource.hpp" - -#include "cru/platform/graph/text_layout.hpp" - -#include - -namespace cru::platform::graph::win::direct { -class DWriteFont; - -class DWriteTextLayout : public DirectGraphResource, - public virtual ITextLayout, - public virtual IComResource { - public: - DWriteTextLayout(DirectGraphFactory* factory, std::shared_ptr font, - std::string text); - - CRU_DELETE_COPY(DWriteTextLayout) - CRU_DELETE_MOVE(DWriteTextLayout) - - ~DWriteTextLayout() override; - - public: - IDWriteTextLayout* GetComInterface() const override { - return text_layout_.Get(); - } - - public: - std::string GetText() override; - void SetText(std::string new_text) override; - - std::shared_ptr GetFont() override; - void SetFont(std::shared_ptr font) override; - - void SetMaxWidth(float max_width) override; - void SetMaxHeight(float max_height) override; - - Rect GetTextBounds() override; - // Return empty vector if text_range.count is 0. Text range could be in - // reverse direction, it should be normalized first in implementation. - std::vector TextRangeRect(const TextRange& text_range) override; - Point TextSinglePoint(gsl::index position, bool trailing) override; - TextHitTestResult HitTest(const Point& point) override; - - private: - std::string text_; - std::wstring w_text_; - std::shared_ptr font_; - float max_width_ = 10000.0f; - float max_height_ = 10000.0f; - Microsoft::WRL::ComPtr text_layout_; -}; -} // namespace cru::platform::graph::win::direct diff --git a/include/cru/win/native/GodWindow.hpp b/include/cru/win/native/GodWindow.hpp new file mode 100644 index 00000000..1dd99661 --- /dev/null +++ b/include/cru/win/native/GodWindow.hpp @@ -0,0 +1,27 @@ +#pragma once +#include "Base.hpp" + +#include + +namespace cru::platform::native::win { +class GodWindow : public Object { + public: + explicit GodWindow(WinUiApplication* application); + + CRU_DELETE_COPY(GodWindow) + CRU_DELETE_MOVE(GodWindow) + + ~GodWindow() override; + + HWND GetHandle() const { return hwnd_; } + + bool HandleGodWindowMessage(HWND hwnd, UINT msg, WPARAM w_param, + LPARAM l_param, LRESULT* result); + + private: + WinUiApplication* application_; + + std::unique_ptr god_window_class_; + HWND hwnd_; +}; +} // namespace cru::platform::native::win diff --git a/include/cru/win/native/InputMethod.hpp b/include/cru/win/native/InputMethod.hpp new file mode 100644 index 00000000..0e9634aa --- /dev/null +++ b/include/cru/win/native/InputMethod.hpp @@ -0,0 +1,98 @@ +// Some useful information can be found from chromium code: +// https://chromium.googlesource.com/chromium/chromium/+/refs/heads/master/ui/base/win/ime_input.h +// https://chromium.googlesource.com/chromium/chromium/+/refs/heads/master/ui/base/win/ime_input.cc + +#pragma once +#include "Resource.hpp" + +#include "cru/platform/native/InputMethod.hpp" +#include "WindowNativeMessageEventArgs.hpp" + +#include + +namespace cru::platform::native::win { +class AutoHIMC : public Object { + public: + explicit AutoHIMC(HWND hwnd); + + CRU_DELETE_COPY(AutoHIMC) + + AutoHIMC(AutoHIMC&& other); + AutoHIMC& operator=(AutoHIMC&& other); + + ~AutoHIMC() override; + + HWND GetHwnd() const { return hwnd_; } + + HIMC Get() const { return handle_; } + + private: + HWND hwnd_; + HIMC handle_; +}; + +class WinInputMethodContext : public WinNativeResource, + public virtual IInputMethodContext { + public: + WinInputMethodContext(gsl::not_null window); + + CRU_DELETE_COPY(WinInputMethodContext) + CRU_DELETE_MOVE(WinInputMethodContext) + + ~WinInputMethodContext() override; + + bool ShouldManuallyDrawCompositionText() override { return true; } + + void EnableIME() override; + + void DisableIME() override; + + void CompleteComposition() override; + + void CancelComposition() override; + + CompositionText GetCompositionText() override; + + void SetCandidateWindowPosition(const Point& point) override; + + IEvent* CompositionStartEvent() override; + + IEvent* CompositionEndEvent() override; + + IEvent* CompositionEvent() override; + + IEvent* TextEvent() override; + + private: + void OnWindowNativeMessage(WindowNativeMessageEventArgs& args); + + std::string GetResultString(); + + std::optional TryGetHIMC(); + + private: + std::shared_ptr native_window_resolver_; + + std::vector event_revoker_guards_; + + Event composition_start_event_; + Event composition_end_event_; + Event composition_event_; + Event text_event_; +}; + +class WinInputMethodManager : public WinNativeResource, + public virtual IInputMethodManager { + public: + WinInputMethodManager(WinUiApplication* application); + + CRU_DELETE_COPY(WinInputMethodManager) + CRU_DELETE_MOVE(WinInputMethodManager) + + ~WinInputMethodManager() override; + + public: + std::unique_ptr GetContext( + INativeWindow* window) override; +}; +} // namespace cru::platform::native::win diff --git a/include/cru/win/native/UiApplication.hpp b/include/cru/win/native/UiApplication.hpp new file mode 100644 index 00000000..cbc08af7 --- /dev/null +++ b/include/cru/win/native/UiApplication.hpp @@ -0,0 +1,76 @@ +#pragma once +#include "Resource.hpp" + +#include "cru/platform/native/UiApplication.hpp" + +#include + +namespace cru::platform::graph::win::direct { +class DirectGraphFactory; +} + +namespace cru::platform::native::win { +class WinUiApplication : public WinNativeResource, + public virtual IUiApplication { + public: + static WinUiApplication* GetInstance() { return instance; } + + private: + static WinUiApplication* instance; + + public: + WinUiApplication(); + + CRU_DELETE_COPY(WinUiApplication) + CRU_DELETE_MOVE(WinUiApplication) + + ~WinUiApplication() override; + + public: + int Run() override; + void RequestQuit(int quit_code) override; + + void AddOnQuitHandler(std::function handler) override; + + void InvokeLater(std::function action) override; + long long SetTimeout(std::chrono::milliseconds milliseconds, + std::function action) override; + long long SetInterval(std::chrono::milliseconds milliseconds, + std::function action) override; + void CancelTimer(long long id) override; + + std::vector GetAllWindow() override; + std::shared_ptr CreateWindow( + INativeWindow* parent) override; + + cru::platform::graph::IGraphFactory* GetGraphFactory() override; + + cru::platform::graph::win::direct::DirectGraphFactory* GetDirectFactory() { + return graph_factory_.get(); + } + + ICursorManager* GetCursorManager() override; + IInputMethodManager* GetInputMethodManager() override; + + HINSTANCE GetInstanceHandle() const { return instance_handle_; } + + GodWindow* GetGodWindow() const { return god_window_.get(); } + TimerManager* GetTimerManager() const { return timer_manager_.get(); } + WindowManager* GetWindowManager() const { return window_manager_.get(); } + + private: + HINSTANCE instance_handle_; + + std::unique_ptr + graph_factory_; + + std::unique_ptr god_window_; + std::unique_ptr timer_manager_; + std::unique_ptr window_manager_; + + std::unique_ptr cursor_manager_; + std::unique_ptr input_method_manager_; + + std::vector> quit_handlers_; +}; +} // namespace cru::platform::native::win diff --git a/include/cru/win/native/WindowClass.hpp b/include/cru/win/native/WindowClass.hpp new file mode 100644 index 00000000..fdd55065 --- /dev/null +++ b/include/cru/win/native/WindowClass.hpp @@ -0,0 +1,24 @@ +#pragma once +#include "Base.hpp" + +#include + +namespace cru::platform::native::win { +class WindowClass : public Object { + public: + WindowClass(std::wstring name, WNDPROC window_proc, HINSTANCE h_instance); + + CRU_DELETE_COPY(WindowClass) + CRU_DELETE_MOVE(WindowClass) + + ~WindowClass() override = default; + + const wchar_t* GetName() const { return name_.c_str(); } + + ATOM GetAtom() const { return atom_; } + + private: + std::wstring name_; + ATOM atom_; +}; +} // namespace cru::platform::native::win diff --git a/include/cru/win/native/WindowNativeMessageEventArgs.hpp b/include/cru/win/native/WindowNativeMessageEventArgs.hpp new file mode 100644 index 00000000..84a7a123 --- /dev/null +++ b/include/cru/win/native/WindowNativeMessageEventArgs.hpp @@ -0,0 +1,40 @@ +#pragma once +#include "../WinPreConfig.hpp" + +#include "cru/common/Base.hpp" + +namespace cru::platform::native::win { +struct WindowNativeMessage { + HWND hwnd; + UINT msg; + WPARAM w_param; + LPARAM l_param; +}; + +class WindowNativeMessageEventArgs : public Object { + public: + WindowNativeMessageEventArgs(const WindowNativeMessage& message) + : message_(message) {} + CRU_DEFAULT_COPY(WindowNativeMessageEventArgs) + CRU_DEFAULT_MOVE(WindowNativeMessageEventArgs) + ~WindowNativeMessageEventArgs() override = default; + + const WindowNativeMessage& GetWindowMessage() const { return message_; } + + LRESULT GetResult() const { return result_; } + void SetResult(LRESULT result) { result_ = result; } + + bool IsHandled() const { return handled_; } + void SetHandled(bool handled) { handled_ = handled; } + + void HandleWithResult(LRESULT result) { + handled_ = true; + result_ = result; + } + + private: + WindowNativeMessage message_; + LRESULT result_; + bool handled_ = false; +}; +} // namespace cru::platform::native::win diff --git a/include/cru/win/native/WindowRenderTarget.hpp b/include/cru/win/native/WindowRenderTarget.hpp new file mode 100644 index 00000000..83ac1e03 --- /dev/null +++ b/include/cru/win/native/WindowRenderTarget.hpp @@ -0,0 +1,47 @@ +#pragma once +#include "Base.hpp" + +namespace cru::platform::graph::win::direct { +class DirectGraphFactory; +} + +namespace cru::platform::native::win { +// Represents a window render target. +class WindowRenderTarget : public Object { + public: + WindowRenderTarget(graph::win::direct::DirectGraphFactory* factory, + HWND hwnd); + + CRU_DELETE_COPY(WindowRenderTarget) + CRU_DELETE_MOVE(WindowRenderTarget) + + ~WindowRenderTarget() override = default; + + public: + graph::win::direct::DirectGraphFactory* GetDirectFactory() const { + return factory_; + } + + ID2D1DeviceContext* GetD2D1DeviceContext() { + return d2d1_device_context_.Get(); + } + + // Resize the underlying buffer. + void ResizeBuffer(int width, int height); + + // Set this render target as the d2d device context's target. + void SetAsTarget(); + + // Present the data of the underlying buffer to the window. + void Present(); + + private: + void CreateTargetBitmap(); + + private: + graph::win::direct::DirectGraphFactory* factory_; + Microsoft::WRL::ComPtr d2d1_device_context_; + Microsoft::WRL::ComPtr dxgi_swap_chain_; + Microsoft::WRL::ComPtr target_bitmap_; +}; +} // namespace cru::platform::native::win diff --git a/include/cru/win/native/base.hpp b/include/cru/win/native/base.hpp index 61c9f4da..a50c6dd1 100644 --- a/include/cru/win/native/base.hpp +++ b/include/cru/win/native/base.hpp @@ -1,7 +1,7 @@ #pragma once -#include "../win_pre_config.hpp" +#include "../WinPreConfig.hpp" -#include "cru/common/base.hpp" +#include "cru/common/Base.hpp" namespace cru::platform::native::win { class GodWindow; diff --git a/include/cru/win/native/cursor.hpp b/include/cru/win/native/cursor.hpp index e5728b1c..152374d8 100644 --- a/include/cru/win/native/cursor.hpp +++ b/include/cru/win/native/cursor.hpp @@ -1,7 +1,7 @@ #pragma once -#include "resource.hpp" +#include "Resource.hpp" -#include "cru/platform/native/cursor.hpp" +#include "cru/platform/native/Cursor.hpp" #include diff --git a/include/cru/win/native/exception.hpp b/include/cru/win/native/exception.hpp index ac15ab2d..6a5265c1 100644 --- a/include/cru/win/native/exception.hpp +++ b/include/cru/win/native/exception.hpp @@ -1,5 +1,5 @@ #pragma once -#include "../exception.hpp" +#include "../Exception.hpp" namespace cru::platform::native::win { using platform::win::Win32Error; diff --git a/include/cru/win/native/god_window.hpp b/include/cru/win/native/god_window.hpp deleted file mode 100644 index 33218a55..00000000 --- a/include/cru/win/native/god_window.hpp +++ /dev/null @@ -1,27 +0,0 @@ -#pragma once -#include "base.hpp" - -#include - -namespace cru::platform::native::win { -class GodWindow : public Object { - public: - explicit GodWindow(WinUiApplication* application); - - CRU_DELETE_COPY(GodWindow) - CRU_DELETE_MOVE(GodWindow) - - ~GodWindow() override; - - HWND GetHandle() const { return hwnd_; } - - bool HandleGodWindowMessage(HWND hwnd, UINT msg, WPARAM w_param, - LPARAM l_param, LRESULT* result); - - private: - WinUiApplication* application_; - - std::unique_ptr god_window_class_; - HWND hwnd_; -}; -} // namespace cru::platform::native::win diff --git a/include/cru/win/native/input_method.hpp b/include/cru/win/native/input_method.hpp deleted file mode 100644 index ff867f3f..00000000 --- a/include/cru/win/native/input_method.hpp +++ /dev/null @@ -1,98 +0,0 @@ -// Some useful information can be found from chromium code: -// https://chromium.googlesource.com/chromium/chromium/+/refs/heads/master/ui/base/win/ime_input.h -// https://chromium.googlesource.com/chromium/chromium/+/refs/heads/master/ui/base/win/ime_input.cc - -#pragma once -#include "resource.hpp" - -#include "cru/platform/native/input_method.hpp" -#include "window_native_message_event_args.hpp" - -#include - -namespace cru::platform::native::win { -class AutoHIMC : public Object { - public: - explicit AutoHIMC(HWND hwnd); - - CRU_DELETE_COPY(AutoHIMC) - - AutoHIMC(AutoHIMC&& other); - AutoHIMC& operator=(AutoHIMC&& other); - - ~AutoHIMC() override; - - HWND GetHwnd() const { return hwnd_; } - - HIMC Get() const { return handle_; } - - private: - HWND hwnd_; - HIMC handle_; -}; - -class WinInputMethodContext : public WinNativeResource, - public virtual IInputMethodContext { - public: - WinInputMethodContext(gsl::not_null window); - - CRU_DELETE_COPY(WinInputMethodContext) - CRU_DELETE_MOVE(WinInputMethodContext) - - ~WinInputMethodContext() override; - - bool ShouldManuallyDrawCompositionText() override { return true; } - - void EnableIME() override; - - void DisableIME() override; - - void CompleteComposition() override; - - void CancelComposition() override; - - CompositionText GetCompositionText() override; - - void SetCandidateWindowPosition(const Point& point) override; - - IEvent* CompositionStartEvent() override; - - IEvent* CompositionEndEvent() override; - - IEvent* CompositionEvent() override; - - IEvent* TextEvent() override; - - private: - void OnWindowNativeMessage(WindowNativeMessageEventArgs& args); - - std::string GetResultString(); - - std::optional TryGetHIMC(); - - private: - std::shared_ptr native_window_resolver_; - - std::vector event_revoker_guards_; - - Event composition_start_event_; - Event composition_end_event_; - Event composition_event_; - Event text_event_; -}; - -class WinInputMethodManager : public WinNativeResource, - public virtual IInputMethodManager { - public: - WinInputMethodManager(WinUiApplication* application); - - CRU_DELETE_COPY(WinInputMethodManager) - CRU_DELETE_MOVE(WinInputMethodManager) - - ~WinInputMethodManager() override; - - public: - std::unique_ptr GetContext( - INativeWindow* window) override; -}; -} // namespace cru::platform::native::win diff --git a/include/cru/win/native/keyboard.hpp b/include/cru/win/native/keyboard.hpp index afa51c92..790e0015 100644 --- a/include/cru/win/native/keyboard.hpp +++ b/include/cru/win/native/keyboard.hpp @@ -1,7 +1,7 @@ #pragma once -#include "base.hpp" +#include "Base.hpp" -#include "cru/platform/native/keyboard.hpp" +#include "cru/platform/native/Keyboard.hpp" namespace cru::platform::native::win { KeyCode VirtualKeyToKeyCode(int virtual_key); diff --git a/include/cru/win/native/resource.hpp b/include/cru/win/native/resource.hpp index 5601e40e..7afaca0f 100644 --- a/include/cru/win/native/resource.hpp +++ b/include/cru/win/native/resource.hpp @@ -1,7 +1,7 @@ #pragma once -#include "base.hpp" +#include "Base.hpp" -#include "cru/platform/resource.hpp" +#include "cru/platform/Resource.hpp" namespace cru::platform::native::win { class WinNativeResource : public Object, public virtual INativeResource { diff --git a/include/cru/win/native/ui_application.hpp b/include/cru/win/native/ui_application.hpp deleted file mode 100644 index 250e855c..00000000 --- a/include/cru/win/native/ui_application.hpp +++ /dev/null @@ -1,76 +0,0 @@ -#pragma once -#include "resource.hpp" - -#include "cru/platform/native/ui_application.hpp" - -#include - -namespace cru::platform::graph::win::direct { -class DirectGraphFactory; -} - -namespace cru::platform::native::win { -class WinUiApplication : public WinNativeResource, - public virtual IUiApplication { - public: - static WinUiApplication* GetInstance() { return instance; } - - private: - static WinUiApplication* instance; - - public: - WinUiApplication(); - - CRU_DELETE_COPY(WinUiApplication) - CRU_DELETE_MOVE(WinUiApplication) - - ~WinUiApplication() override; - - public: - int Run() override; - void RequestQuit(int quit_code) override; - - void AddOnQuitHandler(std::function handler) override; - - void InvokeLater(std::function action) override; - long long SetTimeout(std::chrono::milliseconds milliseconds, - std::function action) override; - long long SetInterval(std::chrono::milliseconds milliseconds, - std::function action) override; - void CancelTimer(long long id) override; - - std::vector GetAllWindow() override; - std::shared_ptr CreateWindow( - INativeWindow* parent) override; - - cru::platform::graph::IGraphFactory* GetGraphFactory() override; - - cru::platform::graph::win::direct::DirectGraphFactory* GetDirectFactory() { - return graph_factory_.get(); - } - - ICursorManager* GetCursorManager() override; - IInputMethodManager* GetInputMethodManager() override; - - HINSTANCE GetInstanceHandle() const { return instance_handle_; } - - GodWindow* GetGodWindow() const { return god_window_.get(); } - TimerManager* GetTimerManager() const { return timer_manager_.get(); } - WindowManager* GetWindowManager() const { return window_manager_.get(); } - - private: - HINSTANCE instance_handle_; - - std::unique_ptr - graph_factory_; - - std::unique_ptr god_window_; - std::unique_ptr timer_manager_; - std::unique_ptr window_manager_; - - std::unique_ptr cursor_manager_; - std::unique_ptr input_method_manager_; - - std::vector> quit_handlers_; -}; -} // namespace cru::platform::native::win diff --git a/include/cru/win/native/window.hpp b/include/cru/win/native/window.hpp index 59b38ab5..80bee39e 100644 --- a/include/cru/win/native/window.hpp +++ b/include/cru/win/native/window.hpp @@ -1,8 +1,8 @@ #pragma once -#include "resource.hpp" +#include "Resource.hpp" -#include "cru/platform/native/window.hpp" -#include "window_native_message_event_args.hpp" +#include "cru/platform/native/Window.hpp" +#include "WindowNativeMessageEventArgs.hpp" #include diff --git a/include/cru/win/native/window_class.hpp b/include/cru/win/native/window_class.hpp deleted file mode 100644 index 2140c304..00000000 --- a/include/cru/win/native/window_class.hpp +++ /dev/null @@ -1,24 +0,0 @@ -#pragma once -#include "base.hpp" - -#include - -namespace cru::platform::native::win { -class WindowClass : public Object { - public: - WindowClass(std::wstring name, WNDPROC window_proc, HINSTANCE h_instance); - - CRU_DELETE_COPY(WindowClass) - CRU_DELETE_MOVE(WindowClass) - - ~WindowClass() override = default; - - const wchar_t* GetName() const { return name_.c_str(); } - - ATOM GetAtom() const { return atom_; } - - private: - std::wstring name_; - ATOM atom_; -}; -} // namespace cru::platform::native::win diff --git a/include/cru/win/native/window_native_message_event_args.hpp b/include/cru/win/native/window_native_message_event_args.hpp deleted file mode 100644 index f5476735..00000000 --- a/include/cru/win/native/window_native_message_event_args.hpp +++ /dev/null @@ -1,40 +0,0 @@ -#pragma once -#include "../win_pre_config.hpp" - -#include "cru/common/base.hpp" - -namespace cru::platform::native::win { -struct WindowNativeMessage { - HWND hwnd; - UINT msg; - WPARAM w_param; - LPARAM l_param; -}; - -class WindowNativeMessageEventArgs : public Object { - public: - WindowNativeMessageEventArgs(const WindowNativeMessage& message) - : message_(message) {} - CRU_DEFAULT_COPY(WindowNativeMessageEventArgs) - CRU_DEFAULT_MOVE(WindowNativeMessageEventArgs) - ~WindowNativeMessageEventArgs() override = default; - - const WindowNativeMessage& GetWindowMessage() const { return message_; } - - LRESULT GetResult() const { return result_; } - void SetResult(LRESULT result) { result_ = result; } - - bool IsHandled() const { return handled_; } - void SetHandled(bool handled) { handled_ = handled; } - - void HandleWithResult(LRESULT result) { - handled_ = true; - result_ = result; - } - - private: - WindowNativeMessage message_; - LRESULT result_; - bool handled_ = false; -}; -} // namespace cru::platform::native::win diff --git a/include/cru/win/native/window_render_target.hpp b/include/cru/win/native/window_render_target.hpp deleted file mode 100644 index 552e87bc..00000000 --- a/include/cru/win/native/window_render_target.hpp +++ /dev/null @@ -1,47 +0,0 @@ -#pragma once -#include "base.hpp" - -namespace cru::platform::graph::win::direct { -class DirectGraphFactory; -} - -namespace cru::platform::native::win { -// Represents a window render target. -class WindowRenderTarget : public Object { - public: - WindowRenderTarget(graph::win::direct::DirectGraphFactory* factory, - HWND hwnd); - - CRU_DELETE_COPY(WindowRenderTarget) - CRU_DELETE_MOVE(WindowRenderTarget) - - ~WindowRenderTarget() override = default; - - public: - graph::win::direct::DirectGraphFactory* GetDirectFactory() const { - return factory_; - } - - ID2D1DeviceContext* GetD2D1DeviceContext() { - return d2d1_device_context_.Get(); - } - - // Resize the underlying buffer. - void ResizeBuffer(int width, int height); - - // Set this render target as the d2d device context's target. - void SetAsTarget(); - - // Present the data of the underlying buffer to the window. - void Present(); - - private: - void CreateTargetBitmap(); - - private: - graph::win::direct::DirectGraphFactory* factory_; - Microsoft::WRL::ComPtr d2d1_device_context_; - Microsoft::WRL::ComPtr dxgi_swap_chain_; - Microsoft::WRL::ComPtr target_bitmap_; -}; -} // namespace cru::platform::native::win diff --git a/include/cru/win/string.hpp b/include/cru/win/string.hpp index aec0e3d9..3d68cff7 100644 --- a/include/cru/win/string.hpp +++ b/include/cru/win/string.hpp @@ -14,9 +14,9 @@ way.) */ #pragma once -#include "win_pre_config.hpp" +#include "WinPreConfig.hpp" -#include "cru/common/base.hpp" +#include "cru/common/Base.hpp" #include #include diff --git a/include/cru/win/win_pre_config.hpp b/include/cru/win/win_pre_config.hpp deleted file mode 100644 index a7dc72f3..00000000 --- a/include/cru/win/win_pre_config.hpp +++ /dev/null @@ -1,16 +0,0 @@ -#pragma once - -#include "cru/common/pre_config.hpp" - -#define NOMINMAX -#define WIN32_LEAN_AND_MEAN -#include -#undef CreateWindow -#undef DrawText -#undef CreateFont - -#include -#include -#include -#include -#include diff --git a/src/common/CMakeLists.txt b/src/common/CMakeLists.txt index 43d1dedb..d53b9740 100644 --- a/src/common/CMakeLists.txt +++ b/src/common/CMakeLists.txt @@ -1,14 +1,14 @@ set(CRU_BASE_INCLUDE_DIR ${CRU_INCLUDE_DIR}/cru/common) add_library(cru_base STATIC - logger.cpp + Logger.cpp ) target_sources(cru_base PUBLIC - ${CRU_BASE_INCLUDE_DIR}/base.hpp - ${CRU_BASE_INCLUDE_DIR}/bitmask.hpp - ${CRU_BASE_INCLUDE_DIR}/event.hpp - ${CRU_BASE_INCLUDE_DIR}/logger.hpp - ${CRU_BASE_INCLUDE_DIR}/pre_config.hpp - ${CRU_BASE_INCLUDE_DIR}/self_resolvable.hpp + ${CRU_BASE_INCLUDE_DIR}/Base.hpp + ${CRU_BASE_INCLUDE_DIR}/Bitmask.hpp + ${CRU_BASE_INCLUDE_DIR}/Event.hpp + ${CRU_BASE_INCLUDE_DIR}/Logger.hpp + ${CRU_BASE_INCLUDE_DIR}/PreConfig.hpp + ${CRU_BASE_INCLUDE_DIR}/SelfResolvable.hpp ) target_include_directories(cru_base PUBLIC ${CRU_INCLUDE_DIR}) target_compile_definitions(cru_base PUBLIC $<$:CRU_DEBUG>) diff --git a/src/common/logger.cpp b/src/common/logger.cpp index 97599b0a..ed9f9e64 100644 --- a/src/common/logger.cpp +++ b/src/common/logger.cpp @@ -1,4 +1,4 @@ -#include "cru/common/logger.hpp" +#include "cru/common/Logger.hpp" #include #include diff --git a/src/platform/CMakeLists.txt b/src/platform/CMakeLists.txt index 01952106..51253b56 100644 --- a/src/platform/CMakeLists.txt +++ b/src/platform/CMakeLists.txt @@ -1,12 +1,12 @@ set(CRU_PLATFORM_BASE_INCLUDE_DIR ${CRU_INCLUDE_DIR}/cru/platform) add_library(cru_platform_base INTERFACE) target_sources(cru_platform_base INTERFACE - ${CRU_PLATFORM_BASE_INCLUDE_DIR}/check.hpp - ${CRU_PLATFORM_BASE_INCLUDE_DIR}/exception.hpp - ${CRU_PLATFORM_BASE_INCLUDE_DIR}/graph_base.hpp - ${CRU_PLATFORM_BASE_INCLUDE_DIR}/heap_debug.hpp - ${CRU_PLATFORM_BASE_INCLUDE_DIR}/matrix.hpp - ${CRU_PLATFORM_BASE_INCLUDE_DIR}/resource.hpp + ${CRU_PLATFORM_BASE_INCLUDE_DIR}/Check.hpp + ${CRU_PLATFORM_BASE_INCLUDE_DIR}/Exception.hpp + ${CRU_PLATFORM_BASE_INCLUDE_DIR}/GraphBase.hpp + ${CRU_PLATFORM_BASE_INCLUDE_DIR}/HeapDebug.hpp + ${CRU_PLATFORM_BASE_INCLUDE_DIR}/Matrix.hpp + ${CRU_PLATFORM_BASE_INCLUDE_DIR}/Resource.hpp ) target_link_libraries(cru_platform_base INTERFACE cru_base) diff --git a/src/platform/graph/CMakeLists.txt b/src/platform/graph/CMakeLists.txt index dac28370..3bf11e8d 100644 --- a/src/platform/graph/CMakeLists.txt +++ b/src/platform/graph/CMakeLists.txt @@ -1,14 +1,14 @@ set(CRU_PLATFORM_GRAPH_INCLUDE_DIR ${CRU_INCLUDE_DIR}/cru/platform/graph) add_library(cru_platform_graph INTERFACE) target_sources(cru_platform_graph INTERFACE - ${CRU_PLATFORM_GRAPH_INCLUDE_DIR}/base.hpp - ${CRU_PLATFORM_GRAPH_INCLUDE_DIR}/brush.hpp - ${CRU_PLATFORM_GRAPH_INCLUDE_DIR}/font.hpp - ${CRU_PLATFORM_GRAPH_INCLUDE_DIR}/geometry.hpp - ${CRU_PLATFORM_GRAPH_INCLUDE_DIR}/factory.hpp - ${CRU_PLATFORM_GRAPH_INCLUDE_DIR}/resource.hpp - ${CRU_PLATFORM_GRAPH_INCLUDE_DIR}/painter.hpp - ${CRU_PLATFORM_GRAPH_INCLUDE_DIR}/text_layout.hpp - ${CRU_PLATFORM_GRAPH_INCLUDE_DIR}/util/painter.hpp + ${CRU_PLATFORM_GRAPH_INCLUDE_DIR}/Base.hpp + ${CRU_PLATFORM_GRAPH_INCLUDE_DIR}/Brush.hpp + ${CRU_PLATFORM_GRAPH_INCLUDE_DIR}/Font.hpp + ${CRU_PLATFORM_GRAPH_INCLUDE_DIR}/Geometry.hpp + ${CRU_PLATFORM_GRAPH_INCLUDE_DIR}/Factory.hpp + ${CRU_PLATFORM_GRAPH_INCLUDE_DIR}/Resource.hpp + ${CRU_PLATFORM_GRAPH_INCLUDE_DIR}/Painter.hpp + ${CRU_PLATFORM_GRAPH_INCLUDE_DIR}/TextLayout.hpp + ${CRU_PLATFORM_GRAPH_INCLUDE_DIR}/util/Painter.hpp ) target_link_libraries(cru_platform_graph INTERFACE cru_platform_base) diff --git a/src/platform/native/CMakeLists.txt b/src/platform/native/CMakeLists.txt index 688f2890..c68a0958 100644 --- a/src/platform/native/CMakeLists.txt +++ b/src/platform/native/CMakeLists.txt @@ -1,13 +1,13 @@ set(CRU_PLATFORM_NATIVE_INCLUDE_DIR ${CRU_INCLUDE_DIR}/cru/platform/native) add_library(cru_platform_native STATIC - ui_application.cpp + UiApplication.cpp ) target_sources(cru_platform_native PUBLIC - ${CRU_PLATFORM_NATIVE_INCLUDE_DIR}/base.hpp - ${CRU_PLATFORM_NATIVE_INCLUDE_DIR}/cursor.hpp - ${CRU_PLATFORM_NATIVE_INCLUDE_DIR}/input_method.hpp - ${CRU_PLATFORM_NATIVE_INCLUDE_DIR}/keyboard.hpp - ${CRU_PLATFORM_NATIVE_INCLUDE_DIR}/window.hpp - ${CRU_PLATFORM_NATIVE_INCLUDE_DIR}/ui_application.hpp + ${CRU_PLATFORM_NATIVE_INCLUDE_DIR}/Base.hpp + ${CRU_PLATFORM_NATIVE_INCLUDE_DIR}/Cursor.hpp + ${CRU_PLATFORM_NATIVE_INCLUDE_DIR}/InputMethod.hpp + ${CRU_PLATFORM_NATIVE_INCLUDE_DIR}/Keyboard.hpp + ${CRU_PLATFORM_NATIVE_INCLUDE_DIR}/Window.hpp + ${CRU_PLATFORM_NATIVE_INCLUDE_DIR}/UiApplication.hpp ) target_link_libraries(cru_platform_native PUBLIC cru_platform_graph) diff --git a/src/platform/native/UiApplication.cpp b/src/platform/native/UiApplication.cpp new file mode 100644 index 00000000..200b10e0 --- /dev/null +++ b/src/platform/native/UiApplication.cpp @@ -0,0 +1,15 @@ +#include "cru/platform/native/UiApplication.hpp" + +namespace cru::platform::native { +IUiApplication* IUiApplication::instance = nullptr; + +IUiApplication::IUiApplication() { + if (instance) { + throw std::runtime_error("An ui application has already been created."); + } + + instance = this; +} + +IUiApplication::~IUiApplication() { instance = nullptr; } +} // namespace cru::platform::native diff --git a/src/platform/native/ui_application.cpp b/src/platform/native/ui_application.cpp deleted file mode 100644 index 212d9104..00000000 --- a/src/platform/native/ui_application.cpp +++ /dev/null @@ -1,15 +0,0 @@ -#include "cru/platform/native/ui_application.hpp" - -namespace cru::platform::native { -IUiApplication* IUiApplication::instance = nullptr; - -IUiApplication::IUiApplication() { - if (instance) { - throw std::runtime_error("An ui application has already been created."); - } - - instance = this; -} - -IUiApplication::~IUiApplication() { instance = nullptr; } -} // namespace cru::platform::native diff --git a/src/ui/CMakeLists.txt b/src/ui/CMakeLists.txt index 777e4fbc..f37982e9 100644 --- a/src/ui/CMakeLists.txt +++ b/src/ui/CMakeLists.txt @@ -1,63 +1,63 @@ set(CRU_UI_INCLUDE_DIR ${CRU_INCLUDE_DIR}/cru/ui) add_library(cru_ui STATIC - helper.hpp - routed_event_dispatch.hpp + Helper.hpp + RoutedEventDispatch.hpp - click_detector.cpp - content_control.cpp - control.cpp - helper.cpp - layout_control.cpp - no_child_control.cpp - ui_host.cpp - ui_manager.cpp - window.cpp - controls/button.cpp - controls/container.cpp - controls/flex_layout.cpp - controls/stack_layout.cpp - controls/text_block.cpp - controls/text_box.cpp - controls/text_control_service.hpp - render/border_render_object.cpp - render/canvas_render_object.cpp - render/flex_layout_render_object.cpp - render/layout_utility.cpp - render/render_object.cpp - render/scroll_render_object.cpp - render/stack_layout_render_object.cpp - render/text_render_object.cpp - render/window_render_object.cpp + ClickDetector.cpp + ContentControl.cpp + Control.cpp + Helper.cpp + LayoutControl.cpp + NoChildControl.cpp + UiHost.cpp + UiManager.cpp + Window.cpp + controls/Button.cpp + controls/Container.cpp + controls/FlexLayout.cpp + controls/StackLayout.cpp + controls/TextBlock.cpp + controls/TextBox.cpp + controls/TextControlService.hpp + render/BorderRenderObject.cpp + render/CanvasRenderObject.cpp + render/FlexLayoutRenderObject.cpp + render/LayoutUtility.cpp + render/RenderObject.cpp + render/ScrollRenderObject.cpp + render/StackLayoutRenderObject.cpp + render/TextRenderObject.cpp + render/WindowRenderObject.cpp ) target_sources(cru_ui PUBLIC - ${CRU_UI_INCLUDE_DIR}/base.hpp - ${CRU_UI_INCLUDE_DIR}/click_detector.hpp - ${CRU_UI_INCLUDE_DIR}/content_control.hpp - ${CRU_UI_INCLUDE_DIR}/control.hpp - ${CRU_UI_INCLUDE_DIR}/layout_control.hpp - ${CRU_UI_INCLUDE_DIR}/no_child_control.hpp - ${CRU_UI_INCLUDE_DIR}/ui_event.hpp - ${CRU_UI_INCLUDE_DIR}/ui_host.hpp - ${CRU_UI_INCLUDE_DIR}/ui_manager.hpp - ${CRU_UI_INCLUDE_DIR}/window.hpp - ${CRU_UI_INCLUDE_DIR}/controls/base.hpp - ${CRU_UI_INCLUDE_DIR}/controls/button.hpp - ${CRU_UI_INCLUDE_DIR}/controls/container.hpp - ${CRU_UI_INCLUDE_DIR}/controls/flex_layout.hpp - ${CRU_UI_INCLUDE_DIR}/controls/stack_layout.hpp - ${CRU_UI_INCLUDE_DIR}/controls/text_box.hpp - ${CRU_UI_INCLUDE_DIR}/controls/text_block.hpp - ${CRU_UI_INCLUDE_DIR}/render/base.hpp - ${CRU_UI_INCLUDE_DIR}/render/border_render_object.hpp - ${CRU_UI_INCLUDE_DIR}/render/canvas_render_object.hpp - ${CRU_UI_INCLUDE_DIR}/render/flex_layout_render_object.hpp - ${CRU_UI_INCLUDE_DIR}/render/layout_render_object.hpp - ${CRU_UI_INCLUDE_DIR}/render/layout_utility.hpp - ${CRU_UI_INCLUDE_DIR}/render/render_object.hpp - ${CRU_UI_INCLUDE_DIR}/render/scroll_render_object.hpp - ${CRU_UI_INCLUDE_DIR}/render/stack_layout_render_object.hpp - ${CRU_UI_INCLUDE_DIR}/render/text_render_object.hpp - ${CRU_UI_INCLUDE_DIR}/render/window_render_object.hpp + ${CRU_UI_INCLUDE_DIR}/Base.hpp + ${CRU_UI_INCLUDE_DIR}/ClickDetector.hpp + ${CRU_UI_INCLUDE_DIR}/ContentControl.hpp + ${CRU_UI_INCLUDE_DIR}/Control.hpp + ${CRU_UI_INCLUDE_DIR}/LayoutControl.hpp + ${CRU_UI_INCLUDE_DIR}/NoChildControl.hpp + ${CRU_UI_INCLUDE_DIR}/UiEvent.hpp + ${CRU_UI_INCLUDE_DIR}/UiHost.hpp + ${CRU_UI_INCLUDE_DIR}/UiManager.hpp + ${CRU_UI_INCLUDE_DIR}/Window.hpp + ${CRU_UI_INCLUDE_DIR}/controls/Base.hpp + ${CRU_UI_INCLUDE_DIR}/controls/Button.hpp + ${CRU_UI_INCLUDE_DIR}/controls/Container.hpp + ${CRU_UI_INCLUDE_DIR}/controls/FlexLayout.hpp + ${CRU_UI_INCLUDE_DIR}/controls/StackLayout.hpp + ${CRU_UI_INCLUDE_DIR}/controls/TextBox.hpp + ${CRU_UI_INCLUDE_DIR}/controls/TextBlock.hpp + ${CRU_UI_INCLUDE_DIR}/render/Base.hpp + ${CRU_UI_INCLUDE_DIR}/render/BorderRenderObject.hpp + ${CRU_UI_INCLUDE_DIR}/render/CanvasRenderObject.hpp + ${CRU_UI_INCLUDE_DIR}/render/FlexLayoutRenderObject.hpp + ${CRU_UI_INCLUDE_DIR}/render/LayoutRenderObject.hpp + ${CRU_UI_INCLUDE_DIR}/render/LayoutUtility.hpp + ${CRU_UI_INCLUDE_DIR}/render/RenderObject.hpp + ${CRU_UI_INCLUDE_DIR}/render/ScrollRenderObject.hpp + ${CRU_UI_INCLUDE_DIR}/render/StackLayoutRenderObject.hpp + ${CRU_UI_INCLUDE_DIR}/render/TextRenderObject.hpp + ${CRU_UI_INCLUDE_DIR}/render/WindowRenderObject.hpp ) target_link_libraries(cru_ui PUBLIC cru_platform_native) diff --git a/src/ui/ClickDetector.cpp b/src/ui/ClickDetector.cpp new file mode 100644 index 00000000..e873efd4 --- /dev/null +++ b/src/ui/ClickDetector.cpp @@ -0,0 +1,129 @@ +#include "cru/ui/ClickDetector.hpp" + +#include "cru/common/Logger.hpp" + +#include + +namespace cru::ui { +ClickDetector::ClickDetector(Control* control) { + Expects(control); + control_ = control; + + event_rovoker_guards_.push_back( + EventRevokerGuard(control->MouseEnterEvent()->Direct()->AddHandler( + [this](event::MouseEventArgs&) { + if (this->enable_) { + if (this->state_ == ClickState::PressInactive) { + if ((this->button_ & this->trigger_button_)) { + this->SetState(ClickState::Press); + } + } else { + this->SetState(ClickState::Hover); + } + } + }))); + + event_rovoker_guards_.push_back( + EventRevokerGuard(control->MouseLeaveEvent()->Direct()->AddHandler( + [this](event::MouseEventArgs&) { + if (this->enable_) { + if (this->state_ == ClickState::Press) { + if ((this->button_ & this->trigger_button_)) { + this->SetState(ClickState::PressInactive); + } + } else { + this->SetState(ClickState::None); + } + } + }))); + + event_rovoker_guards_.push_back( + EventRevokerGuard(control->MouseDownEvent()->Direct()->AddHandler( + [this](event::MouseButtonEventArgs& args) { + const auto button = args.GetButton(); + if (this->enable_ && (button & this->trigger_button_) && + this->state_ == ClickState::Hover) { + if (!this->control_->CaptureMouse()) { + log::Debug("Failed to capture mouse when begin click."); + return; + } + this->down_point_ = args.GetPoint(); + this->button_ = button; + this->SetState(ClickState::Press); + } + }))); + + event_rovoker_guards_.push_back( + EventRevokerGuard(control->MouseUpEvent()->Direct()->AddHandler( + [this](event::MouseButtonEventArgs& args) { + const auto button = args.GetButton(); + if (this->enable_ && (button & this->trigger_button_) && + button == button_) { + if (this->state_ == ClickState::Press) { + this->SetState(ClickState::Hover); + this->event_.Raise(ClickEventArgs{this->control_, + this->down_point_, + args.GetPoint(), button}); + this->control_->ReleaseMouse(); + } else if (this->state_ == ClickState::PressInactive) { + this->SetState(ClickState::None); + this->control_->ReleaseMouse(); + } + } + }))); +} // namespace cru::ui + +void ClickDetector::SetEnabled(bool enable) { + if (enable == enable_) { + return; + } + + enable_ = enable; + if (enable) { + SetState(control_->IsMouseOver() ? ClickState::Hover : ClickState::None); + } else { + if (state_ == ClickState::Press || state_ == ClickState::PressInactive) { + SetState(ClickState::None); + control_->ReleaseMouse(); + } else if (state_ == ClickState::Hover) { + SetState(ClickState::None); + } + } +} + +void ClickDetector::SetTriggerButton(MouseButton trigger_button) { + if (trigger_button == trigger_button_) { + return; + } + + trigger_button_ = trigger_button; + if ((state_ == ClickState::Press || state_ == ClickState::PressInactive) && + !(button_ & trigger_button)) { + SetState(control_->IsMouseOver() ? ClickState::Hover : ClickState::None); + control_->ReleaseMouse(); + } +} + +void ClickDetector::SetState(ClickState state) { +#ifdef CRU_DEBUG + auto to_string = [](ClickState state) -> std::string_view { + switch (state) { + case ClickState::None: + return "None"; + case ClickState::Hover: + return "Hover"; + case ClickState::Press: + return "Press"; + case ClickState::PressInactive: + return "PressInvactive"; + default: + UnreachableCode(); + } + }; + log::Debug("Click state changed, new state: {}.", to_string(state)); +#endif + + state_ = state; + state_change_event_.Raise(state); +} +} // namespace cru::ui diff --git a/src/ui/ContentControl.cpp b/src/ui/ContentControl.cpp new file mode 100644 index 00000000..8d1a17d2 --- /dev/null +++ b/src/ui/ContentControl.cpp @@ -0,0 +1,33 @@ +#include "cru/ui/ContentControl.hpp" + +#include "cru/ui/Window.hpp" + +namespace cru::ui { +ContentControl::ContentControl() + : child_vector_{nullptr}, child_(child_vector_[0]) {} + +ContentControl::~ContentControl() { delete child_; } + +void ContentControl::SetChild(Control* child) { + Expects(!dynamic_cast(child)); // Can't add a window as child. + if (child == child_) return; + + const auto host = GetUiHost(); + const auto old_child = child_; + child_ = child; + if (old_child) { + old_child->_SetParent(nullptr); + old_child->_SetDescendantUiHost(nullptr); + } + if (child) { + child->_SetParent(this); + child->_SetDescendantUiHost(host); + } + OnChildChanged(old_child, child); +} + +void ContentControl::OnChildChanged(Control* old_child, Control* new_child) { + CRU_UNUSED(old_child) + CRU_UNUSED(new_child) +} +} // namespace cru::ui diff --git a/src/ui/LayoutControl.cpp b/src/ui/LayoutControl.cpp new file mode 100644 index 00000000..4813566b --- /dev/null +++ b/src/ui/LayoutControl.cpp @@ -0,0 +1,53 @@ +#include "cru/ui/LayoutControl.hpp" + +#include "cru/ui/Window.hpp" + +namespace cru::ui { +LayoutControl::~LayoutControl() { + for (const auto child : children_) delete child; +} + +void LayoutControl::AddChild(Control* control, const Index position) { + Expects(control->GetParent() == + nullptr); // The control already has a parent. + Expects(!dynamic_cast(control)); // Can't add a window as child. + Expects(position >= 0); + Expects(position <= + static_cast( + this->children_.size())); // The position is out of range. + + children_.insert(this->children_.cbegin() + position, control); + + control->_SetParent(this); + control->_SetDescendantUiHost(GetUiHost()); + + OnAddChild(control, position); +} + +void LayoutControl::RemoveChild(const Index position) { + Expects(position >= 0); + Expects(position < + static_cast( + this->children_.size())); // The position is out of range. + + const auto i = children_.cbegin() + position; + const auto child = *i; + + children_.erase(i); + + child->_SetParent(nullptr); + child->_SetDescendantUiHost(nullptr); + + OnRemoveChild(child, position); +} + +void LayoutControl::OnAddChild(Control* child, const Index position) { + CRU_UNUSED(child) + CRU_UNUSED(position) +} + +void LayoutControl::OnRemoveChild(Control* child, const Index position) { + CRU_UNUSED(child) + CRU_UNUSED(position) +} +} // namespace cru::ui diff --git a/src/ui/NoChildControl.cpp b/src/ui/NoChildControl.cpp new file mode 100644 index 00000000..86861049 --- /dev/null +++ b/src/ui/NoChildControl.cpp @@ -0,0 +1,5 @@ +#include "cru/ui/NoChildControl.hpp" + +namespace cru::ui { +const std::vector NoChildControl::empty_control_vector{}; +} diff --git a/src/ui/RoutedEventDispatch.hpp b/src/ui/RoutedEventDispatch.hpp new file mode 100644 index 00000000..5ff21a74 --- /dev/null +++ b/src/ui/RoutedEventDispatch.hpp @@ -0,0 +1,134 @@ +#pragma once +#include "cru/ui/Control.hpp" + +#include "cru/common/Logger.hpp" + +#include + +namespace cru::ui { +// Dispatch the event. +// +// This will raise routed event of the control and its parent and parent's +// parent ... (until "last_receiver" if it's not nullptr) with appropriate args. +// +// First tunnel from top to bottom possibly stopped by "handled" flag in +// EventArgs. Second bubble from bottom to top possibly stopped by "handled" +// flag in EventArgs. Last direct to each control. +// +// Args is of type "EventArgs". The first init argument is "sender", which is +// automatically bound to each receiving control. The second init argument is +// "original_sender", which is unchanged. And "args" will be perfectly forwarded +// as the rest arguments. +template +void DispatchEvent(const std::string_view& event_name, + Control* const original_sender, + event::RoutedEvent* (Control::*event_ptr)(), + Control* const last_receiver, Args&&... args) { +#ifndef CRU_DEBUG + CRU_UNUSED(event_name) +#endif + +#ifdef CRU_DEBUG + bool do_log = true; + if (event_name == "MouseMove") do_log = false; +#endif + + if (original_sender == last_receiver) { + /* + #ifdef CRU_DEBUG + if (do_log) + log::Debug( + "Routed event {} no need to dispatch (original_sender == " + "last_receiver). Original sender is {}.", + event_name, original_sender->GetControlType()); + #endif + */ + return; + } + + std::list receive_list; + + auto parent = original_sender; + while (parent != last_receiver) { + receive_list.push_back(parent); + parent = parent->GetParent(); + } + +#ifdef CRU_DEBUG + if (do_log) { + std::string log = "Dispatch routed event "; + log += event_name; + log += ". Path (parent first): "; + auto i = receive_list.crbegin(); + const auto end = --receive_list.crend(); + for (; i != end; ++i) { + log += (*i)->GetControlType(); + log += " -> "; + } + log += (*i)->GetControlType(); + log::Debug(log); + } +#endif + + auto handled = false; + +#ifdef CRU_DEBUG + int count = 0; +#endif + + // tunnel + for (auto i = receive_list.crbegin(); i != receive_list.crend(); ++i) { +#ifdef CRU_DEBUG + count++; +#endif + EventArgs event_args(*i, original_sender, std::forward(args)...); + static_cast*>(((*i)->*event_ptr)()->Tunnel()) + ->Raise(event_args); + if (event_args.IsHandled()) { + handled = true; +#ifdef CRU_DEBUG + if (do_log) + log::Debug( + "Routed event is short-circuit in TUNNEL at {}-st control (count " + "from parent).", + count); +#endif + break; + } + } + + // bubble + if (!handled) { + for (auto i : receive_list) { +#ifdef CRU_DEBUG + count--; +#endif + EventArgs event_args(i, original_sender, std::forward(args)...); + static_cast*>((i->*event_ptr)()->Bubble()) + ->Raise(event_args); + if (event_args.IsHandled()) { +#ifdef CRU_DEBUG + if (do_log) + log::Debug( + "Routed event is short-circuit in BUBBLE at {}-st control " + "(count " + "from parent).", + count); +#endif + break; + } + } + } + + // direct + for (auto i : receive_list) { + EventArgs event_args(i, original_sender, std::forward(args)...); + static_cast*>((i->*event_ptr)()->Direct()) + ->Raise(event_args); + } + +#ifdef CRU_DEBUG + if (do_log) log::Debug("Routed event dispatch finished."); +#endif +} +} // namespace cru::ui diff --git a/src/ui/UiHost.cpp b/src/ui/UiHost.cpp new file mode 100644 index 00000000..069e68de --- /dev/null +++ b/src/ui/UiHost.cpp @@ -0,0 +1,368 @@ +#include "cru/ui/UiHost.hpp" + +#include "cru/common/Logger.hpp" +#include "cru/platform/graph/Painter.hpp" +#include "cru/platform/native/UiApplication.hpp" +#include "cru/platform/native/Window.hpp" +#include "cru/ui/render/WindowRenderObject.hpp" +#include "cru/ui/Window.hpp" +#include "RoutedEventDispatch.hpp" + +namespace cru::ui { +using platform::native::INativeWindow; +using platform::native::IUiApplication; + +namespace event_names { +#ifdef CRU_DEBUG +#define CRU_DEFINE_EVENT_NAME(name) constexpr const char* name = #name; +#else +#define CRU_DEFINE_EVENT_NAME(name) constexpr const char* name = ""; +#endif + +CRU_DEFINE_EVENT_NAME(LoseFocus) +CRU_DEFINE_EVENT_NAME(GainFocus) +CRU_DEFINE_EVENT_NAME(MouseEnter) +CRU_DEFINE_EVENT_NAME(MouseLeave) +CRU_DEFINE_EVENT_NAME(MouseMove) +CRU_DEFINE_EVENT_NAME(MouseDown) +CRU_DEFINE_EVENT_NAME(MouseUp) +CRU_DEFINE_EVENT_NAME(KeyDown) +CRU_DEFINE_EVENT_NAME(KeyUp) +CRU_DEFINE_EVENT_NAME(Char) + +#undef CRU_DEFINE_EVENT_NAME +} // namespace event_names + +namespace { +bool IsAncestor(Control* control, Control* ancestor) { + while (control != nullptr) { + if (control == ancestor) return true; + control = control->GetParent(); + } + return false; +} + +std::list GetAncestorList(Control* control) { + std::list l; + while (control != nullptr) { + l.push_front(control); + control = control->GetParent(); + } + return l; +} + +Control* FindLowestCommonAncestor(Control* left, Control* right) { + if (left == nullptr || right == nullptr) return nullptr; + + auto&& left_list = GetAncestorList(left); + auto&& right_list = GetAncestorList(right); + + // the root is different + if (left_list.front() != right_list.front()) return nullptr; + + // find the last same control or the last control (one is ancestor of the + // other) + auto left_i = left_list.cbegin(); + auto right_i = right_list.cbegin(); + + while (true) { + if (left_i == left_list.cend()) { + return *(--left_i); + } + if (right_i == right_list.cend()) { + return *(--right_i); + } + if (*left_i != *right_i) { + return *(--left_i); + } + ++left_i; + ++right_i; + } +} +} // namespace + +namespace { +template +inline void BindNativeEvent( + UiHost* host, INativeWindow* native_window, IEvent* event, + void (UiHost::*handler)(INativeWindow*, typename IEvent::EventArgs), + std::vector& guard_pool) { + guard_pool.push_back(EventRevokerGuard(event->AddHandler( + std::bind(handler, host, native_window, std::placeholders::_1)))); +} +} // namespace + +UiHost::UiHost(Window* window) + : mouse_hover_control_(nullptr), + focus_control_(window), + mouse_captured_control_(nullptr), + window_control_(window) { + native_window_resolver_ = + IUiApplication::GetInstance()->CreateWindow(nullptr); + + const auto native_window = native_window_resolver_->Resolve(); + window->ui_host_ = this; + + root_render_object_ = std::make_unique(this); + root_render_object_->SetAttachedControl(window); + window->render_object_ = root_render_object_.get(); + + BindNativeEvent(this, native_window, native_window->DestroyEvent(), + &UiHost::OnNativeDestroy, event_revoker_guards_); + BindNativeEvent(this, native_window, native_window->PaintEvent(), + &UiHost::OnNativePaint, event_revoker_guards_); + BindNativeEvent(this, native_window, native_window->ResizeEvent(), + &UiHost::OnNativeResize, event_revoker_guards_); + BindNativeEvent(this, native_window, native_window->FocusEvent(), + &UiHost::OnNativeFocus, event_revoker_guards_); + BindNativeEvent(this, native_window, native_window->MouseEnterLeaveEvent(), + &UiHost::OnNativeMouseEnterLeave, event_revoker_guards_); + BindNativeEvent(this, native_window, native_window->MouseMoveEvent(), + &UiHost::OnNativeMouseMove, event_revoker_guards_); + BindNativeEvent(this, native_window, native_window->MouseDownEvent(), + &UiHost::OnNativeMouseDown, event_revoker_guards_); + BindNativeEvent(this, native_window, native_window->MouseUpEvent(), + &UiHost::OnNativeMouseUp, event_revoker_guards_); + BindNativeEvent(this, native_window, native_window->KeyDownEvent(), + &UiHost::OnNativeKeyDown, event_revoker_guards_); + BindNativeEvent(this, native_window, native_window->KeyUpEvent(), + &UiHost::OnNativeKeyUp, event_revoker_guards_); +} + +UiHost::~UiHost() { + deleting_ = true; + window_control_->TraverseDescendants( + [this](Control* control) { control->OnDetachFromHost(this); }); + if (!native_window_destroyed_) { + const auto native_window = native_window_resolver_->Resolve(); + if (native_window) { + native_window->Close(); + } + } +} + +void UiHost::InvalidatePaint() { + if (const auto native_window = native_window_resolver_->Resolve()) + native_window->RequestRepaint(); +} + +void UiHost::InvalidateLayout() { + if (!need_layout_) { + platform::native::IUiApplication::GetInstance()->InvokeLater( + [resolver = this->CreateResolver()] { + if (const auto host = resolver.Resolve()) { + host->Relayout(); + host->need_layout_ = false; + host->after_layout_event_.Raise(AfterLayoutEventArgs{}); + log::Debug("A relayout finished."); + host->InvalidatePaint(); + } + }); + need_layout_ = true; + } +} + +void UiHost::Relayout() { + const auto native_window = native_window_resolver_->Resolve(); + const auto client_size = native_window + ? native_window->GetClientSize() + : Size{100, 100}; // a reasonable assumed size + root_render_object_->Measure(client_size); + root_render_object_->Layout(Rect{Point{}, client_size}); +} + +bool UiHost::RequestFocusFor(Control* control) { + Expects(control != nullptr); // The control to request focus can't be null. + // You can set it as the window. + + if (focus_control_ == control) return true; + + DispatchEvent(event_names::LoseFocus, focus_control_, + &Control::LoseFocusEvent, nullptr, false); + + focus_control_ = control; + + DispatchEvent(event_names::GainFocus, control, &Control::GainFocusEvent, + nullptr, false); + + return true; +} + +Control* UiHost::GetFocusControl() { return focus_control_; } + +bool UiHost::CaptureMouseFor(Control* control) { + const auto native_window = native_window_resolver_->Resolve(); + if (!native_window) return false; + + if (control == mouse_captured_control_) return true; + + if (control == nullptr) { + const auto old_capture_control = mouse_captured_control_; + mouse_captured_control_ = + nullptr; // update this in case this is used in event handlers + if (old_capture_control != mouse_hover_control_) { + DispatchMouseHoverControlChangeEvent( + old_capture_control, mouse_hover_control_, + native_window->GetMousePosition(), true, false); + } + UpdateCursor(); + return true; + } + + if (mouse_captured_control_) return false; + + mouse_captured_control_ = control; + DispatchMouseHoverControlChangeEvent( + mouse_hover_control_, mouse_captured_control_, + native_window->GetMousePosition(), false, true); + UpdateCursor(); + return true; +} + +Control* UiHost::GetMouseCaptureControl() { return mouse_captured_control_; } + +void UiHost::OnNativeDestroy(INativeWindow* window, std::nullptr_t) { + CRU_UNUSED(window) + native_window_destroyed_ = true; + if (!deleting_ && !retain_after_destroy_) delete window_control_; +} + +void UiHost::OnNativePaint(INativeWindow* window, std::nullptr_t) { + auto painter = window->BeginPaint(); + root_render_object_->Draw(painter.get()); + painter->EndDraw(); +} + +void UiHost::OnNativeResize(INativeWindow* window, const Size& size) { + CRU_UNUSED(window) + CRU_UNUSED(size) + + InvalidateLayout(); +} + +void UiHost::OnNativeFocus(INativeWindow* window, + platform::native::FocusChangeType focus) { + CRU_UNUSED(window) + + focus == platform::native::FocusChangeType::Gain + ? DispatchEvent(event_names::GainFocus, focus_control_, + &Control::GainFocusEvent, nullptr, true) + : DispatchEvent(event_names::LoseFocus, focus_control_, + &Control::LoseFocusEvent, nullptr, true); +} + +void UiHost::OnNativeMouseEnterLeave( + INativeWindow* window, platform::native::MouseEnterLeaveType type) { + CRU_UNUSED(window) + + if (type == platform::native::MouseEnterLeaveType::Leave) { + DispatchEvent(event_names::MouseLeave, mouse_hover_control_, + &Control::MouseLeaveEvent, nullptr); + mouse_hover_control_ = nullptr; + } +} + +void UiHost::OnNativeMouseMove(INativeWindow* window, const Point& point) { + CRU_UNUSED(window) + + // Find the first control that hit test succeed. + const auto new_mouse_hover_control = HitTest(point); + const auto old_mouse_hover_control = mouse_hover_control_; + mouse_hover_control_ = new_mouse_hover_control; + + if (mouse_captured_control_) { + const auto n = FindLowestCommonAncestor(new_mouse_hover_control, + mouse_captured_control_); + const auto o = FindLowestCommonAncestor(old_mouse_hover_control, + mouse_captured_control_); + bool a = IsAncestor(o, n); + if (a) { + DispatchEvent(event_names::MouseLeave, o, &Control::MouseLeaveEvent, n); + } else { + DispatchEvent(event_names::MouseEnter, n, &Control::MouseEnterEvent, o, + point); + } + DispatchEvent(event_names::MouseMove, mouse_captured_control_, + &Control::MouseMoveEvent, nullptr, point); + UpdateCursor(); + return; + } + + DispatchMouseHoverControlChangeEvent( + old_mouse_hover_control, new_mouse_hover_control, point, false, false); + DispatchEvent(event_names::MouseMove, new_mouse_hover_control, + &Control::MouseMoveEvent, nullptr, point); + UpdateCursor(); +} + +void UiHost::OnNativeMouseDown( + INativeWindow* window, + const platform::native::NativeMouseButtonEventArgs& args) { + CRU_UNUSED(window) + + Control* control = + mouse_captured_control_ ? mouse_captured_control_ : HitTest(args.point); + DispatchEvent(event_names::MouseDown, control, &Control::MouseDownEvent, + nullptr, args.point, args.button, args.modifier); +} + +void UiHost::OnNativeMouseUp( + INativeWindow* window, + const platform::native::NativeMouseButtonEventArgs& args) { + CRU_UNUSED(window) + + Control* control = + mouse_captured_control_ ? mouse_captured_control_ : HitTest(args.point); + DispatchEvent(event_names::MouseUp, control, &Control::MouseUpEvent, nullptr, + args.point, args.button, args.modifier); +} + +void UiHost::OnNativeKeyDown(INativeWindow* window, + const platform::native::NativeKeyEventArgs& args) { + CRU_UNUSED(window) + + DispatchEvent(event_names::KeyDown, focus_control_, &Control::KeyDownEvent, + nullptr, args.key, args.modifier); +} + +void UiHost::OnNativeKeyUp(INativeWindow* window, + const platform::native::NativeKeyEventArgs& args) { + CRU_UNUSED(window) + + DispatchEvent(event_names::KeyUp, focus_control_, &Control::KeyUpEvent, + nullptr, args.key, args.modifier); +} + +void UiHost::DispatchMouseHoverControlChangeEvent(Control* old_control, + Control* new_control, + const Point& point, + bool no_leave, + bool no_enter) { + if (new_control != old_control) // if the mouse-hover-on control changed + { + const auto lowest_common_ancestor = + FindLowestCommonAncestor(old_control, new_control); + if (!no_leave && old_control != nullptr) + DispatchEvent(event_names::MouseLeave, old_control, + &Control::MouseLeaveEvent, + lowest_common_ancestor); // dispatch mouse leave event. + if (!no_enter && new_control != nullptr) { + DispatchEvent(event_names::MouseEnter, new_control, + &Control::MouseEnterEvent, lowest_common_ancestor, + point); // dispatch mouse enter event. + } + } +} + +void UiHost::UpdateCursor() { + if (const auto native_window = native_window_resolver_->Resolve()) { + const auto capture = GetMouseCaptureControl(); + native_window->SetCursor( + (capture ? capture : GetMouseHoverControl())->GetInheritedCursor()); + } +} + +Control* UiHost::HitTest(const Point& point) { + return root_render_object_->HitTest(point)->GetAttachedControl(); +} +} // namespace cru::ui diff --git a/src/ui/UiManager.cpp b/src/ui/UiManager.cpp new file mode 100644 index 00000000..b8effdfd --- /dev/null +++ b/src/ui/UiManager.cpp @@ -0,0 +1,81 @@ +#include "cru/ui/UiManager.hpp" + +#include "cru/platform/graph/Brush.hpp" +#include "cru/platform/graph/Factory.hpp" +#include "cru/platform/graph/Font.hpp" +#include "cru/platform/native/UiApplication.hpp" +#include "Helper.hpp" + +namespace cru::ui { +using namespace cru::platform::graph; + +namespace { +std::unique_ptr CreateSolidColorBrush(IGraphFactory* factory, + const Color& color) { + auto brush = factory->CreateSolidColorBrush(); + brush->SetColor(color); + return brush; +} +} // namespace + +UiManager* UiManager::GetInstance() { + static UiManager* instance = new UiManager(); + GetUiApplication()->AddOnQuitHandler([] { + delete instance; + instance = nullptr; + }); + return instance; +} + +UiManager::UiManager() { + const auto factory = GetGraphFactory(); + + theme_resource_.default_font = factory->CreateFont("等线", 24.0f); + + const auto black_brush = std::shared_ptr( + CreateSolidColorBrush(factory, colors::black)); + theme_resource_.text_brush = black_brush; + theme_resource_.text_selection_brush = + CreateSolidColorBrush(factory, colors::skyblue); + theme_resource_.caret_brush = black_brush; + + theme_resource_.button_style.normal.border_brush = + CreateSolidColorBrush(factory, Color::FromHex(0x00bfff)); + theme_resource_.button_style.hover.border_brush = + CreateSolidColorBrush(factory, Color::FromHex(0x47d1ff)); + theme_resource_.button_style.press.border_brush = + CreateSolidColorBrush(factory, Color::FromHex(0x91e4ff)); + theme_resource_.button_style.press_cancel.border_brush = + CreateSolidColorBrush(factory, Color::FromHex(0x91e4ff)); + + theme_resource_.button_style.normal.border_thickness = + theme_resource_.button_style.hover.border_thickness = + theme_resource_.button_style.press.border_thickness = + theme_resource_.button_style.press_cancel.border_thickness = + Thickness(3); + + theme_resource_.button_style.normal.border_radius = + theme_resource_.button_style.hover.border_radius = + theme_resource_.button_style.press.border_radius = + theme_resource_.button_style.press_cancel.border_radius = + CornerRadius({5, 5}); + + theme_resource_.text_box_border_style.normal.border_brush = + CreateSolidColorBrush(factory, Color::FromHex(0xced4da)); + theme_resource_.text_box_border_style.normal.border_radius = CornerRadius(5); + theme_resource_.text_box_border_style.normal.border_thickness = Thickness(1); + + theme_resource_.text_box_border_style.hover = + theme_resource_.text_box_border_style.normal; + + theme_resource_.text_box_border_style.normal.border_brush = + CreateSolidColorBrush(factory, Color::FromHex(0x495057)); + theme_resource_.text_box_border_style.normal.border_radius = CornerRadius(5); + theme_resource_.text_box_border_style.normal.border_thickness = Thickness(1); + + theme_resource_.text_box_border_style.focus_hover = + theme_resource_.text_box_border_style.focus; +} + +UiManager::~UiManager() = default; +} // namespace cru::ui diff --git a/src/ui/click_detector.cpp b/src/ui/click_detector.cpp deleted file mode 100644 index 3f342a31..00000000 --- a/src/ui/click_detector.cpp +++ /dev/null @@ -1,129 +0,0 @@ -#include "cru/ui/click_detector.hpp" - -#include "cru/common/logger.hpp" - -#include - -namespace cru::ui { -ClickDetector::ClickDetector(Control* control) { - Expects(control); - control_ = control; - - event_rovoker_guards_.push_back( - EventRevokerGuard(control->MouseEnterEvent()->Direct()->AddHandler( - [this](event::MouseEventArgs&) { - if (this->enable_) { - if (this->state_ == ClickState::PressInactive) { - if ((this->button_ & this->trigger_button_)) { - this->SetState(ClickState::Press); - } - } else { - this->SetState(ClickState::Hover); - } - } - }))); - - event_rovoker_guards_.push_back( - EventRevokerGuard(control->MouseLeaveEvent()->Direct()->AddHandler( - [this](event::MouseEventArgs&) { - if (this->enable_) { - if (this->state_ == ClickState::Press) { - if ((this->button_ & this->trigger_button_)) { - this->SetState(ClickState::PressInactive); - } - } else { - this->SetState(ClickState::None); - } - } - }))); - - event_rovoker_guards_.push_back( - EventRevokerGuard(control->MouseDownEvent()->Direct()->AddHandler( - [this](event::MouseButtonEventArgs& args) { - const auto button = args.GetButton(); - if (this->enable_ && (button & this->trigger_button_) && - this->state_ == ClickState::Hover) { - if (!this->control_->CaptureMouse()) { - log::Debug("Failed to capture mouse when begin click."); - return; - } - this->down_point_ = args.GetPoint(); - this->button_ = button; - this->SetState(ClickState::Press); - } - }))); - - event_rovoker_guards_.push_back( - EventRevokerGuard(control->MouseUpEvent()->Direct()->AddHandler( - [this](event::MouseButtonEventArgs& args) { - const auto button = args.GetButton(); - if (this->enable_ && (button & this->trigger_button_) && - button == button_) { - if (this->state_ == ClickState::Press) { - this->SetState(ClickState::Hover); - this->event_.Raise(ClickEventArgs{this->control_, - this->down_point_, - args.GetPoint(), button}); - this->control_->ReleaseMouse(); - } else if (this->state_ == ClickState::PressInactive) { - this->SetState(ClickState::None); - this->control_->ReleaseMouse(); - } - } - }))); -} // namespace cru::ui - -void ClickDetector::SetEnabled(bool enable) { - if (enable == enable_) { - return; - } - - enable_ = enable; - if (enable) { - SetState(control_->IsMouseOver() ? ClickState::Hover : ClickState::None); - } else { - if (state_ == ClickState::Press || state_ == ClickState::PressInactive) { - SetState(ClickState::None); - control_->ReleaseMouse(); - } else if (state_ == ClickState::Hover) { - SetState(ClickState::None); - } - } -} - -void ClickDetector::SetTriggerButton(MouseButton trigger_button) { - if (trigger_button == trigger_button_) { - return; - } - - trigger_button_ = trigger_button; - if ((state_ == ClickState::Press || state_ == ClickState::PressInactive) && - !(button_ & trigger_button)) { - SetState(control_->IsMouseOver() ? ClickState::Hover : ClickState::None); - control_->ReleaseMouse(); - } -} - -void ClickDetector::SetState(ClickState state) { -#ifdef CRU_DEBUG - auto to_string = [](ClickState state) -> std::string_view { - switch (state) { - case ClickState::None: - return "None"; - case ClickState::Hover: - return "Hover"; - case ClickState::Press: - return "Press"; - case ClickState::PressInactive: - return "PressInvactive"; - default: - UnreachableCode(); - } - }; - log::Debug("Click state changed, new state: {}.", to_string(state)); -#endif - - state_ = state; - state_change_event_.Raise(state); -} -} // namespace cru::ui diff --git a/src/ui/content_control.cpp b/src/ui/content_control.cpp deleted file mode 100644 index eb13f4cb..00000000 --- a/src/ui/content_control.cpp +++ /dev/null @@ -1,33 +0,0 @@ -#include "cru/ui/content_control.hpp" - -#include "cru/ui/window.hpp" - -namespace cru::ui { -ContentControl::ContentControl() - : child_vector_{nullptr}, child_(child_vector_[0]) {} - -ContentControl::~ContentControl() { delete child_; } - -void ContentControl::SetChild(Control* child) { - Expects(!dynamic_cast(child)); // Can't add a window as child. - if (child == child_) return; - - const auto host = GetUiHost(); - const auto old_child = child_; - child_ = child; - if (old_child) { - old_child->_SetParent(nullptr); - old_child->_SetDescendantUiHost(nullptr); - } - if (child) { - child->_SetParent(this); - child->_SetDescendantUiHost(host); - } - OnChildChanged(old_child, child); -} - -void ContentControl::OnChildChanged(Control* old_child, Control* new_child) { - CRU_UNUSED(old_child) - CRU_UNUSED(new_child) -} -} // namespace cru::ui diff --git a/src/ui/control.cpp b/src/ui/control.cpp index 5417f4ce..cd1367fe 100644 --- a/src/ui/control.cpp +++ b/src/ui/control.cpp @@ -1,10 +1,10 @@ -#include "cru/ui/control.hpp" +#include "cru/ui/Control.hpp" -#include "cru/platform/native/cursor.hpp" -#include "cru/platform/native/ui_application.hpp" -#include "cru/ui/base.hpp" -#include "cru/ui/ui_host.hpp" -#include "routed_event_dispatch.hpp" +#include "cru/platform/native/Cursor.hpp" +#include "cru/platform/native/UiApplication.hpp" +#include "cru/ui/Base.hpp" +#include "cru/ui/UiHost.hpp" +#include "RoutedEventDispatch.hpp" #include diff --git a/src/ui/controls/FlexLayout.cpp b/src/ui/controls/FlexLayout.cpp new file mode 100644 index 00000000..25f30558 --- /dev/null +++ b/src/ui/controls/FlexLayout.cpp @@ -0,0 +1,71 @@ +#include "cru/ui/controls/FlexLayout.hpp" + +#include "cru/ui/render/FlexLayoutRenderObject.hpp" + +namespace cru::ui::controls { +using render::FlexLayoutRenderObject; + +FlexLayout::FlexLayout() { + render_object_.reset(new FlexLayoutRenderObject()); + render_object_->SetAttachedControl(this); +} + +FlexLayout::~FlexLayout() = default; + +render::RenderObject* FlexLayout::GetRenderObject() const { + return render_object_.get(); +} + +namespace { +int FindPosition(render::RenderObject* parent, render::RenderObject* child) { + const auto& render_objects = parent->GetChildren(); + const auto find_result = + std::find(render_objects.cbegin(), render_objects.cend(), child); + if (find_result == render_objects.cend()) { + throw std::logic_error("Control is not a child of FlexLayout."); + } + return static_cast(find_result - render_objects.cbegin()); +} +} // namespace + +FlexChildLayoutData FlexLayout::GetChildLayoutData(Control* control) { + Expects(control); + return *render_object_->GetChildLayoutData( + FindPosition(render_object_.get(), control->GetRenderObject())); +} + +void FlexLayout::SetChildLayoutData(Control* control, + const FlexChildLayoutData& data) { + Expects(control); + *render_object_->GetChildLayoutData( + FindPosition(render_object_.get(), control->GetRenderObject())) = data; +} + +FlexMainAlignment FlexLayout::GetContentMainAlign() const { + return render_object_->GetContentMainAlign(); +} + +void FlexLayout::SetContentMainAlign(FlexMainAlignment value) { + if (value == GetContentMainAlign()) return; + render_object_->SetContentMainAlign(value); +} + +FlexDirection FlexLayout::GetFlexDirection() const { + return render_object_->GetFlexDirection(); +} + +void FlexLayout::SetFlexDirection(FlexDirection direction) { + if (direction == GetFlexDirection()) return; + render_object_->SetFlexDirection(direction); +} + +void FlexLayout::OnAddChild(Control* child, const Index position) { + render_object_->AddChild(child->GetRenderObject(), position); +} + +void FlexLayout::OnRemoveChild(Control* child, const Index position) { + CRU_UNUSED(child) + + render_object_->RemoveChild(position); +} +} // namespace cru::ui::controls diff --git a/src/ui/controls/StackLayout.cpp b/src/ui/controls/StackLayout.cpp new file mode 100644 index 00000000..ce500b79 --- /dev/null +++ b/src/ui/controls/StackLayout.cpp @@ -0,0 +1,27 @@ +#include "cru/ui/controls/StackLayout.hpp" + +#include "cru/ui/render/StackLayoutRenderObject.hpp" + +namespace cru::ui::controls { +using render::StackLayoutRenderObject; + +StackLayout::StackLayout() : render_object_(new StackLayoutRenderObject()) { + render_object_->SetAttachedControl(this); +} + +StackLayout::~StackLayout() = default; + +render::RenderObject* StackLayout::GetRenderObject() const { + return render_object_.get(); +} + +void StackLayout::OnAddChild(Control* child, const Index position) { + render_object_->AddChild(child->GetRenderObject(), position); +} + +void StackLayout::OnRemoveChild(Control* child, const Index position) { + CRU_UNUSED(child) + + render_object_->RemoveChild(position); +} +} // namespace cru::ui::controls diff --git a/src/ui/controls/TextBlock.cpp b/src/ui/controls/TextBlock.cpp new file mode 100644 index 00000000..f77e279b --- /dev/null +++ b/src/ui/controls/TextBlock.cpp @@ -0,0 +1,44 @@ +#include "cru/ui/controls/TextBlock.hpp" + +#include "cru/ui/render/CanvasRenderObject.hpp" +#include "cru/ui/render/StackLayoutRenderObject.hpp" +#include "cru/ui/render/TextRenderObject.hpp" +#include "cru/ui/UiManager.hpp" +#include "TextControlService.hpp" + +namespace cru::ui::controls { +using render::CanvasRenderObject; +using render::StackLayoutRenderObject; +using render::TextRenderObject; + +TextBlock::TextBlock() { + const auto theme_resources = UiManager::GetInstance()->GetThemeResources(); + + text_render_object_ = std::make_unique( + theme_resources->text_brush, theme_resources->default_font, + theme_resources->text_selection_brush, theme_resources->caret_brush); + + text_render_object_->SetAttachedControl(this); + + service_ = std::make_unique>(this); + service_->SetEnabled(true); +} + +TextBlock::~TextBlock() = default; + +render::RenderObject* TextBlock::GetRenderObject() const { + return text_render_object_.get(); +} + +std::string TextBlock::GetText() const { + return text_render_object_->GetText(); +} + +void TextBlock::SetText(std::string text) { + text_render_object_->SetText(std::move(text)); +} + +render::TextRenderObject* TextBlock::GetTextRenderObject() { + return text_render_object_.get(); +} +} // namespace cru::ui::controls diff --git a/src/ui/controls/TextBox.cpp b/src/ui/controls/TextBox.cpp new file mode 100644 index 00000000..64fd4c60 --- /dev/null +++ b/src/ui/controls/TextBox.cpp @@ -0,0 +1,70 @@ +#include "cru/ui/controls/TextBox.hpp" + +#include "cru/ui/render/BorderRenderObject.hpp" +#include "cru/ui/render/CanvasRenderObject.hpp" +#include "cru/ui/render/StackLayoutRenderObject.hpp" +#include "cru/ui/render/TextRenderObject.hpp" +#include "cru/ui/UiManager.hpp" +#include "TextControlService.hpp" + +namespace cru::ui::controls { +using render::BorderRenderObject; +using render::CanvasRenderObject; +using render::StackLayoutRenderObject; +using render::TextRenderObject; + +TextBox::TextBox() : border_render_object_(new BorderRenderObject()) { + const auto theme_resources = UiManager::GetInstance()->GetThemeResources(); + + border_style_ = theme_resources->text_box_border_style; + + text_render_object_ = std::make_unique( + theme_resources->text_brush, theme_resources->default_font, + theme_resources->text_selection_brush, theme_resources->caret_brush); + + border_render_object_->AddChild(text_render_object_.get(), 0); + + border_render_object_->SetAttachedControl(this); + text_render_object_->SetAttachedControl(this); + + service_ = std::make_unique>(this); + service_->SetEnabled(true); + service_->SetCaretVisible(true); + + GainFocusEvent()->Direct()->AddHandler([this](event::FocusChangeEventArgs&) { + this->service_->SetEnabled(true); + this->UpdateBorderStyle(); + }); + + LoseFocusEvent()->Direct()->AddHandler([this](event::FocusChangeEventArgs&) { + this->service_->SetEnabled(false); + this->UpdateBorderStyle(); + }); +} + +TextBox::~TextBox() {} + +render::RenderObject* TextBox::GetRenderObject() const { + return border_render_object_.get(); +} + +render::TextRenderObject* TextBox::GetTextRenderObject() { + return text_render_object_.get(); +} + +const TextBoxBorderStyle& TextBox::GetBorderStyle() { return border_style_; } + +void TextBox::SetBorderStyle(TextBoxBorderStyle border_style) { + border_style_ = std::move(border_style); +} + +void TextBox::OnMouseHoverChange(bool) { UpdateBorderStyle(); } + +void TextBox::UpdateBorderStyle() { + const auto focus = HasFocus(); + const auto hover = IsMouseOver(); + border_render_object_->SetBorderStyle( + focus ? (hover ? border_style_.focus_hover : border_style_.focus) + : (hover ? border_style_.hover : border_style_.normal)); +} +} // namespace cru::ui::controls diff --git a/src/ui/controls/TextControlService.hpp b/src/ui/controls/TextControlService.hpp new file mode 100644 index 00000000..ad0db343 --- /dev/null +++ b/src/ui/controls/TextControlService.hpp @@ -0,0 +1,227 @@ +#pragma once +#include "../Helper.hpp" +#include "cru/common/Logger.hpp" +#include "cru/platform/graph/Font.hpp" +#include "cru/platform/graph/Painter.hpp" +#include "cru/platform/native/UiApplication.hpp" +#include "cru/ui/Control.hpp" +#include "cru/ui/render/CanvasRenderObject.hpp" +#include "cru/ui/render/TextRenderObject.hpp" +#include "cru/ui/UiEvent.hpp" + +namespace cru::ui::controls { +constexpr int k_default_caret_blink_duration = 500; + +// TControl should inherits `Control` and has following methods: +// ``` +// render::TextRenderObject* GetTextRenderObject(); +// ``` +template +class TextControlService : public Object { + public: + TextControlService(TControl* control); + + CRU_DELETE_COPY(TextControlService) + CRU_DELETE_MOVE(TextControlService) + + ~TextControlService(); + + public: + bool IsEnabled() { return enable_; } + void SetEnabled(bool enable); + + bool IsCaretVisible() { return caret_visible_; } + void SetCaretVisible(bool visible); + + int GetCaretBlinkDuration() { return caret_blink_duration_; } + void SetCaretBlinkDuration(int milliseconds); + + private: + void AbortSelection(); + + void SetupCaret(); + void TearDownCaret(); + + void SetupHandlers(); + + void MouseMoveHandler(event::MouseEventArgs& args); + void MouseDownHandler(event::MouseButtonEventArgs& args); + void MouseUpHandler(event::MouseButtonEventArgs& args); + void LoseFocusHandler(event::FocusChangeEventArgs& args); + + private: + TControl* control_; + std::vector event_revoker_guards_; + + bool enable_ = false; + + bool caret_visible_ = false; + long long caret_timer_id_ = -1; + int caret_blink_duration_ = k_default_caret_blink_duration; + + // nullopt means not selecting + std::optional select_down_button_; + + // before the char + int select_start_position_; +}; + +template +TextControlService::TextControlService(TControl* control) + : control_(control) {} + +template +TextControlService::~TextControlService() { + const auto application = GetUiApplication(); + // Don't call TearDownCaret, because it use text render object of control, + // which may be destroyed already. + application->CancelTimer(this->caret_timer_id_); +} + +template +void TextControlService::SetEnabled(bool enable) { + if (enable == this->enable_) return; + if (enable) { + this->SetupHandlers(); + if (this->caret_visible_) { + this->SetupCaret(); + } + } else { + this->AbortSelection(); + this->event_revoker_guards_.clear(); + this->TearDownCaret(); + } +} + +template +void TextControlService::SetCaretVisible(bool visible) { + if (visible == this->caret_visible_) return; + + this->caret_visible_ = visible; + + if (this->enable_) { + if (visible) { + this->SetupCaret(); + } else { + this->TearDownCaret(); + } + } +} + +template +void TextControlService::SetCaretBlinkDuration(int milliseconds) { + if (this->caret_blink_duration_ == milliseconds) return; + + if (this->enable_ && this->caret_visible_) { + this->TearDownCaret(); + this->SetupCaret(); + } +} + +template +void TextControlService::AbortSelection() { + if (this->select_down_button_.has_value()) { + this->control_->ReleaseMouse(); + this->select_down_button_ = std::nullopt; + } + this->control_->GetTextRenderObject()->SetSelectionRange(std::nullopt); +} + +template +void TextControlService::SetupCaret() { + const auto application = GetUiApplication(); + + // Cancel first anyhow for safety. + application->CancelTimer(this->caret_timer_id_); + + this->control_->GetTextRenderObject()->SetDrawCaret(true); + this->caret_timer_id_ = application->SetInterval( + std::chrono::milliseconds(this->caret_blink_duration_), + [this] { this->control_->GetTextRenderObject()->ToggleDrawCaret(); }); +} + +template +void TextControlService::TearDownCaret() { + const auto application = GetUiApplication(); + application->CancelTimer(this->caret_timer_id_); + this->control_->GetTextRenderObject()->SetDrawCaret(false); +} + +template +void TextControlService::SetupHandlers() { + Expects(event_revoker_guards_.empty()); + this->event_revoker_guards_.push_back( + EventRevokerGuard{control_->MouseMoveEvent()->Direct()->AddHandler( + std::bind(&TextControlService::MouseMoveHandler, this, + std::placeholders::_1))}); + this->event_revoker_guards_.push_back( + EventRevokerGuard{control_->MouseDownEvent()->Direct()->AddHandler( + std::bind(&TextControlService::MouseDownHandler, this, + std::placeholders::_1))}); + this->event_revoker_guards_.push_back(EventRevokerGuard{ + control_->MouseUpEvent()->Direct()->AddHandler(std::bind( + &TextControlService::MouseUpHandler, this, std::placeholders::_1))}); + this->event_revoker_guards_.push_back( + EventRevokerGuard{control_->LoseFocusEvent()->Direct()->AddHandler( + std::bind(&TextControlService::LoseFocusHandler, this, + std::placeholders::_1))}); +} + +template +void TextControlService::MouseMoveHandler( + event::MouseEventArgs& args) { + if (this->select_down_button_.has_value()) { + const auto text_render_object = this->control_->GetTextRenderObject(); + const auto result = text_render_object->TextHitTest( + text_render_object->FromRootToContent(args.GetPoint())); + const auto position = result.position + (result.trailing ? 1 : 0); + log::Debug( + "TextControlService: Text selection changed on mouse move, range: {}, " + "{}.", + position, this->select_start_position_); + this->control_->GetTextRenderObject()->SetSelectionRange( + TextRange::FromTwoSides( + static_cast(position), + static_cast(this->select_start_position_))); + text_render_object->SetCaretPosition(position); + } +} + +template +void TextControlService::MouseDownHandler( + event::MouseButtonEventArgs& args) { + if (this->select_down_button_.has_value()) { + return; + } else { + if (!this->control_->CaptureMouse()) return; + if (!this->control_->RequestFocus()) return; + const auto text_render_object = this->control_->GetTextRenderObject(); + this->select_down_button_ = args.GetButton(); + const auto result = text_render_object->TextHitTest( + text_render_object->FromRootToContent(args.GetPoint())); + const auto position = result.position + (result.trailing ? 1 : 0); + text_render_object->SetSelectionRange(std::nullopt); + text_render_object->SetCaretPosition(position); + this->select_start_position_ = position; + log::Debug("TextControlService: Begin to select text, start position: {}.", + position); + } +} + +template +void TextControlService::MouseUpHandler( + event::MouseButtonEventArgs& args) { + if (this->select_down_button_.has_value() && + this->select_down_button_.value() == args.GetButton()) { + this->control_->ReleaseMouse(); + this->select_down_button_ = std::nullopt; + log::Debug("TextControlService: End selecting text."); + } +} + +template +void TextControlService::LoseFocusHandler( + event::FocusChangeEventArgs& args) { + if (!args.IsWindow()) this->AbortSelection(); +} +} // namespace cru::ui::controls diff --git a/src/ui/controls/button.cpp b/src/ui/controls/button.cpp index 7dd087ba..6f6af878 100644 --- a/src/ui/controls/button.cpp +++ b/src/ui/controls/button.cpp @@ -1,13 +1,13 @@ -#include "cru/ui/controls/button.hpp" +#include "cru/ui/controls/Button.hpp" #include -#include "../helper.hpp" -#include "cru/platform/graph/brush.hpp" -#include "cru/platform/native/cursor.hpp" -#include "cru/platform/native/ui_application.hpp" -#include "cru/ui/render/border_render_object.hpp" -#include "cru/ui/ui_manager.hpp" -#include "cru/ui/window.hpp" +#include "../Helper.hpp" +#include "cru/platform/graph/Brush.hpp" +#include "cru/platform/native/Cursor.hpp" +#include "cru/platform/native/UiApplication.hpp" +#include "cru/ui/render/BorderRenderObject.hpp" +#include "cru/ui/UiManager.hpp" +#include "cru/ui/Window.hpp" namespace cru::ui::controls { using cru::platform::native::SystemCursorType; diff --git a/src/ui/controls/container.cpp b/src/ui/controls/container.cpp index 84582d80..de58ee64 100644 --- a/src/ui/controls/container.cpp +++ b/src/ui/controls/container.cpp @@ -1,7 +1,7 @@ -#include "cru/ui/controls/container.hpp" +#include "cru/ui/controls/Container.hpp" -#include "cru/platform/graph/factory.hpp" -#include "cru/ui/render/border_render_object.hpp" +#include "cru/platform/graph/Factory.hpp" +#include "cru/ui/render/BorderRenderObject.hpp" namespace cru::ui::controls { Container::Container() { diff --git a/src/ui/controls/flex_layout.cpp b/src/ui/controls/flex_layout.cpp deleted file mode 100644 index 5412164a..00000000 --- a/src/ui/controls/flex_layout.cpp +++ /dev/null @@ -1,71 +0,0 @@ -#include "cru/ui/controls/flex_layout.hpp" - -#include "cru/ui/render/flex_layout_render_object.hpp" - -namespace cru::ui::controls { -using render::FlexLayoutRenderObject; - -FlexLayout::FlexLayout() { - render_object_.reset(new FlexLayoutRenderObject()); - render_object_->SetAttachedControl(this); -} - -FlexLayout::~FlexLayout() = default; - -render::RenderObject* FlexLayout::GetRenderObject() const { - return render_object_.get(); -} - -namespace { -int FindPosition(render::RenderObject* parent, render::RenderObject* child) { - const auto& render_objects = parent->GetChildren(); - const auto find_result = - std::find(render_objects.cbegin(), render_objects.cend(), child); - if (find_result == render_objects.cend()) { - throw std::logic_error("Control is not a child of FlexLayout."); - } - return static_cast(find_result - render_objects.cbegin()); -} -} // namespace - -FlexChildLayoutData FlexLayout::GetChildLayoutData(Control* control) { - Expects(control); - return *render_object_->GetChildLayoutData( - FindPosition(render_object_.get(), control->GetRenderObject())); -} - -void FlexLayout::SetChildLayoutData(Control* control, - const FlexChildLayoutData& data) { - Expects(control); - *render_object_->GetChildLayoutData( - FindPosition(render_object_.get(), control->GetRenderObject())) = data; -} - -FlexMainAlignment FlexLayout::GetContentMainAlign() const { - return render_object_->GetContentMainAlign(); -} - -void FlexLayout::SetContentMainAlign(FlexMainAlignment value) { - if (value == GetContentMainAlign()) return; - render_object_->SetContentMainAlign(value); -} - -FlexDirection FlexLayout::GetFlexDirection() const { - return render_object_->GetFlexDirection(); -} - -void FlexLayout::SetFlexDirection(FlexDirection direction) { - if (direction == GetFlexDirection()) return; - render_object_->SetFlexDirection(direction); -} - -void FlexLayout::OnAddChild(Control* child, const Index position) { - render_object_->AddChild(child->GetRenderObject(), position); -} - -void FlexLayout::OnRemoveChild(Control* child, const Index position) { - CRU_UNUSED(child) - - render_object_->RemoveChild(position); -} -} // namespace cru::ui::controls diff --git a/src/ui/controls/stack_layout.cpp b/src/ui/controls/stack_layout.cpp deleted file mode 100644 index 47511f33..00000000 --- a/src/ui/controls/stack_layout.cpp +++ /dev/null @@ -1,27 +0,0 @@ -#include "cru/ui/controls/stack_layout.hpp" - -#include "cru/ui/render/stack_layout_render_object.hpp" - -namespace cru::ui::controls { -using render::StackLayoutRenderObject; - -StackLayout::StackLayout() : render_object_(new StackLayoutRenderObject()) { - render_object_->SetAttachedControl(this); -} - -StackLayout::~StackLayout() = default; - -render::RenderObject* StackLayout::GetRenderObject() const { - return render_object_.get(); -} - -void StackLayout::OnAddChild(Control* child, const Index position) { - render_object_->AddChild(child->GetRenderObject(), position); -} - -void StackLayout::OnRemoveChild(Control* child, const Index position) { - CRU_UNUSED(child) - - render_object_->RemoveChild(position); -} -} // namespace cru::ui::controls diff --git a/src/ui/controls/text_block.cpp b/src/ui/controls/text_block.cpp deleted file mode 100644 index a3ec9f54..00000000 --- a/src/ui/controls/text_block.cpp +++ /dev/null @@ -1,44 +0,0 @@ -#include "cru/ui/controls/text_block.hpp" - -#include "cru/ui/render/canvas_render_object.hpp" -#include "cru/ui/render/stack_layout_render_object.hpp" -#include "cru/ui/render/text_render_object.hpp" -#include "cru/ui/ui_manager.hpp" -#include "text_control_service.hpp" - -namespace cru::ui::controls { -using render::CanvasRenderObject; -using render::StackLayoutRenderObject; -using render::TextRenderObject; - -TextBlock::TextBlock() { - const auto theme_resources = UiManager::GetInstance()->GetThemeResources(); - - text_render_object_ = std::make_unique( - theme_resources->text_brush, theme_resources->default_font, - theme_resources->text_selection_brush, theme_resources->caret_brush); - - text_render_object_->SetAttachedControl(this); - - service_ = std::make_unique>(this); - service_->SetEnabled(true); -} - -TextBlock::~TextBlock() = default; - -render::RenderObject* TextBlock::GetRenderObject() const { - return text_render_object_.get(); -} - -std::string TextBlock::GetText() const { - return text_render_object_->GetText(); -} - -void TextBlock::SetText(std::string text) { - text_render_object_->SetText(std::move(text)); -} - -render::TextRenderObject* TextBlock::GetTextRenderObject() { - return text_render_object_.get(); -} -} // namespace cru::ui::controls diff --git a/src/ui/controls/text_box.cpp b/src/ui/controls/text_box.cpp deleted file mode 100644 index 8b7dc692..00000000 --- a/src/ui/controls/text_box.cpp +++ /dev/null @@ -1,70 +0,0 @@ -#include "cru/ui/controls/text_box.hpp" - -#include "cru/ui/render/border_render_object.hpp" -#include "cru/ui/render/canvas_render_object.hpp" -#include "cru/ui/render/stack_layout_render_object.hpp" -#include "cru/ui/render/text_render_object.hpp" -#include "cru/ui/ui_manager.hpp" -#include "text_control_service.hpp" - -namespace cru::ui::controls { -using render::BorderRenderObject; -using render::CanvasRenderObject; -using render::StackLayoutRenderObject; -using render::TextRenderObject; - -TextBox::TextBox() : border_render_object_(new BorderRenderObject()) { - const auto theme_resources = UiManager::GetInstance()->GetThemeResources(); - - border_style_ = theme_resources->text_box_border_style; - - text_render_object_ = std::make_unique( - theme_resources->text_brush, theme_resources->default_font, - theme_resources->text_selection_brush, theme_resources->caret_brush); - - border_render_object_->AddChild(text_render_object_.get(), 0); - - border_render_object_->SetAttachedControl(this); - text_render_object_->SetAttachedControl(this); - - service_ = std::make_unique>(this); - service_->SetEnabled(true); - service_->SetCaretVisible(true); - - GainFocusEvent()->Direct()->AddHandler([this](event::FocusChangeEventArgs&) { - this->service_->SetEnabled(true); - this->UpdateBorderStyle(); - }); - - LoseFocusEvent()->Direct()->AddHandler([this](event::FocusChangeEventArgs&) { - this->service_->SetEnabled(false); - this->UpdateBorderStyle(); - }); -} - -TextBox::~TextBox() {} - -render::RenderObject* TextBox::GetRenderObject() const { - return border_render_object_.get(); -} - -render::TextRenderObject* TextBox::GetTextRenderObject() { - return text_render_object_.get(); -} - -const TextBoxBorderStyle& TextBox::GetBorderStyle() { return border_style_; } - -void TextBox::SetBorderStyle(TextBoxBorderStyle border_style) { - border_style_ = std::move(border_style); -} - -void TextBox::OnMouseHoverChange(bool) { UpdateBorderStyle(); } - -void TextBox::UpdateBorderStyle() { - const auto focus = HasFocus(); - const auto hover = IsMouseOver(); - border_render_object_->SetBorderStyle( - focus ? (hover ? border_style_.focus_hover : border_style_.focus) - : (hover ? border_style_.hover : border_style_.normal)); -} -} // namespace cru::ui::controls diff --git a/src/ui/controls/text_control_service.hpp b/src/ui/controls/text_control_service.hpp deleted file mode 100644 index 626423bf..00000000 --- a/src/ui/controls/text_control_service.hpp +++ /dev/null @@ -1,227 +0,0 @@ -#pragma once -#include "../helper.hpp" -#include "cru/common/logger.hpp" -#include "cru/platform/graph/font.hpp" -#include "cru/platform/graph/painter.hpp" -#include "cru/platform/native/ui_application.hpp" -#include "cru/ui/control.hpp" -#include "cru/ui/render/canvas_render_object.hpp" -#include "cru/ui/render/text_render_object.hpp" -#include "cru/ui/ui_event.hpp" - -namespace cru::ui::controls { -constexpr int k_default_caret_blink_duration = 500; - -// TControl should inherits `Control` and has following methods: -// ``` -// render::TextRenderObject* GetTextRenderObject(); -// ``` -template -class TextControlService : public Object { - public: - TextControlService(TControl* control); - - CRU_DELETE_COPY(TextControlService) - CRU_DELETE_MOVE(TextControlService) - - ~TextControlService(); - - public: - bool IsEnabled() { return enable_; } - void SetEnabled(bool enable); - - bool IsCaretVisible() { return caret_visible_; } - void SetCaretVisible(bool visible); - - int GetCaretBlinkDuration() { return caret_blink_duration_; } - void SetCaretBlinkDuration(int milliseconds); - - private: - void AbortSelection(); - - void SetupCaret(); - void TearDownCaret(); - - void SetupHandlers(); - - void MouseMoveHandler(event::MouseEventArgs& args); - void MouseDownHandler(event::MouseButtonEventArgs& args); - void MouseUpHandler(event::MouseButtonEventArgs& args); - void LoseFocusHandler(event::FocusChangeEventArgs& args); - - private: - TControl* control_; - std::vector event_revoker_guards_; - - bool enable_ = false; - - bool caret_visible_ = false; - long long caret_timer_id_ = -1; - int caret_blink_duration_ = k_default_caret_blink_duration; - - // nullopt means not selecting - std::optional select_down_button_; - - // before the char - int select_start_position_; -}; - -template -TextControlService::TextControlService(TControl* control) - : control_(control) {} - -template -TextControlService::~TextControlService() { - const auto application = GetUiApplication(); - // Don't call TearDownCaret, because it use text render object of control, - // which may be destroyed already. - application->CancelTimer(this->caret_timer_id_); -} - -template -void TextControlService::SetEnabled(bool enable) { - if (enable == this->enable_) return; - if (enable) { - this->SetupHandlers(); - if (this->caret_visible_) { - this->SetupCaret(); - } - } else { - this->AbortSelection(); - this->event_revoker_guards_.clear(); - this->TearDownCaret(); - } -} - -template -void TextControlService::SetCaretVisible(bool visible) { - if (visible == this->caret_visible_) return; - - this->caret_visible_ = visible; - - if (this->enable_) { - if (visible) { - this->SetupCaret(); - } else { - this->TearDownCaret(); - } - } -} - -template -void TextControlService::SetCaretBlinkDuration(int milliseconds) { - if (this->caret_blink_duration_ == milliseconds) return; - - if (this->enable_ && this->caret_visible_) { - this->TearDownCaret(); - this->SetupCaret(); - } -} - -template -void TextControlService::AbortSelection() { - if (this->select_down_button_.has_value()) { - this->control_->ReleaseMouse(); - this->select_down_button_ = std::nullopt; - } - this->control_->GetTextRenderObject()->SetSelectionRange(std::nullopt); -} - -template -void TextControlService::SetupCaret() { - const auto application = GetUiApplication(); - - // Cancel first anyhow for safety. - application->CancelTimer(this->caret_timer_id_); - - this->control_->GetTextRenderObject()->SetDrawCaret(true); - this->caret_timer_id_ = application->SetInterval( - std::chrono::milliseconds(this->caret_blink_duration_), - [this] { this->control_->GetTextRenderObject()->ToggleDrawCaret(); }); -} - -template -void TextControlService::TearDownCaret() { - const auto application = GetUiApplication(); - application->CancelTimer(this->caret_timer_id_); - this->control_->GetTextRenderObject()->SetDrawCaret(false); -} - -template -void TextControlService::SetupHandlers() { - Expects(event_revoker_guards_.empty()); - this->event_revoker_guards_.push_back( - EventRevokerGuard{control_->MouseMoveEvent()->Direct()->AddHandler( - std::bind(&TextControlService::MouseMoveHandler, this, - std::placeholders::_1))}); - this->event_revoker_guards_.push_back( - EventRevokerGuard{control_->MouseDownEvent()->Direct()->AddHandler( - std::bind(&TextControlService::MouseDownHandler, this, - std::placeholders::_1))}); - this->event_revoker_guards_.push_back(EventRevokerGuard{ - control_->MouseUpEvent()->Direct()->AddHandler(std::bind( - &TextControlService::MouseUpHandler, this, std::placeholders::_1))}); - this->event_revoker_guards_.push_back( - EventRevokerGuard{control_->LoseFocusEvent()->Direct()->AddHandler( - std::bind(&TextControlService::LoseFocusHandler, this, - std::placeholders::_1))}); -} - -template -void TextControlService::MouseMoveHandler( - event::MouseEventArgs& args) { - if (this->select_down_button_.has_value()) { - const auto text_render_object = this->control_->GetTextRenderObject(); - const auto result = text_render_object->TextHitTest( - text_render_object->FromRootToContent(args.GetPoint())); - const auto position = result.position + (result.trailing ? 1 : 0); - log::Debug( - "TextControlService: Text selection changed on mouse move, range: {}, " - "{}.", - position, this->select_start_position_); - this->control_->GetTextRenderObject()->SetSelectionRange( - TextRange::FromTwoSides( - static_cast(position), - static_cast(this->select_start_position_))); - text_render_object->SetCaretPosition(position); - } -} - -template -void TextControlService::MouseDownHandler( - event::MouseButtonEventArgs& args) { - if (this->select_down_button_.has_value()) { - return; - } else { - if (!this->control_->CaptureMouse()) return; - if (!this->control_->RequestFocus()) return; - const auto text_render_object = this->control_->GetTextRenderObject(); - this->select_down_button_ = args.GetButton(); - const auto result = text_render_object->TextHitTest( - text_render_object->FromRootToContent(args.GetPoint())); - const auto position = result.position + (result.trailing ? 1 : 0); - text_render_object->SetSelectionRange(std::nullopt); - text_render_object->SetCaretPosition(position); - this->select_start_position_ = position; - log::Debug("TextControlService: Begin to select text, start position: {}.", - position); - } -} - -template -void TextControlService::MouseUpHandler( - event::MouseButtonEventArgs& args) { - if (this->select_down_button_.has_value() && - this->select_down_button_.value() == args.GetButton()) { - this->control_->ReleaseMouse(); - this->select_down_button_ = std::nullopt; - log::Debug("TextControlService: End selecting text."); - } -} - -template -void TextControlService::LoseFocusHandler( - event::FocusChangeEventArgs& args) { - if (!args.IsWindow()) this->AbortSelection(); -} -} // namespace cru::ui::controls diff --git a/src/ui/helper.cpp b/src/ui/helper.cpp index 4d5d8665..6f67e701 100644 --- a/src/ui/helper.cpp +++ b/src/ui/helper.cpp @@ -1,7 +1,7 @@ -#include "helper.hpp" +#include "Helper.hpp" -#include "cru/platform/graph/factory.hpp" -#include "cru/platform/native/ui_application.hpp" +#include "cru/platform/graph/Factory.hpp" +#include "cru/platform/native/UiApplication.hpp" namespace cru::ui { using cru::platform::graph::IGraphFactory; diff --git a/src/ui/helper.hpp b/src/ui/helper.hpp index 4fd14aa6..6923852f 100644 --- a/src/ui/helper.hpp +++ b/src/ui/helper.hpp @@ -1,5 +1,5 @@ #pragma once -#include "cru/ui/base.hpp" +#include "cru/ui/Base.hpp" namespace cru::platform { namespace graph { diff --git a/src/ui/layout_control.cpp b/src/ui/layout_control.cpp deleted file mode 100644 index 1d5d1ede..00000000 --- a/src/ui/layout_control.cpp +++ /dev/null @@ -1,53 +0,0 @@ -#include "cru/ui/layout_control.hpp" - -#include "cru/ui/window.hpp" - -namespace cru::ui { -LayoutControl::~LayoutControl() { - for (const auto child : children_) delete child; -} - -void LayoutControl::AddChild(Control* control, const Index position) { - Expects(control->GetParent() == - nullptr); // The control already has a parent. - Expects(!dynamic_cast(control)); // Can't add a window as child. - Expects(position >= 0); - Expects(position <= - static_cast( - this->children_.size())); // The position is out of range. - - children_.insert(this->children_.cbegin() + position, control); - - control->_SetParent(this); - control->_SetDescendantUiHost(GetUiHost()); - - OnAddChild(control, position); -} - -void LayoutControl::RemoveChild(const Index position) { - Expects(position >= 0); - Expects(position < - static_cast( - this->children_.size())); // The position is out of range. - - const auto i = children_.cbegin() + position; - const auto child = *i; - - children_.erase(i); - - child->_SetParent(nullptr); - child->_SetDescendantUiHost(nullptr); - - OnRemoveChild(child, position); -} - -void LayoutControl::OnAddChild(Control* child, const Index position) { - CRU_UNUSED(child) - CRU_UNUSED(position) -} - -void LayoutControl::OnRemoveChild(Control* child, const Index position) { - CRU_UNUSED(child) - CRU_UNUSED(position) -} -} // namespace cru::ui diff --git a/src/ui/no_child_control.cpp b/src/ui/no_child_control.cpp deleted file mode 100644 index 81299411..00000000 --- a/src/ui/no_child_control.cpp +++ /dev/null @@ -1,5 +0,0 @@ -#include "cru/ui/no_child_control.hpp" - -namespace cru::ui { -const std::vector NoChildControl::empty_control_vector{}; -} diff --git a/src/ui/render/BorderRenderObject.cpp b/src/ui/render/BorderRenderObject.cpp new file mode 100644 index 00000000..a656cb99 --- /dev/null +++ b/src/ui/render/BorderRenderObject.cpp @@ -0,0 +1,236 @@ +#include "cru/ui/render/BorderRenderObject.hpp" + +#include "../Helper.hpp" +#include "cru/common/Logger.hpp" +#include "cru/platform/graph/Factory.hpp" +#include "cru/platform/graph/Geometry.hpp" +#include "cru/platform/graph/util/Painter.hpp" + +#include + +namespace cru::ui::render { +BorderRenderObject::BorderRenderObject() { + SetChildMode(ChildMode::Single); + RecreateGeometry(); +} + +BorderRenderObject::~BorderRenderObject() {} + +void BorderRenderObject::Draw(platform::graph::IPainter* painter) { + if (background_brush_ != nullptr) + painter->FillGeometry(border_inner_geometry_.get(), + background_brush_.get()); + if (is_border_enabled_) { + if (border_brush_ == nullptr) { + log::Warn("Border is enabled but brush is null"); + } else { + painter->FillGeometry(geometry_.get(), border_brush_.get()); + } + } + if (const auto child = GetChild()) { + auto offset = child->GetOffset(); + platform::graph::util::WithTransform( + painter, platform::Matrix::Translation(offset.x, offset.y), + [child](auto p) { child->Draw(p); }); + } + if (foreground_brush_ != nullptr) + painter->FillGeometry(border_inner_geometry_.get(), + foreground_brush_.get()); +} + +void BorderRenderObject::SetBorderStyle(const BorderStyle& style) { + border_brush_ = style.border_brush; + border_thickness_ = style.border_thickness; + border_radius_ = style.border_radius; + foreground_brush_ = style.foreground_brush; + background_brush_ = style.background_brush; + InvalidateLayout(); +} + +RenderObject* BorderRenderObject::HitTest(const Point& point) { + if (const auto child = GetChild()) { + auto offset = child->GetOffset(); + Point p{point.x - offset.x, point.y - offset.y}; + const auto result = child->HitTest(p); + if (result != nullptr) { + return result; + } + } + + if (is_border_enabled_) { + const auto contains = + border_outer_geometry_->FillContains(Point{point.x, point.y}); + return contains ? this : nullptr; + } else { + const auto margin = GetMargin(); + const auto size = GetSize(); + return Rect{margin.left, margin.top, + std::max(size.width - margin.GetHorizontalTotal(), 0.0f), + std::max(size.height - margin.GetVerticalTotal(), 0.0f)} + .IsPointInside(point) + ? this + : nullptr; + } +} + +void BorderRenderObject::OnMeasureCore(const Size& available_size) { + const auto margin = GetMargin(); + const auto padding = GetPadding(); + Size margin_border_padding_size{ + margin.GetHorizontalTotal() + padding.GetHorizontalTotal(), + margin.GetVerticalTotal() + padding.GetVerticalTotal()}; + + if (is_border_enabled_) { + margin_border_padding_size.width += border_thickness_.GetHorizontalTotal(); + margin_border_padding_size.height += border_thickness_.GetVerticalTotal(); + } + + auto coerced_margin_border_padding_size = margin_border_padding_size; + if (coerced_margin_border_padding_size.width > available_size.width) { + log::Warn( + "Measure: horizontal length of padding, border and margin is bigger " + "than available length."); + coerced_margin_border_padding_size.width = available_size.width; + } + if (coerced_margin_border_padding_size.height > available_size.height) { + log::Warn( + "Measure: vertical length of padding, border and margin is bigger " + "than available length."); + coerced_margin_border_padding_size.height = available_size.height; + } + + const auto coerced_content_available_size = + available_size - coerced_margin_border_padding_size; + + const auto actual_content_size = + OnMeasureContent(coerced_content_available_size); + + SetPreferredSize(coerced_margin_border_padding_size + actual_content_size); +} + +void BorderRenderObject::OnLayoutCore(const Rect& rect) { + const auto margin = GetMargin(); + const auto padding = GetPadding(); + Size margin_border_padding_size{ + margin.GetHorizontalTotal() + padding.GetHorizontalTotal(), + margin.GetVerticalTotal() + padding.GetVerticalTotal()}; + + if (is_border_enabled_) { + margin_border_padding_size.width += border_thickness_.GetHorizontalTotal(); + margin_border_padding_size.height += border_thickness_.GetVerticalTotal(); + } + + const auto content_available_size = + rect.GetSize() - margin_border_padding_size; + auto coerced_content_available_size = content_available_size; + + if (coerced_content_available_size.width < 0) { + log::Warn( + "Layout: horizontal length of padding, border and margin is bigger " + "than available length."); + coerced_content_available_size.width = 0; + } + if (coerced_content_available_size.height < 0) { + log::Warn( + "Layout: vertical length of padding, border and margin is bigger " + "than available length."); + coerced_content_available_size.height = 0; + } + + OnLayoutContent( + Rect{margin.left + (is_border_enabled_ ? border_thickness_.left : 0) + + padding.left, + margin.top + (is_border_enabled_ ? border_thickness_.top : 0) + + padding.top, + coerced_content_available_size.width, + coerced_content_available_size.height}); +} + +Size BorderRenderObject::OnMeasureContent(const Size& available_size) { + const auto child = GetChild(); + if (child) { + child->Measure(available_size); + return child->GetPreferredSize(); + } else { + return Size{}; + } +} + +void BorderRenderObject::OnLayoutContent(const Rect& content_rect) { + const auto child = GetChild(); + if (child) { + child->Layout(content_rect); + } +} + +void BorderRenderObject::OnAfterLayout() { RecreateGeometry(); } + +void BorderRenderObject::RecreateGeometry() { + geometry_.reset(); + border_outer_geometry_.reset(); + + Thickness t = border_thickness_; + t.left /= 2.0; + t.top /= 2.0; + t.right /= 2.0; + t.bottom /= 2.0; + const CornerRadius& r = border_radius_; + + CornerRadius outer_radius(r.left_top + Point{t.left, t.top}, + r.right_top + Point{t.right, t.top}, + r.left_bottom + Point{t.left, t.bottom}, + r.right_bottom + Point{t.right, t.bottom}); + + CornerRadius inner_radius(r.left_top - Point{t.left, t.top}, + r.right_top - Point{t.right, t.top}, + r.left_bottom - Point{t.left, t.bottom}, + r.right_bottom - Point{t.right, t.bottom}); + + auto f = [](platform::graph::IGeometryBuilder* builder, const Rect& rect, + const CornerRadius& corner) { + builder->BeginFigure(Point(rect.left + corner.left_top.x, rect.top)); + builder->LineTo(Point(rect.GetRight() - corner.right_top.x, rect.top)); + builder->QuadraticBezierTo( + Point(rect.GetRight(), rect.top), + Point(rect.GetRight(), rect.top + corner.right_top.y)); + builder->LineTo( + Point(rect.GetRight(), rect.GetBottom() - corner.right_bottom.y)); + builder->QuadraticBezierTo( + Point(rect.GetRight(), rect.GetBottom()), + Point(rect.GetRight() - corner.right_bottom.x, rect.GetBottom())); + builder->LineTo(Point(rect.left + corner.left_bottom.x, rect.GetBottom())); + builder->QuadraticBezierTo( + Point(rect.left, rect.GetBottom()), + Point(rect.left, rect.GetBottom() - corner.left_bottom.y)); + builder->LineTo(Point(rect.left, rect.top + corner.left_top.y)); + builder->QuadraticBezierTo(Point(rect.left, rect.top), + Point(rect.left + corner.left_top.x, rect.top)); + builder->CloseFigure(true); + }; + + const auto size = GetSize(); + const auto margin = GetMargin(); + const Rect outer_rect{margin.left, margin.top, + size.width - margin.GetHorizontalTotal(), + size.height - margin.GetVerticalTotal()}; + const auto graph_factory = GetGraphFactory(); + std::unique_ptr builder{ + graph_factory->CreateGeometryBuilder()}; + f(builder.get(), outer_rect, outer_radius); + border_outer_geometry_ = builder->Build(); + builder.reset(); + + const Rect inner_rect = outer_rect.Shrink(border_thickness_); + + builder = graph_factory->CreateGeometryBuilder(); + f(builder.get(), inner_rect, inner_radius); + border_inner_geometry_ = builder->Build(); + builder.reset(); + + builder = graph_factory->CreateGeometryBuilder(); + f(builder.get(), outer_rect, outer_radius); + f(builder.get(), inner_rect, inner_radius); + geometry_ = builder->Build(); + builder.reset(); +} +} // namespace cru::ui::render diff --git a/src/ui/render/CanvasRenderObject.cpp b/src/ui/render/CanvasRenderObject.cpp new file mode 100644 index 00000000..16ac9239 --- /dev/null +++ b/src/ui/render/CanvasRenderObject.cpp @@ -0,0 +1,28 @@ +#include "cru/ui/render/CanvasRenderObject.hpp" + +#include "cru/ui/render/LayoutUtility.hpp" + +namespace cru::ui::render { +CanvasRenderObject::CanvasRenderObject() : RenderObject(ChildMode::None) {} + +CanvasRenderObject::~CanvasRenderObject() = default; + +void CanvasRenderObject::Draw(platform::graph::IPainter* painter) { + const auto rect = GetContentRect(); + CanvasPaintEventArgs args{painter, rect}; + paint_event_.Raise(args); +} + +RenderObject* CanvasRenderObject::HitTest(const Point& point) { + const auto padding_rect = GetPaddingRect(); + return padding_rect.IsPointInside(point) ? this : nullptr; +} + +Size CanvasRenderObject::OnMeasureContent(const Size& available_size) { + return Min(available_size, GetDesiredSize()); +} + +void CanvasRenderObject::OnLayoutContent(const Rect& content_rect) { + CRU_UNUSED(content_rect) +} +} // namespace cru::ui::render diff --git a/src/ui/render/FlexLayoutRenderObject.cpp b/src/ui/render/FlexLayoutRenderObject.cpp new file mode 100644 index 00000000..b609fd97 --- /dev/null +++ b/src/ui/render/FlexLayoutRenderObject.cpp @@ -0,0 +1,197 @@ +#include "cru/ui/render/FlexLayoutRenderObject.hpp" + +#include "cru/platform/graph/util/Painter.hpp" + +#include +#include + +namespace cru::ui::render { +Size FlexLayoutRenderObject::OnMeasureContent(const Size& available_size) { + std::vector has_basis_children; + std::vector no_basis_children; + std::vector grow_children; + std::vector shrink_chilren; + const auto child_count = GetChildCount(); + for (int i = 0; i < child_count; i++) { + const auto& layout_data = *GetChildLayoutData(i); + if (layout_data.flex_basis.has_value()) + has_basis_children.push_back(i); + else + no_basis_children.push_back(i); + if (layout_data.flex_grow > 0) grow_children.push_back(i); + if (layout_data.flex_shrink > 0) shrink_chilren.push_back(i); + } + + std::function get_main_length; + std::function get_cross_length; + std::function create_size; + + if (direction_ == FlexDirection::Horizontal || + direction_ == FlexDirection::HorizontalReverse) { + get_main_length = [](const Size& size) { return size.width; }; + get_cross_length = [](const Size& size) { return size.height; }; + create_size = [](float main, float cross) { return Size(main, cross); }; + } else { + get_main_length = [](const Size& size) { return size.height; }; + get_cross_length = [](const Size& size) { return size.width; }; + create_size = [](float main, float cross) { return Size(cross, main); }; + } + + const auto& children = GetChildren(); + + float remain_main_length = get_main_length(available_size); + float max_cross_length = 0; + + for (const int i : has_basis_children) { + const auto child = children[i]; + const float basis = GetChildLayoutData(i)->flex_basis.value(); + child->Measure(create_size(basis, get_cross_length(available_size))); + remain_main_length -= basis; + const float child_preferred_cross_length = + get_cross_length(child->GetPreferredSize()); + child->SetPreferredSize(create_size(basis, child_preferred_cross_length)); + max_cross_length = std::max(max_cross_length, child_preferred_cross_length); + } + + for (const int i : no_basis_children) { + const auto child = children[i]; + child->Measure(create_size(remain_main_length > 0 ? remain_main_length : 0, + get_cross_length(available_size))); + remain_main_length -= get_main_length(child->GetPreferredSize()); + max_cross_length = + std::max(max_cross_length, get_cross_length(child->GetPreferredSize())); + } + + if (remain_main_length > 0) { + float total_grow = 0; + for (const int i : grow_children) + total_grow += GetChildLayoutData(i)->flex_grow; + + for (const int i : grow_children) { + const float distributed_grow_length = + remain_main_length * (GetChildLayoutData(i)->flex_grow / total_grow); + const auto child = children[i]; + const float new_main_length = + get_main_length(child->GetPreferredSize()) + distributed_grow_length; + child->Measure( + create_size(new_main_length, get_cross_length(available_size))); + const float new_child_preferred_cross_length = + get_cross_length(child->GetPreferredSize()); + child->SetPreferredSize( + create_size(new_main_length, new_child_preferred_cross_length)); + max_cross_length = + std::max(max_cross_length, new_child_preferred_cross_length); + } + } + + if (remain_main_length < 0) { + float total_shrink = 0; + for (const int i : shrink_chilren) + total_shrink += GetChildLayoutData(i)->flex_shrink; + + for (const int i : shrink_chilren) { + const float distributed_shrink_length = // negative + remain_main_length * + (GetChildLayoutData(i)->flex_shrink / total_shrink); + const auto child = children[i]; + float new_main_length = get_main_length(child->GetPreferredSize()) + + distributed_shrink_length; + new_main_length = new_main_length > 0 ? new_main_length : 0; + child->Measure( + create_size(new_main_length, get_cross_length(available_size))); + const float new_child_preferred_cross_length = + get_cross_length(child->GetPreferredSize()); + child->SetPreferredSize( + create_size(new_main_length, new_child_preferred_cross_length)); + max_cross_length = + std::max(max_cross_length, new_child_preferred_cross_length); + } + } + + return create_size(get_main_length(available_size) - + (remain_main_length > 0 ? remain_main_length : 0), + max_cross_length); +} + +void FlexLayoutRenderObject::OnLayoutContent(const Rect& content_rect) { + auto calculate_anchor = [](int alignment, float start_point, + float total_length, + float content_length) -> float { + switch (alignment) { + case internal::align_start: + return start_point; + case internal::align_center: + return start_point + (total_length - content_length) / 2.0f; + case internal::align_end: + return start_point + total_length - content_length; + default: + return 0; + } + }; + + const auto& children = GetChildren(); + if (direction_ == FlexDirection::Horizontal || + direction_ == FlexDirection::HorizontalReverse) { + float actual_content_width = 0; + for (const auto child : children) { + actual_content_width += child->GetPreferredSize().width; + } + + const float content_anchor_x = + calculate_anchor(static_cast(content_main_align_), 0, + content_rect.width, actual_content_width); + + float anchor_x = 0; + for (int i = 0; i < static_cast(children.size()); i++) { + const auto child = children[i]; + const auto size = child->GetPreferredSize(); + + float real_anchor_x = anchor_x + content_anchor_x; + if (direction_ == FlexDirection::Horizontal) + real_anchor_x = content_rect.left + real_anchor_x; + else + real_anchor_x = content_rect.GetRight() - real_anchor_x; + child->Layout(Rect{ + real_anchor_x, + calculate_anchor( + static_cast(GetChildLayoutData(i)->cross_alignment.value_or( + this->item_cross_align_)), + content_rect.top, content_rect.height, size.height), + size.width, size.height}); + + anchor_x += size.width; + } + } else { + float actual_content_height = 0; + for (const auto child : children) { + actual_content_height = child->GetPreferredSize().height; + } + + const float content_anchor_y = + calculate_anchor(static_cast(content_main_align_), 0, + content_rect.height, actual_content_height); + + float anchor_y = 0; + for (int i = 0; i < static_cast(children.size()); i++) { + const auto child = children[i]; + const auto size = child->GetPreferredSize(); + + float real_anchor_y = anchor_y + content_anchor_y; + if (direction_ == FlexDirection::Vertical) { + real_anchor_y = content_rect.top + real_anchor_y; + } else { + real_anchor_y = content_rect.GetBottom() - real_anchor_y; + } + child->Layout(Rect{ + real_anchor_y, + calculate_anchor( + static_cast(GetChildLayoutData(i)->cross_alignment.value_or( + this->item_cross_align_)), + content_rect.left, content_rect.width, size.width), + size.width, size.height}); + + anchor_y += size.height; + } + } +} +} // namespace cru::ui::render diff --git a/src/ui/render/LayoutUtility.cpp b/src/ui/render/LayoutUtility.cpp new file mode 100644 index 00000000..03252f1c --- /dev/null +++ b/src/ui/render/LayoutUtility.cpp @@ -0,0 +1,15 @@ +#include "cru/ui/render/LayoutUtility.hpp" + +#include + +namespace cru::ui::render { +Size Min(const Size& left, const Size& right) { + return Size{std::min(left.width, right.width), + std::min(left.height, right.height)}; +} + +Size Max(const Size& left, const Size& right) { + return Size{std::max(left.width, right.width), + std::max(left.height, right.height)}; +} +} // namespace cru::ui::render diff --git a/src/ui/render/RenderObject.cpp b/src/ui/render/RenderObject.cpp new file mode 100644 index 00000000..83e306a8 --- /dev/null +++ b/src/ui/render/RenderObject.cpp @@ -0,0 +1,190 @@ +#include "cru/ui/render/RenderObject.hpp" + +#include "cru/common/Logger.hpp" +#include "cru/ui/UiHost.hpp" + +#include + +namespace cru::ui::render { +void RenderObject::AddChild(RenderObject* render_object, const Index position) { + Expects(child_mode_ != ChildMode::None); + Expects(!(child_mode_ == ChildMode::Single && children_.size() > 0)); + + Expects(render_object->GetParent() == + nullptr); // Render object already has a parent. + Expects(position >= 0); // Position index is less than 0. + Expects( + position <= + static_cast(children_.size())); // Position index is out of bound. + + children_.insert(children_.cbegin() + position, render_object); + render_object->SetParent(this); + render_object->SetRenderHostRecursive(GetUiHost()); + OnAddChild(render_object, position); +} + +void RenderObject::RemoveChild(const Index position) { + Expects(position >= 0); // Position index is less than 0. + Expects(position < static_cast( + children_.size())); // Position index is out of bound. + + const auto i = children_.cbegin() + position; + const auto removed_child = *i; + children_.erase(i); + removed_child->SetParent(nullptr); + removed_child->SetRenderHostRecursive(nullptr); + OnRemoveChild(removed_child, position); +} + +Point RenderObject::GetTotalOffset() const { + Point result{}; + const RenderObject* render_object = this; + + while (render_object != nullptr) { + const auto o = render_object->GetOffset(); + result.x += o.x; + result.y += o.y; + render_object = render_object->GetParent(); + } + + return result; +} + +Point RenderObject::FromRootToContent(const Point& point) const { + const auto offset = GetTotalOffset(); + const auto rect = GetContentRect(); + return Point{point.x - (offset.x + rect.left), + point.y - (offset.y + rect.top)}; +} + +void RenderObject::Measure(const Size& available_size) { + OnMeasureCore(available_size); +} + +void RenderObject::Layout(const Rect& rect) { + SetOffset(rect.GetLeftTop()); + SetSize(rect.GetSize()); + OnLayoutCore(Rect{Point{}, rect.GetSize()}); +} + +void RenderObject::OnParentChanged(RenderObject* old_parent, + RenderObject* new_parent) { + CRU_UNUSED(old_parent) + CRU_UNUSED(new_parent) +} + +void RenderObject::OnAddChild(RenderObject* new_child, Index position) { + CRU_UNUSED(new_child) + CRU_UNUSED(position) + + InvalidateLayout(); + InvalidatePaint(); +} + +void RenderObject::OnRemoveChild(RenderObject* removed_child, Index position) { + CRU_UNUSED(removed_child) + CRU_UNUSED(position) + + InvalidateLayout(); + InvalidatePaint(); +} + +void RenderObject::OnMeasureCore(const Size& available_size) { + Size margin_padding_size{ + margin_.GetHorizontalTotal() + padding_.GetHorizontalTotal(), + margin_.GetVerticalTotal() + padding_.GetVerticalTotal()}; + + auto coerced_margin_padding_size = margin_padding_size; + if (coerced_margin_padding_size.width > available_size.width) { + log::Warn( + "Measure: horizontal length of padding and margin is bigger than " + "available length."); + coerced_margin_padding_size.width = available_size.width; + } + if (coerced_margin_padding_size.height > available_size.height) { + log::Warn( + "Measure: vertical length of padding and margin is bigger than " + "available length."); + coerced_margin_padding_size.height = available_size.height; + } + + const auto coerced_content_available_size = + available_size - coerced_margin_padding_size; + const auto actual_content_size = + OnMeasureContent(coerced_content_available_size); + + SetPreferredSize(coerced_margin_padding_size + actual_content_size); +} + +void RenderObject::OnLayoutCore(const Rect& rect) { + Size margin_padding_size{ + margin_.GetHorizontalTotal() + padding_.GetHorizontalTotal(), + margin_.GetVerticalTotal() + padding_.GetVerticalTotal()}; + const auto content_available_size = rect.GetSize() - margin_padding_size; + auto coerced_content_available_size = content_available_size; + + if (coerced_content_available_size.width < 0) { + log::Warn( + "Layout: horizontal length of padding and margin is bigger than " + "available length."); + coerced_content_available_size.width = 0; + } + if (coerced_content_available_size.height < 0) { + log::Warn( + "Layout: vertical length of padding and margin is bigger than " + "available length."); + coerced_content_available_size.height = 0; + } + + OnLayoutContent(Rect{margin_.left + padding_.left, margin_.top + padding_.top, + coerced_content_available_size.width, + coerced_content_available_size.height}); +} + +void RenderObject::OnAfterLayout() {} + +Rect RenderObject::GetPaddingRect() const { + Rect rect{Point{}, GetSize()}; + rect = rect.Shrink(GetMargin()); + rect.width = std::max(rect.width, 0.0f); + rect.height = std::max(rect.height, 0.0f); + return rect; +} + +Rect RenderObject::GetContentRect() const { + Rect rect{Point{}, GetSize()}; + rect = rect.Shrink(GetMargin()); + rect = rect.Shrink(GetPadding()); + rect.width = std::max(rect.width, 0.0f); + rect.height = std::max(rect.height, 0.0f); + return rect; +} + +void RenderObject::SetParent(RenderObject* new_parent) { + const auto old_parent = parent_; + parent_ = new_parent; + OnParentChanged(old_parent, new_parent); +} + +void RenderObject::InvalidateLayout() { + if (ui_host_ != nullptr) ui_host_->InvalidateLayout(); +} + +void RenderObject::InvalidatePaint() { + if (ui_host_ != nullptr) ui_host_->InvalidatePaint(); +} + +void RenderObject::NotifyAfterLayoutRecursive(RenderObject* render_object) { + render_object->OnAfterLayout(); + for (const auto o : render_object->GetChildren()) { + NotifyAfterLayoutRecursive(o); + } +} + +void RenderObject::SetRenderHostRecursive(UiHost* host) { + ui_host_ = host; + for (const auto child : GetChildren()) { + child->SetRenderHostRecursive(host); + } +} +} // namespace cru::ui::render diff --git a/src/ui/render/ScrollRenderObject.cpp b/src/ui/render/ScrollRenderObject.cpp new file mode 100644 index 00000000..33e9e407 --- /dev/null +++ b/src/ui/render/ScrollRenderObject.cpp @@ -0,0 +1 @@ +#include "cru/ui/render/ScrollRenderObject.hpp" diff --git a/src/ui/render/StackLayoutRenderObject.cpp b/src/ui/render/StackLayoutRenderObject.cpp new file mode 100644 index 00000000..d08e136d --- /dev/null +++ b/src/ui/render/StackLayoutRenderObject.cpp @@ -0,0 +1,49 @@ +#include "cru/ui/render/StackLayoutRenderObject.hpp" + +#include + +namespace cru::ui::render { +Size StackLayoutRenderObject::OnMeasureContent(const Size& available_size) { + auto size = Size{}; + for (const auto child : GetChildren()) { + child->Measure(available_size); + const auto& preferred_size = child->GetPreferredSize(); + size.width = std::max(size.width, preferred_size.width); + size.height = std::max(size.height, preferred_size.height); + } + return size; +} + +void StackLayoutRenderObject::OnLayoutContent(const Rect& rect) { + auto calculate_anchor = [](int alignment, float start_point, + float total_length, + float content_length) -> float { + switch (alignment) { + case internal::align_start: + return start_point; + case internal::align_center: + return start_point + (total_length - content_length) / 2.0f; + case internal::align_end: + return start_point + total_length - content_length; + default: + return 0; + } + }; + + const auto count = GetChildCount(); + const auto& children = GetChildren(); + + for (int i = 0; i < count; i++) { + const auto layout_data = GetChildLayoutData(i); + const auto child = children[i]; + const auto& size = child->GetPreferredSize(); + child->Layout( + Rect{calculate_anchor(static_cast(layout_data->horizontal), + rect.left, rect.width, size.width), + calculate_anchor(static_cast(layout_data->vertical), rect.top, + rect.height, size.height), + size.width, size.height}); + } +} + +} // namespace cru::ui::render diff --git a/src/ui/render/TextRenderObject.cpp b/src/ui/render/TextRenderObject.cpp new file mode 100644 index 00000000..05acd086 --- /dev/null +++ b/src/ui/render/TextRenderObject.cpp @@ -0,0 +1,177 @@ +#include "cru/ui/render/TextRenderObject.hpp" + +#include "../Helper.hpp" +#include "cru/platform/graph/Factory.hpp" +#include "cru/platform/graph/TextLayout.hpp" +#include "cru/platform/graph/util/Painter.hpp" + +#include + +namespace cru::ui::render { +TextRenderObject::TextRenderObject( + std::shared_ptr brush, + std::shared_ptr font, + std::shared_ptr selection_brush, + std::shared_ptr caret_brush) { + Expects(brush); + Expects(font); + Expects(selection_brush); + Expects(caret_brush); + + SetChildMode(ChildMode::None); + + brush.swap(brush_); + font.swap(font_); + selection_brush.swap(selection_brush_); + caret_brush.swap(caret_brush_); + + const auto graph_factory = GetGraphFactory(); + text_layout_ = graph_factory->CreateTextLayout(font_, ""); +} + +TextRenderObject::~TextRenderObject() = default; + +std::string TextRenderObject::GetText() const { + return text_layout_->GetText(); +} + +void TextRenderObject::SetText(std::string new_text) { + text_layout_->SetText(std::move(new_text)); +} + +void TextRenderObject::SetBrush( + std::shared_ptr new_brush) { + Expects(new_brush); + new_brush.swap(brush_); + InvalidatePaint(); +} + +std::shared_ptr TextRenderObject::GetFont() const { + return text_layout_->GetFont(); +} + +void TextRenderObject::SetFont(std::shared_ptr font) { + Expects(font); + text_layout_->SetFont(std::move(font)); +} + +std::vector TextRenderObject::TextRangeRect(const TextRange& text_range) { + return text_layout_->TextRangeRect(text_range); +} + +Point TextRenderObject::TextSinglePoint(gsl::index position, bool trailing) { + return text_layout_->TextSinglePoint(position, trailing); +} + +platform::graph::TextHitTestResult TextRenderObject::TextHitTest( + const Point& point) { + return text_layout_->HitTest(point); +} + +void TextRenderObject::SetSelectionRange(std::optional new_range) { + selection_range_ = std::move(new_range); + InvalidatePaint(); +} + +void TextRenderObject::SetSelectionBrush( + std::shared_ptr new_brush) { + Expects(new_brush); + new_brush.swap(selection_brush_); + if (selection_range_ && selection_range_->count) { + InvalidatePaint(); + } +} + +void TextRenderObject::SetDrawCaret(bool draw_caret) { + if (draw_caret_ != draw_caret) { + draw_caret_ = draw_caret; + InvalidatePaint(); + } +} + +void TextRenderObject::SetCaretPosition(gsl::index position) { + if (position != caret_position_) { + caret_position_ = position; + if (draw_caret_) { + InvalidatePaint(); + } + } +} + +void TextRenderObject::GetCaretBrush( + std::shared_ptr brush) { + Expects(brush); + brush.swap(caret_brush_); + if (draw_caret_) { + InvalidatePaint(); + } +} + +void TextRenderObject::SetCaretWidth(const float width) { + Expects(width >= 0.0f); + + caret_width_ = width; + if (draw_caret_) { + InvalidatePaint(); + } +} + +void TextRenderObject::Draw(platform::graph::IPainter* painter) { + platform::graph::util::WithTransform( + painter, + platform::Matrix::Translation(GetMargin().left + GetPadding().left, + GetMargin().top + GetPadding().top), + [this](platform::graph::IPainter* p) { + if (this->selection_range_.has_value()) { + const auto&& rects = + text_layout_->TextRangeRect(this->selection_range_.value()); + for (const auto& rect : rects) + p->FillRectangle(rect, this->GetSelectionBrush().get()); + } + + p->DrawText(Point{}, text_layout_.get(), brush_.get()); + + if (this->draw_caret_ && this->caret_width_ != 0.0f) { + auto caret_pos = this->caret_position_; + gsl::index text_size = this->GetText().size(); + if (caret_pos < 0) { + caret_pos = 0; + } else if (caret_pos > text_size) { + caret_pos = text_size; + } + + const auto caret_top_center = + this->text_layout_->TextSinglePoint(caret_pos, false); + + const auto font_height = this->font_->GetFontSize(); + const auto caret_width = this->caret_width_; + + p->FillRectangle(Rect{caret_top_center.x - caret_width / 2.0f, + caret_top_center.y, caret_width, font_height}, + this->caret_brush_.get()); + } + }); +} + +RenderObject* TextRenderObject::HitTest(const Point& point) { + const auto padding_rect = GetPaddingRect(); + return padding_rect.IsPointInside(point) ? this : nullptr; +} + +Size TextRenderObject::OnMeasureContent(const Size& available_size) { + text_layout_->SetMaxWidth(available_size.width); + text_layout_->SetMaxHeight(available_size.height); + return text_layout_->GetTextBounds().GetSize(); +} + +void TextRenderObject::OnLayoutContent(const Rect& content_rect) { + CRU_UNUSED(content_rect) +} + +void TextRenderObject::OnAfterLayout() { + const auto&& size = GetContentRect().GetSize(); + text_layout_->SetMaxWidth(size.width); + text_layout_->SetMaxHeight(size.height); +} + +} // namespace cru::ui::render diff --git a/src/ui/render/WindowRenderObject.cpp b/src/ui/render/WindowRenderObject.cpp new file mode 100644 index 00000000..cd1f806f --- /dev/null +++ b/src/ui/render/WindowRenderObject.cpp @@ -0,0 +1,45 @@ +#include "cru/ui/render/WindowRenderObject.hpp" + +#include "../Helper.hpp" +#include "cru/platform/graph/util/Painter.hpp" +#include "cru/ui/UiHost.hpp" + +namespace cru::ui::render { +WindowRenderObject::WindowRenderObject(UiHost* host) { + SetChildMode(ChildMode::Single); + ui_host_ = host; + after_layout_event_guard_.Reset(host->AfterLayoutEvent()->AddHandler( + [this](auto) { NotifyAfterLayoutRecursive(this); })); +} + +void WindowRenderObject::Draw(platform::graph::IPainter* painter) { + painter->Clear(colors::white); + if (const auto child = GetChild()) { + auto offset = child->GetOffset(); + platform::graph::util::WithTransform( + painter, platform::Matrix::Translation(offset.x, offset.y), + [child](platform::graph::IPainter* p) { child->Draw(p); }); + } +} + +RenderObject* WindowRenderObject::HitTest(const Point& point) { + if (const auto child = GetChild()) { + auto offset = child->GetOffset(); + Point p{point.x - offset.x, point.y - offset.y}; + const auto result = child->HitTest(p); + if (result != nullptr) { + return result; + } + } + return Rect{Point{}, GetSize()}.IsPointInside(point) ? this : nullptr; +} + +Size WindowRenderObject::OnMeasureContent(const Size& available_size) { + if (const auto child = GetChild()) child->Measure(available_size); + return available_size; +} + +void WindowRenderObject::OnLayoutContent(const Rect& content_rect) { + if (const auto child = GetChild()) child->Layout(content_rect); +} +} // namespace cru::ui::render diff --git a/src/ui/render/border_render_object.cpp b/src/ui/render/border_render_object.cpp deleted file mode 100644 index 7c96a3b6..00000000 --- a/src/ui/render/border_render_object.cpp +++ /dev/null @@ -1,236 +0,0 @@ -#include "cru/ui/render/border_render_object.hpp" - -#include "../helper.hpp" -#include "cru/common/logger.hpp" -#include "cru/platform/graph/factory.hpp" -#include "cru/platform/graph/geometry.hpp" -#include "cru/platform/graph/util/painter.hpp" - -#include - -namespace cru::ui::render { -BorderRenderObject::BorderRenderObject() { - SetChildMode(ChildMode::Single); - RecreateGeometry(); -} - -BorderRenderObject::~BorderRenderObject() {} - -void BorderRenderObject::Draw(platform::graph::IPainter* painter) { - if (background_brush_ != nullptr) - painter->FillGeometry(border_inner_geometry_.get(), - background_brush_.get()); - if (is_border_enabled_) { - if (border_brush_ == nullptr) { - log::Warn("Border is enabled but brush is null"); - } else { - painter->FillGeometry(geometry_.get(), border_brush_.get()); - } - } - if (const auto child = GetChild()) { - auto offset = child->GetOffset(); - platform::graph::util::WithTransform( - painter, platform::Matrix::Translation(offset.x, offset.y), - [child](auto p) { child->Draw(p); }); - } - if (foreground_brush_ != nullptr) - painter->FillGeometry(border_inner_geometry_.get(), - foreground_brush_.get()); -} - -void BorderRenderObject::SetBorderStyle(const BorderStyle& style) { - border_brush_ = style.border_brush; - border_thickness_ = style.border_thickness; - border_radius_ = style.border_radius; - foreground_brush_ = style.foreground_brush; - background_brush_ = style.background_brush; - InvalidateLayout(); -} - -RenderObject* BorderRenderObject::HitTest(const Point& point) { - if (const auto child = GetChild()) { - auto offset = child->GetOffset(); - Point p{point.x - offset.x, point.y - offset.y}; - const auto result = child->HitTest(p); - if (result != nullptr) { - return result; - } - } - - if (is_border_enabled_) { - const auto contains = - border_outer_geometry_->FillContains(Point{point.x, point.y}); - return contains ? this : nullptr; - } else { - const auto margin = GetMargin(); - const auto size = GetSize(); - return Rect{margin.left, margin.top, - std::max(size.width - margin.GetHorizontalTotal(), 0.0f), - std::max(size.height - margin.GetVerticalTotal(), 0.0f)} - .IsPointInside(point) - ? this - : nullptr; - } -} - -void BorderRenderObject::OnMeasureCore(const Size& available_size) { - const auto margin = GetMargin(); - const auto padding = GetPadding(); - Size margin_border_padding_size{ - margin.GetHorizontalTotal() + padding.GetHorizontalTotal(), - margin.GetVerticalTotal() + padding.GetVerticalTotal()}; - - if (is_border_enabled_) { - margin_border_padding_size.width += border_thickness_.GetHorizontalTotal(); - margin_border_padding_size.height += border_thickness_.GetVerticalTotal(); - } - - auto coerced_margin_border_padding_size = margin_border_padding_size; - if (coerced_margin_border_padding_size.width > available_size.width) { - log::Warn( - "Measure: horizontal length of padding, border and margin is bigger " - "than available length."); - coerced_margin_border_padding_size.width = available_size.width; - } - if (coerced_margin_border_padding_size.height > available_size.height) { - log::Warn( - "Measure: vertical length of padding, border and margin is bigger " - "than available length."); - coerced_margin_border_padding_size.height = available_size.height; - } - - const auto coerced_content_available_size = - available_size - coerced_margin_border_padding_size; - - const auto actual_content_size = - OnMeasureContent(coerced_content_available_size); - - SetPreferredSize(coerced_margin_border_padding_size + actual_content_size); -} - -void BorderRenderObject::OnLayoutCore(const Rect& rect) { - const auto margin = GetMargin(); - const auto padding = GetPadding(); - Size margin_border_padding_size{ - margin.GetHorizontalTotal() + padding.GetHorizontalTotal(), - margin.GetVerticalTotal() + padding.GetVerticalTotal()}; - - if (is_border_enabled_) { - margin_border_padding_size.width += border_thickness_.GetHorizontalTotal(); - margin_border_padding_size.height += border_thickness_.GetVerticalTotal(); - } - - const auto content_available_size = - rect.GetSize() - margin_border_padding_size; - auto coerced_content_available_size = content_available_size; - - if (coerced_content_available_size.width < 0) { - log::Warn( - "Layout: horizontal length of padding, border and margin is bigger " - "than available length."); - coerced_content_available_size.width = 0; - } - if (coerced_content_available_size.height < 0) { - log::Warn( - "Layout: vertical length of padding, border and margin is bigger " - "than available length."); - coerced_content_available_size.height = 0; - } - - OnLayoutContent( - Rect{margin.left + (is_border_enabled_ ? border_thickness_.left : 0) + - padding.left, - margin.top + (is_border_enabled_ ? border_thickness_.top : 0) + - padding.top, - coerced_content_available_size.width, - coerced_content_available_size.height}); -} - -Size BorderRenderObject::OnMeasureContent(const Size& available_size) { - const auto child = GetChild(); - if (child) { - child->Measure(available_size); - return child->GetPreferredSize(); - } else { - return Size{}; - } -} - -void BorderRenderObject::OnLayoutContent(const Rect& content_rect) { - const auto child = GetChild(); - if (child) { - child->Layout(content_rect); - } -} - -void BorderRenderObject::OnAfterLayout() { RecreateGeometry(); } - -void BorderRenderObject::RecreateGeometry() { - geometry_.reset(); - border_outer_geometry_.reset(); - - Thickness t = border_thickness_; - t.left /= 2.0; - t.top /= 2.0; - t.right /= 2.0; - t.bottom /= 2.0; - const CornerRadius& r = border_radius_; - - CornerRadius outer_radius(r.left_top + Point{t.left, t.top}, - r.right_top + Point{t.right, t.top}, - r.left_bottom + Point{t.left, t.bottom}, - r.right_bottom + Point{t.right, t.bottom}); - - CornerRadius inner_radius(r.left_top - Point{t.left, t.top}, - r.right_top - Point{t.right, t.top}, - r.left_bottom - Point{t.left, t.bottom}, - r.right_bottom - Point{t.right, t.bottom}); - - auto f = [](platform::graph::IGeometryBuilder* builder, const Rect& rect, - const CornerRadius& corner) { - builder->BeginFigure(Point(rect.left + corner.left_top.x, rect.top)); - builder->LineTo(Point(rect.GetRight() - corner.right_top.x, rect.top)); - builder->QuadraticBezierTo( - Point(rect.GetRight(), rect.top), - Point(rect.GetRight(), rect.top + corner.right_top.y)); - builder->LineTo( - Point(rect.GetRight(), rect.GetBottom() - corner.right_bottom.y)); - builder->QuadraticBezierTo( - Point(rect.GetRight(), rect.GetBottom()), - Point(rect.GetRight() - corner.right_bottom.x, rect.GetBottom())); - builder->LineTo(Point(rect.left + corner.left_bottom.x, rect.GetBottom())); - builder->QuadraticBezierTo( - Point(rect.left, rect.GetBottom()), - Point(rect.left, rect.GetBottom() - corner.left_bottom.y)); - builder->LineTo(Point(rect.left, rect.top + corner.left_top.y)); - builder->QuadraticBezierTo(Point(rect.left, rect.top), - Point(rect.left + corner.left_top.x, rect.top)); - builder->CloseFigure(true); - }; - - const auto size = GetSize(); - const auto margin = GetMargin(); - const Rect outer_rect{margin.left, margin.top, - size.width - margin.GetHorizontalTotal(), - size.height - margin.GetVerticalTotal()}; - const auto graph_factory = GetGraphFactory(); - std::unique_ptr builder{ - graph_factory->CreateGeometryBuilder()}; - f(builder.get(), outer_rect, outer_radius); - border_outer_geometry_ = builder->Build(); - builder.reset(); - - const Rect inner_rect = outer_rect.Shrink(border_thickness_); - - builder = graph_factory->CreateGeometryBuilder(); - f(builder.get(), inner_rect, inner_radius); - border_inner_geometry_ = builder->Build(); - builder.reset(); - - builder = graph_factory->CreateGeometryBuilder(); - f(builder.get(), outer_rect, outer_radius); - f(builder.get(), inner_rect, inner_radius); - geometry_ = builder->Build(); - builder.reset(); -} -} // namespace cru::ui::render diff --git a/src/ui/render/canvas_render_object.cpp b/src/ui/render/canvas_render_object.cpp deleted file mode 100644 index 0508e276..00000000 --- a/src/ui/render/canvas_render_object.cpp +++ /dev/null @@ -1,28 +0,0 @@ -#include "cru/ui/render/canvas_render_object.hpp" - -#include "cru/ui/render/layout_utility.hpp" - -namespace cru::ui::render { -CanvasRenderObject::CanvasRenderObject() : RenderObject(ChildMode::None) {} - -CanvasRenderObject::~CanvasRenderObject() = default; - -void CanvasRenderObject::Draw(platform::graph::IPainter* painter) { - const auto rect = GetContentRect(); - CanvasPaintEventArgs args{painter, rect}; - paint_event_.Raise(args); -} - -RenderObject* CanvasRenderObject::HitTest(const Point& point) { - const auto padding_rect = GetPaddingRect(); - return padding_rect.IsPointInside(point) ? this : nullptr; -} - -Size CanvasRenderObject::OnMeasureContent(const Size& available_size) { - return Min(available_size, GetDesiredSize()); -} - -void CanvasRenderObject::OnLayoutContent(const Rect& content_rect) { - CRU_UNUSED(content_rect) -} -} // namespace cru::ui::render diff --git a/src/ui/render/flex_layout_render_object.cpp b/src/ui/render/flex_layout_render_object.cpp deleted file mode 100644 index f40a9b3e..00000000 --- a/src/ui/render/flex_layout_render_object.cpp +++ /dev/null @@ -1,197 +0,0 @@ -#include "cru/ui/render/flex_layout_render_object.hpp" - -#include "cru/platform/graph/util/painter.hpp" - -#include -#include - -namespace cru::ui::render { -Size FlexLayoutRenderObject::OnMeasureContent(const Size& available_size) { - std::vector has_basis_children; - std::vector no_basis_children; - std::vector grow_children; - std::vector shrink_chilren; - const auto child_count = GetChildCount(); - for (int i = 0; i < child_count; i++) { - const auto& layout_data = *GetChildLayoutData(i); - if (layout_data.flex_basis.has_value()) - has_basis_children.push_back(i); - else - no_basis_children.push_back(i); - if (layout_data.flex_grow > 0) grow_children.push_back(i); - if (layout_data.flex_shrink > 0) shrink_chilren.push_back(i); - } - - std::function get_main_length; - std::function get_cross_length; - std::function create_size; - - if (direction_ == FlexDirection::Horizontal || - direction_ == FlexDirection::HorizontalReverse) { - get_main_length = [](const Size& size) { return size.width; }; - get_cross_length = [](const Size& size) { return size.height; }; - create_size = [](float main, float cross) { return Size(main, cross); }; - } else { - get_main_length = [](const Size& size) { return size.height; }; - get_cross_length = [](const Size& size) { return size.width; }; - create_size = [](float main, float cross) { return Size(cross, main); }; - } - - const auto& children = GetChildren(); - - float remain_main_length = get_main_length(available_size); - float max_cross_length = 0; - - for (const int i : has_basis_children) { - const auto child = children[i]; - const float basis = GetChildLayoutData(i)->flex_basis.value(); - child->Measure(create_size(basis, get_cross_length(available_size))); - remain_main_length -= basis; - const float child_preferred_cross_length = - get_cross_length(child->GetPreferredSize()); - child->SetPreferredSize(create_size(basis, child_preferred_cross_length)); - max_cross_length = std::max(max_cross_length, child_preferred_cross_length); - } - - for (const int i : no_basis_children) { - const auto child = children[i]; - child->Measure(create_size(remain_main_length > 0 ? remain_main_length : 0, - get_cross_length(available_size))); - remain_main_length -= get_main_length(child->GetPreferredSize()); - max_cross_length = - std::max(max_cross_length, get_cross_length(child->GetPreferredSize())); - } - - if (remain_main_length > 0) { - float total_grow = 0; - for (const int i : grow_children) - total_grow += GetChildLayoutData(i)->flex_grow; - - for (const int i : grow_children) { - const float distributed_grow_length = - remain_main_length * (GetChildLayoutData(i)->flex_grow / total_grow); - const auto child = children[i]; - const float new_main_length = - get_main_length(child->GetPreferredSize()) + distributed_grow_length; - child->Measure( - create_size(new_main_length, get_cross_length(available_size))); - const float new_child_preferred_cross_length = - get_cross_length(child->GetPreferredSize()); - child->SetPreferredSize( - create_size(new_main_length, new_child_preferred_cross_length)); - max_cross_length = - std::max(max_cross_length, new_child_preferred_cross_length); - } - } - - if (remain_main_length < 0) { - float total_shrink = 0; - for (const int i : shrink_chilren) - total_shrink += GetChildLayoutData(i)->flex_shrink; - - for (const int i : shrink_chilren) { - const float distributed_shrink_length = // negative - remain_main_length * - (GetChildLayoutData(i)->flex_shrink / total_shrink); - const auto child = children[i]; - float new_main_length = get_main_length(child->GetPreferredSize()) + - distributed_shrink_length; - new_main_length = new_main_length > 0 ? new_main_length : 0; - child->Measure( - create_size(new_main_length, get_cross_length(available_size))); - const float new_child_preferred_cross_length = - get_cross_length(child->GetPreferredSize()); - child->SetPreferredSize( - create_size(new_main_length, new_child_preferred_cross_length)); - max_cross_length = - std::max(max_cross_length, new_child_preferred_cross_length); - } - } - - return create_size(get_main_length(available_size) - - (remain_main_length > 0 ? remain_main_length : 0), - max_cross_length); -} - -void FlexLayoutRenderObject::OnLayoutContent(const Rect& content_rect) { - auto calculate_anchor = [](int alignment, float start_point, - float total_length, - float content_length) -> float { - switch (alignment) { - case internal::align_start: - return start_point; - case internal::align_center: - return start_point + (total_length - content_length) / 2.0f; - case internal::align_end: - return start_point + total_length - content_length; - default: - return 0; - } - }; - - const auto& children = GetChildren(); - if (direction_ == FlexDirection::Horizontal || - direction_ == FlexDirection::HorizontalReverse) { - float actual_content_width = 0; - for (const auto child : children) { - actual_content_width += child->GetPreferredSize().width; - } - - const float content_anchor_x = - calculate_anchor(static_cast(content_main_align_), 0, - content_rect.width, actual_content_width); - - float anchor_x = 0; - for (int i = 0; i < static_cast(children.size()); i++) { - const auto child = children[i]; - const auto size = child->GetPreferredSize(); - - float real_anchor_x = anchor_x + content_anchor_x; - if (direction_ == FlexDirection::Horizontal) - real_anchor_x = content_rect.left + real_anchor_x; - else - real_anchor_x = content_rect.GetRight() - real_anchor_x; - child->Layout(Rect{ - real_anchor_x, - calculate_anchor( - static_cast(GetChildLayoutData(i)->cross_alignment.value_or( - this->item_cross_align_)), - content_rect.top, content_rect.height, size.height), - size.width, size.height}); - - anchor_x += size.width; - } - } else { - float actual_content_height = 0; - for (const auto child : children) { - actual_content_height = child->GetPreferredSize().height; - } - - const float content_anchor_y = - calculate_anchor(static_cast(content_main_align_), 0, - content_rect.height, actual_content_height); - - float anchor_y = 0; - for (int i = 0; i < static_cast(children.size()); i++) { - const auto child = children[i]; - const auto size = child->GetPreferredSize(); - - float real_anchor_y = anchor_y + content_anchor_y; - if (direction_ == FlexDirection::Vertical) { - real_anchor_y = content_rect.top + real_anchor_y; - } else { - real_anchor_y = content_rect.GetBottom() - real_anchor_y; - } - child->Layout(Rect{ - real_anchor_y, - calculate_anchor( - static_cast(GetChildLayoutData(i)->cross_alignment.value_or( - this->item_cross_align_)), - content_rect.left, content_rect.width, size.width), - size.width, size.height}); - - anchor_y += size.height; - } - } -} -} // namespace cru::ui::render diff --git a/src/ui/render/layout_utility.cpp b/src/ui/render/layout_utility.cpp deleted file mode 100644 index aae62d35..00000000 --- a/src/ui/render/layout_utility.cpp +++ /dev/null @@ -1,15 +0,0 @@ -#include "cru/ui/render/layout_utility.hpp" - -#include - -namespace cru::ui::render { -Size Min(const Size& left, const Size& right) { - return Size{std::min(left.width, right.width), - std::min(left.height, right.height)}; -} - -Size Max(const Size& left, const Size& right) { - return Size{std::max(left.width, right.width), - std::max(left.height, right.height)}; -} -} // namespace cru::ui::render diff --git a/src/ui/render/render_object.cpp b/src/ui/render/render_object.cpp deleted file mode 100644 index 4a1bedf3..00000000 --- a/src/ui/render/render_object.cpp +++ /dev/null @@ -1,190 +0,0 @@ -#include "cru/ui/render/render_object.hpp" - -#include "cru/common/logger.hpp" -#include "cru/ui/ui_host.hpp" - -#include - -namespace cru::ui::render { -void RenderObject::AddChild(RenderObject* render_object, const Index position) { - Expects(child_mode_ != ChildMode::None); - Expects(!(child_mode_ == ChildMode::Single && children_.size() > 0)); - - Expects(render_object->GetParent() == - nullptr); // Render object already has a parent. - Expects(position >= 0); // Position index is less than 0. - Expects( - position <= - static_cast(children_.size())); // Position index is out of bound. - - children_.insert(children_.cbegin() + position, render_object); - render_object->SetParent(this); - render_object->SetRenderHostRecursive(GetUiHost()); - OnAddChild(render_object, position); -} - -void RenderObject::RemoveChild(const Index position) { - Expects(position >= 0); // Position index is less than 0. - Expects(position < static_cast( - children_.size())); // Position index is out of bound. - - const auto i = children_.cbegin() + position; - const auto removed_child = *i; - children_.erase(i); - removed_child->SetParent(nullptr); - removed_child->SetRenderHostRecursive(nullptr); - OnRemoveChild(removed_child, position); -} - -Point RenderObject::GetTotalOffset() const { - Point result{}; - const RenderObject* render_object = this; - - while (render_object != nullptr) { - const auto o = render_object->GetOffset(); - result.x += o.x; - result.y += o.y; - render_object = render_object->GetParent(); - } - - return result; -} - -Point RenderObject::FromRootToContent(const Point& point) const { - const auto offset = GetTotalOffset(); - const auto rect = GetContentRect(); - return Point{point.x - (offset.x + rect.left), - point.y - (offset.y + rect.top)}; -} - -void RenderObject::Measure(const Size& available_size) { - OnMeasureCore(available_size); -} - -void RenderObject::Layout(const Rect& rect) { - SetOffset(rect.GetLeftTop()); - SetSize(rect.GetSize()); - OnLayoutCore(Rect{Point{}, rect.GetSize()}); -} - -void RenderObject::OnParentChanged(RenderObject* old_parent, - RenderObject* new_parent) { - CRU_UNUSED(old_parent) - CRU_UNUSED(new_parent) -} - -void RenderObject::OnAddChild(RenderObject* new_child, Index position) { - CRU_UNUSED(new_child) - CRU_UNUSED(position) - - InvalidateLayout(); - InvalidatePaint(); -} - -void RenderObject::OnRemoveChild(RenderObject* removed_child, Index position) { - CRU_UNUSED(removed_child) - CRU_UNUSED(position) - - InvalidateLayout(); - InvalidatePaint(); -} - -void RenderObject::OnMeasureCore(const Size& available_size) { - Size margin_padding_size{ - margin_.GetHorizontalTotal() + padding_.GetHorizontalTotal(), - margin_.GetVerticalTotal() + padding_.GetVerticalTotal()}; - - auto coerced_margin_padding_size = margin_padding_size; - if (coerced_margin_padding_size.width > available_size.width) { - log::Warn( - "Measure: horizontal length of padding and margin is bigger than " - "available length."); - coerced_margin_padding_size.width = available_size.width; - } - if (coerced_margin_padding_size.height > available_size.height) { - log::Warn( - "Measure: vertical length of padding and margin is bigger than " - "available length."); - coerced_margin_padding_size.height = available_size.height; - } - - const auto coerced_content_available_size = - available_size - coerced_margin_padding_size; - const auto actual_content_size = - OnMeasureContent(coerced_content_available_size); - - SetPreferredSize(coerced_margin_padding_size + actual_content_size); -} - -void RenderObject::OnLayoutCore(const Rect& rect) { - Size margin_padding_size{ - margin_.GetHorizontalTotal() + padding_.GetHorizontalTotal(), - margin_.GetVerticalTotal() + padding_.GetVerticalTotal()}; - const auto content_available_size = rect.GetSize() - margin_padding_size; - auto coerced_content_available_size = content_available_size; - - if (coerced_content_available_size.width < 0) { - log::Warn( - "Layout: horizontal length of padding and margin is bigger than " - "available length."); - coerced_content_available_size.width = 0; - } - if (coerced_content_available_size.height < 0) { - log::Warn( - "Layout: vertical length of padding and margin is bigger than " - "available length."); - coerced_content_available_size.height = 0; - } - - OnLayoutContent(Rect{margin_.left + padding_.left, margin_.top + padding_.top, - coerced_content_available_size.width, - coerced_content_available_size.height}); -} - -void RenderObject::OnAfterLayout() {} - -Rect RenderObject::GetPaddingRect() const { - Rect rect{Point{}, GetSize()}; - rect = rect.Shrink(GetMargin()); - rect.width = std::max(rect.width, 0.0f); - rect.height = std::max(rect.height, 0.0f); - return rect; -} - -Rect RenderObject::GetContentRect() const { - Rect rect{Point{}, GetSize()}; - rect = rect.Shrink(GetMargin()); - rect = rect.Shrink(GetPadding()); - rect.width = std::max(rect.width, 0.0f); - rect.height = std::max(rect.height, 0.0f); - return rect; -} - -void RenderObject::SetParent(RenderObject* new_parent) { - const auto old_parent = parent_; - parent_ = new_parent; - OnParentChanged(old_parent, new_parent); -} - -void RenderObject::InvalidateLayout() { - if (ui_host_ != nullptr) ui_host_->InvalidateLayout(); -} - -void RenderObject::InvalidatePaint() { - if (ui_host_ != nullptr) ui_host_->InvalidatePaint(); -} - -void RenderObject::NotifyAfterLayoutRecursive(RenderObject* render_object) { - render_object->OnAfterLayout(); - for (const auto o : render_object->GetChildren()) { - NotifyAfterLayoutRecursive(o); - } -} - -void RenderObject::SetRenderHostRecursive(UiHost* host) { - ui_host_ = host; - for (const auto child : GetChildren()) { - child->SetRenderHostRecursive(host); - } -} -} // namespace cru::ui::render diff --git a/src/ui/render/scroll_render_object.cpp b/src/ui/render/scroll_render_object.cpp deleted file mode 100644 index f77ee636..00000000 --- a/src/ui/render/scroll_render_object.cpp +++ /dev/null @@ -1 +0,0 @@ -#include "cru/ui/render/scroll_render_object.hpp" diff --git a/src/ui/render/stack_layout_render_object.cpp b/src/ui/render/stack_layout_render_object.cpp deleted file mode 100644 index 1cb31252..00000000 --- a/src/ui/render/stack_layout_render_object.cpp +++ /dev/null @@ -1,49 +0,0 @@ -#include "cru/ui/render/stack_layout_render_object.hpp" - -#include - -namespace cru::ui::render { -Size StackLayoutRenderObject::OnMeasureContent(const Size& available_size) { - auto size = Size{}; - for (const auto child : GetChildren()) { - child->Measure(available_size); - const auto& preferred_size = child->GetPreferredSize(); - size.width = std::max(size.width, preferred_size.width); - size.height = std::max(size.height, preferred_size.height); - } - return size; -} - -void StackLayoutRenderObject::OnLayoutContent(const Rect& rect) { - auto calculate_anchor = [](int alignment, float start_point, - float total_length, - float content_length) -> float { - switch (alignment) { - case internal::align_start: - return start_point; - case internal::align_center: - return start_point + (total_length - content_length) / 2.0f; - case internal::align_end: - return start_point + total_length - content_length; - default: - return 0; - } - }; - - const auto count = GetChildCount(); - const auto& children = GetChildren(); - - for (int i = 0; i < count; i++) { - const auto layout_data = GetChildLayoutData(i); - const auto child = children[i]; - const auto& size = child->GetPreferredSize(); - child->Layout( - Rect{calculate_anchor(static_cast(layout_data->horizontal), - rect.left, rect.width, size.width), - calculate_anchor(static_cast(layout_data->vertical), rect.top, - rect.height, size.height), - size.width, size.height}); - } -} - -} // namespace cru::ui::render diff --git a/src/ui/render/text_render_object.cpp b/src/ui/render/text_render_object.cpp deleted file mode 100644 index cd670db1..00000000 --- a/src/ui/render/text_render_object.cpp +++ /dev/null @@ -1,177 +0,0 @@ -#include "cru/ui/render/text_render_object.hpp" - -#include "../helper.hpp" -#include "cru/platform/graph/factory.hpp" -#include "cru/platform/graph/text_layout.hpp" -#include "cru/platform/graph/util/painter.hpp" - -#include - -namespace cru::ui::render { -TextRenderObject::TextRenderObject( - std::shared_ptr brush, - std::shared_ptr font, - std::shared_ptr selection_brush, - std::shared_ptr caret_brush) { - Expects(brush); - Expects(font); - Expects(selection_brush); - Expects(caret_brush); - - SetChildMode(ChildMode::None); - - brush.swap(brush_); - font.swap(font_); - selection_brush.swap(selection_brush_); - caret_brush.swap(caret_brush_); - - const auto graph_factory = GetGraphFactory(); - text_layout_ = graph_factory->CreateTextLayout(font_, ""); -} - -TextRenderObject::~TextRenderObject() = default; - -std::string TextRenderObject::GetText() const { - return text_layout_->GetText(); -} - -void TextRenderObject::SetText(std::string new_text) { - text_layout_->SetText(std::move(new_text)); -} - -void TextRenderObject::SetBrush( - std::shared_ptr new_brush) { - Expects(new_brush); - new_brush.swap(brush_); - InvalidatePaint(); -} - -std::shared_ptr TextRenderObject::GetFont() const { - return text_layout_->GetFont(); -} - -void TextRenderObject::SetFont(std::shared_ptr font) { - Expects(font); - text_layout_->SetFont(std::move(font)); -} - -std::vector TextRenderObject::TextRangeRect(const TextRange& text_range) { - return text_layout_->TextRangeRect(text_range); -} - -Point TextRenderObject::TextSinglePoint(gsl::index position, bool trailing) { - return text_layout_->TextSinglePoint(position, trailing); -} - -platform::graph::TextHitTestResult TextRenderObject::TextHitTest( - const Point& point) { - return text_layout_->HitTest(point); -} - -void TextRenderObject::SetSelectionRange(std::optional new_range) { - selection_range_ = std::move(new_range); - InvalidatePaint(); -} - -void TextRenderObject::SetSelectionBrush( - std::shared_ptr new_brush) { - Expects(new_brush); - new_brush.swap(selection_brush_); - if (selection_range_ && selection_range_->count) { - InvalidatePaint(); - } -} - -void TextRenderObject::SetDrawCaret(bool draw_caret) { - if (draw_caret_ != draw_caret) { - draw_caret_ = draw_caret; - InvalidatePaint(); - } -} - -void TextRenderObject::SetCaretPosition(gsl::index position) { - if (position != caret_position_) { - caret_position_ = position; - if (draw_caret_) { - InvalidatePaint(); - } - } -} - -void TextRenderObject::GetCaretBrush( - std::shared_ptr brush) { - Expects(brush); - brush.swap(caret_brush_); - if (draw_caret_) { - InvalidatePaint(); - } -} - -void TextRenderObject::SetCaretWidth(const float width) { - Expects(width >= 0.0f); - - caret_width_ = width; - if (draw_caret_) { - InvalidatePaint(); - } -} - -void TextRenderObject::Draw(platform::graph::IPainter* painter) { - platform::graph::util::WithTransform( - painter, - platform::Matrix::Translation(GetMargin().left + GetPadding().left, - GetMargin().top + GetPadding().top), - [this](platform::graph::IPainter* p) { - if (this->selection_range_.has_value()) { - const auto&& rects = - text_layout_->TextRangeRect(this->selection_range_.value()); - for (const auto& rect : rects) - p->FillRectangle(rect, this->GetSelectionBrush().get()); - } - - p->DrawText(Point{}, text_layout_.get(), brush_.get()); - - if (this->draw_caret_ && this->caret_width_ != 0.0f) { - auto caret_pos = this->caret_position_; - gsl::index text_size = this->GetText().size(); - if (caret_pos < 0) { - caret_pos = 0; - } else if (caret_pos > text_size) { - caret_pos = text_size; - } - - const auto caret_top_center = - this->text_layout_->TextSinglePoint(caret_pos, false); - - const auto font_height = this->font_->GetFontSize(); - const auto caret_width = this->caret_width_; - - p->FillRectangle(Rect{caret_top_center.x - caret_width / 2.0f, - caret_top_center.y, caret_width, font_height}, - this->caret_brush_.get()); - } - }); -} - -RenderObject* TextRenderObject::HitTest(const Point& point) { - const auto padding_rect = GetPaddingRect(); - return padding_rect.IsPointInside(point) ? this : nullptr; -} - -Size TextRenderObject::OnMeasureContent(const Size& available_size) { - text_layout_->SetMaxWidth(available_size.width); - text_layout_->SetMaxHeight(available_size.height); - return text_layout_->GetTextBounds().GetSize(); -} - -void TextRenderObject::OnLayoutContent(const Rect& content_rect) { - CRU_UNUSED(content_rect) -} - -void TextRenderObject::OnAfterLayout() { - const auto&& size = GetContentRect().GetSize(); - text_layout_->SetMaxWidth(size.width); - text_layout_->SetMaxHeight(size.height); -} - -} // namespace cru::ui::render diff --git a/src/ui/render/window_render_object.cpp b/src/ui/render/window_render_object.cpp deleted file mode 100644 index abed6fa4..00000000 --- a/src/ui/render/window_render_object.cpp +++ /dev/null @@ -1,45 +0,0 @@ -#include "cru/ui/render/window_render_object.hpp" - -#include "../helper.hpp" -#include "cru/platform/graph/util/painter.hpp" -#include "cru/ui/ui_host.hpp" - -namespace cru::ui::render { -WindowRenderObject::WindowRenderObject(UiHost* host) { - SetChildMode(ChildMode::Single); - ui_host_ = host; - after_layout_event_guard_.Reset(host->AfterLayoutEvent()->AddHandler( - [this](auto) { NotifyAfterLayoutRecursive(this); })); -} - -void WindowRenderObject::Draw(platform::graph::IPainter* painter) { - painter->Clear(colors::white); - if (const auto child = GetChild()) { - auto offset = child->GetOffset(); - platform::graph::util::WithTransform( - painter, platform::Matrix::Translation(offset.x, offset.y), - [child](platform::graph::IPainter* p) { child->Draw(p); }); - } -} - -RenderObject* WindowRenderObject::HitTest(const Point& point) { - if (const auto child = GetChild()) { - auto offset = child->GetOffset(); - Point p{point.x - offset.x, point.y - offset.y}; - const auto result = child->HitTest(p); - if (result != nullptr) { - return result; - } - } - return Rect{Point{}, GetSize()}.IsPointInside(point) ? this : nullptr; -} - -Size WindowRenderObject::OnMeasureContent(const Size& available_size) { - if (const auto child = GetChild()) child->Measure(available_size); - return available_size; -} - -void WindowRenderObject::OnLayoutContent(const Rect& content_rect) { - if (const auto child = GetChild()) child->Layout(content_rect); -} -} // namespace cru::ui::render diff --git a/src/ui/routed_event_dispatch.hpp b/src/ui/routed_event_dispatch.hpp deleted file mode 100644 index dd55ce17..00000000 --- a/src/ui/routed_event_dispatch.hpp +++ /dev/null @@ -1,134 +0,0 @@ -#pragma once -#include "cru/ui/control.hpp" - -#include "cru/common/logger.hpp" - -#include - -namespace cru::ui { -// Dispatch the event. -// -// This will raise routed event of the control and its parent and parent's -// parent ... (until "last_receiver" if it's not nullptr) with appropriate args. -// -// First tunnel from top to bottom possibly stopped by "handled" flag in -// EventArgs. Second bubble from bottom to top possibly stopped by "handled" -// flag in EventArgs. Last direct to each control. -// -// Args is of type "EventArgs". The first init argument is "sender", which is -// automatically bound to each receiving control. The second init argument is -// "original_sender", which is unchanged. And "args" will be perfectly forwarded -// as the rest arguments. -template -void DispatchEvent(const std::string_view& event_name, - Control* const original_sender, - event::RoutedEvent* (Control::*event_ptr)(), - Control* const last_receiver, Args&&... args) { -#ifndef CRU_DEBUG - CRU_UNUSED(event_name) -#endif - -#ifdef CRU_DEBUG - bool do_log = true; - if (event_name == "MouseMove") do_log = false; -#endif - - if (original_sender == last_receiver) { - /* - #ifdef CRU_DEBUG - if (do_log) - log::Debug( - "Routed event {} no need to dispatch (original_sender == " - "last_receiver). Original sender is {}.", - event_name, original_sender->GetControlType()); - #endif - */ - return; - } - - std::list receive_list; - - auto parent = original_sender; - while (parent != last_receiver) { - receive_list.push_back(parent); - parent = parent->GetParent(); - } - -#ifdef CRU_DEBUG - if (do_log) { - std::string log = "Dispatch routed event "; - log += event_name; - log += ". Path (parent first): "; - auto i = receive_list.crbegin(); - const auto end = --receive_list.crend(); - for (; i != end; ++i) { - log += (*i)->GetControlType(); - log += " -> "; - } - log += (*i)->GetControlType(); - log::Debug(log); - } -#endif - - auto handled = false; - -#ifdef CRU_DEBUG - int count = 0; -#endif - - // tunnel - for (auto i = receive_list.crbegin(); i != receive_list.crend(); ++i) { -#ifdef CRU_DEBUG - count++; -#endif - EventArgs event_args(*i, original_sender, std::forward(args)...); - static_cast*>(((*i)->*event_ptr)()->Tunnel()) - ->Raise(event_args); - if (event_args.IsHandled()) { - handled = true; -#ifdef CRU_DEBUG - if (do_log) - log::Debug( - "Routed event is short-circuit in TUNNEL at {}-st control (count " - "from parent).", - count); -#endif - break; - } - } - - // bubble - if (!handled) { - for (auto i : receive_list) { -#ifdef CRU_DEBUG - count--; -#endif - EventArgs event_args(i, original_sender, std::forward(args)...); - static_cast*>((i->*event_ptr)()->Bubble()) - ->Raise(event_args); - if (event_args.IsHandled()) { -#ifdef CRU_DEBUG - if (do_log) - log::Debug( - "Routed event is short-circuit in BUBBLE at {}-st control " - "(count " - "from parent).", - count); -#endif - break; - } - } - } - - // direct - for (auto i : receive_list) { - EventArgs event_args(i, original_sender, std::forward(args)...); - static_cast*>((i->*event_ptr)()->Direct()) - ->Raise(event_args); - } - -#ifdef CRU_DEBUG - if (do_log) log::Debug("Routed event dispatch finished."); -#endif -} -} // namespace cru::ui diff --git a/src/ui/ui_host.cpp b/src/ui/ui_host.cpp deleted file mode 100644 index 8b85ad30..00000000 --- a/src/ui/ui_host.cpp +++ /dev/null @@ -1,368 +0,0 @@ -#include "cru/ui/ui_host.hpp" - -#include "cru/common/logger.hpp" -#include "cru/platform/graph/painter.hpp" -#include "cru/platform/native/ui_application.hpp" -#include "cru/platform/native/window.hpp" -#include "cru/ui/render/window_render_object.hpp" -#include "cru/ui/window.hpp" -#include "routed_event_dispatch.hpp" - -namespace cru::ui { -using platform::native::INativeWindow; -using platform::native::IUiApplication; - -namespace event_names { -#ifdef CRU_DEBUG -#define CRU_DEFINE_EVENT_NAME(name) constexpr const char* name = #name; -#else -#define CRU_DEFINE_EVENT_NAME(name) constexpr const char* name = ""; -#endif - -CRU_DEFINE_EVENT_NAME(LoseFocus) -CRU_DEFINE_EVENT_NAME(GainFocus) -CRU_DEFINE_EVENT_NAME(MouseEnter) -CRU_DEFINE_EVENT_NAME(MouseLeave) -CRU_DEFINE_EVENT_NAME(MouseMove) -CRU_DEFINE_EVENT_NAME(MouseDown) -CRU_DEFINE_EVENT_NAME(MouseUp) -CRU_DEFINE_EVENT_NAME(KeyDown) -CRU_DEFINE_EVENT_NAME(KeyUp) -CRU_DEFINE_EVENT_NAME(Char) - -#undef CRU_DEFINE_EVENT_NAME -} // namespace event_names - -namespace { -bool IsAncestor(Control* control, Control* ancestor) { - while (control != nullptr) { - if (control == ancestor) return true; - control = control->GetParent(); - } - return false; -} - -std::list GetAncestorList(Control* control) { - std::list l; - while (control != nullptr) { - l.push_front(control); - control = control->GetParent(); - } - return l; -} - -Control* FindLowestCommonAncestor(Control* left, Control* right) { - if (left == nullptr || right == nullptr) return nullptr; - - auto&& left_list = GetAncestorList(left); - auto&& right_list = GetAncestorList(right); - - // the root is different - if (left_list.front() != right_list.front()) return nullptr; - - // find the last same control or the last control (one is ancestor of the - // other) - auto left_i = left_list.cbegin(); - auto right_i = right_list.cbegin(); - - while (true) { - if (left_i == left_list.cend()) { - return *(--left_i); - } - if (right_i == right_list.cend()) { - return *(--right_i); - } - if (*left_i != *right_i) { - return *(--left_i); - } - ++left_i; - ++right_i; - } -} -} // namespace - -namespace { -template -inline void BindNativeEvent( - UiHost* host, INativeWindow* native_window, IEvent* event, - void (UiHost::*handler)(INativeWindow*, typename IEvent::EventArgs), - std::vector& guard_pool) { - guard_pool.push_back(EventRevokerGuard(event->AddHandler( - std::bind(handler, host, native_window, std::placeholders::_1)))); -} -} // namespace - -UiHost::UiHost(Window* window) - : mouse_hover_control_(nullptr), - focus_control_(window), - mouse_captured_control_(nullptr), - window_control_(window) { - native_window_resolver_ = - IUiApplication::GetInstance()->CreateWindow(nullptr); - - const auto native_window = native_window_resolver_->Resolve(); - window->ui_host_ = this; - - root_render_object_ = std::make_unique(this); - root_render_object_->SetAttachedControl(window); - window->render_object_ = root_render_object_.get(); - - BindNativeEvent(this, native_window, native_window->DestroyEvent(), - &UiHost::OnNativeDestroy, event_revoker_guards_); - BindNativeEvent(this, native_window, native_window->PaintEvent(), - &UiHost::OnNativePaint, event_revoker_guards_); - BindNativeEvent(this, native_window, native_window->ResizeEvent(), - &UiHost::OnNativeResize, event_revoker_guards_); - BindNativeEvent(this, native_window, native_window->FocusEvent(), - &UiHost::OnNativeFocus, event_revoker_guards_); - BindNativeEvent(this, native_window, native_window->MouseEnterLeaveEvent(), - &UiHost::OnNativeMouseEnterLeave, event_revoker_guards_); - BindNativeEvent(this, native_window, native_window->MouseMoveEvent(), - &UiHost::OnNativeMouseMove, event_revoker_guards_); - BindNativeEvent(this, native_window, native_window->MouseDownEvent(), - &UiHost::OnNativeMouseDown, event_revoker_guards_); - BindNativeEvent(this, native_window, native_window->MouseUpEvent(), - &UiHost::OnNativeMouseUp, event_revoker_guards_); - BindNativeEvent(this, native_window, native_window->KeyDownEvent(), - &UiHost::OnNativeKeyDown, event_revoker_guards_); - BindNativeEvent(this, native_window, native_window->KeyUpEvent(), - &UiHost::OnNativeKeyUp, event_revoker_guards_); -} - -UiHost::~UiHost() { - deleting_ = true; - window_control_->TraverseDescendants( - [this](Control* control) { control->OnDetachFromHost(this); }); - if (!native_window_destroyed_) { - const auto native_window = native_window_resolver_->Resolve(); - if (native_window) { - native_window->Close(); - } - } -} - -void UiHost::InvalidatePaint() { - if (const auto native_window = native_window_resolver_->Resolve()) - native_window->RequestRepaint(); -} - -void UiHost::InvalidateLayout() { - if (!need_layout_) { - platform::native::IUiApplication::GetInstance()->InvokeLater( - [resolver = this->CreateResolver()] { - if (const auto host = resolver.Resolve()) { - host->Relayout(); - host->need_layout_ = false; - host->after_layout_event_.Raise(AfterLayoutEventArgs{}); - log::Debug("A relayout finished."); - host->InvalidatePaint(); - } - }); - need_layout_ = true; - } -} - -void UiHost::Relayout() { - const auto native_window = native_window_resolver_->Resolve(); - const auto client_size = native_window - ? native_window->GetClientSize() - : Size{100, 100}; // a reasonable assumed size - root_render_object_->Measure(client_size); - root_render_object_->Layout(Rect{Point{}, client_size}); -} - -bool UiHost::RequestFocusFor(Control* control) { - Expects(control != nullptr); // The control to request focus can't be null. - // You can set it as the window. - - if (focus_control_ == control) return true; - - DispatchEvent(event_names::LoseFocus, focus_control_, - &Control::LoseFocusEvent, nullptr, false); - - focus_control_ = control; - - DispatchEvent(event_names::GainFocus, control, &Control::GainFocusEvent, - nullptr, false); - - return true; -} - -Control* UiHost::GetFocusControl() { return focus_control_; } - -bool UiHost::CaptureMouseFor(Control* control) { - const auto native_window = native_window_resolver_->Resolve(); - if (!native_window) return false; - - if (control == mouse_captured_control_) return true; - - if (control == nullptr) { - const auto old_capture_control = mouse_captured_control_; - mouse_captured_control_ = - nullptr; // update this in case this is used in event handlers - if (old_capture_control != mouse_hover_control_) { - DispatchMouseHoverControlChangeEvent( - old_capture_control, mouse_hover_control_, - native_window->GetMousePosition(), true, false); - } - UpdateCursor(); - return true; - } - - if (mouse_captured_control_) return false; - - mouse_captured_control_ = control; - DispatchMouseHoverControlChangeEvent( - mouse_hover_control_, mouse_captured_control_, - native_window->GetMousePosition(), false, true); - UpdateCursor(); - return true; -} - -Control* UiHost::GetMouseCaptureControl() { return mouse_captured_control_; } - -void UiHost::OnNativeDestroy(INativeWindow* window, std::nullptr_t) { - CRU_UNUSED(window) - native_window_destroyed_ = true; - if (!deleting_ && !retain_after_destroy_) delete window_control_; -} - -void UiHost::OnNativePaint(INativeWindow* window, std::nullptr_t) { - auto painter = window->BeginPaint(); - root_render_object_->Draw(painter.get()); - painter->EndDraw(); -} - -void UiHost::OnNativeResize(INativeWindow* window, const Size& size) { - CRU_UNUSED(window) - CRU_UNUSED(size) - - InvalidateLayout(); -} - -void UiHost::OnNativeFocus(INativeWindow* window, - platform::native::FocusChangeType focus) { - CRU_UNUSED(window) - - focus == platform::native::FocusChangeType::Gain - ? DispatchEvent(event_names::GainFocus, focus_control_, - &Control::GainFocusEvent, nullptr, true) - : DispatchEvent(event_names::LoseFocus, focus_control_, - &Control::LoseFocusEvent, nullptr, true); -} - -void UiHost::OnNativeMouseEnterLeave( - INativeWindow* window, platform::native::MouseEnterLeaveType type) { - CRU_UNUSED(window) - - if (type == platform::native::MouseEnterLeaveType::Leave) { - DispatchEvent(event_names::MouseLeave, mouse_hover_control_, - &Control::MouseLeaveEvent, nullptr); - mouse_hover_control_ = nullptr; - } -} - -void UiHost::OnNativeMouseMove(INativeWindow* window, const Point& point) { - CRU_UNUSED(window) - - // Find the first control that hit test succeed. - const auto new_mouse_hover_control = HitTest(point); - const auto old_mouse_hover_control = mouse_hover_control_; - mouse_hover_control_ = new_mouse_hover_control; - - if (mouse_captured_control_) { - const auto n = FindLowestCommonAncestor(new_mouse_hover_control, - mouse_captured_control_); - const auto o = FindLowestCommonAncestor(old_mouse_hover_control, - mouse_captured_control_); - bool a = IsAncestor(o, n); - if (a) { - DispatchEvent(event_names::MouseLeave, o, &Control::MouseLeaveEvent, n); - } else { - DispatchEvent(event_names::MouseEnter, n, &Control::MouseEnterEvent, o, - point); - } - DispatchEvent(event_names::MouseMove, mouse_captured_control_, - &Control::MouseMoveEvent, nullptr, point); - UpdateCursor(); - return; - } - - DispatchMouseHoverControlChangeEvent( - old_mouse_hover_control, new_mouse_hover_control, point, false, false); - DispatchEvent(event_names::MouseMove, new_mouse_hover_control, - &Control::MouseMoveEvent, nullptr, point); - UpdateCursor(); -} - -void UiHost::OnNativeMouseDown( - INativeWindow* window, - const platform::native::NativeMouseButtonEventArgs& args) { - CRU_UNUSED(window) - - Control* control = - mouse_captured_control_ ? mouse_captured_control_ : HitTest(args.point); - DispatchEvent(event_names::MouseDown, control, &Control::MouseDownEvent, - nullptr, args.point, args.button, args.modifier); -} - -void UiHost::OnNativeMouseUp( - INativeWindow* window, - const platform::native::NativeMouseButtonEventArgs& args) { - CRU_UNUSED(window) - - Control* control = - mouse_captured_control_ ? mouse_captured_control_ : HitTest(args.point); - DispatchEvent(event_names::MouseUp, control, &Control::MouseUpEvent, nullptr, - args.point, args.button, args.modifier); -} - -void UiHost::OnNativeKeyDown(INativeWindow* window, - const platform::native::NativeKeyEventArgs& args) { - CRU_UNUSED(window) - - DispatchEvent(event_names::KeyDown, focus_control_, &Control::KeyDownEvent, - nullptr, args.key, args.modifier); -} - -void UiHost::OnNativeKeyUp(INativeWindow* window, - const platform::native::NativeKeyEventArgs& args) { - CRU_UNUSED(window) - - DispatchEvent(event_names::KeyUp, focus_control_, &Control::KeyUpEvent, - nullptr, args.key, args.modifier); -} - -void UiHost::DispatchMouseHoverControlChangeEvent(Control* old_control, - Control* new_control, - const Point& point, - bool no_leave, - bool no_enter) { - if (new_control != old_control) // if the mouse-hover-on control changed - { - const auto lowest_common_ancestor = - FindLowestCommonAncestor(old_control, new_control); - if (!no_leave && old_control != nullptr) - DispatchEvent(event_names::MouseLeave, old_control, - &Control::MouseLeaveEvent, - lowest_common_ancestor); // dispatch mouse leave event. - if (!no_enter && new_control != nullptr) { - DispatchEvent(event_names::MouseEnter, new_control, - &Control::MouseEnterEvent, lowest_common_ancestor, - point); // dispatch mouse enter event. - } - } -} - -void UiHost::UpdateCursor() { - if (const auto native_window = native_window_resolver_->Resolve()) { - const auto capture = GetMouseCaptureControl(); - native_window->SetCursor( - (capture ? capture : GetMouseHoverControl())->GetInheritedCursor()); - } -} - -Control* UiHost::HitTest(const Point& point) { - return root_render_object_->HitTest(point)->GetAttachedControl(); -} -} // namespace cru::ui diff --git a/src/ui/ui_manager.cpp b/src/ui/ui_manager.cpp deleted file mode 100644 index 905d29ad..00000000 --- a/src/ui/ui_manager.cpp +++ /dev/null @@ -1,81 +0,0 @@ -#include "cru/ui/ui_manager.hpp" - -#include "cru/platform/graph/brush.hpp" -#include "cru/platform/graph/factory.hpp" -#include "cru/platform/graph/font.hpp" -#include "cru/platform/native/ui_application.hpp" -#include "helper.hpp" - -namespace cru::ui { -using namespace cru::platform::graph; - -namespace { -std::unique_ptr CreateSolidColorBrush(IGraphFactory* factory, - const Color& color) { - auto brush = factory->CreateSolidColorBrush(); - brush->SetColor(color); - return brush; -} -} // namespace - -UiManager* UiManager::GetInstance() { - static UiManager* instance = new UiManager(); - GetUiApplication()->AddOnQuitHandler([] { - delete instance; - instance = nullptr; - }); - return instance; -} - -UiManager::UiManager() { - const auto factory = GetGraphFactory(); - - theme_resource_.default_font = factory->CreateFont("等线", 24.0f); - - const auto black_brush = std::shared_ptr( - CreateSolidColorBrush(factory, colors::black)); - theme_resource_.text_brush = black_brush; - theme_resource_.text_selection_brush = - CreateSolidColorBrush(factory, colors::skyblue); - theme_resource_.caret_brush = black_brush; - - theme_resource_.button_style.normal.border_brush = - CreateSolidColorBrush(factory, Color::FromHex(0x00bfff)); - theme_resource_.button_style.hover.border_brush = - CreateSolidColorBrush(factory, Color::FromHex(0x47d1ff)); - theme_resource_.button_style.press.border_brush = - CreateSolidColorBrush(factory, Color::FromHex(0x91e4ff)); - theme_resource_.button_style.press_cancel.border_brush = - CreateSolidColorBrush(factory, Color::FromHex(0x91e4ff)); - - theme_resource_.button_style.normal.border_thickness = - theme_resource_.button_style.hover.border_thickness = - theme_resource_.button_style.press.border_thickness = - theme_resource_.button_style.press_cancel.border_thickness = - Thickness(3); - - theme_resource_.button_style.normal.border_radius = - theme_resource_.button_style.hover.border_radius = - theme_resource_.button_style.press.border_radius = - theme_resource_.button_style.press_cancel.border_radius = - CornerRadius({5, 5}); - - theme_resource_.text_box_border_style.normal.border_brush = - CreateSolidColorBrush(factory, Color::FromHex(0xced4da)); - theme_resource_.text_box_border_style.normal.border_radius = CornerRadius(5); - theme_resource_.text_box_border_style.normal.border_thickness = Thickness(1); - - theme_resource_.text_box_border_style.hover = - theme_resource_.text_box_border_style.normal; - - theme_resource_.text_box_border_style.normal.border_brush = - CreateSolidColorBrush(factory, Color::FromHex(0x495057)); - theme_resource_.text_box_border_style.normal.border_radius = CornerRadius(5); - theme_resource_.text_box_border_style.normal.border_thickness = Thickness(1); - - theme_resource_.text_box_border_style.focus_hover = - theme_resource_.text_box_border_style.focus; -} - -UiManager::~UiManager() = default; -} // namespace cru::ui diff --git a/src/ui/window.cpp b/src/ui/window.cpp index 7c0683af..de7044dd 100644 --- a/src/ui/window.cpp +++ b/src/ui/window.cpp @@ -1,7 +1,7 @@ -#include "cru/ui/window.hpp" +#include "cru/ui/Window.hpp" -#include "cru/ui/render/window_render_object.hpp" -#include "cru/ui/ui_host.hpp" +#include "cru/ui/render/WindowRenderObject.hpp" +#include "cru/ui/UiHost.hpp" namespace cru::ui { Window* Window::CreateOverlapped() { diff --git a/src/win/CMakeLists.txt b/src/win/CMakeLists.txt index 696ef200..f4062411 100644 --- a/src/win/CMakeLists.txt +++ b/src/win/CMakeLists.txt @@ -1,16 +1,16 @@ set(CRU_WIN_BASE_INCLUDE_DIR ${CRU_INCLUDE_DIR}/cru/win/) add_library(cru_win_base STATIC - debug_logger.hpp + DebugLogger.hpp - exception.cpp - heap_debug.cpp - string.cpp + Exception.cpp + HeapDebug.cpp + String.cpp ) target_sources(cru_win_base PUBLIC - ${CRU_WIN_BASE_INCLUDE_DIR}/exception.hpp - ${CRU_WIN_BASE_INCLUDE_DIR}/string.hpp - ${CRU_WIN_BASE_INCLUDE_DIR}/win_pre_config.hpp + ${CRU_WIN_BASE_INCLUDE_DIR}/Exception.hpp + ${CRU_WIN_BASE_INCLUDE_DIR}/String.hpp + ${CRU_WIN_BASE_INCLUDE_DIR}/WinPreConfig.hpp ) target_compile_definitions(cru_win_base PUBLIC UNICODE _UNICODE) # use unicode target_link_libraries(cru_win_base PUBLIC cru_base) diff --git a/src/win/DebugLogger.hpp b/src/win/DebugLogger.hpp new file mode 100644 index 00000000..381803bc --- /dev/null +++ b/src/win/DebugLogger.hpp @@ -0,0 +1,24 @@ +#include "cru/win/WinPreConfig.hpp" + +#include "cru/common/Logger.hpp" +#include "cru/win/String.hpp" + +namespace cru::platform::win { + +class WinDebugLoggerSource : public ::cru::log::ILogSource { + public: + WinDebugLoggerSource() = default; + + CRU_DELETE_COPY(WinDebugLoggerSource) + CRU_DELETE_MOVE(WinDebugLoggerSource) + + ~WinDebugLoggerSource() = default; + + void Write(::cru::log::LogLevel level, const std::string_view& s) override { + CRU_UNUSED(level) + + const std::wstring&& ws = ToUtf16String(s); + ::OutputDebugStringW(ws.data()); + } +}; +} // namespace cru::platform::win diff --git a/src/win/HeapDebug.cpp b/src/win/HeapDebug.cpp new file mode 100644 index 00000000..6ec1ffcc --- /dev/null +++ b/src/win/HeapDebug.cpp @@ -0,0 +1,11 @@ +#include "cru/win/WinPreConfig.hpp" + +#include "cru/platform/HeapDebug.hpp" + +#include + +namespace cru::platform { +void SetupHeapDebug() { + _CrtSetDbgFlag(_CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF); +} +} diff --git a/src/win/debug_logger.hpp b/src/win/debug_logger.hpp deleted file mode 100644 index b6694279..00000000 --- a/src/win/debug_logger.hpp +++ /dev/null @@ -1,24 +0,0 @@ -#include "cru/win/win_pre_config.hpp" - -#include "cru/common/logger.hpp" -#include "cru/win/string.hpp" - -namespace cru::platform::win { - -class WinDebugLoggerSource : public ::cru::log::ILogSource { - public: - WinDebugLoggerSource() = default; - - CRU_DELETE_COPY(WinDebugLoggerSource) - CRU_DELETE_MOVE(WinDebugLoggerSource) - - ~WinDebugLoggerSource() = default; - - void Write(::cru::log::LogLevel level, const std::string_view& s) override { - CRU_UNUSED(level) - - const std::wstring&& ws = ToUtf16String(s); - ::OutputDebugStringW(ws.data()); - } -}; -} // namespace cru::platform::win diff --git a/src/win/exception.cpp b/src/win/exception.cpp index fde3ec6f..9f9fb03d 100644 --- a/src/win/exception.cpp +++ b/src/win/exception.cpp @@ -1,4 +1,4 @@ -#include "cru/win/exception.hpp" +#include "cru/win/Exception.hpp" #include #include diff --git a/src/win/graph/direct/CMakeLists.txt b/src/win/graph/direct/CMakeLists.txt index 492f6749..5505b0b5 100644 --- a/src/win/graph/direct/CMakeLists.txt +++ b/src/win/graph/direct/CMakeLists.txt @@ -1,25 +1,25 @@ set(CRU_WIN_GRAPH_DIRECT_INCLUDE_DIR ${CRU_INCLUDE_DIR}/cru/win/graph/direct) add_library(cru_win_graph_direct STATIC - brush.cpp - font.cpp - geometry.cpp - factory.cpp - painter.cpp - resource.cpp - text_layout.cpp + Brush.cpp + Font.cpp + Geometry.cpp + Factory.cpp + Painter.cpp + Resource.cpp + TextLayout.cpp ) target_sources(cru_win_graph_direct PUBLIC - ${CRU_WIN_GRAPH_DIRECT_INCLUDE_DIR}/brush.hpp - ${CRU_WIN_GRAPH_DIRECT_INCLUDE_DIR}/com_resource.hpp - ${CRU_WIN_GRAPH_DIRECT_INCLUDE_DIR}/convert_util.hpp - ${CRU_WIN_GRAPH_DIRECT_INCLUDE_DIR}/exception.hpp - ${CRU_WIN_GRAPH_DIRECT_INCLUDE_DIR}/font.hpp - ${CRU_WIN_GRAPH_DIRECT_INCLUDE_DIR}/geometry.hpp - ${CRU_WIN_GRAPH_DIRECT_INCLUDE_DIR}/factory.hpp - ${CRU_WIN_GRAPH_DIRECT_INCLUDE_DIR}/painter.hpp - ${CRU_WIN_GRAPH_DIRECT_INCLUDE_DIR}/resource.hpp - ${CRU_WIN_GRAPH_DIRECT_INCLUDE_DIR}/text_layout.hpp + ${CRU_WIN_GRAPH_DIRECT_INCLUDE_DIR}/Brush.hpp + ${CRU_WIN_GRAPH_DIRECT_INCLUDE_DIR}/ComResource.hpp + ${CRU_WIN_GRAPH_DIRECT_INCLUDE_DIR}/ConvertUtil.hpp + ${CRU_WIN_GRAPH_DIRECT_INCLUDE_DIR}/Exception.hpp + ${CRU_WIN_GRAPH_DIRECT_INCLUDE_DIR}/Font.hpp + ${CRU_WIN_GRAPH_DIRECT_INCLUDE_DIR}/Geometry.hpp + ${CRU_WIN_GRAPH_DIRECT_INCLUDE_DIR}/Factory.hpp + ${CRU_WIN_GRAPH_DIRECT_INCLUDE_DIR}/Painter.hpp + ${CRU_WIN_GRAPH_DIRECT_INCLUDE_DIR}/Resource.hpp + ${CRU_WIN_GRAPH_DIRECT_INCLUDE_DIR}/TextLayout.hpp ) target_link_libraries(cru_win_graph_direct PUBLIC D3D11 D2d1 DWrite) target_link_libraries(cru_win_graph_direct PUBLIC cru_win_base cru_platform_graph) diff --git a/src/win/graph/direct/TextLayout.cpp b/src/win/graph/direct/TextLayout.cpp new file mode 100644 index 00000000..16db77f0 --- /dev/null +++ b/src/win/graph/direct/TextLayout.cpp @@ -0,0 +1,137 @@ +#include "cru/win/graph/direct/TextLayout.hpp" + +#include "cru/common/Logger.hpp" +#include "cru/platform/Check.hpp" +#include "cru/win/graph/direct/Exception.hpp" +#include "cru/win/graph/direct/Factory.hpp" +#include "cru/win/graph/direct/Font.hpp" +#include "cru/win/String.hpp" + +#include + +namespace cru::platform::graph::win::direct { +using cru::platform::win::IndexUtf16ToUtf8; +using cru::platform::win::IndexUtf8ToUtf16; + +DWriteTextLayout::DWriteTextLayout(DirectGraphFactory* factory, + std::shared_ptr font, + std::string text) + : DirectGraphResource(factory), text_(std::move(text)) { + Expects(font); + font_ = CheckPlatform(font, GetPlatformId()); + + w_text_ = cru::platform::win::ToUtf16String(text_); + + ThrowIfFailed(factory->GetDWriteFactory()->CreateTextLayout( + w_text_.c_str(), static_cast(w_text_.size()), + font_->GetComInterface(), max_width_, max_height_, &text_layout_)); +} + +DWriteTextLayout::~DWriteTextLayout() = default; + +std::string DWriteTextLayout::GetText() { return text_; } + +void DWriteTextLayout::SetText(std::string new_text) { + text_.swap(new_text); + w_text_ = cru::platform::win::ToUtf16String(text_); + ThrowIfFailed(GetDirectFactory()->GetDWriteFactory()->CreateTextLayout( + w_text_.c_str(), static_cast(w_text_.size()), + font_->GetComInterface(), max_width_, max_height_, &text_layout_)); +} + +std::shared_ptr DWriteTextLayout::GetFont() { + return std::dynamic_pointer_cast(font_); +} + +void DWriteTextLayout::SetFont(std::shared_ptr font) { + font_ = CheckPlatform(font, GetPlatformId()); + ThrowIfFailed(GetDirectFactory()->GetDWriteFactory()->CreateTextLayout( + w_text_.c_str(), static_cast(w_text_.size()), + font_->GetComInterface(), max_width_, max_height_, &text_layout_)); +} + +void DWriteTextLayout::SetMaxWidth(float max_width) { + max_width_ = max_width; + ThrowIfFailed(text_layout_->SetMaxWidth(max_width_)); +} + +void DWriteTextLayout::SetMaxHeight(float max_height) { + max_height_ = max_height; + ThrowIfFailed(text_layout_->SetMaxHeight(max_height_)); +} + +Rect DWriteTextLayout::GetTextBounds() { + DWRITE_TEXT_METRICS metrics; + ThrowIfFailed(text_layout_->GetMetrics(&metrics)); + return Rect{metrics.left, metrics.top, metrics.width, metrics.height}; +} + +std::vector DWriteTextLayout::TextRangeRect( + const TextRange& text_range_arg) { + if (text_range_arg.count == 0) { + return {}; + } + const auto text_range = text_range_arg.Normalize(); + + // TODO: This can be faster with one iteration. + const int start_index = + IndexUtf8ToUtf16(text_, static_cast(text_range.position), w_text_); + const int end_index = IndexUtf8ToUtf16( + text_, static_cast(text_range.position + text_range.count), w_text_); + + DWRITE_TEXT_METRICS text_metrics; + ThrowIfFailed(text_layout_->GetMetrics(&text_metrics)); + const auto metrics_count = + text_metrics.lineCount * text_metrics.maxBidiReorderingDepth; + + std::vector hit_test_metrics(metrics_count); + UINT32 actual_count; + ThrowIfFailed(text_layout_->HitTestTextRange( + static_cast(start_index), + static_cast(end_index - start_index), 0, 0, + hit_test_metrics.data(), metrics_count, &actual_count)); + + hit_test_metrics.erase(hit_test_metrics.cbegin() + actual_count, + hit_test_metrics.cend()); + + std::vector result; + result.reserve(actual_count); + + for (const auto& metrics : hit_test_metrics) { + result.push_back( + Rect{metrics.left, metrics.top, metrics.width, metrics.height}); + } + + return result; +} + +Point DWriteTextLayout::TextSinglePoint(gsl::index position, bool trailing) { + const auto index = + IndexUtf8ToUtf16(text_, static_cast(position), w_text_); + + DWRITE_HIT_TEST_METRICS metrics; + FLOAT left; + FLOAT top; + ThrowIfFailed(text_layout_->HitTestTextPosition(static_cast(index), + static_cast(trailing), + &left, &top, &metrics)); + return Point{left, top}; +} + +TextHitTestResult DWriteTextLayout::HitTest(const Point& point) { + BOOL trailing; + BOOL inside; + DWRITE_HIT_TEST_METRICS metrics; + + ThrowIfFailed(text_layout_->HitTestPoint(point.x, point.y, &trailing, &inside, + &metrics)); + + const auto index = + IndexUtf16ToUtf8(w_text_, static_cast(metrics.textPosition), text_); + TextHitTestResult result; + result.position = index; + result.trailing = trailing != 0; + result.insideText = inside != 0; + return result; +} +} // namespace cru::platform::graph::win::direct diff --git a/src/win/graph/direct/brush.cpp b/src/win/graph/direct/brush.cpp index 329ce509..2b9d4ac3 100644 --- a/src/win/graph/direct/brush.cpp +++ b/src/win/graph/direct/brush.cpp @@ -1,8 +1,8 @@ -#include "cru/win/graph/direct/brush.hpp" +#include "cru/win/graph/direct/Brush.hpp" -#include "cru/win/graph/direct/convert_util.hpp" -#include "cru/win/graph/direct/exception.hpp" -#include "cru/win/graph/direct/factory.hpp" +#include "cru/win/graph/direct/ConvertUtil.hpp" +#include "cru/win/graph/direct/Exception.hpp" +#include "cru/win/graph/direct/Factory.hpp" namespace cru::platform::graph::win::direct { D2DSolidColorBrush::D2DSolidColorBrush(DirectGraphFactory* factory) diff --git a/src/win/graph/direct/factory.cpp b/src/win/graph/direct/factory.cpp index 0f4739ee..d9213994 100644 --- a/src/win/graph/direct/factory.cpp +++ b/src/win/graph/direct/factory.cpp @@ -1,11 +1,11 @@ -#include "cru/win/graph/direct/factory.hpp" - -#include "cru/common/logger.hpp" -#include "cru/win/graph/direct/brush.hpp" -#include "cru/win/graph/direct/exception.hpp" -#include "cru/win/graph/direct/font.hpp" -#include "cru/win/graph/direct/geometry.hpp" -#include "cru/win/graph/direct/text_layout.hpp" +#include "cru/win/graph/direct/Factory.hpp" + +#include "cru/common/Logger.hpp" +#include "cru/win/graph/direct/Brush.hpp" +#include "cru/win/graph/direct/Exception.hpp" +#include "cru/win/graph/direct/Font.hpp" +#include "cru/win/graph/direct/Geometry.hpp" +#include "cru/win/graph/direct/TextLayout.hpp" #include #include diff --git a/src/win/graph/direct/font.cpp b/src/win/graph/direct/font.cpp index 6d50fafe..edbbc59d 100644 --- a/src/win/graph/direct/font.cpp +++ b/src/win/graph/direct/font.cpp @@ -1,8 +1,8 @@ -#include "cru/win/graph/direct/font.hpp" +#include "cru/win/graph/direct/Font.hpp" -#include "cru/win/graph/direct/exception.hpp" -#include "cru/win/graph/direct/factory.hpp" -#include "cru/win/string.hpp" +#include "cru/win/graph/direct/Exception.hpp" +#include "cru/win/graph/direct/Factory.hpp" +#include "cru/win/String.hpp" #include #include diff --git a/src/win/graph/direct/geometry.cpp b/src/win/graph/direct/geometry.cpp index 57b7f237..e77b4749 100644 --- a/src/win/graph/direct/geometry.cpp +++ b/src/win/graph/direct/geometry.cpp @@ -1,8 +1,8 @@ -#include "cru/win/graph/direct/geometry.hpp" +#include "cru/win/graph/direct/Geometry.hpp" -#include "cru/win/graph/direct/convert_util.hpp" -#include "cru/win/graph/direct/exception.hpp" -#include "cru/win/graph/direct/factory.hpp" +#include "cru/win/graph/direct/ConvertUtil.hpp" +#include "cru/win/graph/direct/Exception.hpp" +#include "cru/win/graph/direct/Factory.hpp" namespace cru::platform::graph::win::direct { D2DGeometryBuilder::D2DGeometryBuilder(DirectGraphFactory* factory) diff --git a/src/win/graph/direct/painter.cpp b/src/win/graph/direct/painter.cpp index df0075e0..3ffb5208 100644 --- a/src/win/graph/direct/painter.cpp +++ b/src/win/graph/direct/painter.cpp @@ -1,11 +1,11 @@ -#include "cru/win/graph/direct/painter.hpp" - -#include "cru/platform/check.hpp" -#include "cru/win/graph/direct/brush.hpp" -#include "cru/win/graph/direct/convert_util.hpp" -#include "cru/win/graph/direct/exception.hpp" -#include "cru/win/graph/direct/geometry.hpp" -#include "cru/win/graph/direct/text_layout.hpp" +#include "cru/win/graph/direct/Painter.hpp" + +#include "cru/platform/Check.hpp" +#include "cru/win/graph/direct/Brush.hpp" +#include "cru/win/graph/direct/ConvertUtil.hpp" +#include "cru/win/graph/direct/Exception.hpp" +#include "cru/win/graph/direct/Geometry.hpp" +#include "cru/win/graph/direct/TextLayout.hpp" #include diff --git a/src/win/graph/direct/resource.cpp b/src/win/graph/direct/resource.cpp index c2be27ed..772bb74b 100644 --- a/src/win/graph/direct/resource.cpp +++ b/src/win/graph/direct/resource.cpp @@ -1,6 +1,6 @@ -#include "cru/win/graph/direct/resource.hpp" +#include "cru/win/graph/direct/Resource.hpp" -#include "cru/win/graph/direct/factory.hpp" +#include "cru/win/graph/direct/Factory.hpp" namespace cru::platform::graph::win::direct { DirectGraphResource::DirectGraphResource(DirectGraphFactory* factory) diff --git a/src/win/graph/direct/text_layout.cpp b/src/win/graph/direct/text_layout.cpp deleted file mode 100644 index 7b8d2ab0..00000000 --- a/src/win/graph/direct/text_layout.cpp +++ /dev/null @@ -1,137 +0,0 @@ -#include "cru/win/graph/direct/text_layout.hpp" - -#include "cru/common/logger.hpp" -#include "cru/platform/check.hpp" -#include "cru/win/graph/direct/exception.hpp" -#include "cru/win/graph/direct/factory.hpp" -#include "cru/win/graph/direct/font.hpp" -#include "cru/win/string.hpp" - -#include - -namespace cru::platform::graph::win::direct { -using cru::platform::win::IndexUtf16ToUtf8; -using cru::platform::win::IndexUtf8ToUtf16; - -DWriteTextLayout::DWriteTextLayout(DirectGraphFactory* factory, - std::shared_ptr font, - std::string text) - : DirectGraphResource(factory), text_(std::move(text)) { - Expects(font); - font_ = CheckPlatform(font, GetPlatformId()); - - w_text_ = cru::platform::win::ToUtf16String(text_); - - ThrowIfFailed(factory->GetDWriteFactory()->CreateTextLayout( - w_text_.c_str(), static_cast(w_text_.size()), - font_->GetComInterface(), max_width_, max_height_, &text_layout_)); -} - -DWriteTextLayout::~DWriteTextLayout() = default; - -std::string DWriteTextLayout::GetText() { return text_; } - -void DWriteTextLayout::SetText(std::string new_text) { - text_.swap(new_text); - w_text_ = cru::platform::win::ToUtf16String(text_); - ThrowIfFailed(GetDirectFactory()->GetDWriteFactory()->CreateTextLayout( - w_text_.c_str(), static_cast(w_text_.size()), - font_->GetComInterface(), max_width_, max_height_, &text_layout_)); -} - -std::shared_ptr DWriteTextLayout::GetFont() { - return std::dynamic_pointer_cast(font_); -} - -void DWriteTextLayout::SetFont(std::shared_ptr font) { - font_ = CheckPlatform(font, GetPlatformId()); - ThrowIfFailed(GetDirectFactory()->GetDWriteFactory()->CreateTextLayout( - w_text_.c_str(), static_cast(w_text_.size()), - font_->GetComInterface(), max_width_, max_height_, &text_layout_)); -} - -void DWriteTextLayout::SetMaxWidth(float max_width) { - max_width_ = max_width; - ThrowIfFailed(text_layout_->SetMaxWidth(max_width_)); -} - -void DWriteTextLayout::SetMaxHeight(float max_height) { - max_height_ = max_height; - ThrowIfFailed(text_layout_->SetMaxHeight(max_height_)); -} - -Rect DWriteTextLayout::GetTextBounds() { - DWRITE_TEXT_METRICS metrics; - ThrowIfFailed(text_layout_->GetMetrics(&metrics)); - return Rect{metrics.left, metrics.top, metrics.width, metrics.height}; -} - -std::vector DWriteTextLayout::TextRangeRect( - const TextRange& text_range_arg) { - if (text_range_arg.count == 0) { - return {}; - } - const auto text_range = text_range_arg.Normalize(); - - // TODO: This can be faster with one iteration. - const int start_index = - IndexUtf8ToUtf16(text_, static_cast(text_range.position), w_text_); - const int end_index = IndexUtf8ToUtf16( - text_, static_cast(text_range.position + text_range.count), w_text_); - - DWRITE_TEXT_METRICS text_metrics; - ThrowIfFailed(text_layout_->GetMetrics(&text_metrics)); - const auto metrics_count = - text_metrics.lineCount * text_metrics.maxBidiReorderingDepth; - - std::vector hit_test_metrics(metrics_count); - UINT32 actual_count; - ThrowIfFailed(text_layout_->HitTestTextRange( - static_cast(start_index), - static_cast(end_index - start_index), 0, 0, - hit_test_metrics.data(), metrics_count, &actual_count)); - - hit_test_metrics.erase(hit_test_metrics.cbegin() + actual_count, - hit_test_metrics.cend()); - - std::vector result; - result.reserve(actual_count); - - for (const auto& metrics : hit_test_metrics) { - result.push_back( - Rect{metrics.left, metrics.top, metrics.width, metrics.height}); - } - - return result; -} - -Point DWriteTextLayout::TextSinglePoint(gsl::index position, bool trailing) { - const auto index = - IndexUtf8ToUtf16(text_, static_cast(position), w_text_); - - DWRITE_HIT_TEST_METRICS metrics; - FLOAT left; - FLOAT top; - ThrowIfFailed(text_layout_->HitTestTextPosition(static_cast(index), - static_cast(trailing), - &left, &top, &metrics)); - return Point{left, top}; -} - -TextHitTestResult DWriteTextLayout::HitTest(const Point& point) { - BOOL trailing; - BOOL inside; - DWRITE_HIT_TEST_METRICS metrics; - - ThrowIfFailed(text_layout_->HitTestPoint(point.x, point.y, &trailing, &inside, - &metrics)); - - const auto index = - IndexUtf16ToUtf8(w_text_, static_cast(metrics.textPosition), text_); - TextHitTestResult result; - result.position = index; - result.trailing = trailing != 0; - result.insideText = inside != 0; - return result; -} -} // namespace cru::platform::graph::win::direct diff --git a/src/win/heap_debug.cpp b/src/win/heap_debug.cpp deleted file mode 100644 index 33ebae7c..00000000 --- a/src/win/heap_debug.cpp +++ /dev/null @@ -1,11 +0,0 @@ -#include "cru/win/win_pre_config.hpp" - -#include "cru/platform/heap_debug.hpp" - -#include - -namespace cru::platform { -void SetupHeapDebug() { - _CrtSetDbgFlag(_CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF); -} -} diff --git a/src/win/native/CMakeLists.txt b/src/win/native/CMakeLists.txt index ba4b3387..f1b167d2 100644 --- a/src/win/native/CMakeLists.txt +++ b/src/win/native/CMakeLists.txt @@ -1,37 +1,37 @@ set(CRU_WIN_NATIVE_INCLUDE_DIR ${CRU_INCLUDE_DIR}/cru/win/native) add_library(cru_win_native STATIC - dpi_util.hpp - god_window_message.hpp - timer.hpp - window_d2d_painter.hpp - window_manager.hpp + DpiUtil.hpp + GodWindowMessage.hpp + Timer.hpp + WindowD2DPainter.hpp + WindowManager.hpp - cursor.cpp - god_window.cpp - input_method.cpp - keyboard.cpp - timer.cpp - ui_application.cpp - window.cpp - window_class.cpp - window_d2d_painter.cpp - window_manager.cpp - window_render_target.cpp + Cursor.cpp + GodWindow.cpp + InputMethod.cpp + Keyboard.cpp + Timer.cpp + UiApplication.cpp + Window.cpp + WindowClass.cpp + WindowD2DPainter.cpp + WindowManager.cpp + WindowRenderTarget.cpp ) target_sources(cru_win_native PUBLIC - ${CRU_WIN_NATIVE_INCLUDE_DIR}/cursor.hpp - ${CRU_WIN_NATIVE_INCLUDE_DIR}/exception.hpp - ${CRU_WIN_NATIVE_INCLUDE_DIR}/base.hpp - ${CRU_WIN_NATIVE_INCLUDE_DIR}/god_window.hpp - ${CRU_WIN_NATIVE_INCLUDE_DIR}/input_method.hpp - ${CRU_WIN_NATIVE_INCLUDE_DIR}/keyboard.hpp - ${CRU_WIN_NATIVE_INCLUDE_DIR}/resource.hpp - ${CRU_WIN_NATIVE_INCLUDE_DIR}/ui_application.hpp - ${CRU_WIN_NATIVE_INCLUDE_DIR}/window.hpp - ${CRU_WIN_NATIVE_INCLUDE_DIR}/window_class.hpp - ${CRU_WIN_NATIVE_INCLUDE_DIR}/window_native_message_event_args.hpp - ${CRU_WIN_NATIVE_INCLUDE_DIR}/window_render_target.hpp + ${CRU_WIN_NATIVE_INCLUDE_DIR}/Cursor.hpp + ${CRU_WIN_NATIVE_INCLUDE_DIR}/Exception.hpp + ${CRU_WIN_NATIVE_INCLUDE_DIR}/Base.hpp + ${CRU_WIN_NATIVE_INCLUDE_DIR}/GodWindow.hpp + ${CRU_WIN_NATIVE_INCLUDE_DIR}/InputMethod.hpp + ${CRU_WIN_NATIVE_INCLUDE_DIR}/Keyboard.hpp + ${CRU_WIN_NATIVE_INCLUDE_DIR}/Resource.hpp + ${CRU_WIN_NATIVE_INCLUDE_DIR}/UiApplication.hpp + ${CRU_WIN_NATIVE_INCLUDE_DIR}/Window.hpp + ${CRU_WIN_NATIVE_INCLUDE_DIR}/WindowClass.hpp + ${CRU_WIN_NATIVE_INCLUDE_DIR}/WindowNativeMessageEventArgs.hpp + ${CRU_WIN_NATIVE_INCLUDE_DIR}/WindowRenderTarget.hpp ) target_link_libraries(cru_win_native PUBLIC imm32) target_link_libraries(cru_win_native PUBLIC cru_win_graph_direct cru_platform_native) diff --git a/src/win/native/DpiUtil.hpp b/src/win/native/DpiUtil.hpp new file mode 100644 index 00000000..16ffda25 --- /dev/null +++ b/src/win/native/DpiUtil.hpp @@ -0,0 +1,46 @@ +#pragma once +#include "cru/platform/native/Base.hpp" + +// The dpi awareness needs to be implemented in the future. Currently we use 96 +// as default. + +namespace cru::platform::native::win { +inline platform::native::Dpi GetDpi() { + return platform::native::Dpi{96.0f, 96.0f}; +} + +inline int DipToPixelInternal(const float dip, const float dpi) { + return static_cast(dip * dpi / 96.0f); +} + +inline int DipToPixelX(const float dip_x) { + return DipToPixelInternal(dip_x, GetDpi().x); +} + +inline int DipToPixelY(const float dip_y) { + return DipToPixelInternal(dip_y, GetDpi().y); +} + +inline float DipToPixelInternal(const int pixel, const float dpi) { + return static_cast(pixel) * 96.0f / dpi; +} + +inline float PixelToDipX(const int pixel_x) { + return DipToPixelInternal(pixel_x, GetDpi().x); +} + +inline float PixelToDipY(const int pixel_y) { + return DipToPixelInternal(pixel_y, GetDpi().y); +} + +inline Point PiToDip(const POINT& pi_point) { + return Point(PixelToDipX(pi_point.x), PixelToDipY(pi_point.y)); +} + +inline POINT DipToPi(const Point& dip_point) { + POINT result; + result.x = DipToPixelX(dip_point.x); + result.y = DipToPixelY(dip_point.y); + return result; +} +} // namespace cru::platform::native::win diff --git a/src/win/native/GodWindow.cpp b/src/win/native/GodWindow.cpp new file mode 100644 index 00000000..076954d4 --- /dev/null +++ b/src/win/native/GodWindow.cpp @@ -0,0 +1,82 @@ +#include "cru/win/native/GodWindow.hpp" + +#include "cru/common/Logger.hpp" +#include "cru/win/native/Exception.hpp" +#include "cru/win/native/UiApplication.hpp" +#include "cru/win/native/WindowClass.hpp" +#include "GodWindowMessage.hpp" +#include "Timer.hpp" + +namespace cru::platform::native::win { +constexpr auto god_window_class_name = L"GodWindowClass"; + +LRESULT CALLBACK GodWndProc(HWND hWnd, UINT uMsg, WPARAM wParam, + LPARAM lParam) { + const auto app = WinUiApplication::GetInstance(); + + if (app) { + LRESULT result; + const auto handled = app->GetGodWindow()->HandleGodWindowMessage( + hWnd, uMsg, wParam, lParam, &result); + if (handled) + return result; + else + return DefWindowProcW(hWnd, uMsg, wParam, lParam); + } else + return DefWindowProcW(hWnd, uMsg, wParam, lParam); +} + +GodWindow::GodWindow(WinUiApplication* application) { + application_ = application; + + const auto h_instance = application->GetInstanceHandle(); + + god_window_class_ = std::make_unique(god_window_class_name, + GodWndProc, h_instance); + + hwnd_ = CreateWindowEx(0, god_window_class_name, L"", 0, CW_USEDEFAULT, + CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, + HWND_MESSAGE, nullptr, h_instance, nullptr); + + if (hwnd_ == nullptr) + throw Win32Error(::GetLastError(), "Failed to create god window."); +} + +GodWindow::~GodWindow() { + if (!::DestroyWindow(hwnd_)) { + // Although this could be "safely" ignore. + log::Warn("Failed to destroy god window."); + } +} + +bool GodWindow::HandleGodWindowMessage(HWND hwnd, UINT msg, WPARAM w_param, + LPARAM l_param, LRESULT* result) { + CRU_UNUSED(hwnd) + CRU_UNUSED(l_param) + + switch (msg) { + case invoke_later_message_id: { + const auto p_action = reinterpret_cast*>(w_param); + (*p_action)(); + delete p_action; + *result = 0; + return true; + } + case WM_TIMER: { + const auto id = static_cast(w_param); + const auto action = application_->GetTimerManager()->GetAction(id); + if (action.has_value()) { + (action.value().second)(); + if (!action.value().first) + application_->GetTimerManager()->KillTimer(id); + result = 0; + return true; + } + break; + } + default: + return false; + } + return false; +} +} // namespace cru::platform::native::win diff --git a/src/win/native/GodWindowMessage.hpp b/src/win/native/GodWindowMessage.hpp new file mode 100644 index 00000000..9063cb4d --- /dev/null +++ b/src/win/native/GodWindowMessage.hpp @@ -0,0 +1,6 @@ +#pragma once +#include "cru/win/WinPreConfig.hpp" + +namespace cru::platform::native::win { +constexpr int invoke_later_message_id = WM_USER + 2000; +} diff --git a/src/win/native/InputMethod.cpp b/src/win/native/InputMethod.cpp new file mode 100644 index 00000000..2e31108e --- /dev/null +++ b/src/win/native/InputMethod.cpp @@ -0,0 +1,324 @@ +#include "cru/win/native/InputMethod.hpp" + +#include "cru/common/Logger.hpp" +#include "cru/platform/Check.hpp" +#include "cru/win/Exception.hpp" +#include "cru/win/native/Window.hpp" +#include "cru/win/String.hpp" +#include "DpiUtil.hpp" + +#include + +namespace cru::platform::native::win { +AutoHIMC::AutoHIMC(HWND hwnd) : hwnd_(hwnd) { + Expects(hwnd); + handle_ = ::ImmGetContext(hwnd); +} + +AutoHIMC::AutoHIMC(AutoHIMC&& other) + : hwnd_(other.hwnd_), handle_(other.handle_) { + other.hwnd_ = nullptr; + other.handle_ = nullptr; +} + +AutoHIMC& AutoHIMC::operator=(AutoHIMC&& other) { + if (this != &other) { + Object::operator=(std::move(other)); + this->hwnd_ = other.hwnd_; + this->handle_ = other.handle_; + other.hwnd_ = nullptr; + other.handle_ = nullptr; + } + return *this; +} + +AutoHIMC::~AutoHIMC() { + if (handle_) { + if (!::ImmReleaseContext(hwnd_, handle_)) + log::Warn("AutoHIMC: Failed to release HIMC."); + } +} + +// copied from chromium +namespace { +// Determines whether or not the given attribute represents a target +// (a.k.a. a selection). +bool IsTargetAttribute(char attribute) { + return (attribute == ATTR_TARGET_CONVERTED || + attribute == ATTR_TARGET_NOTCONVERTED); +} +// Helper function for ImeInput::GetCompositionInfo() method, to get the target +// range that's selected by the user in the current composition string. +void GetCompositionTargetRange(HIMC imm_context, int* target_start, + int* target_end) { + int attribute_size = + ::ImmGetCompositionString(imm_context, GCS_COMPATTR, NULL, 0); + if (attribute_size > 0) { + int start = 0; + int end = 0; + std::vector attribute_data(attribute_size); + ::ImmGetCompositionString(imm_context, GCS_COMPATTR, attribute_data.data(), + attribute_size); + for (start = 0; start < attribute_size; ++start) { + if (IsTargetAttribute(attribute_data[start])) break; + } + for (end = start; end < attribute_size; ++end) { + if (!IsTargetAttribute(attribute_data[end])) break; + } + if (start == attribute_size) { + // This composition clause does not contain any target clauses, + // i.e. this clauses is an input clause. + // We treat the whole composition as a target clause. + start = 0; + end = attribute_size; + } + *target_start = start; + *target_end = end; + } +} +// Helper function for ImeInput::GetCompositionInfo() method, to get underlines +// information of the current composition string. +CompositionClauses GetCompositionClauses(HIMC imm_context, int target_start, + int target_end) { + CompositionClauses result; + int clause_size = + ::ImmGetCompositionString(imm_context, GCS_COMPCLAUSE, NULL, 0); + int clause_length = clause_size / sizeof(std::uint32_t); + if (clause_length) { + result.reserve(clause_length - 1); + std::vector clause_data(clause_length); + ::ImmGetCompositionString(imm_context, GCS_COMPCLAUSE, clause_data.data(), + clause_size); + for (int i = 0; i < clause_length - 1; ++i) { + CompositionClause clause; + clause.start = clause_data[i]; + clause.end = clause_data[i + 1]; + clause.target = false; + // Use thick underline for the target clause. + if (clause.start >= target_start && clause.end <= target_end) { + clause.target = true; + } + result.push_back(clause); + } + } + return result; +} + +std::wstring GetString(HIMC imm_context) { + LONG string_size = + ::ImmGetCompositionString(imm_context, GCS_COMPSTR, NULL, 0); + std::wstring result((string_size / sizeof(wchar_t)), 0); + ::ImmGetCompositionString(imm_context, GCS_COMPSTR, result.data(), + string_size); + return result; +} + +std::wstring GetResultString(HIMC imm_context) { + LONG string_size = + ::ImmGetCompositionString(imm_context, GCS_RESULTSTR, NULL, 0); + std::wstring result((string_size / sizeof(wchar_t)), 0); + ::ImmGetCompositionString(imm_context, GCS_RESULTSTR, result.data(), + string_size); + return result; +} + +CompositionText GetCompositionInfo(HIMC imm_context) { + // We only care about GCS_COMPATTR, GCS_COMPCLAUSE and GCS_CURSORPOS, and + // convert them into underlines and selection range respectively. + + auto w_text = GetString(imm_context); + + int w_length = static_cast(w_text.length()); + // Find out the range selected by the user. + int w_target_start = w_length; + int w_target_end = w_length; + GetCompositionTargetRange(imm_context, &w_target_start, &w_target_end); + + auto clauses = + GetCompositionClauses(imm_context, w_target_start, w_target_end); + + int w_cursor = ::ImmGetCompositionString(imm_context, GCS_CURSORPOS, NULL, 0); + + auto text = platform::win::ToUtf8String(w_text); + for (auto& clause : clauses) { + clause.start = platform::win::IndexUtf16ToUtf8(w_text, clause.start, text); + clause.end = platform::win::IndexUtf16ToUtf8(w_text, clause.end, text); + } + int cursor = platform::win::IndexUtf16ToUtf8(w_text, w_cursor, text); + + return CompositionText{std::move(text), std::move(clauses), + TextRange{cursor}}; +} + +} // namespace + +WinInputMethodContext::WinInputMethodContext( + gsl::not_null window) + : native_window_resolver_(window->GetResolver()) { + event_revoker_guards_.push_back( + EventRevokerGuard(window->NativeMessageEvent()->AddHandler( + std::bind(&WinInputMethodContext::OnWindowNativeMessage, this, + std::placeholders::_1)))); +} + +WinInputMethodContext::~WinInputMethodContext() {} + +void WinInputMethodContext::EnableIME() { + const auto native_window = Resolve(native_window_resolver_.get()); + if (native_window == nullptr) return; + const auto hwnd = native_window->GetWindowHandle(); + + if (::ImmAssociateContextEx(hwnd, nullptr, IACE_DEFAULT) == FALSE) { + log::Warn("WinInputMethodContext: Failed to enable ime."); + } +} + +void WinInputMethodContext::DisableIME() { + const auto native_window = Resolve(native_window_resolver_.get()); + if (native_window == nullptr) return; + const auto hwnd = native_window->GetWindowHandle(); + + AutoHIMC himc{hwnd}; + + if (!::ImmNotifyIME(himc.Get(), NI_COMPOSITIONSTR, CPS_COMPLETE, 0)) { + log::Warn( + "WinInputMethodContext: Failed to complete composition before disable " + "ime."); + } + + if (::ImmAssociateContextEx(hwnd, nullptr, 0) == FALSE) { + log::Warn("WinInputMethodContext: Failed to disable ime."); + } +} + +void WinInputMethodContext::CompleteComposition() { + auto optional_himc = TryGetHIMC(); + if (!optional_himc.has_value()) return; + auto himc = *std::move(optional_himc); + + if (!::ImmNotifyIME(himc.Get(), NI_COMPOSITIONSTR, CPS_COMPLETE, 0)) { + log::Warn("WinInputMethodContext: Failed to complete composition."); + } +} + +void WinInputMethodContext::CancelComposition() { + auto optional_himc = TryGetHIMC(); + if (!optional_himc.has_value()) return; + auto himc = *std::move(optional_himc); + + if (!::ImmNotifyIME(himc.Get(), NI_COMPOSITIONSTR, CPS_CANCEL, 0)) { + log::Warn("WinInputMethodContext: Failed to complete composition."); + } +} + +CompositionText WinInputMethodContext::GetCompositionText() { + auto optional_himc = TryGetHIMC(); + if (!optional_himc.has_value()) return CompositionText{}; + auto himc = *std::move(optional_himc); + + return GetCompositionInfo(himc.Get()); +} + +void WinInputMethodContext::SetCandidateWindowPosition(const Point& point) { + auto optional_himc = TryGetHIMC(); + if (!optional_himc.has_value()) return; + auto himc = *std::move(optional_himc); + + ::CANDIDATEFORM form; + form.dwIndex = 1; + form.dwStyle = CFS_CANDIDATEPOS; + form.ptCurrentPos = DipToPi(point); + + if (!::ImmSetCandidateWindow(himc.Get(), &form)) + log::Debug( + "WinInputMethodContext: Failed to set input method candidate window " + "position."); +} + +IEvent* WinInputMethodContext::CompositionStartEvent() { + return &composition_start_event_; +} + +IEvent* WinInputMethodContext::CompositionEndEvent() { + return &composition_end_event_; +}; + +IEvent* WinInputMethodContext::CompositionEvent() { + return &composition_event_; +} + +IEvent* WinInputMethodContext::TextEvent() { + return &text_event_; +} + +void WinInputMethodContext::OnWindowNativeMessage( + WindowNativeMessageEventArgs& args) { + const auto& message = args.GetWindowMessage(); + switch (message.msg) { + case WM_CHAR: { + const auto c = static_cast(message.w_param); + if (platform::win::IsSurrogatePair(c)) { + // I don't think this will happen because normal key strike without ime + // should only trigger ascci character. If it is a charater from + // supplementary planes, it should be handled with ime messages. + log::Warn( + "WinInputMethodContext: A WM_CHAR message for character from " + "supplementary planes is ignored."); + } else { + wchar_t s[1] = {c}; + auto str = platform::win::ToUtf8String({s, 1}); + text_event_.Raise(str); + } + args.HandleWithResult(0); + break; + } + case WM_IME_COMPOSITION: { + composition_event_.Raise(nullptr); + auto composition_text = GetCompositionText(); + log::Debug( + "WinInputMethodContext: WM_IME_COMPOSITION composition text:\n{}", + composition_text); + if (message.l_param & GCS_RESULTSTR) { + auto result_string = GetResultString(); + text_event_.Raise(result_string); + } + break; + } + case WM_IME_STARTCOMPOSITION: { + composition_start_event_.Raise(nullptr); + break; + } + case WM_IME_ENDCOMPOSITION: { + composition_end_event_.Raise(nullptr); + break; + } + } +} + +std::string WinInputMethodContext::GetResultString() { + auto optional_himc = TryGetHIMC(); + if (!optional_himc.has_value()) return ""; + auto himc = *std::move(optional_himc); + + auto w_result = win::GetResultString(himc.Get()); + return platform::win::ToUtf8String(w_result); +} + +std::optional WinInputMethodContext::TryGetHIMC() { + const auto native_window = Resolve(native_window_resolver_.get()); + if (native_window == nullptr) return std::nullopt; + const auto hwnd = native_window->GetWindowHandle(); + return AutoHIMC{hwnd}; +} + +WinInputMethodManager::WinInputMethodManager(WinUiApplication*) {} + +WinInputMethodManager::~WinInputMethodManager() {} + +std::unique_ptr WinInputMethodManager::GetContext( + INativeWindow* window) { + Expects(window); + const auto w = CheckPlatform(window, GetPlatformId()); + return std::make_unique(w); +} +} // namespace cru::platform::native::win diff --git a/src/win/native/UiApplication.cpp b/src/win/native/UiApplication.cpp new file mode 100644 index 00000000..dbf52e05 --- /dev/null +++ b/src/win/native/UiApplication.cpp @@ -0,0 +1,124 @@ +#include "cru/win/native/UiApplication.hpp" + +#include "../DebugLogger.hpp" +#include "cru/common/Logger.hpp" +#include "cru/platform/Check.hpp" +#include "cru/win/graph/direct/Factory.hpp" +#include "cru/win/native/Cursor.hpp" +#include "cru/win/native/Exception.hpp" +#include "cru/win/native/GodWindow.hpp" +#include "cru/win/native/InputMethod.hpp" +#include "cru/win/native/Window.hpp" +#include "GodWindowMessage.hpp" +#include "Timer.hpp" +#include "WindowManager.hpp" + +namespace cru::platform::native { +std::unique_ptr CreateUiApplication() { + return std::make_unique(); +} +} // namespace cru::platform::native + +namespace cru::platform::native::win { +WinUiApplication* WinUiApplication::instance = nullptr; + +WinUiApplication::WinUiApplication() { + instance = this; + + instance_handle_ = ::GetModuleHandleW(nullptr); + if (!instance_handle_) + throw Win32Error("Failed to get module(instance) handle."); + + log::Logger::GetInstance()->AddSource( + std::make_unique<::cru::platform::win::WinDebugLoggerSource>()); + + graph_factory_ = + std::make_unique(); + + god_window_ = std::make_unique(this); + timer_manager_ = std::make_unique(god_window_.get()); + window_manager_ = std::make_unique(this); + cursor_manager_ = std::make_unique(); + input_method_manager_ = std::make_unique(this); +} + +WinUiApplication::~WinUiApplication() { instance = nullptr; } + +int WinUiApplication::Run() { + MSG msg; + while (GetMessageW(&msg, nullptr, 0, 0)) { + TranslateMessage(&msg); + DispatchMessageW(&msg); + } + + for (const auto& handler : quit_handlers_) handler(); + + return static_cast(msg.wParam); +} + +void WinUiApplication::RequestQuit(const int quit_code) { + ::PostQuitMessage(quit_code); +} + +void WinUiApplication::AddOnQuitHandler(std::function handler) { + quit_handlers_.push_back(std::move(handler)); +} + +void WinUiApplication::InvokeLater(std::function action) { + // copy the action to a safe place + auto p_action_copy = new std::function(std::move(action)); + + if (::PostMessageW(GetGodWindow()->GetHandle(), invoke_later_message_id, + reinterpret_cast(p_action_copy), 0) == 0) + throw Win32Error(::GetLastError(), "InvokeLater failed to post message."); +} + +long long WinUiApplication::SetTimeout(std::chrono::milliseconds milliseconds, + std::function action) { + return gsl::narrow(timer_manager_->CreateTimer( + static_cast(milliseconds.count()), false, std::move(action))); +} + +long long WinUiApplication::SetInterval(std::chrono::milliseconds milliseconds, + std::function action) { + return gsl::narrow(timer_manager_->CreateTimer( + static_cast(milliseconds.count()), true, std::move(action))); +} + +void WinUiApplication::CancelTimer(long long id) { + if (id < 0) return; + timer_manager_->KillTimer(static_cast(id)); +} + +std::vector WinUiApplication::GetAllWindow() { + const auto&& windows = window_manager_->GetAllWindows(); + std::vector result; + for (const auto w : windows) { + result.push_back(static_cast(w)); + } + return result; +} + +std::shared_ptr WinUiApplication::CreateWindow( + INativeWindow* parent) { + WinNativeWindow* p = nullptr; + if (parent != nullptr) { + p = CheckPlatform(parent, GetPlatformId()); + } + return (new WinNativeWindow(this, window_manager_->GetGeneralWindowClass(), + WS_OVERLAPPEDWINDOW, p)) + ->GetResolver(); +} + +cru::platform::graph::IGraphFactory* WinUiApplication::GetGraphFactory() { + return graph_factory_.get(); +} + +ICursorManager* WinUiApplication::GetCursorManager() { + return cursor_manager_.get(); +} + +IInputMethodManager* WinUiApplication::GetInputMethodManager() { + return input_method_manager_.get(); +} +} // namespace cru::platform::native::win diff --git a/src/win/native/WindowClass.cpp b/src/win/native/WindowClass.cpp new file mode 100644 index 00000000..2e74606e --- /dev/null +++ b/src/win/native/WindowClass.cpp @@ -0,0 +1,28 @@ +#include "cru/win/native/WindowClass.hpp" + +#include "cru/win/native/Exception.hpp" + +namespace cru::platform::native::win { +WindowClass::WindowClass(std::wstring name, WNDPROC window_proc, + HINSTANCE h_instance) + : name_(std::move(name)) { + WNDCLASSEXW window_class; + window_class.cbSize = sizeof(WNDCLASSEXW); + + window_class.style = CS_HREDRAW | CS_VREDRAW; + window_class.lpfnWndProc = window_proc; + window_class.cbClsExtra = 0; + window_class.cbWndExtra = 0; + window_class.hInstance = h_instance; + window_class.hIcon = LoadIcon(NULL, IDI_APPLICATION); + window_class.hCursor = LoadCursor(NULL, IDC_ARROW); + window_class.hbrBackground = GetSysColorBrush(COLOR_BTNFACE); + window_class.lpszMenuName = NULL; + window_class.lpszClassName = name_.c_str(); + window_class.hIconSm = NULL; + + atom_ = ::RegisterClassExW(&window_class); + if (atom_ == 0) + throw Win32Error(::GetLastError(), "Failed to create window class."); +} +} // namespace cru::platform::native::win diff --git a/src/win/native/WindowD2DPainter.cpp b/src/win/native/WindowD2DPainter.cpp new file mode 100644 index 00000000..7a97480b --- /dev/null +++ b/src/win/native/WindowD2DPainter.cpp @@ -0,0 +1,22 @@ +#include "WindowD2DPainter.hpp" + +#include "cru/win/graph/direct/Exception.hpp" +#include "cru/win/graph/direct/Factory.hpp" +#include "cru/win/native/WindowRenderTarget.hpp" + +namespace cru::platform::native::win { +using namespace cru::platform::graph::win::direct; + +WindowD2DPainter::WindowD2DPainter(WindowRenderTarget* render_target) + : D2DPainter(render_target->GetD2D1DeviceContext()), + render_target_(render_target) { + render_target_->GetD2D1DeviceContext()->BeginDraw(); +} + +WindowD2DPainter::~WindowD2DPainter() { EndDraw(); } + +void WindowD2DPainter::DoEndDraw() { + ThrowIfFailed(render_target_->GetD2D1DeviceContext()->EndDraw()); + render_target_->Present(); +} +} // namespace cru::platform::native::win diff --git a/src/win/native/WindowD2DPainter.hpp b/src/win/native/WindowD2DPainter.hpp new file mode 100644 index 00000000..a638b77a --- /dev/null +++ b/src/win/native/WindowD2DPainter.hpp @@ -0,0 +1,21 @@ +#pragma once +#include "cru/win/graph/direct/Painter.hpp" +#include "cru/win/native/WindowRenderTarget.hpp" + +namespace cru::platform::native::win { +class WindowD2DPainter : public graph::win::direct::D2DPainter { + public: + explicit WindowD2DPainter(WindowRenderTarget* window); + + CRU_DELETE_COPY(WindowD2DPainter) + CRU_DELETE_MOVE(WindowD2DPainter) + + ~WindowD2DPainter() override; + + protected: + void DoEndDraw() override; + + private: + WindowRenderTarget* render_target_; +}; +} // namespace cru::platform::native::win diff --git a/src/win/native/WindowManager.cpp b/src/win/native/WindowManager.cpp new file mode 100644 index 00000000..56cc8981 --- /dev/null +++ b/src/win/native/WindowManager.cpp @@ -0,0 +1,56 @@ +#include "WindowManager.hpp" + +#include "cru/win/native/UiApplication.hpp" +#include "cru/win/native/Window.hpp" +#include "cru/win/native/WindowClass.hpp" + +namespace cru::platform::native::win { +LRESULT __stdcall GeneralWndProc(HWND hWnd, UINT Msg, WPARAM wParam, + LPARAM lParam) { + auto window = + WinUiApplication::GetInstance()->GetWindowManager()->FromHandle(hWnd); + + LRESULT result; + if (window != nullptr && + window->HandleNativeWindowMessage(hWnd, Msg, wParam, lParam, &result)) + return result; + + return DefWindowProc(hWnd, Msg, wParam, lParam); +} + +WindowManager::WindowManager(WinUiApplication* application) { + application_ = application; + general_window_class_ = std::make_unique( + L"CruUIWindowClass", GeneralWndProc, application->GetInstanceHandle()); +} + +WindowManager::~WindowManager() { + for (const auto& [key, window] : window_map_) delete window; +} + +void WindowManager::RegisterWindow(HWND hwnd, WinNativeWindow* window) { + Expects(window_map_.count(hwnd) == 0); // The hwnd is already in the map. + window_map_.emplace(hwnd, window); +} + +void WindowManager::UnregisterWindow(HWND hwnd) { + const auto find_result = window_map_.find(hwnd); + Expects(find_result != window_map_.end()); // The hwnd is not in the map. + window_map_.erase(find_result); + if (window_map_.empty()) application_->RequestQuit(0); +} + +WinNativeWindow* WindowManager::FromHandle(HWND hwnd) { + const auto find_result = window_map_.find(hwnd); + if (find_result == window_map_.end()) + return nullptr; + else + return find_result->second; +} + +std::vector WindowManager::GetAllWindows() const { + std::vector windows; + for (const auto& [key, value] : window_map_) windows.push_back(value); + return windows; +} +} // namespace cru::platform::native::win diff --git a/src/win/native/WindowManager.hpp b/src/win/native/WindowManager.hpp new file mode 100644 index 00000000..3f6387f7 --- /dev/null +++ b/src/win/native/WindowManager.hpp @@ -0,0 +1,51 @@ +#pragma once +#include "cru/win/WinPreConfig.hpp" + +#include "cru/common/Base.hpp" + +#include +#include +#include + +namespace cru::platform::native::win { +class WinUiApplication; +class WinNativeWindow; +class WindowClass; + +class WindowManager : public Object { + public: + WindowManager(WinUiApplication* application); + + CRU_DELETE_COPY(WindowManager) + CRU_DELETE_MOVE(WindowManager) + + ~WindowManager() override; + + // Get the general window class for creating ordinary window. + WindowClass* GetGeneralWindowClass() const { + return general_window_class_.get(); + } + + // Register a window newly created. + // This function adds the hwnd to hwnd-window map. + // It should be called immediately after a window was created. + void RegisterWindow(HWND hwnd, WinNativeWindow* window); + + // Unregister a window that is going to be destroyed. + // This function removes the hwnd from the hwnd-window map. + // It should be called immediately before a window is going to be destroyed, + void UnregisterWindow(HWND hwnd); + + // Return a pointer to the Window object related to the HWND or nullptr if the + // hwnd is not in the map. + WinNativeWindow* FromHandle(HWND hwnd); + + std::vector GetAllWindows() const; + + private: + WinUiApplication* application_; + + std::unique_ptr general_window_class_; + std::map window_map_; +}; +} // namespace cru::platform::native::win diff --git a/src/win/native/WindowRenderTarget.cpp b/src/win/native/WindowRenderTarget.cpp new file mode 100644 index 00000000..4a114ebf --- /dev/null +++ b/src/win/native/WindowRenderTarget.cpp @@ -0,0 +1,78 @@ +#include "cru/win/native/WindowRenderTarget.hpp" + +#include "cru/win/graph/direct/Exception.hpp" +#include "cru/win/graph/direct/Factory.hpp" +#include "DpiUtil.hpp" + +namespace cru::platform::native::win { +using namespace cru::platform::graph::win::direct; +WindowRenderTarget::WindowRenderTarget(DirectGraphFactory* factory, HWND hwnd) + : factory_(factory) { + Expects(factory); + + const auto d3d11_device = factory->GetD3D11Device(); + const auto dxgi_factory = factory->GetDxgiFactory(); + + d2d1_device_context_ = factory->CreateD2D1DeviceContext(); + + // Allocate a descriptor. + DXGI_SWAP_CHAIN_DESC1 swap_chain_desc; + swap_chain_desc.Width = 0; // use automatic sizing + swap_chain_desc.Height = 0; + swap_chain_desc.Format = + DXGI_FORMAT_B8G8R8A8_UNORM; // this is the most common swapchain format + swap_chain_desc.Stereo = false; + swap_chain_desc.SampleDesc.Count = 1; // don't use multi-sampling + swap_chain_desc.SampleDesc.Quality = 0; + swap_chain_desc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT; + swap_chain_desc.BufferCount = 2; // use double buffering to enable flip + swap_chain_desc.Scaling = DXGI_SCALING_NONE; + swap_chain_desc.SwapEffect = + DXGI_SWAP_EFFECT_FLIP_SEQUENTIAL; // all apps must use this SwapEffect + swap_chain_desc.AlphaMode = DXGI_ALPHA_MODE_UNSPECIFIED; + swap_chain_desc.Flags = 0; + + // Get the final swap chain for this window from the DXGI factory. + ThrowIfFailed(dxgi_factory->CreateSwapChainForHwnd( + d3d11_device, hwnd, &swap_chain_desc, nullptr, nullptr, + &dxgi_swap_chain_)); + + CreateTargetBitmap(); +} + +void WindowRenderTarget::ResizeBuffer(const int width, const int height) { + // In order to resize buffer, we need to untarget the buffer first. + d2d1_device_context_->SetTarget(nullptr); + target_bitmap_ = nullptr; + ThrowIfFailed(dxgi_swap_chain_->ResizeBuffers(0, width, height, + DXGI_FORMAT_UNKNOWN, 0)); + CreateTargetBitmap(); +} + +void WindowRenderTarget::Present() { + ThrowIfFailed(dxgi_swap_chain_->Present(1, 0)); +} + +void WindowRenderTarget::CreateTargetBitmap() { + Expects(target_bitmap_ == nullptr); // target bitmap must not exist. + + // Direct2D needs the dxgi version of the backbuffer surface pointer. + Microsoft::WRL::ComPtr dxgi_back_buffer; + ThrowIfFailed( + dxgi_swap_chain_->GetBuffer(0, IID_PPV_ARGS(&dxgi_back_buffer))); + + const auto dpi = GetDpi(); // TODO! DPI awareness. + + auto bitmap_properties = D2D1::BitmapProperties1( + D2D1_BITMAP_OPTIONS_TARGET | D2D1_BITMAP_OPTIONS_CANNOT_DRAW, + D2D1::PixelFormat(DXGI_FORMAT_B8G8R8A8_UNORM, D2D1_ALPHA_MODE_IGNORE), + dpi.x, dpi.y); + + // Get a D2D surface from the DXGI back buffer to use as the D2D render + // target. + ThrowIfFailed(d2d1_device_context_->CreateBitmapFromDxgiSurface( + dxgi_back_buffer.Get(), &bitmap_properties, &target_bitmap_)); + + d2d1_device_context_->SetTarget(target_bitmap_.Get()); +} +} // namespace cru::platform::native::win diff --git a/src/win/native/cursor.cpp b/src/win/native/cursor.cpp index 096f3fdf..ca8bb1cd 100644 --- a/src/win/native/cursor.cpp +++ b/src/win/native/cursor.cpp @@ -1,7 +1,7 @@ -#include "cru/win/native/cursor.hpp" +#include "cru/win/native/Cursor.hpp" -#include "cru/common/logger.hpp" -#include "cru/win/native/exception.hpp" +#include "cru/common/Logger.hpp" +#include "cru/win/native/Exception.hpp" #include diff --git a/src/win/native/dpi_util.hpp b/src/win/native/dpi_util.hpp deleted file mode 100644 index 07b89a95..00000000 --- a/src/win/native/dpi_util.hpp +++ /dev/null @@ -1,46 +0,0 @@ -#pragma once -#include "cru/platform/native/base.hpp" - -// The dpi awareness needs to be implemented in the future. Currently we use 96 -// as default. - -namespace cru::platform::native::win { -inline platform::native::Dpi GetDpi() { - return platform::native::Dpi{96.0f, 96.0f}; -} - -inline int DipToPixelInternal(const float dip, const float dpi) { - return static_cast(dip * dpi / 96.0f); -} - -inline int DipToPixelX(const float dip_x) { - return DipToPixelInternal(dip_x, GetDpi().x); -} - -inline int DipToPixelY(const float dip_y) { - return DipToPixelInternal(dip_y, GetDpi().y); -} - -inline float DipToPixelInternal(const int pixel, const float dpi) { - return static_cast(pixel) * 96.0f / dpi; -} - -inline float PixelToDipX(const int pixel_x) { - return DipToPixelInternal(pixel_x, GetDpi().x); -} - -inline float PixelToDipY(const int pixel_y) { - return DipToPixelInternal(pixel_y, GetDpi().y); -} - -inline Point PiToDip(const POINT& pi_point) { - return Point(PixelToDipX(pi_point.x), PixelToDipY(pi_point.y)); -} - -inline POINT DipToPi(const Point& dip_point) { - POINT result; - result.x = DipToPixelX(dip_point.x); - result.y = DipToPixelY(dip_point.y); - return result; -} -} // namespace cru::platform::native::win diff --git a/src/win/native/god_window.cpp b/src/win/native/god_window.cpp deleted file mode 100644 index 00577002..00000000 --- a/src/win/native/god_window.cpp +++ /dev/null @@ -1,82 +0,0 @@ -#include "cru/win/native/god_window.hpp" - -#include "cru/common/logger.hpp" -#include "cru/win/native/exception.hpp" -#include "cru/win/native/ui_application.hpp" -#include "cru/win/native/window_class.hpp" -#include "god_window_message.hpp" -#include "timer.hpp" - -namespace cru::platform::native::win { -constexpr auto god_window_class_name = L"GodWindowClass"; - -LRESULT CALLBACK GodWndProc(HWND hWnd, UINT uMsg, WPARAM wParam, - LPARAM lParam) { - const auto app = WinUiApplication::GetInstance(); - - if (app) { - LRESULT result; - const auto handled = app->GetGodWindow()->HandleGodWindowMessage( - hWnd, uMsg, wParam, lParam, &result); - if (handled) - return result; - else - return DefWindowProcW(hWnd, uMsg, wParam, lParam); - } else - return DefWindowProcW(hWnd, uMsg, wParam, lParam); -} - -GodWindow::GodWindow(WinUiApplication* application) { - application_ = application; - - const auto h_instance = application->GetInstanceHandle(); - - god_window_class_ = std::make_unique(god_window_class_name, - GodWndProc, h_instance); - - hwnd_ = CreateWindowEx(0, god_window_class_name, L"", 0, CW_USEDEFAULT, - CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, - HWND_MESSAGE, nullptr, h_instance, nullptr); - - if (hwnd_ == nullptr) - throw Win32Error(::GetLastError(), "Failed to create god window."); -} - -GodWindow::~GodWindow() { - if (!::DestroyWindow(hwnd_)) { - // Although this could be "safely" ignore. - log::Warn("Failed to destroy god window."); - } -} - -bool GodWindow::HandleGodWindowMessage(HWND hwnd, UINT msg, WPARAM w_param, - LPARAM l_param, LRESULT* result) { - CRU_UNUSED(hwnd) - CRU_UNUSED(l_param) - - switch (msg) { - case invoke_later_message_id: { - const auto p_action = reinterpret_cast*>(w_param); - (*p_action)(); - delete p_action; - *result = 0; - return true; - } - case WM_TIMER: { - const auto id = static_cast(w_param); - const auto action = application_->GetTimerManager()->GetAction(id); - if (action.has_value()) { - (action.value().second)(); - if (!action.value().first) - application_->GetTimerManager()->KillTimer(id); - result = 0; - return true; - } - break; - } - default: - return false; - } - return false; -} -} // namespace cru::platform::native::win diff --git a/src/win/native/god_window_message.hpp b/src/win/native/god_window_message.hpp deleted file mode 100644 index 591270d9..00000000 --- a/src/win/native/god_window_message.hpp +++ /dev/null @@ -1,6 +0,0 @@ -#pragma once -#include "cru/win/win_pre_config.hpp" - -namespace cru::platform::native::win { -constexpr int invoke_later_message_id = WM_USER + 2000; -} diff --git a/src/win/native/input_method.cpp b/src/win/native/input_method.cpp deleted file mode 100644 index dba2b1eb..00000000 --- a/src/win/native/input_method.cpp +++ /dev/null @@ -1,324 +0,0 @@ -#include "cru/win/native/input_method.hpp" - -#include "cru/common/logger.hpp" -#include "cru/platform/check.hpp" -#include "cru/win/exception.hpp" -#include "cru/win/native/window.hpp" -#include "cru/win/string.hpp" -#include "dpi_util.hpp" - -#include - -namespace cru::platform::native::win { -AutoHIMC::AutoHIMC(HWND hwnd) : hwnd_(hwnd) { - Expects(hwnd); - handle_ = ::ImmGetContext(hwnd); -} - -AutoHIMC::AutoHIMC(AutoHIMC&& other) - : hwnd_(other.hwnd_), handle_(other.handle_) { - other.hwnd_ = nullptr; - other.handle_ = nullptr; -} - -AutoHIMC& AutoHIMC::operator=(AutoHIMC&& other) { - if (this != &other) { - Object::operator=(std::move(other)); - this->hwnd_ = other.hwnd_; - this->handle_ = other.handle_; - other.hwnd_ = nullptr; - other.handle_ = nullptr; - } - return *this; -} - -AutoHIMC::~AutoHIMC() { - if (handle_) { - if (!::ImmReleaseContext(hwnd_, handle_)) - log::Warn("AutoHIMC: Failed to release HIMC."); - } -} - -// copied from chromium -namespace { -// Determines whether or not the given attribute represents a target -// (a.k.a. a selection). -bool IsTargetAttribute(char attribute) { - return (attribute == ATTR_TARGET_CONVERTED || - attribute == ATTR_TARGET_NOTCONVERTED); -} -// Helper function for ImeInput::GetCompositionInfo() method, to get the target -// range that's selected by the user in the current composition string. -void GetCompositionTargetRange(HIMC imm_context, int* target_start, - int* target_end) { - int attribute_size = - ::ImmGetCompositionString(imm_context, GCS_COMPATTR, NULL, 0); - if (attribute_size > 0) { - int start = 0; - int end = 0; - std::vector attribute_data(attribute_size); - ::ImmGetCompositionString(imm_context, GCS_COMPATTR, attribute_data.data(), - attribute_size); - for (start = 0; start < attribute_size; ++start) { - if (IsTargetAttribute(attribute_data[start])) break; - } - for (end = start; end < attribute_size; ++end) { - if (!IsTargetAttribute(attribute_data[end])) break; - } - if (start == attribute_size) { - // This composition clause does not contain any target clauses, - // i.e. this clauses is an input clause. - // We treat the whole composition as a target clause. - start = 0; - end = attribute_size; - } - *target_start = start; - *target_end = end; - } -} -// Helper function for ImeInput::GetCompositionInfo() method, to get underlines -// information of the current composition string. -CompositionClauses GetCompositionClauses(HIMC imm_context, int target_start, - int target_end) { - CompositionClauses result; - int clause_size = - ::ImmGetCompositionString(imm_context, GCS_COMPCLAUSE, NULL, 0); - int clause_length = clause_size / sizeof(std::uint32_t); - if (clause_length) { - result.reserve(clause_length - 1); - std::vector clause_data(clause_length); - ::ImmGetCompositionString(imm_context, GCS_COMPCLAUSE, clause_data.data(), - clause_size); - for (int i = 0; i < clause_length - 1; ++i) { - CompositionClause clause; - clause.start = clause_data[i]; - clause.end = clause_data[i + 1]; - clause.target = false; - // Use thick underline for the target clause. - if (clause.start >= target_start && clause.end <= target_end) { - clause.target = true; - } - result.push_back(clause); - } - } - return result; -} - -std::wstring GetString(HIMC imm_context) { - LONG string_size = - ::ImmGetCompositionString(imm_context, GCS_COMPSTR, NULL, 0); - std::wstring result((string_size / sizeof(wchar_t)), 0); - ::ImmGetCompositionString(imm_context, GCS_COMPSTR, result.data(), - string_size); - return result; -} - -std::wstring GetResultString(HIMC imm_context) { - LONG string_size = - ::ImmGetCompositionString(imm_context, GCS_RESULTSTR, NULL, 0); - std::wstring result((string_size / sizeof(wchar_t)), 0); - ::ImmGetCompositionString(imm_context, GCS_RESULTSTR, result.data(), - string_size); - return result; -} - -CompositionText GetCompositionInfo(HIMC imm_context) { - // We only care about GCS_COMPATTR, GCS_COMPCLAUSE and GCS_CURSORPOS, and - // convert them into underlines and selection range respectively. - - auto w_text = GetString(imm_context); - - int w_length = static_cast(w_text.length()); - // Find out the range selected by the user. - int w_target_start = w_length; - int w_target_end = w_length; - GetCompositionTargetRange(imm_context, &w_target_start, &w_target_end); - - auto clauses = - GetCompositionClauses(imm_context, w_target_start, w_target_end); - - int w_cursor = ::ImmGetCompositionString(imm_context, GCS_CURSORPOS, NULL, 0); - - auto text = platform::win::ToUtf8String(w_text); - for (auto& clause : clauses) { - clause.start = platform::win::IndexUtf16ToUtf8(w_text, clause.start, text); - clause.end = platform::win::IndexUtf16ToUtf8(w_text, clause.end, text); - } - int cursor = platform::win::IndexUtf16ToUtf8(w_text, w_cursor, text); - - return CompositionText{std::move(text), std::move(clauses), - TextRange{cursor}}; -} - -} // namespace - -WinInputMethodContext::WinInputMethodContext( - gsl::not_null window) - : native_window_resolver_(window->GetResolver()) { - event_revoker_guards_.push_back( - EventRevokerGuard(window->NativeMessageEvent()->AddHandler( - std::bind(&WinInputMethodContext::OnWindowNativeMessage, this, - std::placeholders::_1)))); -} - -WinInputMethodContext::~WinInputMethodContext() {} - -void WinInputMethodContext::EnableIME() { - const auto native_window = Resolve(native_window_resolver_.get()); - if (native_window == nullptr) return; - const auto hwnd = native_window->GetWindowHandle(); - - if (::ImmAssociateContextEx(hwnd, nullptr, IACE_DEFAULT) == FALSE) { - log::Warn("WinInputMethodContext: Failed to enable ime."); - } -} - -void WinInputMethodContext::DisableIME() { - const auto native_window = Resolve(native_window_resolver_.get()); - if (native_window == nullptr) return; - const auto hwnd = native_window->GetWindowHandle(); - - AutoHIMC himc{hwnd}; - - if (!::ImmNotifyIME(himc.Get(), NI_COMPOSITIONSTR, CPS_COMPLETE, 0)) { - log::Warn( - "WinInputMethodContext: Failed to complete composition before disable " - "ime."); - } - - if (::ImmAssociateContextEx(hwnd, nullptr, 0) == FALSE) { - log::Warn("WinInputMethodContext: Failed to disable ime."); - } -} - -void WinInputMethodContext::CompleteComposition() { - auto optional_himc = TryGetHIMC(); - if (!optional_himc.has_value()) return; - auto himc = *std::move(optional_himc); - - if (!::ImmNotifyIME(himc.Get(), NI_COMPOSITIONSTR, CPS_COMPLETE, 0)) { - log::Warn("WinInputMethodContext: Failed to complete composition."); - } -} - -void WinInputMethodContext::CancelComposition() { - auto optional_himc = TryGetHIMC(); - if (!optional_himc.has_value()) return; - auto himc = *std::move(optional_himc); - - if (!::ImmNotifyIME(himc.Get(), NI_COMPOSITIONSTR, CPS_CANCEL, 0)) { - log::Warn("WinInputMethodContext: Failed to complete composition."); - } -} - -CompositionText WinInputMethodContext::GetCompositionText() { - auto optional_himc = TryGetHIMC(); - if (!optional_himc.has_value()) return CompositionText{}; - auto himc = *std::move(optional_himc); - - return GetCompositionInfo(himc.Get()); -} - -void WinInputMethodContext::SetCandidateWindowPosition(const Point& point) { - auto optional_himc = TryGetHIMC(); - if (!optional_himc.has_value()) return; - auto himc = *std::move(optional_himc); - - ::CANDIDATEFORM form; - form.dwIndex = 1; - form.dwStyle = CFS_CANDIDATEPOS; - form.ptCurrentPos = DipToPi(point); - - if (!::ImmSetCandidateWindow(himc.Get(), &form)) - log::Debug( - "WinInputMethodContext: Failed to set input method candidate window " - "position."); -} - -IEvent* WinInputMethodContext::CompositionStartEvent() { - return &composition_start_event_; -} - -IEvent* WinInputMethodContext::CompositionEndEvent() { - return &composition_end_event_; -}; - -IEvent* WinInputMethodContext::CompositionEvent() { - return &composition_event_; -} - -IEvent* WinInputMethodContext::TextEvent() { - return &text_event_; -} - -void WinInputMethodContext::OnWindowNativeMessage( - WindowNativeMessageEventArgs& args) { - const auto& message = args.GetWindowMessage(); - switch (message.msg) { - case WM_CHAR: { - const auto c = static_cast(message.w_param); - if (platform::win::IsSurrogatePair(c)) { - // I don't think this will happen because normal key strike without ime - // should only trigger ascci character. If it is a charater from - // supplementary planes, it should be handled with ime messages. - log::Warn( - "WinInputMethodContext: A WM_CHAR message for character from " - "supplementary planes is ignored."); - } else { - wchar_t s[1] = {c}; - auto str = platform::win::ToUtf8String({s, 1}); - text_event_.Raise(str); - } - args.HandleWithResult(0); - break; - } - case WM_IME_COMPOSITION: { - composition_event_.Raise(nullptr); - auto composition_text = GetCompositionText(); - log::Debug( - "WinInputMethodContext: WM_IME_COMPOSITION composition text:\n{}", - composition_text); - if (message.l_param & GCS_RESULTSTR) { - auto result_string = GetResultString(); - text_event_.Raise(result_string); - } - break; - } - case WM_IME_STARTCOMPOSITION: { - composition_start_event_.Raise(nullptr); - break; - } - case WM_IME_ENDCOMPOSITION: { - composition_end_event_.Raise(nullptr); - break; - } - } -} - -std::string WinInputMethodContext::GetResultString() { - auto optional_himc = TryGetHIMC(); - if (!optional_himc.has_value()) return ""; - auto himc = *std::move(optional_himc); - - auto w_result = win::GetResultString(himc.Get()); - return platform::win::ToUtf8String(w_result); -} - -std::optional WinInputMethodContext::TryGetHIMC() { - const auto native_window = Resolve(native_window_resolver_.get()); - if (native_window == nullptr) return std::nullopt; - const auto hwnd = native_window->GetWindowHandle(); - return AutoHIMC{hwnd}; -} - -WinInputMethodManager::WinInputMethodManager(WinUiApplication*) {} - -WinInputMethodManager::~WinInputMethodManager() {} - -std::unique_ptr WinInputMethodManager::GetContext( - INativeWindow* window) { - Expects(window); - const auto w = CheckPlatform(window, GetPlatformId()); - return std::make_unique(w); -} -} // namespace cru::platform::native::win diff --git a/src/win/native/keyboard.cpp b/src/win/native/keyboard.cpp index 98a95778..aa22e4a4 100644 --- a/src/win/native/keyboard.cpp +++ b/src/win/native/keyboard.cpp @@ -1,4 +1,4 @@ -#include "cru/win/native/keyboard.hpp" +#include "cru/win/native/Keyboard.hpp" namespace cru::platform::native::win { KeyCode VirtualKeyToKeyCode(int virtual_key) { diff --git a/src/win/native/timer.cpp b/src/win/native/timer.cpp index 66743963..662067fb 100644 --- a/src/win/native/timer.cpp +++ b/src/win/native/timer.cpp @@ -1,4 +1,4 @@ -#include "timer.hpp" +#include "Timer.hpp" namespace cru::platform::native::win { TimerManager::TimerManager(GodWindow* god_window) { god_window_ = god_window; } diff --git a/src/win/native/timer.hpp b/src/win/native/timer.hpp index 6c4871dd..95f186a1 100644 --- a/src/win/native/timer.hpp +++ b/src/win/native/timer.hpp @@ -1,8 +1,8 @@ #pragma once -#include "cru/win/win_pre_config.hpp" +#include "cru/win/WinPreConfig.hpp" -#include "cru/common/base.hpp" -#include "cru/win/native/god_window.hpp" +#include "cru/common/Base.hpp" +#include "cru/win/native/GodWindow.hpp" #include #include diff --git a/src/win/native/ui_application.cpp b/src/win/native/ui_application.cpp deleted file mode 100644 index 599ecadc..00000000 --- a/src/win/native/ui_application.cpp +++ /dev/null @@ -1,124 +0,0 @@ -#include "cru/win/native/ui_application.hpp" - -#include "../debug_logger.hpp" -#include "cru/common/logger.hpp" -#include "cru/platform/check.hpp" -#include "cru/win/graph/direct/factory.hpp" -#include "cru/win/native/cursor.hpp" -#include "cru/win/native/exception.hpp" -#include "cru/win/native/god_window.hpp" -#include "cru/win/native/input_method.hpp" -#include "cru/win/native/window.hpp" -#include "god_window_message.hpp" -#include "timer.hpp" -#include "window_manager.hpp" - -namespace cru::platform::native { -std::unique_ptr CreateUiApplication() { - return std::make_unique(); -} -} // namespace cru::platform::native - -namespace cru::platform::native::win { -WinUiApplication* WinUiApplication::instance = nullptr; - -WinUiApplication::WinUiApplication() { - instance = this; - - instance_handle_ = ::GetModuleHandleW(nullptr); - if (!instance_handle_) - throw Win32Error("Failed to get module(instance) handle."); - - log::Logger::GetInstance()->AddSource( - std::make_unique<::cru::platform::win::WinDebugLoggerSource>()); - - graph_factory_ = - std::make_unique(); - - god_window_ = std::make_unique(this); - timer_manager_ = std::make_unique(god_window_.get()); - window_manager_ = std::make_unique(this); - cursor_manager_ = std::make_unique(); - input_method_manager_ = std::make_unique(this); -} - -WinUiApplication::~WinUiApplication() { instance = nullptr; } - -int WinUiApplication::Run() { - MSG msg; - while (GetMessageW(&msg, nullptr, 0, 0)) { - TranslateMessage(&msg); - DispatchMessageW(&msg); - } - - for (const auto& handler : quit_handlers_) handler(); - - return static_cast(msg.wParam); -} - -void WinUiApplication::RequestQuit(const int quit_code) { - ::PostQuitMessage(quit_code); -} - -void WinUiApplication::AddOnQuitHandler(std::function handler) { - quit_handlers_.push_back(std::move(handler)); -} - -void WinUiApplication::InvokeLater(std::function action) { - // copy the action to a safe place - auto p_action_copy = new std::function(std::move(action)); - - if (::PostMessageW(GetGodWindow()->GetHandle(), invoke_later_message_id, - reinterpret_cast(p_action_copy), 0) == 0) - throw Win32Error(::GetLastError(), "InvokeLater failed to post message."); -} - -long long WinUiApplication::SetTimeout(std::chrono::milliseconds milliseconds, - std::function action) { - return gsl::narrow(timer_manager_->CreateTimer( - static_cast(milliseconds.count()), false, std::move(action))); -} - -long long WinUiApplication::SetInterval(std::chrono::milliseconds milliseconds, - std::function action) { - return gsl::narrow(timer_manager_->CreateTimer( - static_cast(milliseconds.count()), true, std::move(action))); -} - -void WinUiApplication::CancelTimer(long long id) { - if (id < 0) return; - timer_manager_->KillTimer(static_cast(id)); -} - -std::vector WinUiApplication::GetAllWindow() { - const auto&& windows = window_manager_->GetAllWindows(); - std::vector result; - for (const auto w : windows) { - result.push_back(static_cast(w)); - } - return result; -} - -std::shared_ptr WinUiApplication::CreateWindow( - INativeWindow* parent) { - WinNativeWindow* p = nullptr; - if (parent != nullptr) { - p = CheckPlatform(parent, GetPlatformId()); - } - return (new WinNativeWindow(this, window_manager_->GetGeneralWindowClass(), - WS_OVERLAPPEDWINDOW, p)) - ->GetResolver(); -} - -cru::platform::graph::IGraphFactory* WinUiApplication::GetGraphFactory() { - return graph_factory_.get(); -} - -ICursorManager* WinUiApplication::GetCursorManager() { - return cursor_manager_.get(); -} - -IInputMethodManager* WinUiApplication::GetInputMethodManager() { - return input_method_manager_.get(); -} -} // namespace cru::platform::native::win diff --git a/src/win/native/window.cpp b/src/win/native/window.cpp index bed9a264..9dde1af3 100644 --- a/src/win/native/window.cpp +++ b/src/win/native/window.cpp @@ -1,17 +1,17 @@ -#include "cru/win/native/window.hpp" - -#include "cru/common/logger.hpp" -#include "cru/platform/check.hpp" -#include "cru/win/native/cursor.hpp" -#include "cru/win/native/exception.hpp" -#include "cru/win/native/keyboard.hpp" -#include "cru/win/native/ui_application.hpp" -#include "cru/win/native/window_class.hpp" -#include "cru/win/native/window_render_target.hpp" -#include "cru/win/string.hpp" -#include "dpi_util.hpp" -#include "window_d2d_painter.hpp" -#include "window_manager.hpp" +#include "cru/win/native/Window.hpp" + +#include "cru/common/Logger.hpp" +#include "cru/platform/Check.hpp" +#include "cru/win/native/Cursor.hpp" +#include "cru/win/native/Exception.hpp" +#include "cru/win/native/Keyboard.hpp" +#include "cru/win/native/UiApplication.hpp" +#include "cru/win/native/WindowClass.hpp" +#include "cru/win/native/WindowRenderTarget.hpp" +#include "cru/win/String.hpp" +#include "DpiUtil.hpp" +#include "WindowD2DPainter.hpp" +#include "WindowManager.hpp" #include #include diff --git a/src/win/native/window_class.cpp b/src/win/native/window_class.cpp deleted file mode 100644 index 11dc86aa..00000000 --- a/src/win/native/window_class.cpp +++ /dev/null @@ -1,28 +0,0 @@ -#include "cru/win/native/window_class.hpp" - -#include "cru/win/native/exception.hpp" - -namespace cru::platform::native::win { -WindowClass::WindowClass(std::wstring name, WNDPROC window_proc, - HINSTANCE h_instance) - : name_(std::move(name)) { - WNDCLASSEXW window_class; - window_class.cbSize = sizeof(WNDCLASSEXW); - - window_class.style = CS_HREDRAW | CS_VREDRAW; - window_class.lpfnWndProc = window_proc; - window_class.cbClsExtra = 0; - window_class.cbWndExtra = 0; - window_class.hInstance = h_instance; - window_class.hIcon = LoadIcon(NULL, IDI_APPLICATION); - window_class.hCursor = LoadCursor(NULL, IDC_ARROW); - window_class.hbrBackground = GetSysColorBrush(COLOR_BTNFACE); - window_class.lpszMenuName = NULL; - window_class.lpszClassName = name_.c_str(); - window_class.hIconSm = NULL; - - atom_ = ::RegisterClassExW(&window_class); - if (atom_ == 0) - throw Win32Error(::GetLastError(), "Failed to create window class."); -} -} // namespace cru::platform::native::win diff --git a/src/win/native/window_d2d_painter.cpp b/src/win/native/window_d2d_painter.cpp deleted file mode 100644 index 54343fbf..00000000 --- a/src/win/native/window_d2d_painter.cpp +++ /dev/null @@ -1,22 +0,0 @@ -#include "window_d2d_painter.hpp" - -#include "cru/win/graph/direct/exception.hpp" -#include "cru/win/graph/direct/factory.hpp" -#include "cru/win/native/window_render_target.hpp" - -namespace cru::platform::native::win { -using namespace cru::platform::graph::win::direct; - -WindowD2DPainter::WindowD2DPainter(WindowRenderTarget* render_target) - : D2DPainter(render_target->GetD2D1DeviceContext()), - render_target_(render_target) { - render_target_->GetD2D1DeviceContext()->BeginDraw(); -} - -WindowD2DPainter::~WindowD2DPainter() { EndDraw(); } - -void WindowD2DPainter::DoEndDraw() { - ThrowIfFailed(render_target_->GetD2D1DeviceContext()->EndDraw()); - render_target_->Present(); -} -} // namespace cru::platform::native::win diff --git a/src/win/native/window_d2d_painter.hpp b/src/win/native/window_d2d_painter.hpp deleted file mode 100644 index 40a5dee6..00000000 --- a/src/win/native/window_d2d_painter.hpp +++ /dev/null @@ -1,21 +0,0 @@ -#pragma once -#include "cru/win/graph/direct/painter.hpp" -#include "cru/win/native/window_render_target.hpp" - -namespace cru::platform::native::win { -class WindowD2DPainter : public graph::win::direct::D2DPainter { - public: - explicit WindowD2DPainter(WindowRenderTarget* window); - - CRU_DELETE_COPY(WindowD2DPainter) - CRU_DELETE_MOVE(WindowD2DPainter) - - ~WindowD2DPainter() override; - - protected: - void DoEndDraw() override; - - private: - WindowRenderTarget* render_target_; -}; -} // namespace cru::platform::native::win diff --git a/src/win/native/window_manager.cpp b/src/win/native/window_manager.cpp deleted file mode 100644 index 205a139c..00000000 --- a/src/win/native/window_manager.cpp +++ /dev/null @@ -1,56 +0,0 @@ -#include "window_manager.hpp" - -#include "cru/win/native/ui_application.hpp" -#include "cru/win/native/window.hpp" -#include "cru/win/native/window_class.hpp" - -namespace cru::platform::native::win { -LRESULT __stdcall GeneralWndProc(HWND hWnd, UINT Msg, WPARAM wParam, - LPARAM lParam) { - auto window = - WinUiApplication::GetInstance()->GetWindowManager()->FromHandle(hWnd); - - LRESULT result; - if (window != nullptr && - window->HandleNativeWindowMessage(hWnd, Msg, wParam, lParam, &result)) - return result; - - return DefWindowProc(hWnd, Msg, wParam, lParam); -} - -WindowManager::WindowManager(WinUiApplication* application) { - application_ = application; - general_window_class_ = std::make_unique( - L"CruUIWindowClass", GeneralWndProc, application->GetInstanceHandle()); -} - -WindowManager::~WindowManager() { - for (const auto& [key, window] : window_map_) delete window; -} - -void WindowManager::RegisterWindow(HWND hwnd, WinNativeWindow* window) { - Expects(window_map_.count(hwnd) == 0); // The hwnd is already in the map. - window_map_.emplace(hwnd, window); -} - -void WindowManager::UnregisterWindow(HWND hwnd) { - const auto find_result = window_map_.find(hwnd); - Expects(find_result != window_map_.end()); // The hwnd is not in the map. - window_map_.erase(find_result); - if (window_map_.empty()) application_->RequestQuit(0); -} - -WinNativeWindow* WindowManager::FromHandle(HWND hwnd) { - const auto find_result = window_map_.find(hwnd); - if (find_result == window_map_.end()) - return nullptr; - else - return find_result->second; -} - -std::vector WindowManager::GetAllWindows() const { - std::vector windows; - for (const auto& [key, value] : window_map_) windows.push_back(value); - return windows; -} -} // namespace cru::platform::native::win diff --git a/src/win/native/window_manager.hpp b/src/win/native/window_manager.hpp deleted file mode 100644 index 677719aa..00000000 --- a/src/win/native/window_manager.hpp +++ /dev/null @@ -1,51 +0,0 @@ -#pragma once -#include "cru/win/win_pre_config.hpp" - -#include "cru/common/base.hpp" - -#include -#include -#include - -namespace cru::platform::native::win { -class WinUiApplication; -class WinNativeWindow; -class WindowClass; - -class WindowManager : public Object { - public: - WindowManager(WinUiApplication* application); - - CRU_DELETE_COPY(WindowManager) - CRU_DELETE_MOVE(WindowManager) - - ~WindowManager() override; - - // Get the general window class for creating ordinary window. - WindowClass* GetGeneralWindowClass() const { - return general_window_class_.get(); - } - - // Register a window newly created. - // This function adds the hwnd to hwnd-window map. - // It should be called immediately after a window was created. - void RegisterWindow(HWND hwnd, WinNativeWindow* window); - - // Unregister a window that is going to be destroyed. - // This function removes the hwnd from the hwnd-window map. - // It should be called immediately before a window is going to be destroyed, - void UnregisterWindow(HWND hwnd); - - // Return a pointer to the Window object related to the HWND or nullptr if the - // hwnd is not in the map. - WinNativeWindow* FromHandle(HWND hwnd); - - std::vector GetAllWindows() const; - - private: - WinUiApplication* application_; - - std::unique_ptr general_window_class_; - std::map window_map_; -}; -} // namespace cru::platform::native::win diff --git a/src/win/native/window_render_target.cpp b/src/win/native/window_render_target.cpp deleted file mode 100644 index eb6673c6..00000000 --- a/src/win/native/window_render_target.cpp +++ /dev/null @@ -1,78 +0,0 @@ -#include "cru/win/native/window_render_target.hpp" - -#include "cru/win/graph/direct/exception.hpp" -#include "cru/win/graph/direct/factory.hpp" -#include "dpi_util.hpp" - -namespace cru::platform::native::win { -using namespace cru::platform::graph::win::direct; -WindowRenderTarget::WindowRenderTarget(DirectGraphFactory* factory, HWND hwnd) - : factory_(factory) { - Expects(factory); - - const auto d3d11_device = factory->GetD3D11Device(); - const auto dxgi_factory = factory->GetDxgiFactory(); - - d2d1_device_context_ = factory->CreateD2D1DeviceContext(); - - // Allocate a descriptor. - DXGI_SWAP_CHAIN_DESC1 swap_chain_desc; - swap_chain_desc.Width = 0; // use automatic sizing - swap_chain_desc.Height = 0; - swap_chain_desc.Format = - DXGI_FORMAT_B8G8R8A8_UNORM; // this is the most common swapchain format - swap_chain_desc.Stereo = false; - swap_chain_desc.SampleDesc.Count = 1; // don't use multi-sampling - swap_chain_desc.SampleDesc.Quality = 0; - swap_chain_desc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT; - swap_chain_desc.BufferCount = 2; // use double buffering to enable flip - swap_chain_desc.Scaling = DXGI_SCALING_NONE; - swap_chain_desc.SwapEffect = - DXGI_SWAP_EFFECT_FLIP_SEQUENTIAL; // all apps must use this SwapEffect - swap_chain_desc.AlphaMode = DXGI_ALPHA_MODE_UNSPECIFIED; - swap_chain_desc.Flags = 0; - - // Get the final swap chain for this window from the DXGI factory. - ThrowIfFailed(dxgi_factory->CreateSwapChainForHwnd( - d3d11_device, hwnd, &swap_chain_desc, nullptr, nullptr, - &dxgi_swap_chain_)); - - CreateTargetBitmap(); -} - -void WindowRenderTarget::ResizeBuffer(const int width, const int height) { - // In order to resize buffer, we need to untarget the buffer first. - d2d1_device_context_->SetTarget(nullptr); - target_bitmap_ = nullptr; - ThrowIfFailed(dxgi_swap_chain_->ResizeBuffers(0, width, height, - DXGI_FORMAT_UNKNOWN, 0)); - CreateTargetBitmap(); -} - -void WindowRenderTarget::Present() { - ThrowIfFailed(dxgi_swap_chain_->Present(1, 0)); -} - -void WindowRenderTarget::CreateTargetBitmap() { - Expects(target_bitmap_ == nullptr); // target bitmap must not exist. - - // Direct2D needs the dxgi version of the backbuffer surface pointer. - Microsoft::WRL::ComPtr dxgi_back_buffer; - ThrowIfFailed( - dxgi_swap_chain_->GetBuffer(0, IID_PPV_ARGS(&dxgi_back_buffer))); - - const auto dpi = GetDpi(); // TODO! DPI awareness. - - auto bitmap_properties = D2D1::BitmapProperties1( - D2D1_BITMAP_OPTIONS_TARGET | D2D1_BITMAP_OPTIONS_CANNOT_DRAW, - D2D1::PixelFormat(DXGI_FORMAT_B8G8R8A8_UNORM, D2D1_ALPHA_MODE_IGNORE), - dpi.x, dpi.y); - - // Get a D2D surface from the DXGI back buffer to use as the D2D render - // target. - ThrowIfFailed(d2d1_device_context_->CreateBitmapFromDxgiSurface( - dxgi_back_buffer.Get(), &bitmap_properties, &target_bitmap_)); - - d2d1_device_context_->SetTarget(target_bitmap_.Get()); -} -} // namespace cru::platform::native::win diff --git a/src/win/string.cpp b/src/win/string.cpp index 5518e6af..65a280f2 100644 --- a/src/win/string.cpp +++ b/src/win/string.cpp @@ -1,6 +1,6 @@ -#include "cru/win/string.hpp" +#include "cru/win/String.hpp" -#include "cru/win/exception.hpp" +#include "cru/win/Exception.hpp" #include diff --git a/test/win/CMakeLists.txt b/test/win/CMakeLists.txt index 343b40fe..138af96b 100644 --- a/test/win/CMakeLists.txt +++ b/test/win/CMakeLists.txt @@ -1,5 +1,5 @@ add_executable(cru_test_win_base - string.cpp + String.cpp ) target_link_libraries(cru_test_win_base PRIVATE cru_win_base cru_test_base) diff --git a/test/win/string.cpp b/test/win/string.cpp index 3864b987..a666dc81 100644 --- a/test/win/string.cpp +++ b/test/win/string.cpp @@ -1,4 +1,4 @@ -#include "cru/win/string.hpp" +#include "cru/win/String.hpp" #include diff --git a/tools/migrate-1/migrate-cmake.py b/tools/migrate-1/migrate-cmake.py new file mode 100644 index 00000000..b90bd2e9 --- /dev/null +++ b/tools/migrate-1/migrate-cmake.py @@ -0,0 +1,26 @@ +import re +from pathlib import Path + +regex = re.compile(r'\s*(.*\/)*(.*)(\.[hc]pp)$') + +def xstr(s): + if s is None: + return '' + return str(s) + +for p in Path('./src').rglob('CMakeLists.txt'): + text = '' + with p.open(mode='r', encoding='utf-8') as f: + for line in f.readlines(): + m = regex.match(line) + if m: + t = m.group(2) + t = ''.join(([i.capitalize() for i in t.split('_')])) + t = '\t{}{}{}\n'.format(xstr(m.group(1)), t, m.group(3)) + text += t + else: + text += line + with p.open(mode='w', encoding='utf-8') as f: + f.write(text) + + diff --git a/tools/migrate-1/migrate-files.py b/tools/migrate-1/migrate-files.py new file mode 100644 index 00000000..8ea8bde6 --- /dev/null +++ b/tools/migrate-1/migrate-files.py @@ -0,0 +1,43 @@ +import re +import pathlib + +regex = re.compile(r'#include\s+"(.*\/)*(.*)"') + + +def xstr(s): + if s is None: + return '' + return str(s) + + +def migrate_includes(path: pathlib.Path): + text = '' + with path.open(mode='r', encoding='utf-8') as f: + for line in f.readlines(): + m = regex.match(line) + if m: + t = m.group(2) + t = ''.join(([i.capitalize() for i in t.split('_')])) + t = '#include "{}{}"\n'.format(xstr(m.group(1)), t) + text += t + else: + text += line + + with path.open(mode='w', encoding='utf-8') as f: + f.write(text) + + +for p in pathlib.Path('./src').rglob('*.cpp'): + migrate_includes(p) + p.rename(p.parent.joinpath( + ''.join([i.capitalize() for i in p.name.split('_')]))) + +for p in pathlib.Path('./src').rglob('*.hpp'): + migrate_includes(p) + p.rename(p.parent.joinpath( + ''.join([i.capitalize() for i in p.name.split('_')]))) + +for p in pathlib.Path('./include/cru').rglob('*.hpp'): + migrate_includes(p) + p.rename(p.parent.joinpath( + ''.join([i.capitalize() for i in p.name.split('_')]))) diff --git a/tools/migrate-files.py b/tools/migrate-files.py deleted file mode 100644 index e69de29b..00000000 -- cgit v1.2.3