aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorcrupest <crupest@outlook.com>2021-03-24 19:14:19 +0800
committercrupest <crupest@outlook.com>2021-03-24 19:14:19 +0800
commit7f15a1ff9a2007e119798053083a0a87d042990a (patch)
treecb35c01a7eaee867376d959b96c9bbd15df939e5
parent74956951ee663012df0c3fe4ebe29799cb2f7732 (diff)
parent7703063a5816b089483e78ccd74bb9902ccfbea8 (diff)
downloadcru-7f15a1ff9a2007e119798053083a0a87d042990a.tar.gz
cru-7f15a1ff9a2007e119798053083a0a87d042990a.tar.bz2
cru-7f15a1ff9a2007e119798053083a0a87d042990a.zip
Merge branch 'master' of https://github.com/crupest/CruUI
-rw-r--r--.github/workflows/ci.yml2
-rw-r--r--azure-pipelines.yml39
-rw-r--r--demos/CMakeLists.txt2
-rw-r--r--demos/input_method/main.cpp23
-rw-r--r--demos/main/main.cpp53
-rw-r--r--include/cru/common/Base.hpp13
-rw-r--r--include/cru/common/ClonablePtr.hpp204
-rw-r--r--include/cru/common/Event.hpp133
-rw-r--r--include/cru/common/Format.hpp23
-rw-r--r--include/cru/common/HandlerRegistry.hpp86
-rw-r--r--include/cru/common/Logger.hpp2
-rw-r--r--include/cru/common/SelfResolvable.hpp24
-rw-r--r--include/cru/common/StringUtil.hpp21
-rw-r--r--include/cru/platform/GraphBase.hpp27
-rw-r--r--include/cru/platform/Matrix.hpp5
-rw-r--r--include/cru/platform/graphics/Base.hpp (renamed from include/cru/platform/graph/Base.hpp)2
-rw-r--r--include/cru/platform/graphics/Brush.hpp (renamed from include/cru/platform/graph/Brush.hpp)2
-rw-r--r--include/cru/platform/graphics/Factory.hpp (renamed from include/cru/platform/graph/Factory.hpp)10
-rw-r--r--include/cru/platform/graphics/Font.hpp (renamed from include/cru/platform/graph/Font.hpp)2
-rw-r--r--include/cru/platform/graphics/Geometry.hpp (renamed from include/cru/platform/graph/Geometry.hpp)2
-rw-r--r--include/cru/platform/graphics/Painter.hpp (renamed from include/cru/platform/graph/Painter.hpp)6
-rw-r--r--include/cru/platform/graphics/Resource.hpp (renamed from include/cru/platform/graph/Resource.hpp)2
-rw-r--r--include/cru/platform/graphics/TextLayout.hpp (renamed from include/cru/platform/graph/TextLayout.hpp)6
-rw-r--r--include/cru/platform/graphics/util/Painter.hpp (renamed from include/cru/platform/graph/util/Painter.hpp)6
-rw-r--r--include/cru/platform/gui/Base.hpp (renamed from include/cru/platform/native/Base.hpp)22
-rw-r--r--include/cru/platform/gui/Cursor.hpp (renamed from include/cru/platform/native/Cursor.hpp)7
-rw-r--r--include/cru/platform/gui/DebugFlags.hpp8
-rw-r--r--include/cru/platform/gui/InputMethod.hpp (renamed from include/cru/platform/native/InputMethod.hpp)17
-rw-r--r--include/cru/platform/gui/Keyboard.hpp (renamed from include/cru/platform/native/Keyboard.hpp)12
-rw-r--r--include/cru/platform/gui/UiApplication.hpp135
-rw-r--r--include/cru/platform/gui/Window.hpp (renamed from include/cru/platform/native/Window.hpp)22
-rw-r--r--include/cru/platform/native/UiApplication.hpp58
-rw-r--r--include/cru/ui/Base.hpp38
-rw-r--r--include/cru/ui/ContentControl.hpp29
-rw-r--r--include/cru/ui/DebugFlags.hpp9
-rw-r--r--include/cru/ui/LayoutControl.hpp31
-rw-r--r--include/cru/ui/UiHost.hpp172
-rw-r--r--include/cru/ui/UiManager.hpp19
-rw-r--r--include/cru/ui/Window.hpp40
-rw-r--r--include/cru/ui/components/Component.hpp19
-rw-r--r--include/cru/ui/components/Menu.hpp60
-rw-r--r--include/cru/ui/controls/Base.hpp22
-rw-r--r--include/cru/ui/controls/Button.hpp29
-rw-r--r--include/cru/ui/controls/Container.hpp5
-rw-r--r--include/cru/ui/controls/ContentControl.hpp38
-rw-r--r--include/cru/ui/controls/Control.hpp (renamed from include/cru/ui/Control.hpp)55
-rw-r--r--include/cru/ui/controls/FlexLayout.hpp9
-rw-r--r--include/cru/ui/controls/IBorderControl.hpp10
-rw-r--r--include/cru/ui/controls/IClickableControl.hpp12
-rw-r--r--include/cru/ui/controls/LayoutControl.hpp35
-rw-r--r--include/cru/ui/controls/NoChildControl.hpp (renamed from include/cru/ui/NoChildControl.hpp)12
-rw-r--r--include/cru/ui/controls/Popup.hpp24
-rw-r--r--include/cru/ui/controls/RootControl.hpp45
-rw-r--r--include/cru/ui/controls/StackLayout.hpp6
-rw-r--r--include/cru/ui/controls/TextBlock.hpp23
-rw-r--r--include/cru/ui/controls/TextBox.hpp27
-rw-r--r--include/cru/ui/controls/TextHostControlService.hpp137
-rw-r--r--include/cru/ui/controls/Window.hpp32
-rw-r--r--include/cru/ui/events/UiEvent.hpp (renamed from include/cru/ui/UiEvent.hpp)33
-rw-r--r--include/cru/ui/helper/ClickDetector.hpp (renamed from include/cru/ui/ClickDetector.hpp)20
-rw-r--r--include/cru/ui/helper/ShortcutHub.hpp134
-rw-r--r--include/cru/ui/host/LayoutPaintCycler.hpp39
-rw-r--r--include/cru/ui/host/WindowHost.hpp176
-rw-r--r--include/cru/ui/render/Base.hpp1
-rw-r--r--include/cru/ui/render/BorderRenderObject.hpp30
-rw-r--r--include/cru/ui/render/CanvasRenderObject.hpp2
-rw-r--r--include/cru/ui/render/FlexLayoutRenderObject.hpp4
-rw-r--r--include/cru/ui/render/LayoutRenderObject.hpp2
-rw-r--r--include/cru/ui/render/MeasureRequirement.hpp28
-rw-r--r--include/cru/ui/render/RenderObject.hpp61
-rw-r--r--include/cru/ui/render/ScrollBar.hpp218
-rw-r--r--include/cru/ui/render/ScrollRenderObject.hpp27
-rw-r--r--include/cru/ui/render/TextRenderObject.hpp47
-rw-r--r--include/cru/ui/render/WindowRenderObject.hpp29
-rw-r--r--include/cru/ui/style/ApplyBorderStyleInfo.hpp28
-rw-r--r--include/cru/ui/style/Condition.hpp123
-rw-r--r--include/cru/ui/style/StyleRule.hpp45
-rw-r--r--include/cru/ui/style/StyleRuleSet.hpp87
-rw-r--r--include/cru/ui/style/Styler.hpp80
-rw-r--r--include/cru/win/graphics/direct/Brush.hpp (renamed from include/cru/win/graph/direct/Brush.hpp)6
-rw-r--r--include/cru/win/graphics/direct/ComResource.hpp (renamed from include/cru/win/graph/direct/ComResource.hpp)4
-rw-r--r--include/cru/win/graphics/direct/ConvertUtil.hpp (renamed from include/cru/win/graph/direct/ConvertUtil.hpp)6
-rw-r--r--include/cru/win/graphics/direct/Exception.hpp (renamed from include/cru/win/graph/direct/Exception.hpp)4
-rw-r--r--include/cru/win/graphics/direct/Factory.hpp (renamed from include/cru/win/graph/direct/Factory.hpp)6
-rw-r--r--include/cru/win/graphics/direct/Font.hpp (renamed from include/cru/win/graph/direct/Font.hpp)6
-rw-r--r--include/cru/win/graphics/direct/Geometry.hpp (renamed from include/cru/win/graph/direct/Geometry.hpp)6
-rw-r--r--include/cru/win/graphics/direct/Painter.hpp (renamed from include/cru/win/graph/direct/Painter.hpp)8
-rw-r--r--include/cru/win/graphics/direct/Resource.hpp (renamed from include/cru/win/graph/direct/Resource.hpp)6
-rw-r--r--include/cru/win/graphics/direct/TextLayout.hpp (renamed from include/cru/win/graph/direct/TextLayout.hpp)8
-rw-r--r--include/cru/win/graphics/direct/WindowPainter.hpp21
-rw-r--r--include/cru/win/graphics/direct/WindowRenderTarget.hpp42
-rw-r--r--include/cru/win/gui/Base.hpp (renamed from include/cru/win/native/Base.hpp)9
-rw-r--r--include/cru/win/gui/Cursor.hpp (renamed from include/cru/win/native/Cursor.hpp)9
-rw-r--r--include/cru/win/gui/Exception.hpp (renamed from include/cru/win/native/Exception.hpp)4
-rw-r--r--include/cru/win/gui/GodWindow.hpp (renamed from include/cru/win/native/GodWindow.hpp)15
-rw-r--r--include/cru/win/gui/InputMethod.hpp (renamed from include/cru/win/native/InputMethod.hpp)31
-rw-r--r--include/cru/win/gui/Keyboard.hpp9
-rw-r--r--include/cru/win/gui/Resource.hpp (renamed from include/cru/win/native/Resource.hpp)4
-rw-r--r--include/cru/win/gui/UiApplication.hpp (renamed from include/cru/win/native/UiApplication.hpp)22
-rw-r--r--include/cru/win/gui/Window.hpp (renamed from include/cru/win/native/Window.hpp)96
-rw-r--r--include/cru/win/gui/WindowClass.hpp (renamed from include/cru/win/native/WindowClass.hpp)4
-rw-r--r--include/cru/win/gui/WindowNativeMessageEventArgs.hpp (renamed from include/cru/win/native/WindowNativeMessageEventArgs.hpp)4
-rw-r--r--include/cru/win/native/Keyboard.hpp9
-rw-r--r--include/cru/win/native/WindowRenderTarget.hpp47
-rw-r--r--src/common/CMakeLists.txt1
-rw-r--r--src/common/Logger.cpp1
-rw-r--r--src/common/StringUtil.cpp68
-rw-r--r--src/platform/CMakeLists.txt4
-rw-r--r--src/platform/graph/CMakeLists.txt14
-rw-r--r--src/platform/graphics/CMakeLists.txt14
-rw-r--r--src/platform/gui/CMakeLists.txt14
-rw-r--r--src/platform/gui/Keyboard.cpp142
-rw-r--r--src/platform/gui/UiApplication.cpp (renamed from src/platform/native/UiApplication.cpp)6
-rw-r--r--src/platform/native/CMakeLists.txt13
-rw-r--r--src/ui/CMakeLists.txt70
-rw-r--r--src/ui/ContentControl.cpp33
-rw-r--r--src/ui/Control.cpp130
-rw-r--r--src/ui/Helper.cpp8
-rw-r--r--src/ui/Helper.hpp4
-rw-r--r--src/ui/LayoutControl.cpp53
-rw-r--r--src/ui/NoChildControl.cpp5
-rw-r--r--src/ui/UiHost.cpp388
-rw-r--r--src/ui/UiManager.cpp122
-rw-r--r--src/ui/Window.cpp28
-rw-r--r--src/ui/components/Component.cpp5
-rw-r--r--src/ui/components/Menu.cpp61
-rw-r--r--src/ui/controls/Button.cpp62
-rw-r--r--src/ui/controls/Container.cpp10
-rw-r--r--src/ui/controls/ContentControl.cpp31
-rw-r--r--src/ui/controls/Control.cpp166
-rw-r--r--src/ui/controls/FlexLayout.cpp12
-rw-r--r--src/ui/controls/LayoutControl.cpp18
-rw-r--r--src/ui/controls/NoChildControl.cpp3
-rw-r--r--src/ui/controls/Popup.cpp22
-rw-r--r--src/ui/controls/RootControl.cpp53
-rw-r--r--src/ui/controls/StackLayout.cpp15
-rw-r--r--src/ui/controls/TextBlock.cpp22
-rw-r--r--src/ui/controls/TextBox.cpp36
-rw-r--r--src/ui/controls/TextControlService.hpp403
-rw-r--r--src/ui/controls/TextHostControlService.cpp469
-rw-r--r--src/ui/controls/Window.cpp24
-rw-r--r--src/ui/events/UiEvent.cpp (renamed from src/ui/UiEvent.cpp)6
-rw-r--r--src/ui/helper/BorderStyle.cpp0
-rw-r--r--src/ui/helper/ClickDetector.cpp (renamed from src/ui/ClickDetector.cpp)51
-rw-r--r--src/ui/helper/ShortcutHub.cpp131
-rw-r--r--src/ui/host/LayoutPaintCycler.cpp35
-rw-r--r--src/ui/host/RoutedEventDispatch.hpp (renamed from src/ui/RoutedEventDispatch.hpp)61
-rw-r--r--src/ui/host/WindowHost.cpp440
-rw-r--r--src/ui/render/BorderRenderObject.cpp34
-rw-r--r--src/ui/render/CanvasRenderObject.cpp2
-rw-r--r--src/ui/render/FlexLayoutRenderObject.cpp23
-rw-r--r--src/ui/render/RenderObject.cpp100
-rw-r--r--src/ui/render/ScrollBar.cpp622
-rw-r--r--src/ui/render/ScrollRenderObject.cpp66
-rw-r--r--src/ui/render/TextRenderObject.cpp40
-rw-r--r--src/ui/render/WindowRenderObject.cpp40
-rw-r--r--src/ui/style/Condition.cpp84
-rw-r--r--src/ui/style/StyleRule.cpp17
-rw-r--r--src/ui/style/StyleRuleSet.cpp97
-rw-r--r--src/ui/style/Styler.cpp29
-rw-r--r--src/win/CMakeLists.txt5
-rw-r--r--src/win/StdOutLogger.hpp24
-rw-r--r--src/win/graph/direct/CMakeLists.txt25
-rw-r--r--src/win/graph/direct/Resource.cpp12
-rw-r--r--src/win/graphics/CMakeLists.txt (renamed from src/win/graph/CMakeLists.txt)0
-rw-r--r--src/win/graphics/direct/Brush.cpp (renamed from src/win/graph/direct/Brush.cpp)12
-rw-r--r--src/win/graphics/direct/CMakeLists.txt29
-rw-r--r--src/win/graphics/direct/Factory.cpp (renamed from src/win/graph/direct/Factory.cpp)16
-rw-r--r--src/win/graphics/direct/Font.cpp (renamed from src/win/graph/direct/Font.cpp)10
-rw-r--r--src/win/graphics/direct/Geometry.cpp (renamed from src/win/graph/direct/Geometry.cpp)12
-rw-r--r--src/win/graphics/direct/Painter.cpp (renamed from src/win/graph/direct/Painter.cpp)24
-rw-r--r--src/win/graphics/direct/Resource.cpp12
-rw-r--r--src/win/graphics/direct/TextLayout.cpp (renamed from src/win/graph/direct/TextLayout.cpp)19
-rw-r--r--src/win/graphics/direct/WindowPainter.cpp20
-rw-r--r--src/win/graphics/direct/WindowRenderTarget.cpp (renamed from src/win/native/WindowRenderTarget.cpp)35
-rw-r--r--src/win/gui/CMakeLists.txt31
-rw-r--r--src/win/gui/Cursor.cpp (renamed from src/win/native/Cursor.cpp)13
-rw-r--r--src/win/gui/GodWindow.cpp (renamed from src/win/native/GodWindow.cpp)61
-rw-r--r--src/win/gui/InputMethod.cpp (renamed from src/win/native/InputMethod.cpp)102
-rw-r--r--src/win/gui/Keyboard.cpp (renamed from src/win/native/Keyboard.cpp)6
-rw-r--r--src/win/gui/TimerManager.cpp100
-rw-r--r--src/win/gui/TimerManager.hpp61
-rw-r--r--src/win/gui/UiApplication.cpp (renamed from src/win/native/UiApplication.cpp)80
-rw-r--r--src/win/gui/Window.cpp (renamed from src/win/native/Window.cpp)128
-rw-r--r--src/win/gui/WindowClass.cpp (renamed from src/win/native/WindowClass.cpp)8
-rw-r--r--src/win/gui/WindowManager.cpp (renamed from src/win/native/WindowManager.cpp)10
-rw-r--r--src/win/gui/WindowManager.hpp (renamed from src/win/native/WindowManager.hpp)4
-rw-r--r--src/win/native/CMakeLists.txt37
-rw-r--r--src/win/native/DpiUtil.hpp46
-rw-r--r--src/win/native/GodWindowMessage.hpp6
-rw-r--r--src/win/native/Timer.cpp28
-rw-r--r--src/win/native/Timer.hpp34
-rw-r--r--src/win/native/WindowD2DPainter.cpp22
-rw-r--r--src/win/native/WindowD2DPainter.hpp21
-rw-r--r--test/CMakeLists.txt9
-rw-r--r--test/common/HandlerRegistryTest.cpp36
-rw-r--r--test/platform/CMakeLists.txt6
-rw-r--r--test/platform/MatrixTest.cpp37
-rw-r--r--test/win/CMakeLists.txt1
-rw-r--r--test/win/graphics/CMakeLists.txt1
-rw-r--r--test/win/graphics/direct/CMakeLists.txt6
-rw-r--r--test/win/graphics/direct/ConvertTest.cpp29
m---------vcpkg0
203 files changed, 6208 insertions, 2892 deletions
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index aa5d19e3..3aa088a5 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -38,7 +38,7 @@ jobs:
submodules: true
- name: Restore artifacts, or run vcpkg, build and cache artifacts
- uses: lukka/run-vcpkg@v3
+ uses: lukka/run-vcpkg@v6
id: runvcpkg
with:
vcpkgArguments: "${{ env.vcpkgPackages }}"
diff --git a/azure-pipelines.yml b/azure-pipelines.yml
deleted file mode 100644
index 90a89d15..00000000
--- a/azure-pipelines.yml
+++ /dev/null
@@ -1,39 +0,0 @@
-trigger:
-- master
-
-jobs:
- - job: Windows_Build
- pool:
- vmImage: 'windows-2019'
- strategy:
- matrix:
- x86_debug:
- buildPlatform: 'x86'
- buildConfiguration: 'Debug'
- x86_release:
- buildPlatform: 'x86'
- buildConfiguration: 'Release'
- x64_debug:
- buildPlatform: 'x64'
- buildConfiguration: 'Debug'
- x64_release:
- buildPlatform: 'x64'
- buildConfiguration: 'Release'
-
- steps:
- - task: UsePythonVersion@0
- inputs:
- versionSpec: '3.x'
- addToPath: true
-
- - script: python tools\win_build.py -a $(buildPlatform) -c $(buildConfiguration)
-
- - task: CopyFiles@2
- inputs:
- contents: '**\$(buildConfiguration)\**\?(*.exe|*.dll|*.pdb)'
- targetFolder: '$(build.artifactStagingDirectory)'
-
- - task: PublishBuildArtifacts@1
- inputs:
- pathToPublish: '$(build.artifactStagingDirectory)'
- artifactName: 'executables_$(buildPlatform)_$(buildConfiguration)'
diff --git a/demos/CMakeLists.txt b/demos/CMakeLists.txt
index 64612bf9..16159d08 100644
--- a/demos/CMakeLists.txt
+++ b/demos/CMakeLists.txt
@@ -1,7 +1,7 @@
add_library(cru_demo_base INTERFACE)
if(WIN32)
- target_link_libraries(cru_demo_base INTERFACE cru_win_native)
+ target_link_libraries(cru_demo_base INTERFACE cru_win_gui)
endif()
add_subdirectory(main)
diff --git a/demos/input_method/main.cpp b/demos/input_method/main.cpp
index bff0e1d3..5a6dc942 100644
--- a/demos/input_method/main.cpp
+++ b/demos/input_method/main.cpp
@@ -1,25 +1,22 @@
-#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"
+#include "cru/platform/graphics/Factory.hpp"
+#include "cru/platform/graphics/Font.hpp"
+#include "cru/platform/graphics/Painter.hpp"
+#include "cru/platform/gui/InputMethod.hpp"
+#include "cru/platform/gui/UiApplication.hpp"
+#include "cru/platform/gui/Window.hpp"
int main() {
using namespace cru::platform;
- using namespace cru::platform::graph;
- using namespace cru::platform::native;
+ using namespace cru::platform::graphics;
+ using namespace cru::platform::gui;
auto application = CreateUiApplication();
auto graph_factory = application->GetGraphFactory();
- auto window_resolver = application->CreateWindow(nullptr);
+ auto window = application->CreateWindow(nullptr);
- auto window = window_resolver->Resolve();
-
- auto input_method_context =
- application->GetInputMethodManager()->GetContext(window);
+ auto input_method_context = window->GetInputMethodContext();
auto brush = graph_factory->CreateSolidColorBrush();
brush->SetColor(colors::black);
diff --git a/demos/main/main.cpp b/demos/main/main.cpp
index 82038731..541a2320 100644
--- a/demos/main/main.cpp
+++ b/demos/main/main.cpp
@@ -1,23 +1,20 @@
#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/platform/gui/UiApplication.hpp"
+#include "cru/platform/gui/Window.hpp"
+#include "cru/ui/Base.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"
+#include "cru/ui/controls/Window.hpp"
+#include "cru/ui/host/WindowHost.hpp"
-using cru::platform::native::CreateUiApplication;
-using cru::ui::Rect;
-using cru::ui::Thickness;
-using cru::ui::Window;
+using cru::platform::gui::CreateUiApplication;
using cru::ui::controls::Button;
using cru::ui::controls::FlexLayout;
-using cru::ui::controls::StackLayout;
using cru::ui::controls::TextBlock;
using cru::ui::controls::TextBox;
+using cru::ui::controls::Window;
int main() {
#ifdef CRU_DEBUG
@@ -26,37 +23,27 @@ int main() {
auto application = CreateUiApplication();
- const auto window = Window::CreateOverlapped();
+ const auto window = Window::Create();
const auto flex_layout = FlexLayout::Create();
+ flex_layout->SetFlexDirection(cru::ui::FlexDirection::Vertical);
+ flex_layout->SetContentMainAlign(cru::ui::FlexCrossAlignment::Center);
+ flex_layout->SetItemCrossAlign(cru::ui::FlexCrossAlignment::Center);
- window->SetChild(flex_layout);
+ window->AddChild(flex_layout, 0);
- const auto button = Button::Create();
- const auto text_block1 = TextBlock::Create();
- text_block1->SetText(u"Hello World!");
- button->SetChild(text_block1);
- flex_layout->AddChild(button, 0);
-
- const auto text_block2 = TextBlock::Create();
- text_block2->SetText(u"Hello World!");
-
- const auto text_block3 = TextBlock::Create();
- text_block3->SetText(u"Overlapped text");
+ const auto text_block = TextBlock::Create(u"Hello World from CruUI!", true);
+ flex_layout->AddChild(text_block, 0);
- const auto stack_layout = StackLayout::Create();
- stack_layout->AddChild(text_block2, 0);
- stack_layout->AddChild(text_block3, 1);
- flex_layout->AddChild(stack_layout, 1);
-
- const auto text_block4 = TextBlock::Create();
- text_block4->SetText(u"Hello World!!!");
- flex_layout->AddChild(text_block4, 2);
+ const auto button_text_block = TextBlock::Create(u"OK");
+ const auto button = Button::Create();
+ button->SetChild(button_text_block);
+ flex_layout->AddChild(button, 1);
const auto text_box = TextBox::Create();
- flex_layout->AddChild(text_box, 3);
+ flex_layout->AddChild(text_box, 2);
- window->GetUiHost()->GetNativeWindowResolver()->Resolve()->SetVisible(true);
+ window->Show();
return application->Run();
}
diff --git a/include/cru/common/Base.hpp b/include/cru/common/Base.hpp
index a5a9421d..560f83bb 100644
--- a/include/cru/common/Base.hpp
+++ b/include/cru/common/Base.hpp
@@ -1,8 +1,8 @@
#pragma once
#include "PreConfig.hpp"
+#include <exception>
#include <gsl/gsl>
-
#include <stdexcept>
#define CRU_UNUSED(entity) static_cast<void>(entity);
@@ -42,12 +42,17 @@ struct Interface {
virtual ~Interface() = default;
};
-[[noreturn]] inline void UnreachableCode() {
- throw std::runtime_error("Unreachable code.");
-}
+[[noreturn]] inline void UnreachableCode() { std::terminate(); }
using Index = gsl::index;
+// https://www.boost.org/doc/libs/1_54_0/doc/html/hash/reference.html#boost.hash_combine
+template <class T>
+inline void hash_combine(std::size_t& s, const T& v) {
+ std::hash<T> h;
+ s ^= h(v) + 0x9e3779b9 + (s << 6) + (s >> 2);
+}
+
#define CRU_DEFINE_CLASS_LOG_TAG(tag) \
private: \
constexpr static std::u16string_view log_tag = tag;
diff --git a/include/cru/common/ClonablePtr.hpp b/include/cru/common/ClonablePtr.hpp
new file mode 100644
index 00000000..5e4b80c9
--- /dev/null
+++ b/include/cru/common/ClonablePtr.hpp
@@ -0,0 +1,204 @@
+#pragma once
+
+#include <cstddef>
+#include <functional>
+#include <memory>
+#include <type_traits>
+
+namespace cru {
+template <typename TClonable>
+class ClonablePtr {
+ template <typename T>
+ friend class ClonablePtr;
+
+ public:
+ using element_type = typename std::unique_ptr<TClonable>::element_type;
+ using pointer = typename std::unique_ptr<TClonable>::pointer;
+
+ ClonablePtr() = default;
+ ClonablePtr(std::nullptr_t) noexcept : ptr_(nullptr) {}
+ explicit ClonablePtr(pointer p) noexcept : ptr_(p) {}
+ ClonablePtr(std::unique_ptr<element_type>&& p) noexcept
+ : ptr_(std::move(p)) {}
+ template <typename O,
+ std::enable_if_t<std::is_convertible_v<
+ typename ClonablePtr<O>::pointer, pointer>,
+ int> = 0>
+ ClonablePtr(std::unique_ptr<O>&& p) : ptr_(std::move(p)) {}
+ ClonablePtr(const ClonablePtr& other) : ptr_(other.ptr_->Clone()) {}
+ ClonablePtr(ClonablePtr&& other) = default;
+ template <typename O,
+ std::enable_if_t<std::is_convertible_v<
+ typename ClonablePtr<O>::pointer, pointer>,
+ int> = 0>
+ ClonablePtr(const ClonablePtr<O>& other) : ptr_(other.ptr_->Clone()) {}
+ template <typename O,
+ std::enable_if_t<std::is_convertible_v<
+ typename ClonablePtr<O>::pointer, pointer>,
+ int> = 0>
+ ClonablePtr(ClonablePtr<O>&& other) noexcept : ptr_(std::move(other.ptr_)) {}
+ ClonablePtr& operator=(std::nullptr_t) noexcept {
+ ptr_ = nullptr;
+ return *this;
+ }
+ ClonablePtr& operator=(std::unique_ptr<element_type>&& other) noexcept {
+ ptr_ = std::move(other);
+ return *this;
+ }
+ template <typename O,
+ std::enable_if_t<std::is_convertible_v<
+ typename ClonablePtr<O>::pointer, pointer>,
+ int> = 0>
+ ClonablePtr& operator=(std::unique_ptr<O>&& p) noexcept {
+ ptr_ = std::move(p);
+ return *this;
+ }
+ ClonablePtr& operator=(const ClonablePtr& other) {
+ if (this != &other) {
+ ptr_ = std::unique_ptr<element_type>(other.ptr_->Clone());
+ }
+ return *this;
+ }
+ ClonablePtr& operator=(ClonablePtr&& other) = default;
+ template <typename O,
+ std::enable_if_t<std::is_convertible_v<
+ typename ClonablePtr<O>::pointer, pointer>,
+ int> = 0>
+ ClonablePtr& operator=(const ClonablePtr<O>& other) noexcept {
+ if (this != &other) {
+ ptr_ = std::unique_ptr<element_type>(other.ptr_->Clone());
+ }
+ return *this;
+ }
+ template <typename O,
+ std::enable_if_t<std::is_convertible_v<
+ typename ClonablePtr<O>::pointer, pointer>,
+ int> = 0>
+ ClonablePtr& operator=(ClonablePtr<O>&& other) noexcept {
+ ptr_ = std::move(other.ptr_);
+ }
+
+ ~ClonablePtr() = default;
+
+ public:
+ pointer release() noexcept { return ptr_.release(); }
+ void reset(pointer p = pointer()) noexcept { ptr_.reset(p); }
+ void swap(ClonablePtr& other) noexcept { ptr_.swap(other.ptr_); }
+
+ public:
+ pointer get() const noexcept { return ptr_.get(); }
+
+ operator bool() const noexcept { return ptr_; }
+
+ element_type& operator*() const noexcept { return *ptr_; }
+ pointer operator->() const noexcept { return ptr_.get(); }
+
+ private:
+ std::unique_ptr<element_type> ptr_;
+};
+
+template <typename T>
+void swap(ClonablePtr<T>& left, ClonablePtr<T>& right) noexcept {
+ left.swap(right);
+}
+
+template <typename T>
+bool operator==(const ClonablePtr<T>& left, const ClonablePtr<T>& right) {
+ return left.get() == right.get();
+}
+
+template <typename T>
+bool operator!=(const ClonablePtr<T>& left, const ClonablePtr<T>& right) {
+ return left.get() != right.get();
+}
+
+template <typename T>
+bool operator<(const ClonablePtr<T>& left, const ClonablePtr<T>& right) {
+ return left.get() < right.get();
+}
+
+template <typename T>
+bool operator<=(const ClonablePtr<T>& left, const ClonablePtr<T>& right) {
+ return left.get() <= right.get();
+}
+
+template <typename T>
+bool operator>(const ClonablePtr<T>& left, const ClonablePtr<T>& right) {
+ return left.get() > right.get();
+}
+
+template <typename T>
+bool operator>=(const ClonablePtr<T>& left, const ClonablePtr<T>& right) {
+ return left.get() >= right.get();
+}
+
+template <typename T>
+bool operator==(const ClonablePtr<T>& left, std::nullptr_t) {
+ return left.get() == nullptr;
+}
+
+template <typename T>
+bool operator!=(const ClonablePtr<T>& left, std::nullptr_t) {
+ return left.get() != nullptr;
+}
+
+template <typename T>
+bool operator<(const ClonablePtr<T>& left, std::nullptr_t) {
+ return left.get() < nullptr;
+}
+
+template <typename T>
+bool operator<=(const ClonablePtr<T>& left, std::nullptr_t) {
+ return left.get() <= nullptr;
+}
+
+template <typename T>
+bool operator>(const ClonablePtr<T>& left, std::nullptr_t) {
+ return left.get() > nullptr;
+}
+
+template <typename T>
+bool operator>=(const ClonablePtr<T>& left, std::nullptr_t) {
+ return left.get() >= nullptr;
+}
+
+template <typename T>
+bool operator==(std::nullptr_t, const ClonablePtr<T>& right) {
+ return nullptr == right.get();
+}
+
+template <typename T>
+bool operator!=(std::nullptr_t, const ClonablePtr<T>& right) {
+ return nullptr != right.get();
+}
+
+template <typename T>
+bool operator<(std::nullptr_t, const ClonablePtr<T>& right) {
+ return nullptr < right.get();
+}
+
+template <typename T>
+bool operator<=(std::nullptr_t, const ClonablePtr<T>& right) {
+ return nullptr <= right.get();
+}
+
+template <typename T>
+bool operator>(std::nullptr_t, const ClonablePtr<T>& right) {
+ return nullptr > right.get();
+}
+
+template <typename T>
+bool operator>=(std::nullptr_t, const ClonablePtr<T>& right) {
+ return nullptr >= right.get();
+}
+
+} // namespace cru
+
+namespace std {
+template <typename T>
+struct hash<cru::ClonablePtr<T>> {
+ std::size_t operator()(const cru::ClonablePtr<T>& p) const {
+ return std::hash<typename cru::ClonablePtr<T>::pointer>(p.get());
+ }
+};
+} // namespace std
diff --git a/include/cru/common/Event.hpp b/include/cru/common/Event.hpp
index 377ca7f3..b6999aa4 100644
--- a/include/cru/common/Event.hpp
+++ b/include/cru/common/Event.hpp
@@ -5,14 +5,19 @@
#include <algorithm>
#include <functional>
+#include <initializer_list>
#include <memory>
#include <utility>
+#include <variant>
#include <vector>
namespace cru {
class EventRevoker;
namespace details {
+template <class>
+inline constexpr bool always_false_v = false;
+
// Base class of all Event<T...>.
// It erases event args types and provides a
// unified form to create event revoker and
@@ -24,10 +29,8 @@ class EventBase : public SelfResolvable<EventBase> {
using EventHandlerToken = long;
EventBase() {}
- EventBase(const EventBase& other) = delete;
- EventBase(EventBase&& other) = delete;
- EventBase& operator=(const EventBase& other) = delete;
- EventBase& operator=(EventBase&& other) = delete;
+ CRU_DELETE_COPY(EventBase)
+ CRU_DEFAULT_MOVE(EventBase)
virtual ~EventBase() = default;
// Remove the handler with the given token. If the token
@@ -84,78 +87,114 @@ using DeducedEventArgs = std::conditional_t<
std::is_lvalue_reference_v<TRaw>, TRaw,
std::conditional_t<std::is_scalar_v<TRaw>, TRaw, const TRaw&>>;
+struct IBaseEvent {
+ protected:
+ IBaseEvent() = default;
+ CRU_DELETE_COPY(IBaseEvent)
+ CRU_DEFAULT_MOVE(IBaseEvent)
+ ~IBaseEvent() = default; // Note that user can't destroy a Event via IEvent.
+ // So destructor should be protected.
+
+ using SpyOnlyHandler = std::function<void()>;
+
+ public:
+ virtual EventRevoker AddSpyOnlyHandler(SpyOnlyHandler handler) = 0;
+};
+
// Provides an interface of event.
// IEvent only allow to add handler but not to raise the event. You may
// want to create an Event object and expose IEvent only so users won't
// be able to emit the event.
template <typename TEventArgs>
-struct IEvent {
+struct IEvent : virtual IBaseEvent {
public:
using EventArgs = DeducedEventArgs<TEventArgs>;
using EventHandler = std::function<void(EventArgs)>;
+ using ShortCircuitHandler = std::function<bool(EventArgs)>;
protected:
IEvent() = default;
- IEvent(const IEvent& other) = delete;
- IEvent(IEvent&& other) = delete;
- IEvent& operator=(const IEvent& other) = delete;
- IEvent& operator=(IEvent&& other) = delete;
+ CRU_DELETE_COPY(IEvent)
+ CRU_DEFAULT_MOVE(IEvent)
~IEvent() = default; // Note that user can't destroy a Event via IEvent. So
// destructor should be protected.
public:
- virtual EventRevoker AddHandler(const EventHandler& handler) = 0;
- virtual EventRevoker AddHandler(EventHandler&& handler) = 0;
+ virtual EventRevoker AddHandler(EventHandler handler) = 0;
+ virtual EventRevoker AddShortCircuitHandler(ShortCircuitHandler handler) = 0;
+ virtual EventRevoker PrependShortCircuitHandler(
+ ShortCircuitHandler handler) = 0;
};
// A non-copyable non-movable Event class.
// It stores a list of event handlers.
template <typename TEventArgs>
class Event : public details::EventBase, public IEvent<TEventArgs> {
+ using typename IEvent<TEventArgs>::EventArgs;
+
+ using typename IBaseEvent::SpyOnlyHandler;
using typename IEvent<TEventArgs>::EventHandler;
+ using typename IEvent<TEventArgs>::ShortCircuitHandler;
private:
struct HandlerData {
- HandlerData(EventHandlerToken token, EventHandler handler)
- : token(token), handler(handler) {}
+ HandlerData(EventHandlerToken token, ShortCircuitHandler handler)
+ : token(token), handler(std::move(handler)) {}
EventHandlerToken token;
- EventHandler handler;
+ ShortCircuitHandler handler;
};
public:
Event() = default;
- Event(const Event&) = delete;
- Event& operator=(const Event&) = delete;
- Event(Event&&) = delete;
- Event& operator=(Event&&) = delete;
+ CRU_DELETE_COPY(Event)
+ CRU_DEFAULT_MOVE(Event)
~Event() = default;
- EventRevoker AddHandler(const EventHandler& handler) override {
+ EventRevoker AddSpyOnlyHandler(SpyOnlyHandler handler) override {
+ return AddShortCircuitHandler([handler = std::move(handler)](EventArgs) {
+ handler();
+ return false;
+ });
+ }
+
+ EventRevoker AddHandler(EventHandler handler) override {
+ return AddShortCircuitHandler(
+ [handler = std::move(handler)](EventArgs args) {
+ handler(args);
+ return false;
+ });
+ }
+
+ // Handler return true to short circuit following handlers.
+ EventRevoker AddShortCircuitHandler(ShortCircuitHandler handler) override {
const auto token = current_token_++;
- this->handler_data_list_.emplace_back(token, handler);
+ this->handler_data_list_.emplace_back(token, std::move(handler));
return CreateRevoker(token);
}
- EventRevoker AddHandler(EventHandler&& handler) override {
+ // Handler return true to short circuit following handlers.
+ EventRevoker PrependShortCircuitHandler(
+ ShortCircuitHandler handler) override {
const auto token = current_token_++;
- this->handler_data_list_.emplace_back(token, std::move(handler));
+ this->handler_data_list_.emplace(this->handler_data_list_.cbegin(), token,
+ std::move(handler));
return CreateRevoker(token);
}
// This method will make a copy of all handlers. Because user might delete a
- // handler in a handler, which may lead to seg fault as the handler is deleted
- // while being executed.
- // Thanks to this behavior, all handlers will be taken a snapshot when Raise
- // is called, so even if you delete a handler during this period, all handlers
- // in the snapshot will be executed.
- void Raise(typename IEvent<TEventArgs>::EventArgs args) {
- std::vector<EventHandler> handlers;
+ // handler in a handler, which may lead to seg fault as the handler is
+ // deleted while being executed. Thanks to this behavior, all handlers will
+ // be taken a snapshot when Raise is called, so even if you delete a handler
+ // during this period, all handlers in the snapshot will be executed.
+ void Raise(EventArgs args) {
+ std::vector<ShortCircuitHandler> handlers;
handlers.reserve(this->handler_data_list_.size());
for (const auto& data : this->handler_data_list_) {
handlers.push_back(data.handler);
}
for (const auto& handler : handlers) {
- handler(args);
+ auto short_circuit = handler(args);
+ if (short_circuit) return;
}
}
@@ -183,6 +222,7 @@ struct EventRevokerDestroyer {
};
} // namespace details
+// A guard class for event revoker. Automatically revoke it when destroyed.
class EventRevokerGuard {
public:
EventRevokerGuard() = default;
@@ -201,7 +241,9 @@ class EventRevokerGuard {
return *revoker_;
}
- void Release() { revoker_.release(); }
+ EventRevoker Release() { return std::move(*revoker_.release()); }
+
+ void Reset() { revoker_.reset(); }
void Reset(EventRevoker&& revoker) {
revoker_.reset(new EventRevoker(std::move(revoker)));
@@ -209,5 +251,32 @@ class EventRevokerGuard {
private:
std::unique_ptr<EventRevoker, details::EventRevokerDestroyer> revoker_;
-}; // namespace cru
+};
+
+class EventRevokerListGuard {
+ public:
+ EventRevokerListGuard() = default;
+ EventRevokerListGuard(const EventRevokerListGuard& other) = delete;
+ EventRevokerListGuard(EventRevokerListGuard&& other) = default;
+ EventRevokerListGuard& operator=(const EventRevokerListGuard& other) = delete;
+ EventRevokerListGuard& operator=(EventRevokerListGuard&& other) = default;
+ ~EventRevokerListGuard() = default;
+
+ public:
+ void Add(EventRevoker&& revoker) {
+ event_revoker_guard_list_.push_back(EventRevokerGuard(std::move(revoker)));
+ }
+
+ EventRevokerListGuard& operator+=(EventRevoker&& revoker) {
+ this->Add(std::move(revoker));
+ return *this;
+ }
+
+ void Clear() { event_revoker_guard_list_.clear(); }
+
+ bool IsEmpty() const { return event_revoker_guard_list_.empty(); }
+
+ private:
+ std::vector<EventRevokerGuard> event_revoker_guard_list_;
+};
} // namespace cru
diff --git a/include/cru/common/Format.hpp b/include/cru/common/Format.hpp
new file mode 100644
index 00000000..59f34036
--- /dev/null
+++ b/include/cru/common/Format.hpp
@@ -0,0 +1,23 @@
+#pragma once
+#include "Base.hpp"
+
+#include "StringUtil.hpp"
+
+#include <array>
+#include <charconv>
+#include <string>
+#include <string_view>
+#include <system_error>
+#include <type_traits>
+
+namespace cru {
+template <typename T, typename = std::enable_if_t<std::is_arithmetic_v<T>>>
+std::u16string ToUtf16String(T number) {
+ std::array<char, 40> buffer;
+ auto result =
+ std::to_chars(buffer.data(), buffer.data() + buffer.size(), number);
+ Ensures(result.ec == std::errc());
+ std::string_view utf8_result(buffer.data(), result.ptr - buffer.data());
+ return ToUtf16(utf8_result);
+}
+} // namespace cru
diff --git a/include/cru/common/HandlerRegistry.hpp b/include/cru/common/HandlerRegistry.hpp
new file mode 100644
index 00000000..bd74a9e0
--- /dev/null
+++ b/include/cru/common/HandlerRegistry.hpp
@@ -0,0 +1,86 @@
+#pragma once
+#include "Base.hpp"
+
+#include <algorithm>
+#include <functional>
+#include <utility>
+#include <vector>
+
+namespace cru {
+
+template <typename T>
+class HandlerRegistryIterator {
+ public:
+ using RawIterator =
+ typename std::vector<std::pair<int, std::function<T>>>::const_iterator;
+
+ explicit HandlerRegistryIterator(RawIterator raw) : raw_(std::move(raw)) {}
+
+ CRU_DELETE_COPY(HandlerRegistryIterator)
+ CRU_DELETE_MOVE(HandlerRegistryIterator)
+
+ ~HandlerRegistryIterator() = default;
+
+ const std::function<T>& operator*() const { return raw_->second; }
+ const std::function<T>* operator->() const { return &raw_->second; }
+
+ HandlerRegistryIterator& operator++() {
+ ++raw_;
+ return *this;
+ }
+
+ HandlerRegistryIterator operator++(int) {
+ auto c = *this;
+ this->operator++();
+ return c;
+ }
+
+ bool operator==(const HandlerRegistryIterator<T>& other) const {
+ return this->raw_ == other.raw_;
+ }
+
+ bool operator!=(const HandlerRegistryIterator<T>& other) const {
+ return !this->operator==(other);
+ }
+
+ private:
+ RawIterator raw_;
+};
+
+template <typename T>
+class HandlerRegistry final {
+ public:
+ HandlerRegistry() = default;
+ CRU_DEFAULT_COPY(HandlerRegistry)
+ CRU_DEFAULT_MOVE(HandlerRegistry)
+ ~HandlerRegistry() = default;
+
+ public:
+ int AddHandler(std::function<T> handler) {
+ auto id = current_id_++;
+ handler_list_.push_back({id, std::move(handler)});
+ }
+
+ void RemoveHandler(int id) {
+ auto result = std::find_if(handler_list_.cbegin(), handler_list_.cend(),
+ [id](const std::pair<int, std::function<T>>& d) {
+ return d.first == id;
+ });
+ if (result != handler_list_.cend()) {
+ handler_list_.erase(result);
+ }
+ }
+
+ HandlerRegistryIterator<T> begin() const {
+ return HandlerRegistryIterator<T>(handler_list_.begin());
+ }
+
+ HandlerRegistryIterator<T> end() const {
+ return HandlerRegistryIterator<T>(handler_list_.begin());
+ }
+
+ private:
+ int current_id_ = 1;
+ std::vector<std::pair<int, std::function<T>>> handler_list_;
+};
+} // namespace cru
diff --git a/include/cru/common/Logger.hpp b/include/cru/common/Logger.hpp
index 4ea17c09..daf2e7d2 100644
--- a/include/cru/common/Logger.hpp
+++ b/include/cru/common/Logger.hpp
@@ -40,6 +40,7 @@ class Logger : public Object {
std::list<std::unique_ptr<ILogSource>> sources_;
};
+// TODO: Remove argument evaluation in Debug.
template <typename... TArgs>
void Debug([[maybe_unused]] TArgs&&... args) {
#ifdef CRU_DEBUG
@@ -66,6 +67,7 @@ void Error(TArgs&&... args) {
fmt::format(std::forward<TArgs>(args)...));
}
+// TODO: Remove argument evaluation in Debug.
template <typename... TArgs>
void TagDebug([[maybe_unused]] std::u16string_view tag,
[[maybe_unused]] TArgs&&... args) {
diff --git a/include/cru/common/SelfResolvable.hpp b/include/cru/common/SelfResolvable.hpp
index 94f3ae87..eaa4ce34 100644
--- a/include/cru/common/SelfResolvable.hpp
+++ b/include/cru/common/SelfResolvable.hpp
@@ -39,9 +39,27 @@ class SelfResolvable {
SelfResolvable() : resolver_(new T*(static_cast<T*>(this))) {}
SelfResolvable(const SelfResolvable&) = delete;
SelfResolvable& operator=(const SelfResolvable&) = delete;
- SelfResolvable(SelfResolvable&&) = delete;
- SelfResolvable& operator=(SelfResolvable&&) = delete;
- virtual ~SelfResolvable() { (*resolver_) = nullptr; }
+
+ // Resolvers to old object will resolve to new object.
+ SelfResolvable(SelfResolvable&& other)
+ : resolver_(std::move(other.resolver_)) {
+ (*resolver_) = static_cast<T*>(this);
+ }
+
+ // Old resolvers for this object will resolve to nullptr.
+ // Other's resolvers will now resolve to this.
+ SelfResolvable& operator=(SelfResolvable&& other) {
+ if (this != &other) {
+ (*resolver_) = nullptr;
+ resolver_ = std::move(other.resolver_);
+ (*resolver_) = static_cast<T*>(this);
+ }
+ return *this;
+ }
+
+ virtual ~SelfResolvable() {
+ if (resolver_ != nullptr) (*resolver_) = nullptr;
+ }
ObjectResolver<T> CreateResolver() { return ObjectResolver<T>(resolver_); }
diff --git a/include/cru/common/StringUtil.hpp b/include/cru/common/StringUtil.hpp
index 5dacfa12..62999d53 100644
--- a/include/cru/common/StringUtil.hpp
+++ b/include/cru/common/StringUtil.hpp
@@ -1,6 +1,10 @@
#pragma once
#include "Base.hpp"
+#include <functional>
+#include <string>
+#include <string_view>
+
namespace cru {
using CodePoint = std::int32_t;
constexpr CodePoint k_invalid_code_point = -1;
@@ -124,4 +128,21 @@ void Utf16EncodeCodePointAppend(CodePoint code_point, std::u16string& str);
std::string ToUtf8(std::u16string_view s);
std::u16string ToUtf16(std::string_view s);
+
+// If given s is not a valid utf16 string, return value is UD.
+bool Utf16IsValidInsertPosition(std::u16string_view s, gsl::index position);
+
+// Return position after the character making predicate returns true or 0 if no
+// character doing so.
+gsl::index Utf16BackwardUntil(std::u16string_view str, gsl::index position,
+ const std::function<bool(CodePoint)>& predicate);
+// Return position before the character making predicate returns true or
+// str.size() if no character doing so.
+gsl::index Utf16ForwardUntil(std::u16string_view str, gsl::index position,
+ const std::function<bool(CodePoint)>& predicate);
+
+gsl::index Utf16PreviousWord(std::u16string_view str, gsl::index position,
+ bool* is_space = nullptr);
+gsl::index Utf16NextWord(std::u16string_view str, gsl::index position,
+ bool* is_space = nullptr);
} // namespace cru
diff --git a/include/cru/platform/GraphBase.hpp b/include/cru/platform/GraphBase.hpp
index 186ee9d0..6bf2736f 100644
--- a/include/cru/platform/GraphBase.hpp
+++ b/include/cru/platform/GraphBase.hpp
@@ -1,9 +1,13 @@
#pragma once
#include "cru/common/Base.hpp"
+#include "cru/common/Format.hpp"
+
+#include <fmt/core.h>
#include <cstdint>
#include <limits>
#include <optional>
+#include <string>
#include <utility>
namespace cru::platform {
@@ -14,6 +18,16 @@ struct Point final {
constexpr Point(const float x, const float y) : x(x), y(y) {}
explicit constexpr Point(const Size& size);
+ std::u16string ToDebugString() const {
+ return fmt::format(u"({}, {})", ToUtf16String(x), ToUtf16String(y));
+ }
+
+ constexpr Point& operator+=(const Point& other) {
+ this->x += other.x;
+ this->y += other.y;
+ return *this;
+ }
+
float x = 0;
float y = 0;
};
@@ -46,6 +60,11 @@ struct Size final {
std::numeric_limits<float>::max()};
}
+ std::u16string ToDebugString() const {
+ return fmt::format(u"({}, {})", ToUtf16String(width),
+ ToUtf16String(height));
+ }
+
float width = 0;
float height = 0;
};
@@ -258,7 +277,7 @@ struct TextRange final {
gsl::index GetStart() const { return position; }
gsl::index GetEnd() const { return position + count; }
- void AdjustEnd(gsl::index new_end) { count = new_end - position; }
+ void ChangeEnd(gsl::index new_end) { count = new_end - position; }
TextRange Normalize() const {
auto result = *this;
@@ -297,6 +316,12 @@ struct Color {
(hex >> 24) & mask);
}
+ constexpr Color WithAlpha(std::uint8_t new_alpha) const {
+ auto result = *this;
+ result.alpha = new_alpha;
+ return result;
+ }
+
std::uint8_t red;
std::uint8_t green;
std::uint8_t blue;
diff --git a/include/cru/platform/Matrix.hpp b/include/cru/platform/Matrix.hpp
index e702df90..8ec5faaa 100644
--- a/include/cru/platform/Matrix.hpp
+++ b/include/cru/platform/Matrix.hpp
@@ -50,10 +50,15 @@ struct Matrix {
return Matrix{1.0f, 0.0f, 0.0f, 1.0f, x, y};
}
+ static Matrix Translation(const Point& point) {
+ return Translation(point.x, point.y);
+ }
+
static Matrix Scale(float sx, float sy) {
return Matrix{sx, 0.0f, 0.0f, sy, 0.0f, 0.0f};
}
+ // Clockwise.
static Matrix Rotation(float angle) {
float r = AngleToRadian(angle);
float s = std::sin(r);
diff --git a/include/cru/platform/graph/Base.hpp b/include/cru/platform/graphics/Base.hpp
index 61cfc5ef..e751ebdb 100644
--- a/include/cru/platform/graph/Base.hpp
+++ b/include/cru/platform/graphics/Base.hpp
@@ -5,7 +5,7 @@
#include <memory>
-namespace cru::platform::graph {
+namespace cru::platform::graphics {
// forward declarations
struct IGraphFactory;
struct IBrush;
diff --git a/include/cru/platform/graph/Brush.hpp b/include/cru/platform/graphics/Brush.hpp
index e67384de..10c666b5 100644
--- a/include/cru/platform/graph/Brush.hpp
+++ b/include/cru/platform/graphics/Brush.hpp
@@ -1,7 +1,7 @@
#pragma once
#include "Resource.hpp"
-namespace cru::platform::graph {
+namespace cru::platform::graphics {
struct IBrush : virtual IGraphResource {};
struct ISolidColorBrush : virtual IBrush {
diff --git a/include/cru/platform/graph/Factory.hpp b/include/cru/platform/graphics/Factory.hpp
index b4e68f12..f9018e13 100644
--- a/include/cru/platform/graph/Factory.hpp
+++ b/include/cru/platform/graphics/Factory.hpp
@@ -9,7 +9,7 @@
#include <string>
#include <string_view>
-namespace cru::platform::graph {
+namespace cru::platform::graphics {
// Entry point of the graph module.
struct IGraphFactory : virtual INativeResource {
virtual std::unique_ptr<ISolidColorBrush> CreateSolidColorBrush() = 0;
@@ -21,5 +21,11 @@ struct IGraphFactory : virtual INativeResource {
virtual std::unique_ptr<ITextLayout> CreateTextLayout(
std::shared_ptr<IFont> font, std::u16string text) = 0;
+
+ std::unique_ptr<ISolidColorBrush> CreateSolidColorBrush(const Color& color) {
+ std::unique_ptr<ISolidColorBrush> brush = CreateSolidColorBrush();
+ brush->SetColor(color);
+ return brush;
+ }
};
-} // namespace cru::platform::graph
+} // namespace cru::platform::graphics
diff --git a/include/cru/platform/graph/Font.hpp b/include/cru/platform/graphics/Font.hpp
index 182cc15b..70392a69 100644
--- a/include/cru/platform/graph/Font.hpp
+++ b/include/cru/platform/graphics/Font.hpp
@@ -1,7 +1,7 @@
#pragma once
#include "Resource.hpp"
-namespace cru::platform::graph {
+namespace cru::platform::graphics {
struct IFont : virtual IGraphResource {
virtual float GetFontSize() = 0;
};
diff --git a/include/cru/platform/graph/Geometry.hpp b/include/cru/platform/graphics/Geometry.hpp
index 354efd97..b0ce6ad9 100644
--- a/include/cru/platform/graph/Geometry.hpp
+++ b/include/cru/platform/graphics/Geometry.hpp
@@ -1,7 +1,7 @@
#pragma once
#include "Resource.hpp"
-namespace cru::platform::graph {
+namespace cru::platform::graphics {
struct IGeometry : virtual IGraphResource {
virtual bool FillContains(const Point& point) = 0;
};
diff --git a/include/cru/platform/graph/Painter.hpp b/include/cru/platform/graphics/Painter.hpp
index 27ae420b..f75ea52b 100644
--- a/include/cru/platform/graph/Painter.hpp
+++ b/include/cru/platform/graphics/Painter.hpp
@@ -1,7 +1,7 @@
#pragma once
#include "Resource.hpp"
-namespace cru::platform::graph {
+namespace cru::platform::graphics {
struct IPainter : virtual INativeResource {
virtual Matrix GetTransform() = 0;
@@ -9,6 +9,8 @@ struct IPainter : virtual INativeResource {
virtual void Clear(const Color& color) = 0;
+ virtual void DrawLine(const Point& start, const Point& end, IBrush* brush,
+ float width) = 0;
virtual void StrokeRectangle(const Rect& rectangle, IBrush* brush,
float width) = 0;
virtual void FillRectangle(const Rect& rectangle, IBrush* brush) = 0;
@@ -26,4 +28,4 @@ struct IPainter : virtual INativeResource {
virtual void EndDraw() = 0;
};
-} // namespace cru::platform::graph
+} // namespace cru::platform::graphics
diff --git a/include/cru/platform/graph/Resource.hpp b/include/cru/platform/graphics/Resource.hpp
index 8859360c..a1625ce4 100644
--- a/include/cru/platform/graph/Resource.hpp
+++ b/include/cru/platform/graphics/Resource.hpp
@@ -1,7 +1,7 @@
#pragma once
#include "Base.hpp"
-namespace cru::platform::graph {
+namespace cru::platform::graphics {
struct IGraphFactory;
struct IGraphResource : virtual INativeResource {
diff --git a/include/cru/platform/graph/TextLayout.hpp b/include/cru/platform/graphics/TextLayout.hpp
index a101983f..b363fb77 100644
--- a/include/cru/platform/graph/TextLayout.hpp
+++ b/include/cru/platform/graphics/TextLayout.hpp
@@ -4,7 +4,7 @@
#include <string>
#include <vector>
-namespace cru::platform::graph {
+namespace cru::platform::graphics {
struct ITextLayout : virtual IGraphResource {
virtual std::u16string GetText() = 0;
virtual std::u16string_view GetTextView() = 0;
@@ -16,9 +16,9 @@ struct ITextLayout : virtual IGraphResource {
virtual void SetMaxWidth(float max_width) = 0;
virtual void SetMaxHeight(float max_height) = 0;
- virtual Rect GetTextBounds() = 0;
+ virtual Rect GetTextBounds(bool includingTrailingSpace = false) = 0;
virtual std::vector<Rect> TextRangeRect(const TextRange& text_range) = 0;
virtual Point TextSinglePoint(Index position, bool trailing) = 0;
virtual TextHitTestResult HitTest(const Point& point) = 0;
};
-} // namespace cru::platform::graph
+} // namespace cru::platform::graphics
diff --git a/include/cru/platform/graph/util/Painter.hpp b/include/cru/platform/graphics/util/Painter.hpp
index f9aec027..90457cf4 100644
--- a/include/cru/platform/graph/util/Painter.hpp
+++ b/include/cru/platform/graphics/util/Painter.hpp
@@ -4,14 +4,14 @@
#include <functional>
#include <type_traits>
-namespace cru::platform::graph::util {
+namespace cru::platform::graphics::util {
template <typename Fn>
void WithTransform(IPainter* painter, const Matrix& matrix, const Fn& action) {
static_assert(std::is_invocable_v<decltype(action), IPainter*>,
"Action must can be be invoked with painter.");
const auto old = painter->GetTransform();
- painter->SetTransform(old * matrix);
+ painter->SetTransform(matrix * old);
action(painter);
painter->SetTransform(old);
}
-} // namespace cru::platform::graph::util
+} // namespace cru::platform::graphics::util
diff --git a/include/cru/platform/native/Base.hpp b/include/cru/platform/gui/Base.hpp
index bba7b960..7a9d1889 100644
--- a/include/cru/platform/native/Base.hpp
+++ b/include/cru/platform/gui/Base.hpp
@@ -1,23 +1,18 @@
#pragma once
+#include "Keyboard.hpp"
#include "cru/common/Base.hpp"
#include "cru/common/Bitmask.hpp"
-#include "cru/platform/graph/Base.hpp"
-#include "Keyboard.hpp"
+#include "cru/platform/graphics/Base.hpp"
-namespace cru::platform::native {
+#include "../Resource.hpp"
+
+namespace cru::platform::gui {
struct ICursor;
struct ICursorManager;
struct IUiApplication;
struct INativeWindow;
-struct INativeWindowResolver;
-struct IInputMethodManager;
struct IInputMethodContext;
-struct Dpi {
- float x;
- float y;
-};
-
namespace details {
struct TagMouseButton {};
} // namespace details
@@ -30,11 +25,6 @@ constexpr MouseButton middle{0b10};
constexpr MouseButton right{0b100};
} // namespace mouse_buttons
-enum class SystemCursorType {
- Arrow,
- Hand,
-};
-
struct NativeMouseButtonEventArgs {
MouseButton button;
Point point;
@@ -49,4 +39,4 @@ struct NativeKeyEventArgs {
enum class FocusChangeType { Gain, Lost };
enum class MouseEnterLeaveType { Enter, Leave };
-} // namespace cru::platform::native
+} // namespace cru::platform::gui
diff --git a/include/cru/platform/native/Cursor.hpp b/include/cru/platform/gui/Cursor.hpp
index 6c8f8068..316496a0 100644
--- a/include/cru/platform/native/Cursor.hpp
+++ b/include/cru/platform/gui/Cursor.hpp
@@ -1,10 +1,11 @@
#pragma once
-#include "../Resource.hpp"
#include "Base.hpp"
#include <memory>
-namespace cru::platform::native {
+namespace cru::platform::gui {
+enum class SystemCursorType { Arrow, Hand, IBeam };
+
struct ICursor : virtual INativeResource {};
struct ICursorManager : virtual INativeResource {
@@ -12,4 +13,4 @@ struct ICursorManager : virtual INativeResource {
// TODO: Add method to create cursor.
};
-} // namespace cru::platform::native
+} // namespace cru::platform::gui
diff --git a/include/cru/platform/gui/DebugFlags.hpp b/include/cru/platform/gui/DebugFlags.hpp
new file mode 100644
index 00000000..2b7c7c19
--- /dev/null
+++ b/include/cru/platform/gui/DebugFlags.hpp
@@ -0,0 +1,8 @@
+#pragma once
+
+namespace cru::platform::gui {
+struct DebugFlags {
+ static constexpr int paint = 0;
+ static constexpr int input_method = 0;
+};
+} // namespace cru::platform::gui
diff --git a/include/cru/platform/native/InputMethod.hpp b/include/cru/platform/gui/InputMethod.hpp
index 6f222a43..9d090eab 100644
--- a/include/cru/platform/native/InputMethod.hpp
+++ b/include/cru/platform/gui/InputMethod.hpp
@@ -1,5 +1,4 @@
#pragma once
-#include "../Resource.hpp"
#include "Base.hpp"
#include "cru/common/Event.hpp"
@@ -8,7 +7,7 @@
#include <memory>
#include <vector>
-namespace cru::platform::native {
+namespace cru::platform::gui {
struct CompositionClause {
int start;
int end;
@@ -38,7 +37,8 @@ struct IInputMethodContext : virtual INativeResource {
virtual CompositionText GetCompositionText() = 0;
- // Set the candidate window lefttop. Use this method to prepare typing.
+ // Set the candidate window lefttop. Relative to window lefttop. Use this
+ // method to prepare typing.
virtual void SetCandidateWindowPosition(const Point& point) = 0;
// Triggered when user starts composition.
@@ -52,22 +52,17 @@ struct IInputMethodContext : virtual INativeResource {
virtual IEvent<std::u16string_view>* TextEvent() = 0;
};
-
-struct IInputMethodManager : virtual INativeResource {
- virtual std::unique_ptr<IInputMethodContext> GetContext(
- INativeWindow* window) = 0;
-};
-} // namespace cru::platform::native
+} // namespace cru::platform::gui
template <>
-struct fmt::formatter<cru::platform::native::CompositionText, char16_t>
+struct fmt::formatter<cru::platform::gui::CompositionText, char16_t>
: fmt::formatter<std::u16string_view, char16_t> {
auto parse(fmt::basic_format_parse_context<char16_t>& ctx) {
return fmt::formatter<std::u16string_view, char16_t>::parse(ctx);
}
template <typename FormatContext>
- auto format(const cru::platform::native::CompositionText& ct,
+ auto format(const cru::platform::gui::CompositionText& ct,
FormatContext& ctx) {
auto output = ctx.out();
output = format_to(output, u"text: {}\n", ct.text);
diff --git a/include/cru/platform/native/Keyboard.hpp b/include/cru/platform/gui/Keyboard.hpp
index 83c61bcc..6c29239b 100644
--- a/include/cru/platform/native/Keyboard.hpp
+++ b/include/cru/platform/gui/Keyboard.hpp
@@ -1,7 +1,10 @@
#pragma once
#include "cru/common/Bitmask.hpp"
-namespace cru::platform::native {
+#include <string>
+#include <string_view>
+
+namespace cru::platform::gui {
// Because of the complexity of keyboard layout, I only add code in US keyboard
// layout, the most widely used layout in China. We should try to make it easy
// to add new keyboard layout.
@@ -113,8 +116,13 @@ struct TagKeyModifier {};
using KeyModifier = Bitmask<details::TagKeyModifier>;
struct KeyModifiers {
+ static constexpr KeyModifier none{0};
static constexpr KeyModifier shift{0b1};
static constexpr KeyModifier ctrl{0b10};
static constexpr KeyModifier alt{0b100};
};
-} // namespace cru::platform::native
+
+std::u16string_view ToString(KeyCode key_code);
+std::u16string ToString(KeyModifier key_modifier,
+ std::u16string_view separator = u"+");
+} // namespace cru::platform::gui
diff --git a/include/cru/platform/gui/UiApplication.hpp b/include/cru/platform/gui/UiApplication.hpp
new file mode 100644
index 00000000..5a5b0b13
--- /dev/null
+++ b/include/cru/platform/gui/UiApplication.hpp
@@ -0,0 +1,135 @@
+#pragma once
+#include "Base.hpp"
+
+#include "cru/common/Bitmask.hpp"
+
+#include <chrono>
+#include <functional>
+#include <memory>
+#include <vector>
+
+namespace cru::platform::gui {
+namespace details {
+struct CreateWindowFlagTag;
+}
+
+using CreateWindowFlag = Bitmask<details::CreateWindowFlagTag>;
+
+struct CreateWindowFlags {
+ static constexpr CreateWindowFlag NoCaptionAndBorder{0b1};
+};
+
+// 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<void()> handler) = 0;
+
+ // Timer id should always be positive (not 0) and never the same. So it's ok
+ // to use negative value (or 0) to represent no timer.
+ virtual long long SetImmediate(std::function<void()> action) = 0;
+ virtual long long SetTimeout(std::chrono::milliseconds milliseconds,
+ std::function<void()> action) = 0;
+ virtual long long SetInterval(std::chrono::milliseconds milliseconds,
+ std::function<void()> action) = 0;
+ // Implementation should guarantee calls on timer id already canceled have no
+ // effects and do not crash. Also canceling negative id or 0 should always
+ // result in no-op.
+ virtual void CancelTimer(long long id) = 0;
+
+ virtual std::vector<INativeWindow*> GetAllWindow() = 0;
+
+ INativeWindow* CreateWindow(INativeWindow* parent) {
+ return this->CreateWindow(parent, CreateWindowFlag(0));
+ };
+ virtual INativeWindow* CreateWindow(INativeWindow* parent,
+ CreateWindowFlag flags) = 0;
+
+ virtual cru::platform::graphics::IGraphFactory* GetGraphFactory() = 0;
+
+ virtual ICursorManager* GetCursorManager() = 0;
+};
+
+class TimerAutoCanceler {
+ public:
+ TimerAutoCanceler() : id_(0) {}
+ explicit TimerAutoCanceler(long long id) : id_(id) {}
+
+ CRU_DELETE_COPY(TimerAutoCanceler)
+
+ TimerAutoCanceler(TimerAutoCanceler&& other) : id_(other.id_) {
+ other.id_ = 0;
+ }
+
+ TimerAutoCanceler& operator=(TimerAutoCanceler&& other) {
+ if (&other == this) {
+ return *this;
+ }
+ Reset(other.id_);
+ other.id_ = 0;
+ return *this;
+ }
+
+ TimerAutoCanceler& operator=(long long other) {
+ return this->operator=(TimerAutoCanceler(other));
+ }
+
+ ~TimerAutoCanceler() { Reset(); }
+
+ long long Release() {
+ auto temp = id_;
+ id_ = 0;
+ return temp;
+ }
+
+ void Reset(long long id = 0) {
+ if (id_ > 0) IUiApplication::GetInstance()->CancelTimer(id_);
+ id_ = id;
+ }
+
+ explicit operator bool() const { return id_; }
+
+ private:
+ long long id_;
+};
+
+class TimerListAutoCanceler {
+ public:
+ TimerListAutoCanceler() = default;
+ CRU_DELETE_COPY(TimerListAutoCanceler)
+ CRU_DEFAULT_MOVE(TimerListAutoCanceler)
+ ~TimerListAutoCanceler() = default;
+
+ TimerListAutoCanceler& operator+=(long long id) {
+ list_.push_back(TimerAutoCanceler(id));
+ return *this;
+ }
+
+ void Clear() { list_.clear(); }
+
+ bool IsEmpty() const { return list_.empty(); }
+
+ private:
+ std::vector<TimerAutoCanceler> list_;
+};
+
+// Bootstrap from this.
+std::unique_ptr<IUiApplication> CreateUiApplication();
+} // namespace cru::platform::gui
diff --git a/include/cru/platform/native/Window.hpp b/include/cru/platform/gui/Window.hpp
index 1fcac1fc..26d1a476 100644
--- a/include/cru/platform/native/Window.hpp
+++ b/include/cru/platform/gui/Window.hpp
@@ -1,21 +1,14 @@
#pragma once
-#include "../Resource.hpp"
#include "Base.hpp"
+
#include "cru/common/Event.hpp"
#include <string_view>
-namespace cru::platform::native {
+namespace cru::platform::gui {
// Represents a native window, which exposes some low-level events and
// operations.
-//
-// Usually you save an INativeWindowResolver after creating a window. Because
-// window may be destroyed when user do certain actions like click the close
-// button. Then the INativeWindow instance is destroyed and
-// INativeWindowResolver::Resolve return nullptr to indicate the fact.
struct INativeWindow : virtual INativeResource {
- virtual std::shared_ptr<INativeWindowResolver> GetResolver() = 0;
-
virtual void Close() = 0;
virtual INativeWindow* GetParent() = 0;
@@ -45,8 +38,9 @@ struct INativeWindow : virtual INativeResource {
virtual void RequestRepaint() = 0;
// Remember to call EndDraw on return value and destroy it.
- virtual std::unique_ptr<graph::IPainter> BeginPaint() = 0;
+ virtual std::unique_ptr<graphics::IPainter> BeginPaint() = 0;
+ // Don't use this instance after receive this event.
virtual IEvent<std::nullptr_t>* DestroyEvent() = 0;
virtual IEvent<std::nullptr_t>* PaintEvent() = 0;
virtual IEvent<Size>* ResizeEvent() = 0;
@@ -57,11 +51,7 @@ struct INativeWindow : virtual INativeResource {
virtual IEvent<NativeMouseButtonEventArgs>* MouseUpEvent() = 0;
virtual IEvent<NativeKeyEventArgs>* KeyDownEvent() = 0;
virtual IEvent<NativeKeyEventArgs>* KeyUpEvent() = 0;
-};
-// See INativeWindow for more info.
-struct INativeWindowResolver : virtual INativeResource {
- // Think twice before you save the return value.
- virtual INativeWindow* Resolve() = 0;
+ virtual IInputMethodContext* GetInputMethodContext() = 0;
};
-} // namespace cru::platform::native
+} // namespace cru::platform::gui
diff --git a/include/cru/platform/native/UiApplication.hpp b/include/cru/platform/native/UiApplication.hpp
deleted file mode 100644
index 1aa4df57..00000000
--- a/include/cru/platform/native/UiApplication.hpp
+++ /dev/null
@@ -1,58 +0,0 @@
-#pragma once
-#include "../Resource.hpp"
-#include "Base.hpp"
-
-#include <chrono>
-#include <functional>
-#include <memory>
-#include <vector>
-
-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<void()> handler) = 0;
-
- virtual void InvokeLater(std::function<void()> 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<void()> action) = 0;
- virtual long long SetInterval(std::chrono::milliseconds milliseconds,
- std::function<void()> 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<INativeWindow*> GetAllWindow() = 0;
- virtual std::shared_ptr<INativeWindowResolver> 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<IUiApplication> CreateUiApplication();
-} // namespace cru::platform::native
diff --git a/include/cru/ui/Base.hpp b/include/cru/ui/Base.hpp
index 6be359ab..fbdfec77 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/platform/graphics/Base.hpp"
+#include "cru/platform/gui/Base.hpp"
#include <functional>
#include <memory>
@@ -19,23 +19,35 @@ using cru::platform::RoundedRect;
using cru::platform::Size;
using cru::platform::TextRange;
using cru::platform::Thickness;
-using cru::platform::native::MouseButton;
+using cru::platform::gui::MouseButton;
-namespace mouse_buttons = cru::platform::native::mouse_buttons;
+namespace mouse_buttons = cru::platform::gui::mouse_buttons;
namespace colors = cru::platform::colors;
//-------------------- region: forward declaration --------------------
+
+namespace controls {
class Window;
class Control;
-class ClickDetector;
-class UiHost;
+} // namespace controls
+
+namespace host {
+class WindowHost;
+}
namespace render {
class RenderObject;
}
+namespace style {
+class StyleRuleSet;
+class StyleRuleSetBind;
+} // namespace style
+
//-------------------- region: basic types --------------------
+enum class Direction { Horizontal, Vertical };
+
namespace internal {
constexpr int align_start = 0;
constexpr int align_end = align_start + 1;
@@ -82,28 +94,20 @@ inline bool operator!=(const CornerRadius& left, const CornerRadius& right) {
return !(left == right);
}
-struct BorderStyle {
- std::shared_ptr<platform::graph::IBrush> border_brush;
- Thickness border_thickness;
- CornerRadius border_radius;
- std::shared_ptr<platform::graph::IBrush> foreground_brush;
- std::shared_ptr<platform::graph::IBrush> background_brush;
-};
-
class CanvasPaintEventArgs {
public:
- CanvasPaintEventArgs(platform::graph::IPainter* painter,
+ CanvasPaintEventArgs(platform::graphics::IPainter* painter,
const Size& paint_size)
: painter_(painter), paint_size_(paint_size) {}
CRU_DEFAULT_COPY(CanvasPaintEventArgs)
CRU_DEFAULT_MOVE(CanvasPaintEventArgs)
~CanvasPaintEventArgs() = default;
- platform::graph::IPainter* GetPainter() const { return painter_; }
+ platform::graphics::IPainter* GetPainter() const { return painter_; }
Size GetPaintSize() const { return paint_size_; }
private:
- platform::graph::IPainter* painter_;
+ platform::graphics::IPainter* painter_;
Size paint_size_;
};
diff --git a/include/cru/ui/ContentControl.hpp b/include/cru/ui/ContentControl.hpp
deleted file mode 100644
index 19f13a1d..00000000
--- a/include/cru/ui/ContentControl.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<Control*>& GetChildren() const override final {
- return child_vector_;
- }
- Control* GetChild() const { return child_; }
- void SetChild(Control* child);
-
- protected:
- virtual void OnChildChanged(Control* old_child, Control* new_child);
-
- private:
- std::vector<Control*> child_vector_;
- Control*& child_;
-};
-} // namespace cru::ui
diff --git a/include/cru/ui/DebugFlags.hpp b/include/cru/ui/DebugFlags.hpp
new file mode 100644
index 00000000..51482135
--- /dev/null
+++ b/include/cru/ui/DebugFlags.hpp
@@ -0,0 +1,9 @@
+#pragma once
+
+namespace cru::ui::debug_flags {
+constexpr bool routed_event = false;
+constexpr bool layout = false;
+constexpr bool shortcut = false;
+constexpr bool text_service = false;
+constexpr int click_detector = 0;
+} // namespace cru::ui::debug_flags
diff --git a/include/cru/ui/LayoutControl.hpp b/include/cru/ui/LayoutControl.hpp
deleted file mode 100644
index 7997b37e..00000000
--- a/include/cru/ui/LayoutControl.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<Control*>& 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<Control*> children_;
-};
-} // namespace cru::ui
diff --git a/include/cru/ui/UiHost.hpp b/include/cru/ui/UiHost.hpp
deleted file mode 100644
index b1658ef6..00000000
--- a/include/cru/ui/UiHost.hpp
+++ /dev/null
@@ -1,172 +0,0 @@
-#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<UiHost> {
- CRU_DEFINE_CLASS_LOG_TAG(u"cru::ui::UiHost")
-
- 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<AfterLayoutEventArgs>* 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<platform::native::INativeWindowResolver>
- 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<AfterLayoutEventArgs> after_layout_event_;
-
- std::shared_ptr<platform::native::INativeWindowResolver>
- 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<EventRevokerGuard> event_revoker_guards_;
-
- Window* window_control_;
- std::unique_ptr<render::WindowRenderObject> 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
index e6facdbd..6c0d9500 100644
--- a/include/cru/ui/UiManager.hpp
+++ b/include/cru/ui/UiManager.hpp
@@ -2,15 +2,22 @@
#include "Base.hpp"
#include "controls/Base.hpp"
+#include "style/StyleRuleSet.hpp"
+
+#include <memory>
+#include <string>
namespace cru::ui {
struct ThemeResources {
- std::shared_ptr<platform::graph::IFont> default_font;
- std::shared_ptr<platform::graph::IBrush> text_brush;
- std::shared_ptr<platform::graph::IBrush> text_selection_brush;
- std::shared_ptr<platform::graph::IBrush> caret_brush;
- controls::ButtonStyle button_style;
- controls::TextBoxBorderStyle text_box_border_style;
+ std::u16string default_font_family;
+ std::shared_ptr<platform::graphics::IFont> default_font;
+ std::shared_ptr<platform::graphics::IBrush> text_brush;
+ std::shared_ptr<platform::graphics::IBrush> text_selection_brush;
+ std::shared_ptr<platform::graphics::IBrush> caret_brush;
+ style::StyleRuleSet button_style;
+ style::StyleRuleSet text_box_style;
+
+ style::StyleRuleSet menu_item_style;
};
class UiManager : public Object {
diff --git a/include/cru/ui/Window.hpp b/include/cru/ui/Window.hpp
deleted file mode 100644
index 450ea97b..00000000
--- a/include/cru/ui/Window.hpp
+++ /dev/null
@@ -1,40 +0,0 @@
-#pragma once
-#include "ContentControl.hpp"
-
-namespace cru::ui {
-class Window final : public ContentControl {
- friend UiHost;
-
- public:
- static constexpr std::u16string_view control_type = u"Window";
-
- public:
- static Window* CreateOverlapped();
-
- private:
- struct tag_overlapped_constructor {};
-
- explicit Window(tag_overlapped_constructor);
-
- public:
- Window(const Window& other) = delete;
- Window(Window&& other) = delete;
- Window& operator=(const Window& other) = delete;
- Window& operator=(Window&& other) = delete;
- ~Window() override;
-
- public:
- std::u16string_view GetControlType() const final;
-
- render::RenderObject* GetRenderObject() const override;
-
- protected:
- void OnChildChanged(Control* old_child, Control* new_child) override;
-
- private:
- std::unique_ptr<UiHost> managed_ui_host_;
-
- // UiHost is responsible to take care of lifetime of this.
- render::WindowRenderObject* render_object_;
-};
-} // namespace cru::ui
diff --git a/include/cru/ui/components/Component.hpp b/include/cru/ui/components/Component.hpp
new file mode 100644
index 00000000..0dfc587b
--- /dev/null
+++ b/include/cru/ui/components/Component.hpp
@@ -0,0 +1,19 @@
+#pragma once
+#include "../Base.hpp"
+
+namespace cru::ui::components {
+// In destructor, component should check all owned controls whether it is
+// attached to window, if not, destroy them, otherwise it is host's duty to
+// destroy them.
+class Component : public Object {
+ public:
+ Component() = default;
+
+ CRU_DELETE_COPY(Component)
+ CRU_DELETE_MOVE(Component)
+
+ ~Component() = default;
+
+ virtual controls::Control* GetRootControl() = 0;
+};
+} // namespace cru::ui::components
diff --git a/include/cru/ui/components/Menu.hpp b/include/cru/ui/components/Menu.hpp
new file mode 100644
index 00000000..dedf2bd5
--- /dev/null
+++ b/include/cru/ui/components/Menu.hpp
@@ -0,0 +1,60 @@
+#pragma once
+#include "Component.hpp"
+#include "cru/common/Base.hpp"
+#include "cru/ui/controls/Button.hpp"
+#include "cru/ui/controls/Control.hpp"
+#include "cru/ui/controls/FlexLayout.hpp"
+#include "cru/ui/controls/TextBlock.hpp"
+
+#include <string>
+#include <vector>
+
+namespace cru::ui::components {
+class MenuItem : public Component {
+ public:
+ MenuItem();
+ explicit MenuItem(std::u16string text);
+
+ CRU_DELETE_COPY(MenuItem)
+ CRU_DELETE_MOVE(MenuItem)
+
+ ~MenuItem();
+
+ public:
+ controls::Control* GetRootControl() override { return container_; }
+
+ void SetText(std::u16string text);
+
+ private:
+ controls::Button* container_;
+ controls::TextBlock* text_;
+};
+
+class Menu : public Component {
+ public:
+ Menu();
+
+ CRU_DELETE_COPY(Menu)
+ CRU_DELETE_MOVE(Menu)
+
+ ~Menu();
+
+ public:
+ gsl::index GetItemCount() const {
+ return static_cast<gsl::index>(items_.size());
+ }
+
+ void AddItem(Component* component) { AddItem(component, GetItemCount()); }
+ void AddItem(Component* component, gsl::index index);
+ Component* RemoveItem(gsl::index index);
+
+ void AddTextItem(std::u16string text) {
+ AddTextItem(std::move(text), GetItemCount());
+ }
+ void AddTextItem(std::u16string text, gsl::index index);
+
+ private:
+ controls::FlexLayout* container_;
+ std::vector<Component*> items_;
+};
+} // namespace cru::ui::components
diff --git a/include/cru/ui/controls/Base.hpp b/include/cru/ui/controls/Base.hpp
index b550601b..7c85cdb2 100644
--- a/include/cru/ui/controls/Base.hpp
+++ b/include/cru/ui/controls/Base.hpp
@@ -1,24 +1,4 @@
#pragma once
#include "../Base.hpp"
-namespace cru::ui::controls {
-using ButtonStateStyle = BorderStyle;
-
-struct ButtonStyle {
- // corresponds to ClickState::None
- ButtonStateStyle normal;
- // corresponds to ClickState::Hover
- ButtonStateStyle hover;
- // corresponds to ClickState::Press
- ButtonStateStyle press;
- // corresponds to ClickState::PressInactive
- ButtonStateStyle press_cancel;
-};
-
-struct TextBoxBorderStyle {
- BorderStyle normal;
- BorderStyle hover;
- BorderStyle focus;
- BorderStyle focus_hover;
-};
-} // namespace cru::ui::controls
+namespace cru::ui::controls {} // namespace cru::ui::controls
diff --git a/include/cru/ui/controls/Button.hpp b/include/cru/ui/controls/Button.hpp
index a4f727d6..1c9b1216 100644
--- a/include/cru/ui/controls/Button.hpp
+++ b/include/cru/ui/controls/Button.hpp
@@ -1,11 +1,16 @@
#pragma once
-#include "../ContentControl.hpp"
-#include "Base.hpp"
+#include "ContentControl.hpp"
-#include "../ClickDetector.hpp"
+#include "../helper/ClickDetector.hpp"
+#include "IBorderControl.hpp"
+#include "IClickableControl.hpp"
+#include "cru/common/Event.hpp"
+#include "cru/ui/style/ApplyBorderStyleInfo.hpp"
namespace cru::ui::controls {
-class Button : public ContentControl {
+class Button : public ContentControl,
+ public virtual IClickableControl,
+ public virtual IBorderControl {
public:
static constexpr std::u16string_view control_type = u"Button";
@@ -26,17 +31,19 @@ class Button : public ContentControl {
render::RenderObject* GetRenderObject() const override;
public:
- const ButtonStyle& GetStyle() const { return style_; }
- void SetStyle(ButtonStyle style);
+ helper::ClickState GetClickState() override {
+ return click_detector_.GetState();
+ }
- protected:
- void OnChildChanged(Control* old_child, Control* new_child) override;
+ IEvent<helper::ClickState>* ClickStateChangeEvent() override {
+ return click_detector_.StateChangeEvent();
+ }
+
+ void ApplyBorderStyle(const style::ApplyBorderStyleInfo& style) override;
private:
std::unique_ptr<render::BorderRenderObject> render_object_{};
- ButtonStyle style_;
-
- ClickDetector click_detector_;
+ helper::ClickDetector click_detector_;
};
} // namespace cru::ui::controls
diff --git a/include/cru/ui/controls/Container.hpp b/include/cru/ui/controls/Container.hpp
index 304d402c..18958837 100644
--- a/include/cru/ui/controls/Container.hpp
+++ b/include/cru/ui/controls/Container.hpp
@@ -1,5 +1,5 @@
#pragma once
-#include "../ContentControl.hpp"
+#include "ContentControl.hpp"
namespace cru::ui::controls {
class Container : public ContentControl {
@@ -19,9 +19,6 @@ class Container : public ContentControl {
render::RenderObject* GetRenderObject() const override;
- protected:
- void OnChildChanged(Control* old_child, Control* new_child) override;
-
private:
std::unique_ptr<render::BorderRenderObject> render_object_;
};
diff --git a/include/cru/ui/controls/ContentControl.hpp b/include/cru/ui/controls/ContentControl.hpp
new file mode 100644
index 00000000..1bdaf7e4
--- /dev/null
+++ b/include/cru/ui/controls/ContentControl.hpp
@@ -0,0 +1,38 @@
+#pragma once
+#include "Control.hpp"
+
+#include "cru/ui/render/RenderObject.hpp"
+
+namespace cru::ui::controls {
+class ContentControl : public Control {
+ protected:
+ ContentControl() = default;
+
+ public:
+ ContentControl(const ContentControl& other) = delete;
+ ContentControl(ContentControl&& other) = delete;
+ ContentControl& operator=(const ContentControl& other) = delete;
+ ContentControl& operator=(ContentControl&& other) = delete;
+ ~ContentControl() override = default;
+
+ Control* GetChild() const;
+ void SetChild(Control* child);
+
+ protected:
+ virtual void OnChildChanged(Control* old_child, Control* new_child);
+
+ render::RenderObject* GetContainerRenderObject() const {
+ return container_render_object_;
+ }
+ void SetContainerRenderObject(render::RenderObject* ro) {
+ container_render_object_ = ro;
+ }
+
+ private:
+ using Control::AddChild;
+ using Control::RemoveChild;
+
+ private:
+ render::RenderObject* container_render_object_ = nullptr;
+};
+} // namespace cru::ui::controls
diff --git a/include/cru/ui/Control.hpp b/include/cru/ui/controls/Control.hpp
index bd86bc2f..341b6ef2 100644
--- a/include/cru/ui/Control.hpp
+++ b/include/cru/ui/controls/Control.hpp
@@ -1,15 +1,15 @@
#pragma once
#include "Base.hpp"
+#include "../events/UiEvent.hpp"
+#include "../render/Base.hpp"
#include "cru/common/Event.hpp"
-#include "render/Base.hpp"
-#include "UiEvent.hpp"
#include <string_view>
-namespace cru::ui {
+namespace cru::ui::controls {
class Control : public Object {
- friend UiHost;
+ friend host::WindowHost;
protected:
Control();
@@ -19,39 +19,31 @@ class Control : public Object {
Control(Control&& other) = delete;
Control& operator=(const Control& other) = delete;
Control& operator=(Control&& other) = delete;
- ~Control() override = default;
+ ~Control() override;
public:
virtual std::u16string_view GetControlType() const = 0;
//*************** region: tree ***************
public:
- // Get the ui host if attached, otherwise, return nullptr.
- UiHost* GetUiHost() const { return ui_host_; }
+ host::WindowHost* GetWindowHost() const;
Control* GetParent() const { return parent_; }
- virtual const std::vector<Control*>& GetChildren() const = 0;
+ const std::vector<Control*>& GetChildren() const { return children_; }
// Traverse the tree rooted the control including itself.
void TraverseDescendants(const std::function<void(Control*)>& predicate);
- void _SetParent(Control* parent);
- void _SetDescendantUiHost(UiHost* host);
-
- private:
- static void _TraverseDescendants(
- Control* control, const std::function<void(Control*)>& predicate);
-
public:
virtual render::RenderObject* GetRenderObject() const = 0;
//*************** region: focus ***************
public:
- bool RequestFocus();
-
bool HasFocus();
+ void SetFocus();
+
//*************** region: mouse ***************
public:
bool IsMouseOver() const { return is_mouse_over_; }
@@ -66,13 +58,16 @@ class Control : public Object {
// Cursor is inherited from parent recursively if not set.
public:
// null for not set
- std::shared_ptr<platform::native::ICursor> GetCursor();
+ std::shared_ptr<platform::gui::ICursor> GetCursor();
// will not return nullptr
- std::shared_ptr<platform::native::ICursor> GetInheritedCursor();
+ std::shared_ptr<platform::gui::ICursor> GetInheritedCursor();
// null to unset
- void SetCursor(std::shared_ptr<platform::native::ICursor> cursor);
+ void SetCursor(std::shared_ptr<platform::gui::ICursor> cursor);
+
+ public:
+ style::StyleRuleSet* GetStyleRuleSet();
//*************** region: events ***************
public:
@@ -134,19 +129,29 @@ class Control : public Object {
//*************** region: tree ***************
protected:
+ void AddChild(Control* control, Index position);
+ void RemoveChild(Index position);
+ virtual void OnAddChild(Control* child, Index position);
+ virtual void OnRemoveChild(Control* child, Index position);
virtual void OnParentChanged(Control* old_parent, Control* new_parent);
- virtual void OnAttachToHost(UiHost* host);
- virtual void OnDetachFromHost(UiHost* host);
+ virtual void OnAttachToHost(host::WindowHost* host);
+ virtual void OnDetachFromHost(host::WindowHost* host);
+ protected:
virtual void OnMouseHoverChange(bool newHover) { CRU_UNUSED(newHover) }
private:
- UiHost* ui_host_ = nullptr;
Control* parent_ = nullptr;
+ std::vector<Control*> children_;
+
+ host::WindowHost* window_host_ = nullptr;
private:
bool is_mouse_over_ = false;
- std::shared_ptr<platform::native::ICursor> cursor_ = nullptr;
+ std::shared_ptr<platform::gui::ICursor> cursor_ = nullptr;
+
+ std::unique_ptr<style::StyleRuleSet> style_rule_set_;
+ std::unique_ptr<style::StyleRuleSetBind> style_rule_set_bind_;
};
-} // namespace cru::ui
+} // namespace cru::ui::controls
diff --git a/include/cru/ui/controls/FlexLayout.hpp b/include/cru/ui/controls/FlexLayout.hpp
index 87162569..4f6abfdb 100644
--- a/include/cru/ui/controls/FlexLayout.hpp
+++ b/include/cru/ui/controls/FlexLayout.hpp
@@ -1,5 +1,5 @@
#pragma once
-#include "../LayoutControl.hpp"
+#include "LayoutControl.hpp"
namespace cru::ui::controls {
class FlexLayout : public LayoutControl {
@@ -28,13 +28,12 @@ class FlexLayout : public LayoutControl {
FlexDirection GetFlexDirection() const;
void SetFlexDirection(FlexDirection direction);
+ FlexCrossAlignment GetItemCrossAlign() const;
+ void SetItemCrossAlign(FlexCrossAlignment alignment);
+
FlexChildLayoutData GetChildLayoutData(Control* control);
void SetChildLayoutData(Control* control, FlexChildLayoutData data);
- protected:
- void OnAddChild(Control* child, Index position) override;
- void OnRemoveChild(Control* child, Index position) override;
-
private:
std::shared_ptr<render::FlexLayoutRenderObject> render_object_;
};
diff --git a/include/cru/ui/controls/IBorderControl.hpp b/include/cru/ui/controls/IBorderControl.hpp
new file mode 100644
index 00000000..817305ef
--- /dev/null
+++ b/include/cru/ui/controls/IBorderControl.hpp
@@ -0,0 +1,10 @@
+#pragma once
+#include "../style/ApplyBorderStyleInfo.hpp"
+#include "Base.hpp"
+#include "cru/common/Base.hpp"
+
+namespace cru::ui::controls {
+struct IBorderControl : virtual Interface {
+ virtual void ApplyBorderStyle(const style::ApplyBorderStyleInfo& style) = 0;
+};
+} // namespace cru::ui::controls
diff --git a/include/cru/ui/controls/IClickableControl.hpp b/include/cru/ui/controls/IClickableControl.hpp
new file mode 100644
index 00000000..aa7f13ab
--- /dev/null
+++ b/include/cru/ui/controls/IClickableControl.hpp
@@ -0,0 +1,12 @@
+#pragma once
+#include "Base.hpp"
+
+#include "cru/common/Event.hpp"
+#include "cru/ui/helper/ClickDetector.hpp"
+
+namespace cru::ui::controls {
+struct IClickableControl : virtual Interface {
+ virtual helper::ClickState GetClickState() = 0;
+ virtual IEvent<helper::ClickState>* ClickStateChangeEvent() = 0;
+};
+} // namespace cru::ui::controls
diff --git a/include/cru/ui/controls/LayoutControl.hpp b/include/cru/ui/controls/LayoutControl.hpp
new file mode 100644
index 00000000..106dd94d
--- /dev/null
+++ b/include/cru/ui/controls/LayoutControl.hpp
@@ -0,0 +1,35 @@
+#pragma once
+#include "Control.hpp"
+
+namespace cru::ui::controls {
+class LayoutControl : public Control {
+ protected:
+ LayoutControl() = default;
+ explicit LayoutControl(render::RenderObject* container_render_object)
+ : container_render_object_(container_render_object) {}
+
+ public:
+ LayoutControl(const LayoutControl& other) = delete;
+ LayoutControl(LayoutControl&& other) = delete;
+ LayoutControl& operator=(const LayoutControl& other) = delete;
+ LayoutControl& operator=(LayoutControl&& other) = delete;
+ ~LayoutControl() override = default;
+
+ using Control::AddChild;
+ using Control::RemoveChild;
+
+ protected:
+ // If container render object is not null. Render object of added or removed
+ // child control will automatically sync to the container render object.
+ render::RenderObject* GetContainerRenderObject() const;
+ void SetContainerRenderObject(render::RenderObject* ro) {
+ container_render_object_ = ro;
+ }
+
+ void OnAddChild(Control* child, Index position) override;
+ void OnRemoveChild(Control* child, Index position) override;
+
+ private:
+ render::RenderObject* container_render_object_ = nullptr;
+};
+} // namespace cru::ui::controls
diff --git a/include/cru/ui/NoChildControl.hpp b/include/cru/ui/controls/NoChildControl.hpp
index 1a31ae7e..562137f1 100644
--- a/include/cru/ui/NoChildControl.hpp
+++ b/include/cru/ui/controls/NoChildControl.hpp
@@ -1,11 +1,8 @@
#pragma once
#include "Control.hpp"
-namespace cru::ui {
+namespace cru::ui::controls {
class NoChildControl : public Control {
- private:
- static const std::vector<Control*> empty_control_vector;
-
protected:
NoChildControl() = default;
@@ -16,9 +13,8 @@ class NoChildControl : public Control {
NoChildControl& operator=(NoChildControl&& other) = delete;
~NoChildControl() override = default;
- protected:
- const std::vector<Control*>& GetChildren() const override final {
- return empty_control_vector;
- }
+ private:
+ using Control::AddChild;
+ using Control::RemoveChild;
};
} // namespace cru::ui
diff --git a/include/cru/ui/controls/Popup.hpp b/include/cru/ui/controls/Popup.hpp
new file mode 100644
index 00000000..d76e1211
--- /dev/null
+++ b/include/cru/ui/controls/Popup.hpp
@@ -0,0 +1,24 @@
+#pragma once
+#include "RootControl.hpp"
+
+#include "cru/ui/Base.hpp"
+#include "cru/platform/gui/Base.hpp"
+
+#include <memory>
+
+namespace cru::ui::controls {
+class Popup : public RootControl {
+ public:
+ explicit Popup(Control* attached_control = nullptr);
+
+ CRU_DELETE_COPY(Popup)
+ CRU_DELETE_MOVE(Popup)
+
+ ~Popup() override;
+
+ protected:
+ gsl::not_null<platform::gui::INativeWindow*> CreateNativeWindow(
+ gsl::not_null<host::WindowHost*> host,
+ platform::gui::INativeWindow* parent) override;
+};
+} // namespace cru::ui::controls
diff --git a/include/cru/ui/controls/RootControl.hpp b/include/cru/ui/controls/RootControl.hpp
new file mode 100644
index 00000000..53e69e7c
--- /dev/null
+++ b/include/cru/ui/controls/RootControl.hpp
@@ -0,0 +1,45 @@
+#pragma once
+#include "LayoutControl.hpp"
+
+#include "cru/common/Base.hpp"
+#include "cru/platform/gui/Base.hpp"
+#include "cru/ui/Base.hpp"
+
+namespace cru::ui::controls {
+class RootControl : public LayoutControl {
+ protected:
+ explicit RootControl(Control* attached_control);
+
+ public:
+ CRU_DELETE_COPY(RootControl)
+ CRU_DELETE_MOVE(RootControl)
+ ~RootControl() override;
+
+ public:
+ render::RenderObject* GetRenderObject() const override;
+
+ void EnsureWindowCreated();
+
+ // If create is false and native window is not create, it will not be created
+ // and shown.
+ void Show(bool create = true);
+
+ Rect GetRect();
+ void SetRect(const Rect& rect);
+
+ protected:
+ virtual gsl::not_null<platform::gui::INativeWindow*> CreateNativeWindow(
+ gsl::not_null<host::WindowHost*> host,
+ platform::gui::INativeWindow* parent) = 0;
+
+ private:
+ platform::gui::INativeWindow* GetNativeWindow(bool create);
+
+ private:
+ std::unique_ptr<host::WindowHost> window_host_;
+
+ std::unique_ptr<render::StackLayoutRenderObject> render_object_;
+
+ Control* attached_control_;
+};
+} // namespace cru::ui::controls
diff --git a/include/cru/ui/controls/StackLayout.hpp b/include/cru/ui/controls/StackLayout.hpp
index c0b95044..aa9440c2 100644
--- a/include/cru/ui/controls/StackLayout.hpp
+++ b/include/cru/ui/controls/StackLayout.hpp
@@ -1,5 +1,5 @@
#pragma once
-#include "../LayoutControl.hpp"
+#include "LayoutControl.hpp"
namespace cru::ui::controls {
class StackLayout : public LayoutControl {
@@ -21,10 +21,6 @@ class StackLayout : public LayoutControl {
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::StackLayoutRenderObject> render_object_;
};
diff --git a/include/cru/ui/controls/TextBlock.hpp b/include/cru/ui/controls/TextBlock.hpp
index 8a9a3bff..be31816c 100644
--- a/include/cru/ui/controls/TextBlock.hpp
+++ b/include/cru/ui/controls/TextBlock.hpp
@@ -1,15 +1,15 @@
#pragma once
-#include "../NoChildControl.hpp"
+#include "NoChildControl.hpp"
-namespace cru::ui::controls {
-template <typename TControl>
-class TextControlService;
+#include "TextHostControlService.hpp"
-class TextBlock : public NoChildControl {
+namespace cru::ui::controls {
+class TextBlock : public NoChildControl, public virtual ITextHostControl {
public:
static constexpr std::u16string_view control_type = u"TextBlock";
- static TextBlock* Create() { return new TextBlock(); }
+ static TextBlock* Create();
+ static TextBlock* Create(std::u16string text, bool selectable = false);
protected:
TextBlock();
@@ -28,12 +28,17 @@ class TextBlock : public NoChildControl {
std::u16string GetText() const;
void SetText(std::u16string text);
- gsl::not_null<render::TextRenderObject*> GetTextRenderObject();
- render::ScrollRenderObject* GetScrollRenderObject() { return nullptr; }
+ bool IsSelectable() const;
+ void SetSelectable(bool value);
+
+ gsl::not_null<render::TextRenderObject*> GetTextRenderObject() override;
+ render::ScrollRenderObject* GetScrollRenderObject() override {
+ return nullptr;
+ }
private:
std::unique_ptr<render::TextRenderObject> text_render_object_;
- std::unique_ptr<TextControlService<TextBlock>> service_;
+ std::unique_ptr<TextHostControlService> service_;
};
} // namespace cru::ui::controls
diff --git a/include/cru/ui/controls/TextBox.hpp b/include/cru/ui/controls/TextBox.hpp
index 5976f6da..5693b315 100644
--- a/include/cru/ui/controls/TextBox.hpp
+++ b/include/cru/ui/controls/TextBox.hpp
@@ -1,6 +1,8 @@
#pragma once
-#include "../NoChildControl.hpp"
-#include "Base.hpp"
+#include "NoChildControl.hpp"
+
+#include "IBorderControl.hpp"
+#include "TextHostControlService.hpp"
#include <memory>
@@ -8,7 +10,9 @@ namespace cru::ui::controls {
template <typename TControl>
class TextControlService;
-class TextBox : public NoChildControl {
+class TextBox : public NoChildControl,
+ public virtual IBorderControl,
+ public virtual ITextHostControl {
public:
static constexpr std::u16string_view control_type = u"TextBox";
@@ -27,25 +31,16 @@ class TextBox : public NoChildControl {
render::RenderObject* GetRenderObject() const override;
- gsl::not_null<render::TextRenderObject*> GetTextRenderObject();
- render::ScrollRenderObject* GetScrollRenderObject();
-
- const TextBoxBorderStyle& GetBorderStyle();
- void SetBorderStyle(TextBoxBorderStyle border_style);
-
- protected:
- void OnMouseHoverChange(bool newHover) override;
+ gsl::not_null<render::TextRenderObject*> GetTextRenderObject() override;
+ render::ScrollRenderObject* GetScrollRenderObject() override;
- private:
- void UpdateBorderStyle();
+ void ApplyBorderStyle(const style::ApplyBorderStyleInfo& style) override;
private:
std::unique_ptr<render::BorderRenderObject> border_render_object_;
std::unique_ptr<render::ScrollRenderObject> scroll_render_object_;
std::unique_ptr<render::TextRenderObject> text_render_object_;
- TextBoxBorderStyle border_style_;
-
- std::unique_ptr<TextControlService<TextBox>> service_;
+ std::unique_ptr<TextHostControlService> service_;
};
} // namespace cru::ui::controls
diff --git a/include/cru/ui/controls/TextHostControlService.hpp b/include/cru/ui/controls/TextHostControlService.hpp
new file mode 100644
index 00000000..340228fe
--- /dev/null
+++ b/include/cru/ui/controls/TextHostControlService.hpp
@@ -0,0 +1,137 @@
+#pragma once
+#include "Base.hpp"
+
+#include "cru/platform/gui/InputMethod.hpp"
+#include "cru/platform/gui/UiApplication.hpp"
+#include "cru/ui/controls/Control.hpp"
+#include "cru/ui/helper/ShortcutHub.hpp"
+
+#include <functional>
+#include <string>
+
+namespace cru::ui::render {
+class TextRenderObject;
+class ScrollRenderObject;
+} // namespace cru::ui::render
+
+namespace cru::ui::controls {
+constexpr int k_default_caret_blink_duration = 500;
+
+struct ITextHostControl : virtual Interface {
+ virtual gsl::not_null<render::TextRenderObject*> GetTextRenderObject() = 0;
+ // May return nullptr.
+ virtual render::ScrollRenderObject* GetScrollRenderObject() = 0;
+};
+
+class TextHostControlService : public Object {
+ CRU_DEFINE_CLASS_LOG_TAG(u"cru::ui::controls::TextControlService")
+
+ public:
+ TextHostControlService(gsl::not_null<Control*> control);
+
+ CRU_DELETE_COPY(TextHostControlService)
+ CRU_DELETE_MOVE(TextHostControlService)
+
+ ~TextHostControlService() = default;
+
+ public:
+ bool IsEnabled() { return enable_; }
+ void SetEnabled(bool enable);
+
+ bool IsEditable() { return this->editable_; }
+ void SetEditable(bool editable);
+
+ std::u16string GetText() { return this->text_; }
+ std::u16string_view GetTextView() { return this->text_; }
+ void SetText(std::u16string text, bool stop_composition = false);
+
+ void InsertText(gsl::index position, std::u16string_view text,
+ bool stop_composition = false);
+ void DeleteChar(gsl::index position, bool stop_composition = false);
+
+ // Return the position of deleted character.
+ gsl::index DeleteCharPrevious(gsl::index position,
+ bool stop_composition = false);
+ void DeleteText(TextRange range, bool stop_composition = false);
+
+ void CancelComposition();
+
+ std::optional<platform::gui::CompositionText> GetCompositionInfo();
+
+ bool IsCaretVisible() { return caret_visible_; }
+ void SetCaretVisible(bool visible);
+
+ int GetCaretBlinkDuration() { return caret_blink_duration_; }
+ void SetCaretBlinkDuration(int milliseconds);
+
+ gsl::index GetCaretPosition() { return selection_.GetEnd(); }
+ TextRange GetSelection() { return selection_; }
+
+ void SetSelection(gsl::index caret_position);
+ void SetSelection(TextRange selection, bool scroll_to_caret = true);
+
+ void ChangeSelectionEnd(gsl::index new_end);
+ void AbortSelection();
+
+ void DeleteSelectedText();
+ // If some text is selected, then they are deleted first. Then insert text
+ // into caret position.
+ void ReplaceSelectedText(std::u16string_view text);
+
+ void ScrollToCaret();
+
+ private:
+ gsl::not_null<render::TextRenderObject*> GetTextRenderObject();
+ render::ScrollRenderObject* GetScrollRenderObject();
+
+ // May return nullptr.
+ platform::gui::IInputMethodContext* GetInputMethodContext();
+
+ void CoerceSelection();
+
+ void SetupCaret();
+ void TearDownCaret();
+
+ void SyncTextRenderObject();
+
+ void UpdateInputMethodPosition();
+
+ template <typename TArgs>
+ void SetupOneHandler(event::RoutedEvent<TArgs>* (Control::*event)(),
+ void (TextHostControlService::*handler)(
+ typename event::RoutedEvent<TArgs>::EventArgs)) {
+ this->event_guard_ += (this->control_->*event)()->Bubble()->AddHandler(
+ std::bind(handler, this, std::placeholders::_1));
+ }
+
+ void MouseMoveHandler(event::MouseEventArgs& args);
+ void MouseDownHandler(event::MouseButtonEventArgs& args);
+ void MouseUpHandler(event::MouseButtonEventArgs& args);
+ void GainFocusHandler(event::FocusChangeEventArgs& args);
+ void LoseFocusHandler(event::FocusChangeEventArgs& args);
+
+ void SetUpShortcuts();
+
+ private:
+ gsl::not_null<Control*> control_;
+ gsl::not_null<ITextHostControl*> text_host_control_;
+
+ EventRevokerListGuard event_guard_;
+ EventRevokerListGuard input_method_context_event_guard_;
+
+ std::u16string text_;
+ TextRange selection_;
+
+ bool enable_ = false;
+ bool editable_ = false;
+
+ bool caret_visible_ = false;
+ platform::gui::TimerAutoCanceler caret_timer_canceler_;
+ int caret_blink_duration_ = k_default_caret_blink_duration;
+
+ helper::ShortcutHub shortcut_hub_;
+
+ // true if left mouse is down and selecting
+ bool mouse_move_selecting_ = false;
+};
+} // namespace cru::ui::controls
diff --git a/include/cru/ui/controls/Window.hpp b/include/cru/ui/controls/Window.hpp
new file mode 100644
index 00000000..cca56b64
--- /dev/null
+++ b/include/cru/ui/controls/Window.hpp
@@ -0,0 +1,32 @@
+#pragma once
+#include "cru/platform/gui/Base.hpp"
+#include "cru/ui/controls/RootControl.hpp"
+
+#include "cru/common/Base.hpp"
+
+namespace cru::ui::controls {
+class Window final : public RootControl {
+ public:
+ static constexpr std::u16string_view control_type = u"Window";
+
+ public:
+ static Window* Create(Control* attached_control = nullptr);
+
+ private:
+ explicit Window(Control* attached_control);
+
+ public:
+ CRU_DELETE_COPY(Window)
+ CRU_DELETE_MOVE(Window)
+
+ ~Window() override;
+
+ public:
+ std::u16string_view GetControlType() const final { return control_type; }
+
+ protected:
+ gsl::not_null<platform::gui::INativeWindow*> CreateNativeWindow(
+ gsl::not_null<host::WindowHost*> host,
+ platform::gui::INativeWindow* parent) override;
+};
+} // namespace cru::ui::controls
diff --git a/include/cru/ui/UiEvent.hpp b/include/cru/ui/events/UiEvent.hpp
index 5adace8a..22ad0150 100644
--- a/include/cru/ui/UiEvent.hpp
+++ b/include/cru/ui/events/UiEvent.hpp
@@ -1,15 +1,15 @@
#pragma once
-#include "Base.hpp"
+#include "../Base.hpp"
#include "cru/common/Event.hpp"
-#include "cru/platform/native/Keyboard.hpp"
+#include "cru/platform/gui/Keyboard.hpp"
#include <memory>
#include <optional>
#include <string>
#include <type_traits>
-namespace cru::platform::graph {
+namespace cru::platform::graphics {
struct IPainter;
}
@@ -84,6 +84,7 @@ class MouseEventArgs : public UiEventArgs {
// This point is relative to window client lefttop.
Point GetPoint() const { return point_.value_or(Point{}); }
+ Point GetPoint(render::RenderObject* render_target) const;
Point GetPointToContent(render::RenderObject* render_target) const;
private:
@@ -94,13 +95,13 @@ class MouseButtonEventArgs : public MouseEventArgs {
public:
MouseButtonEventArgs(Object* sender, Object* original_sender,
const Point& point, const MouseButton button,
- platform::native::KeyModifier key_modifier)
+ platform::gui::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)
+ platform::gui::KeyModifier key_modifier)
: MouseEventArgs(sender, original_sender),
button_(button),
key_modifier_(key_modifier) {}
@@ -111,11 +112,11 @@ class MouseButtonEventArgs : public MouseEventArgs {
~MouseButtonEventArgs() override = default;
MouseButton GetButton() const { return button_; }
- platform::native::KeyModifier GetKeyModifier() const { return key_modifier_; }
+ platform::gui::KeyModifier GetKeyModifier() const { return key_modifier_; }
private:
MouseButton button_;
- platform::native::KeyModifier key_modifier_;
+ platform::gui::KeyModifier key_modifier_;
};
class MouseWheelEventArgs : public MouseEventArgs {
@@ -138,7 +139,7 @@ class MouseWheelEventArgs : public MouseEventArgs {
class PaintEventArgs : public UiEventArgs {
public:
PaintEventArgs(Object* sender, Object* original_sender,
- platform::graph::IPainter* painter)
+ platform::graphics::IPainter* painter)
: UiEventArgs(sender, original_sender), painter_(painter) {}
PaintEventArgs(const PaintEventArgs& other) = default;
PaintEventArgs(PaintEventArgs&& other) = default;
@@ -146,10 +147,10 @@ class PaintEventArgs : public UiEventArgs {
PaintEventArgs& operator=(PaintEventArgs&& other) = default;
~PaintEventArgs() = default;
- platform::graph::IPainter* GetPainter() const { return painter_; }
+ platform::graphics::IPainter* GetPainter() const { return painter_; }
private:
- platform::graph::IPainter* painter_;
+ platform::graphics::IPainter* painter_;
};
class FocusChangeEventArgs : public UiEventArgs {
@@ -191,8 +192,8 @@ class ToggleEventArgs : public UiEventArgs {
class KeyEventArgs : public UiEventArgs {
public:
KeyEventArgs(Object* sender, Object* original_sender,
- platform::native::KeyCode key_code,
- platform::native::KeyModifier key_modifier)
+ platform::gui::KeyCode key_code,
+ platform::gui::KeyModifier key_modifier)
: UiEventArgs(sender, original_sender),
key_code_(key_code),
key_modifier_(key_modifier) {}
@@ -202,12 +203,12 @@ class KeyEventArgs : public UiEventArgs {
KeyEventArgs& operator=(KeyEventArgs&& other) = default;
~KeyEventArgs() override = default;
- platform::native::KeyCode GetKeyCode() const { return key_code_; }
- platform::native::KeyModifier GetKeyModifier() const { return key_modifier_; }
+ platform::gui::KeyCode GetKeyCode() const { return key_code_; }
+ platform::gui::KeyModifier GetKeyModifier() const { return key_modifier_; }
private:
- platform::native::KeyCode key_code_;
- platform::native::KeyModifier key_modifier_;
+ platform::gui::KeyCode key_code_;
+ platform::gui::KeyModifier key_modifier_;
};
class CharEventArgs : public UiEventArgs {
diff --git a/include/cru/ui/ClickDetector.hpp b/include/cru/ui/helper/ClickDetector.hpp
index 4ffe5d05..b58297b1 100644
--- a/include/cru/ui/ClickDetector.hpp
+++ b/include/cru/ui/helper/ClickDetector.hpp
@@ -1,10 +1,10 @@
#pragma once
-#include "Control.hpp"
+#include "../controls/Control.hpp"
-namespace cru::ui {
+namespace cru::ui::helper {
class ClickEventArgs : Object {
public:
- ClickEventArgs(Control* sender, const Point& down_point,
+ ClickEventArgs(controls::Control* sender, const Point& down_point,
const Point& up_point, MouseButton button)
: sender_(sender),
down_point_(down_point),
@@ -16,13 +16,13 @@ class ClickEventArgs : Object {
~ClickEventArgs() override = default;
- Control* GetSender() const { return sender_; }
+ controls::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_;
+ controls::Control* sender_;
Point down_point_;
Point up_point_;
MouseButton button_;
@@ -39,14 +39,14 @@ class ClickDetector : public Object {
CRU_DEFINE_CLASS_LOG_TAG(u"cru::ui::ClickDetector")
public:
- explicit ClickDetector(Control* control);
+ explicit ClickDetector(controls::Control* control);
CRU_DELETE_COPY(ClickDetector)
CRU_DELETE_MOVE(ClickDetector)
~ClickDetector() override = default;
- Control* GetControl() const { return control_; }
+ controls::Control* GetControl() const { return control_; }
ClickState GetState() const { return state_; }
@@ -69,9 +69,9 @@ class ClickDetector : public Object {
void SetState(ClickState state);
private:
- Control* control_;
+ controls::Control* control_;
- ClickState state_;
+ ClickState state_ = ClickState::None;
bool enable_ = true;
MouseButton trigger_button_ = mouse_buttons::left | mouse_buttons::right;
@@ -84,4 +84,4 @@ class ClickDetector : public Object {
Point down_point_;
MouseButton button_;
};
-} // namespace cru::ui
+} // namespace cru::ui::helper
diff --git a/include/cru/ui/helper/ShortcutHub.hpp b/include/cru/ui/helper/ShortcutHub.hpp
new file mode 100644
index 00000000..fe3414fe
--- /dev/null
+++ b/include/cru/ui/helper/ShortcutHub.hpp
@@ -0,0 +1,134 @@
+#pragma once
+#include "../Base.hpp"
+
+#include "../events/UiEvent.hpp"
+#include "cru/common/Event.hpp"
+#include "cru/platform/gui/Keyboard.hpp"
+
+#include <cstddef>
+#include <functional>
+#include <memory>
+#include <optional>
+#include <string>
+#include <string_view>
+#include <type_traits>
+#include <unordered_map>
+#include <vector>
+
+namespace cru::ui::helper {
+
+class ShortcutKeyBind {
+ public:
+ ShortcutKeyBind(
+ platform::gui::KeyCode key,
+ platform::gui::KeyModifier modifier = platform::gui::KeyModifiers::none)
+ : key_(key), modifier_(modifier) {}
+
+ CRU_DEFAULT_COPY(ShortcutKeyBind)
+ CRU_DEFAULT_MOVE(ShortcutKeyBind)
+
+ ~ShortcutKeyBind() = default;
+
+ platform::gui::KeyCode GetKey() const { return key_; }
+ platform::gui::KeyModifier GetModifier() const { return modifier_; }
+
+ bool Is(platform::gui::KeyCode key,
+ platform::gui::KeyModifier modifier) const {
+ return key == key_ && modifier == modifier_;
+ }
+
+ bool operator==(const ShortcutKeyBind& other) const {
+ return this->key_ == other.key_ && this->modifier_ == other.modifier_;
+ }
+
+ bool operator!=(const ShortcutKeyBind& other) const {
+ return !this->operator==(other);
+ }
+
+ std::u16string ToString() {
+ std::u16string result = u"(";
+ result += platform::gui::ToString(modifier_);
+ result += u")";
+ result += platform::gui::ToString(key_);
+ return result;
+ }
+
+ private:
+ platform::gui::KeyCode key_;
+ platform::gui::KeyModifier modifier_;
+};
+} // namespace cru::ui::helper
+
+namespace std {
+template <>
+struct hash<cru::ui::helper::ShortcutKeyBind> {
+ std::size_t operator()(const cru::ui::helper::ShortcutKeyBind& value) const {
+ std::size_t result = 0;
+ cru::hash_combine(result, static_cast<int>(value.GetKey()));
+ cru::hash_combine(result, static_cast<int>(value.GetModifier()));
+ return result;
+ }
+};
+} // namespace std
+
+namespace cru::ui::helper {
+struct Shortcut {
+ // Just for debug.
+ std::u16string name;
+ ShortcutKeyBind key_bind;
+ // Return true if it consumes the shortcut. Or return false if it does not
+ // handle the shortcut.
+ std::function<bool()> handler;
+};
+
+struct ShortcutInfo {
+ int id;
+ std::u16string name;
+ ShortcutKeyBind key_bind;
+ std::function<bool()> handler;
+};
+
+class ShortcutHub : public Object {
+ public:
+ ShortcutHub() = default;
+
+ CRU_DELETE_COPY(ShortcutHub)
+ CRU_DELETE_MOVE(ShortcutHub)
+
+ ~ShortcutHub() override = default;
+
+ int RegisterShortcut(std::u16string name, ShortcutKeyBind bind,
+ std::function<bool()> handler) {
+ return RegisterShortcut({std::move(name), bind, std::move(handler)});
+ }
+
+ // Return an id used for unregistering.
+ int RegisterShortcut(Shortcut shortcut);
+
+ void UnregisterShortcut(int id);
+
+ std::vector<ShortcutInfo> GetAllShortcuts() const;
+ std::optional<ShortcutInfo> GetShortcut(int id) const;
+ const std::vector<ShortcutInfo>& GetShortcutByKeyBind(
+ const ShortcutKeyBind& key_bind) const;
+
+ IEvent<event::KeyEventArgs&>* FallbackKeyEvent() { return &fallback_event_; }
+
+ void Install(controls::Control* control);
+ void Uninstall();
+
+ private:
+ void OnKeyDown(event::KeyEventArgs& event);
+
+ private:
+ std::unordered_map<ShortcutKeyBind, std::vector<ShortcutInfo>> map_;
+
+ const std::vector<ShortcutInfo> empty_list_;
+
+ int current_id_ = 1;
+
+ Event<event::KeyEventArgs&> fallback_event_;
+
+ EventRevokerListGuard event_guard_;
+};
+} // namespace cru::ui::helper
diff --git a/include/cru/ui/host/LayoutPaintCycler.hpp b/include/cru/ui/host/LayoutPaintCycler.hpp
new file mode 100644
index 00000000..ed543afa
--- /dev/null
+++ b/include/cru/ui/host/LayoutPaintCycler.hpp
@@ -0,0 +1,39 @@
+#pragma once
+#include "../Base.hpp"
+
+#include "cru/platform/gui/UiApplication.hpp"
+
+#include <chrono>
+
+namespace cru::ui::host {
+class LayoutPaintCycler {
+ public:
+ explicit LayoutPaintCycler(WindowHost* host);
+
+ CRU_DELETE_COPY(LayoutPaintCycler)
+ CRU_DELETE_MOVE(LayoutPaintCycler)
+
+ ~LayoutPaintCycler();
+
+ public:
+ void InvalidateLayout();
+ void InvalidatePaint();
+
+ bool IsLayoutDirty() { return layout_dirty_; }
+
+ private:
+ void OnCycle();
+
+ private:
+ WindowHost* host_;
+
+ platform::gui::TimerAutoCanceler timer_canceler_;
+
+ bool layout_dirty_ = true;
+ bool paint_dirty_ = true;
+
+ std::chrono::steady_clock::time_point last_cycle_time_;
+ std::chrono::steady_clock::duration cycle_threshold_ =
+ std::chrono::milliseconds(1000) / 144;
+};
+} // namespace cru::ui::host
diff --git a/include/cru/ui/host/WindowHost.hpp b/include/cru/ui/host/WindowHost.hpp
new file mode 100644
index 00000000..bd2f7c16
--- /dev/null
+++ b/include/cru/ui/host/WindowHost.hpp
@@ -0,0 +1,176 @@
+#pragma once
+#include "../Base.hpp"
+
+#include "../render/Base.hpp"
+#include "cru/common/Event.hpp"
+#include "cru/platform/gui/UiApplication.hpp"
+#include "cru/platform/gui/Window.hpp"
+
+#include <functional>
+#include <memory>
+#include <optional>
+
+namespace cru::ui::host {
+class LayoutPaintCycler;
+
+struct AfterLayoutEventArgs {};
+
+struct CreateWindowParams {
+ CreateWindowParams(platform::gui::INativeWindow* parent = nullptr,
+ platform::gui::CreateWindowFlag flag = {})
+ : parent(parent), flag(flag) {}
+
+ platform::gui::INativeWindow* parent;
+ platform::gui::CreateWindowFlag flag;
+};
+
+// The bridge between control tree and native window.
+class WindowHost : public Object {
+ CRU_DEFINE_CLASS_LOG_TAG(u"cru::ui::host::WindowHost")
+
+ public:
+ WindowHost(controls::Control* root_control);
+
+ CRU_DELETE_COPY(WindowHost)
+ CRU_DELETE_MOVE(WindowHost)
+
+ ~WindowHost() override;
+
+ public:
+ platform::gui::INativeWindow* GetNativeWindow() { return native_window_; }
+
+ // Do nothing if native window is already created.
+ gsl::not_null<platform::gui::INativeWindow*> CreateNativeWindow(
+ CreateWindowParams create_window_params = {});
+
+ // 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<AfterLayoutEventArgs>* AfterLayoutEvent() {
+ return &after_layout_event_;
+ }
+
+ void Relayout();
+ void Relayout(const Size& available_size);
+
+ void Repaint();
+
+ // Is layout is invalid, wait for relayout and then run the action. Otherwist
+ // run it right now.
+ void RunAfterLayoutStable(std::function<void()> action);
+
+ // If true, preferred size of root render object is set to window size when
+ // measure. Default is true.
+ bool IsLayoutPreferToFillWindow() const;
+ void SetLayoutPreferToFillWindow(bool value);
+
+ // 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.
+ controls::Control* GetMouseHoverControl() const {
+ return mouse_hover_control_;
+ }
+
+ //*************** region: focus ***************
+
+ controls::Control* GetFocusControl();
+
+ void SetFocusControl(controls::Control* control);
+
+ //*************** 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(controls::Control* control);
+
+ // Return null if not captured.
+ controls::Control* GetMouseCaptureControl();
+
+ controls::Control* HitTest(const Point& point);
+
+ void UpdateCursor();
+
+ IEvent<platform::gui::INativeWindow*>* NativeWindowChangeEvent() {
+ return &native_window_change_event_;
+ }
+
+ // If window exist, return window actual size. Otherwise if saved rect exists,
+ // return it. Otherwise return 0.
+ Rect GetWindowRect();
+
+ void SetSavedWindowRect(std::optional<Rect> rect);
+
+ void SetWindowRect(const Rect& rect);
+
+ private:
+ //*************** region: native messages ***************
+ void OnNativeDestroy(platform::gui::INativeWindow* window, std::nullptr_t);
+ void OnNativePaint(platform::gui::INativeWindow* window, std::nullptr_t);
+ void OnNativeResize(platform::gui::INativeWindow* window, const Size& size);
+
+ void OnNativeFocus(platform::gui::INativeWindow* window,
+ cru::platform::gui::FocusChangeType focus);
+
+ void OnNativeMouseEnterLeave(platform::gui::INativeWindow* window,
+ cru::platform::gui::MouseEnterLeaveType enter);
+ void OnNativeMouseMove(platform::gui::INativeWindow* window,
+ const Point& point);
+ void OnNativeMouseDown(platform::gui::INativeWindow* window,
+ const platform::gui::NativeMouseButtonEventArgs& args);
+ void OnNativeMouseUp(platform::gui::INativeWindow* window,
+ const platform::gui::NativeMouseButtonEventArgs& args);
+
+ void OnNativeKeyDown(platform::gui::INativeWindow* window,
+ const platform::gui::NativeKeyEventArgs& args);
+ void OnNativeKeyUp(platform::gui::INativeWindow* window,
+ const platform::gui::NativeKeyEventArgs& args);
+
+ //*************** region: event dispatcher helper ***************
+
+ void DispatchMouseHoverControlChangeEvent(controls::Control* old_control,
+ controls::Control* new_control,
+ const Point& point, bool no_leave,
+ bool no_enter);
+
+ private:
+ controls::Control* root_control_ = nullptr;
+ render::RenderObject* root_render_object_ = nullptr;
+
+ platform::gui::INativeWindow* native_window_ = nullptr;
+
+ std::unique_ptr<LayoutPaintCycler> layout_paint_cycler_;
+
+ Event<AfterLayoutEventArgs> after_layout_event_;
+ std::vector<std::function<void()> > after_layout_stable_action_;
+
+ std::vector<EventRevokerGuard> event_revoker_guards_;
+
+ controls::Control* mouse_hover_control_ = nullptr;
+
+ controls::Control* focus_control_;
+
+ controls::Control* mouse_captured_control_ = nullptr;
+
+ bool layout_prefer_to_fill_window_ = true;
+
+ Event<platform::gui::INativeWindow*> native_window_change_event_;
+
+ std::optional<Rect> saved_rect_;
+};
+} // namespace cru::ui::host
diff --git a/include/cru/ui/render/Base.hpp b/include/cru/ui/render/Base.hpp
index 801d58bd..ac67349e 100644
--- a/include/cru/ui/render/Base.hpp
+++ b/include/cru/ui/render/Base.hpp
@@ -9,5 +9,4 @@ class FlexLayoutRenderObject;
class ScrollRenderObject;
class StackLayoutRenderObject;
class TextRenderObject;
-class WindowRenderObject;
} // namespace cru::ui::render
diff --git a/include/cru/ui/render/BorderRenderObject.hpp b/include/cru/ui/render/BorderRenderObject.hpp
index 587f051a..3d4f4dad 100644
--- a/include/cru/ui/render/BorderRenderObject.hpp
+++ b/include/cru/ui/render/BorderRenderObject.hpp
@@ -1,5 +1,7 @@
#pragma once
+#include "../style/ApplyBorderStyleInfo.hpp"
#include "RenderObject.hpp"
+#include "cru/ui/Base.hpp"
namespace cru::ui::render {
class BorderRenderObject : public RenderObject {
@@ -16,11 +18,11 @@ class BorderRenderObject : public RenderObject {
bool IsBorderEnabled() const { return is_border_enabled_; }
void SetBorderEnabled(bool enabled) { is_border_enabled_ = enabled; }
- std::shared_ptr<platform::graph::IBrush> GetBorderBrush() {
+ std::shared_ptr<platform::graphics::IBrush> GetBorderBrush() {
return border_brush_;
}
- void SetBorderBrush(std::shared_ptr<platform::graph::IBrush> brush) {
+ void SetBorderBrush(std::shared_ptr<platform::graphics::IBrush> brush) {
if (brush == border_brush_) return;
border_brush_ = std::move(brush);
InvalidatePaint();
@@ -42,32 +44,32 @@ class BorderRenderObject : public RenderObject {
RecreateGeometry();
}
- std::shared_ptr<platform::graph::IBrush> GetForegroundBrush() {
+ std::shared_ptr<platform::graphics::IBrush> GetForegroundBrush() {
return foreground_brush_;
}
- void SetForegroundBrush(std::shared_ptr<platform::graph::IBrush> brush) {
+ void SetForegroundBrush(std::shared_ptr<platform::graphics::IBrush> brush) {
if (brush == foreground_brush_) return;
foreground_brush_ = std::move(brush);
InvalidatePaint();
}
- std::shared_ptr<platform::graph::IBrush> GetBackgroundBrush() {
+ std::shared_ptr<platform::graphics::IBrush> GetBackgroundBrush() {
return background_brush_;
}
- void SetBackgroundBrush(std::shared_ptr<platform::graph::IBrush> brush) {
+ void SetBackgroundBrush(std::shared_ptr<platform::graphics::IBrush> brush) {
if (brush == background_brush_) return;
background_brush_ = std::move(brush);
InvalidatePaint();
}
- void SetBorderStyle(const BorderStyle& style);
+ void ApplyBorderStyle(const style::ApplyBorderStyleInfo& style);
RenderObject* HitTest(const Point& point) override;
protected:
- void OnDrawCore(platform::graph::IPainter* painter) override;
+ void OnDrawCore(platform::graphics::IPainter* painter) override;
Size OnMeasureCore(const MeasureRequirement& requirement,
const MeasureSize& preferred_size) override;
@@ -87,19 +89,19 @@ class BorderRenderObject : public RenderObject {
private:
bool is_border_enabled_ = false;
- std::shared_ptr<platform::graph::IBrush> border_brush_;
+ std::shared_ptr<platform::graphics::IBrush> border_brush_;
Thickness border_thickness_;
CornerRadius border_radius_;
- std::shared_ptr<platform::graph::IBrush> foreground_brush_;
- std::shared_ptr<platform::graph::IBrush> background_brush_;
+ std::shared_ptr<platform::graphics::IBrush> foreground_brush_;
+ std::shared_ptr<platform::graphics::IBrush> background_brush_;
// The ring. Used for painting.
- std::unique_ptr<platform::graph::IGeometry> geometry_;
+ std::unique_ptr<platform::graphics::IGeometry> geometry_;
// Area including inner area of the border. Used for painting foreground and
// background.
- std::unique_ptr<platform::graph::IGeometry> border_inner_geometry_;
+ std::unique_ptr<platform::graphics::IGeometry> border_inner_geometry_;
// Area including border ring and inner area. Used for hit test.
- std::unique_ptr<platform::graph::IGeometry> border_outer_geometry_;
+ std::unique_ptr<platform::graphics::IGeometry> border_outer_geometry_;
};
} // namespace cru::ui::render
diff --git a/include/cru/ui/render/CanvasRenderObject.hpp b/include/cru/ui/render/CanvasRenderObject.hpp
index 3216f08c..58fee59c 100644
--- a/include/cru/ui/render/CanvasRenderObject.hpp
+++ b/include/cru/ui/render/CanvasRenderObject.hpp
@@ -22,7 +22,7 @@ class CanvasRenderObject : public RenderObject {
IEvent<CanvasPaintEventArgs>* PaintEvent() { return &paint_event_; }
protected:
- void OnDrawContent(platform::graph::IPainter* painter) override;
+ void OnDrawContent(platform::graphics::IPainter* painter) override;
Size OnMeasureContent(const MeasureRequirement& requirement,
const MeasureSize& preferred_size) override;
diff --git a/include/cru/ui/render/FlexLayoutRenderObject.hpp b/include/cru/ui/render/FlexLayoutRenderObject.hpp
index ee29d1e4..a8154487 100644
--- a/include/cru/ui/render/FlexLayoutRenderObject.hpp
+++ b/include/cru/ui/render/FlexLayoutRenderObject.hpp
@@ -1,6 +1,8 @@
#pragma once
#include "LayoutRenderObject.hpp"
+#include <string_view>
+
namespace cru::ui::render {
// Measure Logic (v0.1):
// Cross axis measure logic is the same as stack layout.
@@ -85,6 +87,8 @@ class FlexLayoutRenderObject : public LayoutRenderObject<FlexChildLayoutData> {
FlexLayoutRenderObject& operator=(FlexLayoutRenderObject&& other) = delete;
~FlexLayoutRenderObject() override = default;
+ std::u16string_view GetName() const override;
+
FlexDirection GetFlexDirection() const { return direction_; }
void SetFlexDirection(FlexDirection direction) {
direction_ = direction;
diff --git a/include/cru/ui/render/LayoutRenderObject.hpp b/include/cru/ui/render/LayoutRenderObject.hpp
index b46ba0d0..732031a1 100644
--- a/include/cru/ui/render/LayoutRenderObject.hpp
+++ b/include/cru/ui/render/LayoutRenderObject.hpp
@@ -1,7 +1,7 @@
#pragma once
#include "RenderObject.hpp"
-#include "cru/platform/graph/util/Painter.hpp"
+#include "cru/platform/graphics/util/Painter.hpp"
namespace cru::ui::render {
template <typename TChildLayoutData>
diff --git a/include/cru/ui/render/MeasureRequirement.hpp b/include/cru/ui/render/MeasureRequirement.hpp
index 2be159f8..6a0c6952 100644
--- a/include/cru/ui/render/MeasureRequirement.hpp
+++ b/include/cru/ui/render/MeasureRequirement.hpp
@@ -1,8 +1,12 @@
#pragma once
#include "Base.hpp"
+#include "cru/common/Format.hpp"
+
+#include <fmt/core.h>
#include <algorithm>
#include <limits>
+#include <string>
namespace cru::ui::render {
constexpr Size Min(const Size& left, const Size& right) {
@@ -112,6 +116,11 @@ class MeasureLength final {
}
}
+ std::u16string ToDebugString() const {
+ return IsSpecified() ? ToUtf16String(GetLengthOrUndefined())
+ : u"UNSPECIFIED";
+ }
+
private:
// -1 for not specify
float length_;
@@ -160,6 +169,11 @@ struct MeasureSize {
};
}
+ std::u16string ToDebugString() const {
+ return fmt::format(u"({}, {})", width.ToDebugString(),
+ height.ToDebugString());
+ }
+
constexpr static MeasureSize NotSpecified() {
return MeasureSize{MeasureLength::NotSpecified(),
MeasureLength::NotSpecified()};
@@ -187,10 +201,11 @@ struct MeasureRequirement {
: max(max), min(min) {}
constexpr bool Satisfy(const Size& size) const {
- return max.width.GetLengthOrMax() >= size.width &&
- max.height.GetLengthOrMax() >= size.height &&
- min.width.GetLengthOr0() <= size.width &&
- min.height.GetLengthOr0() <= size.height;
+ auto normalized = Normalize();
+ return normalized.max.width.GetLengthOrMax() >= size.width &&
+ normalized.max.height.GetLengthOrMax() >= size.height &&
+ normalized.min.width.GetLengthOr0() <= size.width &&
+ normalized.min.height.GetLengthOr0() <= size.height;
}
constexpr MeasureRequirement Normalize() const {
@@ -225,6 +240,11 @@ struct MeasureRequirement {
return result;
}
+ std::u16string ToDebugString() const {
+ return fmt::format(u"{{min: {}, max: {}}}", min.ToDebugString(),
+ max.ToDebugString());
+ }
+
constexpr static MeasureRequirement Merge(const MeasureRequirement& left,
const MeasureRequirement& right) {
return MeasureRequirement{MeasureSize::Min(left.max, right.max),
diff --git a/include/cru/ui/render/RenderObject.hpp b/include/cru/ui/render/RenderObject.hpp
index f820f029..8bcd4c62 100644
--- a/include/cru/ui/render/RenderObject.hpp
+++ b/include/cru/ui/render/RenderObject.hpp
@@ -2,10 +2,15 @@
#include "Base.hpp"
#include "MeasureRequirement.hpp"
+#include "cru/common/Base.hpp"
#include "cru/common/Event.hpp"
+#include "cru/ui/Base.hpp"
-namespace cru::ui::render {
+#include <cstddef>
+#include <string>
+#include <string_view>
+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.
@@ -29,13 +34,13 @@ namespace cru::ui::render {
//
// To write a custom RenderObject, override following methods:
// public:
-// void Draw(platform::graph::IPainter* painter) override;
+// void Draw(platform::graphics::IPainter* painter) override;
// RenderObject* HitTest(const Point& point) override;
// protected:
// Size OnMeasureContent(const MeasureRequirement& requirement) override;
// void OnLayoutContent(const Rect& content_rect) override;
class RenderObject : public Object {
- friend WindowRenderObject;
+ friend host::WindowHost;
CRU_DEFINE_CLASS_LOG_TAG(u"cru::ui::render::RenderObject")
@@ -58,10 +63,10 @@ class RenderObject : public Object {
RenderObject& operator=(RenderObject&& other) = delete;
~RenderObject() override = default;
- Control* GetAttachedControl() const { return control_; }
- void SetAttachedControl(Control* new_control) { control_ = new_control; }
+ controls::Control* GetAttachedControl() const { return control_; }
+ void SetAttachedControl(controls::Control* new_control);
- UiHost* GetUiHost() const { return ui_host_; }
+ host::WindowHost* GetWindowHost() const { return window_host_; }
RenderObject* GetParent() const { return parent_; }
@@ -70,6 +75,9 @@ class RenderObject : public Object {
void AddChild(RenderObject* render_object, Index position);
void RemoveChild(Index position);
+ RenderObject* GetFirstChild() const;
+ void TraverseDescendants(const std::function<void(RenderObject*)>& action);
+
// Offset from parent's lefttop to lefttop of this render object. Margin is
// accounted for.
Point GetOffset() const { return offset_; }
@@ -123,16 +131,30 @@ class RenderObject : public Object {
// This will set offset of this render object and call OnLayoutCore.
void Layout(const Point& offset);
- void Draw(platform::graph::IPainter* painter);
+ virtual Rect GetPaddingRect() const;
+ virtual Rect GetContentRect() const;
+
+ void Draw(platform::graphics::IPainter* painter);
// Param point must be relative the lefttop of render object including margin.
// Add offset before pass point to children.
virtual RenderObject* HitTest(const Point& point) = 0;
+ IEvent<host::WindowHost*>* AttachToHostEvent() {
+ return &attach_to_host_event_;
+ }
+ IEvent<std::nullptr_t>* DetachFromHostEvent() {
+ return &detach_from_host_event_;
+ }
+
public:
void InvalidateLayout();
void InvalidatePaint();
+ public:
+ virtual std::u16string_view GetName() const;
+ std::u16string GetDebugPathInTree() const;
+
protected:
void SetChildMode(ChildMode mode) { child_mode_ = mode; }
@@ -148,15 +170,15 @@ class RenderObject : public Object {
virtual void OnRemoveChild(RenderObject* removed_child, Index position);
// Draw all children with offset.
- void DefaultDrawChildren(platform::graph::IPainter* painter);
+ void DefaultDrawChildren(platform::graphics::IPainter* painter);
// Draw all children with translation of content rect lefttop.
- void DefaultDrawContent(platform::graph::IPainter* painter);
+ void DefaultDrawContent(platform::graphics::IPainter* painter);
// Call DefaultDrawContent. Then call DefaultDrawChildren.
- virtual void OnDrawCore(platform::graph::IPainter* painter);
+ virtual void OnDrawCore(platform::graphics::IPainter* painter);
- virtual void OnDrawContent(platform::graph::IPainter* painter);
+ virtual void OnDrawContent(platform::graphics::IPainter* painter);
// Size measure including margin and padding. Please reduce margin and padding
// or other custom things and pass the result content measure requirement and
@@ -182,20 +204,20 @@ class RenderObject : public Object {
// Lefttop of content_rect should be added when calculated children's offset.
virtual void OnLayoutContent(const Rect& content_rect) = 0;
- virtual void OnAfterLayout();
- static void NotifyAfterLayoutRecursive(RenderObject* render_object);
+ virtual void OnAttachedControlChanged(controls::Control* control) {
+ CRU_UNUSED(control)
+ }
- virtual Rect GetPaddingRect() const;
- virtual Rect GetContentRect() const;
+ virtual void OnAfterLayout();
private:
void SetParent(RenderObject* new_parent);
- void SetRenderHostRecursive(UiHost* host);
+ void SetWindowHostRecursive(host::WindowHost* host);
private:
- Control* control_ = nullptr;
- UiHost* ui_host_ = nullptr;
+ controls::Control* control_ = nullptr;
+ host::WindowHost* window_host_ = nullptr;
RenderObject* parent_ = nullptr;
std::vector<RenderObject*> children_{};
@@ -210,5 +232,8 @@ class RenderObject : public Object {
Thickness margin_{};
Thickness padding_{};
+
+ Event<host::WindowHost*> attach_to_host_event_;
+ Event<std::nullptr_t> detach_from_host_event_;
};
} // namespace cru::ui::render
diff --git a/include/cru/ui/render/ScrollBar.hpp b/include/cru/ui/render/ScrollBar.hpp
new file mode 100644
index 00000000..3293e9d0
--- /dev/null
+++ b/include/cru/ui/render/ScrollBar.hpp
@@ -0,0 +1,218 @@
+#pragma once
+#include "Base.hpp"
+#include "cru/common/Base.hpp"
+#include "cru/common/Event.hpp"
+#include "cru/platform/graphics/Base.hpp"
+#include "cru/platform/graphics/Geometry.hpp"
+#include "cru/platform/graphics/Painter.hpp"
+#include "cru/platform/gui/Cursor.hpp"
+#include "cru/platform/gui/UiApplication.hpp"
+#include "cru/ui/Base.hpp"
+#include "cru/ui/controls/Control.hpp"
+
+#include <gsl/pointers>
+#include <memory>
+#include <optional>
+
+namespace cru::ui::render {
+class ScrollRenderObject;
+
+enum class ScrollKind { Absolute, Relative, Page, Line };
+
+struct Scroll {
+ Direction direction;
+ ScrollKind kind;
+ // For absolute, the new scroll position. Otherwise, offset.
+ float value;
+};
+
+enum class ScrollBarAreaKind {
+ UpArrow, // Line up
+ DownArrow, // Line down
+ UpSlot, // Page up
+ DownSlot, // Page down
+ Thumb
+};
+
+class ScrollBar : public Object {
+ public:
+ ScrollBar(gsl::not_null<ScrollRenderObject*> render_object,
+ Direction direction);
+
+ CRU_DELETE_COPY(ScrollBar)
+ CRU_DELETE_MOVE(ScrollBar)
+
+ ~ScrollBar() override;
+
+ public:
+ Direction GetDirection() const { return direction_; }
+
+ bool IsEnabled() const { return is_enabled_; }
+ void SetEnabled(bool value);
+
+ bool IsExpanded() const { return is_expanded_; }
+ void SetExpanded(bool value);
+
+ void Draw(platform::graphics::IPainter* painter);
+
+ IEvent<Scroll>* ScrollAttemptEvent() { return &scroll_attempt_event_; }
+
+ void InstallHandlers(controls::Control* control);
+ void UninstallHandlers() { InstallHandlers(nullptr); }
+
+ gsl::not_null<std::shared_ptr<platform::graphics::IBrush>>
+ GetCollapsedThumbBrush() const;
+ gsl::not_null<std::shared_ptr<platform::graphics::IBrush>>
+ GetExpandedThumbBrush() const;
+ gsl::not_null<std::shared_ptr<platform::graphics::IBrush>>
+ GetExpandedSlotBrush() const;
+ gsl::not_null<std::shared_ptr<platform::graphics::IBrush>>
+ GetExpandedArrowBrush() const;
+ gsl::not_null<std::shared_ptr<platform::graphics::IBrush>>
+ GetExpandedArrowBackgroundBrush() const;
+
+ protected:
+ void OnDraw(platform::graphics::IPainter* painter, bool expand);
+
+ virtual void DrawUpArrow(platform::graphics::IPainter* painter,
+ const Rect& area) = 0;
+ virtual void DrawDownArrow(platform::graphics::IPainter* painter,
+ const Rect& area) = 0;
+
+ std::optional<ScrollBarAreaKind> ExpandedHitTest(const Point& point);
+
+ virtual bool IsShowBar() = 0;
+
+ virtual std::optional<Rect> GetExpandedAreaRect(
+ ScrollBarAreaKind area_kind) = 0;
+ virtual std::optional<Rect> GetCollapsedTriggerExpandAreaRect() = 0;
+ virtual std::optional<Rect> GetCollapsedThumbRect() = 0;
+
+ virtual float CalculateNewScrollPosition(const Rect& thumb_original_rect,
+ const Point& mouse_offset) = 0;
+
+ private:
+ void SetCursor();
+ void RestoreCursor();
+
+ void BeginAutoCollapseTimer();
+ void StopAutoCollapseTimer();
+
+ void OnMouseLeave();
+
+ protected:
+ gsl::not_null<ScrollRenderObject*> render_object_;
+
+ std::unique_ptr<platform::graphics::IGeometry> arrow_geometry_;
+
+ private:
+ Direction direction_;
+
+ bool is_enabled_ = true;
+
+ bool is_expanded_ = false;
+
+ std::shared_ptr<platform::graphics::IBrush> collapsed_thumb_brush_;
+ std::shared_ptr<platform::graphics::IBrush> expanded_thumb_brush_;
+ std::shared_ptr<platform::graphics::IBrush> expanded_slot_brush_;
+ std::shared_ptr<platform::graphics::IBrush> expanded_arrow_brush_;
+ std::shared_ptr<platform::graphics::IBrush> expanded_arrow_background_brush_;
+
+ Rect move_thumb_thumb_original_rect_;
+ std::optional<Point> move_thumb_start_;
+
+ EventRevokerListGuard event_guard_;
+
+ Event<Scroll> scroll_attempt_event_;
+
+ std::optional<std::shared_ptr<platform::gui::ICursor>> old_cursor_;
+
+ platform::gui::TimerAutoCanceler auto_collapse_timer_canceler_;
+};
+
+class HorizontalScrollBar : public ScrollBar {
+ public:
+ explicit HorizontalScrollBar(
+ gsl::not_null<ScrollRenderObject*> render_object);
+
+ CRU_DELETE_COPY(HorizontalScrollBar)
+ CRU_DELETE_MOVE(HorizontalScrollBar)
+
+ ~HorizontalScrollBar() override = default;
+
+ protected:
+ void DrawUpArrow(platform::graphics::IPainter* painter,
+ const Rect& area) override;
+ void DrawDownArrow(platform::graphics::IPainter* painter,
+ const Rect& area) override;
+
+ bool IsShowBar() override;
+
+ std::optional<Rect> GetExpandedAreaRect(ScrollBarAreaKind area_kind) override;
+ std::optional<Rect> GetCollapsedTriggerExpandAreaRect() override;
+ std::optional<Rect> GetCollapsedThumbRect() override;
+
+ float CalculateNewScrollPosition(const Rect& thumb_original_rect,
+ const Point& mouse_offset) override;
+};
+
+class VerticalScrollBar : public ScrollBar {
+ public:
+ explicit VerticalScrollBar(gsl::not_null<ScrollRenderObject*> render_object);
+
+ CRU_DELETE_COPY(VerticalScrollBar)
+ CRU_DELETE_MOVE(VerticalScrollBar)
+
+ ~VerticalScrollBar() override = default;
+
+ protected:
+ void DrawUpArrow(platform::graphics::IPainter* painter,
+ const Rect& area) override;
+ void DrawDownArrow(platform::graphics::IPainter* painter,
+ const Rect& area) override;
+
+ bool IsShowBar() override;
+
+ std::optional<Rect> GetExpandedAreaRect(ScrollBarAreaKind area_kind) override;
+ std::optional<Rect> GetCollapsedTriggerExpandAreaRect() override;
+ std::optional<Rect> GetCollapsedThumbRect() override;
+
+ float CalculateNewScrollPosition(const Rect& thumb_original_rect,
+ const Point& mouse_offset) override;
+};
+
+// A delegate to draw scrollbar and register related events.
+class ScrollBarDelegate : public Object {
+ public:
+ explicit ScrollBarDelegate(gsl::not_null<ScrollRenderObject*> render_object);
+
+ CRU_DELETE_COPY(ScrollBarDelegate)
+ CRU_DELETE_MOVE(ScrollBarDelegate)
+
+ ~ScrollBarDelegate() override = default;
+
+ public:
+ bool IsHorizontalBarEnabled() const { return horizontal_bar_.IsEnabled(); }
+ void SetHorizontalBarEnabled(bool value) {
+ horizontal_bar_.SetEnabled(value);
+ }
+
+ bool IsVerticalBarEnabled() const { return horizontal_bar_.IsEnabled(); }
+ void SetVerticalBarEnabled(bool value) { horizontal_bar_.SetEnabled(value); }
+
+ IEvent<Scroll>* ScrollAttemptEvent() { return &scroll_attempt_event_; }
+
+ void DrawScrollBar(platform::graphics::IPainter* painter);
+
+ void InstallHandlers(controls::Control* control);
+ void UninstallHandlers() { InstallHandlers(nullptr); }
+
+ private:
+ gsl::not_null<ScrollRenderObject*> render_object_;
+
+ HorizontalScrollBar horizontal_bar_;
+ VerticalScrollBar vertical_bar_;
+
+ Event<Scroll> scroll_attempt_event_;
+};
+} // namespace cru::ui::render
diff --git a/include/cru/ui/render/ScrollRenderObject.hpp b/include/cru/ui/render/ScrollRenderObject.hpp
index 9b0cbf9a..aed25f8e 100644
--- a/include/cru/ui/render/ScrollRenderObject.hpp
+++ b/include/cru/ui/render/ScrollRenderObject.hpp
@@ -1,8 +1,11 @@
#pragma once
#include "RenderObject.hpp"
-#include "cru/platform/graph/util/Painter.hpp"
+#include "cru/platform/graphics/util/Painter.hpp"
+#include "cru/ui/Base.hpp"
+#include "cru/ui/render/ScrollBar.hpp"
+#include <memory>
#include <optional>
namespace cru::ui::render {
@@ -16,7 +19,7 @@ namespace cru::ui::render {
// Or layout by scroll state.
class ScrollRenderObject : public RenderObject {
public:
- ScrollRenderObject() : RenderObject(ChildMode::Single) {}
+ ScrollRenderObject();
CRU_DELETE_COPY(ScrollRenderObject)
CRU_DELETE_MOVE(ScrollRenderObject)
@@ -27,8 +30,22 @@ class ScrollRenderObject : public RenderObject {
// Return the coerced scroll offset.
Point GetScrollOffset();
+ float GetScrollOffset(Direction direction) {
+ return direction == Direction::Horizontal ? GetScrollOffset().x
+ : GetScrollOffset().y;
+ }
void SetScrollOffset(const Point& offset);
void SetScrollOffset(std::optional<float> x, std::optional<float> y);
+ void SetScrollOffset(Direction direction, std::optional<float> value) {
+ if (direction == Direction::Horizontal) {
+ SetScrollOffset(value, std::nullopt);
+ } else {
+ SetScrollOffset(std::nullopt, value);
+ }
+ }
+
+ void Scroll(const Scroll& scroll);
+
Point GetRawScrollOffset() const { return scroll_offset_; }
// Return the viewable area rect.
@@ -44,7 +61,7 @@ class ScrollRenderObject : public RenderObject {
void ScrollToContain(const Rect& rect, const Thickness& margin = Thickness{});
protected:
- void OnDrawCore(platform::graph::IPainter* painter) override;
+ void OnDrawCore(platform::graphics::IPainter* painter) override;
// Logic:
// If available size is bigger than child's preferred size, then child's
@@ -54,7 +71,11 @@ class ScrollRenderObject : public RenderObject {
const MeasureSize& preferred_size) override;
void OnLayoutContent(const Rect& content_rect) override;
+ void OnAttachedControlChanged(controls::Control* control) override;
+
private:
Point scroll_offset_;
+
+ std::unique_ptr<ScrollBarDelegate> scroll_bar_delegate_;
};
} // namespace cru::ui::render
diff --git a/include/cru/ui/render/TextRenderObject.hpp b/include/cru/ui/render/TextRenderObject.hpp
index 3be42bbb..bdec18d1 100644
--- a/include/cru/ui/render/TextRenderObject.hpp
+++ b/include/cru/ui/render/TextRenderObject.hpp
@@ -24,10 +24,10 @@ class TextRenderObject : public RenderObject {
constexpr static float default_caret_width = 2;
public:
- TextRenderObject(std::shared_ptr<platform::graph::IBrush> brush,
- std::shared_ptr<platform::graph::IFont> font,
- std::shared_ptr<platform::graph::IBrush> selection_brush,
- std::shared_ptr<platform::graph::IBrush> caret_brush);
+ TextRenderObject(std::shared_ptr<platform::graphics::IBrush> brush,
+ std::shared_ptr<platform::graphics::IFont> font,
+ std::shared_ptr<platform::graphics::IBrush> selection_brush,
+ std::shared_ptr<platform::graphics::IBrush> caret_brush);
TextRenderObject(const TextRenderObject& other) = delete;
TextRenderObject(TextRenderObject&& other) = delete;
TextRenderObject& operator=(const TextRenderObject& other) = delete;
@@ -38,25 +38,27 @@ class TextRenderObject : public RenderObject {
std::u16string_view GetTextView() const;
void SetText(std::u16string new_text);
- std::shared_ptr<platform::graph::IBrush> GetBrush() const { return brush_; }
- void SetBrush(std::shared_ptr<platform::graph::IBrush> new_brush);
+ std::shared_ptr<platform::graphics::IBrush> GetBrush() const {
+ return brush_;
+ }
+ void SetBrush(std::shared_ptr<platform::graphics::IBrush> new_brush);
- std::shared_ptr<platform::graph::IFont> GetFont() const;
- void SetFont(std::shared_ptr<platform::graph::IFont> font);
+ std::shared_ptr<platform::graphics::IFont> GetFont() const;
+ void SetFont(std::shared_ptr<platform::graphics::IFont> font);
std::vector<Rect> TextRangeRect(const TextRange& text_range);
Point TextSinglePoint(gsl::index position, bool trailing);
- platform::graph::TextHitTestResult TextHitTest(const Point& point);
+ platform::graphics::TextHitTestResult TextHitTest(const Point& point);
std::optional<TextRange> GetSelectionRange() const {
return selection_range_;
}
void SetSelectionRange(std::optional<TextRange> new_range);
- std::shared_ptr<platform::graph::IBrush> GetSelectionBrush() const {
+ std::shared_ptr<platform::graphics::IBrush> GetSelectionBrush() const {
return selection_brush_;
}
- void SetSelectionBrush(std::shared_ptr<platform::graph::IBrush> new_brush);
+ void SetSelectionBrush(std::shared_ptr<platform::graphics::IBrush> new_brush);
bool IsDrawCaret() const { return draw_caret_; }
void SetDrawCaret(bool draw_caret);
@@ -72,18 +74,23 @@ class TextRenderObject : public RenderObject {
// Lefttop relative to render object lefttop.
Rect GetCaretRect();
- std::shared_ptr<platform::graph::IBrush> GetCaretBrush() const {
+ std::shared_ptr<platform::graphics::IBrush> GetCaretBrush() const {
return caret_brush_;
}
- void GetCaretBrush(std::shared_ptr<platform::graph::IBrush> brush);
+ void GetCaretBrush(std::shared_ptr<platform::graphics::IBrush> brush);
float GetCaretWidth() const { return caret_width_; }
void SetCaretWidth(float width);
+ bool IsMeasureIncludingTrailingSpace() const {
+ return is_measure_including_trailing_space_;
+ }
+ void SetMeasureIncludingTrailingSpace(bool including);
+
RenderObject* HitTest(const Point& point) override;
protected:
- void OnDrawContent(platform::graph::IPainter* painter) override;
+ void OnDrawContent(platform::graphics::IPainter* painter) override;
// See remarks of this class.
Size OnMeasureContent(const MeasureRequirement& requirement,
@@ -93,16 +100,18 @@ class TextRenderObject : public RenderObject {
void OnAfterLayout() override;
private:
- std::shared_ptr<platform::graph::IBrush> brush_;
- std::shared_ptr<platform::graph::IFont> font_;
- std::unique_ptr<platform::graph::ITextLayout> text_layout_;
+ std::shared_ptr<platform::graphics::IBrush> brush_;
+ std::shared_ptr<platform::graphics::IFont> font_;
+ std::unique_ptr<platform::graphics::ITextLayout> text_layout_;
std::optional<TextRange> selection_range_ = std::nullopt;
- std::shared_ptr<platform::graph::IBrush> selection_brush_;
+ std::shared_ptr<platform::graphics::IBrush> selection_brush_;
bool draw_caret_ = false;
gsl::index caret_position_ = 0;
- std::shared_ptr<platform::graph::IBrush> caret_brush_;
+ std::shared_ptr<platform::graphics::IBrush> caret_brush_;
float caret_width_ = default_caret_width;
+
+ bool is_measure_including_trailing_space_ = false;
};
} // namespace cru::ui::render
diff --git a/include/cru/ui/render/WindowRenderObject.hpp b/include/cru/ui/render/WindowRenderObject.hpp
deleted file mode 100644
index 4c254f42..00000000
--- a/include/cru/ui/render/WindowRenderObject.hpp
+++ /dev/null
@@ -1,29 +0,0 @@
-#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;
-
- RenderObject* HitTest(const Point& point) override;
-
- protected:
- Size OnMeasureContent(const MeasureRequirement& requirement,
- const MeasureSize& preferred_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/style/ApplyBorderStyleInfo.hpp b/include/cru/ui/style/ApplyBorderStyleInfo.hpp
new file mode 100644
index 00000000..5058b51f
--- /dev/null
+++ b/include/cru/ui/style/ApplyBorderStyleInfo.hpp
@@ -0,0 +1,28 @@
+#pragma once
+#include <optional>
+#include "../Base.hpp"
+
+namespace cru::ui::style {
+struct ApplyBorderStyleInfo {
+ explicit ApplyBorderStyleInfo(
+ std::optional<std::shared_ptr<platform::graphics::IBrush>> border_brush =
+ std::nullopt,
+ std::optional<Thickness> border_thickness = std::nullopt,
+ std::optional<CornerRadius> border_radius = std::nullopt,
+ std::optional<std::shared_ptr<platform::graphics::IBrush>>
+ foreground_brush = std::nullopt,
+ std::optional<std::shared_ptr<platform::graphics::IBrush>>
+ background_brush = std::nullopt)
+ : border_brush(std::move(border_brush)),
+ border_thickness(std::move(border_thickness)),
+ border_radius(std::move(border_radius)),
+ foreground_brush(std::move(foreground_brush)),
+ background_brush(std::move(background_brush)) {}
+
+ std::optional<std::shared_ptr<platform::graphics::IBrush>> border_brush;
+ std::optional<Thickness> border_thickness;
+ std::optional<CornerRadius> border_radius;
+ std::optional<std::shared_ptr<platform::graphics::IBrush>> foreground_brush;
+ std::optional<std::shared_ptr<platform::graphics::IBrush>> background_brush;
+};
+} // namespace cru::ui::style
diff --git a/include/cru/ui/style/Condition.hpp b/include/cru/ui/style/Condition.hpp
new file mode 100644
index 00000000..d5cf16f2
--- /dev/null
+++ b/include/cru/ui/style/Condition.hpp
@@ -0,0 +1,123 @@
+#pragma once
+#include "../Base.hpp"
+#include "cru/common/Base.hpp"
+#include "cru/common/ClonablePtr.hpp"
+#include "cru/common/Event.hpp"
+#include "cru/ui/controls/IClickableControl.hpp"
+#include "cru/ui/helper/ClickDetector.hpp"
+
+#include <memory>
+#include <type_traits>
+#include <utility>
+#include <vector>
+
+namespace cru::ui::style {
+class Condition : public Object {
+ public:
+ virtual std::vector<IBaseEvent*> ChangeOn(
+ controls::Control* control) const = 0;
+ virtual bool Judge(controls::Control* control) const = 0;
+
+ virtual Condition* Clone() const = 0;
+};
+
+class NoCondition : public Condition {
+ public:
+ static ClonablePtr<NoCondition> Create() {
+ return ClonablePtr<NoCondition>(new NoCondition);
+ };
+
+ std::vector<IBaseEvent*> ChangeOn(controls::Control*) const override {
+ return {};
+ }
+
+ bool Judge(controls::Control*) const override { return true; }
+
+ NoCondition* Clone() const override { return new NoCondition; }
+};
+
+class CompoundCondition : public Condition {
+ public:
+ explicit CompoundCondition(std::vector<ClonablePtr<Condition>> conditions);
+
+ std::vector<IBaseEvent*> ChangeOn(controls::Control* control) const override;
+
+ protected:
+ std::vector<ClonablePtr<Condition>> conditions_;
+};
+
+class AndCondition : public CompoundCondition {
+ public:
+ using CompoundCondition::CompoundCondition;
+
+ bool Judge(controls::Control* control) const override;
+
+ AndCondition* Clone() const override { return new AndCondition(conditions_); }
+};
+
+class OrCondition : public CompoundCondition {
+ public:
+ using CompoundCondition::CompoundCondition;
+
+ bool Judge(controls::Control* control) const override;
+
+ OrCondition* Clone() const override { return new OrCondition(conditions_); }
+};
+
+class FocusCondition : public Condition {
+ public:
+ static ClonablePtr<FocusCondition> Create(bool has_focus) {
+ return ClonablePtr<FocusCondition>(new FocusCondition(has_focus));
+ }
+
+ explicit FocusCondition(bool has_focus);
+
+ std::vector<IBaseEvent*> ChangeOn(controls::Control* control) const override;
+ bool Judge(controls::Control* control) const override;
+
+ FocusCondition* Clone() const override {
+ return new FocusCondition(has_focus_);
+ }
+
+ private:
+ bool has_focus_;
+};
+
+class HoverCondition : public Condition {
+ public:
+ static ClonablePtr<HoverCondition> Create(bool hover) {
+ return ClonablePtr<HoverCondition>(new HoverCondition(hover));
+ }
+
+ explicit HoverCondition(bool hover) : hover_(hover) {}
+
+ std::vector<IBaseEvent*> ChangeOn(controls::Control* control) const override;
+ bool Judge(controls::Control* control) const override;
+
+ HoverCondition* Clone() const override { return new HoverCondition(hover_); }
+
+ private:
+ bool hover_;
+};
+
+class ClickStateCondition : public Condition {
+ public:
+ static ClonablePtr<ClickStateCondition> Create(
+ helper::ClickState click_state) {
+ return ClonablePtr<ClickStateCondition>(
+ new ClickStateCondition(click_state));
+ }
+
+ explicit ClickStateCondition(helper::ClickState click_state);
+
+ std::vector<IBaseEvent*> ChangeOn(controls::Control* control) const override;
+ bool Judge(controls::Control* control) const override;
+
+ ClickStateCondition* Clone() const override {
+ return new ClickStateCondition(click_state_);
+ }
+
+ private:
+ helper::ClickState click_state_;
+};
+} // namespace cru::ui::style
diff --git a/include/cru/ui/style/StyleRule.hpp b/include/cru/ui/style/StyleRule.hpp
new file mode 100644
index 00000000..8ac42cd0
--- /dev/null
+++ b/include/cru/ui/style/StyleRule.hpp
@@ -0,0 +1,45 @@
+#pragma once
+#include "Condition.hpp"
+#include "Styler.hpp"
+#include "cru/common/Base.hpp"
+#include "cru/common/ClonablePtr.hpp"
+#include "cru/ui/Base.hpp"
+
+#include <memory>
+#include <string>
+#include <vector>
+
+namespace cru::ui::style {
+class StyleRule : public Object {
+ public:
+ StyleRule(ClonablePtr<Condition> condition, ClonablePtr<Styler> styler,
+ std::u16string name = {});
+
+ CRU_DEFAULT_COPY(StyleRule)
+ CRU_DEFAULT_MOVE(StyleRule)
+
+ ~StyleRule() override = default;
+
+ public:
+ const std::u16string& GetName() const { return name_; }
+ Condition* GetCondition() const { return condition_.get(); }
+ Styler* GetStyler() const { return styler_.get(); }
+
+ StyleRule WithNewCondition(ClonablePtr<Condition> condition,
+ std::u16string name = {}) const {
+ return StyleRule{std::move(condition), styler_, std::move(name)};
+ }
+
+ StyleRule WithNewStyler(ClonablePtr<Styler> styler,
+ std::u16string name = {}) const {
+ return StyleRule{condition_, std::move(styler), std::move(name)};
+ }
+
+ bool CheckAndApply(controls::Control* control) const;
+
+ private:
+ ClonablePtr<Condition> condition_;
+ ClonablePtr<Styler> styler_;
+ std::u16string name_;
+};
+} // namespace cru::ui::style
diff --git a/include/cru/ui/style/StyleRuleSet.hpp b/include/cru/ui/style/StyleRuleSet.hpp
new file mode 100644
index 00000000..e62dd2de
--- /dev/null
+++ b/include/cru/ui/style/StyleRuleSet.hpp
@@ -0,0 +1,87 @@
+#pragma once
+#include "StyleRule.hpp"
+#include "cru/common/Base.hpp"
+#include "cru/common/Event.hpp"
+
+#include <cstddef>
+
+namespace cru::ui::style {
+class StyleRuleSet : public Object {
+ public:
+ StyleRuleSet() = default;
+ explicit StyleRuleSet(StyleRuleSet* parent);
+
+ CRU_DELETE_COPY(StyleRuleSet)
+ CRU_DELETE_MOVE(StyleRuleSet)
+
+ ~StyleRuleSet() override = default;
+
+ public:
+ StyleRuleSet* GetParent() const { return parent_; }
+ void SetParent(StyleRuleSet* parent);
+
+ gsl::index GetSize() const { return static_cast<gsl::index>(rules_.size()); }
+ const std::vector<StyleRule>& GetRules() const { return rules_; }
+
+ void AddStyleRule(StyleRule rule) {
+ AddStyleRule(std::move(rule), GetSize());
+ }
+
+ void AddStyleRule(StyleRule rule, gsl::index index);
+
+ template <typename Iter>
+ void AddStyleRuleRange(Iter start, Iter end, gsl::index index) {
+ Expects(index >= 0 && index <= GetSize());
+ rules_.insert(rules_.cbegin() + index, std::move(start), std::move(end));
+ UpdateChangeListener();
+ UpdateStyle();
+ }
+
+ void RemoveStyleRule(gsl::index index, gsl::index count = 1);
+
+ void Clear() { RemoveStyleRule(0, GetSize()); }
+
+ void Set(const StyleRuleSet& other, bool set_parent = false);
+
+ const StyleRule& operator[](gsl::index index) const { return rules_[index]; }
+
+ // Triggered whenever a change happened to this (rule add or remove, parent
+ // change ...). Subscribe to this and update style change listeners and style.
+ IEvent<std::nullptr_t>* ChangeEvent() { return &change_event_; }
+
+ private:
+ void RaiseChangeEvent() { change_event_.Raise(nullptr); }
+
+ private:
+ Event<std::nullptr_t> change_event_;
+
+ StyleRuleSet* parent_ = nullptr;
+ EventRevokerGuard parent_change_event_guard_;
+
+ std::vector<StyleRule> rules_;
+};
+
+class StyleRuleSetBind {
+ public:
+ StyleRuleSetBind(controls::Control* control, StyleRuleSet* ruleset);
+
+ CRU_DELETE_COPY(StyleRuleSetBind)
+ CRU_DELETE_MOVE(StyleRuleSetBind)
+
+ ~StyleRuleSetBind() = default;
+
+ private:
+ void UpdateRuleSetChainCache();
+ void UpdateChangeListener();
+ void UpdateStyle();
+
+ private:
+ controls::Control* control_;
+ StyleRuleSet* ruleset_;
+
+ // child first, parent last.
+ std::vector<StyleRuleSet*> ruleset_chain_cache_;
+
+ EventRevokerListGuard guard_;
+};
+} // namespace cru::ui::style
diff --git a/include/cru/ui/style/Styler.hpp b/include/cru/ui/style/Styler.hpp
new file mode 100644
index 00000000..865cbbaf
--- /dev/null
+++ b/include/cru/ui/style/Styler.hpp
@@ -0,0 +1,80 @@
+#pragma once
+#include "../Base.hpp"
+#include "ApplyBorderStyleInfo.hpp"
+#include "cru/common/Base.hpp"
+#include "cru/common/ClonablePtr.hpp"
+#include "cru/platform/gui/Cursor.hpp"
+#include "cru/ui/controls/Control.hpp"
+
+#include <memory>
+#include <vector>
+
+namespace cru::ui::style {
+class Styler : public Object {
+ public:
+ virtual void Apply(controls::Control* control) const = 0;
+
+ virtual Styler* Clone() const = 0;
+};
+
+class CompoundStyler : public Styler {
+ public:
+ template <typename... S>
+ static ClonablePtr<CompoundStyler> Create(ClonablePtr<S>... s) {
+ return ClonablePtr<CompoundStyler>(
+ new CompoundStyler(std::vector<ClonablePtr<Styler>>{std::move(s)...}));
+ }
+
+ explicit CompoundStyler(std::vector<ClonablePtr<Styler>> stylers)
+ : stylers_(std::move(stylers)) {}
+
+ void Apply(controls::Control* control) const override {
+ for (const auto& styler : stylers_) {
+ styler->Apply(control);
+ }
+ }
+
+ virtual CompoundStyler* Clone() const override {
+ return new CompoundStyler(stylers_);
+ }
+
+ private:
+ std::vector<ClonablePtr<Styler>> stylers_;
+};
+
+class BorderStyler : public Styler {
+ public:
+ static ClonablePtr<BorderStyler> Create(ApplyBorderStyleInfo style) {
+ return ClonablePtr<BorderStyler>(new BorderStyler(std::move(style)));
+ }
+
+ explicit BorderStyler(ApplyBorderStyleInfo style);
+
+ void Apply(controls::Control* control) const override;
+
+ BorderStyler* Clone() const override { return new BorderStyler(style_); }
+
+ private:
+ ApplyBorderStyleInfo style_;
+};
+
+class CursorStyler : public Styler {
+ public:
+ static ClonablePtr<CursorStyler> Create(
+ std::shared_ptr<platform::gui::ICursor> cursor) {
+ return ClonablePtr<CursorStyler>(new CursorStyler(std::move(cursor)));
+ }
+
+ static ClonablePtr<CursorStyler> Create(platform::gui::SystemCursorType type);
+
+ explicit CursorStyler(std::shared_ptr<platform::gui::ICursor> cursor)
+ : cursor_(std::move(cursor)) {}
+
+ void Apply(controls::Control* control) const override;
+
+ CursorStyler* Clone() const override { return new CursorStyler(cursor_); }
+
+ private:
+ std::shared_ptr<platform::gui::ICursor> cursor_;
+};
+} // namespace cru::ui::style
diff --git a/include/cru/win/graph/direct/Brush.hpp b/include/cru/win/graphics/direct/Brush.hpp
index df1debe3..fbff83b5 100644
--- a/include/cru/win/graph/direct/Brush.hpp
+++ b/include/cru/win/graphics/direct/Brush.hpp
@@ -2,9 +2,9 @@
#include "ComResource.hpp"
#include "Resource.hpp"
-#include "cru/platform/graph/Brush.hpp"
+#include "cru/platform/graphics/Brush.hpp"
-namespace cru::platform::graph::win::direct {
+namespace cru::platform::graphics::win::direct {
struct ID2DBrush : virtual IBrush {
virtual ID2D1Brush* GetD2DBrushInterface() const = 0;
};
@@ -36,4 +36,4 @@ class D2DSolidColorBrush : public DirectGraphResource,
Microsoft::WRL::ComPtr<ID2D1SolidColorBrush> brush_;
};
-} // namespace cru::platform::graph::win::direct
+} // namespace cru::platform::graphics::win::direct
diff --git a/include/cru/win/graph/direct/ComResource.hpp b/include/cru/win/graphics/direct/ComResource.hpp
index 2ac332cd..34ea39ed 100644
--- a/include/cru/win/graph/direct/ComResource.hpp
+++ b/include/cru/win/graphics/direct/ComResource.hpp
@@ -3,9 +3,9 @@
#include "cru/common/Base.hpp"
-namespace cru::platform::graph::win::direct {
+namespace cru::platform::graphics::win::direct {
template <typename TInterface>
struct IComResource : virtual Interface {
virtual TInterface* GetComInterface() const = 0;
};
-} // namespace cru::platform::graph::win::direct
+} // namespace cru::platform::graphics::win::direct
diff --git a/include/cru/win/graph/direct/ConvertUtil.hpp b/include/cru/win/graphics/direct/ConvertUtil.hpp
index 12a04c7b..0d8da8a1 100644
--- a/include/cru/win/graph/direct/ConvertUtil.hpp
+++ b/include/cru/win/graphics/direct/ConvertUtil.hpp
@@ -1,9 +1,9 @@
#pragma once
#include "../../WinPreConfig.hpp"
-#include "cru/platform/graph/Base.hpp"
+#include "cru/platform/graphics/Base.hpp"
-namespace cru::platform::graph::win::direct {
+namespace cru::platform::graphics::win::direct {
inline D2D1_MATRIX_3X2_F Convert(const platform::Matrix& matrix) {
D2D1_MATRIX_3X2_F m;
m._11 = matrix.m11;
@@ -104,4 +104,4 @@ inline bool operator==(const D2D1_ELLIPSE& left, const D2D1_ELLIPSE& right) {
inline bool operator!=(const D2D1_ELLIPSE& left, const D2D1_ELLIPSE& right) {
return !(left == right);
}
-} // namespace cru::platform::graph::win::direct
+} // namespace cru::platform::graphics::win::direct
diff --git a/include/cru/win/graph/direct/Exception.hpp b/include/cru/win/graphics/direct/Exception.hpp
index 8b62e8fa..72493f2f 100644
--- a/include/cru/win/graph/direct/Exception.hpp
+++ b/include/cru/win/graphics/direct/Exception.hpp
@@ -1,7 +1,7 @@
#pragma once
#include "../../Exception.hpp"
-namespace cru::platform::graph::win::direct {
+namespace cru::platform::graphics::win::direct {
using platform::win::HResultError;
using platform::win::ThrowIfFailed;
-} // namespace cru::platform::graph::win::direct \ No newline at end of file
+} // namespace cru::platform::graphics::win::direct
diff --git a/include/cru/win/graph/direct/Factory.hpp b/include/cru/win/graphics/direct/Factory.hpp
index e70454f5..70f3ede1 100644
--- a/include/cru/win/graph/direct/Factory.hpp
+++ b/include/cru/win/graphics/direct/Factory.hpp
@@ -1,9 +1,9 @@
#pragma once
#include "Resource.hpp"
-#include "cru/platform/graph/Factory.hpp"
+#include "cru/platform/graphics/Factory.hpp"
-namespace cru::platform::graph::win::direct {
+namespace cru::platform::graphics::win::direct {
class DirectGraphFactory : public DirectResource, public virtual IGraphFactory {
public:
DirectGraphFactory();
@@ -55,4 +55,4 @@ class DirectGraphFactory : public DirectResource, public virtual IGraphFactory {
Microsoft::WRL::ComPtr<IDWriteFactory> dwrite_factory_;
Microsoft::WRL::ComPtr<IDWriteFontCollection> dwrite_system_font_collection_;
};
-} // namespace cru::platform::graph::win::direct
+} // namespace cru::platform::graphics::win::direct
diff --git a/include/cru/win/graph/direct/Font.hpp b/include/cru/win/graphics/direct/Font.hpp
index 2195f3e4..fd3921a3 100644
--- a/include/cru/win/graph/direct/Font.hpp
+++ b/include/cru/win/graphics/direct/Font.hpp
@@ -2,11 +2,11 @@
#include "ComResource.hpp"
#include "Resource.hpp"
-#include "cru/platform/graph/Font.hpp"
+#include "cru/platform/graphics/Font.hpp"
#include <string_view>
-namespace cru::platform::graph::win::direct {
+namespace cru::platform::graphics::win::direct {
class DWriteFont : public DirectGraphResource,
public virtual IFont,
public virtual IComResource<IDWriteTextFormat> {
@@ -30,4 +30,4 @@ class DWriteFont : public DirectGraphResource,
std::u16string font_family_;
Microsoft::WRL::ComPtr<IDWriteTextFormat> text_format_;
};
-} // namespace cru::platform::graph::win::direct
+} // namespace cru::platform::graphics::win::direct
diff --git a/include/cru/win/graph/direct/Geometry.hpp b/include/cru/win/graphics/direct/Geometry.hpp
index 87987d3e..edfec590 100644
--- a/include/cru/win/graph/direct/Geometry.hpp
+++ b/include/cru/win/graphics/direct/Geometry.hpp
@@ -2,9 +2,9 @@
#include "ComResource.hpp"
#include "Resource.hpp"
-#include "cru/platform/graph/Geometry.hpp"
+#include "cru/platform/graphics/Geometry.hpp"
-namespace cru::platform::graph::win::direct {
+namespace cru::platform::graphics::win::direct {
class D2DGeometryBuilder : public DirectGraphResource,
public virtual IGeometryBuilder {
public:
@@ -54,4 +54,4 @@ class D2DGeometry : public DirectGraphResource,
private:
Microsoft::WRL::ComPtr<ID2D1PathGeometry> geometry_;
};
-} // namespace cru::platform::graph::win::direct
+} // namespace cru::platform::graphics::win::direct
diff --git a/include/cru/win/graph/direct/Painter.hpp b/include/cru/win/graphics/direct/Painter.hpp
index a50f962d..b34c1563 100644
--- a/include/cru/win/graph/direct/Painter.hpp
+++ b/include/cru/win/graphics/direct/Painter.hpp
@@ -2,11 +2,11 @@
#include "ComResource.hpp"
#include "Resource.hpp"
-#include "cru/platform/graph/Painter.hpp"
+#include "cru/platform/graphics/Painter.hpp"
#include <vector>
-namespace cru::platform::graph::win::direct {
+namespace cru::platform::graphics::win::direct {
class D2DPainter : public DirectResource,
public virtual IPainter,
public virtual IComResource<ID2D1RenderTarget> {
@@ -27,6 +27,8 @@ class D2DPainter : public DirectResource,
void Clear(const Color& color) override;
+ void DrawLine(const Point& start, const Point& end, IBrush* brush,
+ float width) override;
void StrokeRectangle(const Rect& rectangle, IBrush* brush,
float width) override;
void FillRectangle(const Rect& rectangle, IBrush* brush) override;
@@ -57,4 +59,4 @@ class D2DPainter : public DirectResource,
bool is_drawing_ = true;
};
-} // namespace cru::platform::graph::win::direct
+} // namespace cru::platform::graphics::win::direct
diff --git a/include/cru/win/graph/direct/Resource.hpp b/include/cru/win/graphics/direct/Resource.hpp
index 6162ebd8..f60f373e 100644
--- a/include/cru/win/graph/direct/Resource.hpp
+++ b/include/cru/win/graphics/direct/Resource.hpp
@@ -1,11 +1,11 @@
#pragma once
#include "../../WinPreConfig.hpp"
-#include "cru/platform/graph/Resource.hpp"
+#include "cru/platform/graphics/Resource.hpp"
#include <string_view>
-namespace cru::platform::graph::win::direct {
+namespace cru::platform::graphics::win::direct {
class DirectGraphFactory;
class DirectResource : public Object, public virtual INativeResource {
@@ -46,4 +46,4 @@ class DirectGraphResource : public DirectResource,
private:
DirectGraphFactory* factory_;
};
-} // namespace cru::platform::graph::win::direct
+} // namespace cru::platform::graphics::win::direct
diff --git a/include/cru/win/graph/direct/TextLayout.hpp b/include/cru/win/graphics/direct/TextLayout.hpp
index 016009ab..aa040278 100644
--- a/include/cru/win/graph/direct/TextLayout.hpp
+++ b/include/cru/win/graphics/direct/TextLayout.hpp
@@ -2,12 +2,12 @@
#include "ComResource.hpp"
#include "Resource.hpp"
-#include "cru/platform/graph/TextLayout.hpp"
+#include "cru/platform/graphics/TextLayout.hpp"
#include <limits>
#include <memory>
-namespace cru::platform::graph::win::direct {
+namespace cru::platform::graphics::win::direct {
class DWriteFont;
class DWriteTextLayout : public DirectGraphResource,
@@ -38,7 +38,7 @@ class DWriteTextLayout : public DirectGraphResource,
void SetMaxWidth(float max_width) override;
void SetMaxHeight(float max_height) override;
- Rect GetTextBounds() override;
+ Rect GetTextBounds(bool includingTrailingSpace = false) 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<Rect> TextRangeRect(const TextRange& text_range) override;
@@ -52,4 +52,4 @@ class DWriteTextLayout : public DirectGraphResource,
float max_height_ = std::numeric_limits<float>::max();
Microsoft::WRL::ComPtr<IDWriteTextLayout> text_layout_;
};
-} // namespace cru::platform::graph::win::direct
+} // namespace cru::platform::graphics::win::direct
diff --git a/include/cru/win/graphics/direct/WindowPainter.hpp b/include/cru/win/graphics/direct/WindowPainter.hpp
new file mode 100644
index 00000000..b5faf7b5
--- /dev/null
+++ b/include/cru/win/graphics/direct/WindowPainter.hpp
@@ -0,0 +1,21 @@
+#pragma once
+#include "Painter.hpp"
+#include "WindowRenderTarget.hpp"
+
+namespace cru::platform::graphics::win::direct {
+class D2DWindowPainter : public graphics::win::direct::D2DPainter {
+ public:
+ explicit D2DWindowPainter(D2DWindowRenderTarget* window);
+
+ CRU_DELETE_COPY(D2DWindowPainter)
+ CRU_DELETE_MOVE(D2DWindowPainter)
+
+ ~D2DWindowPainter() override;
+
+ protected:
+ void DoEndDraw() override;
+
+ private:
+ D2DWindowRenderTarget* render_target_;
+};
+} // namespace cru::platform::graphics::win::direct
diff --git a/include/cru/win/graphics/direct/WindowRenderTarget.hpp b/include/cru/win/graphics/direct/WindowRenderTarget.hpp
new file mode 100644
index 00000000..75b1bf20
--- /dev/null
+++ b/include/cru/win/graphics/direct/WindowRenderTarget.hpp
@@ -0,0 +1,42 @@
+#pragma once
+#include "Factory.hpp"
+
+namespace cru::platform::graphics::win::direct {
+// Represents a window render target.
+class D2DWindowRenderTarget : public Object {
+ public:
+ D2DWindowRenderTarget(gsl::not_null<DirectGraphFactory*> factory, HWND hwnd);
+
+ CRU_DELETE_COPY(D2DWindowRenderTarget)
+ CRU_DELETE_MOVE(D2DWindowRenderTarget)
+
+ ~D2DWindowRenderTarget() override = default;
+
+ public:
+ graphics::win::direct::DirectGraphFactory* GetDirectFactory() const {
+ return factory_;
+ }
+
+ ID2D1DeviceContext* GetD2D1DeviceContext() {
+ return d2d1_device_context_.Get();
+ }
+
+ void SetDpi(float x, float y);
+
+ // Resize the underlying buffer.
+ void ResizeBuffer(int width, int height);
+
+ // Present the data of the underlying buffer to the window.
+ void Present();
+
+ private:
+ void CreateTargetBitmap();
+
+ private:
+ DirectGraphFactory* factory_;
+ HWND hwnd_;
+ Microsoft::WRL::ComPtr<ID2D1DeviceContext> d2d1_device_context_;
+ Microsoft::WRL::ComPtr<IDXGISwapChain1> dxgi_swap_chain_;
+ Microsoft::WRL::ComPtr<ID2D1Bitmap1> target_bitmap_;
+};
+} // namespace cru::platform::graphics::win::direct
diff --git a/include/cru/win/native/Base.hpp b/include/cru/win/gui/Base.hpp
index a50c6dd1..00782663 100644
--- a/include/cru/win/native/Base.hpp
+++ b/include/cru/win/gui/Base.hpp
@@ -3,17 +3,14 @@
#include "cru/common/Base.hpp"
-namespace cru::platform::native::win {
+namespace cru::platform::gui::win {
class GodWindow;
class TimerManager;
class WinCursor;
class WinCursorManager;
class WindowClass;
class WindowManager;
-class WindowRenderTarget;
class WinNativeWindow;
-class WinNativeWindowResolver;
class WinUiApplication;
-class WinInputMethodManager;
-class WinInputMethodContextRef;
-} // namespace cru::platform::native::win
+class WinInputMethodContext;
+} // namespace cru::platform::gui::win
diff --git a/include/cru/win/native/Cursor.hpp b/include/cru/win/gui/Cursor.hpp
index 373b9170..e7c76879 100644
--- a/include/cru/win/native/Cursor.hpp
+++ b/include/cru/win/gui/Cursor.hpp
@@ -1,13 +1,13 @@
#pragma once
#include "Resource.hpp"
-#include "cru/platform/native/Cursor.hpp"
+#include "cru/platform/gui/Cursor.hpp"
#include <memory>
-namespace cru::platform::native::win {
+namespace cru::platform::gui::win {
class WinCursor : public WinNativeResource, public virtual ICursor {
- CRU_DEFINE_CLASS_LOG_TAG(u"cru::platform::native::win::WinCursor")
+ CRU_DEFINE_CLASS_LOG_TAG(u"cru::platform::gui::win::WinCursor")
public:
WinCursor(HCURSOR handle, bool auto_destroy);
@@ -45,5 +45,6 @@ class WinCursorManager : public WinNativeResource,
private:
std::shared_ptr<WinCursor> sys_arrow_;
std::shared_ptr<WinCursor> sys_hand_;
+ std::shared_ptr<WinCursor> sys_ibeam_;
};
-} // namespace cru::platform::native::win
+} // namespace cru::platform::gui::win
diff --git a/include/cru/win/native/Exception.hpp b/include/cru/win/gui/Exception.hpp
index 6a5265c1..895e6c14 100644
--- a/include/cru/win/native/Exception.hpp
+++ b/include/cru/win/gui/Exception.hpp
@@ -1,7 +1,7 @@
#pragma once
#include "../Exception.hpp"
-namespace cru::platform::native::win {
+namespace cru::platform::gui::win {
using platform::win::Win32Error;
using platform::win::HResultError;
-} // namespace cru::platform::native::win
+} // namespace cru::platform::gui::win
diff --git a/include/cru/win/native/GodWindow.hpp b/include/cru/win/gui/GodWindow.hpp
index 8b20e01f..0343b159 100644
--- a/include/cru/win/native/GodWindow.hpp
+++ b/include/cru/win/gui/GodWindow.hpp
@@ -1,11 +1,14 @@
#pragma once
#include "Base.hpp"
+#include "WindowNativeMessageEventArgs.hpp"
+#include "cru/common/Event.hpp"
+
#include <memory>
-namespace cru::platform::native::win {
+namespace cru::platform::gui::win {
class GodWindow : public Object {
- CRU_DEFINE_CLASS_LOG_TAG(u"cru::platform::native::win::GodWindow")
+ CRU_DEFINE_CLASS_LOG_TAG(u"cru::platform::gui::win::GodWindow")
public:
explicit GodWindow(WinUiApplication* application);
@@ -20,10 +23,16 @@ class GodWindow : public Object {
bool HandleGodWindowMessage(HWND hwnd, UINT msg, WPARAM w_param,
LPARAM l_param, LRESULT* result);
+ IEvent<WindowNativeMessageEventArgs&>* MessageEvent() {
+ return &message_event_;
+ }
+
private:
WinUiApplication* application_;
std::unique_ptr<WindowClass> god_window_class_;
HWND hwnd_;
+
+ Event<WindowNativeMessageEventArgs&> message_event_;
};
-} // namespace cru::platform::native::win
+} // namespace cru::platform::gui::win
diff --git a/include/cru/win/native/InputMethod.hpp b/include/cru/win/gui/InputMethod.hpp
index 113f460d..51a007d8 100644
--- a/include/cru/win/native/InputMethod.hpp
+++ b/include/cru/win/gui/InputMethod.hpp
@@ -6,13 +6,13 @@
#include "Resource.hpp"
#include "WindowNativeMessageEventArgs.hpp"
-#include "cru/platform/native/InputMethod.hpp"
+#include "cru/platform/gui/InputMethod.hpp"
#include <imm.h>
-namespace cru::platform::native::win {
+namespace cru::platform::gui::win {
class AutoHIMC : public Object {
- CRU_DEFINE_CLASS_LOG_TAG(u"cru::platform::native::win::AutoHIMC")
+ CRU_DEFINE_CLASS_LOG_TAG(u"cru::platform::gui::win::AutoHIMC")
public:
explicit AutoHIMC(HWND hwnd);
@@ -35,7 +35,7 @@ class AutoHIMC : public Object {
class WinInputMethodContext : public WinNativeResource,
public virtual IInputMethodContext {
- CRU_DEFINE_CLASS_LOG_TAG(u"cru::platform::native::win::WinInputMethodContext")
+ CRU_DEFINE_CLASS_LOG_TAG(u"cru::platform::gui::win::WinInputMethodContext")
public:
WinInputMethodContext(gsl::not_null<WinNativeWindow*> window);
@@ -72,31 +72,16 @@ class WinInputMethodContext : public WinNativeResource,
std::u16string GetResultString();
- std::optional<AutoHIMC> TryGetHIMC();
+ AutoHIMC GetHIMC();
private:
- std::shared_ptr<INativeWindowResolver> native_window_resolver_;
+ WinNativeWindow* native_window_;
- std::vector<EventRevokerGuard> event_revoker_guards_;
+ EventRevokerListGuard event_guard_;
Event<std::nullptr_t> composition_start_event_;
Event<std::nullptr_t> composition_end_event_;
Event<std::nullptr_t> composition_event_;
Event<std::u16string_view> 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<IInputMethodContext> GetContext(
- INativeWindow* window) override;
-};
-} // namespace cru::platform::native::win
+} // namespace cru::platform::gui::win
diff --git a/include/cru/win/gui/Keyboard.hpp b/include/cru/win/gui/Keyboard.hpp
new file mode 100644
index 00000000..5b98833c
--- /dev/null
+++ b/include/cru/win/gui/Keyboard.hpp
@@ -0,0 +1,9 @@
+#pragma once
+#include "Base.hpp"
+
+#include "cru/platform/gui/Keyboard.hpp"
+
+namespace cru::platform::gui::win {
+KeyCode VirtualKeyToKeyCode(int virtual_key);
+KeyModifier RetrieveKeyMofifier();
+} // namespace cru::platform::gui::win
diff --git a/include/cru/win/native/Resource.hpp b/include/cru/win/gui/Resource.hpp
index 0de0e1a8..1f6f0a4a 100644
--- a/include/cru/win/native/Resource.hpp
+++ b/include/cru/win/gui/Resource.hpp
@@ -3,7 +3,7 @@
#include "cru/platform/Resource.hpp"
-namespace cru::platform::native::win {
+namespace cru::platform::gui::win {
class WinNativeResource : public Object, public virtual INativeResource {
public:
static constexpr std::u16string_view k_platform_id = u"Windows";
@@ -20,4 +20,4 @@ class WinNativeResource : public Object, public virtual INativeResource {
public:
std::u16string_view GetPlatformId() const final { return k_platform_id; }
};
-} // namespace cru::platform::native::win
+} // namespace cru::platform::gui::win
diff --git a/include/cru/win/native/UiApplication.hpp b/include/cru/win/gui/UiApplication.hpp
index cbc08af7..4cf46858 100644
--- a/include/cru/win/native/UiApplication.hpp
+++ b/include/cru/win/gui/UiApplication.hpp
@@ -1,15 +1,16 @@
#pragma once
#include "Resource.hpp"
-#include "cru/platform/native/UiApplication.hpp"
+#include "cru/platform/gui/Base.hpp"
+#include "cru/platform/gui/UiApplication.hpp"
#include <memory>
-namespace cru::platform::graph::win::direct {
+namespace cru::platform::graphics::win::direct {
class DirectGraphFactory;
}
-namespace cru::platform::native::win {
+namespace cru::platform::gui::win {
class WinUiApplication : public WinNativeResource,
public virtual IUiApplication {
public:
@@ -32,7 +33,7 @@ class WinUiApplication : public WinNativeResource,
void AddOnQuitHandler(std::function<void()> handler) override;
- void InvokeLater(std::function<void()> action) override;
+ long long SetImmediate(std::function<void()> action) override;
long long SetTimeout(std::chrono::milliseconds milliseconds,
std::function<void()> action) override;
long long SetInterval(std::chrono::milliseconds milliseconds,
@@ -40,17 +41,15 @@ class WinUiApplication : public WinNativeResource,
void CancelTimer(long long id) override;
std::vector<INativeWindow*> GetAllWindow() override;
- std::shared_ptr<INativeWindowResolver> CreateWindow(
- INativeWindow* parent) override;
+ INativeWindow* CreateWindow(INativeWindow* parent, CreateWindowFlag flag) override;
- cru::platform::graph::IGraphFactory* GetGraphFactory() override;
+ cru::platform::graphics::IGraphFactory* GetGraphFactory() override;
- cru::platform::graph::win::direct::DirectGraphFactory* GetDirectFactory() {
+ cru::platform::graphics::win::direct::DirectGraphFactory* GetDirectFactory() {
return graph_factory_.get();
}
ICursorManager* GetCursorManager() override;
- IInputMethodManager* GetInputMethodManager() override;
HINSTANCE GetInstanceHandle() const { return instance_handle_; }
@@ -61,7 +60,7 @@ class WinUiApplication : public WinNativeResource,
private:
HINSTANCE instance_handle_;
- std::unique_ptr<cru::platform::graph::win::direct::DirectGraphFactory>
+ std::unique_ptr<cru::platform::graphics::win::direct::DirectGraphFactory>
graph_factory_;
std::unique_ptr<GodWindow> god_window_;
@@ -69,8 +68,7 @@ class WinUiApplication : public WinNativeResource,
std::unique_ptr<WindowManager> window_manager_;
std::unique_ptr<WinCursorManager> cursor_manager_;
- std::unique_ptr<WinInputMethodManager> input_method_manager_;
std::vector<std::function<void()>> quit_handlers_;
};
-} // namespace cru::platform::native::win
+} // namespace cru::platform::gui::win
diff --git a/include/cru/win/native/Window.hpp b/include/cru/win/gui/Window.hpp
index 3e0b11cd..3ba9ef68 100644
--- a/include/cru/win/native/Window.hpp
+++ b/include/cru/win/gui/Window.hpp
@@ -2,13 +2,16 @@
#include "Resource.hpp"
#include "WindowNativeMessageEventArgs.hpp"
-#include "cru/platform/native/Window.hpp"
+#include "cru/platform/GraphBase.hpp"
+#include "cru/platform/gui/Base.hpp"
+#include "cru/platform/gui/Window.hpp"
+#include "cru/win/graphics/direct/WindowRenderTarget.hpp"
#include <memory>
-namespace cru::platform::native::win {
+namespace cru::platform::gui::win {
class WinNativeWindow : public WinNativeResource, public virtual INativeWindow {
- CRU_DEFINE_CLASS_LOG_TAG(u"cru::platform::native::win::WinNativeWindow")
+ CRU_DEFINE_CLASS_LOG_TAG(u"cru::platform::gui::win::WinNativeWindow")
public:
WinNativeWindow(WinUiApplication* application, WindowClass* window_class,
@@ -20,10 +23,6 @@ class WinNativeWindow : public WinNativeResource, public virtual INativeWindow {
~WinNativeWindow() override;
public:
- std::shared_ptr<INativeWindowResolver> GetResolver() override {
- return std::static_pointer_cast<INativeWindowResolver>(resolver_);
- }
-
void Close() override;
WinNativeWindow* GetParent() override { return parent_window_; }
@@ -48,7 +47,7 @@ class WinNativeWindow : public WinNativeResource, public virtual INativeWindow {
bool ReleaseMouse() override;
void RequestRepaint() override;
- std::unique_ptr<graph::IPainter> BeginPaint() override;
+ std::unique_ptr<graphics::IPainter> BeginPaint() override;
void SetCursor(std::shared_ptr<ICursor> cursor) override;
@@ -60,18 +59,18 @@ class WinNativeWindow : public WinNativeResource, public virtual INativeWindow {
return &mouse_enter_leave_event_;
}
IEvent<Point>* MouseMoveEvent() override { return &mouse_move_event_; }
- IEvent<platform::native::NativeMouseButtonEventArgs>* MouseDownEvent()
+ IEvent<platform::gui::NativeMouseButtonEventArgs>* MouseDownEvent()
override {
return &mouse_down_event_;
}
- IEvent<platform::native::NativeMouseButtonEventArgs>* MouseUpEvent()
+ IEvent<platform::gui::NativeMouseButtonEventArgs>* MouseUpEvent()
override {
return &mouse_up_event_;
}
- IEvent<platform::native::NativeKeyEventArgs>* KeyDownEvent() override {
+ IEvent<platform::gui::NativeKeyEventArgs>* KeyDownEvent() override {
return &key_down_event_;
}
- IEvent<platform::native::NativeKeyEventArgs>* KeyUpEvent() override {
+ IEvent<platform::gui::NativeKeyEventArgs>* KeyUpEvent() override {
return &key_up_event_;
}
@@ -79,16 +78,40 @@ class WinNativeWindow : public WinNativeResource, public virtual INativeWindow {
return &native_message_event_;
}
+ IInputMethodContext* GetInputMethodContext() override;
+
// Get the handle of the window. Return null if window is invalid.
HWND GetWindowHandle() const { return hwnd_; }
bool HandleNativeWindowMessage(HWND hwnd, UINT msg, WPARAM w_param,
LPARAM l_param, LRESULT* result);
- WindowRenderTarget* GetWindowRenderTarget() const {
+ graphics::win::direct::D2DWindowRenderTarget* GetWindowRenderTarget() const {
return window_render_target_.get();
}
+ //*************** region: dpi ***************
+ float GetDpi() const { return dpi_; }
+
+ inline int DipToPixel(const float dip) {
+ return static_cast<int>(dip * GetDpi() / 96.0f);
+ }
+
+ inline POINT DipToPixel(const Point& dip_point) {
+ POINT result;
+ result.x = DipToPixel(dip_point.x);
+ result.y = DipToPixel(dip_point.y);
+ return result;
+ }
+
+ inline float PixelToDip(const int pixel) {
+ return static_cast<float>(pixel) * 96.0f / GetDpi();
+ }
+
+ inline Point PixelToDip(const POINT& pi_point) {
+ return Point(PixelToDip(pi_point.x), PixelToDip(pi_point.y));
+ }
+
private:
// Get the client rect in pixel.
RECT GetClientRectPixel();
@@ -104,8 +127,8 @@ class WinNativeWindow : public WinNativeResource, public virtual INativeWindow {
void OnMouseMoveInternal(POINT point);
void OnMouseLeaveInternal();
- void OnMouseDownInternal(platform::native::MouseButton button, POINT point);
- void OnMouseUpInternal(platform::native::MouseButton button, POINT point);
+ void OnMouseDownInternal(platform::gui::MouseButton button, POINT point);
+ void OnMouseUpInternal(platform::gui::MouseButton button, POINT point);
void OnMouseWheelInternal(short delta, POINT point);
void OnKeyDownInternal(int virtual_code);
@@ -124,53 +147,32 @@ class WinNativeWindow : public WinNativeResource, public virtual INativeWindow {
// again.
bool sync_flag_ = false;
- std::shared_ptr<WinNativeWindowResolver> resolver_;
-
HWND hwnd_;
WinNativeWindow* parent_window_;
+ float dpi_;
+
bool has_focus_ = false;
bool is_mouse_in_ = false;
- std::unique_ptr<WindowRenderTarget> window_render_target_;
+ std::unique_ptr<graphics::win::direct::D2DWindowRenderTarget>
+ window_render_target_;
std::shared_ptr<WinCursor> cursor_;
+ std::unique_ptr<WinInputMethodContext> input_method_context_;
+
Event<std::nullptr_t> destroy_event_;
Event<std::nullptr_t> paint_event_;
Event<Size> resize_event_;
Event<FocusChangeType> focus_event_;
Event<MouseEnterLeaveType> mouse_enter_leave_event_;
Event<Point> mouse_move_event_;
- Event<platform::native::NativeMouseButtonEventArgs> mouse_down_event_;
- Event<platform::native::NativeMouseButtonEventArgs> mouse_up_event_;
- Event<platform::native::NativeKeyEventArgs> key_down_event_;
- Event<platform::native::NativeKeyEventArgs> key_up_event_;
+ Event<platform::gui::NativeMouseButtonEventArgs> mouse_down_event_;
+ Event<platform::gui::NativeMouseButtonEventArgs> mouse_up_event_;
+ Event<platform::gui::NativeKeyEventArgs> key_down_event_;
+ Event<platform::gui::NativeKeyEventArgs> key_up_event_;
Event<WindowNativeMessageEventArgs&> native_message_event_;
};
-
-class WinNativeWindowResolver : public WinNativeResource,
- public virtual INativeWindowResolver {
- friend WinNativeWindow::~WinNativeWindow();
-
- public:
- WinNativeWindowResolver(WinNativeWindow* window) : window_(window) {}
-
- CRU_DELETE_COPY(WinNativeWindowResolver)
- CRU_DELETE_MOVE(WinNativeWindowResolver)
-
- ~WinNativeWindowResolver() override = default;
-
- public:
- INativeWindow* Resolve() override { return window_; }
-
- private:
- void Reset();
-
- private:
- WinNativeWindow* window_;
-};
-
-WinNativeWindow* Resolve(gsl::not_null<INativeWindowResolver*> resolver);
-} // namespace cru::platform::native::win
+} // namespace cru::platform::gui::win
diff --git a/include/cru/win/native/WindowClass.hpp b/include/cru/win/gui/WindowClass.hpp
index fdd55065..2c07b68f 100644
--- a/include/cru/win/native/WindowClass.hpp
+++ b/include/cru/win/gui/WindowClass.hpp
@@ -3,7 +3,7 @@
#include <string>
-namespace cru::platform::native::win {
+namespace cru::platform::gui::win {
class WindowClass : public Object {
public:
WindowClass(std::wstring name, WNDPROC window_proc, HINSTANCE h_instance);
@@ -21,4 +21,4 @@ class WindowClass : public Object {
std::wstring name_;
ATOM atom_;
};
-} // namespace cru::platform::native::win
+} // namespace cru::platform::gui::win
diff --git a/include/cru/win/native/WindowNativeMessageEventArgs.hpp b/include/cru/win/gui/WindowNativeMessageEventArgs.hpp
index 84a7a123..834ba3c2 100644
--- a/include/cru/win/native/WindowNativeMessageEventArgs.hpp
+++ b/include/cru/win/gui/WindowNativeMessageEventArgs.hpp
@@ -3,7 +3,7 @@
#include "cru/common/Base.hpp"
-namespace cru::platform::native::win {
+namespace cru::platform::gui::win {
struct WindowNativeMessage {
HWND hwnd;
UINT msg;
@@ -37,4 +37,4 @@ class WindowNativeMessageEventArgs : public Object {
LRESULT result_;
bool handled_ = false;
};
-} // namespace cru::platform::native::win
+} // namespace cru::platform::gui::win
diff --git a/include/cru/win/native/Keyboard.hpp b/include/cru/win/native/Keyboard.hpp
deleted file mode 100644
index 790e0015..00000000
--- a/include/cru/win/native/Keyboard.hpp
+++ /dev/null
@@ -1,9 +0,0 @@
-#pragma once
-#include "Base.hpp"
-
-#include "cru/platform/native/Keyboard.hpp"
-
-namespace cru::platform::native::win {
-KeyCode VirtualKeyToKeyCode(int virtual_key);
-KeyModifier RetrieveKeyMofifier();
-} // namespace cru::platform::native::win
diff --git a/include/cru/win/native/WindowRenderTarget.hpp b/include/cru/win/native/WindowRenderTarget.hpp
deleted file mode 100644
index 83ac1e03..00000000
--- a/include/cru/win/native/WindowRenderTarget.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<ID2D1DeviceContext> d2d1_device_context_;
- Microsoft::WRL::ComPtr<IDXGISwapChain1> dxgi_swap_chain_;
- Microsoft::WRL::ComPtr<ID2D1Bitmap1> target_bitmap_;
-};
-} // namespace cru::platform::native::win
diff --git a/src/common/CMakeLists.txt b/src/common/CMakeLists.txt
index 6a18ef2b..73ad9456 100644
--- a/src/common/CMakeLists.txt
+++ b/src/common/CMakeLists.txt
@@ -7,6 +7,7 @@ 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}/Format.hpp
${CRU_BASE_INCLUDE_DIR}/Logger.hpp
${CRU_BASE_INCLUDE_DIR}/PreConfig.hpp
${CRU_BASE_INCLUDE_DIR}/SelfResolvable.hpp
diff --git a/src/common/Logger.cpp b/src/common/Logger.cpp
index dfa25347..af1dd692 100644
--- a/src/common/Logger.cpp
+++ b/src/common/Logger.cpp
@@ -73,7 +73,6 @@ void Logger::Log(LogLevel level, std::u16string_view tag,
}
#endif
for (const auto &source : sources_) {
- auto now = std::time(nullptr);
source->Write(level, fmt::format(u"[{}] {} {}: {}\n", GetLogTime(),
LogLevelToString(level), tag, s));
}
diff --git a/src/common/StringUtil.cpp b/src/common/StringUtil.cpp
index fc6d6349..0cadc545 100644
--- a/src/common/StringUtil.cpp
+++ b/src/common/StringUtil.cpp
@@ -1,4 +1,6 @@
#include "cru/common/StringUtil.hpp"
+#include "cru/common/Base.hpp"
+#include "gsl/gsl_util"
namespace cru {
namespace {
@@ -191,8 +193,8 @@ void Utf8EncodeCodePointAppend(CodePoint code_point, std::string& str) {
}
void Utf16EncodeCodePointAppend(CodePoint code_point, std::u16string& str) {
- if (code_point >= 0 && code_point <= 0xD7FF ||
- code_point >= 0xE000 && code_point <= 0xFFFF) {
+ if ((code_point >= 0 && code_point <= 0xD7FF) ||
+ (code_point >= 0xE000 && code_point <= 0xFFFF)) {
str.push_back(static_cast<char16_t>(code_point));
} else if (code_point >= 0x10000 && code_point <= 0x10FFFF) {
std::uint32_t u = code_point - 0x10000;
@@ -220,4 +222,66 @@ std::u16string ToUtf16(std::string_view s) {
}
return result;
}
+
+bool Utf16IsValidInsertPosition(std::u16string_view s, gsl::index position) {
+ if (position < 0) return false;
+ if (position > static_cast<gsl::index>(s.size())) return false;
+ if (position == 0) return true;
+ if (position == static_cast<gsl::index>(s.size())) return true;
+ return !IsUtf16SurrogatePairTrailing(s[position]);
+}
+
+gsl::index Utf16BackwardUntil(std::u16string_view str, gsl::index position,
+ const std::function<bool(CodePoint)>& predicate) {
+ if (position <= 0) return position;
+ while (true) {
+ gsl::index p = position;
+ auto c = Utf16PreviousCodePoint(str, p, &position);
+ if (predicate(c)) return p;
+ if (c == k_invalid_code_point) return p;
+ }
+ UnreachableCode();
+}
+
+gsl::index Utf16ForwardUntil(std::u16string_view str, gsl::index position,
+ const std::function<bool(CodePoint)>& predicate) {
+ if (position >= static_cast<gsl::index>(str.size())) return position;
+ while (true) {
+ gsl::index p = position;
+ auto c = Utf16NextCodePoint(str, p, &position);
+ if (predicate(c)) return p;
+ if (c == k_invalid_code_point) return p;
+ }
+ UnreachableCode();
+}
+
+inline bool IsSpace(CodePoint c) { return c == 0x20 || c == 0xA; }
+
+gsl::index Utf16PreviousWord(std::u16string_view str, gsl::index position,
+ bool* is_space) {
+ if (position <= 0) return position;
+ auto c = Utf16PreviousCodePoint(str, position, nullptr);
+ if (IsSpace(c)) { // TODO: Currently only test against 0x20(space).
+ if (is_space) *is_space = true;
+ return Utf16BackwardUntil(str, position,
+ [](CodePoint c) { return !IsSpace(c); });
+ } else {
+ if (is_space) *is_space = false;
+ return Utf16BackwardUntil(str, position, IsSpace);
+ }
+}
+
+gsl::index Utf16NextWord(std::u16string_view str, gsl::index position,
+ bool* is_space) {
+ if (position >= static_cast<gsl::index>(str.size())) return position;
+ auto c = Utf16NextCodePoint(str, position, nullptr);
+ if (IsSpace(c)) { // TODO: Currently only test against 0x20(space).
+ if (is_space) *is_space = true;
+ return Utf16ForwardUntil(str, position,
+ [](CodePoint c) { return !IsSpace(c); });
+ } else {
+ if (is_space) *is_space = false;
+ return Utf16ForwardUntil(str, position, IsSpace);
+ }
+}
} // namespace cru
diff --git a/src/platform/CMakeLists.txt b/src/platform/CMakeLists.txt
index 51253b56..623ec08f 100644
--- a/src/platform/CMakeLists.txt
+++ b/src/platform/CMakeLists.txt
@@ -10,5 +10,5 @@ target_sources(cru_platform_base INTERFACE
)
target_link_libraries(cru_platform_base INTERFACE cru_base)
-add_subdirectory(graph)
-add_subdirectory(native)
+add_subdirectory(graphics)
+add_subdirectory(gui)
diff --git a/src/platform/graph/CMakeLists.txt b/src/platform/graph/CMakeLists.txt
deleted file mode 100644
index 3bf11e8d..00000000
--- a/src/platform/graph/CMakeLists.txt
+++ /dev/null
@@ -1,14 +0,0 @@
-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}/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/graphics/CMakeLists.txt b/src/platform/graphics/CMakeLists.txt
new file mode 100644
index 00000000..5f841267
--- /dev/null
+++ b/src/platform/graphics/CMakeLists.txt
@@ -0,0 +1,14 @@
+set(CRU_PLATFORM_GRAPHICS_INCLUDE_DIR ${CRU_INCLUDE_DIR}/cru/platform/graphics)
+add_library(cru_platform_graphics INTERFACE)
+target_sources(cru_platform_graphics INTERFACE
+ ${CRU_PLATFORM_GRAPHICS_INCLUDE_DIR}/Base.hpp
+ ${CRU_PLATFORM_GRAPHICS_INCLUDE_DIR}/Brush.hpp
+ ${CRU_PLATFORM_GRAPHICS_INCLUDE_DIR}/Font.hpp
+ ${CRU_PLATFORM_GRAPHICS_INCLUDE_DIR}/Geometry.hpp
+ ${CRU_PLATFORM_GRAPHICS_INCLUDE_DIR}/Factory.hpp
+ ${CRU_PLATFORM_GRAPHICS_INCLUDE_DIR}/Resource.hpp
+ ${CRU_PLATFORM_GRAPHICS_INCLUDE_DIR}/Painter.hpp
+ ${CRU_PLATFORM_GRAPHICS_INCLUDE_DIR}/TextLayout.hpp
+ ${CRU_PLATFORM_GRAPHICS_INCLUDE_DIR}/util/Painter.hpp
+)
+target_link_libraries(cru_platform_graphics INTERFACE cru_platform_base)
diff --git a/src/platform/gui/CMakeLists.txt b/src/platform/gui/CMakeLists.txt
new file mode 100644
index 00000000..aca7620c
--- /dev/null
+++ b/src/platform/gui/CMakeLists.txt
@@ -0,0 +1,14 @@
+set(CRU_PLATFORM_GUI_INCLUDE_DIR ${CRU_INCLUDE_DIR}/cru/platform/gui)
+add_library(cru_platform_gui STATIC
+ Keyboard.cpp
+ UiApplication.cpp
+)
+target_sources(cru_platform_gui PUBLIC
+ ${CRU_PLATFORM_GUI_INCLUDE_DIR}/Base.hpp
+ ${CRU_PLATFORM_GUI_INCLUDE_DIR}/Cursor.hpp
+ ${CRU_PLATFORM_GUI_INCLUDE_DIR}/InputMethod.hpp
+ ${CRU_PLATFORM_GUI_INCLUDE_DIR}/Keyboard.hpp
+ ${CRU_PLATFORM_GUI_INCLUDE_DIR}/Window.hpp
+ ${CRU_PLATFORM_GUI_INCLUDE_DIR}/UiApplication.hpp
+)
+target_link_libraries(cru_platform_gui PUBLIC cru_platform_graphics)
diff --git a/src/platform/gui/Keyboard.cpp b/src/platform/gui/Keyboard.cpp
new file mode 100644
index 00000000..24880e00
--- /dev/null
+++ b/src/platform/gui/Keyboard.cpp
@@ -0,0 +1,142 @@
+#include "cru/platform/gui/Keyboard.hpp"
+
+#include <array>
+#include <string>
+#include <string_view>
+
+namespace cru::platform::gui {
+constexpr std::array<std::u16string_view,
+ static_cast<int>(KeyCode::NumPad9) + 1>
+ key_code_string_list{u"Unknown",
+ u"LeftButton",
+ u"MiddleButton",
+ u"RightButton",
+ u"Escape",
+ u"F1",
+ u"F2",
+ u"F3",
+ u"F4",
+ u"F5",
+ u"F6",
+ u"F7",
+ u"F8",
+ u"F9",
+ u"F10",
+ u"F11",
+ u"F12",
+ u"N0",
+ u"N1",
+ u"N2",
+ u"N3",
+ u"N4",
+ u"N5",
+ u"N6",
+ u"N7",
+ u"N8",
+ u"N9",
+ u"A",
+ u"B",
+ u"C",
+ u"D",
+ u"E",
+ u"F",
+ u"G",
+ u"H",
+ u"I",
+ u"J",
+ u"K",
+ u"L",
+ u"M",
+ u"N",
+ u"O",
+ u"P",
+ u"Q",
+ u"R",
+ u"S",
+ u"T",
+ u"U",
+ u"V",
+ u"W",
+ u"X",
+ u"Y",
+ u"Z",
+ u"GraveAccent",
+ u"Tab",
+ u"CapsLock",
+ u"LeftShift",
+ u"LeftCtrl",
+ u"LeftSuper",
+ u"LeftAlt",
+ u"Minus",
+ u"Equal",
+ u"Backspace",
+ u"LeftSquareBracket",
+ u"RightSquareBracket",
+ u"BackSlash",
+ u"Semicolon",
+ u"Quote",
+ u"Comma",
+ u"Period",
+ u"Slash",
+ u"RightShift",
+ u"RightCtrl",
+ u"RightSuper",
+ u"RightAlt",
+ u"Insert",
+ u"Delete",
+ u"Home",
+ u"End",
+ u"PageUp",
+ u"PageDown",
+ u"Up",
+ u"Left",
+ u"Down",
+ u"Right",
+ u"PrintScreen",
+ u"ScrollLock",
+ u"Pause",
+ u"NumPad0",
+ u"NumPad1",
+ u"NumPad2",
+ u"NumPad3",
+ u"NumPad4",
+ u"NumPad5",
+ u"NumPad6",
+ u"NumPad7",
+ u"NumPad8",
+ u"NumPad9"};
+
+std::u16string_view ToString(KeyCode key_code) {
+ if (static_cast<int>(key_code) < 0 ||
+ static_cast<int>(key_code) >=
+ static_cast<int>(key_code_string_list.size()))
+ return u"UNKNOWN_KEYCODENAME";
+
+ return key_code_string_list[static_cast<int>(key_code)];
+}
+
+std::u16string ToString(KeyModifier key_modifier,
+ std::u16string_view separator) {
+ std::vector<std::u16string> list;
+ if (key_modifier & KeyModifiers::shift) {
+ list.push_back(u"Shift");
+ }
+
+ if (key_modifier & KeyModifiers::ctrl) {
+ list.push_back(u"Ctrl");
+ }
+
+ if (key_modifier & KeyModifiers::alt) {
+ list.push_back(u"Shift");
+ }
+
+ if (list.empty()) return u"";
+ std::u16string result = list.front();
+ for (auto iter = list.cbegin() + 1; iter != list.cend(); ++iter) {
+ result += separator;
+ result += *iter;
+ }
+
+ return result;
+}
+} // namespace cru::platform::gui
diff --git a/src/platform/native/UiApplication.cpp b/src/platform/gui/UiApplication.cpp
index 200b10e0..f095361e 100644
--- a/src/platform/native/UiApplication.cpp
+++ b/src/platform/gui/UiApplication.cpp
@@ -1,6 +1,6 @@
-#include "cru/platform/native/UiApplication.hpp"
+#include "cru/platform/gui/UiApplication.hpp"
-namespace cru::platform::native {
+namespace cru::platform::gui {
IUiApplication* IUiApplication::instance = nullptr;
IUiApplication::IUiApplication() {
@@ -12,4 +12,4 @@ IUiApplication::IUiApplication() {
}
IUiApplication::~IUiApplication() { instance = nullptr; }
-} // namespace cru::platform::native
+} // namespace cru::platform::gui
diff --git a/src/platform/native/CMakeLists.txt b/src/platform/native/CMakeLists.txt
deleted file mode 100644
index c68a0958..00000000
--- a/src/platform/native/CMakeLists.txt
+++ /dev/null
@@ -1,13 +0,0 @@
-set(CRU_PLATFORM_NATIVE_INCLUDE_DIR ${CRU_INCLUDE_DIR}/cru/platform/native)
-add_library(cru_platform_native STATIC
- 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}/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/ui/CMakeLists.txt b/src/ui/CMakeLists.txt
index 6c50ec57..7d2792d6 100644
--- a/src/ui/CMakeLists.txt
+++ b/src/ui/CMakeLists.txt
@@ -2,53 +2,74 @@ set(CRU_UI_INCLUDE_DIR ${CRU_INCLUDE_DIR}/cru/ui)
add_library(cru_ui STATIC
Helper.hpp
- RoutedEventDispatch.hpp
+ host/RoutedEventDispatch.hpp
- ClickDetector.cpp
- ContentControl.cpp
- Control.cpp
Helper.cpp
- LayoutControl.cpp
- NoChildControl.cpp
- UiEvent.cpp
- UiHost.cpp
UiManager.cpp
- Window.cpp
+ components/Component.cpp
+ components/Menu.cpp
controls/Button.cpp
controls/Container.cpp
+ controls/ContentControl.cpp
+ controls/Control.cpp
controls/FlexLayout.cpp
+ controls/LayoutControl.cpp
+ controls/NoChildControl.cpp
+ controls/Popup.cpp
+ controls/RootControl.cpp
controls/StackLayout.cpp
controls/TextBlock.cpp
controls/TextBox.cpp
- controls/TextControlService.hpp
+ controls/TextHostControlService.cpp
+ controls/Window.cpp
+ events/UiEvent.cpp
+ helper/BorderStyle.cpp
+ helper/ClickDetector.cpp
+ helper/ShortcutHub.cpp
+ host/LayoutPaintCycler.cpp
+ host/WindowHost.cpp
render/BorderRenderObject.cpp
render/CanvasRenderObject.cpp
render/FlexLayoutRenderObject.cpp
render/LayoutHelper.cpp
render/RenderObject.cpp
+ render/ScrollBar.cpp
render/ScrollRenderObject.cpp
render/StackLayoutRenderObject.cpp
render/TextRenderObject.cpp
- render/WindowRenderObject.cpp
+ style/Condition.cpp
+ style/Styler.cpp
+ style/StyleRule.cpp
+ style/StyleRuleSet.cpp
)
target_sources(cru_ui PUBLIC
${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}/DebugFlags.hpp
${CRU_UI_INCLUDE_DIR}/UiManager.hpp
- ${CRU_UI_INCLUDE_DIR}/Window.hpp
+ ${CRU_UI_INCLUDE_DIR}/components/Component.hpp
+ ${CRU_UI_INCLUDE_DIR}/components/Menu.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/ContentControl.hpp
+ ${CRU_UI_INCLUDE_DIR}/controls/Control.hpp
${CRU_UI_INCLUDE_DIR}/controls/FlexLayout.hpp
+ ${CRU_UI_INCLUDE_DIR}/controls/IBorderControl.hpp
+ ${CRU_UI_INCLUDE_DIR}/controls/IClickableControl.hpp
+ ${CRU_UI_INCLUDE_DIR}/controls/LayoutControl.hpp
+ ${CRU_UI_INCLUDE_DIR}/controls/NoChildControl.hpp
+ ${CRU_UI_INCLUDE_DIR}/controls/Popup.hpp
+ ${CRU_UI_INCLUDE_DIR}/controls/RootControl.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}/controls/TextBox.hpp
+ ${CRU_UI_INCLUDE_DIR}/controls/TextHostControlService.hpp
+ ${CRU_UI_INCLUDE_DIR}/controls/Window.hpp
+ ${CRU_UI_INCLUDE_DIR}/events/UiEvent.hpp
+ ${CRU_UI_INCLUDE_DIR}/helper/ClickDetector.hpp
+ ${CRU_UI_INCLUDE_DIR}/helper/ShortcutHub.hpp
+ ${CRU_UI_INCLUDE_DIR}/host/LayoutPaintCycler.hpp
+ ${CRU_UI_INCLUDE_DIR}/host/WindowHost.hpp
${CRU_UI_INCLUDE_DIR}/render/Base.hpp
${CRU_UI_INCLUDE_DIR}/render/BorderRenderObject.hpp
${CRU_UI_INCLUDE_DIR}/render/CanvasRenderObject.hpp
@@ -57,9 +78,14 @@ target_sources(cru_ui PUBLIC
${CRU_UI_INCLUDE_DIR}/render/LayoutRenderObject.hpp
${CRU_UI_INCLUDE_DIR}/render/MeasureRequirement.hpp
${CRU_UI_INCLUDE_DIR}/render/RenderObject.hpp
+ ${CRU_UI_INCLUDE_DIR}/render/ScrollBar.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
+ ${CRU_UI_INCLUDE_DIR}/style/ApplyBorderStyleInfo.hpp
+ ${CRU_UI_INCLUDE_DIR}/style/Condition.hpp
+ ${CRU_UI_INCLUDE_DIR}/style/Styler.hpp
+ ${CRU_UI_INCLUDE_DIR}/style/StyleRule.hpp
+ ${CRU_UI_INCLUDE_DIR}/style/StyleRuleSet.hpp
)
-target_link_libraries(cru_ui PUBLIC cru_platform_native)
+target_link_libraries(cru_ui PUBLIC cru_platform_gui)
diff --git a/src/ui/ContentControl.cpp b/src/ui/ContentControl.cpp
deleted file mode 100644
index 8d1a17d2..00000000
--- a/src/ui/ContentControl.cpp
+++ /dev/null
@@ -1,33 +0,0 @@
-#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<Window*>(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
deleted file mode 100644
index cd1367fe..00000000
--- a/src/ui/Control.cpp
+++ /dev/null
@@ -1,130 +0,0 @@
-#include "cru/ui/Control.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 <memory>
-
-namespace cru::ui {
-using platform::native::ICursor;
-using platform::native::IUiApplication;
-using platform::native::SystemCursorType;
-
-Control::Control() {
- MouseEnterEvent()->Direct()->AddHandler([this](event::MouseEventArgs&) {
- this->is_mouse_over_ = true;
- this->OnMouseHoverChange(true);
- });
-
- MouseLeaveEvent()->Direct()->AddHandler([this](event::MouseEventArgs&) {
- this->is_mouse_over_ = false;
- this->OnMouseHoverChange(true);
- });
-}
-
-void Control::_SetParent(Control* parent) {
- const auto old_parent = GetParent();
- parent_ = parent;
- const auto new_parent = GetParent();
- if (old_parent != new_parent) OnParentChanged(old_parent, new_parent);
-}
-
-void Control::_SetDescendantUiHost(UiHost* host) {
- if (host == nullptr && ui_host_ == nullptr) return;
-
- // You can only attach or detach window.
- Expects((host != nullptr && ui_host_ == nullptr) ||
- (host == nullptr && ui_host_ != nullptr));
-
- if (host == nullptr) {
- const auto old = ui_host_;
- TraverseDescendants([old](Control* control) {
- control->ui_host_ = nullptr;
- control->OnDetachFromHost(old);
- });
- } else
- TraverseDescendants([host](Control* control) {
- control->ui_host_ = host;
- control->OnAttachToHost(host);
- });
-}
-
-void Control::TraverseDescendants(
- const std::function<void(Control*)>& predicate) {
- _TraverseDescendants(this, predicate);
-}
-
-void Control::_TraverseDescendants(
- Control* control, const std::function<void(Control*)>& predicate) {
- predicate(control);
- for (auto c : control->GetChildren()) _TraverseDescendants(c, predicate);
-}
-
-bool Control::RequestFocus() {
- auto host = GetUiHost();
- if (host == nullptr) return false;
-
- return host->RequestFocusFor(this);
-}
-
-bool Control::HasFocus() {
- auto host = GetUiHost();
- if (host == nullptr) return false;
-
- return host->GetFocusControl() == this;
-}
-
-bool Control::CaptureMouse() {
- auto host = GetUiHost();
- if (host == nullptr) return false;
-
- return host->CaptureMouseFor(this);
-}
-
-bool Control::ReleaseMouse() {
- auto host = GetUiHost();
- if (host == nullptr) return false;
-
- return host->CaptureMouseFor(nullptr);
-}
-
-bool Control::IsMouseCaptured() {
- auto host = GetUiHost();
- if (host == nullptr) return false;
-
- return host->GetMouseCaptureControl() == this;
-}
-
-std::shared_ptr<ICursor> Control::GetCursor() { return cursor_; }
-
-std::shared_ptr<ICursor> Control::GetInheritedCursor() {
- Control* control = this;
- while (control != nullptr) {
- const auto cursor = control->GetCursor();
- if (cursor != nullptr) return cursor;
- control = control->GetParent();
- }
- return IUiApplication::GetInstance()->GetCursorManager()->GetSystemCursor(
- SystemCursorType::Arrow);
-}
-
-void Control::SetCursor(std::shared_ptr<ICursor> cursor) {
- cursor_ = std::move(cursor);
- const auto host = GetUiHost();
- if (host != nullptr) {
- host->UpdateCursor();
- }
-}
-
-void Control::OnParentChanged(Control* old_parent, Control* new_parent) {
- CRU_UNUSED(old_parent)
- CRU_UNUSED(new_parent)
-}
-
-void Control::OnAttachToHost(UiHost* host) { CRU_UNUSED(host) }
-
-void Control::OnDetachFromHost(UiHost* host) { CRU_UNUSED(host) }
-} // namespace cru::ui
diff --git a/src/ui/Helper.cpp b/src/ui/Helper.cpp
index 6f67e701..88ead993 100644
--- a/src/ui/Helper.cpp
+++ b/src/ui/Helper.cpp
@@ -1,11 +1,11 @@
#include "Helper.hpp"
-#include "cru/platform/graph/Factory.hpp"
-#include "cru/platform/native/UiApplication.hpp"
+#include "cru/platform/graphics/Factory.hpp"
+#include "cru/platform/gui/UiApplication.hpp"
namespace cru::ui {
-using cru::platform::graph::IGraphFactory;
-using cru::platform::native::IUiApplication;
+using cru::platform::graphics::IGraphFactory;
+using cru::platform::gui::IUiApplication;
IGraphFactory* GetGraphFactory() {
return IUiApplication::GetInstance()->GetGraphFactory();
diff --git a/src/ui/Helper.hpp b/src/ui/Helper.hpp
index 6923852f..327f91ff 100644
--- a/src/ui/Helper.hpp
+++ b/src/ui/Helper.hpp
@@ -12,6 +12,6 @@ struct IUiApplication;
} // namespace cru::platform
namespace cru::ui {
-cru::platform::graph::IGraphFactory* GetGraphFactory();
-cru::platform::native::IUiApplication* GetUiApplication();
+cru::platform::graphics::IGraphFactory* GetGraphFactory();
+cru::platform::gui::IUiApplication* GetUiApplication();
} // namespace cru::ui
diff --git a/src/ui/LayoutControl.cpp b/src/ui/LayoutControl.cpp
deleted file mode 100644
index 4813566b..00000000
--- a/src/ui/LayoutControl.cpp
+++ /dev/null
@@ -1,53 +0,0 @@
-#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<Window*>(control)); // Can't add a window as child.
- Expects(position >= 0);
- Expects(position <=
- static_cast<Index>(
- 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<Index>(
- 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
deleted file mode 100644
index 86861049..00000000
--- a/src/ui/NoChildControl.cpp
+++ /dev/null
@@ -1,5 +0,0 @@
-#include "cru/ui/NoChildControl.hpp"
-
-namespace cru::ui {
-const std::vector<Control*> NoChildControl::empty_control_vector{};
-}
diff --git a/src/ui/UiHost.cpp b/src/ui/UiHost.cpp
deleted file mode 100644
index d8dcb6da..00000000
--- a/src/ui/UiHost.cpp
+++ /dev/null
@@ -1,388 +0,0 @@
-#include "cru/ui/UiHost.hpp"
-
-#include "RoutedEventDispatch.hpp"
-#include "cru/common/Logger.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"
-#include "cru/ui/Window.hpp"
-#include "cru/ui/render/WindowRenderObject.hpp"
-
-namespace cru::ui {
-using platform::native::INativeWindow;
-using platform::native::IUiApplication;
-
-namespace event_names {
-#ifdef CRU_DEBUG
-// clang-format off
-#define CRU_DEFINE_EVENT_NAME(name) constexpr const char16_t* name = CRU_MAKE_UNICODE_LITERAL(name);
-// clang-format on
-#else
-#define CRU_DEFINE_EVENT_NAME(name) constexpr const char16_t* name = u"";
-#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)
-
-#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<Control*> GetAncestorList(Control* control) {
- std::list<Control*> l;
- while (control != nullptr) {
- l.push_front(control);
- control = control->GetParent();
- }
- return l;
-}
-
-Control* FindLowestCommonAncestor(Control* left, Control* right) {
- if (left == nullptr || right == nullptr) 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 <typename T>
-inline void BindNativeEvent(
- UiHost* host, INativeWindow* native_window, IEvent<T>* event,
- void (UiHost::*handler)(INativeWindow*, typename IEvent<T>::EventArgs),
- std::vector<EventRevokerGuard>& guard_pool) {
- guard_pool.push_back(EventRevokerGuard(event->AddHandler(
- std::bind(handler, host, native_window, std::placeholders::_1))));
-}
-} // namespace
-
-UiHost::UiHost(Window* window)
- : window_control_(window),
- mouse_hover_control_(nullptr),
- focus_control_(window),
- mouse_captured_control_(nullptr) {
- const auto ui_application = IUiApplication::GetInstance();
- native_window_resolver_ = ui_application->CreateWindow(nullptr);
-
- const auto native_window = native_window_resolver_->Resolve();
-
- auto input_method_context =
- ui_application->GetInputMethodManager()->GetContext(native_window);
- input_method_context->DisableIME();
-
- window->ui_host_ = this;
-
- root_render_object_ = std::make_unique<render::WindowRenderObject>(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() {
- log::TagDebug(log_tag, u"A relayout is requested.");
- if (!need_layout_) {
- platform::native::IUiApplication::GetInstance()->InvokeLater(
- [resolver = this->CreateResolver()] {
- if (const auto host = resolver.Resolve()) {
- host->Relayout();
- host->need_layout_ = false;
- 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(
- render::MeasureRequirement{client_size,
- render::MeasureSize::NotSpecified()},
- render::MeasureSize::NotSpecified());
- root_render_object_->Layout(Point{});
- after_layout_event_.Raise(AfterLayoutEventArgs{});
- log::TagDebug(log_tag, u"A relayout is finished.");
-}
-
-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;
-
- const auto old_focus_control = focus_control_;
-
- focus_control_ = control;
-
- DispatchEvent(event_names::LoseFocus, old_focus_control,
- &Control::LoseFocusEvent, nullptr, false);
-
- 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();
- painter->Clear(colors::white);
- 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) {
- const auto render_object = root_render_object_->HitTest(point);
- if (render_object) {
- const auto control = render_object->GetAttachedControl();
- Ensures(control);
- return control;
- }
- return window_control_;
-}
-} // namespace cru::ui
diff --git a/src/ui/UiManager.cpp b/src/ui/UiManager.cpp
index 4cd38efa..7981aa86 100644
--- a/src/ui/UiManager.cpp
+++ b/src/ui/UiManager.cpp
@@ -1,13 +1,22 @@
#include "cru/ui/UiManager.hpp"
+#include <optional>
#include "Helper.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 "cru/platform/GraphBase.hpp"
+#include "cru/platform/graphics/Brush.hpp"
+#include "cru/platform/graphics/Factory.hpp"
+#include "cru/platform/graphics/Font.hpp"
+#include "cru/platform/gui/Cursor.hpp"
+#include "cru/platform/gui/UiApplication.hpp"
+#include "cru/ui/Base.hpp"
+#include "cru/ui/style/ApplyBorderStyleInfo.hpp"
+#include "cru/ui/style/Condition.hpp"
+#include "cru/ui/style/Styler.hpp"
namespace cru::ui {
-using namespace cru::platform::graph;
+using namespace cru::platform::graphics;
+using namespace cru::ui::style;
+using namespace cru::ui::helper;
namespace {
std::unique_ptr<ISolidColorBrush> CreateSolidColorBrush(IGraphFactory* factory,
@@ -30,51 +39,80 @@ UiManager* UiManager::GetInstance() {
UiManager::UiManager() {
const auto factory = GetGraphFactory();
- theme_resource_.default_font = factory->CreateFont(u"等线", 24.0f);
+ theme_resource_.default_font_family = u"等线";
- const auto black_brush = std::shared_ptr<platform::graph::ISolidColorBrush>(
- CreateSolidColorBrush(factory, colors::black));
+ theme_resource_.default_font =
+ factory->CreateFont(theme_resource_.default_font_family, 24.0f);
+
+ const auto black_brush =
+ std::shared_ptr<platform::graphics::ISolidColorBrush>(
+ 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_.button_style.AddStyleRule(
+ {NoCondition::Create(),
+ BorderStyler::Create(ApplyBorderStyleInfo{std::nullopt, Thickness(3),
+ CornerRadius(5), std::nullopt,
+ std::nullopt}),
+ u"DefaultButton"});
+ theme_resource_.button_style.AddStyleRule(
+ {ClickStateCondition::Create(ClickState::None),
+ CompoundStyler::Create(
+ BorderStyler::Create(ApplyBorderStyleInfo{
+ CreateSolidColorBrush(factory, Color::FromHex(0x00bfff))}),
+ CursorStyler::Create(platform::gui::SystemCursorType::Arrow)),
+ u"DefaultButtonNormal"});
+ theme_resource_.button_style.AddStyleRule(
+ {ClickStateCondition::Create(ClickState::Hover),
+ CompoundStyler::Create(
+ BorderStyler::Create(ApplyBorderStyleInfo{
+ CreateSolidColorBrush(factory, Color::FromHex(0x47d1ff))}),
+ CursorStyler::Create(platform::gui::SystemCursorType::Hand)),
+ u"DefaultButtonHover"});
+ theme_resource_.button_style.AddStyleRule(
+ {ClickStateCondition::Create(ClickState::Press),
+ CompoundStyler::Create(
+ BorderStyler::Create(ApplyBorderStyleInfo{
+ CreateSolidColorBrush(factory, Color::FromHex(0x91e4ff))}),
+ CursorStyler::Create(platform::gui::SystemCursorType::Hand)),
+ u"DefaultButtonPress"});
+ theme_resource_.button_style.AddStyleRule(
+ {ClickStateCondition::Create(ClickState::PressInactive),
+ CompoundStyler::Create(
+ BorderStyler::Create(ApplyBorderStyleInfo{
+ CreateSolidColorBrush(factory, Color::FromHex(0x91e4ff))}),
+ CursorStyler::Create(platform::gui::SystemCursorType::Arrow)),
+ u"DefaultButtonPressInactive"});
- theme_resource_.text_box_border_style.focus.border_brush =
- CreateSolidColorBrush(factory, Color::FromHex(0x495057));
- theme_resource_.text_box_border_style.focus.border_radius = CornerRadius(5);
- theme_resource_.text_box_border_style.focus.border_thickness = Thickness(1);
+ theme_resource_.text_box_style.AddStyleRule(
+ {NoCondition::Create(),
+ BorderStyler::Create(
+ ApplyBorderStyleInfo{std::nullopt, Thickness{1}, CornerRadius{5}}),
+ u"DefaultTextBox"});
+ theme_resource_.text_box_style.AddStyleRule(
+ {HoverCondition::Create(false),
+ BorderStyler::Create(ApplyBorderStyleInfo{
+ CreateSolidColorBrush(factory, Color::FromHex(0xced4da))}),
+ u"DefaultTextBoxNormal"});
+ theme_resource_.text_box_style.AddStyleRule(
+ {HoverCondition::Create(true),
+ BorderStyler::Create(ApplyBorderStyleInfo{
+ CreateSolidColorBrush(factory, Color::FromHex(0xced4da))}),
+ u"DefaultTextBoxHover"});
+ theme_resource_.text_box_style.AddStyleRule(
+ {FocusCondition::Create(true),
+ BorderStyler::Create(ApplyBorderStyleInfo{
+ CreateSolidColorBrush(factory, Color::FromHex(0x495057))}),
+ u"DefaultTextBoxFocus"});
- theme_resource_.text_box_border_style.focus_hover =
- theme_resource_.text_box_border_style.focus;
+ theme_resource_.menu_item_style.AddStyleRule(
+ {NoCondition::Create(),
+ BorderStyler::Create(
+ ApplyBorderStyleInfo{std::nullopt, Thickness{0}, CornerRadius{0}}),
+ u"DefaultMenuItem"});
}
UiManager::~UiManager() = default;
diff --git a/src/ui/Window.cpp b/src/ui/Window.cpp
deleted file mode 100644
index dca95ebb..00000000
--- a/src/ui/Window.cpp
+++ /dev/null
@@ -1,28 +0,0 @@
-#include "cru/ui/Window.hpp"
-
-#include "cru/ui/render/WindowRenderObject.hpp"
-#include "cru/ui/UiHost.hpp"
-
-namespace cru::ui {
-Window* Window::CreateOverlapped() {
- return new Window(tag_overlapped_constructor{});
-}
-
-Window::Window(tag_overlapped_constructor) {
- managed_ui_host_ = std::make_unique<UiHost>(this);
-}
-
-Window::~Window() {
- // explicit destroy ui host first.
- managed_ui_host_.reset();
-}
-
-std::u16string_view Window::GetControlType() const { return control_type; }
-
-render::RenderObject* Window::GetRenderObject() const { return render_object_; }
-
-void Window::OnChildChanged(Control* old_child, Control* new_child) {
- if (old_child) render_object_->RemoveChild(0);
- if (new_child) render_object_->AddChild(new_child->GetRenderObject(), 0);
-}
-} // namespace cru::ui
diff --git a/src/ui/components/Component.cpp b/src/ui/components/Component.cpp
new file mode 100644
index 00000000..5b62ffc9
--- /dev/null
+++ b/src/ui/components/Component.cpp
@@ -0,0 +1,5 @@
+#include "cru/ui/components/Component.hpp"
+
+namespace cru::ui::components {
+
+}
diff --git a/src/ui/components/Menu.cpp b/src/ui/components/Menu.cpp
new file mode 100644
index 00000000..d45bc44f
--- /dev/null
+++ b/src/ui/components/Menu.cpp
@@ -0,0 +1,61 @@
+#include "cru/ui/components/Menu.hpp"
+#include "cru/ui/UiManager.hpp"
+#include "cru/ui/controls/Button.hpp"
+#include "cru/ui/controls/FlexLayout.hpp"
+#include "cru/ui/controls/TextBlock.hpp"
+#include "cru/ui/style/StyleRuleSet.hpp"
+
+#include <string>
+
+namespace cru::ui::components {
+MenuItem::MenuItem() {
+ container_ = controls::Button::Create();
+ text_ = controls::TextBlock::Create();
+ container_->SetChild(text_);
+ container_->GetStyleRuleSet()->SetParent(
+ &UiManager::GetInstance()->GetThemeResources()->menu_item_style);
+}
+
+MenuItem::MenuItem(std::u16string text) : MenuItem() {
+ SetText(std::move(text));
+}
+
+MenuItem::~MenuItem() {
+ if (!container_->GetWindowHost()) {
+ delete container_;
+ delete text_;
+ }
+}
+
+void MenuItem::SetText(std::u16string text) { text_->SetText(std::move(text)); }
+
+Menu::Menu() { container_ = controls::FlexLayout::Create(); }
+
+Menu::~Menu() {
+ if (!container_->GetWindowHost()) {
+ delete container_;
+ }
+
+ for (auto item : items_) {
+ delete item;
+ }
+}
+
+void Menu::AddItem(Component* item, gsl::index index) {
+ Expects(index >= 0 && index <= GetItemCount());
+
+ items_.insert(items_.cbegin() + index, item);
+ container_->AddChild(item->GetRootControl(), index);
+}
+
+Component* Menu::RemoveItem(gsl::index index) {
+ Expects(index >= 0 && index < GetItemCount());
+
+ Component* item = items_[index];
+
+ items_.erase(items_.cbegin() + index);
+ container_->RemoveChild(index);
+
+ return item;
+}
+} // namespace cru::ui::components
diff --git a/src/ui/controls/Button.cpp b/src/ui/controls/Button.cpp
index 6f6af878..c6480b77 100644
--- a/src/ui/controls/Button.cpp
+++ b/src/ui/controls/Button.cpp
@@ -1,61 +1,22 @@
#include "cru/ui/controls/Button.hpp"
-#include <memory>
#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/platform/graphics/Brush.hpp"
+#include "cru/platform/gui/Cursor.hpp"
+#include "cru/platform/gui/UiApplication.hpp"
#include "cru/ui/UiManager.hpp"
-#include "cru/ui/Window.hpp"
+#include "cru/ui/helper/ClickDetector.hpp"
+#include "cru/ui/render/BorderRenderObject.hpp"
namespace cru::ui::controls {
-using cru::platform::native::SystemCursorType;
-
-namespace {
-void Set(render::BorderRenderObject* o, const ButtonStateStyle& s) {
- o->SetBorderBrush(s.border_brush);
- o->SetBorderThickness(s.border_thickness);
- o->SetBorderRadius(s.border_radius);
- o->SetForegroundBrush(s.foreground_brush);
- o->SetBackgroundBrush(s.background_brush);
-}
-
-std::shared_ptr<platform::native::ICursor> GetSystemCursor(
- SystemCursorType type) {
- return GetUiApplication()->GetCursorManager()->GetSystemCursor(type);
-}
-} // namespace
-
Button::Button() : click_detector_(this) {
- style_ = UiManager::GetInstance()->GetThemeResources()->button_style;
-
render_object_ = std::make_unique<render::BorderRenderObject>();
render_object_->SetAttachedControl(this);
- Set(render_object_.get(), style_.normal);
+ SetContainerRenderObject(render_object_.get());
render_object_->SetBorderEnabled(true);
- click_detector_.StateChangeEvent()->AddHandler(
- [this](const ClickState& state) {
- switch (state) {
- case ClickState::None:
- Set(render_object_.get(), style_.normal);
- SetCursor(GetSystemCursor(SystemCursorType::Arrow));
- break;
- case ClickState::Hover:
- Set(render_object_.get(), style_.hover);
- SetCursor(GetSystemCursor(SystemCursorType::Hand));
- break;
- case ClickState::Press:
- Set(render_object_.get(), style_.press);
- SetCursor(GetSystemCursor(SystemCursorType::Hand));
- break;
- case ClickState::PressInactive:
- Set(render_object_.get(), style_.press_cancel);
- SetCursor(GetSystemCursor(SystemCursorType::Arrow));
- break;
- }
- });
+ GetStyleRuleSet()->SetParent(
+ &UiManager::GetInstance()->GetThemeResources()->button_style);
}
Button::~Button() = default;
@@ -64,10 +25,7 @@ render::RenderObject* Button::GetRenderObject() const {
return render_object_.get();
}
-void Button::OnChildChanged(Control* old_child, Control* new_child) {
- if (old_child != nullptr) render_object_->RemoveChild(0);
- if (new_child != nullptr)
- render_object_->AddChild(new_child->GetRenderObject(), 0);
+void Button::ApplyBorderStyle(const style::ApplyBorderStyleInfo& style) {
+ render_object_->ApplyBorderStyle(style);
}
-
} // namespace cru::ui::controls
diff --git a/src/ui/controls/Container.cpp b/src/ui/controls/Container.cpp
index de58ee64..30129f64 100644
--- a/src/ui/controls/Container.cpp
+++ b/src/ui/controls/Container.cpp
@@ -1,18 +1,20 @@
#include "cru/ui/controls/Container.hpp"
-#include "cru/platform/graph/Factory.hpp"
+#include "cru/platform/graphics/Factory.hpp"
#include "cru/ui/render/BorderRenderObject.hpp"
+#include "cru/ui/render/RenderObject.hpp"
namespace cru::ui::controls {
Container::Container() {
render_object_ = std::make_unique<render::BorderRenderObject>();
render_object_->SetBorderEnabled(false);
+ render_object_->SetAttachedControl(this);
+ SetContainerRenderObject(render_object_.get());
}
Container::~Container() = default;
-void Container::OnChildChanged(Control*, Control* new_child) {
- render_object_->RemoveChild(0);
- render_object_->AddChild(new_child->GetRenderObject(), 0);
+render::RenderObject* Container::GetRenderObject() const {
+ return render_object_.get();
}
} // namespace cru::ui::controls
diff --git a/src/ui/controls/ContentControl.cpp b/src/ui/controls/ContentControl.cpp
new file mode 100644
index 00000000..8c6f0b00
--- /dev/null
+++ b/src/ui/controls/ContentControl.cpp
@@ -0,0 +1,31 @@
+#include "cru/ui/controls/ContentControl.hpp"
+
+namespace cru::ui::controls {
+Control* ContentControl::GetChild() const {
+ if (GetChildren().empty()) return nullptr;
+ return GetChildren()[0];
+}
+
+void ContentControl::SetChild(Control* child) {
+ Control* old_child = nullptr;
+ if (!GetChildren().empty()) {
+ old_child = GetChildren()[0];
+ this->RemoveChild(0);
+ }
+ if (child) {
+ this->AddChild(child, 0);
+ }
+ OnChildChanged(old_child, child);
+}
+
+void ContentControl::OnChildChanged(Control* old_child, Control* new_child) {
+ if (container_render_object_) {
+ if (old_child) {
+ container_render_object_->RemoveChild(0);
+ }
+ if (new_child) {
+ container_render_object_->AddChild(new_child->GetRenderObject(), 0);
+ }
+ }
+}
+} // namespace cru::ui::controls
diff --git a/src/ui/controls/Control.cpp b/src/ui/controls/Control.cpp
new file mode 100644
index 00000000..29c2c46a
--- /dev/null
+++ b/src/ui/controls/Control.cpp
@@ -0,0 +1,166 @@
+#include "cru/ui/controls/Control.hpp"
+
+#include "cru/common/Base.hpp"
+#include "cru/platform/gui/Cursor.hpp"
+#include "cru/platform/gui/UiApplication.hpp"
+#include "cru/ui/Base.hpp"
+#include "cru/ui/host/WindowHost.hpp"
+#include "cru/ui/render/RenderObject.hpp"
+#include "cru/ui/style/StyleRuleSet.hpp"
+
+#include <memory>
+
+namespace cru::ui::controls {
+using platform::gui::ICursor;
+using platform::gui::IUiApplication;
+using platform::gui::SystemCursorType;
+
+Control::Control() {
+ style_rule_set_ = std::make_unique<style::StyleRuleSet>();
+ style_rule_set_bind_ =
+ std::make_unique<style::StyleRuleSetBind>(this, style_rule_set_.get());
+
+ MouseEnterEvent()->Direct()->AddHandler([this](event::MouseEventArgs&) {
+ this->is_mouse_over_ = true;
+ this->OnMouseHoverChange(true);
+ });
+
+ MouseLeaveEvent()->Direct()->AddHandler([this](event::MouseEventArgs&) {
+ this->is_mouse_over_ = false;
+ this->OnMouseHoverChange(true);
+ });
+}
+
+Control::~Control() {
+ for (const auto child : children_) delete child;
+}
+
+host::WindowHost* Control::GetWindowHost() const { return window_host_; }
+
+void Control::TraverseDescendants(
+ const std::function<void(Control*)>& predicate) {
+ predicate(this);
+ for (auto c : GetChildren()) c->TraverseDescendants(predicate);
+}
+
+bool Control::HasFocus() {
+ auto host = GetWindowHost();
+ if (host == nullptr) return false;
+
+ return host->GetFocusControl() == this;
+}
+
+bool Control::CaptureMouse() {
+ auto host = GetWindowHost();
+ if (host == nullptr) return false;
+
+ return host->CaptureMouseFor(this);
+}
+
+void Control::SetFocus() {
+ auto host = GetWindowHost();
+ if (host == nullptr) return;
+
+ host->SetFocusControl(this);
+}
+
+bool Control::ReleaseMouse() {
+ auto host = GetWindowHost();
+ if (host == nullptr) return false;
+
+ return host->CaptureMouseFor(nullptr);
+}
+
+bool Control::IsMouseCaptured() {
+ auto host = GetWindowHost();
+ if (host == nullptr) return false;
+
+ return host->GetMouseCaptureControl() == this;
+}
+
+std::shared_ptr<ICursor> Control::GetCursor() { return cursor_; }
+
+std::shared_ptr<ICursor> Control::GetInheritedCursor() {
+ Control* control = this;
+ while (control != nullptr) {
+ const auto cursor = control->GetCursor();
+ if (cursor != nullptr) return cursor;
+ control = control->GetParent();
+ }
+ return IUiApplication::GetInstance()->GetCursorManager()->GetSystemCursor(
+ SystemCursorType::Arrow);
+}
+
+void Control::SetCursor(std::shared_ptr<ICursor> cursor) {
+ cursor_ = std::move(cursor);
+ const auto host = GetWindowHost();
+ if (host != nullptr) {
+ host->UpdateCursor();
+ }
+}
+
+style::StyleRuleSet* Control::GetStyleRuleSet() {
+ return style_rule_set_.get();
+}
+
+void Control::AddChild(Control* control, const Index position) {
+ Expects(control->GetParent() ==
+ nullptr); // The control already has a parent.
+ Expects(position >= 0);
+ Expects(position <= static_cast<Index>(
+ children_.size())); // The position is out of range.
+
+ children_.insert(children_.cbegin() + position, control);
+
+ const auto old_parent = control->parent_;
+ control->parent_ = this;
+
+ OnAddChild(control, position);
+ control->OnParentChanged(old_parent, this);
+
+ if (window_host_)
+ control->TraverseDescendants([this](Control* control) {
+ control->window_host_ = window_host_;
+ control->OnAttachToHost(window_host_);
+ });
+}
+
+void Control::RemoveChild(const Index position) {
+ Expects(position >= 0);
+ Expects(position < static_cast<Index>(
+ children_.size())); // The position is out of range.
+
+ const auto i = children_.cbegin() + position;
+ const auto control = *i;
+
+ children_.erase(i);
+ control->parent_ = nullptr;
+
+ OnRemoveChild(control, position);
+ control->OnParentChanged(this, nullptr);
+
+ if (window_host_)
+ control->TraverseDescendants([this](Control* control) {
+ control->window_host_ = nullptr;
+ control->OnDetachFromHost(window_host_);
+ });
+}
+
+void Control::OnAddChild(Control* child, Index position) {
+ CRU_UNUSED(child)
+ CRU_UNUSED(position)
+}
+void Control::OnRemoveChild(Control* child, Index position) {
+ CRU_UNUSED(child)
+ CRU_UNUSED(position)
+}
+
+void Control::OnParentChanged(Control* old_parent, Control* new_parent) {
+ CRU_UNUSED(old_parent)
+ CRU_UNUSED(new_parent)
+}
+
+void Control::OnAttachToHost(host::WindowHost* host) { CRU_UNUSED(host) }
+
+void Control::OnDetachFromHost(host::WindowHost* host) { CRU_UNUSED(host) }
+} // namespace cru::ui::controls
diff --git a/src/ui/controls/FlexLayout.cpp b/src/ui/controls/FlexLayout.cpp
index b7f350dc..e390241f 100644
--- a/src/ui/controls/FlexLayout.cpp
+++ b/src/ui/controls/FlexLayout.cpp
@@ -8,6 +8,7 @@ using render::FlexLayoutRenderObject;
FlexLayout::FlexLayout() {
render_object_.reset(new FlexLayoutRenderObject());
render_object_->SetAttachedControl(this);
+ SetContainerRenderObject(render_object_.get());
}
FlexLayout::~FlexLayout() = default;
@@ -60,13 +61,12 @@ void FlexLayout::SetFlexDirection(FlexDirection direction) {
render_object_->SetFlexDirection(direction);
}
-void FlexLayout::OnAddChild(Control* child, const Index position) {
- render_object_->AddChild(child->GetRenderObject(), position);
+FlexCrossAlignment FlexLayout::GetItemCrossAlign() const {
+ return render_object_->GetItemCrossAlign();
}
-void FlexLayout::OnRemoveChild(Control* child, const Index position) {
- CRU_UNUSED(child)
-
- render_object_->RemoveChild(position);
+void FlexLayout::SetItemCrossAlign(FlexCrossAlignment alignment) {
+ if (alignment == GetItemCrossAlign()) return;
+ render_object_->SetItemCrossAlign(alignment);
}
} // namespace cru::ui::controls
diff --git a/src/ui/controls/LayoutControl.cpp b/src/ui/controls/LayoutControl.cpp
new file mode 100644
index 00000000..5954853e
--- /dev/null
+++ b/src/ui/controls/LayoutControl.cpp
@@ -0,0 +1,18 @@
+#include "cru/ui/controls/LayoutControl.hpp"
+
+#include "cru/ui/render/RenderObject.hpp"
+
+namespace cru::ui::controls {
+void LayoutControl::OnAddChild(Control* child, Index position) {
+ if (container_render_object_ != nullptr) {
+ container_render_object_->AddChild(child->GetRenderObject(), position);
+ }
+}
+
+void LayoutControl::OnRemoveChild(Control* child, Index position) {
+ CRU_UNUSED(child)
+ if (container_render_object_ != nullptr) {
+ container_render_object_->RemoveChild(position);
+ }
+}
+} // namespace cru::ui::controls
diff --git a/src/ui/controls/NoChildControl.cpp b/src/ui/controls/NoChildControl.cpp
new file mode 100644
index 00000000..c62c5819
--- /dev/null
+++ b/src/ui/controls/NoChildControl.cpp
@@ -0,0 +1,3 @@
+#include "cru/ui/controls/NoChildControl.hpp"
+
+namespace cru::ui::controls {}
diff --git a/src/ui/controls/Popup.cpp b/src/ui/controls/Popup.cpp
new file mode 100644
index 00000000..bc217bf5
--- /dev/null
+++ b/src/ui/controls/Popup.cpp
@@ -0,0 +1,22 @@
+#include "cru/ui/controls/Popup.hpp"
+
+#include "cru/platform/gui/UiApplication.hpp"
+#include "cru/ui/controls/RootControl.hpp"
+#include "cru/ui/host/WindowHost.hpp"
+#include "cru/ui/render/StackLayoutRenderObject.hpp"
+
+#include <memory>
+
+namespace cru::ui::controls {
+Popup::Popup(Control* attached_control) : RootControl(attached_control) {}
+
+Popup::~Popup() = default;
+
+gsl::not_null<platform::gui::INativeWindow*> Popup::CreateNativeWindow(
+ gsl::not_null<host::WindowHost*> host,
+ platform::gui::INativeWindow* parent) {
+ return host->CreateNativeWindow(
+ {parent, platform::gui::CreateWindowFlags::NoCaptionAndBorder});
+}
+
+} // namespace cru::ui::controls
diff --git a/src/ui/controls/RootControl.cpp b/src/ui/controls/RootControl.cpp
new file mode 100644
index 00000000..015703c3
--- /dev/null
+++ b/src/ui/controls/RootControl.cpp
@@ -0,0 +1,53 @@
+#include "cru/ui/controls/RootControl.hpp"
+
+#include "cru/common/Base.hpp"
+#include "cru/platform/gui/Base.hpp"
+#include "cru/ui/host/WindowHost.hpp"
+#include "cru/ui/render/Base.hpp"
+#include "cru/ui/render/StackLayoutRenderObject.hpp"
+#include "gsl/pointers"
+
+#include <memory>
+
+namespace cru::ui::controls {
+RootControl::RootControl(Control* attached_control)
+ : attached_control_(attached_control) {
+ render_object_ = std::make_unique<render::StackLayoutRenderObject>();
+ render_object_->SetAttachedControl(this);
+ SetContainerRenderObject(render_object_.get());
+ window_host_ = std::make_unique<host::WindowHost>(this);
+}
+
+RootControl::~RootControl() {}
+
+render::RenderObject* RootControl::GetRenderObject() const {
+ return render_object_.get();
+}
+
+void RootControl::EnsureWindowCreated() { this->GetNativeWindow(true); }
+
+Rect RootControl::GetRect() { return window_host_->GetWindowRect(); }
+
+void RootControl::SetRect(const Rect& rect) {
+ window_host_->SetWindowRect(rect);
+}
+
+void RootControl::Show(bool create) {
+ platform::gui::INativeWindow* native_window = GetNativeWindow(create);
+ if (!native_window) return;
+ native_window->SetVisible(true);
+}
+
+platform::gui::INativeWindow* RootControl::GetNativeWindow(bool create) {
+ const auto host = GetWindowHost();
+ platform::gui::INativeWindow* native_window = host->GetNativeWindow();
+ if (!create) return native_window;
+ if (!native_window) {
+ native_window = this->CreateNativeWindow(
+ host, attached_control_
+ ? attached_control_->GetWindowHost()->GetNativeWindow()
+ : nullptr);
+ }
+ return native_window;
+}
+} // namespace cru::ui::controls
diff --git a/src/ui/controls/StackLayout.cpp b/src/ui/controls/StackLayout.cpp
index ce500b79..89968571 100644
--- a/src/ui/controls/StackLayout.cpp
+++ b/src/ui/controls/StackLayout.cpp
@@ -1,12 +1,15 @@
#include "cru/ui/controls/StackLayout.hpp"
+#include <memory>
#include "cru/ui/render/StackLayoutRenderObject.hpp"
namespace cru::ui::controls {
using render::StackLayoutRenderObject;
-StackLayout::StackLayout() : render_object_(new StackLayoutRenderObject()) {
+StackLayout::StackLayout() {
+ render_object_ = std::make_unique<StackLayoutRenderObject>();
render_object_->SetAttachedControl(this);
+ SetContainerRenderObject(render_object_.get());
}
StackLayout::~StackLayout() = default;
@@ -14,14 +17,4 @@ 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
index 9ce99ab6..0724edcf 100644
--- a/src/ui/controls/TextBlock.cpp
+++ b/src/ui/controls/TextBlock.cpp
@@ -1,16 +1,22 @@
#include "cru/ui/controls/TextBlock.hpp"
-#include "TextControlService.hpp"
#include "cru/ui/UiManager.hpp"
#include "cru/ui/render/CanvasRenderObject.hpp"
#include "cru/ui/render/StackLayoutRenderObject.hpp"
#include "cru/ui/render/TextRenderObject.hpp"
namespace cru::ui::controls {
-using render::CanvasRenderObject;
-using render::StackLayoutRenderObject;
using render::TextRenderObject;
+TextBlock* TextBlock::Create() { return new TextBlock(); }
+
+TextBlock* TextBlock::Create(std::u16string text, bool selectable) {
+ auto c = new TextBlock();
+ c->SetText(text);
+ c->SetSelectable(selectable);
+ return c;
+}
+
TextBlock::TextBlock() {
const auto theme_resources = UiManager::GetInstance()->GetThemeResources();
@@ -20,8 +26,10 @@ TextBlock::TextBlock() {
text_render_object_->SetAttachedControl(this);
- service_ = std::make_unique<TextControlService<TextBlock>>(this);
- service_->SetEnabled(true);
+ service_ = std::make_unique<TextHostControlService>(this);
+
+ service_->SetEnabled(false);
+ service_->SetEditable(false);
}
TextBlock::~TextBlock() = default;
@@ -36,6 +44,10 @@ void TextBlock::SetText(std::u16string text) {
service_->SetText(std::move(text));
}
+bool TextBlock::IsSelectable() const { return service_->IsEnabled(); }
+
+void TextBlock::SetSelectable(bool value) { service_->SetEnabled(value); }
+
gsl::not_null<render::TextRenderObject*> TextBlock::GetTextRenderObject() {
return text_render_object_.get();
}
diff --git a/src/ui/controls/TextBox.cpp b/src/ui/controls/TextBox.cpp
index 4a8d6658..bfc98c06 100644
--- a/src/ui/controls/TextBox.cpp
+++ b/src/ui/controls/TextBox.cpp
@@ -1,6 +1,5 @@
#include "cru/ui/controls/TextBox.hpp"
-#include "TextControlService.hpp"
#include "cru/ui/UiManager.hpp"
#include "cru/ui/render/BorderRenderObject.hpp"
#include "cru/ui/render/CanvasRenderObject.hpp"
@@ -10,9 +9,7 @@
namespace cru::ui::controls {
using render::BorderRenderObject;
-using render::CanvasRenderObject;
using render::ScrollRenderObject;
-using render::StackLayoutRenderObject;
using render::TextRenderObject;
TextBox::TextBox()
@@ -20,8 +17,6 @@ TextBox::TextBox()
scroll_render_object_(new ScrollRenderObject()) {
const auto theme_resources = UiManager::GetInstance()->GetThemeResources();
- border_style_ = theme_resources->text_box_border_style;
-
text_render_object_ = std::make_unique<TextRenderObject>(
theme_resources->text_brush, theme_resources->default_font,
theme_resources->text_selection_brush, theme_resources->caret_brush);
@@ -33,24 +28,15 @@ TextBox::TextBox()
scroll_render_object_->SetAttachedControl(this);
text_render_object_->SetAttachedControl(this);
text_render_object_->SetMinSize(Size{100, 24});
+ text_render_object_->SetMeasureIncludingTrailingSpace(true);
- service_ = std::make_unique<TextControlService<TextBox>>(this);
+ service_ = std::make_unique<TextHostControlService>(this);
service_->SetEnabled(true);
- service_->SetCaretVisible(true);
service_->SetEditable(true);
border_render_object_->SetBorderEnabled(true);
- border_render_object_->SetBorderStyle(border_style_.normal);
-
- GainFocusEvent()->Direct()->AddHandler([this](event::FocusChangeEventArgs&) {
- this->service_->SetCaretVisible(true);
- this->UpdateBorderStyle();
- });
- LoseFocusEvent()->Direct()->AddHandler([this](event::FocusChangeEventArgs&) {
- this->service_->SetCaretVisible(false);
- this->UpdateBorderStyle();
- });
+ GetStyleRuleSet()->SetParent(&theme_resources->text_box_style);
}
TextBox::~TextBox() {}
@@ -67,19 +53,7 @@ render::ScrollRenderObject* TextBox::GetScrollRenderObject() {
return scroll_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));
+void TextBox::ApplyBorderStyle(const style::ApplyBorderStyleInfo& style) {
+ border_render_object_->ApplyBorderStyle(style);
}
} // namespace cru::ui::controls
diff --git a/src/ui/controls/TextControlService.hpp b/src/ui/controls/TextControlService.hpp
deleted file mode 100644
index 5d8d4645..00000000
--- a/src/ui/controls/TextControlService.hpp
+++ /dev/null
@@ -1,403 +0,0 @@
-#pragma once
-#include "../Helper.hpp"
-#include "cru/common/Logger.hpp"
-#include "cru/common/StringUtil.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"
-#include "cru/ui/Control.hpp"
-#include "cru/ui/UiEvent.hpp"
-#include "cru/ui/UiHost.hpp"
-#include "cru/ui/render/CanvasRenderObject.hpp"
-#include "cru/ui/render/ScrollRenderObject.hpp"
-#include "cru/ui/render/TextRenderObject.hpp"
-
-namespace cru::ui::controls {
-constexpr int k_default_caret_blink_duration = 500;
-
-// TControl should inherits `Control` and has following methods:
-// ```
-// gsl::not_null<render::TextRenderObject*> GetTextRenderObject();
-// render::ScrollRenderObject* GetScrollRenderObject();
-// ```
-template <typename TControl>
-class TextControlService : public Object {
- CRU_DEFINE_CLASS_LOG_TAG(u"cru::ui::controls::TextControlService")
-
- public:
- TextControlService(gsl::not_null<TControl*> control) : control_(control) {}
-
- CRU_DELETE_COPY(TextControlService)
- CRU_DELETE_MOVE(TextControlService)
-
- ~TextControlService() override {
- 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_);
- }
-
- public:
- bool IsEnabled() { return enable_; }
-
- void SetEnabled(bool enable) {
- if (enable == this->enable_) return;
- this->enable_ = enable;
- if (enable) {
- this->SetupHandlers();
- if (this->caret_visible_) {
- this->SetupCaret();
- }
- } else {
- this->AbortSelection();
- this->event_revoker_guards_.clear();
- this->TearDownCaret();
- }
- }
-
- bool IsEditable() { return this->editable_; }
-
- void SetEditable(bool editable) {
- this->editable_ = editable;
- this->input_method_context_.reset();
- }
-
- std::u16string GetText() { return this->text_; }
- std::u16string_view GetTextView() { return this->text_; }
- void SetText(std::u16string text, bool stop_composition = false) {
- this->text_ = std::move(text);
- if (stop_composition && this->input_method_context_) {
- this->input_method_context_->CancelComposition();
- }
- CoerceSelection();
- SyncTextRenderObject();
- }
-
- std::optional<platform::native::CompositionText> GetCompositionInfo() {
- if (this->input_method_context_ == nullptr) return std::nullopt;
- auto composition_info = this->input_method_context_->GetCompositionText();
- if (composition_info.text.empty()) return std::nullopt;
- return composition_info;
- }
-
- bool IsCaretVisible() { return caret_visible_; }
-
- void SetCaretVisible(bool visible) {
- if (visible == this->caret_visible_) return;
-
- this->caret_visible_ = visible;
-
- if (this->enable_) {
- if (visible) {
- this->SetupCaret();
- } else {
- this->TearDownCaret();
- }
- }
- }
-
- int GetCaretBlinkDuration() { return caret_blink_duration_; }
-
- void SetCaretBlinkDuration(int milliseconds) {
- if (this->caret_blink_duration_ == milliseconds) return;
-
- if (this->enable_ && this->caret_visible_) {
- this->TearDownCaret();
- this->SetupCaret();
- }
- }
-
- gsl::not_null<render::TextRenderObject*> GetTextRenderObject() {
- return this->control_->GetTextRenderObject();
- }
-
- render::ScrollRenderObject* GetScrollRenderObject() {
- return this->control_->GetScrollRenderObject();
- }
-
- gsl::index GetCaretPosition() { return selection_.GetEnd(); }
-
- TextRange GetSelection() { return selection_; }
-
- void SetSelection(gsl::index caret_position) {
- this->SetSelection(TextRange{caret_position, 0});
- }
-
- void SetSelection(TextRange selection, bool scroll_to_caret = true) {
- this->selection_ = selection;
- CoerceSelection();
- SyncTextRenderObject();
- if (scroll_to_caret) {
- if (const auto scroll_render_object = this->GetScrollRenderObject()) {
- const auto caret_rect = this->GetTextRenderObject()->GetCaretRect();
- // TODO: Wait a tick for layout completed.
- this->GetScrollRenderObject()->ScrollToContain(caret_rect,
- Thickness{5.f});
- }
- }
- }
-
- void DeleteSelectedText() {
- auto selection = GetSelection().Normalize();
- if (selection.count == 0) return;
- this->text_.erase(this->text_.cbegin() + selection.GetStart(),
- this->text_.cbegin() + selection.GetEnd());
- SetSelection(selection.GetStart());
- }
-
- private:
- void CoerceSelection() {
- this->selection_ = this->selection_.CoerceInto(0, text_.size());
- }
-
- void AbortSelection() {
- if (this->select_down_button_.has_value()) {
- this->control_->ReleaseMouse();
- this->select_down_button_ = std::nullopt;
- }
- this->GetTextRenderObject()->SetSelectionRange(std::nullopt);
- }
-
- void SetupCaret() {
- const auto application = GetUiApplication();
-
- // Cancel first anyhow for safety.
- application->CancelTimer(this->caret_timer_id_);
-
- this->GetTextRenderObject()->SetDrawCaret(true);
- this->caret_timer_id_ = application->SetInterval(
- std::chrono::milliseconds(this->caret_blink_duration_),
- [this] { this->GetTextRenderObject()->ToggleDrawCaret(); });
- }
-
- void TearDownCaret() {
- const auto application = GetUiApplication();
- application->CancelTimer(this->caret_timer_id_);
- this->GetTextRenderObject()->SetDrawCaret(false);
- }
-
- void SyncTextRenderObject() {
- const auto text_render_object = this->GetTextRenderObject();
- const auto composition_info = this->GetCompositionInfo();
- if (composition_info) {
- const auto caret_position = GetCaretPosition();
- auto text = this->text_;
- text.insert(caret_position, composition_info->text);
- text_render_object->SetText(text);
- text_render_object->SetCaretPosition(
- caret_position + composition_info->selection.GetEnd());
- auto selection = composition_info->selection;
- selection.position += caret_position;
- text_render_object->SetSelectionRange(selection);
- } else {
- text_render_object->SetText(this->text_);
- text_render_object->SetCaretPosition(this->GetCaretPosition());
- text_render_object->SetSelectionRange(this->GetSelection());
- }
- }
-
- template <typename TArgs>
- void SetupOneHandler(event::RoutedEvent<TArgs>* (Control::*event)(),
- void (TextControlService::*handler)(
- typename event::RoutedEvent<TArgs>::EventArgs)) {
- this->event_revoker_guards_.push_back(
- EventRevokerGuard{(this->control_->*event)()->Direct()->AddHandler(
- std::bind(handler, this, std::placeholders::_1))});
- }
-
- void StartSelection(Index start) {
- SetSelection(start);
- log::TagDebug(log_tag, u"Text selection started, position: {}.", start);
- }
-
- void UpdateSelection(Index new_end) {
- auto selection = GetSelection();
- selection.AdjustEnd(new_end);
- this->SetSelection(selection);
- log::TagDebug(log_tag, u"Text selection updated, range: {}, {}.",
- selection.GetStart(), selection.GetEnd());
- }
-
- void SetupHandlers() {
- Expects(event_revoker_guards_.empty());
-
- SetupOneHandler(&Control::MouseMoveEvent,
- &TextControlService::MouseMoveHandler);
- SetupOneHandler(&Control::MouseDownEvent,
- &TextControlService::MouseDownHandler);
- SetupOneHandler(&Control::MouseUpEvent,
- &TextControlService::MouseUpHandler);
- SetupOneHandler(&Control::KeyDownEvent,
- &TextControlService::KeyDownHandler);
- SetupOneHandler(&Control::KeyUpEvent, &TextControlService::KeyUpHandler);
- SetupOneHandler(&Control::GainFocusEvent,
- &TextControlService::GainFocusHandler);
- SetupOneHandler(&Control::LoseFocusEvent,
- &TextControlService::LoseFocusHandler);
- }
-
- void MouseMoveHandler(event::MouseEventArgs& args) {
- if (this->select_down_button_.has_value()) {
- const auto text_render_object = this->GetTextRenderObject();
- const auto result = text_render_object->TextHitTest(
- args.GetPointToContent(text_render_object));
- const auto position = result.position + (result.trailing ? 1 : 0);
- UpdateSelection(position);
- }
- }
-
- void MouseDownHandler(event::MouseButtonEventArgs& args) {
- this->control_->RequestFocus();
- 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->GetTextRenderObject();
- this->select_down_button_ = args.GetButton();
- const auto result = text_render_object->TextHitTest(
- args.GetPointToContent(text_render_object));
- const auto position = result.position + (result.trailing ? 1 : 0);
- StartSelection(position);
- }
- }
-
- void 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;
- }
- }
-
- void KeyDownHandler(event::KeyEventArgs& args) {
- const auto key_code = args.GetKeyCode();
- using cru::platform::native::KeyCode;
- using cru::platform::native::KeyModifiers;
-
- switch (key_code) {
- case KeyCode::Backspace: {
- if (!IsEditable()) return;
- const auto selection = GetSelection();
- if (selection.count == 0) {
- const auto text = this->GetTextView();
- const auto caret_position = GetCaretPosition();
- if (caret_position == 0) return;
- gsl::index new_position;
- Utf16PreviousCodePoint(text, caret_position, &new_position);
- text_.erase(text_.cbegin() + new_position,
- text_.cbegin() + caret_position);
- SetSelection(new_position);
- } else {
- this->DeleteSelectedText();
- }
- } break;
- case KeyCode::Delete: {
- if (!IsEditable()) return;
- const auto selection = GetSelection();
- if (selection.count == 0) {
- const auto text = this->GetTextView();
- const auto caret_position = GetCaretPosition();
- if (caret_position == static_cast<gsl::index>(text.size())) return;
- gsl::index new_position;
- Utf16NextCodePoint(text, caret_position, &new_position);
- text_.erase(text_.cbegin() + caret_position,
- text_.cbegin() + new_position);
- SyncTextRenderObject();
- } else {
- this->DeleteSelectedText();
- }
- } break;
- case KeyCode::Left: {
- const auto key_modifier = args.GetKeyModifier();
- const bool shift = key_modifier & KeyModifiers::shift;
- auto text = this->GetTextView();
- if (shift) {
- auto selection = this->GetSelection();
- gsl::index new_position;
- Utf16PreviousCodePoint(text, selection.GetEnd(), &new_position);
- selection.AdjustEnd(new_position);
- this->SetSelection(selection);
- } else {
- const auto caret = this->GetCaretPosition();
- gsl::index new_position;
- Utf16PreviousCodePoint(text, caret, &new_position);
- this->SetSelection(new_position);
- }
- } break;
- case KeyCode::Right: {
- const auto key_modifier = args.GetKeyModifier();
- const bool shift = key_modifier & KeyModifiers::shift;
- auto text = this->GetTextView();
- if (shift) {
- auto selection = this->GetSelection();
- gsl::index new_position;
- Utf16NextCodePoint(text, selection.GetEnd(), &new_position);
- selection.AdjustEnd(new_position);
- this->SetSelection(selection);
- } else {
- const auto caret = this->GetCaretPosition();
- gsl::index new_position;
- Utf16NextCodePoint(text, caret, &new_position);
- this->SetSelection(new_position);
- }
- } break;
- }
- }
-
- void KeyUpHandler(event::KeyEventArgs& args) { CRU_UNUSED(args); }
-
- void GainFocusHandler(event::FocusChangeEventArgs& args) {
- CRU_UNUSED(args);
- if (editable_) {
- UiHost* ui_host = this->control_->GetUiHost();
- auto window = ui_host->GetNativeWindowResolver()->Resolve();
- if (window == nullptr) return;
- input_method_context_ =
- GetUiApplication()->GetInputMethodManager()->GetContext(window);
- input_method_context_->EnableIME();
- auto sync = [this](std::nullptr_t) { this->SyncTextRenderObject(); };
- input_method_context_->CompositionStartEvent()->AddHandler(
- [this](std::nullptr_t) { this->DeleteSelectedText(); });
- input_method_context_->CompositionEvent()->AddHandler(sync);
- input_method_context_->CompositionEndEvent()->AddHandler(sync);
- input_method_context_->TextEvent()->AddHandler(
- [this](const std::u16string_view& text) {
- if (text == u"\b") return;
- this->text_.insert(GetCaretPosition(), text);
- this->SetSelection(GetCaretPosition() + text.size());
- });
- }
- }
-
- void LoseFocusHandler(event::FocusChangeEventArgs& args) {
- if (!args.IsWindow()) this->AbortSelection();
- if (input_method_context_) {
- input_method_context_->DisableIME();
- input_method_context_.reset();
- }
- SyncTextRenderObject();
- }
-
- private:
- gsl::not_null<TControl*> control_;
- std::vector<EventRevokerGuard> event_revoker_guards_;
-
- std::u16string text_;
- TextRange selection_;
-
- bool enable_ = false;
- bool editable_ = 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<MouseButton> select_down_button_;
-
- std::unique_ptr<platform::native::IInputMethodContext> input_method_context_;
-}; // namespace cru::ui::controls
-} // namespace cru::ui::controls
diff --git a/src/ui/controls/TextHostControlService.cpp b/src/ui/controls/TextHostControlService.cpp
new file mode 100644
index 00000000..07b4f1e8
--- /dev/null
+++ b/src/ui/controls/TextHostControlService.cpp
@@ -0,0 +1,469 @@
+#include "cru/ui/controls/TextHostControlService.hpp"
+
+#include "../Helper.hpp"
+#include "cru/common/Logger.hpp"
+#include "cru/common/StringUtil.hpp"
+#include "cru/platform/gui/Base.hpp"
+#include "cru/platform/gui/Cursor.hpp"
+#include "cru/platform/gui/InputMethod.hpp"
+#include "cru/platform/gui/Keyboard.hpp"
+#include "cru/platform/gui/UiApplication.hpp"
+#include "cru/platform/gui/Window.hpp"
+#include "cru/ui/Base.hpp"
+#include "cru/ui/DebugFlags.hpp"
+#include "cru/ui/events/UiEvent.hpp"
+#include "cru/ui/helper/ShortcutHub.hpp"
+#include "cru/ui/host/WindowHost.hpp"
+#include "cru/ui/render/ScrollRenderObject.hpp"
+#include "cru/ui/render/TextRenderObject.hpp"
+
+namespace cru::ui::controls {
+TextHostControlService::TextHostControlService(gsl::not_null<Control*> control)
+ : control_(control),
+ text_host_control_(dynamic_cast<ITextHostControl*>(control.get())) {
+ SetUpShortcuts();
+
+ SetupOneHandler(&Control::MouseMoveEvent,
+ &TextHostControlService::MouseMoveHandler);
+ SetupOneHandler(&Control::MouseDownEvent,
+ &TextHostControlService::MouseDownHandler);
+ SetupOneHandler(&Control::MouseUpEvent,
+ &TextHostControlService::MouseUpHandler);
+ SetupOneHandler(&Control::GainFocusEvent,
+ &TextHostControlService::GainFocusHandler);
+ SetupOneHandler(&Control::LoseFocusEvent,
+ &TextHostControlService::LoseFocusHandler);
+
+ shortcut_hub_.Install(control_);
+}
+
+void TextHostControlService::SetEnabled(bool enable) {
+ if (enable == this->enable_) return;
+ this->enable_ = enable;
+ if (enable) {
+ if (this->caret_visible_) {
+ this->SetupCaret();
+ }
+ this->control_->SetCursor(
+ GetUiApplication()->GetCursorManager()->GetSystemCursor(
+ platform::gui::SystemCursorType::IBeam));
+ } else {
+ this->AbortSelection();
+ this->TearDownCaret();
+ this->control_->SetCursor(nullptr);
+ }
+}
+
+void TextHostControlService::SetEditable(bool editable) {
+ this->editable_ = editable;
+ if (!editable) CancelComposition();
+}
+
+void TextHostControlService::SetText(std::u16string text,
+ bool stop_composition) {
+ this->text_ = std::move(text);
+ CoerceSelection();
+ if (stop_composition) {
+ CancelComposition();
+ }
+ SyncTextRenderObject();
+}
+
+void TextHostControlService::InsertText(gsl::index position,
+ std::u16string_view text,
+ bool stop_composition) {
+ if (!Utf16IsValidInsertPosition(this->text_, position)) {
+ log::TagError(log_tag, u"Invalid text insert position.");
+ return;
+ }
+ this->text_.insert(this->text_.cbegin() + position, text.begin(), text.end());
+ if (stop_composition) {
+ CancelComposition();
+ }
+ SyncTextRenderObject();
+}
+
+void TextHostControlService::DeleteChar(gsl::index position,
+ bool stop_composition) {
+ if (!Utf16IsValidInsertPosition(this->text_, position)) {
+ log::TagError(log_tag, u"Invalid text delete position.");
+ return;
+ }
+ if (position == static_cast<gsl::index>(this->text_.size())) return;
+ Index next;
+ Utf16NextCodePoint(this->text_, position, &next);
+ this->DeleteText(TextRange::FromTwoSides(position, next), stop_composition);
+}
+
+// Return the position of deleted character.
+gsl::index TextHostControlService::DeleteCharPrevious(gsl::index position,
+ bool stop_composition) {
+ if (!Utf16IsValidInsertPosition(this->text_, position)) {
+ log::TagError(log_tag, u"Invalid text delete position.");
+ return 0;
+ }
+ if (position == 0) return 0;
+ Index previous;
+ Utf16PreviousCodePoint(this->text_, position, &previous);
+ this->DeleteText(TextRange::FromTwoSides(previous, position),
+ stop_composition);
+ return previous;
+}
+
+void TextHostControlService::DeleteText(TextRange range,
+ bool stop_composition) {
+ if (range.count == 0) return;
+ range = range.Normalize();
+ if (!Utf16IsValidInsertPosition(this->text_, range.GetStart())) {
+ log::TagError(log_tag, u"Invalid text delete start position.");
+ return;
+ }
+ if (!Utf16IsValidInsertPosition(this->text_, range.GetStart())) {
+ log::TagError(log_tag, u"Invalid text delete end position.");
+ return;
+ }
+ this->text_.erase(this->text_.cbegin() + range.GetStart(),
+ this->text_.cbegin() + range.GetEnd());
+ this->CoerceSelection();
+ if (stop_composition) {
+ CancelComposition();
+ }
+ this->SyncTextRenderObject();
+}
+
+platform::gui::IInputMethodContext*
+TextHostControlService ::GetInputMethodContext() {
+ host::WindowHost* host = this->control_->GetWindowHost();
+ if (!host) return nullptr;
+ platform::gui::INativeWindow* native_window = host->GetNativeWindow();
+ if (!native_window) return nullptr;
+ return native_window->GetInputMethodContext();
+}
+
+void TextHostControlService::CancelComposition() {
+ auto input_method_context = GetInputMethodContext();
+ if (input_method_context == nullptr) return;
+ input_method_context->CancelComposition();
+}
+
+std::optional<platform::gui::CompositionText>
+TextHostControlService::GetCompositionInfo() {
+ auto input_method_context = GetInputMethodContext();
+ if (input_method_context == nullptr) return std::nullopt;
+ auto composition_info = input_method_context->GetCompositionText();
+ if (composition_info.text.empty()) return std::nullopt;
+ return composition_info;
+}
+
+void TextHostControlService::SetCaretVisible(bool visible) {
+ if (visible == this->caret_visible_) return;
+
+ this->caret_visible_ = visible;
+
+ if (this->enable_) {
+ if (visible) {
+ this->SetupCaret();
+ } else {
+ this->TearDownCaret();
+ }
+ }
+}
+
+void TextHostControlService::SetCaretBlinkDuration(int milliseconds) {
+ if (this->caret_blink_duration_ == milliseconds) return;
+
+ if (this->enable_ && this->caret_visible_) {
+ this->TearDownCaret();
+ this->SetupCaret();
+ }
+}
+
+void TextHostControlService::ScrollToCaret() {
+ if (const auto scroll_render_object = this->GetScrollRenderObject()) {
+ this->control_->GetWindowHost()->RunAfterLayoutStable(
+ [this, scroll_render_object]() {
+ const auto caret_rect = this->GetTextRenderObject()->GetCaretRect();
+ scroll_render_object->ScrollToContain(caret_rect, Thickness{5.f});
+ });
+ }
+}
+
+gsl::not_null<render::TextRenderObject*>
+TextHostControlService::GetTextRenderObject() {
+ return this->text_host_control_->GetTextRenderObject();
+}
+
+render::ScrollRenderObject* TextHostControlService::GetScrollRenderObject() {
+ return this->text_host_control_->GetScrollRenderObject();
+}
+
+void TextHostControlService::SetSelection(gsl::index caret_position) {
+ this->SetSelection(TextRange{caret_position, 0});
+}
+
+void TextHostControlService::SetSelection(TextRange selection,
+ bool scroll_to_caret) {
+ this->selection_ = selection;
+ CoerceSelection();
+ SyncTextRenderObject();
+ if (scroll_to_caret) {
+ this->ScrollToCaret();
+ }
+}
+
+void TextHostControlService::ChangeSelectionEnd(Index new_end) {
+ auto selection = GetSelection();
+ selection.ChangeEnd(new_end);
+ this->SetSelection(selection);
+}
+
+void TextHostControlService::AbortSelection() {
+ if (this->mouse_move_selecting_) {
+ this->control_->ReleaseMouse();
+ this->mouse_move_selecting_ = false;
+ }
+ SetSelection(GetCaretPosition());
+}
+
+void TextHostControlService::ReplaceSelectedText(std::u16string_view text) {
+ DeleteSelectedText();
+ InsertText(GetSelection().GetStart(), text);
+ SetSelection(GetSelection().GetStart() + text.size());
+}
+
+void TextHostControlService::DeleteSelectedText() {
+ this->DeleteText(GetSelection());
+ SetSelection(GetSelection().Normalize().GetStart());
+}
+
+void TextHostControlService::SetupCaret() {
+ const auto application = GetUiApplication();
+ this->GetTextRenderObject()->SetDrawCaret(true);
+ this->caret_timer_canceler_.Reset(application->SetInterval(
+ std::chrono::milliseconds(this->caret_blink_duration_),
+ [this] { this->GetTextRenderObject()->ToggleDrawCaret(); }));
+}
+
+void TextHostControlService::TearDownCaret() {
+ this->caret_timer_canceler_.Reset();
+ this->GetTextRenderObject()->SetDrawCaret(false);
+}
+
+void TextHostControlService::CoerceSelection() {
+ this->selection_ = this->selection_.CoerceInto(0, text_.size());
+}
+
+void TextHostControlService::SyncTextRenderObject() {
+ const auto text_render_object = this->GetTextRenderObject();
+ const auto composition_info = this->GetCompositionInfo();
+ if (composition_info) {
+ const auto caret_position = GetCaretPosition();
+ auto text = this->text_;
+ text.insert(caret_position, composition_info->text);
+ text_render_object->SetText(text);
+ text_render_object->SetCaretPosition(caret_position +
+ composition_info->selection.GetEnd());
+ auto selection = composition_info->selection;
+ selection.position += caret_position;
+ text_render_object->SetSelectionRange(selection);
+ } else {
+ text_render_object->SetText(this->text_);
+ text_render_object->SetCaretPosition(this->GetCaretPosition());
+ text_render_object->SetSelectionRange(this->GetSelection());
+ }
+}
+
+void TextHostControlService::UpdateInputMethodPosition() {
+ if (auto input_method_context = this->GetInputMethodContext()) {
+ Point right_bottom =
+ this->GetTextRenderObject()->GetTotalOffset() +
+ this->GetTextRenderObject()->GetCaretRect().GetRightBottom();
+ right_bottom.x += 5;
+ right_bottom.y += 5;
+
+ if constexpr (debug_flags::text_service) {
+ log::TagDebug(log_tag,
+ u"Calculate input method candidate window position: {}.",
+ right_bottom.ToDebugString());
+ }
+
+ input_method_context->SetCandidateWindowPosition(right_bottom);
+ }
+}
+
+void TextHostControlService::MouseDownHandler(
+ event::MouseButtonEventArgs& args) {
+ if (IsEnabled()) {
+ this->control_->SetFocus();
+ if (args.GetButton() == mouse_buttons::left &&
+ !this->mouse_move_selecting_) {
+ if (!this->control_->CaptureMouse()) return;
+ this->mouse_move_selecting_ = true;
+ const auto text_render_object = this->GetTextRenderObject();
+ const auto result = text_render_object->TextHitTest(
+ args.GetPointToContent(text_render_object));
+ const auto position = result.position + (result.trailing ? 1 : 0);
+ SetSelection(position);
+ }
+ }
+}
+
+void TextHostControlService::MouseUpHandler(event::MouseButtonEventArgs& args) {
+ if (args.GetButton() == mouse_buttons::left && mouse_move_selecting_) {
+ this->control_->ReleaseMouse();
+ this->mouse_move_selecting_ = false;
+ }
+}
+
+void TextHostControlService::MouseMoveHandler(event::MouseEventArgs& args) {
+ if (this->mouse_move_selecting_) {
+ const auto text_render_object = this->GetTextRenderObject();
+ const auto result = text_render_object->TextHitTest(
+ args.GetPointToContent(text_render_object));
+ const auto position = result.position + (result.trailing ? 1 : 0);
+ ChangeSelectionEnd(position);
+ }
+}
+
+void TextHostControlService::GainFocusHandler(
+ event::FocusChangeEventArgs& args) {
+ CRU_UNUSED(args);
+ if (editable_) {
+ auto input_method_context = GetInputMethodContext();
+ if (input_method_context == nullptr) return;
+ input_method_context->EnableIME();
+ auto sync = [this](std::nullptr_t) {
+ this->SyncTextRenderObject();
+ ScrollToCaret();
+ };
+ input_method_context_event_guard_ +=
+ input_method_context->CompositionStartEvent()->AddHandler(
+ [this](std::nullptr_t) { this->DeleteSelectedText(); });
+ input_method_context_event_guard_ +=
+ input_method_context->CompositionEvent()->AddHandler(sync);
+ input_method_context_event_guard_ +=
+ input_method_context->CompositionEndEvent()->AddHandler(sync);
+ input_method_context_event_guard_ +=
+ input_method_context->TextEvent()->AddHandler(
+ [this](const std::u16string_view& text) {
+ this->ReplaceSelectedText(text);
+ });
+
+ host::WindowHost* window_host = control_->GetWindowHost();
+ if (window_host)
+ input_method_context_event_guard_ +=
+ window_host->AfterLayoutEvent()->AddHandler(
+ [this](auto) { this->UpdateInputMethodPosition(); });
+ SetCaretVisible(true);
+ }
+}
+
+void TextHostControlService::LoseFocusHandler(
+ event::FocusChangeEventArgs& args) {
+ if (!args.IsWindow()) this->AbortSelection();
+ input_method_context_event_guard_.Clear();
+ auto input_method_context = GetInputMethodContext();
+ if (input_method_context) {
+ input_method_context->DisableIME();
+ }
+ SetCaretVisible(false);
+ SyncTextRenderObject();
+}
+
+void TextHostControlService::SetUpShortcuts() {
+ using platform::gui::KeyCode;
+ using platform::gui::KeyModifiers;
+
+ shortcut_hub_.RegisterShortcut(u"Backspace", KeyCode::Backspace, [this] {
+ if (!IsEnabled()) return false;
+ if (!IsEditable()) return false;
+ const auto selection = GetSelection();
+ if (selection.count == 0) {
+ SetSelection(DeleteCharPrevious(GetCaretPosition()));
+ } else {
+ this->DeleteSelectedText();
+ }
+ return true;
+ });
+
+ shortcut_hub_.RegisterShortcut(u"Delete", KeyCode::Delete, [this] {
+ if (!IsEnabled()) return false;
+ if (!IsEditable()) return false;
+ const auto selection = GetSelection();
+ if (selection.count == 0) {
+ DeleteChar(GetCaretPosition());
+ } else {
+ this->DeleteSelectedText();
+ }
+ return true;
+ });
+
+ shortcut_hub_.RegisterShortcut(u"Left", KeyCode::Left, [this] {
+ auto text = this->GetTextView();
+ auto caret = this->GetCaretPosition();
+ Utf16PreviousCodePoint(text, caret, &caret);
+ this->SetSelection(caret);
+ return true;
+ });
+
+ shortcut_hub_.RegisterShortcut(u"ShiftLeft",
+ {KeyCode::Left, KeyModifiers::shift}, [this] {
+ auto text = this->GetTextView();
+ auto caret = this->GetCaretPosition();
+ Utf16PreviousCodePoint(text, caret, &caret);
+ this->ChangeSelectionEnd(caret);
+ return true;
+ });
+
+ shortcut_hub_.RegisterShortcut(
+ u"CtrlLeft", {KeyCode::Left, KeyModifiers::ctrl}, [this] {
+ auto text = this->GetTextView();
+ auto caret = this->GetCaretPosition();
+ this->SetSelection(Utf16PreviousWord(text, caret));
+ return true;
+ });
+
+ shortcut_hub_.RegisterShortcut(
+ u"CtrlShiftLeft",
+ {KeyCode::Left, KeyModifiers::ctrl | KeyModifiers::shift}, [this] {
+ auto text = this->GetTextView();
+ auto caret = this->GetCaretPosition();
+ this->ChangeSelectionEnd(Utf16PreviousWord(text, caret));
+ return true;
+ });
+
+ shortcut_hub_.RegisterShortcut(u"Right", KeyCode::Right, [this] {
+ auto text = this->GetTextView();
+ auto caret = this->GetCaretPosition();
+ Utf16NextCodePoint(text, caret, &caret);
+ this->SetSelection(caret);
+ return true;
+ });
+
+ shortcut_hub_.RegisterShortcut(u"ShiftRight",
+ {KeyCode::Right, KeyModifiers::shift}, [this] {
+ auto text = this->GetTextView();
+ auto caret = this->GetCaretPosition();
+ Utf16NextCodePoint(text, caret, &caret);
+ this->ChangeSelectionEnd(caret);
+ return true;
+ });
+
+ shortcut_hub_.RegisterShortcut(
+ u"CtrlRight", {KeyCode::Right, KeyModifiers::ctrl}, [this] {
+ auto text = this->GetTextView();
+ auto caret = this->GetCaretPosition();
+ this->SetSelection(Utf16NextWord(text, caret));
+ return true;
+ });
+
+ shortcut_hub_.RegisterShortcut(
+ u"CtrlShiftRight",
+ {KeyCode::Right, KeyModifiers::ctrl | KeyModifiers::shift}, [this] {
+ auto text = this->GetTextView();
+ auto caret = this->GetCaretPosition();
+ this->ChangeSelectionEnd(Utf16NextWord(text, caret));
+ return true;
+ });
+}
+} // namespace cru::ui::controls
diff --git a/src/ui/controls/Window.cpp b/src/ui/controls/Window.cpp
new file mode 100644
index 00000000..ba66f42e
--- /dev/null
+++ b/src/ui/controls/Window.cpp
@@ -0,0 +1,24 @@
+#include "cru/ui/controls/Window.hpp"
+
+#include "cru/common/Base.hpp"
+#include "cru/platform/gui/Base.hpp"
+#include "cru/ui/controls/RootControl.hpp"
+#include "cru/ui/host/WindowHost.hpp"
+#include "cru/ui/render/Base.hpp"
+#include "cru/ui/render/StackLayoutRenderObject.hpp"
+
+namespace cru::ui::controls {
+Window* Window::Create(Control* attached_control) {
+ return new Window(attached_control);
+}
+
+Window::Window(Control* attached_control) : RootControl(attached_control) {}
+
+Window::~Window() {}
+
+gsl::not_null<platform::gui::INativeWindow*> Window::CreateNativeWindow(
+ gsl::not_null<host::WindowHost*> host,
+ platform::gui::INativeWindow* parent) {
+ return host->CreateNativeWindow({parent});
+}
+} // namespace cru::ui::controls
diff --git a/src/ui/UiEvent.cpp b/src/ui/events/UiEvent.cpp
index 74dd54dc..4c75f690 100644
--- a/src/ui/UiEvent.cpp
+++ b/src/ui/events/UiEvent.cpp
@@ -1,8 +1,12 @@
-#include "cru/ui/UiEvent.hpp"
+#include "cru/ui/events/UiEvent.hpp"
#include "cru/ui/render/RenderObject.hpp"
namespace cru::ui::event {
+Point MouseEventArgs::GetPoint(render::RenderObject* render_object) const {
+ return GetPoint() - render_object->GetTotalOffset();
+}
+
Point MouseEventArgs::GetPointToContent(
render::RenderObject* render_object) const {
return render_object->FromRootToContent(GetPoint());
diff --git a/src/ui/helper/BorderStyle.cpp b/src/ui/helper/BorderStyle.cpp
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/src/ui/helper/BorderStyle.cpp
diff --git a/src/ui/ClickDetector.cpp b/src/ui/helper/ClickDetector.cpp
index 09f208cd..309685d3 100644
--- a/src/ui/ClickDetector.cpp
+++ b/src/ui/helper/ClickDetector.cpp
@@ -1,11 +1,12 @@
-#include "cru/ui/ClickDetector.hpp"
+#include "cru/ui/helper/ClickDetector.hpp"
#include "cru/common/Logger.hpp"
+#include "cru/ui/DebugFlags.hpp"
#include <optional>
-namespace cru::ui {
-ClickDetector::ClickDetector(Control* control) {
+namespace cru::ui::helper {
+ClickDetector::ClickDetector(controls::Control* control) {
Expects(control);
control_ = control;
@@ -44,8 +45,10 @@ ClickDetector::ClickDetector(Control* control) {
if (this->enable_ && (button & this->trigger_button_) &&
this->state_ == ClickState::Hover) {
if (!this->control_->CaptureMouse()) {
- log::TagDebug(log_tag,
- u"Failed to capture mouse when begin click.");
+ if constexpr (debug_flags::click_detector) {
+ log::TagDebug(log_tag,
+ u"Failed to capture mouse when begin click.");
+ }
return;
}
this->down_point_ = args.GetPoint();
@@ -106,26 +109,26 @@ void ClickDetector::SetTriggerButton(MouseButton trigger_button) {
}
void ClickDetector::SetState(ClickState state) {
-#ifdef CRU_DEBUG
- auto to_string = [](ClickState state) -> std::u16string_view {
- switch (state) {
- case ClickState::None:
- return u"None";
- case ClickState::Hover:
- return u"Hover";
- case ClickState::Press:
- return u"Press";
- case ClickState::PressInactive:
- return u"PressInvactive";
- default:
- UnreachableCode();
- }
- };
- log::TagDebug(log_tag, u"Click state changed, new state: {}.",
- to_string(state));
-#endif
+ if constexpr (debug_flags::click_detector) {
+ auto to_string = [](ClickState state) -> std::u16string_view {
+ switch (state) {
+ case ClickState::None:
+ return u"None";
+ case ClickState::Hover:
+ return u"Hover";
+ case ClickState::Press:
+ return u"Press";
+ case ClickState::PressInactive:
+ return u"PressInvactive";
+ default:
+ UnreachableCode();
+ }
+ };
+ log::TagDebug(log_tag, u"Click state changed, new state: {}.",
+ to_string(state));
+ }
state_ = state;
state_change_event_.Raise(state);
}
-} // namespace cru::ui
+} // namespace cru::ui::helper
diff --git a/src/ui/helper/ShortcutHub.cpp b/src/ui/helper/ShortcutHub.cpp
new file mode 100644
index 00000000..f35ad0ef
--- /dev/null
+++ b/src/ui/helper/ShortcutHub.cpp
@@ -0,0 +1,131 @@
+#include "cru/ui/helper/ShortcutHub.hpp"
+
+#include "cru/common/Logger.hpp"
+#include "cru/ui/DebugFlags.hpp"
+#include "cru/ui/controls/Control.hpp"
+
+#include <algorithm>
+#include <functional>
+#include <iterator>
+#include <optional>
+
+namespace cru::ui::helper {
+int ShortcutHub::RegisterShortcut(Shortcut shortcut) {
+ const int id = current_id_++;
+ map_[shortcut.key_bind].push_back({id, std::move(shortcut.name),
+ shortcut.key_bind,
+ std::move(shortcut.handler)});
+ return id;
+}
+
+void ShortcutHub::UnregisterShortcut(int id) {
+ if (id <= 0) return;
+ for (auto& pair : map_) {
+ auto& list = pair.second;
+ auto result =
+ std::find_if(list.cbegin(), list.cend(),
+ [id](const ShortcutInfo& info) { return info.id == id; });
+ if (result != list.cend()) {
+ list.erase(result);
+ }
+ }
+}
+
+std::vector<ShortcutInfo> ShortcutHub::GetAllShortcuts() const {
+ std::vector<ShortcutInfo> result;
+
+ for (const auto& pair : map_) {
+ std::copy(pair.second.cbegin(), pair.second.cend(),
+ std::back_inserter(result));
+ }
+
+ return result;
+}
+
+std::optional<ShortcutInfo> ShortcutHub::GetShortcut(int id) const {
+ for (auto& pair : map_) {
+ auto& list = pair.second;
+ auto result =
+ std::find_if(list.cbegin(), list.cend(),
+ [id](const ShortcutInfo& info) { return info.id == id; });
+ if (result != list.cend()) {
+ return *result;
+ }
+ }
+ return std::nullopt;
+}
+
+const std::vector<ShortcutInfo>& ShortcutHub::GetShortcutByKeyBind(
+ const ShortcutKeyBind& key_bind) const {
+ auto result = map_.find(key_bind);
+ if (result != map_.cend()) return result->second;
+ return empty_list_;
+}
+
+void ShortcutHub::Install(controls::Control* control) {
+ if (!event_guard_.IsEmpty()) {
+ log::Error(u"Shortcut hub is already installed. Failed to install.");
+ return;
+ }
+
+ event_guard_ += control->KeyDownEvent()->Bubble()->AddHandler(
+ std::bind(&ShortcutHub::OnKeyDown, this, std::placeholders::_1));
+}
+
+void ShortcutHub::Uninstall() {
+ if (event_guard_.IsEmpty()) {
+ log::Warn(u"Shortcut hub is not installed. Failed to uninstall.");
+ return;
+ }
+
+ event_guard_.Clear();
+}
+
+void ShortcutHub::OnKeyDown(event::KeyEventArgs& event) {
+ ShortcutKeyBind key_bind(event.GetKeyCode(), event.GetKeyModifier());
+ const auto& shortcut_list = this->GetShortcutByKeyBind(key_bind);
+
+ bool handled = false;
+
+ if constexpr (debug_flags::shortcut) {
+ if (shortcut_list.empty()) {
+ log::Debug(u"No shortcut for key bind {}.", key_bind.ToString());
+ }
+ log::Debug(u"Begin to handle shortcut for key bind {}.",
+ key_bind.ToString());
+ }
+
+ for (const auto& shortcut : shortcut_list) {
+ auto is_handled = shortcut.handler();
+ if (is_handled) {
+ if constexpr (debug_flags::shortcut) {
+ log::Debug(u"Handle {} handled it.", shortcut.name);
+ }
+
+ handled = true;
+ event.SetHandled();
+
+ break;
+ } else {
+ if constexpr (debug_flags::shortcut) {
+ log::Debug(u"Handle {} didn't handle it.", shortcut.name);
+ }
+ }
+ }
+
+ if constexpr (debug_flags::shortcut) {
+ if (!shortcut_list.empty()) {
+ log::Debug(u"End handling shortcut for key bind {}.",
+ key_bind.ToString());
+ }
+ }
+
+ if (!handled) {
+ if constexpr (debug_flags::shortcut) {
+ log::Debug(u"Raise fallback event for unhandled shortcut of key bind {}.",
+ key_bind.ToString());
+ }
+ fallback_event_.Raise(event);
+ }
+}
+} // namespace cru::ui::helper
diff --git a/src/ui/host/LayoutPaintCycler.cpp b/src/ui/host/LayoutPaintCycler.cpp
new file mode 100644
index 00000000..fd581e00
--- /dev/null
+++ b/src/ui/host/LayoutPaintCycler.cpp
@@ -0,0 +1,35 @@
+#include "cru/ui/host/LayoutPaintCycler.hpp"
+#include <chrono>
+
+#include "../Helper.hpp"
+#include "cru/ui/Base.hpp"
+#include "cru/ui/host/WindowHost.hpp"
+
+namespace cru::ui::host {
+LayoutPaintCycler::LayoutPaintCycler(WindowHost* host) : host_(host) {
+ timer_canceler_ = GetUiApplication()->SetInterval(
+ std::chrono::duration_cast<std::chrono::milliseconds>(
+ this->cycle_threshold_),
+ [this] { OnCycle(); });
+}
+
+LayoutPaintCycler::~LayoutPaintCycler() = default;
+
+void LayoutPaintCycler::InvalidateLayout() { layout_dirty_ = true; }
+
+void LayoutPaintCycler::InvalidatePaint() { paint_dirty_ = true; }
+
+void LayoutPaintCycler::OnCycle() {
+ last_cycle_time_ = std::chrono::steady_clock::now();
+ if (layout_dirty_) {
+ host_->Relayout();
+ host_->Repaint();
+ } else {
+ if (paint_dirty_) {
+ host_->Repaint();
+ }
+ }
+ layout_dirty_ = false;
+ paint_dirty_ = false;
+}
+} // namespace cru::ui::host
diff --git a/src/ui/RoutedEventDispatch.hpp b/src/ui/host/RoutedEventDispatch.hpp
index 9337e9ec..52507fc7 100644
--- a/src/ui/RoutedEventDispatch.hpp
+++ b/src/ui/host/RoutedEventDispatch.hpp
@@ -1,9 +1,9 @@
#pragma once
-#include "cru/ui/Control.hpp"
-
#include "cru/common/Logger.hpp"
+#include "cru/ui/DebugFlags.hpp"
+#include "cru/ui/controls/Control.hpp"
-#include <list>
+#include <vector>
namespace cru::ui {
// Dispatch the event.
@@ -20,33 +20,23 @@ namespace cru::ui {
// "original_sender", which is unchanged. And "args" will be perfectly forwarded
// as the rest arguments.
template <typename EventArgs, typename... Args>
-void DispatchEvent(const std::u16string_view& event_name,
- Control* const original_sender,
- event::RoutedEvent<EventArgs>* (Control::*event_ptr)(),
- Control* const last_receiver, Args&&... args) {
-#ifndef CRU_DEBUG
+void DispatchEvent(
+ const std::u16string_view& event_name,
+ controls::Control* const original_sender,
+ event::RoutedEvent<EventArgs>* (controls::Control::*event_ptr)(),
+ controls::Control* const last_receiver, Args&&... args) {
CRU_UNUSED(event_name)
-#endif
-
-#ifdef CRU_DEBUG
- bool do_log = true;
- if (event_name == u"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
- */
+ if constexpr (debug_flags::routed_event)
+ log::Debug(
+ "Routed event {} no need to dispatch (original_sender == "
+ "last_receiver). Original sender is {}.",
+ event_name, original_sender->GetControlType());
return;
}
- std::list<Control*> receive_list;
+ std::vector<controls::Control*> receive_list;
auto parent = original_sender;
while (parent != last_receiver) {
@@ -54,8 +44,7 @@ void DispatchEvent(const std::u16string_view& event_name,
parent = parent->GetParent();
}
-#ifdef CRU_DEBUG
- if (do_log) {
+ if constexpr (debug_flags::routed_event) {
std::u16string log = u"Dispatch routed event ";
log += event_name;
log += u". Path (parent first): ";
@@ -68,31 +57,24 @@ void DispatchEvent(const std::u16string_view& event_name,
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>(args)...);
static_cast<Event<EventArgs&>*>(((*i)->*event_ptr)()->Tunnel())
->Raise(event_args);
if (event_args.IsHandled()) {
handled = true;
-#ifdef CRU_DEBUG
- if (do_log)
+ if constexpr (debug_flags::routed_event)
log::Debug(
u"Routed event is short-circuit in TUNNEL at {}-st control (count "
u"from parent).",
count);
-#endif
break;
}
}
@@ -100,20 +82,16 @@ void DispatchEvent(const std::u16string_view& event_name,
// bubble
if (!handled) {
for (auto i : receive_list) {
-#ifdef CRU_DEBUG
count--;
-#endif
EventArgs event_args(i, original_sender, std::forward<Args>(args)...);
static_cast<Event<EventArgs&>*>((i->*event_ptr)()->Bubble())
->Raise(event_args);
if (event_args.IsHandled()) {
-#ifdef CRU_DEBUG
- if (do_log)
+ if constexpr (debug_flags::routed_event)
log::Debug(
u"Routed event is short-circuit in BUBBLE at {}-st control "
u"(count from parent).",
count);
-#endif
break;
}
}
@@ -126,8 +104,7 @@ void DispatchEvent(const std::u16string_view& event_name,
->Raise(event_args);
}
-#ifdef CRU_DEBUG
- if (do_log) log::Debug(u"Routed event dispatch finished.");
-#endif
+ if constexpr (debug_flags::routed_event)
+ log::Debug(u"Routed event dispatch finished.");
}
} // namespace cru::ui
diff --git a/src/ui/host/WindowHost.cpp b/src/ui/host/WindowHost.cpp
new file mode 100644
index 00000000..5e107733
--- /dev/null
+++ b/src/ui/host/WindowHost.cpp
@@ -0,0 +1,440 @@
+#include "cru/ui/host/WindowHost.hpp"
+
+#include "RoutedEventDispatch.hpp"
+#include "cru/common/Base.hpp"
+#include "cru/common/Logger.hpp"
+#include "cru/platform/graphics/Painter.hpp"
+#include "cru/platform/gui/InputMethod.hpp"
+#include "cru/platform/gui/UiApplication.hpp"
+#include "cru/platform/gui/Window.hpp"
+#include "cru/ui/DebugFlags.hpp"
+#include "cru/ui/controls/Window.hpp"
+#include "cru/ui/host/LayoutPaintCycler.hpp"
+#include "cru/ui/render/MeasureRequirement.hpp"
+#include "cru/ui/render/RenderObject.hpp"
+
+#include <cstddef>
+#include <memory>
+
+namespace cru::ui::host {
+using platform::gui::INativeWindow;
+using platform::gui::IUiApplication;
+
+namespace event_names {
+#ifdef CRU_DEBUG
+// clang-format off
+#define CRU_DEFINE_EVENT_NAME(name) constexpr const char16_t* name = CRU_MAKE_UNICODE_LITERAL(name);
+// clang-format on
+#else
+#define CRU_DEFINE_EVENT_NAME(name) constexpr const char16_t* name = u"";
+#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)
+
+#undef CRU_DEFINE_EVENT_NAME
+} // namespace event_names
+
+namespace {
+bool IsAncestor(controls::Control* control, controls::Control* ancestor) {
+ while (control != nullptr) {
+ if (control == ancestor) return true;
+ control = control->GetParent();
+ }
+ return false;
+}
+
+// Ancestor at last.
+std::vector<controls::Control*> GetAncestorList(controls::Control* control) {
+ std::vector<controls::Control*> l;
+ while (control != nullptr) {
+ l.push_back(control);
+ control = control->GetParent();
+ }
+ return l;
+}
+
+controls::Control* FindLowestCommonAncestor(controls::Control* left,
+ controls::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.back() != right_list.back()) return nullptr;
+
+ // find the last same control or the last control (one is ancestor of the
+ // other)
+ auto left_iter = left_list.crbegin();
+ auto right_iter = right_list.crbegin();
+
+ while (true) {
+ if (left_iter == left_list.crend()) {
+ return left_list.front();
+ }
+ if (right_iter == right_list.crend()) {
+ return right_list.front();
+ }
+ if (*left_iter != *right_iter) {
+ return *(--left_iter);
+ }
+ ++left_iter;
+ ++right_iter;
+ }
+}
+} // namespace
+
+namespace {
+template <typename T>
+inline void BindNativeEvent(
+ WindowHost* host, INativeWindow* native_window, IEvent<T>* event,
+ void (WindowHost::*handler)(INativeWindow*, typename IEvent<T>::EventArgs),
+ std::vector<EventRevokerGuard>& guard_pool) {
+ guard_pool.push_back(EventRevokerGuard(event->AddHandler(
+ std::bind(handler, host, native_window, std::placeholders::_1))));
+}
+} // namespace
+
+WindowHost::WindowHost(controls::Control* root_control)
+ : root_control_(root_control), focus_control_(root_control) {
+ root_control_->TraverseDescendants([this](controls::Control* control) {
+ control->window_host_ = this;
+ control->OnAttachToHost(this);
+ });
+
+ root_render_object_ = root_control->GetRenderObject();
+ root_render_object_->SetWindowHostRecursive(this);
+
+ this->layout_paint_cycler_ = std::make_unique<LayoutPaintCycler>(this);
+}
+
+WindowHost::~WindowHost() {}
+
+gsl::not_null<platform::gui::INativeWindow*> WindowHost::CreateNativeWindow(
+ CreateWindowParams create_window_params) {
+ if (native_window_ != nullptr) return native_window_;
+
+ const auto ui_application = IUiApplication::GetInstance();
+
+ auto native_window = ui_application->CreateWindow(create_window_params.parent,
+ create_window_params.flag);
+
+ native_window_ = native_window;
+
+ BindNativeEvent(this, native_window, native_window->DestroyEvent(),
+ &WindowHost::OnNativeDestroy, event_revoker_guards_);
+ BindNativeEvent(this, native_window, native_window->PaintEvent(),
+ &WindowHost::OnNativePaint, event_revoker_guards_);
+ BindNativeEvent(this, native_window, native_window->ResizeEvent(),
+ &WindowHost::OnNativeResize, event_revoker_guards_);
+ BindNativeEvent(this, native_window, native_window->FocusEvent(),
+ &WindowHost::OnNativeFocus, event_revoker_guards_);
+ BindNativeEvent(this, native_window, native_window->MouseEnterLeaveEvent(),
+ &WindowHost::OnNativeMouseEnterLeave, event_revoker_guards_);
+ BindNativeEvent(this, native_window, native_window->MouseMoveEvent(),
+ &WindowHost::OnNativeMouseMove, event_revoker_guards_);
+ BindNativeEvent(this, native_window, native_window->MouseDownEvent(),
+ &WindowHost::OnNativeMouseDown, event_revoker_guards_);
+ BindNativeEvent(this, native_window, native_window->MouseUpEvent(),
+ &WindowHost::OnNativeMouseUp, event_revoker_guards_);
+ BindNativeEvent(this, native_window, native_window->KeyDownEvent(),
+ &WindowHost::OnNativeKeyDown, event_revoker_guards_);
+ BindNativeEvent(this, native_window, native_window->KeyUpEvent(),
+ &WindowHost::OnNativeKeyUp, event_revoker_guards_);
+
+ if (saved_rect_) {
+ native_window->SetWindowRect(saved_rect_.value());
+ }
+
+ native_window_change_event_.Raise(native_window);
+
+ return native_window_;
+}
+
+void WindowHost::InvalidatePaint() { layout_paint_cycler_->InvalidatePaint(); }
+
+void WindowHost::InvalidateLayout() {
+ layout_paint_cycler_->InvalidateLayout();
+}
+
+bool WindowHost::IsLayoutPreferToFillWindow() const {
+ return layout_prefer_to_fill_window_;
+}
+
+void WindowHost::SetLayoutPreferToFillWindow(bool value) {
+ if (value == layout_prefer_to_fill_window_) return;
+ layout_prefer_to_fill_window_ = value;
+ InvalidateLayout();
+}
+
+void WindowHost::Relayout() {
+ const auto available_size =
+ native_window_ ? native_window_->GetClientSize()
+ : Size{100, 100}; // a reasonable assumed size
+ Relayout(available_size);
+}
+
+void WindowHost::Relayout(const Size& available_size) {
+ root_render_object_->Measure(
+ render::MeasureRequirement{available_size,
+ IsLayoutPreferToFillWindow()
+ ? render::MeasureSize(available_size)
+ : render::MeasureSize::NotSpecified()},
+ render::MeasureSize::NotSpecified());
+ root_render_object_->Layout(Point{});
+ for (auto& action : after_layout_stable_action_) action();
+ after_layout_event_.Raise(AfterLayoutEventArgs{});
+ root_render_object_->TraverseDescendants(
+ [](render::RenderObject* render_object) {
+ render_object->OnAfterLayout();
+ });
+ after_layout_stable_action_.clear();
+ if constexpr (debug_flags::layout)
+ log::TagDebug(log_tag, u"A relayout is finished.");
+}
+
+void WindowHost::Repaint() {
+ auto painter = native_window_->BeginPaint();
+ painter->Clear(colors::white);
+ root_render_object_->Draw(painter.get());
+ painter->EndDraw();
+}
+
+controls::Control* WindowHost::GetFocusControl() { return focus_control_; }
+
+void WindowHost::SetFocusControl(controls::Control* control) {
+ if (focus_control_ == control) return;
+ if (control == nullptr) control = root_control_;
+
+ const auto old_focus_control = focus_control_;
+
+ focus_control_ = control;
+
+ DispatchEvent(event_names::LoseFocus, old_focus_control,
+ &controls::Control::LoseFocusEvent, nullptr, false);
+
+ DispatchEvent(event_names::GainFocus, control,
+ &controls::Control::GainFocusEvent, nullptr, false);
+}
+
+bool WindowHost::CaptureMouseFor(controls::Control* control) {
+ if (!native_window_) return false;
+ if (!native_window_->CaptureMouse()) return false;
+
+ if (control == mouse_captured_control_) return true;
+
+ if (control == nullptr) {
+ native_window_->ReleaseMouse();
+ 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;
+}
+
+controls::Control* WindowHost::GetMouseCaptureControl() {
+ return mouse_captured_control_;
+}
+
+void WindowHost::RunAfterLayoutStable(std::function<void()> action) {
+ if (layout_paint_cycler_->IsLayoutDirty()) {
+ after_layout_stable_action_.push_back(std::move(action));
+ } else {
+ action();
+ }
+}
+
+Rect WindowHost::GetWindowRect() {
+ if (native_window_) return native_window_->GetWindowRect();
+ return saved_rect_.value_or(Rect{});
+}
+
+void WindowHost::SetSavedWindowRect(std::optional<Rect> rect) {
+ saved_rect_ = std::move(rect);
+}
+
+void WindowHost::SetWindowRect(const Rect& rect) {
+ SetSavedWindowRect(rect);
+ if (native_window_) native_window_->SetWindowRect(rect);
+}
+
+void WindowHost::OnNativeDestroy(INativeWindow* window, std::nullptr_t) {
+ CRU_UNUSED(window)
+
+ saved_rect_ = this->native_window_->GetWindowRect();
+
+ this->native_window_ = nullptr;
+ event_revoker_guards_.clear();
+
+ native_window_change_event_.Raise(nullptr);
+}
+
+void WindowHost::OnNativePaint(INativeWindow* window, std::nullptr_t) {
+ CRU_UNUSED(window)
+ layout_paint_cycler_->InvalidatePaint();
+}
+
+void WindowHost::OnNativeResize(INativeWindow* window, const Size& size) {
+ CRU_UNUSED(window)
+ CRU_UNUSED(size)
+
+ InvalidateLayout();
+}
+
+void WindowHost::OnNativeFocus(INativeWindow* window,
+ platform::gui::FocusChangeType focus) {
+ CRU_UNUSED(window)
+
+ focus == platform::gui::FocusChangeType::Gain
+ ? DispatchEvent(event_names::GainFocus, focus_control_,
+ &controls::Control::GainFocusEvent, nullptr, true)
+ : DispatchEvent(event_names::LoseFocus, focus_control_,
+ &controls::Control::LoseFocusEvent, nullptr, true);
+}
+
+void WindowHost::OnNativeMouseEnterLeave(
+ INativeWindow* window, platform::gui::MouseEnterLeaveType type) {
+ CRU_UNUSED(window)
+
+ if (type == platform::gui::MouseEnterLeaveType::Leave) {
+ DispatchEvent(event_names::MouseLeave, mouse_hover_control_,
+ &controls::Control::MouseLeaveEvent, nullptr);
+ mouse_hover_control_ = nullptr;
+ }
+}
+
+void WindowHost::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,
+ &controls::Control::MouseLeaveEvent, n);
+ } else {
+ DispatchEvent(event_names::MouseEnter, n,
+ &controls::Control::MouseEnterEvent, o, point);
+ }
+ DispatchEvent(event_names::MouseMove, mouse_captured_control_,
+ &controls::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,
+ &controls::Control::MouseMoveEvent, nullptr, point);
+ UpdateCursor();
+}
+
+void WindowHost::OnNativeMouseDown(
+ INativeWindow* window,
+ const platform::gui::NativeMouseButtonEventArgs& args) {
+ CRU_UNUSED(window)
+
+ controls::Control* control =
+ mouse_captured_control_ ? mouse_captured_control_ : HitTest(args.point);
+ DispatchEvent(event_names::MouseDown, control,
+ &controls::Control::MouseDownEvent, nullptr, args.point,
+ args.button, args.modifier);
+}
+
+void WindowHost::OnNativeMouseUp(
+ INativeWindow* window,
+ const platform::gui::NativeMouseButtonEventArgs& args) {
+ CRU_UNUSED(window)
+
+ controls::Control* control =
+ mouse_captured_control_ ? mouse_captured_control_ : HitTest(args.point);
+ DispatchEvent(event_names::MouseUp, control, &controls::Control::MouseUpEvent,
+ nullptr, args.point, args.button, args.modifier);
+}
+
+void WindowHost::OnNativeKeyDown(
+ INativeWindow* window, const platform::gui::NativeKeyEventArgs& args) {
+ CRU_UNUSED(window)
+
+ DispatchEvent(event_names::KeyDown, focus_control_,
+ &controls::Control::KeyDownEvent, nullptr, args.key,
+ args.modifier);
+}
+
+void WindowHost::OnNativeKeyUp(INativeWindow* window,
+ const platform::gui::NativeKeyEventArgs& args) {
+ CRU_UNUSED(window)
+
+ DispatchEvent(event_names::KeyUp, focus_control_,
+ &controls::Control::KeyUpEvent, nullptr, args.key,
+ args.modifier);
+}
+
+void WindowHost::DispatchMouseHoverControlChangeEvent(
+ controls::Control* old_control, controls::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,
+ &controls::Control::MouseLeaveEvent,
+ lowest_common_ancestor); // dispatch mouse leave event.
+ if (!no_enter && new_control != nullptr) {
+ DispatchEvent(event_names::MouseEnter, new_control,
+ &controls::Control::MouseEnterEvent, lowest_common_ancestor,
+ point); // dispatch mouse enter event.
+ }
+ }
+}
+
+void WindowHost::UpdateCursor() {
+ if (native_window_) {
+ const auto capture = GetMouseCaptureControl();
+ native_window_->SetCursor(
+ (capture ? capture : GetMouseHoverControl())->GetInheritedCursor());
+ }
+}
+
+controls::Control* WindowHost::HitTest(const Point& point) {
+ const auto render_object = root_render_object_->HitTest(point);
+ if (render_object) {
+ const auto control = render_object->GetAttachedControl();
+ Ensures(control);
+ return control;
+ }
+ return root_control_;
+}
+} // namespace cru::ui::host
diff --git a/src/ui/render/BorderRenderObject.cpp b/src/ui/render/BorderRenderObject.cpp
index b7e1e709..e2c40f0c 100644
--- a/src/ui/render/BorderRenderObject.cpp
+++ b/src/ui/render/BorderRenderObject.cpp
@@ -2,9 +2,11 @@
#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 "cru/platform/graphics/Factory.hpp"
+#include "cru/platform/graphics/Geometry.hpp"
+#include "cru/platform/graphics/util/Painter.hpp"
+#include "cru/ui/style/ApplyBorderStyleInfo.hpp"
+#include "gsl/gsl_assert"
#include <algorithm>
@@ -16,12 +18,13 @@ BorderRenderObject::BorderRenderObject() {
BorderRenderObject::~BorderRenderObject() {}
-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;
+void BorderRenderObject::ApplyBorderStyle(
+ const style::ApplyBorderStyleInfo& style) {
+ if (style.border_brush) border_brush_ = *style.border_brush;
+ if (style.border_thickness) border_thickness_ = *style.border_thickness;
+ if (style.border_radius) border_radius_ = *style.border_radius;
+ if (style.foreground_brush) foreground_brush_ = *style.foreground_brush;
+ if (style.background_brush) background_brush_ = *style.background_brush;
InvalidateLayout();
}
@@ -51,7 +54,7 @@ RenderObject* BorderRenderObject::HitTest(const Point& point) {
}
}
-void BorderRenderObject::OnDrawCore(platform::graph::IPainter* painter) {
+void BorderRenderObject::OnDrawCore(platform::graphics::IPainter* painter) {
if (background_brush_ != nullptr)
painter->FillGeometry(border_inner_geometry_.get(),
background_brush_.get());
@@ -109,9 +112,10 @@ Size BorderRenderObject::OnMeasureCore(const MeasureRequirement& requirement,
if (!requirement.max.height.IsNotSpecified()) {
const auto max_height = requirement.max.height.GetLengthOrMax();
if (coerced_space_size.height > max_height) {
- log::TagWarn(log_tag,
- u"(Measure) Vertical length of padding, border and margin is "
- u"bigger than required max length.");
+ log::TagWarn(
+ log_tag,
+ u"(Measure) Vertical length of padding, border and margin is "
+ u"bigger than required max length.");
coerced_space_size.height = max_height;
}
content_requirement.max.height = max_height - coerced_space_size.height;
@@ -235,7 +239,7 @@ void BorderRenderObject::RecreateGeometry() {
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,
+ auto f = [](platform::graphics::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));
@@ -263,7 +267,7 @@ void BorderRenderObject::RecreateGeometry() {
size.width - margin.GetHorizontalTotal(),
size.height - margin.GetVerticalTotal()};
const auto graph_factory = GetGraphFactory();
- std::unique_ptr<platform::graph::IGeometryBuilder> builder{
+ std::unique_ptr<platform::graphics::IGeometryBuilder> builder{
graph_factory->CreateGeometryBuilder()};
f(builder.get(), outer_rect, outer_radius);
border_outer_geometry_ = builder->Build();
diff --git a/src/ui/render/CanvasRenderObject.cpp b/src/ui/render/CanvasRenderObject.cpp
index 967fdcec..bf1155e1 100644
--- a/src/ui/render/CanvasRenderObject.cpp
+++ b/src/ui/render/CanvasRenderObject.cpp
@@ -10,7 +10,7 @@ RenderObject* CanvasRenderObject::HitTest(const Point& point) {
return padding_rect.IsPointInside(point) ? this : nullptr;
}
-void CanvasRenderObject::OnDrawContent(platform::graph::IPainter* painter) {
+void CanvasRenderObject::OnDrawContent(platform::graphics::IPainter* painter) {
const auto rect = GetContentRect();
CanvasPaintEventArgs args{painter, rect.GetSize()};
paint_event_.Raise(args);
diff --git a/src/ui/render/FlexLayoutRenderObject.cpp b/src/ui/render/FlexLayoutRenderObject.cpp
index ade230b5..b1ef69ee 100644
--- a/src/ui/render/FlexLayoutRenderObject.cpp
+++ b/src/ui/render/FlexLayoutRenderObject.cpp
@@ -1,7 +1,7 @@
#include "cru/ui/render/FlexLayoutRenderObject.hpp"
#include "cru/common/Logger.hpp"
-#include "cru/platform/graph/util/Painter.hpp"
+#include "cru/platform/graphics/util/Painter.hpp"
#include "cru/ui/render/LayoutHelper.hpp"
#include <algorithm>
@@ -10,6 +10,10 @@
namespace cru::ui::render {
+std::u16string_view FlexLayoutRenderObject::GetName() const {
+ return u"FlexLayoutRenderObject";
+}
+
struct tag_horizontal_t {};
struct tag_vertical_t {};
@@ -64,7 +68,7 @@ template <typename TSize>
constexpr TSize CreateTSize(decltype(std::declval<TSize>().width) main,
decltype(std::declval<TSize>().height) cross,
tag_vertical_t) {
- return TSize{main, cross};
+ return TSize{cross, main};
}
enum class FlexLayoutAdjustType { None, Expand, Shrink };
@@ -387,10 +391,11 @@ void FlexLayoutRenderObject::OnLayoutContent(const Rect& content_rect) {
const auto cross_align =
GetChildLayoutDataList()[i].cross_alignment.value_or(
GetItemCrossAlign());
- child->Layout(
- Point{content_rect.top + current_main_offset,
- CalculateAnchorByAlignment(cross_align, content_rect.left,
- content_rect.width, size.width)});
+ child->Layout(Point{
+ CalculateAnchorByAlignment(cross_align, content_rect.left,
+ content_rect.width, size.width),
+ content_rect.top + current_main_offset,
+ });
current_main_offset += size.height;
}
} else {
@@ -402,9 +407,9 @@ void FlexLayoutRenderObject::OnLayoutContent(const Rect& content_rect) {
GetChildLayoutDataList()[i].cross_alignment.value_or(
GetItemCrossAlign());
child->Layout(
- Point{content_rect.GetBottom() - current_main_offset,
- CalculateAnchorByAlignment(cross_align, content_rect.left,
- content_rect.width, size.width)});
+ Point{CalculateAnchorByAlignment(cross_align, content_rect.left,
+ content_rect.width, size.width),
+ content_rect.GetBottom() - current_main_offset});
current_main_offset += size.height;
}
}
diff --git a/src/ui/render/RenderObject.cpp b/src/ui/render/RenderObject.cpp
index 30433868..7cf750cd 100644
--- a/src/ui/render/RenderObject.cpp
+++ b/src/ui/render/RenderObject.cpp
@@ -1,12 +1,21 @@
#include "cru/ui/render/RenderObject.hpp"
#include "cru/common/Logger.hpp"
-#include "cru/platform/graph/util/Painter.hpp"
-#include "cru/ui/UiHost.hpp"
+#include "cru/platform/graphics/util/Painter.hpp"
+#include "cru/ui/DebugFlags.hpp"
+#include "cru/ui/host/WindowHost.hpp"
#include <algorithm>
+#include <string>
+#include <string_view>
+#include <vector>
namespace cru::ui::render {
+void RenderObject::SetAttachedControl(controls::Control* new_control) {
+ control_ = new_control;
+ OnAttachedControlChanged(new_control);
+}
+
void RenderObject::AddChild(RenderObject* render_object, const Index position) {
Expects(child_mode_ != ChildMode::None);
Expects(!(child_mode_ == ChildMode::Single && children_.size() > 0));
@@ -20,7 +29,7 @@ void RenderObject::AddChild(RenderObject* render_object, const Index position) {
children_.insert(children_.cbegin() + position, render_object);
render_object->SetParent(this);
- render_object->SetRenderHostRecursive(GetUiHost());
+ render_object->SetWindowHostRecursive(GetWindowHost());
OnAddChild(render_object, position);
}
@@ -33,10 +42,25 @@ void RenderObject::RemoveChild(const Index position) {
const auto removed_child = *i;
children_.erase(i);
removed_child->SetParent(nullptr);
- removed_child->SetRenderHostRecursive(nullptr);
+ removed_child->SetWindowHostRecursive(nullptr);
OnRemoveChild(removed_child, position);
}
+RenderObject* RenderObject::GetFirstChild() const {
+ const auto& children = GetChildren();
+ if (children.empty()) {
+ return nullptr;
+ } else {
+ return children.front();
+ }
+}
+
+void RenderObject::TraverseDescendants(
+ const std::function<void(RenderObject*)>& action) {
+ action(this);
+ for (auto child : children_) child->TraverseDescendants(action);
+}
+
Point RenderObject::GetTotalOffset() const {
Point result{};
const RenderObject* render_object = this;
@@ -66,18 +90,34 @@ void RenderObject::Measure(const MeasureRequirement& requirement,
MeasureSize merged_preferred_size =
preferred_size.OverrideBy(preferred_size_);
+ if constexpr (cru::ui::debug_flags::layout) {
+ log::Debug(u"{} Measure begins :\nrequirement: {}\npreferred size: {}",
+ this->GetDebugPathInTree(), requirement.ToDebugString(),
+ preferred_size.ToDebugString());
+ }
+
size_ = OnMeasureCore(merged_requirement, merged_preferred_size);
+
+ if constexpr (cru::ui::debug_flags::layout) {
+ log::Debug(u"{} Measure ends :\nresult size: {}",
+ this->GetDebugPathInTree(), size_.ToDebugString());
+ }
+
Ensures(size_.width >= 0);
Ensures(size_.height >= 0);
Ensures(requirement.Satisfy(size_));
}
void RenderObject::Layout(const Point& offset) {
+ if constexpr (cru::ui::debug_flags::layout) {
+ log::Debug(u"{} Layout :\noffset: {}", this->GetDebugPathInTree(),
+ offset.ToDebugString());
+ }
offset_ = offset;
OnLayoutCore();
}
-void RenderObject::Draw(platform::graph::IPainter* painter) {
+void RenderObject::Draw(platform::graphics::IPainter* painter) {
OnDrawCore(painter);
}
@@ -112,29 +152,29 @@ void RenderObject::OnRemoveChild(RenderObject* removed_child, Index position) {
InvalidatePaint();
}
-void RenderObject::DefaultDrawChildren(platform::graph::IPainter* painter) {
+void RenderObject::DefaultDrawChildren(platform::graphics::IPainter* painter) {
for (const auto child : GetChildren()) {
auto offset = child->GetOffset();
- platform::graph::util::WithTransform(
+ platform::graphics::util::WithTransform(
painter, platform::Matrix::Translation(offset.x, offset.y),
[child](auto p) { child->Draw(p); });
}
}
-void RenderObject::DefaultDrawContent(platform::graph::IPainter* painter) {
+void RenderObject::DefaultDrawContent(platform::graphics::IPainter* painter) {
const auto content_rect = GetContentRect();
- platform::graph::util::WithTransform(
+ platform::graphics::util::WithTransform(
painter, Matrix::Translation(content_rect.left, content_rect.top),
[this](auto p) { this->OnDrawContent(p); });
}
-void RenderObject::OnDrawCore(platform::graph::IPainter* painter) {
+void RenderObject::OnDrawCore(platform::graphics::IPainter* painter) {
DefaultDrawContent(painter);
DefaultDrawChildren(painter);
}
-void RenderObject::OnDrawContent(platform::graph::IPainter* painter) {
+void RenderObject::OnDrawContent(platform::graphics::IPainter* painter) {
CRU_UNUSED(painter);
}
@@ -249,24 +289,44 @@ void RenderObject::SetParent(RenderObject* new_parent) {
}
void RenderObject::InvalidateLayout() {
- if (ui_host_ != nullptr) ui_host_->InvalidateLayout();
+ if (window_host_ != nullptr) window_host_->InvalidateLayout();
}
void RenderObject::InvalidatePaint() {
- if (ui_host_ != nullptr) ui_host_->InvalidatePaint();
+ if (window_host_ != nullptr) window_host_->InvalidatePaint();
}
-void RenderObject::NotifyAfterLayoutRecursive(RenderObject* render_object) {
- render_object->OnAfterLayout();
- for (const auto o : render_object->GetChildren()) {
- NotifyAfterLayoutRecursive(o);
+constexpr std::u16string_view kUnamedName(u"UNNAMED");
+
+std::u16string_view RenderObject::GetName() const { return kUnamedName; }
+
+std::u16string RenderObject::GetDebugPathInTree() const {
+ std::vector<std::u16string_view> chain;
+ const RenderObject* parent = this;
+ while (parent != nullptr) {
+ chain.push_back(parent->GetName());
+ parent = parent->GetParent();
}
+
+ std::u16string result(chain.back());
+ for (auto iter = chain.crbegin() + 1; iter != chain.crend(); ++iter) {
+ result += u" -> ";
+ result += *iter;
+ }
+
+ return result;
}
-void RenderObject::SetRenderHostRecursive(UiHost* host) {
- ui_host_ = host;
+void RenderObject::SetWindowHostRecursive(host::WindowHost* host) {
+ if (window_host_ != nullptr) {
+ detach_from_host_event_.Raise(nullptr);
+ }
+ window_host_ = host;
+ if (host != nullptr) {
+ attach_to_host_event_.Raise(nullptr);
+ }
for (const auto child : GetChildren()) {
- child->SetRenderHostRecursive(host);
+ child->SetWindowHostRecursive(host);
}
}
} // namespace cru::ui::render
diff --git a/src/ui/render/ScrollBar.cpp b/src/ui/render/ScrollBar.cpp
new file mode 100644
index 00000000..7f69c1e2
--- /dev/null
+++ b/src/ui/render/ScrollBar.cpp
@@ -0,0 +1,622 @@
+#include "cru/ui/render/ScrollBar.hpp"
+
+#include "../Helper.hpp"
+#include "cru/common/Base.hpp"
+#include "cru/platform/GraphBase.hpp"
+#include "cru/platform/graphics/Factory.hpp"
+#include "cru/platform/graphics/Geometry.hpp"
+#include "cru/platform/graphics/Painter.hpp"
+#include "cru/platform/graphics/util/Painter.hpp"
+#include "cru/platform/gui/Base.hpp"
+#include "cru/platform/gui/Cursor.hpp"
+#include "cru/ui/Base.hpp"
+#include "cru/ui/events/UiEvent.hpp"
+#include "cru/ui/render/ScrollRenderObject.hpp"
+#include "gsl/gsl_assert"
+
+#include <algorithm>
+#include <cassert>
+#include <chrono>
+#include <gsl/pointers>
+#include <memory>
+#include <optional>
+#include <stdexcept>
+
+namespace cru::ui::render {
+using namespace std::chrono_literals;
+constexpr float kScrollBarCollapseThumbWidth = 2;
+constexpr float kScrollBarCollapsedTriggerExpandAreaWidth = 5;
+constexpr float kScrollBarExpandWidth = 10;
+constexpr float kScrollBarArrowHeight = 3.5;
+constexpr auto kScrollBarAutoCollapseDelay = 1500ms;
+
+constexpr std::array<ScrollBarAreaKind, 5> kScrollBarAreaKindList{
+ ScrollBarAreaKind::UpArrow, ScrollBarAreaKind::DownArrow,
+ ScrollBarAreaKind::UpSlot, ScrollBarAreaKind::DownSlot,
+ ScrollBarAreaKind::Thumb};
+
+namespace {
+std::unique_ptr<platform::graphics::IGeometry> CreateScrollBarArrowGeometry() {
+ auto geometry_builder = GetGraphFactory()->CreateGeometryBuilder();
+ geometry_builder->BeginFigure({-kScrollBarArrowHeight / 2, 0});
+ geometry_builder->LineTo({kScrollBarArrowHeight / 2, kScrollBarArrowHeight});
+ geometry_builder->LineTo({kScrollBarArrowHeight / 2, -kScrollBarArrowHeight});
+ geometry_builder->CloseFigure(true);
+ return geometry_builder->Build();
+}
+} // namespace
+
+ScrollBar::ScrollBar(gsl::not_null<ScrollRenderObject*> render_object,
+ Direction direction)
+ : render_object_(render_object), direction_(direction) {
+ // TODO: Use theme resource and delete this.
+
+ auto graphics_factory = GetUiApplication()->GetInstance()->GetGraphFactory();
+
+ collapsed_thumb_brush_ =
+ graphics_factory->CreateSolidColorBrush(colors::gray.WithAlpha(128));
+ expanded_thumb_brush_ = graphics_factory->CreateSolidColorBrush(colors::gray);
+ expanded_slot_brush_ =
+ graphics_factory->CreateSolidColorBrush(colors::seashell);
+ expanded_arrow_brush_ = graphics_factory->CreateSolidColorBrush(colors::gray);
+ expanded_arrow_background_brush_ =
+ graphics_factory->CreateSolidColorBrush(colors::seashell);
+
+ arrow_geometry_ = CreateScrollBarArrowGeometry();
+}
+
+ScrollBar::~ScrollBar() { RestoreCursor(); }
+
+void ScrollBar::SetEnabled(bool value) {
+ if (value == is_enabled_) return;
+ if (!value) {
+ SetExpanded(false);
+ if (move_thumb_start_) {
+ if (const auto control = this->render_object_->GetAttachedControl()) {
+ control->ReleaseMouse();
+ }
+ move_thumb_start_ = std::nullopt;
+ }
+ }
+}
+
+void ScrollBar::SetExpanded(bool value) {
+ if (is_expanded_ == value) return;
+ is_expanded_ = value;
+ render_object_->InvalidatePaint();
+}
+
+void ScrollBar::Draw(platform::graphics::IPainter* painter) {
+ if (is_enabled_) {
+ OnDraw(painter, is_expanded_);
+ }
+}
+
+void ScrollBar::InstallHandlers(controls::Control* control) {
+ event_guard_.Clear();
+ if (control != nullptr) {
+ event_guard_ +=
+ control->MouseDownEvent()->Bubble()->PrependShortCircuitHandler(
+ [control, this](event::MouseButtonEventArgs& event) {
+ if (event.GetButton() == mouse_buttons::left && IsEnabled() &&
+ IsExpanded()) {
+ auto hit_test_result =
+ ExpandedHitTest(event.GetPoint(render_object_));
+ if (!hit_test_result) return false;
+
+ switch (*hit_test_result) {
+ case ScrollBarAreaKind::UpArrow:
+ this->scroll_attempt_event_.Raise(
+ {GetDirection(), ScrollKind::Line, -1});
+ event.SetHandled();
+ return true;
+ case ScrollBarAreaKind::DownArrow:
+ this->scroll_attempt_event_.Raise(
+ {GetDirection(), ScrollKind::Line, 1});
+ event.SetHandled();
+ return true;
+ case ScrollBarAreaKind::UpSlot:
+ this->scroll_attempt_event_.Raise(
+ {GetDirection(), ScrollKind::Page, -1});
+ event.SetHandled();
+ return true;
+ case ScrollBarAreaKind::DownSlot:
+ this->scroll_attempt_event_.Raise(
+ {GetDirection(), ScrollKind::Page, 1});
+ event.SetHandled();
+ return true;
+ case ScrollBarAreaKind::Thumb: {
+ auto thumb_rect =
+ GetExpandedAreaRect(ScrollBarAreaKind::Thumb);
+ assert(thumb_rect);
+
+ if (!control->CaptureMouse()) break;
+ move_thumb_thumb_original_rect_ = *thumb_rect;
+ move_thumb_start_ = event.GetPoint();
+ event.SetHandled();
+ return true;
+ }
+ default:
+ break;
+ }
+ }
+
+ return false;
+ });
+
+ event_guard_ +=
+ control->MouseUpEvent()->Bubble()->PrependShortCircuitHandler(
+ [control, this](event::MouseButtonEventArgs& event) {
+ if (event.GetButton() == mouse_buttons::left &&
+ move_thumb_start_) {
+ move_thumb_start_ = std::nullopt;
+
+ auto hit_test_result =
+ ExpandedHitTest(event.GetPoint(this->render_object_));
+ if (!hit_test_result) {
+ OnMouseLeave();
+ }
+
+ control->ReleaseMouse();
+ event.SetHandled();
+ return true;
+ }
+ return false;
+ });
+
+ event_guard_ +=
+ control->MouseMoveEvent()->Bubble()->PrependShortCircuitHandler(
+ [this](event::MouseEventArgs& event) {
+ if (move_thumb_start_) {
+ auto new_scroll_position = CalculateNewScrollPosition(
+ move_thumb_thumb_original_rect_,
+ event.GetPoint() - *move_thumb_start_);
+
+ this->scroll_attempt_event_.Raise({GetDirection(),
+ ScrollKind::Absolute,
+ new_scroll_position});
+ event.SetHandled();
+ return true;
+ }
+
+ if (IsEnabled()) {
+ if (IsExpanded()) {
+ auto hit_test_result =
+ ExpandedHitTest(event.GetPoint(this->render_object_));
+ if (hit_test_result) {
+ SetCursor();
+ StopAutoCollapseTimer();
+ } else {
+ OnMouseLeave();
+ }
+ } else {
+ auto trigger_expand_area =
+ GetCollapsedTriggerExpandAreaRect();
+ if (trigger_expand_area &&
+ trigger_expand_area->IsPointInside(
+ event.GetPoint(this->render_object_))) {
+ SetExpanded(true);
+ SetCursor();
+ event.SetHandled();
+ return true;
+ }
+ }
+ }
+
+ return false;
+ });
+
+ event_guard_ +=
+ control->MouseLeaveEvent()->Bubble()->PrependShortCircuitHandler(
+ [this](event::MouseEventArgs&) {
+ if (IsExpanded() && !move_thumb_start_) {
+ OnMouseLeave();
+ }
+ return false;
+ });
+ }
+}
+
+gsl::not_null<std::shared_ptr<platform::graphics::IBrush>>
+ScrollBar::GetCollapsedThumbBrush() const {
+ // TODO: Read theme resource.
+ return collapsed_thumb_brush_;
+}
+
+gsl::not_null<std::shared_ptr<platform::graphics::IBrush>>
+ScrollBar::GetExpandedThumbBrush() const {
+ // TODO: Read theme resource.
+ return expanded_thumb_brush_;
+}
+
+gsl::not_null<std::shared_ptr<platform::graphics::IBrush>>
+ScrollBar::GetExpandedSlotBrush() const {
+ // TODO: Read theme resource.
+ return expanded_slot_brush_;
+}
+
+gsl::not_null<std::shared_ptr<platform::graphics::IBrush>>
+ScrollBar::GetExpandedArrowBrush() const {
+ // TODO: Read theme resource.
+ return expanded_arrow_brush_;
+}
+
+gsl::not_null<std::shared_ptr<platform::graphics::IBrush>>
+ScrollBar::GetExpandedArrowBackgroundBrush() const {
+ // TODO: Read theme resource.
+ return expanded_arrow_background_brush_;
+}
+
+void ScrollBar::OnDraw(platform::graphics::IPainter* painter,
+ bool is_expanded) {
+ if (is_expanded) {
+ auto thumb_rect = GetExpandedAreaRect(ScrollBarAreaKind::Thumb);
+ if (thumb_rect)
+ painter->FillRectangle(*thumb_rect, GetExpandedThumbBrush().get().get());
+
+ auto slot_brush = GetExpandedSlotBrush().get().get();
+
+ auto up_slot_rect = GetExpandedAreaRect(ScrollBarAreaKind::UpSlot);
+ if (up_slot_rect) painter->FillRectangle(*up_slot_rect, slot_brush);
+
+ auto down_slot_rect = GetExpandedAreaRect(ScrollBarAreaKind::DownSlot);
+ if (down_slot_rect) painter->FillRectangle(*down_slot_rect, slot_brush);
+
+ auto up_arrow = GetExpandedAreaRect(ScrollBarAreaKind::UpArrow);
+ if (up_arrow) this->DrawUpArrow(painter, *up_arrow);
+
+ auto down_arrow = GetExpandedAreaRect(ScrollBarAreaKind::DownArrow);
+ if (down_arrow) this->DrawDownArrow(painter, *down_arrow);
+ } else {
+ auto optional_rect = GetCollapsedThumbRect();
+ if (optional_rect) {
+ painter->FillRectangle(*optional_rect,
+ GetCollapsedThumbBrush().get().get());
+ }
+ }
+}
+
+void ScrollBar::SetCursor() {
+ if (!old_cursor_) {
+ if (const auto control = render_object_->GetAttachedControl()) {
+ old_cursor_ = control->GetCursor();
+ control->SetCursor(
+ GetUiApplication()->GetCursorManager()->GetSystemCursor(
+ platform::gui::SystemCursorType::Arrow));
+ }
+ }
+}
+
+void ScrollBar::RestoreCursor() {
+ if (old_cursor_) {
+ if (const auto control = render_object_->GetAttachedControl()) {
+ control->SetCursor(*old_cursor_);
+ }
+ old_cursor_ = std::nullopt;
+ }
+}
+
+void ScrollBar::BeginAutoCollapseTimer() {
+ if (!auto_collapse_timer_canceler_ && IsExpanded()) {
+ auto_collapse_timer_canceler_ = GetUiApplication()->SetTimeout(
+ kScrollBarAutoCollapseDelay, [this] { this->SetExpanded(false); });
+ }
+}
+
+void ScrollBar::StopAutoCollapseTimer() {
+ auto_collapse_timer_canceler_.Reset();
+}
+
+void ScrollBar::OnMouseLeave() {
+ RestoreCursor();
+ BeginAutoCollapseTimer();
+}
+
+std::optional<ScrollBarAreaKind> ScrollBar::ExpandedHitTest(
+ const Point& point) {
+ for (auto kind : kScrollBarAreaKindList) {
+ auto rect = this->GetExpandedAreaRect(kind);
+ if (rect) {
+ if (rect->IsPointInside(point)) return kind;
+ }
+ }
+ return std::nullopt;
+}
+
+HorizontalScrollBar::HorizontalScrollBar(
+ gsl::not_null<ScrollRenderObject*> render_object)
+ : ScrollBar(render_object, Direction::Horizontal) {}
+
+void HorizontalScrollBar::DrawUpArrow(platform::graphics::IPainter* painter,
+ const Rect& area) {
+ painter->FillRectangle(area, GetExpandedArrowBackgroundBrush().get().get());
+
+ platform::graphics::util::WithTransform(
+ painter, Matrix::Translation(area.GetCenter()),
+ [this](platform::graphics::IPainter* painter) {
+ painter->FillGeometry(arrow_geometry_.get(),
+ GetExpandedArrowBrush().get().get());
+ });
+}
+
+void HorizontalScrollBar::DrawDownArrow(platform::graphics::IPainter* painter,
+ const Rect& area) {
+ painter->FillRectangle(area, GetExpandedArrowBackgroundBrush().get().get());
+
+ platform::graphics::util::WithTransform(
+ painter, Matrix::Rotation(180) * Matrix::Translation(area.GetCenter()),
+ [this](platform::graphics::IPainter* painter) {
+ painter->FillGeometry(arrow_geometry_.get(),
+ GetExpandedArrowBrush().get().get());
+ });
+}
+
+bool HorizontalScrollBar::IsShowBar() {
+ const auto child = render_object_->GetFirstChild();
+ if (child == nullptr) return false;
+
+ const auto view_rect = render_object_->GetViewRect();
+ const auto child_size = child->GetSize();
+
+ if (view_rect.width >= child_size.width) return false;
+
+ return true;
+}
+
+std::optional<Rect> HorizontalScrollBar::GetExpandedAreaRect(
+ ScrollBarAreaKind area_kind) {
+ auto show = IsShowBar();
+ if (!show) return std::nullopt;
+
+ const auto padding_rect = render_object_->GetPaddingRect();
+
+ const auto child = render_object_->GetFirstChild();
+
+ const auto view_rect = render_object_->GetViewRect();
+ const auto child_size = child->GetSize();
+
+ const float start_percentage = view_rect.left / child_size.width;
+ const float length_percentage = view_rect.width / child_size.width;
+ const float end_percentage = start_percentage + length_percentage;
+
+ const float top = padding_rect.GetBottom() - kScrollBarExpandWidth;
+ const float height = kScrollBarExpandWidth;
+
+ // Without arrow.
+ const float bar_area_length = padding_rect.width - 3 * kScrollBarExpandWidth;
+ const float bar_area_start = padding_rect.left + kScrollBarExpandWidth;
+
+ switch (area_kind) {
+ case ScrollBarAreaKind::UpArrow:
+ return Rect{padding_rect.left, top, kScrollBarExpandWidth, height};
+ case ScrollBarAreaKind::DownArrow:
+ return Rect{padding_rect.GetRight() - 2 * kScrollBarExpandWidth, top,
+ kScrollBarExpandWidth, height};
+ case ScrollBarAreaKind::UpSlot:
+ return Rect{bar_area_start, top, bar_area_length * start_percentage,
+ height};
+ case ScrollBarAreaKind::DownSlot:
+ return Rect{bar_area_start + bar_area_length * end_percentage, top,
+ bar_area_length * (1 - end_percentage), height};
+ case ScrollBarAreaKind::Thumb:
+ return Rect{bar_area_start + bar_area_length * start_percentage, top,
+ bar_area_length * length_percentage, height};
+ default:
+ throw std::invalid_argument("Unsupported scroll area kind.");
+ }
+}
+
+std::optional<Rect> HorizontalScrollBar::GetCollapsedTriggerExpandAreaRect() {
+ auto show = IsShowBar();
+ if (!show) return std::nullopt;
+
+ const auto padding_rect = render_object_->GetPaddingRect();
+
+ return Rect{
+ padding_rect.left,
+ padding_rect.GetBottom() - kScrollBarCollapsedTriggerExpandAreaWidth,
+ padding_rect.width, kScrollBarCollapseThumbWidth};
+}
+
+std::optional<Rect> HorizontalScrollBar::GetCollapsedThumbRect() {
+ auto show = IsShowBar();
+ if (!show) return std::nullopt;
+
+ const auto child = render_object_->GetFirstChild();
+
+ const auto view_rect = render_object_->GetViewRect();
+ const auto child_size = child->GetSize();
+
+ const float start_percentage = view_rect.left / child_size.width;
+ const float length_percentage = view_rect.width / child_size.width;
+ // const float end_percentage = start_percentage + length_percentage;
+
+ const auto padding_rect = render_object_->GetPaddingRect();
+
+ return Rect{padding_rect.left + padding_rect.width * start_percentage,
+ padding_rect.GetBottom() - kScrollBarCollapseThumbWidth,
+ padding_rect.width * length_percentage,
+ kScrollBarCollapseThumbWidth};
+}
+
+float HorizontalScrollBar::CalculateNewScrollPosition(
+ const Rect& thumb_original_rect, const Point& mouse_offset) {
+ auto new_thumb_start = thumb_original_rect.left + mouse_offset.x;
+
+ const auto padding_rect = render_object_->GetPaddingRect();
+
+ auto scroll_area_start = padding_rect.left + kScrollBarExpandWidth;
+ auto scroll_area_end = padding_rect.GetRight() - 2 * kScrollBarExpandWidth;
+
+ auto thumb_head_end = scroll_area_end - thumb_original_rect.width;
+
+ const auto child = render_object_->GetFirstChild();
+ const auto child_size = child->GetSize();
+
+ new_thumb_start =
+ std::clamp(new_thumb_start, scroll_area_start, thumb_head_end);
+
+ auto offset = (new_thumb_start - scroll_area_start) /
+ (scroll_area_end - scroll_area_start) * child_size.width;
+
+ return offset;
+}
+
+VerticalScrollBar::VerticalScrollBar(
+ gsl::not_null<ScrollRenderObject*> render_object)
+ : ScrollBar(render_object, Direction::Vertical) {}
+
+void VerticalScrollBar::DrawUpArrow(platform::graphics::IPainter* painter,
+ const Rect& area) {
+ painter->FillRectangle(area, GetExpandedArrowBackgroundBrush().get().get());
+
+ platform::graphics::util::WithTransform(
+ painter, Matrix::Rotation(90) * Matrix::Translation(area.GetCenter()),
+ [this](platform::graphics::IPainter* painter) {
+ painter->FillGeometry(arrow_geometry_.get(),
+ GetExpandedArrowBrush().get().get());
+ });
+}
+
+void VerticalScrollBar::DrawDownArrow(platform::graphics::IPainter* painter,
+ const Rect& area) {
+ painter->FillRectangle(area, GetExpandedArrowBackgroundBrush().get().get());
+
+ platform::graphics::util::WithTransform(
+ painter, Matrix::Rotation(270) * Matrix::Translation(area.GetCenter()),
+ [this](platform::graphics::IPainter* painter) {
+ painter->FillGeometry(arrow_geometry_.get(),
+ GetExpandedArrowBrush().get().get());
+ });
+}
+
+bool VerticalScrollBar::IsShowBar() {
+ const auto child = render_object_->GetFirstChild();
+ if (child == nullptr) return false;
+
+ const auto view_rect = render_object_->GetViewRect();
+ const auto child_size = child->GetSize();
+
+ if (view_rect.height >= child_size.height) return false;
+
+ return true;
+}
+
+std::optional<Rect> VerticalScrollBar::GetExpandedAreaRect(
+ ScrollBarAreaKind area_kind) {
+ auto show = IsShowBar();
+ if (!show) return std::nullopt;
+
+ const auto padding_rect = render_object_->GetPaddingRect();
+
+ const auto child = render_object_->GetFirstChild();
+
+ const auto view_rect = render_object_->GetViewRect();
+ const auto child_size = child->GetSize();
+
+ const float start_percentage = view_rect.top / child_size.height;
+ const float length_percentage = view_rect.height / child_size.height;
+ const float end_percentage = start_percentage + length_percentage;
+
+ const float left = padding_rect.GetRight() - kScrollBarExpandWidth;
+ const float width = kScrollBarExpandWidth;
+
+ // Without arrow.
+ const float bar_area_length = padding_rect.height - 3 * kScrollBarExpandWidth;
+ const float bar_area_start = padding_rect.top + kScrollBarExpandWidth;
+
+ switch (area_kind) {
+ case ScrollBarAreaKind::UpArrow:
+ return Rect{left, padding_rect.top, width, kScrollBarExpandWidth};
+ case ScrollBarAreaKind::DownArrow:
+ return Rect{left, padding_rect.GetBottom() - 2 * kScrollBarExpandWidth,
+ width, kScrollBarExpandWidth};
+ case ScrollBarAreaKind::UpSlot:
+ return Rect{left, bar_area_start, width,
+ bar_area_length * start_percentage};
+ case ScrollBarAreaKind::DownSlot:
+ return Rect{left, bar_area_start + bar_area_length * end_percentage,
+ width, bar_area_length * (1 - end_percentage)};
+ case ScrollBarAreaKind::Thumb:
+ return Rect{left, bar_area_start + bar_area_length * start_percentage,
+ width, bar_area_length * length_percentage};
+ default:
+ throw std::invalid_argument("Unsupported scroll area kind.");
+ }
+}
+
+std::optional<Rect> VerticalScrollBar::GetCollapsedTriggerExpandAreaRect() {
+ auto show = IsShowBar();
+ if (!show) return std::nullopt;
+
+ const auto padding_rect = render_object_->GetPaddingRect();
+
+ return Rect{
+ padding_rect.GetRight() - kScrollBarCollapsedTriggerExpandAreaWidth,
+ padding_rect.top, kScrollBarCollapseThumbWidth, padding_rect.height};
+}
+
+std::optional<Rect> VerticalScrollBar::GetCollapsedThumbRect() {
+ const auto child = render_object_->GetFirstChild();
+ if (child == nullptr) return std::nullopt;
+
+ const auto view_rect = render_object_->GetViewRect();
+ const auto padding_rect = render_object_->GetPaddingRect();
+ const auto child_size = child->GetSize();
+
+ if (view_rect.height >= child_size.height) return std::nullopt;
+
+ const float start_percentage = view_rect.top / child_size.height;
+ const float length_percentage = view_rect.height / child_size.height;
+ // const float end_percentage = start_percentage + length_percentage;
+
+ return Rect{padding_rect.GetRight() - kScrollBarCollapseThumbWidth,
+ padding_rect.top + padding_rect.height * start_percentage,
+ kScrollBarCollapseThumbWidth,
+ padding_rect.height * length_percentage};
+}
+
+float VerticalScrollBar::CalculateNewScrollPosition(
+ const Rect& thumb_original_rect, const Point& mouse_offset) {
+ auto new_thumb_start = thumb_original_rect.top + mouse_offset.y;
+
+ const auto padding_rect = render_object_->GetPaddingRect();
+
+ auto scroll_area_start = padding_rect.top + kScrollBarExpandWidth;
+ auto scroll_area_end = padding_rect.GetBottom() - 2 * kScrollBarExpandWidth;
+
+ auto thumb_head_end = scroll_area_end - thumb_original_rect.height;
+
+ const auto child = render_object_->GetFirstChild();
+ const auto child_size = child->GetSize();
+
+ new_thumb_start =
+ std::clamp(new_thumb_start, scroll_area_start, thumb_head_end);
+
+ auto offset = (new_thumb_start - scroll_area_start) /
+ (scroll_area_end - scroll_area_start) * child_size.width;
+
+ return offset;
+}
+
+ScrollBarDelegate::ScrollBarDelegate(
+ gsl::not_null<ScrollRenderObject*> render_object)
+ : render_object_(render_object),
+ horizontal_bar_(render_object),
+ vertical_bar_(render_object) {
+ horizontal_bar_.ScrollAttemptEvent()->AddHandler(
+ [this](auto scroll) { this->scroll_attempt_event_.Raise(scroll); });
+ vertical_bar_.ScrollAttemptEvent()->AddHandler(
+ [this](auto scroll) { this->scroll_attempt_event_.Raise(scroll); });
+}
+
+void ScrollBarDelegate::DrawScrollBar(platform::graphics::IPainter* painter) {
+ horizontal_bar_.Draw(painter);
+ vertical_bar_.Draw(painter);
+}
+
+void ScrollBarDelegate::InstallHandlers(controls::Control* control) {
+ horizontal_bar_.InstallHandlers(control);
+ vertical_bar_.InstallHandlers(control);
+}
+} // namespace cru::ui::render
diff --git a/src/ui/render/ScrollRenderObject.cpp b/src/ui/render/ScrollRenderObject.cpp
index 08ce744b..fd5143ff 100644
--- a/src/ui/render/ScrollRenderObject.cpp
+++ b/src/ui/render/ScrollRenderObject.cpp
@@ -1,11 +1,18 @@
#include "cru/ui/render/ScrollRenderObject.hpp"
-#include "cru/platform/graph/Painter.hpp"
-#include "cru/platform/graph/util/Painter.hpp"
+#include "cru/platform/graphics/Painter.hpp"
+#include "cru/platform/graphics/util/Painter.hpp"
+#include "cru/ui/Base.hpp"
+#include "cru/ui/controls/Control.hpp"
+#include "cru/ui/render/ScrollBar.hpp"
#include <algorithm>
+#include <memory>
+#include <optional>
namespace cru::ui::render {
+constexpr float kLineHeight = 16;
+
namespace {
// This method assumes margin offset is already considered.
// It promises that it won't return negetive value.
@@ -24,13 +31,46 @@ Point CoerceScroll(const Point& scroll_offset, const Size& content_size,
n = max;
};
- coerce(result.x, scroll_offset.x);
- coerce(result.y, scroll_offset.y);
+ coerce(result.x, max_scroll.x);
+ coerce(result.y, max_scroll.y);
return result;
}
} // namespace
+ScrollRenderObject::ScrollRenderObject() : RenderObject(ChildMode::Single) {
+ scroll_bar_delegate_ = std::make_unique<ScrollBarDelegate>(this);
+ scroll_bar_delegate_->ScrollAttemptEvent()->AddHandler(
+ [this](const struct Scroll& scroll) { this->Scroll(scroll); });
+}
+
+void ScrollRenderObject::Scroll(const struct Scroll& scroll) {
+ auto direction = scroll.direction;
+
+ switch (scroll.kind) {
+ case ScrollKind::Absolute:
+ SetScrollOffset(direction, scroll.value);
+ break;
+ case ScrollKind::Relative:
+ SetScrollOffset(direction,
+ GetScrollOffset(scroll.direction) + scroll.value);
+ break;
+ case ScrollKind::Page:
+ SetScrollOffset(direction, GetScrollOffset(direction) +
+ (direction == Direction::Horizontal
+ ? GetViewRect().width
+ : GetViewRect().height) *
+ scroll.value);
+ break;
+ case ScrollKind::Line:
+ SetScrollOffset(direction,
+ GetScrollOffset(direction) + kLineHeight * scroll.value);
+ break;
+ default:
+ break;
+ }
+}
+
RenderObject* ScrollRenderObject::HitTest(const Point& point) {
if (const auto child = GetSingleChild()) {
const auto offset = child->GetOffset();
@@ -42,16 +82,17 @@ RenderObject* ScrollRenderObject::HitTest(const Point& point) {
return rect.IsPointInside(point) ? this : nullptr;
} // namespace cru::ui::render
-void ScrollRenderObject::OnDrawCore(platform::graph::IPainter* painter) {
+void ScrollRenderObject::OnDrawCore(platform::graphics::IPainter* painter) {
DefaultDrawContent(painter);
if (const auto child = GetSingleChild()) {
- painter->PushLayer(this->GetPaddingRect());
+ painter->PushLayer(this->GetContentRect());
const auto offset = child->GetOffset();
- platform::graph::util::WithTransform(
+ platform::graphics::util::WithTransform(
painter, Matrix::Translation(offset.x, offset.y),
- [child](platform::graph::IPainter* p) { child->Draw(p); });
+ [child](platform::graphics::IPainter* p) { child->Draw(p); });
painter->PopLayer();
}
+ scroll_bar_delegate_->DrawScrollBar(painter);
}
Point ScrollRenderObject::GetScrollOffset() {
@@ -138,8 +179,15 @@ Size ScrollRenderObject::OnMeasureContent(const MeasureRequirement& requirement,
void ScrollRenderObject::OnLayoutContent(const Rect& content_rect) {
if (const auto child = GetSingleChild()) {
- const auto child_size = child->GetSize();
child->Layout(content_rect.GetLeftTop() - GetScrollOffset());
}
}
+
+void ScrollRenderObject::OnAttachedControlChanged(controls::Control* control) {
+ if (control) {
+ scroll_bar_delegate_->InstallHandlers(control);
+ } else {
+ scroll_bar_delegate_->UninstallHandlers();
+ }
+}
} // namespace cru::ui::render
diff --git a/src/ui/render/TextRenderObject.cpp b/src/ui/render/TextRenderObject.cpp
index cecbe1f3..06092d52 100644
--- a/src/ui/render/TextRenderObject.cpp
+++ b/src/ui/render/TextRenderObject.cpp
@@ -2,19 +2,19 @@
#include "../Helper.hpp"
#include "cru/common/Logger.hpp"
-#include "cru/platform/graph/Factory.hpp"
-#include "cru/platform/graph/TextLayout.hpp"
-#include "cru/platform/graph/util/Painter.hpp"
+#include "cru/platform/graphics/Factory.hpp"
+#include "cru/platform/graphics/TextLayout.hpp"
+#include "cru/platform/graphics/util/Painter.hpp"
#include <algorithm>
#include <limits>
namespace cru::ui::render {
TextRenderObject::TextRenderObject(
- std::shared_ptr<platform::graph::IBrush> brush,
- std::shared_ptr<platform::graph::IFont> font,
- std::shared_ptr<platform::graph::IBrush> selection_brush,
- std::shared_ptr<platform::graph::IBrush> caret_brush) {
+ std::shared_ptr<platform::graphics::IBrush> brush,
+ std::shared_ptr<platform::graphics::IFont> font,
+ std::shared_ptr<platform::graphics::IBrush> selection_brush,
+ std::shared_ptr<platform::graphics::IBrush> caret_brush) {
Expects(brush);
Expects(font);
Expects(selection_brush);
@@ -43,20 +43,22 @@ std::u16string_view TextRenderObject::GetTextView() const {
void TextRenderObject::SetText(std::u16string new_text) {
text_layout_->SetText(std::move(new_text));
+ InvalidateLayout();
}
void TextRenderObject::SetBrush(
- std::shared_ptr<platform::graph::IBrush> new_brush) {
+ std::shared_ptr<platform::graphics::IBrush> new_brush) {
Expects(new_brush);
new_brush.swap(brush_);
InvalidatePaint();
}
-std::shared_ptr<platform::graph::IFont> TextRenderObject::GetFont() const {
+std::shared_ptr<platform::graphics::IFont> TextRenderObject::GetFont() const {
return text_layout_->GetFont();
}
-void TextRenderObject::SetFont(std::shared_ptr<platform::graph::IFont> font) {
+void TextRenderObject::SetFont(
+ std::shared_ptr<platform::graphics::IFont> font) {
Expects(font);
text_layout_->SetFont(std::move(font));
}
@@ -69,7 +71,7 @@ Point TextRenderObject::TextSinglePoint(gsl::index position, bool trailing) {
return text_layout_->TextSinglePoint(position, trailing);
}
-platform::graph::TextHitTestResult TextRenderObject::TextHitTest(
+platform::graphics::TextHitTestResult TextRenderObject::TextHitTest(
const Point& point) {
return text_layout_->HitTest(point);
}
@@ -80,7 +82,7 @@ void TextRenderObject::SetSelectionRange(std::optional<TextRange> new_range) {
}
void TextRenderObject::SetSelectionBrush(
- std::shared_ptr<platform::graph::IBrush> new_brush) {
+ std::shared_ptr<platform::graphics::IBrush> new_brush) {
Expects(new_brush);
new_brush.swap(selection_brush_);
if (selection_range_ && selection_range_->count) {
@@ -105,7 +107,7 @@ void TextRenderObject::SetCaretPosition(gsl::index position) {
}
void TextRenderObject::GetCaretBrush(
- std::shared_ptr<platform::graph::IBrush> brush) {
+ std::shared_ptr<platform::graphics::IBrush> brush) {
Expects(brush);
brush.swap(caret_brush_);
if (draw_caret_) {
@@ -153,12 +155,18 @@ Rect TextRenderObject::GetCaretRect() {
return rect;
}
+void TextRenderObject::SetMeasureIncludingTrailingSpace(bool including) {
+ if (is_measure_including_trailing_space_ == including) return;
+ is_measure_including_trailing_space_ = including;
+ InvalidateLayout();
+}
+
RenderObject* TextRenderObject::HitTest(const Point& point) {
const auto padding_rect = GetPaddingRect();
return padding_rect.IsPointInside(point) ? this : nullptr;
}
-void TextRenderObject::OnDrawContent(platform::graph::IPainter* painter) {
+void TextRenderObject::OnDrawContent(platform::graphics::IPainter* painter) {
if (this->selection_range_.has_value()) {
const auto&& rects =
text_layout_->TextRangeRect(this->selection_range_.value());
@@ -184,7 +192,9 @@ Size TextRenderObject::OnMeasureContent(const MeasureRequirement& requirement,
text_layout_->SetMaxWidth(measure_width);
text_layout_->SetMaxHeight(std::numeric_limits<float>::max());
- const auto text_size = text_layout_->GetTextBounds().GetSize();
+ const auto text_size =
+ text_layout_->GetTextBounds(is_measure_including_trailing_space_)
+ .GetSize();
auto result = text_size;
if (requirement.max.width.IsSpecified() &&
diff --git a/src/ui/render/WindowRenderObject.cpp b/src/ui/render/WindowRenderObject.cpp
deleted file mode 100644
index 4adf559e..00000000
--- a/src/ui/render/WindowRenderObject.cpp
+++ /dev/null
@@ -1,40 +0,0 @@
-#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); }));
-}
-
-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 MeasureRequirement& requirement,
- const MeasureSize& preferred_size) {
- if (const auto child = GetChild()) {
- child->Measure(requirement, preferred_size);
- return child->GetSize();
- } else {
- return Size{};
- }
-}
-
-void WindowRenderObject::OnLayoutContent(const Rect& content_rect) {
- if (const auto child = GetChild()) child->Layout(content_rect.GetLeftTop());
-}
-} // namespace cru::ui::render
diff --git a/src/ui/style/Condition.cpp b/src/ui/style/Condition.cpp
new file mode 100644
index 00000000..f4866c04
--- /dev/null
+++ b/src/ui/style/Condition.cpp
@@ -0,0 +1,84 @@
+#include "cru/ui/style/Condition.hpp"
+#include <memory>
+
+#include "cru/common/ClonablePtr.hpp"
+#include "cru/common/Event.hpp"
+#include "cru/ui/controls/Control.hpp"
+#include "cru/ui/controls/IClickableControl.hpp"
+#include "cru/ui/helper/ClickDetector.hpp"
+
+namespace cru::ui::style {
+CompoundCondition::CompoundCondition(
+ std::vector<ClonablePtr<Condition>> conditions)
+ : conditions_(std::move(conditions)) {}
+
+std::vector<IBaseEvent*> CompoundCondition::ChangeOn(
+ controls::Control* control) const {
+ std::vector<IBaseEvent*> result;
+
+ for (auto condition : conditions_) {
+ for (auto e : condition->ChangeOn(control)) {
+ result.push_back(e);
+ }
+ }
+
+ return result;
+}
+
+bool AndCondition::Judge(controls::Control* control) const {
+ for (auto condition : conditions_) {
+ if (!condition->Judge(control)) return false;
+ }
+ return true;
+}
+
+bool OrCondition::Judge(controls::Control* control) const {
+ for (auto condition : conditions_) {
+ if (condition->Judge(control)) return true;
+ }
+ return false;
+}
+
+FocusCondition::FocusCondition(bool has_focus) : has_focus_(has_focus) {}
+
+std::vector<IBaseEvent*> FocusCondition::ChangeOn(
+ controls::Control* control) const {
+ return {control->GainFocusEvent()->Direct(),
+ control->LoseFocusEvent()->Direct()};
+}
+
+bool FocusCondition::Judge(controls::Control* control) const {
+ return control->HasFocus() == has_focus_;
+}
+
+std::vector<IBaseEvent*> HoverCondition::ChangeOn(
+ controls::Control* control) const {
+ return {control->MouseEnterEvent()->Direct(),
+ control->MouseLeaveEvent()->Direct()};
+}
+
+bool HoverCondition::Judge(controls::Control* control) const {
+ return control->IsMouseOver() == hover_;
+}
+
+ClickStateCondition::ClickStateCondition(helper::ClickState click_state)
+ : click_state_(click_state) {}
+
+std::vector<IBaseEvent*> ClickStateCondition::ChangeOn(
+ controls::Control* control) const {
+ auto clickable_control = dynamic_cast<controls::IClickableControl*>(control);
+ if (clickable_control) {
+ return {clickable_control->ClickStateChangeEvent()};
+ } else {
+ return {};
+ }
+}
+
+bool ClickStateCondition::Judge(controls::Control* control) const {
+ auto clickable_control = dynamic_cast<controls::IClickableControl*>(control);
+ if (clickable_control) {
+ return clickable_control->GetClickState() == click_state_;
+ }
+ return false;
+}
+} // namespace cru::ui::style
diff --git a/src/ui/style/StyleRule.cpp b/src/ui/style/StyleRule.cpp
new file mode 100644
index 00000000..1a72a970
--- /dev/null
+++ b/src/ui/style/StyleRule.cpp
@@ -0,0 +1,17 @@
+#include "cru/ui/style/StyleRule.hpp"
+
+namespace cru::ui::style {
+StyleRule::StyleRule(ClonablePtr<Condition> condition,
+ ClonablePtr<Styler> styler, std::u16string name)
+ : condition_(std::move(condition)),
+ styler_(std::move(styler)),
+ name_(std::move(name)) {}
+
+bool StyleRule::CheckAndApply(controls::Control *control) const {
+ auto active = condition_->Judge(control);
+ if (active) {
+ styler_->Apply(control);
+ }
+ return active;
+}
+} // namespace cru::ui::style
diff --git a/src/ui/style/StyleRuleSet.cpp b/src/ui/style/StyleRuleSet.cpp
new file mode 100644
index 00000000..537d1956
--- /dev/null
+++ b/src/ui/style/StyleRuleSet.cpp
@@ -0,0 +1,97 @@
+#include "cru/ui/style/StyleRuleSet.hpp"
+#include "cru/common/Event.hpp"
+#include "cru/ui/controls/Control.hpp"
+#include "gsl/gsl_assert"
+
+#include <unordered_set>
+
+namespace cru::ui::style {
+StyleRuleSet::StyleRuleSet(StyleRuleSet* parent) { SetParent(parent); }
+
+void StyleRuleSet::SetParent(StyleRuleSet* parent) {
+ if (parent == parent_) return;
+ parent_change_event_guard_.Reset();
+ parent_ = parent;
+ if (parent != nullptr) {
+ parent_change_event_guard_.Reset(parent->ChangeEvent()->AddSpyOnlyHandler(
+ [this] { this->RaiseChangeEvent(); }));
+ }
+ RaiseChangeEvent();
+}
+
+void StyleRuleSet::AddStyleRule(StyleRule rule, gsl::index index) {
+ Expects(index >= 0 && index <= GetSize());
+
+ rules_.insert(rules_.cbegin() + index, std::move(rule));
+
+ RaiseChangeEvent();
+}
+
+void StyleRuleSet::RemoveStyleRule(gsl::index index, gsl::index count) {
+ Expects(index >= 0);
+ Expects(count >= 0 && index + count <= GetSize());
+
+ rules_.erase(rules_.cbegin() + index, rules_.cbegin() + index + count);
+
+ RaiseChangeEvent();
+}
+
+void StyleRuleSet::Set(const StyleRuleSet& other, bool set_parent) {
+ rules_ = other.rules_;
+ if (set_parent) parent_ = other.parent_;
+
+ RaiseChangeEvent();
+}
+
+StyleRuleSetBind::StyleRuleSetBind(controls::Control* control,
+ StyleRuleSet* ruleset)
+ : control_(control), ruleset_(ruleset) {
+ Expects(control);
+ Expects(ruleset);
+
+ ruleset->ChangeEvent()->AddSpyOnlyHandler([this] {
+ UpdateRuleSetChainCache();
+ UpdateChangeListener();
+ UpdateStyle();
+ });
+}
+
+void StyleRuleSetBind::UpdateRuleSetChainCache() {
+ ruleset_chain_cache_.clear();
+ auto parent = ruleset_;
+ while (parent != nullptr) {
+ ruleset_chain_cache_.push_back(parent);
+ parent = parent->GetParent();
+ }
+}
+
+void StyleRuleSetBind::UpdateChangeListener() {
+ guard_.Clear();
+
+ std::unordered_set<IBaseEvent*> events;
+
+ // ruleset order does not matter
+ for (auto ruleset : ruleset_chain_cache_) {
+ for (const auto& rule : ruleset->GetRules()) {
+ auto e = rule.GetCondition()->ChangeOn(control_);
+ events.insert(e.cbegin(), e.cend());
+ }
+ }
+
+ for (auto e : events) {
+ guard_ += e->AddSpyOnlyHandler([this] { this->UpdateStyle(); });
+ }
+}
+
+void StyleRuleSetBind::UpdateStyle() {
+ // cache is parent last, but when calculate style, parent first, so iterate
+ // reverse.
+ for (auto iter = ruleset_chain_cache_.crbegin();
+ iter != ruleset_chain_cache_.crend(); ++iter) {
+ for (const auto& rule : (*iter)->GetRules())
+ if (rule.GetCondition()->Judge(control_)) {
+ rule.GetStyler()->Apply(control_);
+ }
+ }
+}
+} // namespace cru::ui::style
diff --git a/src/ui/style/Styler.cpp b/src/ui/style/Styler.cpp
new file mode 100644
index 00000000..da3a2247
--- /dev/null
+++ b/src/ui/style/Styler.cpp
@@ -0,0 +1,29 @@
+#include "cru/ui/style/Styler.hpp"
+
+#include "../Helper.hpp"
+#include "cru/common/ClonablePtr.hpp"
+#include "cru/platform/gui/Cursor.hpp"
+#include "cru/platform/gui/UiApplication.hpp"
+#include "cru/ui/controls/Control.hpp"
+#include "cru/ui/controls/IBorderControl.hpp"
+#include "cru/ui/style/ApplyBorderStyleInfo.hpp"
+
+namespace cru::ui::style {
+BorderStyler::BorderStyler(ApplyBorderStyleInfo style)
+ : style_(std::move(style)) {}
+
+void BorderStyler::Apply(controls::Control *control) const {
+ if (auto border_control = dynamic_cast<controls::IBorderControl *>(control)) {
+ border_control->ApplyBorderStyle(style_);
+ }
+}
+
+ClonablePtr<CursorStyler> CursorStyler::Create(
+ platform::gui::SystemCursorType type) {
+ return Create(GetUiApplication()->GetCursorManager()->GetSystemCursor(type));
+}
+
+void CursorStyler::Apply(controls::Control *control) const {
+ control->SetCursor(cursor_);
+}
+} // namespace cru::ui::style
diff --git a/src/win/CMakeLists.txt b/src/win/CMakeLists.txt
index 75b0a7ca..bf8de863 100644
--- a/src/win/CMakeLists.txt
+++ b/src/win/CMakeLists.txt
@@ -2,6 +2,7 @@ set(CRU_WIN_BASE_INCLUDE_DIR ${CRU_INCLUDE_DIR}/cru/win/)
add_library(cru_win_base STATIC
DebugLogger.hpp
+ StdOutLogger.hpp
Exception.cpp
HeapDebug.cpp
@@ -13,5 +14,5 @@ target_sources(cru_win_base PUBLIC
target_compile_definitions(cru_win_base PUBLIC UNICODE _UNICODE) # use unicode
target_link_libraries(cru_win_base PUBLIC cru_base)
-add_subdirectory(graph)
-add_subdirectory(native)
+add_subdirectory(graphics)
+add_subdirectory(gui)
diff --git a/src/win/StdOutLogger.hpp b/src/win/StdOutLogger.hpp
new file mode 100644
index 00000000..bff8b30e
--- /dev/null
+++ b/src/win/StdOutLogger.hpp
@@ -0,0 +1,24 @@
+#include "cru/common/Base.hpp"
+#include "cru/win/WinPreConfig.hpp"
+
+#include "cru/common/Logger.hpp"
+
+#include <cwchar>
+
+namespace cru::platform::win {
+class WinStdOutLoggerSource : public ::cru::log::ILogSource {
+ public:
+ WinStdOutLoggerSource() = default;
+
+ CRU_DELETE_COPY(WinStdOutLoggerSource)
+ CRU_DELETE_MOVE(WinStdOutLoggerSource)
+
+ ~WinStdOutLoggerSource() = default;
+
+ void Write(::cru::log::LogLevel level, const std::u16string& s) override {
+ CRU_UNUSED(level)
+
+ std::fputws(reinterpret_cast<const wchar_t*>(s.c_str()), stdout);
+ }
+};
+} // namespace cru::platform::win
diff --git a/src/win/graph/direct/CMakeLists.txt b/src/win/graph/direct/CMakeLists.txt
deleted file mode 100644
index 5505b0b5..00000000
--- a/src/win/graph/direct/CMakeLists.txt
+++ /dev/null
@@ -1,25 +0,0 @@
-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
- TextLayout.cpp
-)
-target_sources(cru_win_graph_direct PUBLIC
- ${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/Resource.cpp b/src/win/graph/direct/Resource.cpp
deleted file mode 100644
index 772bb74b..00000000
--- a/src/win/graph/direct/Resource.cpp
+++ /dev/null
@@ -1,12 +0,0 @@
-#include "cru/win/graph/direct/Resource.hpp"
-
-#include "cru/win/graph/direct/Factory.hpp"
-
-namespace cru::platform::graph::win::direct {
-DirectGraphResource::DirectGraphResource(DirectGraphFactory* factory)
- : factory_(factory) {
- Expects(factory);
-}
-
-IGraphFactory* DirectGraphResource::GetGraphFactory() { return factory_; }
-} // namespace cru::platform::graph::win::direct
diff --git a/src/win/graph/CMakeLists.txt b/src/win/graphics/CMakeLists.txt
index c90537ac..c90537ac 100644
--- a/src/win/graph/CMakeLists.txt
+++ b/src/win/graphics/CMakeLists.txt
diff --git a/src/win/graph/direct/Brush.cpp b/src/win/graphics/direct/Brush.cpp
index 2b9d4ac3..b7842b97 100644
--- a/src/win/graph/direct/Brush.cpp
+++ b/src/win/graphics/direct/Brush.cpp
@@ -1,10 +1,10 @@
-#include "cru/win/graph/direct/Brush.hpp"
+#include "cru/win/graphics/direct/Brush.hpp"
-#include "cru/win/graph/direct/ConvertUtil.hpp"
-#include "cru/win/graph/direct/Exception.hpp"
-#include "cru/win/graph/direct/Factory.hpp"
+#include "cru/win/graphics/direct/ConvertUtil.hpp"
+#include "cru/win/graphics/direct/Exception.hpp"
+#include "cru/win/graphics/direct/Factory.hpp"
-namespace cru::platform::graph::win::direct {
+namespace cru::platform::graphics::win::direct {
D2DSolidColorBrush::D2DSolidColorBrush(DirectGraphFactory* factory)
: DirectGraphResource(factory) {
ThrowIfFailed(factory->GetDefaultD2D1DeviceContext()->CreateSolidColorBrush(
@@ -14,4 +14,4 @@ D2DSolidColorBrush::D2DSolidColorBrush(DirectGraphFactory* factory)
void D2DSolidColorBrush::SetColor(const Color& color) {
brush_->SetColor(Convert(color));
}
-} // namespace cru::platform::graph::win::direct
+} // namespace cru::platform::graphics::win::direct
diff --git a/src/win/graphics/direct/CMakeLists.txt b/src/win/graphics/direct/CMakeLists.txt
new file mode 100644
index 00000000..d4c96a65
--- /dev/null
+++ b/src/win/graphics/direct/CMakeLists.txt
@@ -0,0 +1,29 @@
+set(CRU_WIN_GRAPHICS_DIRECT_INCLUDE_DIR ${CRU_INCLUDE_DIR}/cru/win/graphics/direct)
+
+add_library(cru_win_graphics_direct STATIC
+ Brush.cpp
+ Font.cpp
+ Geometry.cpp
+ Factory.cpp
+ Painter.cpp
+ Resource.cpp
+ TextLayout.cpp
+ WindowPainter.cpp
+ WindowRenderTarget.cpp
+)
+target_sources(cru_win_graphics_direct PUBLIC
+ ${CRU_WIN_GRAPHICS_DIRECT_INCLUDE_DIR}/Brush.hpp
+ ${CRU_WIN_GRAPHICS_DIRECT_INCLUDE_DIR}/ComResource.hpp
+ ${CRU_WIN_GRAPHICS_DIRECT_INCLUDE_DIR}/ConvertUtil.hpp
+ ${CRU_WIN_GRAPHICS_DIRECT_INCLUDE_DIR}/Exception.hpp
+ ${CRU_WIN_GRAPHICS_DIRECT_INCLUDE_DIR}/Font.hpp
+ ${CRU_WIN_GRAPHICS_DIRECT_INCLUDE_DIR}/Geometry.hpp
+ ${CRU_WIN_GRAPHICS_DIRECT_INCLUDE_DIR}/Factory.hpp
+ ${CRU_WIN_GRAPHICS_DIRECT_INCLUDE_DIR}/Painter.hpp
+ ${CRU_WIN_GRAPHICS_DIRECT_INCLUDE_DIR}/Resource.hpp
+ ${CRU_WIN_GRAPHICS_DIRECT_INCLUDE_DIR}/TextLayout.hpp
+ ${CRU_WIN_GRAPHICS_DIRECT_INCLUDE_DIR}/WindowPainter.hpp
+ ${CRU_WIN_GRAPHICS_DIRECT_INCLUDE_DIR}/WindowRenderTarget.hpp
+)
+target_link_libraries(cru_win_graphics_direct PUBLIC D3D11 D2d1 DWrite)
+target_link_libraries(cru_win_graphics_direct PUBLIC cru_win_base cru_platform_graphics)
diff --git a/src/win/graph/direct/Factory.cpp b/src/win/graphics/direct/Factory.cpp
index 03e64e13..6694801f 100644
--- a/src/win/graph/direct/Factory.cpp
+++ b/src/win/graphics/direct/Factory.cpp
@@ -1,16 +1,16 @@
-#include "cru/win/graph/direct/Factory.hpp"
+#include "cru/win/graphics/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 "cru/win/graphics/direct/Brush.hpp"
+#include "cru/win/graphics/direct/Exception.hpp"
+#include "cru/win/graphics/direct/Font.hpp"
+#include "cru/win/graphics/direct/Geometry.hpp"
+#include "cru/win/graphics/direct/TextLayout.hpp"
#include <cstdlib>
#include <utility>
-namespace cru::platform::graph::win::direct {
+namespace cru::platform::graphics::win::direct {
namespace {
void InitializeCom() {
const auto hresult = ::CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED);
@@ -104,4 +104,4 @@ std::unique_ptr<ITextLayout> DirectGraphFactory::CreateTextLayout(
return std::make_unique<DWriteTextLayout>(this, std::move(font),
std::move(text));
}
-} // namespace cru::platform::graph::win::direct
+} // namespace cru::platform::graphics::win::direct
diff --git a/src/win/graph/direct/Font.cpp b/src/win/graphics/direct/Font.cpp
index 34de5b71..1d6a5c88 100644
--- a/src/win/graph/direct/Font.cpp
+++ b/src/win/graphics/direct/Font.cpp
@@ -1,12 +1,12 @@
-#include "cru/win/graph/direct/Font.hpp"
+#include "cru/win/graphics/direct/Font.hpp"
-#include "cru/win/graph/direct/Exception.hpp"
-#include "cru/win/graph/direct/Factory.hpp"
+#include "cru/win/graphics/direct/Exception.hpp"
+#include "cru/win/graphics/direct/Factory.hpp"
#include <array>
#include <utility>
-namespace cru::platform::graph::win::direct {
+namespace cru::platform::graphics::win::direct {
DWriteFont::DWriteFont(DirectGraphFactory* factory, std::u16string font_family,
float font_size)
: DirectGraphResource(factory), font_family_(std::move(font_family)) {
@@ -28,4 +28,4 @@ DWriteFont::DWriteFont(DirectGraphFactory* factory, std::u16string font_family,
}
float DWriteFont::GetFontSize() { return text_format_->GetFontSize(); }
-} // namespace cru::platform::graph::win::direct
+} // namespace cru::platform::graphics::win::direct
diff --git a/src/win/graph/direct/Geometry.cpp b/src/win/graphics/direct/Geometry.cpp
index e77b4749..8aa961b2 100644
--- a/src/win/graph/direct/Geometry.cpp
+++ b/src/win/graphics/direct/Geometry.cpp
@@ -1,10 +1,10 @@
-#include "cru/win/graph/direct/Geometry.hpp"
+#include "cru/win/graphics/direct/Geometry.hpp"
-#include "cru/win/graph/direct/ConvertUtil.hpp"
-#include "cru/win/graph/direct/Exception.hpp"
-#include "cru/win/graph/direct/Factory.hpp"
+#include "cru/win/graphics/direct/ConvertUtil.hpp"
+#include "cru/win/graphics/direct/Exception.hpp"
+#include "cru/win/graphics/direct/Factory.hpp"
-namespace cru::platform::graph::win::direct {
+namespace cru::platform::graphics::win::direct {
D2DGeometryBuilder::D2DGeometryBuilder(DirectGraphFactory* factory)
: DirectGraphResource(factory) {
ThrowIfFailed(factory->GetD2D1Factory()->CreatePathGeometry(&geometry_));
@@ -59,4 +59,4 @@ bool D2DGeometry::FillContains(const Point& point) {
Convert(point), D2D1::Matrix3x2F::Identity(), &result));
return result != 0;
}
-} // namespace cru::platform::graph::win::direct
+} // namespace cru::platform::graphics::win::direct
diff --git a/src/win/graph/direct/Painter.cpp b/src/win/graphics/direct/Painter.cpp
index 3ffb5208..d6999cfa 100644
--- a/src/win/graph/direct/Painter.cpp
+++ b/src/win/graphics/direct/Painter.cpp
@@ -1,15 +1,15 @@
-#include "cru/win/graph/direct/Painter.hpp"
+#include "cru/win/graphics/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 "cru/win/graphics/direct/Brush.hpp"
+#include "cru/win/graphics/direct/ConvertUtil.hpp"
+#include "cru/win/graphics/direct/Exception.hpp"
+#include "cru/win/graphics/direct/Geometry.hpp"
+#include "cru/win/graphics/direct/TextLayout.hpp"
#include <type_traits>
-namespace cru::platform::graph::win::direct {
+namespace cru::platform::graphics::win::direct {
D2DPainter::D2DPainter(ID2D1RenderTarget* render_target) {
Expects(render_target);
render_target_ = render_target;
@@ -32,6 +32,14 @@ void D2DPainter::Clear(const Color& color) {
render_target_->Clear(Convert(color));
}
+void D2DPainter::DrawLine(const Point& start, const Point& end, IBrush* brush,
+ float width) {
+ CheckValidation();
+ const auto b = CheckPlatform<ID2DBrush>(brush, GetPlatformId());
+ render_target_->DrawLine(Convert(start), Convert(end),
+ b->GetD2DBrushInterface(), width);
+}
+
void D2DPainter::StrokeRectangle(const Rect& rectangle, IBrush* brush,
float width) {
CheckValidation();
@@ -101,4 +109,4 @@ void D2DPainter::CheckValidation() {
"Can't do that on painter after end drawing.");
}
}
-} // namespace cru::platform::graph::win::direct
+} // namespace cru::platform::graphics::win::direct
diff --git a/src/win/graphics/direct/Resource.cpp b/src/win/graphics/direct/Resource.cpp
new file mode 100644
index 00000000..2b4a0772
--- /dev/null
+++ b/src/win/graphics/direct/Resource.cpp
@@ -0,0 +1,12 @@
+#include "cru/win/graphics/direct/Resource.hpp"
+
+#include "cru/win/graphics/direct/Factory.hpp"
+
+namespace cru::platform::graphics::win::direct {
+DirectGraphResource::DirectGraphResource(DirectGraphFactory* factory)
+ : factory_(factory) {
+ Expects(factory);
+}
+
+IGraphFactory* DirectGraphResource::GetGraphFactory() { return factory_; }
+} // namespace cru::platform::graphics::win::direct
diff --git a/src/win/graph/direct/TextLayout.cpp b/src/win/graphics/direct/TextLayout.cpp
index 0d4a6392..0b3c68ca 100644
--- a/src/win/graph/direct/TextLayout.cpp
+++ b/src/win/graphics/direct/TextLayout.cpp
@@ -1,14 +1,14 @@
-#include "cru/win/graph/direct/TextLayout.hpp"
+#include "cru/win/graphics/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/graphics/direct/Exception.hpp"
+#include "cru/win/graphics/direct/Factory.hpp"
+#include "cru/win/graphics/direct/Font.hpp"
#include <utility>
-namespace cru::platform::graph::win::direct {
+namespace cru::platform::graphics::win::direct {
DWriteTextLayout::DWriteTextLayout(DirectGraphFactory* factory,
std::shared_ptr<IFont> font,
std::u16string text)
@@ -58,10 +58,13 @@ void DWriteTextLayout::SetMaxHeight(float max_height) {
ThrowIfFailed(text_layout_->SetMaxHeight(max_height_));
}
-Rect DWriteTextLayout::GetTextBounds() {
+Rect DWriteTextLayout::GetTextBounds(bool includingTrailingSpace) {
DWRITE_TEXT_METRICS metrics;
ThrowIfFailed(text_layout_->GetMetrics(&metrics));
- return Rect{metrics.left, metrics.top, metrics.width, metrics.height};
+ return Rect{metrics.left, metrics.top,
+ includingTrailingSpace ? metrics.widthIncludingTrailingWhitespace
+ : metrics.width,
+ metrics.height};
}
std::vector<Rect> DWriteTextLayout::TextRangeRect(
@@ -121,4 +124,4 @@ TextHitTestResult DWriteTextLayout::HitTest(const Point& point) {
result.insideText = inside != 0;
return result;
}
-} // namespace cru::platform::graph::win::direct
+} // namespace cru::platform::graphics::win::direct
diff --git a/src/win/graphics/direct/WindowPainter.cpp b/src/win/graphics/direct/WindowPainter.cpp
new file mode 100644
index 00000000..c88667b6
--- /dev/null
+++ b/src/win/graphics/direct/WindowPainter.cpp
@@ -0,0 +1,20 @@
+#include "cru/win/graphics/direct/WindowPainter.hpp"
+
+#include "cru/win/graphics/direct/Exception.hpp"
+#include "cru/win/graphics/direct/Factory.hpp"
+#include "cru/win/graphics/direct/WindowRenderTarget.hpp"
+
+namespace cru::platform::graphics::win::direct {
+D2DWindowPainter::D2DWindowPainter(D2DWindowRenderTarget* render_target)
+ : D2DPainter(render_target->GetD2D1DeviceContext()),
+ render_target_(render_target) {
+ render_target_->GetD2D1DeviceContext()->BeginDraw();
+}
+
+D2DWindowPainter::~D2DWindowPainter() { EndDraw(); }
+
+void D2DWindowPainter::DoEndDraw() {
+ ThrowIfFailed(render_target_->GetD2D1DeviceContext()->EndDraw());
+ render_target_->Present();
+}
+} // namespace cru::platform::graphics::win::direct
diff --git a/src/win/native/WindowRenderTarget.cpp b/src/win/graphics/direct/WindowRenderTarget.cpp
index 4a114ebf..7479ae24 100644
--- a/src/win/native/WindowRenderTarget.cpp
+++ b/src/win/graphics/direct/WindowRenderTarget.cpp
@@ -1,19 +1,17 @@
-#include "cru/win/native/WindowRenderTarget.hpp"
+#include "cru/win/graphics/direct/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);
+#include "cru/win/graphics/direct/Exception.hpp"
+#include "cru/win/graphics/direct/Factory.hpp"
+namespace cru::platform::graphics::win::direct {
+D2DWindowRenderTarget::D2DWindowRenderTarget(
+ gsl::not_null<DirectGraphFactory*> factory, HWND hwnd)
+ : factory_(factory), hwnd_(hwnd) {
const auto d3d11_device = factory->GetD3D11Device();
const auto dxgi_factory = factory->GetDxgiFactory();
d2d1_device_context_ = factory->CreateD2D1DeviceContext();
+ d2d1_device_context_->SetUnitMode(D2D1_UNIT_MODE_DIPS);
// Allocate a descriptor.
DXGI_SWAP_CHAIN_DESC1 swap_chain_desc;
@@ -40,7 +38,11 @@ WindowRenderTarget::WindowRenderTarget(DirectGraphFactory* factory, HWND hwnd)
CreateTargetBitmap();
}
-void WindowRenderTarget::ResizeBuffer(const int width, const int height) {
+void D2DWindowRenderTarget::SetDpi(float x, float y) {
+ d2d1_device_context_->SetDpi(x, y);
+}
+
+void D2DWindowRenderTarget::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;
@@ -49,11 +51,11 @@ void WindowRenderTarget::ResizeBuffer(const int width, const int height) {
CreateTargetBitmap();
}
-void WindowRenderTarget::Present() {
+void D2DWindowRenderTarget::Present() {
ThrowIfFailed(dxgi_swap_chain_->Present(1, 0));
}
-void WindowRenderTarget::CreateTargetBitmap() {
+void D2DWindowRenderTarget::CreateTargetBitmap() {
Expects(target_bitmap_ == nullptr); // target bitmap must not exist.
// Direct2D needs the dxgi version of the backbuffer surface pointer.
@@ -61,12 +63,13 @@ void WindowRenderTarget::CreateTargetBitmap() {
ThrowIfFailed(
dxgi_swap_chain_->GetBuffer(0, IID_PPV_ARGS(&dxgi_back_buffer)));
- const auto dpi = GetDpi(); // TODO! DPI awareness.
+ float dpi_x, dpi_y;
+ d2d1_device_context_->GetDpi(&dpi_x, &dpi_y);
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);
+ dpi_x, dpi_y);
// Get a D2D surface from the DXGI back buffer to use as the D2D render
// target.
@@ -75,4 +78,4 @@ void WindowRenderTarget::CreateTargetBitmap() {
d2d1_device_context_->SetTarget(target_bitmap_.Get());
}
-} // namespace cru::platform::native::win
+} // namespace cru::platform::graphics::win::direct
diff --git a/src/win/gui/CMakeLists.txt b/src/win/gui/CMakeLists.txt
new file mode 100644
index 00000000..48bed00d
--- /dev/null
+++ b/src/win/gui/CMakeLists.txt
@@ -0,0 +1,31 @@
+set(CRU_WIN_GUI_INCLUDE_DIR ${CRU_INCLUDE_DIR}/cru/win/gui)
+
+add_library(cru_win_gui STATIC
+ TimerManager.hpp
+ WindowManager.hpp
+
+ Cursor.cpp
+ GodWindow.cpp
+ InputMethod.cpp
+ Keyboard.cpp
+ TimerManager.cpp
+ UiApplication.cpp
+ Window.cpp
+ WindowClass.cpp
+ WindowManager.cpp
+)
+target_sources(cru_win_gui PUBLIC
+ ${CRU_WIN_GUI_INCLUDE_DIR}/Cursor.hpp
+ ${CRU_WIN_GUI_INCLUDE_DIR}/Exception.hpp
+ ${CRU_WIN_GUI_INCLUDE_DIR}/Base.hpp
+ ${CRU_WIN_GUI_INCLUDE_DIR}/GodWindow.hpp
+ ${CRU_WIN_GUI_INCLUDE_DIR}/InputMethod.hpp
+ ${CRU_WIN_GUI_INCLUDE_DIR}/Keyboard.hpp
+ ${CRU_WIN_GUI_INCLUDE_DIR}/Resource.hpp
+ ${CRU_WIN_GUI_INCLUDE_DIR}/UiApplication.hpp
+ ${CRU_WIN_GUI_INCLUDE_DIR}/Window.hpp
+ ${CRU_WIN_GUI_INCLUDE_DIR}/WindowClass.hpp
+ ${CRU_WIN_GUI_INCLUDE_DIR}/WindowNativeMessageEventArgs.hpp
+)
+target_link_libraries(cru_win_gui PUBLIC imm32)
+target_link_libraries(cru_win_gui PUBLIC cru_win_graphics_direct cru_platform_gui)
diff --git a/src/win/native/Cursor.cpp b/src/win/gui/Cursor.cpp
index 429f6e7c..80e8a749 100644
--- a/src/win/native/Cursor.cpp
+++ b/src/win/gui/Cursor.cpp
@@ -1,11 +1,11 @@
-#include "cru/win/native/Cursor.hpp"
+#include "cru/win/gui/Cursor.hpp"
#include "cru/common/Logger.hpp"
-#include "cru/win/native/Exception.hpp"
+#include "cru/win/gui/Exception.hpp"
#include <stdexcept>
-namespace cru::platform::native::win {
+namespace cru::platform::gui::win {
WinCursor::WinCursor(HCURSOR handle, bool auto_destroy) {
handle_ = handle;
auto_destroy_ = auto_destroy;
@@ -35,7 +35,8 @@ WinCursor* LoadWinCursor(const wchar_t* name) {
WinCursorManager::WinCursorManager()
: sys_arrow_(LoadWinCursor(IDC_ARROW)),
- sys_hand_(LoadWinCursor(IDC_HAND)) {}
+ sys_hand_(LoadWinCursor(IDC_HAND)),
+ sys_ibeam_(LoadWinCursor(IDC_IBEAM)) {}
std::shared_ptr<WinCursor> WinCursorManager::GetSystemWinCursor(
SystemCursorType type) {
@@ -44,8 +45,10 @@ std::shared_ptr<WinCursor> WinCursorManager::GetSystemWinCursor(
return sys_arrow_;
case SystemCursorType::Hand:
return sys_hand_;
+ case SystemCursorType::IBeam:
+ return sys_ibeam_;
default:
throw std::runtime_error("Unknown system cursor value.");
}
}
-} // namespace cru::platform::native::win
+} // namespace cru::platform::gui::win
diff --git a/src/win/native/GodWindow.cpp b/src/win/gui/GodWindow.cpp
index b1e7275e..7bce83a3 100644
--- a/src/win/native/GodWindow.cpp
+++ b/src/win/gui/GodWindow.cpp
@@ -1,13 +1,11 @@
-#include "cru/win/native/GodWindow.hpp"
+#include "cru/win/gui/GodWindow.hpp"
-#include "GodWindowMessage.hpp"
-#include "Timer.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 "cru/win/gui/Exception.hpp"
+#include "cru/win/gui/UiApplication.hpp"
+#include "cru/win/gui/WindowClass.hpp"
-namespace cru::platform::native::win {
+namespace cru::platform::gui::win {
constexpr auto god_window_class_name = L"GodWindowClass";
LRESULT CALLBACK GodWndProc(HWND hWnd, UINT uMsg, WPARAM wParam,
@@ -16,14 +14,14 @@ LRESULT CALLBACK GodWndProc(HWND hWnd, UINT uMsg, WPARAM wParam,
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);
+ auto god_window = app->GetGodWindow();
+ if (god_window != nullptr) {
+ const auto handled = god_window->HandleGodWindowMessage(
+ hWnd, uMsg, wParam, lParam, &result);
+ if (handled) return result;
+ }
+ }
+ return DefWindowProcW(hWnd, uMsg, wParam, lParam);
}
GodWindow::GodWindow(WinUiApplication* application) {
@@ -51,32 +49,15 @@ GodWindow::~GodWindow() {
bool GodWindow::HandleGodWindowMessage(HWND hwnd, UINT msg, WPARAM w_param,
LPARAM l_param, LRESULT* result) {
- CRU_UNUSED(hwnd)
- CRU_UNUSED(l_param)
+ WindowNativeMessageEventArgs args(
+ WindowNativeMessage{hwnd, msg, w_param, l_param});
+ message_event_.Raise(args);
- switch (msg) {
- case invoke_later_message_id: {
- const auto p_action = reinterpret_cast<std::function<void()>*>(w_param);
- (*p_action)();
- delete p_action;
- *result = 0;
- return true;
- }
- case WM_TIMER: {
- const auto id = static_cast<UINT_PTR>(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;
+ if (args.IsHandled()) {
+ *result = args.GetResult();
+ return true;
}
+
return false;
}
-} // namespace cru::platform::native::win
+} // namespace cru::platform::gui::win
diff --git a/src/win/native/InputMethod.cpp b/src/win/gui/InputMethod.cpp
index 21681de2..cc237e88 100644
--- a/src/win/native/InputMethod.cpp
+++ b/src/win/gui/InputMethod.cpp
@@ -1,15 +1,15 @@
-#include "cru/win/native/InputMethod.hpp"
+#include "cru/win/gui/InputMethod.hpp"
-#include "DpiUtil.hpp"
#include "cru/common/Logger.hpp"
#include "cru/common/StringUtil.hpp"
#include "cru/platform/Check.hpp"
+#include "cru/platform/gui/DebugFlags.hpp"
#include "cru/win/Exception.hpp"
-#include "cru/win/native/Window.hpp"
+#include "cru/win/gui/Window.hpp"
#include <vector>
-namespace cru::platform::native::win {
+namespace cru::platform::gui::win {
AutoHIMC::AutoHIMC(HWND hwnd) : hwnd_(hwnd) {
Expects(hwnd);
handle_ = ::ImmGetContext(hwnd);
@@ -146,36 +146,26 @@ CompositionText GetCompositionInfo(HIMC imm_context) {
WinInputMethodContext::WinInputMethodContext(
gsl::not_null<WinNativeWindow*> window)
- : native_window_resolver_(window->GetResolver()) {
- event_revoker_guards_.push_back(
- EventRevokerGuard(window->NativeMessageEvent()->AddHandler(
- std::bind(&WinInputMethodContext::OnWindowNativeMessage, this,
- std::placeholders::_1))));
+ : native_window_(window) {
+ event_guard_ += 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();
-
+ const auto hwnd = native_window_->GetWindowHandle();
if (::ImmAssociateContextEx(hwnd, nullptr, IACE_DEFAULT) == FALSE) {
log::TagWarn(log_tag, u"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();
-
+ const auto hwnd = native_window_->GetWindowHandle();
AutoHIMC himc{hwnd};
- if (!::ImmNotifyIME(himc.Get(), NI_COMPOSITIONSTR, CPS_COMPLETE, 0)) {
- log::TagWarn(log_tag,
- u"Failed to complete composition before disable ime.");
- }
+ ::ImmNotifyIME(himc.Get(), NI_COMPOSITIONSTR, CPS_COMPLETE, 0);
if (::ImmAssociateContextEx(hwnd, nullptr, 0) == FALSE) {
log::TagWarn(log_tag, u"Failed to disable ime.");
@@ -183,42 +173,32 @@ void WinInputMethodContext::DisableIME() {
}
void WinInputMethodContext::CompleteComposition() {
- auto optional_himc = TryGetHIMC();
- if (!optional_himc.has_value()) return;
- auto himc = *std::move(optional_himc);
-
+ auto himc = GetHIMC();
if (!::ImmNotifyIME(himc.Get(), NI_COMPOSITIONSTR, CPS_COMPLETE, 0)) {
log::TagWarn(log_tag, u"Failed to complete composition.");
}
}
void WinInputMethodContext::CancelComposition() {
- auto optional_himc = TryGetHIMC();
- if (!optional_himc.has_value()) return;
- auto himc = *std::move(optional_himc);
-
+ auto himc = GetHIMC();
if (!::ImmNotifyIME(himc.Get(), NI_COMPOSITIONSTR, CPS_CANCEL, 0)) {
log::TagWarn(log_tag, u"Failed to complete composition.");
}
}
CompositionText WinInputMethodContext::GetCompositionText() {
- auto optional_himc = TryGetHIMC();
- if (!optional_himc.has_value()) return CompositionText{};
- auto himc = *std::move(optional_himc);
-
+ auto himc = GetHIMC();
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);
+ auto himc = GetHIMC();
::CANDIDATEFORM form;
- form.dwIndex = 1;
+ form.dwIndex = 0;
form.dwStyle = CFS_CANDIDATEPOS;
- form.ptCurrentPos = DipToPi(point);
+
+ form.ptCurrentPos = native_window_->DipToPixel(point);
if (!::ImmSetCandidateWindow(himc.Get(), &form))
log::TagDebug(log_tag,
@@ -246,7 +226,7 @@ void WinInputMethodContext::OnWindowNativeMessage(
const auto& message = args.GetWindowMessage();
switch (message.msg) {
case WM_CHAR: {
- const auto c = static_cast<char16_t>(message.w_param);
+ auto c = static_cast<char16_t>(message.w_param);
if (IsUtf16SurrogatePairCodeUnit(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
@@ -255,8 +235,12 @@ void WinInputMethodContext::OnWindowNativeMessage(
u"A WM_CHAR message for character from supplementary "
u"planes is ignored.");
} else {
- char16_t s[1] = {c};
- text_event_.Raise({s, 1});
+ if (c != '\b') { // ignore backspace
+ if (c == '\r') c = '\n'; // Change \r to \n
+
+ char16_t s[1] = {c};
+ text_event_.Raise({s, 1});
+ }
}
args.HandleWithResult(0);
break;
@@ -264,8 +248,10 @@ void WinInputMethodContext::OnWindowNativeMessage(
case WM_IME_COMPOSITION: {
composition_event_.Raise(nullptr);
auto composition_text = GetCompositionText();
- log::TagDebug(log_tag, u"WM_IME_COMPOSITION composition text:\n{}",
- composition_text);
+ if constexpr (DebugFlags::input_method) {
+ log::TagDebug(log_tag, u"WM_IME_COMPOSITION composition text:\n{}",
+ composition_text);
+ }
if (message.l_param & GCS_RESULTSTR) {
auto result_string = GetResultString();
text_event_.Raise(result_string);
@@ -273,10 +259,16 @@ void WinInputMethodContext::OnWindowNativeMessage(
break;
}
case WM_IME_STARTCOMPOSITION: {
+ if constexpr (DebugFlags::input_method) {
+ log::TagDebug(log_tag, u"WM_IME_STARTCOMPOSITION received.");
+ }
composition_start_event_.Raise(nullptr);
break;
}
case WM_IME_ENDCOMPOSITION: {
+ if constexpr (DebugFlags::input_method) {
+ log::TagDebug(log_tag, u"WM_IME_ENDCOMPOSITION received.");
+ }
composition_end_event_.Raise(nullptr);
break;
}
@@ -284,29 +276,13 @@ void WinInputMethodContext::OnWindowNativeMessage(
}
std::u16string WinInputMethodContext::GetResultString() {
- auto optional_himc = TryGetHIMC();
- if (!optional_himc.has_value()) return u"";
- auto himc = *std::move(optional_himc);
-
+ auto himc = GetHIMC();
auto result = win::GetResultString(himc.Get());
return result;
}
-std::optional<AutoHIMC> WinInputMethodContext::TryGetHIMC() {
- const auto native_window = Resolve(native_window_resolver_.get());
- if (native_window == nullptr) return std::nullopt;
- const auto hwnd = native_window->GetWindowHandle();
+AutoHIMC WinInputMethodContext::GetHIMC() {
+ const auto hwnd = native_window_->GetWindowHandle();
return AutoHIMC{hwnd};
}
-
-WinInputMethodManager::WinInputMethodManager(WinUiApplication*) {}
-
-WinInputMethodManager::~WinInputMethodManager() {}
-
-std::unique_ptr<IInputMethodContext> WinInputMethodManager::GetContext(
- INativeWindow* window) {
- Expects(window);
- const auto w = CheckPlatform<WinNativeWindow>(window, GetPlatformId());
- return std::make_unique<WinInputMethodContext>(w);
-}
-} // namespace cru::platform::native::win
+} // namespace cru::platform::gui::win
diff --git a/src/win/native/Keyboard.cpp b/src/win/gui/Keyboard.cpp
index 929ca737..b706b240 100644
--- a/src/win/native/Keyboard.cpp
+++ b/src/win/gui/Keyboard.cpp
@@ -1,6 +1,6 @@
-#include "cru/win/native/Keyboard.hpp"
+#include "cru/win/gui/Keyboard.hpp"
-namespace cru::platform::native::win {
+namespace cru::platform::gui::win {
KeyCode VirtualKeyToKeyCode(int virtual_key) {
if (virtual_key >= 0x30 && virtual_key <= 0x39) {
return KeyCode{static_cast<int>(KeyCode::N0) + (virtual_key - 0x30)};
@@ -71,4 +71,4 @@ KeyModifier RetrieveKeyMofifier() {
if (::GetKeyState(VK_MENU) < 0) result |= KeyModifiers::alt;
return result;
}
-} // namespace cru::platform::native::win
+} // namespace cru::platform::gui::win
diff --git a/src/win/gui/TimerManager.cpp b/src/win/gui/TimerManager.cpp
new file mode 100644
index 00000000..fc26b6c4
--- /dev/null
+++ b/src/win/gui/TimerManager.cpp
@@ -0,0 +1,100 @@
+#include "TimerManager.hpp"
+
+#include "cru/win/gui/Base.hpp"
+#include "cru/win/gui/Exception.hpp"
+#include "gsl/gsl_util"
+
+#include <functional>
+#include <type_traits>
+
+namespace cru::platform::gui::win {
+constexpr int kSetImmediateWindowMessageId = WM_USER + 2000;
+
+TimerManager::TimerManager(GodWindow* god_window) {
+ god_window_ = god_window;
+ event_guard_ += god_window->MessageEvent()->AddHandler(std::bind(
+ &TimerManager::HandleGodWindowMessage, this, std::placeholders::_1));
+}
+
+long long TimerManager::SetTimer(TimerType type, int period,
+ std::function<void()> action) {
+ auto id = next_id_++;
+ TimerInfo timer_info{id, type, type == TimerType::Immediate ? 0 : period,
+ std::move(action)};
+ if (type == TimerType::Immediate) {
+ if (!::PostMessageW(god_window_->GetHandle(), kSetImmediateWindowMessageId,
+ gsl::narrow<UINT_PTR>(id), 0)) {
+ throw Win32Error(
+ ::GetLastError(),
+ "Failed to post window message to god window for set immediate.");
+ }
+ } else {
+ CreateNativeTimer(&timer_info);
+ }
+
+ info_map_.emplace(id, std::move(timer_info));
+ return id;
+}
+
+void TimerManager::CancelTimer(long long id) {
+ if (id <= 0) return;
+ auto find_result = this->info_map_.find(id);
+ if (find_result != info_map_.cend()) {
+ auto& info = find_result->second;
+ KillNativeTimer(&info);
+ this->info_map_.erase(find_result);
+ }
+}
+
+void TimerManager::CreateNativeTimer(TimerInfo* info) {
+ info->native_timer_id = gsl::narrow<UINT_PTR>(info->id);
+ ::SetTimer(god_window_->GetHandle(), info->native_timer_id, info->period,
+ nullptr);
+}
+
+void TimerManager::KillNativeTimer(TimerInfo* info) {
+ if (info->native_timer_id == 0) return;
+ ::KillTimer(god_window_->GetHandle(), info->native_timer_id);
+ info->native_timer_id = 0;
+}
+
+void TimerManager::HandleGodWindowMessage(WindowNativeMessageEventArgs& args) {
+ const auto& message = args.GetWindowMessage();
+
+ switch (message.msg) {
+ case kSetImmediateWindowMessageId: {
+ auto find_result =
+ this->info_map_.find(static_cast<long long>(message.w_param));
+ if (find_result != info_map_.cend()) {
+ auto& info = find_result->second;
+ info.action();
+ info_map_.erase(find_result);
+ }
+ args.SetResult(0);
+ args.SetHandled(true);
+ return;
+ }
+ case WM_TIMER: {
+ auto find_result =
+ this->info_map_.find(static_cast<long long>(message.w_param));
+ if (find_result != info_map_.cend()) {
+ auto& info = find_result->second;
+ if (info.type == TimerType::Interval) {
+ info.action();
+ args.SetResult(0);
+ args.SetHandled(true);
+ } else if (info.type == TimerType::Timeout) {
+ info.action();
+ KillNativeTimer(&info);
+ info_map_.erase(find_result);
+ args.SetResult(0);
+ args.SetHandled(true);
+ }
+ }
+ return;
+ }
+ default:
+ return;
+ }
+}
+} // namespace cru::platform::gui::win
diff --git a/src/win/gui/TimerManager.hpp b/src/win/gui/TimerManager.hpp
new file mode 100644
index 00000000..a8db1075
--- /dev/null
+++ b/src/win/gui/TimerManager.hpp
@@ -0,0 +1,61 @@
+#pragma once
+#include "cru/common/Event.hpp"
+#include "cru/win/WinPreConfig.hpp"
+
+#include "cru/common/Base.hpp"
+#include "cru/win/gui/GodWindow.hpp"
+#include "cru/win/gui/WindowNativeMessageEventArgs.hpp"
+
+#include <chrono>
+#include <functional>
+#include <optional>
+#include <unordered_map>
+
+namespace cru::platform::gui::win {
+enum class TimerType { Immediate, Timeout, Interval };
+
+struct TimerInfo {
+ TimerInfo(long long id, TimerType type, int period,
+ std::function<void()> action, UINT_PTR native_timer_id = 0)
+ : id(id),
+ type(type),
+ period(period),
+ action(std::move(action)),
+ native_timer_id(native_timer_id) {}
+
+ long long id;
+ TimerType type;
+ int period; // in milliseconds
+ std::function<void()> action;
+ UINT_PTR native_timer_id;
+};
+
+class TimerManager : public Object {
+ public:
+ TimerManager(GodWindow* god_window);
+
+ CRU_DELETE_COPY(TimerManager)
+ CRU_DELETE_MOVE(TimerManager)
+
+ ~TimerManager() override = default;
+
+ // Period is in milliseconds. When type is immediate, it is not checked and
+ // used.
+ long long SetTimer(TimerType type, int period, std::function<void()> action);
+ void CancelTimer(long long id);
+
+ private:
+ void HandleGodWindowMessage(WindowNativeMessageEventArgs& args);
+
+ void CreateNativeTimer(TimerInfo* info);
+ void KillNativeTimer(TimerInfo* info);
+
+ private:
+ GodWindow* god_window_;
+
+ EventRevokerListGuard event_guard_;
+
+ long long next_id_ = 1;
+ std::unordered_map<long long, TimerInfo> info_map_;
+};
+} // namespace cru::platform::gui::win
diff --git a/src/win/native/UiApplication.cpp b/src/win/gui/UiApplication.cpp
index dbf52e05..f4541dd0 100644
--- a/src/win/native/UiApplication.cpp
+++ b/src/win/gui/UiApplication.cpp
@@ -1,25 +1,25 @@
-#include "cru/win/native/UiApplication.hpp"
+#include "cru/win/gui/UiApplication.hpp"
#include "../DebugLogger.hpp"
+#include "../StdOutLogger.hpp"
+#include "TimerManager.hpp"
+#include "WindowManager.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 {
+#include "cru/win/graphics/direct/Factory.hpp"
+#include "cru/win/gui/Cursor.hpp"
+#include "cru/win/gui/Exception.hpp"
+#include "cru/win/gui/GodWindow.hpp"
+#include "cru/win/gui/InputMethod.hpp"
+#include "cru/win/gui/Window.hpp"
+
+namespace cru::platform::gui {
std::unique_ptr<IUiApplication> CreateUiApplication() {
return std::make_unique<win::WinUiApplication>();
}
-} // namespace cru::platform::native
+} // namespace cru::platform::gui
-namespace cru::platform::native::win {
+namespace cru::platform::gui::win {
WinUiApplication* WinUiApplication::instance = nullptr;
WinUiApplication::WinUiApplication() {
@@ -29,17 +29,20 @@ WinUiApplication::WinUiApplication() {
if (!instance_handle_)
throw Win32Error("Failed to get module(instance) handle.");
+ ::SetThreadDpiAwarenessContext(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE);
+
log::Logger::GetInstance()->AddSource(
std::make_unique<::cru::platform::win::WinDebugLoggerSource>());
+ log::Logger::GetInstance()->AddSource(
+ std::make_unique<::cru::platform::win::WinStdOutLoggerSource>());
- graph_factory_ =
- std::make_unique<cru::platform::graph::win::direct::DirectGraphFactory>();
+ graph_factory_ = std::make_unique<
+ cru::platform::graphics::win::direct::DirectGraphFactory>();
god_window_ = std::make_unique<GodWindow>(this);
timer_manager_ = std::make_unique<TimerManager>(god_window_.get());
window_manager_ = std::make_unique<WindowManager>(this);
cursor_manager_ = std::make_unique<WinCursorManager>();
- input_method_manager_ = std::make_unique<WinInputMethodManager>(this);
}
WinUiApplication::~WinUiApplication() { instance = nullptr; }
@@ -64,30 +67,27 @@ void WinUiApplication::AddOnQuitHandler(std::function<void()> handler) {
quit_handlers_.push_back(std::move(handler));
}
-void WinUiApplication::InvokeLater(std::function<void()> action) {
- // copy the action to a safe place
- auto p_action_copy = new std::function<void()>(std::move(action));
-
- if (::PostMessageW(GetGodWindow()->GetHandle(), invoke_later_message_id,
- reinterpret_cast<WPARAM>(p_action_copy), 0) == 0)
- throw Win32Error(::GetLastError(), "InvokeLater failed to post message.");
+long long WinUiApplication::SetImmediate(std::function<void()> action) {
+ return this->timer_manager_->SetTimer(TimerType::Immediate, 0,
+ std::move(action));
}
long long WinUiApplication::SetTimeout(std::chrono::milliseconds milliseconds,
std::function<void()> action) {
- return gsl::narrow<long long>(timer_manager_->CreateTimer(
- static_cast<UINT>(milliseconds.count()), false, std::move(action)));
+ return this->timer_manager_->SetTimer(TimerType::Timeout,
+ gsl::narrow<int>(milliseconds.count()),
+ std::move(action));
}
long long WinUiApplication::SetInterval(std::chrono::milliseconds milliseconds,
std::function<void()> action) {
- return gsl::narrow<long long>(timer_manager_->CreateTimer(
- static_cast<UINT>(milliseconds.count()), true, std::move(action)));
+ return this->timer_manager_->SetTimer(TimerType::Interval,
+ gsl::narrow<int>(milliseconds.count()),
+ std::move(action));
}
void WinUiApplication::CancelTimer(long long id) {
- if (id < 0) return;
- timer_manager_->KillTimer(static_cast<UINT_PTR>(id));
+ timer_manager_->CancelTimer(id);
}
std::vector<INativeWindow*> WinUiApplication::GetAllWindow() {
@@ -99,26 +99,24 @@ std::vector<INativeWindow*> WinUiApplication::GetAllWindow() {
return result;
}
-std::shared_ptr<INativeWindowResolver> WinUiApplication::CreateWindow(
- INativeWindow* parent) {
+INativeWindow* WinUiApplication::CreateWindow(INativeWindow* parent,
+ CreateWindowFlag flag) {
WinNativeWindow* p = nullptr;
if (parent != nullptr) {
p = CheckPlatform<WinNativeWindow>(parent, GetPlatformId());
}
- return (new WinNativeWindow(this, window_manager_->GetGeneralWindowClass(),
- WS_OVERLAPPEDWINDOW, p))
- ->GetResolver();
+ return new WinNativeWindow(this, window_manager_->GetGeneralWindowClass(),
+ flag & CreateWindowFlags::NoCaptionAndBorder
+ ? WS_POPUP
+ : WS_OVERLAPPEDWINDOW,
+ p);
}
-cru::platform::graph::IGraphFactory* WinUiApplication::GetGraphFactory() {
+cru::platform::graphics::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
+} // namespace cru::platform::gui::win
diff --git a/src/win/native/Window.cpp b/src/win/gui/Window.cpp
index 81642451..efd3bfcc 100644
--- a/src/win/native/Window.cpp
+++ b/src/win/gui/Window.cpp
@@ -1,27 +1,27 @@
-#include "cru/win/native/Window.hpp"
+#include "cru/win/gui/Window.hpp"
-#include "DpiUtil.hpp"
-#include "WindowD2DPainter.hpp"
#include "WindowManager.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/platform/gui/Base.hpp"
+#include "cru/platform/gui/DebugFlags.hpp"
+#include "cru/win/graphics/direct/WindowPainter.hpp"
+#include "cru/win/gui/Cursor.hpp"
+#include "cru/win/gui/Exception.hpp"
+#include "cru/win/gui/InputMethod.hpp"
+#include "cru/win/gui/Keyboard.hpp"
+#include "cru/win/gui/UiApplication.hpp"
+#include "cru/win/gui/WindowClass.hpp"
#include <imm.h>
#include <windowsx.h>
+#include <memory>
-namespace cru::platform::native::win {
+namespace cru::platform::gui::win {
WinNativeWindow::WinNativeWindow(WinUiApplication* application,
WindowClass* window_class, DWORD window_style,
WinNativeWindow* parent)
- : application_(application),
- resolver_(std::make_shared<WinNativeWindowResolver>(this)),
- parent_window_(parent) {
+ : application_(application), parent_window_(parent) {
Expects(application); // application can't be null.
if (parent != nullptr) {
@@ -39,13 +39,24 @@ WinNativeWindow::WinNativeWindow(WinUiApplication* application,
if (hwnd_ == nullptr)
throw Win32Error(::GetLastError(), "Failed to create window.");
+ auto dpi = ::GetDpiForWindow(hwnd_);
+ if (dpi == 0)
+ throw Win32Error(::GetLastError(), "Failed to get dpi of window.");
+ dpi_ = static_cast<float>(dpi);
+ log::Debug(u"Dpi of window is {}.", dpi_);
+
window_manager->RegisterWindow(hwnd_, this);
SetCursor(application->GetCursorManager()->GetSystemCursor(
- cru::platform::native::SystemCursorType::Arrow));
+ cru::platform::gui::SystemCursorType::Arrow));
+
+ window_render_target_ =
+ std::make_unique<graphics::win::direct::D2DWindowRenderTarget>(
+ application->GetDirectFactory(), hwnd_);
+ window_render_target_->SetDpi(dpi_, dpi_);
- window_render_target_ = std::make_unique<WindowRenderTarget>(
- application->GetDirectFactory(), hwnd_);
+ input_method_context_ = std::make_unique<WinInputMethodContext>(this);
+ input_method_context_->DisableIME();
}
WinNativeWindow::~WinNativeWindow() {
@@ -53,7 +64,6 @@ WinNativeWindow::~WinNativeWindow() {
sync_flag_ = true;
Close();
}
- resolver_->Reset();
}
void WinNativeWindow::Close() { ::DestroyWindow(hwnd_); }
@@ -65,7 +75,7 @@ void WinNativeWindow::SetVisible(bool is_visible) {
}
Size WinNativeWindow::GetClientSize() {
const auto pixel_rect = GetClientRectPixel();
- return Size(PixelToDipX(pixel_rect.right), PixelToDipY(pixel_rect.bottom));
+ return Size(PixelToDip(pixel_rect.right), PixelToDip(pixel_rect.bottom));
}
void WinNativeWindow::SetClientSize(const Size& size) {
@@ -77,8 +87,8 @@ void WinNativeWindow::SetClientSize(const Size& size) {
RECT rect;
rect.left = 0;
rect.top = 0;
- rect.right = DipToPixelX(size.width);
- rect.bottom = DipToPixelY(size.height);
+ rect.right = DipToPixel(size.width);
+ rect.bottom = DipToPixel(size.height);
if (!AdjustWindowRectEx(&rect, window_style, FALSE, window_ex_style))
throw Win32Error(::GetLastError(), "Failed to invoke AdjustWindowRectEx.");
@@ -92,14 +102,14 @@ Rect WinNativeWindow::GetWindowRect() {
if (!::GetWindowRect(hwnd_, &rect))
throw Win32Error(::GetLastError(), "Failed to invoke GetWindowRect.");
- return Rect::FromVertices(PixelToDipX(rect.left), PixelToDipY(rect.top),
- PixelToDipX(rect.right), PixelToDipY(rect.bottom));
+ return Rect::FromVertices(PixelToDip(rect.left), PixelToDip(rect.top),
+ PixelToDip(rect.right), PixelToDip(rect.bottom));
}
void WinNativeWindow::SetWindowRect(const Rect& rect) {
- if (!SetWindowPos(hwnd_, nullptr, DipToPixelX(rect.left),
- DipToPixelY(rect.top), DipToPixelX(rect.GetRight()),
- DipToPixelY(rect.GetBottom()), SWP_NOZORDER))
+ if (!SetWindowPos(hwnd_, nullptr, DipToPixel(rect.left), DipToPixel(rect.top),
+ DipToPixel(rect.GetRight()), DipToPixel(rect.GetBottom()),
+ SWP_NOZORDER))
throw Win32Error(::GetLastError(), "Failed to invoke SetWindowPos.");
}
@@ -109,7 +119,7 @@ Point WinNativeWindow::GetMousePosition() {
throw Win32Error(::GetLastError(), "Failed to get cursor position.");
if (!::ScreenToClient(hwnd_, &p))
throw Win32Error(::GetLastError(), "Failed to call ScreenToClient.");
- return PiToDip(p);
+ return PixelToDip(p);
}
bool WinNativeWindow::CaptureMouse() {
@@ -123,15 +133,18 @@ bool WinNativeWindow::ReleaseMouse() {
}
void WinNativeWindow::RequestRepaint() {
- log::TagDebug(log_tag, u"A repaint is requested.");
+ if constexpr (DebugFlags::paint) {
+ log::TagDebug(log_tag, u"A repaint is requested.");
+ }
if (!::InvalidateRect(hwnd_, nullptr, FALSE))
throw Win32Error(::GetLastError(), "Failed to invalidate window.");
if (!::UpdateWindow(hwnd_))
throw Win32Error(::GetLastError(), "Failed to update window.");
}
-std::unique_ptr<graph::IPainter> WinNativeWindow::BeginPaint() {
- return std::make_unique<WindowD2DPainter>(window_render_target_.get());
+std::unique_ptr<graphics::IPainter> WinNativeWindow::BeginPaint() {
+ return std::make_unique<graphics::win::direct::D2DWindowPainter>(
+ window_render_target_.get());
}
void WinNativeWindow::SetCursor(std::shared_ptr<ICursor> cursor) {
@@ -190,6 +203,10 @@ void WinNativeWindow::SetCursor(std::shared_ptr<ICursor> cursor) {
}
}
+IInputMethodContext* WinNativeWindow::GetInputMethodContext() {
+ return static_cast<IInputMethodContext*>(input_method_context_.get());
+}
+
bool WinNativeWindow::HandleNativeWindowMessage(HWND hwnd, UINT msg,
WPARAM w_param, LPARAM l_param,
LRESULT* result) {
@@ -229,7 +246,7 @@ bool WinNativeWindow::HandleNativeWindowMessage(HWND hwnd, UINT msg,
POINT point;
point.x = GET_X_LPARAM(l_param);
point.y = GET_Y_LPARAM(l_param);
- OnMouseDownInternal(platform::native::mouse_buttons::left, point);
+ OnMouseDownInternal(platform::gui::mouse_buttons::left, point);
*result = 0;
return true;
}
@@ -237,7 +254,7 @@ bool WinNativeWindow::HandleNativeWindowMessage(HWND hwnd, UINT msg,
POINT point;
point.x = GET_X_LPARAM(l_param);
point.y = GET_Y_LPARAM(l_param);
- OnMouseUpInternal(platform::native::mouse_buttons::left, point);
+ OnMouseUpInternal(platform::gui::mouse_buttons::left, point);
*result = 0;
return true;
}
@@ -245,7 +262,7 @@ bool WinNativeWindow::HandleNativeWindowMessage(HWND hwnd, UINT msg,
POINT point;
point.x = GET_X_LPARAM(l_param);
point.y = GET_Y_LPARAM(l_param);
- OnMouseDownInternal(platform::native::mouse_buttons::right, point);
+ OnMouseDownInternal(platform::gui::mouse_buttons::right, point);
*result = 0;
return true;
}
@@ -253,7 +270,7 @@ bool WinNativeWindow::HandleNativeWindowMessage(HWND hwnd, UINT msg,
POINT point;
point.x = GET_X_LPARAM(l_param);
point.y = GET_Y_LPARAM(l_param);
- OnMouseUpInternal(platform::native::mouse_buttons::right, point);
+ OnMouseUpInternal(platform::gui::mouse_buttons::right, point);
*result = 0;
return true;
}
@@ -261,7 +278,7 @@ bool WinNativeWindow::HandleNativeWindowMessage(HWND hwnd, UINT msg,
POINT point;
point.x = GET_X_LPARAM(l_param);
point.y = GET_Y_LPARAM(l_param);
- OnMouseDownInternal(platform::native::mouse_buttons::middle, point);
+ OnMouseDownInternal(platform::gui::mouse_buttons::middle, point);
*result = 0;
return true;
}
@@ -269,7 +286,7 @@ bool WinNativeWindow::HandleNativeWindowMessage(HWND hwnd, UINT msg,
POINT point;
point.x = GET_X_LPARAM(l_param);
point.y = GET_Y_LPARAM(l_param);
- OnMouseUpInternal(platform::native::mouse_buttons::middle, point);
+ OnMouseUpInternal(platform::gui::mouse_buttons::middle, point);
*result = 0;
return true;
}
@@ -328,6 +345,15 @@ bool WinNativeWindow::HandleNativeWindowMessage(HWND hwnd, UINT msg,
case WM_IME_COMPOSITION:
*result = 0;
return true;
+ case WM_DPICHANGED: {
+ dpi_ = static_cast<float>(LOWORD(w_param));
+ const RECT* suggest_rect = reinterpret_cast<const RECT*>(l_param);
+ window_render_target_->SetDpi(dpi_, dpi_);
+ SetWindowPos(hwnd_, NULL, suggest_rect->left, suggest_rect->top,
+ suggest_rect->right - suggest_rect->left,
+ suggest_rect->bottom - suggest_rect->top,
+ SWP_NOZORDER | SWP_NOACTIVATE);
+ }
default:
return false;
}
@@ -341,9 +367,9 @@ RECT WinNativeWindow::GetClientRectPixel() {
}
void WinNativeWindow::OnDestroyInternal() {
+ destroy_event_.Raise(nullptr);
application_->GetWindowManager()->UnregisterWindow(hwnd_);
hwnd_ = nullptr;
- destroy_event_.Raise(nullptr);
if (!sync_flag_) {
sync_flag_ = true;
delete this;
@@ -353,14 +379,16 @@ void WinNativeWindow::OnDestroyInternal() {
void WinNativeWindow::OnPaintInternal() {
paint_event_.Raise(nullptr);
ValidateRect(hwnd_, nullptr);
- log::TagDebug(log_tag, u"A repaint is finished.");
+ if constexpr (DebugFlags::paint) {
+ log::TagDebug(log_tag, u"A repaint is finished.");
+ }
}
void WinNativeWindow::OnResizeInternal(const int new_width,
const int new_height) {
if (!(new_width == 0 && new_height == 0)) {
window_render_target_->ResizeBuffer(new_width, new_height);
- resize_event_.Raise(Size{PixelToDipX(new_width), PixelToDipY(new_height)});
+ resize_event_.Raise(Size{PixelToDip(new_width), PixelToDip(new_height)});
}
}
@@ -389,7 +417,7 @@ void WinNativeWindow::OnMouseMoveInternal(const POINT point) {
mouse_enter_leave_event_.Raise(MouseEnterLeaveType::Enter);
}
- mouse_move_event_.Raise(PiToDip(point));
+ mouse_move_event_.Raise(PixelToDip(point));
}
void WinNativeWindow::OnMouseLeaveInternal() {
@@ -397,15 +425,15 @@ void WinNativeWindow::OnMouseLeaveInternal() {
mouse_enter_leave_event_.Raise(MouseEnterLeaveType::Leave);
}
-void WinNativeWindow::OnMouseDownInternal(platform::native::MouseButton button,
+void WinNativeWindow::OnMouseDownInternal(platform::gui::MouseButton button,
POINT point) {
- const auto dip_point = PiToDip(point);
+ const auto dip_point = PixelToDip(point);
mouse_down_event_.Raise({button, dip_point, RetrieveKeyMofifier()});
}
-void WinNativeWindow::OnMouseUpInternal(platform::native::MouseButton button,
+void WinNativeWindow::OnMouseUpInternal(platform::gui::MouseButton button,
POINT point) {
- const auto dip_point = PiToDip(point);
+ const auto dip_point = PixelToDip(point);
mouse_up_event_.Raise({button, dip_point, RetrieveKeyMofifier()});
}
@@ -427,16 +455,4 @@ void WinNativeWindow::OnKeyUpInternal(int virtual_code) {
void WinNativeWindow::OnActivatedInternal() {}
void WinNativeWindow::OnDeactivatedInternal() {}
-
-void WinNativeWindowResolver::Reset() {
- Expects(window_); // already reset, can't reset again
- window_ = nullptr;
-}
-
-WinNativeWindow* Resolve(gsl::not_null<INativeWindowResolver*> resolver) {
- const auto window = resolver->Resolve();
- return window == nullptr ? nullptr
- : CheckPlatform<WinNativeWindow>(
- window, WinNativeResource::k_platform_id);
-} // namespace cru::platform::native::win
-} // namespace cru::platform::native::win
+} // namespace cru::platform::gui::win
diff --git a/src/win/native/WindowClass.cpp b/src/win/gui/WindowClass.cpp
index 2e74606e..a033d091 100644
--- a/src/win/native/WindowClass.cpp
+++ b/src/win/gui/WindowClass.cpp
@@ -1,8 +1,8 @@
-#include "cru/win/native/WindowClass.hpp"
+#include "cru/win/gui/WindowClass.hpp"
-#include "cru/win/native/Exception.hpp"
+#include "cru/win/gui/Exception.hpp"
-namespace cru::platform::native::win {
+namespace cru::platform::gui::win {
WindowClass::WindowClass(std::wstring name, WNDPROC window_proc,
HINSTANCE h_instance)
: name_(std::move(name)) {
@@ -25,4 +25,4 @@ WindowClass::WindowClass(std::wstring name, WNDPROC window_proc,
if (atom_ == 0)
throw Win32Error(::GetLastError(), "Failed to create window class.");
}
-} // namespace cru::platform::native::win
+} // namespace cru::platform::gui::win
diff --git a/src/win/native/WindowManager.cpp b/src/win/gui/WindowManager.cpp
index 56cc8981..4e84e967 100644
--- a/src/win/native/WindowManager.cpp
+++ b/src/win/gui/WindowManager.cpp
@@ -1,10 +1,10 @@
#include "WindowManager.hpp"
-#include "cru/win/native/UiApplication.hpp"
-#include "cru/win/native/Window.hpp"
-#include "cru/win/native/WindowClass.hpp"
+#include "cru/win/gui/UiApplication.hpp"
+#include "cru/win/gui/Window.hpp"
+#include "cru/win/gui/WindowClass.hpp"
-namespace cru::platform::native::win {
+namespace cru::platform::gui::win {
LRESULT __stdcall GeneralWndProc(HWND hWnd, UINT Msg, WPARAM wParam,
LPARAM lParam) {
auto window =
@@ -53,4 +53,4 @@ std::vector<WinNativeWindow*> WindowManager::GetAllWindows() const {
for (const auto& [key, value] : window_map_) windows.push_back(value);
return windows;
}
-} // namespace cru::platform::native::win
+} // namespace cru::platform::gui::win
diff --git a/src/win/native/WindowManager.hpp b/src/win/gui/WindowManager.hpp
index 3f6387f7..3b037f89 100644
--- a/src/win/native/WindowManager.hpp
+++ b/src/win/gui/WindowManager.hpp
@@ -7,7 +7,7 @@
#include <memory>
#include <vector>
-namespace cru::platform::native::win {
+namespace cru::platform::gui::win {
class WinUiApplication;
class WinNativeWindow;
class WindowClass;
@@ -48,4 +48,4 @@ class WindowManager : public Object {
std::unique_ptr<WindowClass> general_window_class_;
std::map<HWND, WinNativeWindow*> window_map_;
};
-} // namespace cru::platform::native::win
+} // namespace cru::platform::gui::win
diff --git a/src/win/native/CMakeLists.txt b/src/win/native/CMakeLists.txt
deleted file mode 100644
index f1b167d2..00000000
--- a/src/win/native/CMakeLists.txt
+++ /dev/null
@@ -1,37 +0,0 @@
-set(CRU_WIN_NATIVE_INCLUDE_DIR ${CRU_INCLUDE_DIR}/cru/win/native)
-
-add_library(cru_win_native STATIC
- DpiUtil.hpp
- GodWindowMessage.hpp
- Timer.hpp
- WindowD2DPainter.hpp
- WindowManager.hpp
-
- 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}/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
deleted file mode 100644
index 16ffda25..00000000
--- a/src/win/native/DpiUtil.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<int>(dip * dpi / 96.0f);
-}
-
-inline int DipToPixelX(const float dip_x) {
- return DipToPixelInternal(dip_x, GetDpi().x);
-}
-
-inline int DipToPixelY(const float dip_y) {
- return DipToPixelInternal(dip_y, GetDpi().y);
-}
-
-inline float DipToPixelInternal(const int pixel, const float dpi) {
- return static_cast<float>(pixel) * 96.0f / dpi;
-}
-
-inline float PixelToDipX(const int pixel_x) {
- return DipToPixelInternal(pixel_x, GetDpi().x);
-}
-
-inline float PixelToDipY(const int pixel_y) {
- return DipToPixelInternal(pixel_y, GetDpi().y);
-}
-
-inline 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/GodWindowMessage.hpp b/src/win/native/GodWindowMessage.hpp
deleted file mode 100644
index 9063cb4d..00000000
--- a/src/win/native/GodWindowMessage.hpp
+++ /dev/null
@@ -1,6 +0,0 @@
-#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/Timer.cpp b/src/win/native/Timer.cpp
deleted file mode 100644
index 662067fb..00000000
--- a/src/win/native/Timer.cpp
+++ /dev/null
@@ -1,28 +0,0 @@
-#include "Timer.hpp"
-
-namespace cru::platform::native::win {
-TimerManager::TimerManager(GodWindow* god_window) { god_window_ = god_window; }
-
-UINT_PTR TimerManager::CreateTimer(const UINT milliseconds, const bool loop,
- TimerAction action) {
- const auto id = current_count_++;
- ::SetTimer(god_window_->GetHandle(), id, milliseconds, nullptr);
- map_.emplace(id, std::make_pair(loop, std::move(action)));
- return id;
-}
-
-void TimerManager::KillTimer(const UINT_PTR id) {
- const auto find_result = map_.find(id);
- if (find_result != map_.cend()) {
- ::KillTimer(god_window_->GetHandle(), id);
- map_.erase(find_result);
- }
-}
-
-std::optional<std::pair<bool, TimerAction>> TimerManager::GetAction(
- const UINT_PTR id) {
- const auto find_result = map_.find(id);
- if (find_result == map_.cend()) return std::nullopt;
- return find_result->second;
-}
-} // namespace cru::platform::native::win
diff --git a/src/win/native/Timer.hpp b/src/win/native/Timer.hpp
deleted file mode 100644
index 95f186a1..00000000
--- a/src/win/native/Timer.hpp
+++ /dev/null
@@ -1,34 +0,0 @@
-#pragma once
-#include "cru/win/WinPreConfig.hpp"
-
-#include "cru/common/Base.hpp"
-#include "cru/win/native/GodWindow.hpp"
-
-#include <chrono>
-#include <functional>
-#include <map>
-#include <optional>
-
-namespace cru::platform::native::win {
-using TimerAction = std::function<void()>;
-
-class TimerManager : public Object {
- public:
- TimerManager(GodWindow* god_window);
-
- CRU_DELETE_COPY(TimerManager)
- CRU_DELETE_MOVE(TimerManager)
-
- ~TimerManager() override = default;
-
- UINT_PTR CreateTimer(UINT milliseconds, bool loop, TimerAction action);
- void KillTimer(UINT_PTR id);
- std::optional<std::pair<bool, TimerAction>> GetAction(UINT_PTR id);
-
- private:
- GodWindow* god_window_;
-
- std::map<UINT_PTR, std::pair<bool, TimerAction>> map_{};
- UINT_PTR current_count_ = 0;
-};
-} // namespace cru::platform::native::win
diff --git a/src/win/native/WindowD2DPainter.cpp b/src/win/native/WindowD2DPainter.cpp
deleted file mode 100644
index 7a97480b..00000000
--- a/src/win/native/WindowD2DPainter.cpp
+++ /dev/null
@@ -1,22 +0,0 @@
-#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
deleted file mode 100644
index a638b77a..00000000
--- a/src/win/native/WindowD2DPainter.hpp
+++ /dev/null
@@ -1,21 +0,0 @@
-#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/test/CMakeLists.txt b/test/CMakeLists.txt
index c534b909..3b9567cd 100644
--- a/test/CMakeLists.txt
+++ b/test/CMakeLists.txt
@@ -2,7 +2,12 @@ find_package(GTest CONFIG REQUIRED)
include(GoogleTest)
-add_subdirectory(common)
-
add_library(cru_test_base INTERFACE)
target_link_libraries(cru_test_base INTERFACE GTest::gtest GTest::gtest_main)
+
+add_subdirectory(common)
+add_subdirectory(platform)
+
+if(WIN32)
+ add_subdirectory(win)
+endif()
diff --git a/test/common/HandlerRegistryTest.cpp b/test/common/HandlerRegistryTest.cpp
new file mode 100644
index 00000000..d1792c7c
--- /dev/null
+++ b/test/common/HandlerRegistryTest.cpp
@@ -0,0 +1,36 @@
+#include "cru/common/HandlerRegistry.hpp"
+
+#include <gtest/gtest.h>
+#include <algorithm>
+
+TEST(HandlerRegistry, Work) {
+ using namespace cru;
+ HandlerRegistry<void()> registry;
+
+ int counter = 1;
+
+ auto tag1 = registry.AddHandler([&counter] { counter++; });
+ auto tag2 = registry.AddHandler([&counter] { counter++; });
+
+ for (const auto& handler : registry) {
+ handler();
+ }
+
+ ASSERT_EQ(counter, 3);
+
+ registry.RemoveHandler(tag1);
+
+ for (const auto& handler : registry) {
+ handler();
+ }
+
+ ASSERT_EQ(counter, 4);
+
+ registry.RemoveHandler(tag2);
+
+ for (const auto& handler : registry) {
+ handler();
+ }
+
+ ASSERT_EQ(counter, 4);
+}
diff --git a/test/platform/CMakeLists.txt b/test/platform/CMakeLists.txt
new file mode 100644
index 00000000..9ad8fb51
--- /dev/null
+++ b/test/platform/CMakeLists.txt
@@ -0,0 +1,6 @@
+add_executable(cru_platform_base_test
+ MatrixTest.cpp
+)
+target_link_libraries(cru_platform_base_test PRIVATE cru_platform_base cru_test_base)
+
+gtest_discover_tests(cru_platform_base_test)
diff --git a/test/platform/MatrixTest.cpp b/test/platform/MatrixTest.cpp
new file mode 100644
index 00000000..3b8aab27
--- /dev/null
+++ b/test/platform/MatrixTest.cpp
@@ -0,0 +1,37 @@
+#include "cru/platform/GraphBase.hpp"
+#include "cru/platform/Matrix.hpp"
+
+#include <gtest/gtest.h>
+
+using cru::platform::Matrix;
+using cru::platform::Point;
+
+TEST(Matrix, Rotation) {
+ Point p(1, 1);
+
+ Point p90 = Matrix::Rotation(90).TransformPoint(p);
+ ASSERT_FLOAT_EQ(p90.x, -1);
+ ASSERT_FLOAT_EQ(p90.y, 1);
+
+ Point p180 = Matrix::Rotation(180).TransformPoint(p);
+ ASSERT_FLOAT_EQ(p180.x, -1);
+ ASSERT_FLOAT_EQ(p180.y, -1);
+
+ Point p270 = Matrix::Rotation(270).TransformPoint(p);
+ ASSERT_FLOAT_EQ(p270.x, 1);
+ ASSERT_FLOAT_EQ(p270.y, -1);
+}
+
+TEST(Matrix, TranslationAndRotation) {
+ Point p =
+ (Matrix::Translation(1, 1) * Matrix::Rotation(90)).TransformPoint({1, 1});
+ ASSERT_FLOAT_EQ(p.x, -2);
+ ASSERT_FLOAT_EQ(p.y, 2);
+}
+
+TEST(Matrix, RotationAndTranslation) {
+ Point p =
+ (Matrix::Rotation(90) * Matrix::Translation(1, 1)).TransformPoint({1, 1});
+ ASSERT_FLOAT_EQ(p.x, 0);
+ ASSERT_FLOAT_EQ(p.y, 2);
+}
diff --git a/test/win/CMakeLists.txt b/test/win/CMakeLists.txt
new file mode 100644
index 00000000..0ebdd7fe
--- /dev/null
+++ b/test/win/CMakeLists.txt
@@ -0,0 +1 @@
+add_subdirectory(graphics)
diff --git a/test/win/graphics/CMakeLists.txt b/test/win/graphics/CMakeLists.txt
new file mode 100644
index 00000000..c90537ac
--- /dev/null
+++ b/test/win/graphics/CMakeLists.txt
@@ -0,0 +1 @@
+add_subdirectory(direct)
diff --git a/test/win/graphics/direct/CMakeLists.txt b/test/win/graphics/direct/CMakeLists.txt
new file mode 100644
index 00000000..69e22ef7
--- /dev/null
+++ b/test/win/graphics/direct/CMakeLists.txt
@@ -0,0 +1,6 @@
+add_executable(cru_win_graphics_direct_test
+ ConvertTest.cpp
+)
+target_link_libraries(cru_win_graphics_direct_test PRIVATE cru_win_graphics_direct cru_test_base)
+
+gtest_discover_tests(cru_win_graphics_direct_test)
diff --git a/test/win/graphics/direct/ConvertTest.cpp b/test/win/graphics/direct/ConvertTest.cpp
new file mode 100644
index 00000000..f8f95dac
--- /dev/null
+++ b/test/win/graphics/direct/ConvertTest.cpp
@@ -0,0 +1,29 @@
+#include "cru/platform/Matrix.hpp"
+#include "cru/win/graphics/direct/ConvertUtil.hpp"
+
+#include <gtest/gtest.h>
+
+using cru::platform::Matrix;
+using cru::platform::graphics::win::direct::Convert;
+
+TEST(MatrixConvert, Rotation) {
+ auto matrix = Convert(Matrix::Rotation(90));
+
+ auto m = *D2D1::Matrix3x2F::ReinterpretBaseType(&matrix);
+
+ auto p = m.TransformPoint({1, 1});
+
+ ASSERT_FLOAT_EQ(p.x, -1);
+ ASSERT_FLOAT_EQ(p.y, 1);
+}
+
+TEST(MatrixConvert, RotationAndTranslation) {
+ auto matrix = Convert(Matrix::Rotation(90) * Matrix::Translation(1, 1));
+
+ auto m = *D2D1::Matrix3x2F::ReinterpretBaseType(&matrix);
+
+ auto p = m.TransformPoint({1, 1});
+
+ ASSERT_FLOAT_EQ(p.x, 0);
+ ASSERT_FLOAT_EQ(p.y, 2);
+}
diff --git a/vcpkg b/vcpkg
-Subproject 13590753fec479c5b0a3d48dd553dde8d49615f
+Subproject acb6b10e7fdf5e8519c18398d0b069e1d58ca02