From d86a71f79afe0e4dac768f61d6bff690567aca5b Mon Sep 17 00:00:00 2001 From: crupest Date: Sun, 24 May 2020 01:40:02 +0800 Subject: ... --- src/win/CMakeLists.txt | 14 +- src/win/DebugLogger.hpp | 24 +++ src/win/HeapDebug.cpp | 11 ++ src/win/debug_logger.hpp | 24 --- src/win/exception.cpp | 2 +- src/win/graph/direct/CMakeLists.txt | 34 ++-- src/win/graph/direct/TextLayout.cpp | 137 ++++++++++++++ src/win/graph/direct/brush.cpp | 8 +- src/win/graph/direct/factory.cpp | 16 +- src/win/graph/direct/font.cpp | 8 +- src/win/graph/direct/geometry.cpp | 8 +- src/win/graph/direct/painter.cpp | 16 +- src/win/graph/direct/resource.cpp | 4 +- src/win/graph/direct/text_layout.cpp | 137 -------------- src/win/heap_debug.cpp | 11 -- src/win/native/CMakeLists.txt | 56 +++--- src/win/native/DpiUtil.hpp | 46 +++++ src/win/native/GodWindow.cpp | 82 ++++++++ src/win/native/GodWindowMessage.hpp | 6 + src/win/native/InputMethod.cpp | 324 ++++++++++++++++++++++++++++++++ src/win/native/UiApplication.cpp | 124 ++++++++++++ src/win/native/WindowClass.cpp | 28 +++ src/win/native/WindowD2DPainter.cpp | 22 +++ src/win/native/WindowD2DPainter.hpp | 21 +++ src/win/native/WindowManager.cpp | 56 ++++++ src/win/native/WindowManager.hpp | 51 +++++ src/win/native/WindowRenderTarget.cpp | 78 ++++++++ src/win/native/cursor.cpp | 6 +- src/win/native/dpi_util.hpp | 46 ----- src/win/native/god_window.cpp | 82 -------- src/win/native/god_window_message.hpp | 6 - src/win/native/input_method.cpp | 324 -------------------------------- src/win/native/keyboard.cpp | 2 +- src/win/native/timer.cpp | 2 +- src/win/native/timer.hpp | 6 +- src/win/native/ui_application.cpp | 124 ------------ src/win/native/window.cpp | 28 +-- src/win/native/window_class.cpp | 28 --- src/win/native/window_d2d_painter.cpp | 22 --- src/win/native/window_d2d_painter.hpp | 21 --- src/win/native/window_manager.cpp | 56 ------ src/win/native/window_manager.hpp | 51 ----- src/win/native/window_render_target.cpp | 78 -------- src/win/string.cpp | 4 +- 44 files changed, 1117 insertions(+), 1117 deletions(-) create mode 100644 src/win/DebugLogger.hpp create mode 100644 src/win/HeapDebug.cpp delete mode 100644 src/win/debug_logger.hpp create mode 100644 src/win/graph/direct/TextLayout.cpp delete mode 100644 src/win/graph/direct/text_layout.cpp delete mode 100644 src/win/heap_debug.cpp create mode 100644 src/win/native/DpiUtil.hpp create mode 100644 src/win/native/GodWindow.cpp create mode 100644 src/win/native/GodWindowMessage.hpp create mode 100644 src/win/native/InputMethod.cpp create mode 100644 src/win/native/UiApplication.cpp create mode 100644 src/win/native/WindowClass.cpp create mode 100644 src/win/native/WindowD2DPainter.cpp create mode 100644 src/win/native/WindowD2DPainter.hpp create mode 100644 src/win/native/WindowManager.cpp create mode 100644 src/win/native/WindowManager.hpp create mode 100644 src/win/native/WindowRenderTarget.cpp delete mode 100644 src/win/native/dpi_util.hpp delete mode 100644 src/win/native/god_window.cpp delete mode 100644 src/win/native/god_window_message.hpp delete mode 100644 src/win/native/input_method.cpp delete mode 100644 src/win/native/ui_application.cpp delete mode 100644 src/win/native/window_class.cpp delete mode 100644 src/win/native/window_d2d_painter.cpp delete mode 100644 src/win/native/window_d2d_painter.hpp delete mode 100644 src/win/native/window_manager.cpp delete mode 100644 src/win/native/window_manager.hpp delete mode 100644 src/win/native/window_render_target.cpp (limited to 'src/win') diff --git a/src/win/CMakeLists.txt b/src/win/CMakeLists.txt index 696ef200..f4062411 100644 --- a/src/win/CMakeLists.txt +++ b/src/win/CMakeLists.txt @@ -1,16 +1,16 @@ set(CRU_WIN_BASE_INCLUDE_DIR ${CRU_INCLUDE_DIR}/cru/win/) add_library(cru_win_base STATIC - debug_logger.hpp + DebugLogger.hpp - exception.cpp - heap_debug.cpp - string.cpp + Exception.cpp + HeapDebug.cpp + String.cpp ) target_sources(cru_win_base PUBLIC - ${CRU_WIN_BASE_INCLUDE_DIR}/exception.hpp - ${CRU_WIN_BASE_INCLUDE_DIR}/string.hpp - ${CRU_WIN_BASE_INCLUDE_DIR}/win_pre_config.hpp + ${CRU_WIN_BASE_INCLUDE_DIR}/Exception.hpp + ${CRU_WIN_BASE_INCLUDE_DIR}/String.hpp + ${CRU_WIN_BASE_INCLUDE_DIR}/WinPreConfig.hpp ) target_compile_definitions(cru_win_base PUBLIC UNICODE _UNICODE) # use unicode target_link_libraries(cru_win_base PUBLIC cru_base) diff --git a/src/win/DebugLogger.hpp b/src/win/DebugLogger.hpp new file mode 100644 index 00000000..381803bc --- /dev/null +++ b/src/win/DebugLogger.hpp @@ -0,0 +1,24 @@ +#include "cru/win/WinPreConfig.hpp" + +#include "cru/common/Logger.hpp" +#include "cru/win/String.hpp" + +namespace cru::platform::win { + +class WinDebugLoggerSource : public ::cru::log::ILogSource { + public: + WinDebugLoggerSource() = default; + + CRU_DELETE_COPY(WinDebugLoggerSource) + CRU_DELETE_MOVE(WinDebugLoggerSource) + + ~WinDebugLoggerSource() = default; + + void Write(::cru::log::LogLevel level, const std::string_view& s) override { + CRU_UNUSED(level) + + const std::wstring&& ws = ToUtf16String(s); + ::OutputDebugStringW(ws.data()); + } +}; +} // namespace cru::platform::win diff --git a/src/win/HeapDebug.cpp b/src/win/HeapDebug.cpp new file mode 100644 index 00000000..6ec1ffcc --- /dev/null +++ b/src/win/HeapDebug.cpp @@ -0,0 +1,11 @@ +#include "cru/win/WinPreConfig.hpp" + +#include "cru/platform/HeapDebug.hpp" + +#include + +namespace cru::platform { +void SetupHeapDebug() { + _CrtSetDbgFlag(_CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF); +} +} diff --git a/src/win/debug_logger.hpp b/src/win/debug_logger.hpp deleted file mode 100644 index b6694279..00000000 --- a/src/win/debug_logger.hpp +++ /dev/null @@ -1,24 +0,0 @@ -#include "cru/win/win_pre_config.hpp" - -#include "cru/common/logger.hpp" -#include "cru/win/string.hpp" - -namespace cru::platform::win { - -class WinDebugLoggerSource : public ::cru::log::ILogSource { - public: - WinDebugLoggerSource() = default; - - CRU_DELETE_COPY(WinDebugLoggerSource) - CRU_DELETE_MOVE(WinDebugLoggerSource) - - ~WinDebugLoggerSource() = default; - - void Write(::cru::log::LogLevel level, const std::string_view& s) override { - CRU_UNUSED(level) - - const std::wstring&& ws = ToUtf16String(s); - ::OutputDebugStringW(ws.data()); - } -}; -} // namespace cru::platform::win diff --git a/src/win/exception.cpp b/src/win/exception.cpp index fde3ec6f..9f9fb03d 100644 --- a/src/win/exception.cpp +++ b/src/win/exception.cpp @@ -1,4 +1,4 @@ -#include "cru/win/exception.hpp" +#include "cru/win/Exception.hpp" #include #include diff --git a/src/win/graph/direct/CMakeLists.txt b/src/win/graph/direct/CMakeLists.txt index 492f6749..5505b0b5 100644 --- a/src/win/graph/direct/CMakeLists.txt +++ b/src/win/graph/direct/CMakeLists.txt @@ -1,25 +1,25 @@ set(CRU_WIN_GRAPH_DIRECT_INCLUDE_DIR ${CRU_INCLUDE_DIR}/cru/win/graph/direct) add_library(cru_win_graph_direct STATIC - brush.cpp - font.cpp - geometry.cpp - factory.cpp - painter.cpp - resource.cpp - text_layout.cpp + Brush.cpp + Font.cpp + Geometry.cpp + Factory.cpp + Painter.cpp + Resource.cpp + TextLayout.cpp ) target_sources(cru_win_graph_direct PUBLIC - ${CRU_WIN_GRAPH_DIRECT_INCLUDE_DIR}/brush.hpp - ${CRU_WIN_GRAPH_DIRECT_INCLUDE_DIR}/com_resource.hpp - ${CRU_WIN_GRAPH_DIRECT_INCLUDE_DIR}/convert_util.hpp - ${CRU_WIN_GRAPH_DIRECT_INCLUDE_DIR}/exception.hpp - ${CRU_WIN_GRAPH_DIRECT_INCLUDE_DIR}/font.hpp - ${CRU_WIN_GRAPH_DIRECT_INCLUDE_DIR}/geometry.hpp - ${CRU_WIN_GRAPH_DIRECT_INCLUDE_DIR}/factory.hpp - ${CRU_WIN_GRAPH_DIRECT_INCLUDE_DIR}/painter.hpp - ${CRU_WIN_GRAPH_DIRECT_INCLUDE_DIR}/resource.hpp - ${CRU_WIN_GRAPH_DIRECT_INCLUDE_DIR}/text_layout.hpp + ${CRU_WIN_GRAPH_DIRECT_INCLUDE_DIR}/Brush.hpp + ${CRU_WIN_GRAPH_DIRECT_INCLUDE_DIR}/ComResource.hpp + ${CRU_WIN_GRAPH_DIRECT_INCLUDE_DIR}/ConvertUtil.hpp + ${CRU_WIN_GRAPH_DIRECT_INCLUDE_DIR}/Exception.hpp + ${CRU_WIN_GRAPH_DIRECT_INCLUDE_DIR}/Font.hpp + ${CRU_WIN_GRAPH_DIRECT_INCLUDE_DIR}/Geometry.hpp + ${CRU_WIN_GRAPH_DIRECT_INCLUDE_DIR}/Factory.hpp + ${CRU_WIN_GRAPH_DIRECT_INCLUDE_DIR}/Painter.hpp + ${CRU_WIN_GRAPH_DIRECT_INCLUDE_DIR}/Resource.hpp + ${CRU_WIN_GRAPH_DIRECT_INCLUDE_DIR}/TextLayout.hpp ) target_link_libraries(cru_win_graph_direct PUBLIC D3D11 D2d1 DWrite) target_link_libraries(cru_win_graph_direct PUBLIC cru_win_base cru_platform_graph) diff --git a/src/win/graph/direct/TextLayout.cpp b/src/win/graph/direct/TextLayout.cpp new file mode 100644 index 00000000..16db77f0 --- /dev/null +++ b/src/win/graph/direct/TextLayout.cpp @@ -0,0 +1,137 @@ +#include "cru/win/graph/direct/TextLayout.hpp" + +#include "cru/common/Logger.hpp" +#include "cru/platform/Check.hpp" +#include "cru/win/graph/direct/Exception.hpp" +#include "cru/win/graph/direct/Factory.hpp" +#include "cru/win/graph/direct/Font.hpp" +#include "cru/win/String.hpp" + +#include + +namespace cru::platform::graph::win::direct { +using cru::platform::win::IndexUtf16ToUtf8; +using cru::platform::win::IndexUtf8ToUtf16; + +DWriteTextLayout::DWriteTextLayout(DirectGraphFactory* factory, + std::shared_ptr font, + std::string text) + : DirectGraphResource(factory), text_(std::move(text)) { + Expects(font); + font_ = CheckPlatform(font, GetPlatformId()); + + w_text_ = cru::platform::win::ToUtf16String(text_); + + ThrowIfFailed(factory->GetDWriteFactory()->CreateTextLayout( + w_text_.c_str(), static_cast(w_text_.size()), + font_->GetComInterface(), max_width_, max_height_, &text_layout_)); +} + +DWriteTextLayout::~DWriteTextLayout() = default; + +std::string DWriteTextLayout::GetText() { return text_; } + +void DWriteTextLayout::SetText(std::string new_text) { + text_.swap(new_text); + w_text_ = cru::platform::win::ToUtf16String(text_); + ThrowIfFailed(GetDirectFactory()->GetDWriteFactory()->CreateTextLayout( + w_text_.c_str(), static_cast(w_text_.size()), + font_->GetComInterface(), max_width_, max_height_, &text_layout_)); +} + +std::shared_ptr DWriteTextLayout::GetFont() { + return std::dynamic_pointer_cast(font_); +} + +void DWriteTextLayout::SetFont(std::shared_ptr font) { + font_ = CheckPlatform(font, GetPlatformId()); + ThrowIfFailed(GetDirectFactory()->GetDWriteFactory()->CreateTextLayout( + w_text_.c_str(), static_cast(w_text_.size()), + font_->GetComInterface(), max_width_, max_height_, &text_layout_)); +} + +void DWriteTextLayout::SetMaxWidth(float max_width) { + max_width_ = max_width; + ThrowIfFailed(text_layout_->SetMaxWidth(max_width_)); +} + +void DWriteTextLayout::SetMaxHeight(float max_height) { + max_height_ = max_height; + ThrowIfFailed(text_layout_->SetMaxHeight(max_height_)); +} + +Rect DWriteTextLayout::GetTextBounds() { + DWRITE_TEXT_METRICS metrics; + ThrowIfFailed(text_layout_->GetMetrics(&metrics)); + return Rect{metrics.left, metrics.top, metrics.width, metrics.height}; +} + +std::vector DWriteTextLayout::TextRangeRect( + const TextRange& text_range_arg) { + if (text_range_arg.count == 0) { + return {}; + } + const auto text_range = text_range_arg.Normalize(); + + // TODO: This can be faster with one iteration. + const int start_index = + IndexUtf8ToUtf16(text_, static_cast(text_range.position), w_text_); + const int end_index = IndexUtf8ToUtf16( + text_, static_cast(text_range.position + text_range.count), w_text_); + + DWRITE_TEXT_METRICS text_metrics; + ThrowIfFailed(text_layout_->GetMetrics(&text_metrics)); + const auto metrics_count = + text_metrics.lineCount * text_metrics.maxBidiReorderingDepth; + + std::vector hit_test_metrics(metrics_count); + UINT32 actual_count; + ThrowIfFailed(text_layout_->HitTestTextRange( + static_cast(start_index), + static_cast(end_index - start_index), 0, 0, + hit_test_metrics.data(), metrics_count, &actual_count)); + + hit_test_metrics.erase(hit_test_metrics.cbegin() + actual_count, + hit_test_metrics.cend()); + + std::vector result; + result.reserve(actual_count); + + for (const auto& metrics : hit_test_metrics) { + result.push_back( + Rect{metrics.left, metrics.top, metrics.width, metrics.height}); + } + + return result; +} + +Point DWriteTextLayout::TextSinglePoint(gsl::index position, bool trailing) { + const auto index = + IndexUtf8ToUtf16(text_, static_cast(position), w_text_); + + DWRITE_HIT_TEST_METRICS metrics; + FLOAT left; + FLOAT top; + ThrowIfFailed(text_layout_->HitTestTextPosition(static_cast(index), + static_cast(trailing), + &left, &top, &metrics)); + return Point{left, top}; +} + +TextHitTestResult DWriteTextLayout::HitTest(const Point& point) { + BOOL trailing; + BOOL inside; + DWRITE_HIT_TEST_METRICS metrics; + + ThrowIfFailed(text_layout_->HitTestPoint(point.x, point.y, &trailing, &inside, + &metrics)); + + const auto index = + IndexUtf16ToUtf8(w_text_, static_cast(metrics.textPosition), text_); + TextHitTestResult result; + result.position = index; + result.trailing = trailing != 0; + result.insideText = inside != 0; + return result; +} +} // namespace cru::platform::graph::win::direct diff --git a/src/win/graph/direct/brush.cpp b/src/win/graph/direct/brush.cpp index 329ce509..2b9d4ac3 100644 --- a/src/win/graph/direct/brush.cpp +++ b/src/win/graph/direct/brush.cpp @@ -1,8 +1,8 @@ -#include "cru/win/graph/direct/brush.hpp" +#include "cru/win/graph/direct/Brush.hpp" -#include "cru/win/graph/direct/convert_util.hpp" -#include "cru/win/graph/direct/exception.hpp" -#include "cru/win/graph/direct/factory.hpp" +#include "cru/win/graph/direct/ConvertUtil.hpp" +#include "cru/win/graph/direct/Exception.hpp" +#include "cru/win/graph/direct/Factory.hpp" namespace cru::platform::graph::win::direct { D2DSolidColorBrush::D2DSolidColorBrush(DirectGraphFactory* factory) diff --git a/src/win/graph/direct/factory.cpp b/src/win/graph/direct/factory.cpp index 0f4739ee..d9213994 100644 --- a/src/win/graph/direct/factory.cpp +++ b/src/win/graph/direct/factory.cpp @@ -1,11 +1,11 @@ -#include "cru/win/graph/direct/factory.hpp" - -#include "cru/common/logger.hpp" -#include "cru/win/graph/direct/brush.hpp" -#include "cru/win/graph/direct/exception.hpp" -#include "cru/win/graph/direct/font.hpp" -#include "cru/win/graph/direct/geometry.hpp" -#include "cru/win/graph/direct/text_layout.hpp" +#include "cru/win/graph/direct/Factory.hpp" + +#include "cru/common/Logger.hpp" +#include "cru/win/graph/direct/Brush.hpp" +#include "cru/win/graph/direct/Exception.hpp" +#include "cru/win/graph/direct/Font.hpp" +#include "cru/win/graph/direct/Geometry.hpp" +#include "cru/win/graph/direct/TextLayout.hpp" #include #include diff --git a/src/win/graph/direct/font.cpp b/src/win/graph/direct/font.cpp index 6d50fafe..edbbc59d 100644 --- a/src/win/graph/direct/font.cpp +++ b/src/win/graph/direct/font.cpp @@ -1,8 +1,8 @@ -#include "cru/win/graph/direct/font.hpp" +#include "cru/win/graph/direct/Font.hpp" -#include "cru/win/graph/direct/exception.hpp" -#include "cru/win/graph/direct/factory.hpp" -#include "cru/win/string.hpp" +#include "cru/win/graph/direct/Exception.hpp" +#include "cru/win/graph/direct/Factory.hpp" +#include "cru/win/String.hpp" #include #include diff --git a/src/win/graph/direct/geometry.cpp b/src/win/graph/direct/geometry.cpp index 57b7f237..e77b4749 100644 --- a/src/win/graph/direct/geometry.cpp +++ b/src/win/graph/direct/geometry.cpp @@ -1,8 +1,8 @@ -#include "cru/win/graph/direct/geometry.hpp" +#include "cru/win/graph/direct/Geometry.hpp" -#include "cru/win/graph/direct/convert_util.hpp" -#include "cru/win/graph/direct/exception.hpp" -#include "cru/win/graph/direct/factory.hpp" +#include "cru/win/graph/direct/ConvertUtil.hpp" +#include "cru/win/graph/direct/Exception.hpp" +#include "cru/win/graph/direct/Factory.hpp" namespace cru::platform::graph::win::direct { D2DGeometryBuilder::D2DGeometryBuilder(DirectGraphFactory* factory) diff --git a/src/win/graph/direct/painter.cpp b/src/win/graph/direct/painter.cpp index df0075e0..3ffb5208 100644 --- a/src/win/graph/direct/painter.cpp +++ b/src/win/graph/direct/painter.cpp @@ -1,11 +1,11 @@ -#include "cru/win/graph/direct/painter.hpp" - -#include "cru/platform/check.hpp" -#include "cru/win/graph/direct/brush.hpp" -#include "cru/win/graph/direct/convert_util.hpp" -#include "cru/win/graph/direct/exception.hpp" -#include "cru/win/graph/direct/geometry.hpp" -#include "cru/win/graph/direct/text_layout.hpp" +#include "cru/win/graph/direct/Painter.hpp" + +#include "cru/platform/Check.hpp" +#include "cru/win/graph/direct/Brush.hpp" +#include "cru/win/graph/direct/ConvertUtil.hpp" +#include "cru/win/graph/direct/Exception.hpp" +#include "cru/win/graph/direct/Geometry.hpp" +#include "cru/win/graph/direct/TextLayout.hpp" #include diff --git a/src/win/graph/direct/resource.cpp b/src/win/graph/direct/resource.cpp index c2be27ed..772bb74b 100644 --- a/src/win/graph/direct/resource.cpp +++ b/src/win/graph/direct/resource.cpp @@ -1,6 +1,6 @@ -#include "cru/win/graph/direct/resource.hpp" +#include "cru/win/graph/direct/Resource.hpp" -#include "cru/win/graph/direct/factory.hpp" +#include "cru/win/graph/direct/Factory.hpp" namespace cru::platform::graph::win::direct { DirectGraphResource::DirectGraphResource(DirectGraphFactory* factory) diff --git a/src/win/graph/direct/text_layout.cpp b/src/win/graph/direct/text_layout.cpp deleted file mode 100644 index 7b8d2ab0..00000000 --- a/src/win/graph/direct/text_layout.cpp +++ /dev/null @@ -1,137 +0,0 @@ -#include "cru/win/graph/direct/text_layout.hpp" - -#include "cru/common/logger.hpp" -#include "cru/platform/check.hpp" -#include "cru/win/graph/direct/exception.hpp" -#include "cru/win/graph/direct/factory.hpp" -#include "cru/win/graph/direct/font.hpp" -#include "cru/win/string.hpp" - -#include - -namespace cru::platform::graph::win::direct { -using cru::platform::win::IndexUtf16ToUtf8; -using cru::platform::win::IndexUtf8ToUtf16; - -DWriteTextLayout::DWriteTextLayout(DirectGraphFactory* factory, - std::shared_ptr font, - std::string text) - : DirectGraphResource(factory), text_(std::move(text)) { - Expects(font); - font_ = CheckPlatform(font, GetPlatformId()); - - w_text_ = cru::platform::win::ToUtf16String(text_); - - ThrowIfFailed(factory->GetDWriteFactory()->CreateTextLayout( - w_text_.c_str(), static_cast(w_text_.size()), - font_->GetComInterface(), max_width_, max_height_, &text_layout_)); -} - -DWriteTextLayout::~DWriteTextLayout() = default; - -std::string DWriteTextLayout::GetText() { return text_; } - -void DWriteTextLayout::SetText(std::string new_text) { - text_.swap(new_text); - w_text_ = cru::platform::win::ToUtf16String(text_); - ThrowIfFailed(GetDirectFactory()->GetDWriteFactory()->CreateTextLayout( - w_text_.c_str(), static_cast(w_text_.size()), - font_->GetComInterface(), max_width_, max_height_, &text_layout_)); -} - -std::shared_ptr DWriteTextLayout::GetFont() { - return std::dynamic_pointer_cast(font_); -} - -void DWriteTextLayout::SetFont(std::shared_ptr font) { - font_ = CheckPlatform(font, GetPlatformId()); - ThrowIfFailed(GetDirectFactory()->GetDWriteFactory()->CreateTextLayout( - w_text_.c_str(), static_cast(w_text_.size()), - font_->GetComInterface(), max_width_, max_height_, &text_layout_)); -} - -void DWriteTextLayout::SetMaxWidth(float max_width) { - max_width_ = max_width; - ThrowIfFailed(text_layout_->SetMaxWidth(max_width_)); -} - -void DWriteTextLayout::SetMaxHeight(float max_height) { - max_height_ = max_height; - ThrowIfFailed(text_layout_->SetMaxHeight(max_height_)); -} - -Rect DWriteTextLayout::GetTextBounds() { - DWRITE_TEXT_METRICS metrics; - ThrowIfFailed(text_layout_->GetMetrics(&metrics)); - return Rect{metrics.left, metrics.top, metrics.width, metrics.height}; -} - -std::vector DWriteTextLayout::TextRangeRect( - const TextRange& text_range_arg) { - if (text_range_arg.count == 0) { - return {}; - } - const auto text_range = text_range_arg.Normalize(); - - // TODO: This can be faster with one iteration. - const int start_index = - IndexUtf8ToUtf16(text_, static_cast(text_range.position), w_text_); - const int end_index = IndexUtf8ToUtf16( - text_, static_cast(text_range.position + text_range.count), w_text_); - - DWRITE_TEXT_METRICS text_metrics; - ThrowIfFailed(text_layout_->GetMetrics(&text_metrics)); - const auto metrics_count = - text_metrics.lineCount * text_metrics.maxBidiReorderingDepth; - - std::vector hit_test_metrics(metrics_count); - UINT32 actual_count; - ThrowIfFailed(text_layout_->HitTestTextRange( - static_cast(start_index), - static_cast(end_index - start_index), 0, 0, - hit_test_metrics.data(), metrics_count, &actual_count)); - - hit_test_metrics.erase(hit_test_metrics.cbegin() + actual_count, - hit_test_metrics.cend()); - - std::vector result; - result.reserve(actual_count); - - for (const auto& metrics : hit_test_metrics) { - result.push_back( - Rect{metrics.left, metrics.top, metrics.width, metrics.height}); - } - - return result; -} - -Point DWriteTextLayout::TextSinglePoint(gsl::index position, bool trailing) { - const auto index = - IndexUtf8ToUtf16(text_, static_cast(position), w_text_); - - DWRITE_HIT_TEST_METRICS metrics; - FLOAT left; - FLOAT top; - ThrowIfFailed(text_layout_->HitTestTextPosition(static_cast(index), - static_cast(trailing), - &left, &top, &metrics)); - return Point{left, top}; -} - -TextHitTestResult DWriteTextLayout::HitTest(const Point& point) { - BOOL trailing; - BOOL inside; - DWRITE_HIT_TEST_METRICS metrics; - - ThrowIfFailed(text_layout_->HitTestPoint(point.x, point.y, &trailing, &inside, - &metrics)); - - const auto index = - IndexUtf16ToUtf8(w_text_, static_cast(metrics.textPosition), text_); - TextHitTestResult result; - result.position = index; - result.trailing = trailing != 0; - result.insideText = inside != 0; - return result; -} -} // namespace cru::platform::graph::win::direct diff --git a/src/win/heap_debug.cpp b/src/win/heap_debug.cpp deleted file mode 100644 index 33ebae7c..00000000 --- a/src/win/heap_debug.cpp +++ /dev/null @@ -1,11 +0,0 @@ -#include "cru/win/win_pre_config.hpp" - -#include "cru/platform/heap_debug.hpp" - -#include - -namespace cru::platform { -void SetupHeapDebug() { - _CrtSetDbgFlag(_CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF); -} -} diff --git a/src/win/native/CMakeLists.txt b/src/win/native/CMakeLists.txt index ba4b3387..f1b167d2 100644 --- a/src/win/native/CMakeLists.txt +++ b/src/win/native/CMakeLists.txt @@ -1,37 +1,37 @@ set(CRU_WIN_NATIVE_INCLUDE_DIR ${CRU_INCLUDE_DIR}/cru/win/native) add_library(cru_win_native STATIC - dpi_util.hpp - god_window_message.hpp - timer.hpp - window_d2d_painter.hpp - window_manager.hpp + DpiUtil.hpp + GodWindowMessage.hpp + Timer.hpp + WindowD2DPainter.hpp + WindowManager.hpp - cursor.cpp - god_window.cpp - input_method.cpp - keyboard.cpp - timer.cpp - ui_application.cpp - window.cpp - window_class.cpp - window_d2d_painter.cpp - window_manager.cpp - window_render_target.cpp + Cursor.cpp + GodWindow.cpp + InputMethod.cpp + Keyboard.cpp + Timer.cpp + UiApplication.cpp + Window.cpp + WindowClass.cpp + WindowD2DPainter.cpp + WindowManager.cpp + WindowRenderTarget.cpp ) target_sources(cru_win_native PUBLIC - ${CRU_WIN_NATIVE_INCLUDE_DIR}/cursor.hpp - ${CRU_WIN_NATIVE_INCLUDE_DIR}/exception.hpp - ${CRU_WIN_NATIVE_INCLUDE_DIR}/base.hpp - ${CRU_WIN_NATIVE_INCLUDE_DIR}/god_window.hpp - ${CRU_WIN_NATIVE_INCLUDE_DIR}/input_method.hpp - ${CRU_WIN_NATIVE_INCLUDE_DIR}/keyboard.hpp - ${CRU_WIN_NATIVE_INCLUDE_DIR}/resource.hpp - ${CRU_WIN_NATIVE_INCLUDE_DIR}/ui_application.hpp - ${CRU_WIN_NATIVE_INCLUDE_DIR}/window.hpp - ${CRU_WIN_NATIVE_INCLUDE_DIR}/window_class.hpp - ${CRU_WIN_NATIVE_INCLUDE_DIR}/window_native_message_event_args.hpp - ${CRU_WIN_NATIVE_INCLUDE_DIR}/window_render_target.hpp + ${CRU_WIN_NATIVE_INCLUDE_DIR}/Cursor.hpp + ${CRU_WIN_NATIVE_INCLUDE_DIR}/Exception.hpp + ${CRU_WIN_NATIVE_INCLUDE_DIR}/Base.hpp + ${CRU_WIN_NATIVE_INCLUDE_DIR}/GodWindow.hpp + ${CRU_WIN_NATIVE_INCLUDE_DIR}/InputMethod.hpp + ${CRU_WIN_NATIVE_INCLUDE_DIR}/Keyboard.hpp + ${CRU_WIN_NATIVE_INCLUDE_DIR}/Resource.hpp + ${CRU_WIN_NATIVE_INCLUDE_DIR}/UiApplication.hpp + ${CRU_WIN_NATIVE_INCLUDE_DIR}/Window.hpp + ${CRU_WIN_NATIVE_INCLUDE_DIR}/WindowClass.hpp + ${CRU_WIN_NATIVE_INCLUDE_DIR}/WindowNativeMessageEventArgs.hpp + ${CRU_WIN_NATIVE_INCLUDE_DIR}/WindowRenderTarget.hpp ) target_link_libraries(cru_win_native PUBLIC imm32) target_link_libraries(cru_win_native PUBLIC cru_win_graph_direct cru_platform_native) diff --git a/src/win/native/DpiUtil.hpp b/src/win/native/DpiUtil.hpp new file mode 100644 index 00000000..16ffda25 --- /dev/null +++ b/src/win/native/DpiUtil.hpp @@ -0,0 +1,46 @@ +#pragma once +#include "cru/platform/native/Base.hpp" + +// The dpi awareness needs to be implemented in the future. Currently we use 96 +// as default. + +namespace cru::platform::native::win { +inline platform::native::Dpi GetDpi() { + return platform::native::Dpi{96.0f, 96.0f}; +} + +inline int DipToPixelInternal(const float dip, const float dpi) { + return static_cast(dip * dpi / 96.0f); +} + +inline int DipToPixelX(const float dip_x) { + return DipToPixelInternal(dip_x, GetDpi().x); +} + +inline int DipToPixelY(const float dip_y) { + return DipToPixelInternal(dip_y, GetDpi().y); +} + +inline float DipToPixelInternal(const int pixel, const float dpi) { + return static_cast(pixel) * 96.0f / dpi; +} + +inline float PixelToDipX(const int pixel_x) { + return DipToPixelInternal(pixel_x, GetDpi().x); +} + +inline float PixelToDipY(const int pixel_y) { + return DipToPixelInternal(pixel_y, GetDpi().y); +} + +inline Point PiToDip(const POINT& pi_point) { + return Point(PixelToDipX(pi_point.x), PixelToDipY(pi_point.y)); +} + +inline POINT DipToPi(const Point& dip_point) { + POINT result; + result.x = DipToPixelX(dip_point.x); + result.y = DipToPixelY(dip_point.y); + return result; +} +} // namespace cru::platform::native::win diff --git a/src/win/native/GodWindow.cpp b/src/win/native/GodWindow.cpp new file mode 100644 index 00000000..076954d4 --- /dev/null +++ b/src/win/native/GodWindow.cpp @@ -0,0 +1,82 @@ +#include "cru/win/native/GodWindow.hpp" + +#include "cru/common/Logger.hpp" +#include "cru/win/native/Exception.hpp" +#include "cru/win/native/UiApplication.hpp" +#include "cru/win/native/WindowClass.hpp" +#include "GodWindowMessage.hpp" +#include "Timer.hpp" + +namespace cru::platform::native::win { +constexpr auto god_window_class_name = L"GodWindowClass"; + +LRESULT CALLBACK GodWndProc(HWND hWnd, UINT uMsg, WPARAM wParam, + LPARAM lParam) { + const auto app = WinUiApplication::GetInstance(); + + if (app) { + LRESULT result; + const auto handled = app->GetGodWindow()->HandleGodWindowMessage( + hWnd, uMsg, wParam, lParam, &result); + if (handled) + return result; + else + return DefWindowProcW(hWnd, uMsg, wParam, lParam); + } else + return DefWindowProcW(hWnd, uMsg, wParam, lParam); +} + +GodWindow::GodWindow(WinUiApplication* application) { + application_ = application; + + const auto h_instance = application->GetInstanceHandle(); + + god_window_class_ = std::make_unique(god_window_class_name, + GodWndProc, h_instance); + + hwnd_ = CreateWindowEx(0, god_window_class_name, L"", 0, CW_USEDEFAULT, + CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, + HWND_MESSAGE, nullptr, h_instance, nullptr); + + if (hwnd_ == nullptr) + throw Win32Error(::GetLastError(), "Failed to create god window."); +} + +GodWindow::~GodWindow() { + if (!::DestroyWindow(hwnd_)) { + // Although this could be "safely" ignore. + log::Warn("Failed to destroy god window."); + } +} + +bool GodWindow::HandleGodWindowMessage(HWND hwnd, UINT msg, WPARAM w_param, + LPARAM l_param, LRESULT* result) { + CRU_UNUSED(hwnd) + CRU_UNUSED(l_param) + + switch (msg) { + case invoke_later_message_id: { + const auto p_action = reinterpret_cast*>(w_param); + (*p_action)(); + delete p_action; + *result = 0; + return true; + } + case WM_TIMER: { + const auto id = static_cast(w_param); + const auto action = application_->GetTimerManager()->GetAction(id); + if (action.has_value()) { + (action.value().second)(); + if (!action.value().first) + application_->GetTimerManager()->KillTimer(id); + result = 0; + return true; + } + break; + } + default: + return false; + } + return false; +} +} // namespace cru::platform::native::win diff --git a/src/win/native/GodWindowMessage.hpp b/src/win/native/GodWindowMessage.hpp new file mode 100644 index 00000000..9063cb4d --- /dev/null +++ b/src/win/native/GodWindowMessage.hpp @@ -0,0 +1,6 @@ +#pragma once +#include "cru/win/WinPreConfig.hpp" + +namespace cru::platform::native::win { +constexpr int invoke_later_message_id = WM_USER + 2000; +} diff --git a/src/win/native/InputMethod.cpp b/src/win/native/InputMethod.cpp new file mode 100644 index 00000000..2e31108e --- /dev/null +++ b/src/win/native/InputMethod.cpp @@ -0,0 +1,324 @@ +#include "cru/win/native/InputMethod.hpp" + +#include "cru/common/Logger.hpp" +#include "cru/platform/Check.hpp" +#include "cru/win/Exception.hpp" +#include "cru/win/native/Window.hpp" +#include "cru/win/String.hpp" +#include "DpiUtil.hpp" + +#include + +namespace cru::platform::native::win { +AutoHIMC::AutoHIMC(HWND hwnd) : hwnd_(hwnd) { + Expects(hwnd); + handle_ = ::ImmGetContext(hwnd); +} + +AutoHIMC::AutoHIMC(AutoHIMC&& other) + : hwnd_(other.hwnd_), handle_(other.handle_) { + other.hwnd_ = nullptr; + other.handle_ = nullptr; +} + +AutoHIMC& AutoHIMC::operator=(AutoHIMC&& other) { + if (this != &other) { + Object::operator=(std::move(other)); + this->hwnd_ = other.hwnd_; + this->handle_ = other.handle_; + other.hwnd_ = nullptr; + other.handle_ = nullptr; + } + return *this; +} + +AutoHIMC::~AutoHIMC() { + if (handle_) { + if (!::ImmReleaseContext(hwnd_, handle_)) + log::Warn("AutoHIMC: Failed to release HIMC."); + } +} + +// copied from chromium +namespace { +// Determines whether or not the given attribute represents a target +// (a.k.a. a selection). +bool IsTargetAttribute(char attribute) { + return (attribute == ATTR_TARGET_CONVERTED || + attribute == ATTR_TARGET_NOTCONVERTED); +} +// Helper function for ImeInput::GetCompositionInfo() method, to get the target +// range that's selected by the user in the current composition string. +void GetCompositionTargetRange(HIMC imm_context, int* target_start, + int* target_end) { + int attribute_size = + ::ImmGetCompositionString(imm_context, GCS_COMPATTR, NULL, 0); + if (attribute_size > 0) { + int start = 0; + int end = 0; + std::vector attribute_data(attribute_size); + ::ImmGetCompositionString(imm_context, GCS_COMPATTR, attribute_data.data(), + attribute_size); + for (start = 0; start < attribute_size; ++start) { + if (IsTargetAttribute(attribute_data[start])) break; + } + for (end = start; end < attribute_size; ++end) { + if (!IsTargetAttribute(attribute_data[end])) break; + } + if (start == attribute_size) { + // This composition clause does not contain any target clauses, + // i.e. this clauses is an input clause. + // We treat the whole composition as a target clause. + start = 0; + end = attribute_size; + } + *target_start = start; + *target_end = end; + } +} +// Helper function for ImeInput::GetCompositionInfo() method, to get underlines +// information of the current composition string. +CompositionClauses GetCompositionClauses(HIMC imm_context, int target_start, + int target_end) { + CompositionClauses result; + int clause_size = + ::ImmGetCompositionString(imm_context, GCS_COMPCLAUSE, NULL, 0); + int clause_length = clause_size / sizeof(std::uint32_t); + if (clause_length) { + result.reserve(clause_length - 1); + std::vector clause_data(clause_length); + ::ImmGetCompositionString(imm_context, GCS_COMPCLAUSE, clause_data.data(), + clause_size); + for (int i = 0; i < clause_length - 1; ++i) { + CompositionClause clause; + clause.start = clause_data[i]; + clause.end = clause_data[i + 1]; + clause.target = false; + // Use thick underline for the target clause. + if (clause.start >= target_start && clause.end <= target_end) { + clause.target = true; + } + result.push_back(clause); + } + } + return result; +} + +std::wstring GetString(HIMC imm_context) { + LONG string_size = + ::ImmGetCompositionString(imm_context, GCS_COMPSTR, NULL, 0); + std::wstring result((string_size / sizeof(wchar_t)), 0); + ::ImmGetCompositionString(imm_context, GCS_COMPSTR, result.data(), + string_size); + return result; +} + +std::wstring GetResultString(HIMC imm_context) { + LONG string_size = + ::ImmGetCompositionString(imm_context, GCS_RESULTSTR, NULL, 0); + std::wstring result((string_size / sizeof(wchar_t)), 0); + ::ImmGetCompositionString(imm_context, GCS_RESULTSTR, result.data(), + string_size); + return result; +} + +CompositionText GetCompositionInfo(HIMC imm_context) { + // We only care about GCS_COMPATTR, GCS_COMPCLAUSE and GCS_CURSORPOS, and + // convert them into underlines and selection range respectively. + + auto w_text = GetString(imm_context); + + int w_length = static_cast(w_text.length()); + // Find out the range selected by the user. + int w_target_start = w_length; + int w_target_end = w_length; + GetCompositionTargetRange(imm_context, &w_target_start, &w_target_end); + + auto clauses = + GetCompositionClauses(imm_context, w_target_start, w_target_end); + + int w_cursor = ::ImmGetCompositionString(imm_context, GCS_CURSORPOS, NULL, 0); + + auto text = platform::win::ToUtf8String(w_text); + for (auto& clause : clauses) { + clause.start = platform::win::IndexUtf16ToUtf8(w_text, clause.start, text); + clause.end = platform::win::IndexUtf16ToUtf8(w_text, clause.end, text); + } + int cursor = platform::win::IndexUtf16ToUtf8(w_text, w_cursor, text); + + return CompositionText{std::move(text), std::move(clauses), + TextRange{cursor}}; +} + +} // namespace + +WinInputMethodContext::WinInputMethodContext( + gsl::not_null window) + : native_window_resolver_(window->GetResolver()) { + event_revoker_guards_.push_back( + EventRevokerGuard(window->NativeMessageEvent()->AddHandler( + std::bind(&WinInputMethodContext::OnWindowNativeMessage, this, + std::placeholders::_1)))); +} + +WinInputMethodContext::~WinInputMethodContext() {} + +void WinInputMethodContext::EnableIME() { + const auto native_window = Resolve(native_window_resolver_.get()); + if (native_window == nullptr) return; + const auto hwnd = native_window->GetWindowHandle(); + + if (::ImmAssociateContextEx(hwnd, nullptr, IACE_DEFAULT) == FALSE) { + log::Warn("WinInputMethodContext: Failed to enable ime."); + } +} + +void WinInputMethodContext::DisableIME() { + const auto native_window = Resolve(native_window_resolver_.get()); + if (native_window == nullptr) return; + const auto hwnd = native_window->GetWindowHandle(); + + AutoHIMC himc{hwnd}; + + if (!::ImmNotifyIME(himc.Get(), NI_COMPOSITIONSTR, CPS_COMPLETE, 0)) { + log::Warn( + "WinInputMethodContext: Failed to complete composition before disable " + "ime."); + } + + if (::ImmAssociateContextEx(hwnd, nullptr, 0) == FALSE) { + log::Warn("WinInputMethodContext: Failed to disable ime."); + } +} + +void WinInputMethodContext::CompleteComposition() { + auto optional_himc = TryGetHIMC(); + if (!optional_himc.has_value()) return; + auto himc = *std::move(optional_himc); + + if (!::ImmNotifyIME(himc.Get(), NI_COMPOSITIONSTR, CPS_COMPLETE, 0)) { + log::Warn("WinInputMethodContext: Failed to complete composition."); + } +} + +void WinInputMethodContext::CancelComposition() { + auto optional_himc = TryGetHIMC(); + if (!optional_himc.has_value()) return; + auto himc = *std::move(optional_himc); + + if (!::ImmNotifyIME(himc.Get(), NI_COMPOSITIONSTR, CPS_CANCEL, 0)) { + log::Warn("WinInputMethodContext: Failed to complete composition."); + } +} + +CompositionText WinInputMethodContext::GetCompositionText() { + auto optional_himc = TryGetHIMC(); + if (!optional_himc.has_value()) return CompositionText{}; + auto himc = *std::move(optional_himc); + + return GetCompositionInfo(himc.Get()); +} + +void WinInputMethodContext::SetCandidateWindowPosition(const Point& point) { + auto optional_himc = TryGetHIMC(); + if (!optional_himc.has_value()) return; + auto himc = *std::move(optional_himc); + + ::CANDIDATEFORM form; + form.dwIndex = 1; + form.dwStyle = CFS_CANDIDATEPOS; + form.ptCurrentPos = DipToPi(point); + + if (!::ImmSetCandidateWindow(himc.Get(), &form)) + log::Debug( + "WinInputMethodContext: Failed to set input method candidate window " + "position."); +} + +IEvent* WinInputMethodContext::CompositionStartEvent() { + return &composition_start_event_; +} + +IEvent* WinInputMethodContext::CompositionEndEvent() { + return &composition_end_event_; +}; + +IEvent* WinInputMethodContext::CompositionEvent() { + return &composition_event_; +} + +IEvent* WinInputMethodContext::TextEvent() { + return &text_event_; +} + +void WinInputMethodContext::OnWindowNativeMessage( + WindowNativeMessageEventArgs& args) { + const auto& message = args.GetWindowMessage(); + switch (message.msg) { + case WM_CHAR: { + const auto c = static_cast(message.w_param); + if (platform::win::IsSurrogatePair(c)) { + // I don't think this will happen because normal key strike without ime + // should only trigger ascci character. If it is a charater from + // supplementary planes, it should be handled with ime messages. + log::Warn( + "WinInputMethodContext: A WM_CHAR message for character from " + "supplementary planes is ignored."); + } else { + wchar_t s[1] = {c}; + auto str = platform::win::ToUtf8String({s, 1}); + text_event_.Raise(str); + } + args.HandleWithResult(0); + break; + } + case WM_IME_COMPOSITION: { + composition_event_.Raise(nullptr); + auto composition_text = GetCompositionText(); + log::Debug( + "WinInputMethodContext: WM_IME_COMPOSITION composition text:\n{}", + composition_text); + if (message.l_param & GCS_RESULTSTR) { + auto result_string = GetResultString(); + text_event_.Raise(result_string); + } + break; + } + case WM_IME_STARTCOMPOSITION: { + composition_start_event_.Raise(nullptr); + break; + } + case WM_IME_ENDCOMPOSITION: { + composition_end_event_.Raise(nullptr); + break; + } + } +} + +std::string WinInputMethodContext::GetResultString() { + auto optional_himc = TryGetHIMC(); + if (!optional_himc.has_value()) return ""; + auto himc = *std::move(optional_himc); + + auto w_result = win::GetResultString(himc.Get()); + return platform::win::ToUtf8String(w_result); +} + +std::optional WinInputMethodContext::TryGetHIMC() { + const auto native_window = Resolve(native_window_resolver_.get()); + if (native_window == nullptr) return std::nullopt; + const auto hwnd = native_window->GetWindowHandle(); + return AutoHIMC{hwnd}; +} + +WinInputMethodManager::WinInputMethodManager(WinUiApplication*) {} + +WinInputMethodManager::~WinInputMethodManager() {} + +std::unique_ptr WinInputMethodManager::GetContext( + INativeWindow* window) { + Expects(window); + const auto w = CheckPlatform(window, GetPlatformId()); + return std::make_unique(w); +} +} // namespace cru::platform::native::win diff --git a/src/win/native/UiApplication.cpp b/src/win/native/UiApplication.cpp new file mode 100644 index 00000000..dbf52e05 --- /dev/null +++ b/src/win/native/UiApplication.cpp @@ -0,0 +1,124 @@ +#include "cru/win/native/UiApplication.hpp" + +#include "../DebugLogger.hpp" +#include "cru/common/Logger.hpp" +#include "cru/platform/Check.hpp" +#include "cru/win/graph/direct/Factory.hpp" +#include "cru/win/native/Cursor.hpp" +#include "cru/win/native/Exception.hpp" +#include "cru/win/native/GodWindow.hpp" +#include "cru/win/native/InputMethod.hpp" +#include "cru/win/native/Window.hpp" +#include "GodWindowMessage.hpp" +#include "Timer.hpp" +#include "WindowManager.hpp" + +namespace cru::platform::native { +std::unique_ptr CreateUiApplication() { + return std::make_unique(); +} +} // namespace cru::platform::native + +namespace cru::platform::native::win { +WinUiApplication* WinUiApplication::instance = nullptr; + +WinUiApplication::WinUiApplication() { + instance = this; + + instance_handle_ = ::GetModuleHandleW(nullptr); + if (!instance_handle_) + throw Win32Error("Failed to get module(instance) handle."); + + log::Logger::GetInstance()->AddSource( + std::make_unique<::cru::platform::win::WinDebugLoggerSource>()); + + graph_factory_ = + std::make_unique(); + + god_window_ = std::make_unique(this); + timer_manager_ = std::make_unique(god_window_.get()); + window_manager_ = std::make_unique(this); + cursor_manager_ = std::make_unique(); + input_method_manager_ = std::make_unique(this); +} + +WinUiApplication::~WinUiApplication() { instance = nullptr; } + +int WinUiApplication::Run() { + MSG msg; + while (GetMessageW(&msg, nullptr, 0, 0)) { + TranslateMessage(&msg); + DispatchMessageW(&msg); + } + + for (const auto& handler : quit_handlers_) handler(); + + return static_cast(msg.wParam); +} + +void WinUiApplication::RequestQuit(const int quit_code) { + ::PostQuitMessage(quit_code); +} + +void WinUiApplication::AddOnQuitHandler(std::function handler) { + quit_handlers_.push_back(std::move(handler)); +} + +void WinUiApplication::InvokeLater(std::function action) { + // copy the action to a safe place + auto p_action_copy = new std::function(std::move(action)); + + if (::PostMessageW(GetGodWindow()->GetHandle(), invoke_later_message_id, + reinterpret_cast(p_action_copy), 0) == 0) + throw Win32Error(::GetLastError(), "InvokeLater failed to post message."); +} + +long long WinUiApplication::SetTimeout(std::chrono::milliseconds milliseconds, + std::function action) { + return gsl::narrow(timer_manager_->CreateTimer( + static_cast(milliseconds.count()), false, std::move(action))); +} + +long long WinUiApplication::SetInterval(std::chrono::milliseconds milliseconds, + std::function action) { + return gsl::narrow(timer_manager_->CreateTimer( + static_cast(milliseconds.count()), true, std::move(action))); +} + +void WinUiApplication::CancelTimer(long long id) { + if (id < 0) return; + timer_manager_->KillTimer(static_cast(id)); +} + +std::vector WinUiApplication::GetAllWindow() { + const auto&& windows = window_manager_->GetAllWindows(); + std::vector result; + for (const auto w : windows) { + result.push_back(static_cast(w)); + } + return result; +} + +std::shared_ptr WinUiApplication::CreateWindow( + INativeWindow* parent) { + WinNativeWindow* p = nullptr; + if (parent != nullptr) { + p = CheckPlatform(parent, GetPlatformId()); + } + return (new WinNativeWindow(this, window_manager_->GetGeneralWindowClass(), + WS_OVERLAPPEDWINDOW, p)) + ->GetResolver(); +} + +cru::platform::graph::IGraphFactory* WinUiApplication::GetGraphFactory() { + return graph_factory_.get(); +} + +ICursorManager* WinUiApplication::GetCursorManager() { + return cursor_manager_.get(); +} + +IInputMethodManager* WinUiApplication::GetInputMethodManager() { + return input_method_manager_.get(); +} +} // namespace cru::platform::native::win diff --git a/src/win/native/WindowClass.cpp b/src/win/native/WindowClass.cpp new file mode 100644 index 00000000..2e74606e --- /dev/null +++ b/src/win/native/WindowClass.cpp @@ -0,0 +1,28 @@ +#include "cru/win/native/WindowClass.hpp" + +#include "cru/win/native/Exception.hpp" + +namespace cru::platform::native::win { +WindowClass::WindowClass(std::wstring name, WNDPROC window_proc, + HINSTANCE h_instance) + : name_(std::move(name)) { + WNDCLASSEXW window_class; + window_class.cbSize = sizeof(WNDCLASSEXW); + + window_class.style = CS_HREDRAW | CS_VREDRAW; + window_class.lpfnWndProc = window_proc; + window_class.cbClsExtra = 0; + window_class.cbWndExtra = 0; + window_class.hInstance = h_instance; + window_class.hIcon = LoadIcon(NULL, IDI_APPLICATION); + window_class.hCursor = LoadCursor(NULL, IDC_ARROW); + window_class.hbrBackground = GetSysColorBrush(COLOR_BTNFACE); + window_class.lpszMenuName = NULL; + window_class.lpszClassName = name_.c_str(); + window_class.hIconSm = NULL; + + atom_ = ::RegisterClassExW(&window_class); + if (atom_ == 0) + throw Win32Error(::GetLastError(), "Failed to create window class."); +} +} // namespace cru::platform::native::win diff --git a/src/win/native/WindowD2DPainter.cpp b/src/win/native/WindowD2DPainter.cpp new file mode 100644 index 00000000..7a97480b --- /dev/null +++ b/src/win/native/WindowD2DPainter.cpp @@ -0,0 +1,22 @@ +#include "WindowD2DPainter.hpp" + +#include "cru/win/graph/direct/Exception.hpp" +#include "cru/win/graph/direct/Factory.hpp" +#include "cru/win/native/WindowRenderTarget.hpp" + +namespace cru::platform::native::win { +using namespace cru::platform::graph::win::direct; + +WindowD2DPainter::WindowD2DPainter(WindowRenderTarget* render_target) + : D2DPainter(render_target->GetD2D1DeviceContext()), + render_target_(render_target) { + render_target_->GetD2D1DeviceContext()->BeginDraw(); +} + +WindowD2DPainter::~WindowD2DPainter() { EndDraw(); } + +void WindowD2DPainter::DoEndDraw() { + ThrowIfFailed(render_target_->GetD2D1DeviceContext()->EndDraw()); + render_target_->Present(); +} +} // namespace cru::platform::native::win diff --git a/src/win/native/WindowD2DPainter.hpp b/src/win/native/WindowD2DPainter.hpp new file mode 100644 index 00000000..a638b77a --- /dev/null +++ b/src/win/native/WindowD2DPainter.hpp @@ -0,0 +1,21 @@ +#pragma once +#include "cru/win/graph/direct/Painter.hpp" +#include "cru/win/native/WindowRenderTarget.hpp" + +namespace cru::platform::native::win { +class WindowD2DPainter : public graph::win::direct::D2DPainter { + public: + explicit WindowD2DPainter(WindowRenderTarget* window); + + CRU_DELETE_COPY(WindowD2DPainter) + CRU_DELETE_MOVE(WindowD2DPainter) + + ~WindowD2DPainter() override; + + protected: + void DoEndDraw() override; + + private: + WindowRenderTarget* render_target_; +}; +} // namespace cru::platform::native::win diff --git a/src/win/native/WindowManager.cpp b/src/win/native/WindowManager.cpp new file mode 100644 index 00000000..56cc8981 --- /dev/null +++ b/src/win/native/WindowManager.cpp @@ -0,0 +1,56 @@ +#include "WindowManager.hpp" + +#include "cru/win/native/UiApplication.hpp" +#include "cru/win/native/Window.hpp" +#include "cru/win/native/WindowClass.hpp" + +namespace cru::platform::native::win { +LRESULT __stdcall GeneralWndProc(HWND hWnd, UINT Msg, WPARAM wParam, + LPARAM lParam) { + auto window = + WinUiApplication::GetInstance()->GetWindowManager()->FromHandle(hWnd); + + LRESULT result; + if (window != nullptr && + window->HandleNativeWindowMessage(hWnd, Msg, wParam, lParam, &result)) + return result; + + return DefWindowProc(hWnd, Msg, wParam, lParam); +} + +WindowManager::WindowManager(WinUiApplication* application) { + application_ = application; + general_window_class_ = std::make_unique( + L"CruUIWindowClass", GeneralWndProc, application->GetInstanceHandle()); +} + +WindowManager::~WindowManager() { + for (const auto& [key, window] : window_map_) delete window; +} + +void WindowManager::RegisterWindow(HWND hwnd, WinNativeWindow* window) { + Expects(window_map_.count(hwnd) == 0); // The hwnd is already in the map. + window_map_.emplace(hwnd, window); +} + +void WindowManager::UnregisterWindow(HWND hwnd) { + const auto find_result = window_map_.find(hwnd); + Expects(find_result != window_map_.end()); // The hwnd is not in the map. + window_map_.erase(find_result); + if (window_map_.empty()) application_->RequestQuit(0); +} + +WinNativeWindow* WindowManager::FromHandle(HWND hwnd) { + const auto find_result = window_map_.find(hwnd); + if (find_result == window_map_.end()) + return nullptr; + else + return find_result->second; +} + +std::vector WindowManager::GetAllWindows() const { + std::vector windows; + for (const auto& [key, value] : window_map_) windows.push_back(value); + return windows; +} +} // namespace cru::platform::native::win diff --git a/src/win/native/WindowManager.hpp b/src/win/native/WindowManager.hpp new file mode 100644 index 00000000..3f6387f7 --- /dev/null +++ b/src/win/native/WindowManager.hpp @@ -0,0 +1,51 @@ +#pragma once +#include "cru/win/WinPreConfig.hpp" + +#include "cru/common/Base.hpp" + +#include +#include +#include + +namespace cru::platform::native::win { +class WinUiApplication; +class WinNativeWindow; +class WindowClass; + +class WindowManager : public Object { + public: + WindowManager(WinUiApplication* application); + + CRU_DELETE_COPY(WindowManager) + CRU_DELETE_MOVE(WindowManager) + + ~WindowManager() override; + + // Get the general window class for creating ordinary window. + WindowClass* GetGeneralWindowClass() const { + return general_window_class_.get(); + } + + // Register a window newly created. + // This function adds the hwnd to hwnd-window map. + // It should be called immediately after a window was created. + void RegisterWindow(HWND hwnd, WinNativeWindow* window); + + // Unregister a window that is going to be destroyed. + // This function removes the hwnd from the hwnd-window map. + // It should be called immediately before a window is going to be destroyed, + void UnregisterWindow(HWND hwnd); + + // Return a pointer to the Window object related to the HWND or nullptr if the + // hwnd is not in the map. + WinNativeWindow* FromHandle(HWND hwnd); + + std::vector GetAllWindows() const; + + private: + WinUiApplication* application_; + + std::unique_ptr general_window_class_; + std::map window_map_; +}; +} // namespace cru::platform::native::win diff --git a/src/win/native/WindowRenderTarget.cpp b/src/win/native/WindowRenderTarget.cpp new file mode 100644 index 00000000..4a114ebf --- /dev/null +++ b/src/win/native/WindowRenderTarget.cpp @@ -0,0 +1,78 @@ +#include "cru/win/native/WindowRenderTarget.hpp" + +#include "cru/win/graph/direct/Exception.hpp" +#include "cru/win/graph/direct/Factory.hpp" +#include "DpiUtil.hpp" + +namespace cru::platform::native::win { +using namespace cru::platform::graph::win::direct; +WindowRenderTarget::WindowRenderTarget(DirectGraphFactory* factory, HWND hwnd) + : factory_(factory) { + Expects(factory); + + const auto d3d11_device = factory->GetD3D11Device(); + const auto dxgi_factory = factory->GetDxgiFactory(); + + d2d1_device_context_ = factory->CreateD2D1DeviceContext(); + + // Allocate a descriptor. + DXGI_SWAP_CHAIN_DESC1 swap_chain_desc; + swap_chain_desc.Width = 0; // use automatic sizing + swap_chain_desc.Height = 0; + swap_chain_desc.Format = + DXGI_FORMAT_B8G8R8A8_UNORM; // this is the most common swapchain format + swap_chain_desc.Stereo = false; + swap_chain_desc.SampleDesc.Count = 1; // don't use multi-sampling + swap_chain_desc.SampleDesc.Quality = 0; + swap_chain_desc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT; + swap_chain_desc.BufferCount = 2; // use double buffering to enable flip + swap_chain_desc.Scaling = DXGI_SCALING_NONE; + swap_chain_desc.SwapEffect = + DXGI_SWAP_EFFECT_FLIP_SEQUENTIAL; // all apps must use this SwapEffect + swap_chain_desc.AlphaMode = DXGI_ALPHA_MODE_UNSPECIFIED; + swap_chain_desc.Flags = 0; + + // Get the final swap chain for this window from the DXGI factory. + ThrowIfFailed(dxgi_factory->CreateSwapChainForHwnd( + d3d11_device, hwnd, &swap_chain_desc, nullptr, nullptr, + &dxgi_swap_chain_)); + + CreateTargetBitmap(); +} + +void WindowRenderTarget::ResizeBuffer(const int width, const int height) { + // In order to resize buffer, we need to untarget the buffer first. + d2d1_device_context_->SetTarget(nullptr); + target_bitmap_ = nullptr; + ThrowIfFailed(dxgi_swap_chain_->ResizeBuffers(0, width, height, + DXGI_FORMAT_UNKNOWN, 0)); + CreateTargetBitmap(); +} + +void WindowRenderTarget::Present() { + ThrowIfFailed(dxgi_swap_chain_->Present(1, 0)); +} + +void WindowRenderTarget::CreateTargetBitmap() { + Expects(target_bitmap_ == nullptr); // target bitmap must not exist. + + // Direct2D needs the dxgi version of the backbuffer surface pointer. + Microsoft::WRL::ComPtr dxgi_back_buffer; + ThrowIfFailed( + dxgi_swap_chain_->GetBuffer(0, IID_PPV_ARGS(&dxgi_back_buffer))); + + const auto dpi = GetDpi(); // TODO! DPI awareness. + + auto bitmap_properties = D2D1::BitmapProperties1( + D2D1_BITMAP_OPTIONS_TARGET | D2D1_BITMAP_OPTIONS_CANNOT_DRAW, + D2D1::PixelFormat(DXGI_FORMAT_B8G8R8A8_UNORM, D2D1_ALPHA_MODE_IGNORE), + dpi.x, dpi.y); + + // Get a D2D surface from the DXGI back buffer to use as the D2D render + // target. + ThrowIfFailed(d2d1_device_context_->CreateBitmapFromDxgiSurface( + dxgi_back_buffer.Get(), &bitmap_properties, &target_bitmap_)); + + d2d1_device_context_->SetTarget(target_bitmap_.Get()); +} +} // namespace cru::platform::native::win diff --git a/src/win/native/cursor.cpp b/src/win/native/cursor.cpp index 096f3fdf..ca8bb1cd 100644 --- a/src/win/native/cursor.cpp +++ b/src/win/native/cursor.cpp @@ -1,7 +1,7 @@ -#include "cru/win/native/cursor.hpp" +#include "cru/win/native/Cursor.hpp" -#include "cru/common/logger.hpp" -#include "cru/win/native/exception.hpp" +#include "cru/common/Logger.hpp" +#include "cru/win/native/Exception.hpp" #include diff --git a/src/win/native/dpi_util.hpp b/src/win/native/dpi_util.hpp deleted file mode 100644 index 07b89a95..00000000 --- a/src/win/native/dpi_util.hpp +++ /dev/null @@ -1,46 +0,0 @@ -#pragma once -#include "cru/platform/native/base.hpp" - -// The dpi awareness needs to be implemented in the future. Currently we use 96 -// as default. - -namespace cru::platform::native::win { -inline platform::native::Dpi GetDpi() { - return platform::native::Dpi{96.0f, 96.0f}; -} - -inline int DipToPixelInternal(const float dip, const float dpi) { - return static_cast(dip * dpi / 96.0f); -} - -inline int DipToPixelX(const float dip_x) { - return DipToPixelInternal(dip_x, GetDpi().x); -} - -inline int DipToPixelY(const float dip_y) { - return DipToPixelInternal(dip_y, GetDpi().y); -} - -inline float DipToPixelInternal(const int pixel, const float dpi) { - return static_cast(pixel) * 96.0f / dpi; -} - -inline float PixelToDipX(const int pixel_x) { - return DipToPixelInternal(pixel_x, GetDpi().x); -} - -inline float PixelToDipY(const int pixel_y) { - return DipToPixelInternal(pixel_y, GetDpi().y); -} - -inline Point PiToDip(const POINT& pi_point) { - return Point(PixelToDipX(pi_point.x), PixelToDipY(pi_point.y)); -} - -inline POINT DipToPi(const Point& dip_point) { - POINT result; - result.x = DipToPixelX(dip_point.x); - result.y = DipToPixelY(dip_point.y); - return result; -} -} // namespace cru::platform::native::win diff --git a/src/win/native/god_window.cpp b/src/win/native/god_window.cpp deleted file mode 100644 index 00577002..00000000 --- a/src/win/native/god_window.cpp +++ /dev/null @@ -1,82 +0,0 @@ -#include "cru/win/native/god_window.hpp" - -#include "cru/common/logger.hpp" -#include "cru/win/native/exception.hpp" -#include "cru/win/native/ui_application.hpp" -#include "cru/win/native/window_class.hpp" -#include "god_window_message.hpp" -#include "timer.hpp" - -namespace cru::platform::native::win { -constexpr auto god_window_class_name = L"GodWindowClass"; - -LRESULT CALLBACK GodWndProc(HWND hWnd, UINT uMsg, WPARAM wParam, - LPARAM lParam) { - const auto app = WinUiApplication::GetInstance(); - - if (app) { - LRESULT result; - const auto handled = app->GetGodWindow()->HandleGodWindowMessage( - hWnd, uMsg, wParam, lParam, &result); - if (handled) - return result; - else - return DefWindowProcW(hWnd, uMsg, wParam, lParam); - } else - return DefWindowProcW(hWnd, uMsg, wParam, lParam); -} - -GodWindow::GodWindow(WinUiApplication* application) { - application_ = application; - - const auto h_instance = application->GetInstanceHandle(); - - god_window_class_ = std::make_unique(god_window_class_name, - GodWndProc, h_instance); - - hwnd_ = CreateWindowEx(0, god_window_class_name, L"", 0, CW_USEDEFAULT, - CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, - HWND_MESSAGE, nullptr, h_instance, nullptr); - - if (hwnd_ == nullptr) - throw Win32Error(::GetLastError(), "Failed to create god window."); -} - -GodWindow::~GodWindow() { - if (!::DestroyWindow(hwnd_)) { - // Although this could be "safely" ignore. - log::Warn("Failed to destroy god window."); - } -} - -bool GodWindow::HandleGodWindowMessage(HWND hwnd, UINT msg, WPARAM w_param, - LPARAM l_param, LRESULT* result) { - CRU_UNUSED(hwnd) - CRU_UNUSED(l_param) - - switch (msg) { - case invoke_later_message_id: { - const auto p_action = reinterpret_cast*>(w_param); - (*p_action)(); - delete p_action; - *result = 0; - return true; - } - case WM_TIMER: { - const auto id = static_cast(w_param); - const auto action = application_->GetTimerManager()->GetAction(id); - if (action.has_value()) { - (action.value().second)(); - if (!action.value().first) - application_->GetTimerManager()->KillTimer(id); - result = 0; - return true; - } - break; - } - default: - return false; - } - return false; -} -} // namespace cru::platform::native::win diff --git a/src/win/native/god_window_message.hpp b/src/win/native/god_window_message.hpp deleted file mode 100644 index 591270d9..00000000 --- a/src/win/native/god_window_message.hpp +++ /dev/null @@ -1,6 +0,0 @@ -#pragma once -#include "cru/win/win_pre_config.hpp" - -namespace cru::platform::native::win { -constexpr int invoke_later_message_id = WM_USER + 2000; -} diff --git a/src/win/native/input_method.cpp b/src/win/native/input_method.cpp deleted file mode 100644 index dba2b1eb..00000000 --- a/src/win/native/input_method.cpp +++ /dev/null @@ -1,324 +0,0 @@ -#include "cru/win/native/input_method.hpp" - -#include "cru/common/logger.hpp" -#include "cru/platform/check.hpp" -#include "cru/win/exception.hpp" -#include "cru/win/native/window.hpp" -#include "cru/win/string.hpp" -#include "dpi_util.hpp" - -#include - -namespace cru::platform::native::win { -AutoHIMC::AutoHIMC(HWND hwnd) : hwnd_(hwnd) { - Expects(hwnd); - handle_ = ::ImmGetContext(hwnd); -} - -AutoHIMC::AutoHIMC(AutoHIMC&& other) - : hwnd_(other.hwnd_), handle_(other.handle_) { - other.hwnd_ = nullptr; - other.handle_ = nullptr; -} - -AutoHIMC& AutoHIMC::operator=(AutoHIMC&& other) { - if (this != &other) { - Object::operator=(std::move(other)); - this->hwnd_ = other.hwnd_; - this->handle_ = other.handle_; - other.hwnd_ = nullptr; - other.handle_ = nullptr; - } - return *this; -} - -AutoHIMC::~AutoHIMC() { - if (handle_) { - if (!::ImmReleaseContext(hwnd_, handle_)) - log::Warn("AutoHIMC: Failed to release HIMC."); - } -} - -// copied from chromium -namespace { -// Determines whether or not the given attribute represents a target -// (a.k.a. a selection). -bool IsTargetAttribute(char attribute) { - return (attribute == ATTR_TARGET_CONVERTED || - attribute == ATTR_TARGET_NOTCONVERTED); -} -// Helper function for ImeInput::GetCompositionInfo() method, to get the target -// range that's selected by the user in the current composition string. -void GetCompositionTargetRange(HIMC imm_context, int* target_start, - int* target_end) { - int attribute_size = - ::ImmGetCompositionString(imm_context, GCS_COMPATTR, NULL, 0); - if (attribute_size > 0) { - int start = 0; - int end = 0; - std::vector attribute_data(attribute_size); - ::ImmGetCompositionString(imm_context, GCS_COMPATTR, attribute_data.data(), - attribute_size); - for (start = 0; start < attribute_size; ++start) { - if (IsTargetAttribute(attribute_data[start])) break; - } - for (end = start; end < attribute_size; ++end) { - if (!IsTargetAttribute(attribute_data[end])) break; - } - if (start == attribute_size) { - // This composition clause does not contain any target clauses, - // i.e. this clauses is an input clause. - // We treat the whole composition as a target clause. - start = 0; - end = attribute_size; - } - *target_start = start; - *target_end = end; - } -} -// Helper function for ImeInput::GetCompositionInfo() method, to get underlines -// information of the current composition string. -CompositionClauses GetCompositionClauses(HIMC imm_context, int target_start, - int target_end) { - CompositionClauses result; - int clause_size = - ::ImmGetCompositionString(imm_context, GCS_COMPCLAUSE, NULL, 0); - int clause_length = clause_size / sizeof(std::uint32_t); - if (clause_length) { - result.reserve(clause_length - 1); - std::vector clause_data(clause_length); - ::ImmGetCompositionString(imm_context, GCS_COMPCLAUSE, clause_data.data(), - clause_size); - for (int i = 0; i < clause_length - 1; ++i) { - CompositionClause clause; - clause.start = clause_data[i]; - clause.end = clause_data[i + 1]; - clause.target = false; - // Use thick underline for the target clause. - if (clause.start >= target_start && clause.end <= target_end) { - clause.target = true; - } - result.push_back(clause); - } - } - return result; -} - -std::wstring GetString(HIMC imm_context) { - LONG string_size = - ::ImmGetCompositionString(imm_context, GCS_COMPSTR, NULL, 0); - std::wstring result((string_size / sizeof(wchar_t)), 0); - ::ImmGetCompositionString(imm_context, GCS_COMPSTR, result.data(), - string_size); - return result; -} - -std::wstring GetResultString(HIMC imm_context) { - LONG string_size = - ::ImmGetCompositionString(imm_context, GCS_RESULTSTR, NULL, 0); - std::wstring result((string_size / sizeof(wchar_t)), 0); - ::ImmGetCompositionString(imm_context, GCS_RESULTSTR, result.data(), - string_size); - return result; -} - -CompositionText GetCompositionInfo(HIMC imm_context) { - // We only care about GCS_COMPATTR, GCS_COMPCLAUSE and GCS_CURSORPOS, and - // convert them into underlines and selection range respectively. - - auto w_text = GetString(imm_context); - - int w_length = static_cast(w_text.length()); - // Find out the range selected by the user. - int w_target_start = w_length; - int w_target_end = w_length; - GetCompositionTargetRange(imm_context, &w_target_start, &w_target_end); - - auto clauses = - GetCompositionClauses(imm_context, w_target_start, w_target_end); - - int w_cursor = ::ImmGetCompositionString(imm_context, GCS_CURSORPOS, NULL, 0); - - auto text = platform::win::ToUtf8String(w_text); - for (auto& clause : clauses) { - clause.start = platform::win::IndexUtf16ToUtf8(w_text, clause.start, text); - clause.end = platform::win::IndexUtf16ToUtf8(w_text, clause.end, text); - } - int cursor = platform::win::IndexUtf16ToUtf8(w_text, w_cursor, text); - - return CompositionText{std::move(text), std::move(clauses), - TextRange{cursor}}; -} - -} // namespace - -WinInputMethodContext::WinInputMethodContext( - gsl::not_null window) - : native_window_resolver_(window->GetResolver()) { - event_revoker_guards_.push_back( - EventRevokerGuard(window->NativeMessageEvent()->AddHandler( - std::bind(&WinInputMethodContext::OnWindowNativeMessage, this, - std::placeholders::_1)))); -} - -WinInputMethodContext::~WinInputMethodContext() {} - -void WinInputMethodContext::EnableIME() { - const auto native_window = Resolve(native_window_resolver_.get()); - if (native_window == nullptr) return; - const auto hwnd = native_window->GetWindowHandle(); - - if (::ImmAssociateContextEx(hwnd, nullptr, IACE_DEFAULT) == FALSE) { - log::Warn("WinInputMethodContext: Failed to enable ime."); - } -} - -void WinInputMethodContext::DisableIME() { - const auto native_window = Resolve(native_window_resolver_.get()); - if (native_window == nullptr) return; - const auto hwnd = native_window->GetWindowHandle(); - - AutoHIMC himc{hwnd}; - - if (!::ImmNotifyIME(himc.Get(), NI_COMPOSITIONSTR, CPS_COMPLETE, 0)) { - log::Warn( - "WinInputMethodContext: Failed to complete composition before disable " - "ime."); - } - - if (::ImmAssociateContextEx(hwnd, nullptr, 0) == FALSE) { - log::Warn("WinInputMethodContext: Failed to disable ime."); - } -} - -void WinInputMethodContext::CompleteComposition() { - auto optional_himc = TryGetHIMC(); - if (!optional_himc.has_value()) return; - auto himc = *std::move(optional_himc); - - if (!::ImmNotifyIME(himc.Get(), NI_COMPOSITIONSTR, CPS_COMPLETE, 0)) { - log::Warn("WinInputMethodContext: Failed to complete composition."); - } -} - -void WinInputMethodContext::CancelComposition() { - auto optional_himc = TryGetHIMC(); - if (!optional_himc.has_value()) return; - auto himc = *std::move(optional_himc); - - if (!::ImmNotifyIME(himc.Get(), NI_COMPOSITIONSTR, CPS_CANCEL, 0)) { - log::Warn("WinInputMethodContext: Failed to complete composition."); - } -} - -CompositionText WinInputMethodContext::GetCompositionText() { - auto optional_himc = TryGetHIMC(); - if (!optional_himc.has_value()) return CompositionText{}; - auto himc = *std::move(optional_himc); - - return GetCompositionInfo(himc.Get()); -} - -void WinInputMethodContext::SetCandidateWindowPosition(const Point& point) { - auto optional_himc = TryGetHIMC(); - if (!optional_himc.has_value()) return; - auto himc = *std::move(optional_himc); - - ::CANDIDATEFORM form; - form.dwIndex = 1; - form.dwStyle = CFS_CANDIDATEPOS; - form.ptCurrentPos = DipToPi(point); - - if (!::ImmSetCandidateWindow(himc.Get(), &form)) - log::Debug( - "WinInputMethodContext: Failed to set input method candidate window " - "position."); -} - -IEvent* WinInputMethodContext::CompositionStartEvent() { - return &composition_start_event_; -} - -IEvent* WinInputMethodContext::CompositionEndEvent() { - return &composition_end_event_; -}; - -IEvent* WinInputMethodContext::CompositionEvent() { - return &composition_event_; -} - -IEvent* WinInputMethodContext::TextEvent() { - return &text_event_; -} - -void WinInputMethodContext::OnWindowNativeMessage( - WindowNativeMessageEventArgs& args) { - const auto& message = args.GetWindowMessage(); - switch (message.msg) { - case WM_CHAR: { - const auto c = static_cast(message.w_param); - if (platform::win::IsSurrogatePair(c)) { - // I don't think this will happen because normal key strike without ime - // should only trigger ascci character. If it is a charater from - // supplementary planes, it should be handled with ime messages. - log::Warn( - "WinInputMethodContext: A WM_CHAR message for character from " - "supplementary planes is ignored."); - } else { - wchar_t s[1] = {c}; - auto str = platform::win::ToUtf8String({s, 1}); - text_event_.Raise(str); - } - args.HandleWithResult(0); - break; - } - case WM_IME_COMPOSITION: { - composition_event_.Raise(nullptr); - auto composition_text = GetCompositionText(); - log::Debug( - "WinInputMethodContext: WM_IME_COMPOSITION composition text:\n{}", - composition_text); - if (message.l_param & GCS_RESULTSTR) { - auto result_string = GetResultString(); - text_event_.Raise(result_string); - } - break; - } - case WM_IME_STARTCOMPOSITION: { - composition_start_event_.Raise(nullptr); - break; - } - case WM_IME_ENDCOMPOSITION: { - composition_end_event_.Raise(nullptr); - break; - } - } -} - -std::string WinInputMethodContext::GetResultString() { - auto optional_himc = TryGetHIMC(); - if (!optional_himc.has_value()) return ""; - auto himc = *std::move(optional_himc); - - auto w_result = win::GetResultString(himc.Get()); - return platform::win::ToUtf8String(w_result); -} - -std::optional WinInputMethodContext::TryGetHIMC() { - const auto native_window = Resolve(native_window_resolver_.get()); - if (native_window == nullptr) return std::nullopt; - const auto hwnd = native_window->GetWindowHandle(); - return AutoHIMC{hwnd}; -} - -WinInputMethodManager::WinInputMethodManager(WinUiApplication*) {} - -WinInputMethodManager::~WinInputMethodManager() {} - -std::unique_ptr WinInputMethodManager::GetContext( - INativeWindow* window) { - Expects(window); - const auto w = CheckPlatform(window, GetPlatformId()); - return std::make_unique(w); -} -} // namespace cru::platform::native::win diff --git a/src/win/native/keyboard.cpp b/src/win/native/keyboard.cpp index 98a95778..aa22e4a4 100644 --- a/src/win/native/keyboard.cpp +++ b/src/win/native/keyboard.cpp @@ -1,4 +1,4 @@ -#include "cru/win/native/keyboard.hpp" +#include "cru/win/native/Keyboard.hpp" namespace cru::platform::native::win { KeyCode VirtualKeyToKeyCode(int virtual_key) { diff --git a/src/win/native/timer.cpp b/src/win/native/timer.cpp index 66743963..662067fb 100644 --- a/src/win/native/timer.cpp +++ b/src/win/native/timer.cpp @@ -1,4 +1,4 @@ -#include "timer.hpp" +#include "Timer.hpp" namespace cru::platform::native::win { TimerManager::TimerManager(GodWindow* god_window) { god_window_ = god_window; } diff --git a/src/win/native/timer.hpp b/src/win/native/timer.hpp index 6c4871dd..95f186a1 100644 --- a/src/win/native/timer.hpp +++ b/src/win/native/timer.hpp @@ -1,8 +1,8 @@ #pragma once -#include "cru/win/win_pre_config.hpp" +#include "cru/win/WinPreConfig.hpp" -#include "cru/common/base.hpp" -#include "cru/win/native/god_window.hpp" +#include "cru/common/Base.hpp" +#include "cru/win/native/GodWindow.hpp" #include #include diff --git a/src/win/native/ui_application.cpp b/src/win/native/ui_application.cpp deleted file mode 100644 index 599ecadc..00000000 --- a/src/win/native/ui_application.cpp +++ /dev/null @@ -1,124 +0,0 @@ -#include "cru/win/native/ui_application.hpp" - -#include "../debug_logger.hpp" -#include "cru/common/logger.hpp" -#include "cru/platform/check.hpp" -#include "cru/win/graph/direct/factory.hpp" -#include "cru/win/native/cursor.hpp" -#include "cru/win/native/exception.hpp" -#include "cru/win/native/god_window.hpp" -#include "cru/win/native/input_method.hpp" -#include "cru/win/native/window.hpp" -#include "god_window_message.hpp" -#include "timer.hpp" -#include "window_manager.hpp" - -namespace cru::platform::native { -std::unique_ptr CreateUiApplication() { - return std::make_unique(); -} -} // namespace cru::platform::native - -namespace cru::platform::native::win { -WinUiApplication* WinUiApplication::instance = nullptr; - -WinUiApplication::WinUiApplication() { - instance = this; - - instance_handle_ = ::GetModuleHandleW(nullptr); - if (!instance_handle_) - throw Win32Error("Failed to get module(instance) handle."); - - log::Logger::GetInstance()->AddSource( - std::make_unique<::cru::platform::win::WinDebugLoggerSource>()); - - graph_factory_ = - std::make_unique(); - - god_window_ = std::make_unique(this); - timer_manager_ = std::make_unique(god_window_.get()); - window_manager_ = std::make_unique(this); - cursor_manager_ = std::make_unique(); - input_method_manager_ = std::make_unique(this); -} - -WinUiApplication::~WinUiApplication() { instance = nullptr; } - -int WinUiApplication::Run() { - MSG msg; - while (GetMessageW(&msg, nullptr, 0, 0)) { - TranslateMessage(&msg); - DispatchMessageW(&msg); - } - - for (const auto& handler : quit_handlers_) handler(); - - return static_cast(msg.wParam); -} - -void WinUiApplication::RequestQuit(const int quit_code) { - ::PostQuitMessage(quit_code); -} - -void WinUiApplication::AddOnQuitHandler(std::function handler) { - quit_handlers_.push_back(std::move(handler)); -} - -void WinUiApplication::InvokeLater(std::function action) { - // copy the action to a safe place - auto p_action_copy = new std::function(std::move(action)); - - if (::PostMessageW(GetGodWindow()->GetHandle(), invoke_later_message_id, - reinterpret_cast(p_action_copy), 0) == 0) - throw Win32Error(::GetLastError(), "InvokeLater failed to post message."); -} - -long long WinUiApplication::SetTimeout(std::chrono::milliseconds milliseconds, - std::function action) { - return gsl::narrow(timer_manager_->CreateTimer( - static_cast(milliseconds.count()), false, std::move(action))); -} - -long long WinUiApplication::SetInterval(std::chrono::milliseconds milliseconds, - std::function action) { - return gsl::narrow(timer_manager_->CreateTimer( - static_cast(milliseconds.count()), true, std::move(action))); -} - -void WinUiApplication::CancelTimer(long long id) { - if (id < 0) return; - timer_manager_->KillTimer(static_cast(id)); -} - -std::vector WinUiApplication::GetAllWindow() { - const auto&& windows = window_manager_->GetAllWindows(); - std::vector result; - for (const auto w : windows) { - result.push_back(static_cast(w)); - } - return result; -} - -std::shared_ptr WinUiApplication::CreateWindow( - INativeWindow* parent) { - WinNativeWindow* p = nullptr; - if (parent != nullptr) { - p = CheckPlatform(parent, GetPlatformId()); - } - return (new WinNativeWindow(this, window_manager_->GetGeneralWindowClass(), - WS_OVERLAPPEDWINDOW, p)) - ->GetResolver(); -} - -cru::platform::graph::IGraphFactory* WinUiApplication::GetGraphFactory() { - return graph_factory_.get(); -} - -ICursorManager* WinUiApplication::GetCursorManager() { - return cursor_manager_.get(); -} - -IInputMethodManager* WinUiApplication::GetInputMethodManager() { - return input_method_manager_.get(); -} -} // namespace cru::platform::native::win diff --git a/src/win/native/window.cpp b/src/win/native/window.cpp index bed9a264..9dde1af3 100644 --- a/src/win/native/window.cpp +++ b/src/win/native/window.cpp @@ -1,17 +1,17 @@ -#include "cru/win/native/window.hpp" - -#include "cru/common/logger.hpp" -#include "cru/platform/check.hpp" -#include "cru/win/native/cursor.hpp" -#include "cru/win/native/exception.hpp" -#include "cru/win/native/keyboard.hpp" -#include "cru/win/native/ui_application.hpp" -#include "cru/win/native/window_class.hpp" -#include "cru/win/native/window_render_target.hpp" -#include "cru/win/string.hpp" -#include "dpi_util.hpp" -#include "window_d2d_painter.hpp" -#include "window_manager.hpp" +#include "cru/win/native/Window.hpp" + +#include "cru/common/Logger.hpp" +#include "cru/platform/Check.hpp" +#include "cru/win/native/Cursor.hpp" +#include "cru/win/native/Exception.hpp" +#include "cru/win/native/Keyboard.hpp" +#include "cru/win/native/UiApplication.hpp" +#include "cru/win/native/WindowClass.hpp" +#include "cru/win/native/WindowRenderTarget.hpp" +#include "cru/win/String.hpp" +#include "DpiUtil.hpp" +#include "WindowD2DPainter.hpp" +#include "WindowManager.hpp" #include #include diff --git a/src/win/native/window_class.cpp b/src/win/native/window_class.cpp deleted file mode 100644 index 11dc86aa..00000000 --- a/src/win/native/window_class.cpp +++ /dev/null @@ -1,28 +0,0 @@ -#include "cru/win/native/window_class.hpp" - -#include "cru/win/native/exception.hpp" - -namespace cru::platform::native::win { -WindowClass::WindowClass(std::wstring name, WNDPROC window_proc, - HINSTANCE h_instance) - : name_(std::move(name)) { - WNDCLASSEXW window_class; - window_class.cbSize = sizeof(WNDCLASSEXW); - - window_class.style = CS_HREDRAW | CS_VREDRAW; - window_class.lpfnWndProc = window_proc; - window_class.cbClsExtra = 0; - window_class.cbWndExtra = 0; - window_class.hInstance = h_instance; - window_class.hIcon = LoadIcon(NULL, IDI_APPLICATION); - window_class.hCursor = LoadCursor(NULL, IDC_ARROW); - window_class.hbrBackground = GetSysColorBrush(COLOR_BTNFACE); - window_class.lpszMenuName = NULL; - window_class.lpszClassName = name_.c_str(); - window_class.hIconSm = NULL; - - atom_ = ::RegisterClassExW(&window_class); - if (atom_ == 0) - throw Win32Error(::GetLastError(), "Failed to create window class."); -} -} // namespace cru::platform::native::win diff --git a/src/win/native/window_d2d_painter.cpp b/src/win/native/window_d2d_painter.cpp deleted file mode 100644 index 54343fbf..00000000 --- a/src/win/native/window_d2d_painter.cpp +++ /dev/null @@ -1,22 +0,0 @@ -#include "window_d2d_painter.hpp" - -#include "cru/win/graph/direct/exception.hpp" -#include "cru/win/graph/direct/factory.hpp" -#include "cru/win/native/window_render_target.hpp" - -namespace cru::platform::native::win { -using namespace cru::platform::graph::win::direct; - -WindowD2DPainter::WindowD2DPainter(WindowRenderTarget* render_target) - : D2DPainter(render_target->GetD2D1DeviceContext()), - render_target_(render_target) { - render_target_->GetD2D1DeviceContext()->BeginDraw(); -} - -WindowD2DPainter::~WindowD2DPainter() { EndDraw(); } - -void WindowD2DPainter::DoEndDraw() { - ThrowIfFailed(render_target_->GetD2D1DeviceContext()->EndDraw()); - render_target_->Present(); -} -} // namespace cru::platform::native::win diff --git a/src/win/native/window_d2d_painter.hpp b/src/win/native/window_d2d_painter.hpp deleted file mode 100644 index 40a5dee6..00000000 --- a/src/win/native/window_d2d_painter.hpp +++ /dev/null @@ -1,21 +0,0 @@ -#pragma once -#include "cru/win/graph/direct/painter.hpp" -#include "cru/win/native/window_render_target.hpp" - -namespace cru::platform::native::win { -class WindowD2DPainter : public graph::win::direct::D2DPainter { - public: - explicit WindowD2DPainter(WindowRenderTarget* window); - - CRU_DELETE_COPY(WindowD2DPainter) - CRU_DELETE_MOVE(WindowD2DPainter) - - ~WindowD2DPainter() override; - - protected: - void DoEndDraw() override; - - private: - WindowRenderTarget* render_target_; -}; -} // namespace cru::platform::native::win diff --git a/src/win/native/window_manager.cpp b/src/win/native/window_manager.cpp deleted file mode 100644 index 205a139c..00000000 --- a/src/win/native/window_manager.cpp +++ /dev/null @@ -1,56 +0,0 @@ -#include "window_manager.hpp" - -#include "cru/win/native/ui_application.hpp" -#include "cru/win/native/window.hpp" -#include "cru/win/native/window_class.hpp" - -namespace cru::platform::native::win { -LRESULT __stdcall GeneralWndProc(HWND hWnd, UINT Msg, WPARAM wParam, - LPARAM lParam) { - auto window = - WinUiApplication::GetInstance()->GetWindowManager()->FromHandle(hWnd); - - LRESULT result; - if (window != nullptr && - window->HandleNativeWindowMessage(hWnd, Msg, wParam, lParam, &result)) - return result; - - return DefWindowProc(hWnd, Msg, wParam, lParam); -} - -WindowManager::WindowManager(WinUiApplication* application) { - application_ = application; - general_window_class_ = std::make_unique( - L"CruUIWindowClass", GeneralWndProc, application->GetInstanceHandle()); -} - -WindowManager::~WindowManager() { - for (const auto& [key, window] : window_map_) delete window; -} - -void WindowManager::RegisterWindow(HWND hwnd, WinNativeWindow* window) { - Expects(window_map_.count(hwnd) == 0); // The hwnd is already in the map. - window_map_.emplace(hwnd, window); -} - -void WindowManager::UnregisterWindow(HWND hwnd) { - const auto find_result = window_map_.find(hwnd); - Expects(find_result != window_map_.end()); // The hwnd is not in the map. - window_map_.erase(find_result); - if (window_map_.empty()) application_->RequestQuit(0); -} - -WinNativeWindow* WindowManager::FromHandle(HWND hwnd) { - const auto find_result = window_map_.find(hwnd); - if (find_result == window_map_.end()) - return nullptr; - else - return find_result->second; -} - -std::vector WindowManager::GetAllWindows() const { - std::vector windows; - for (const auto& [key, value] : window_map_) windows.push_back(value); - return windows; -} -} // namespace cru::platform::native::win diff --git a/src/win/native/window_manager.hpp b/src/win/native/window_manager.hpp deleted file mode 100644 index 677719aa..00000000 --- a/src/win/native/window_manager.hpp +++ /dev/null @@ -1,51 +0,0 @@ -#pragma once -#include "cru/win/win_pre_config.hpp" - -#include "cru/common/base.hpp" - -#include -#include -#include - -namespace cru::platform::native::win { -class WinUiApplication; -class WinNativeWindow; -class WindowClass; - -class WindowManager : public Object { - public: - WindowManager(WinUiApplication* application); - - CRU_DELETE_COPY(WindowManager) - CRU_DELETE_MOVE(WindowManager) - - ~WindowManager() override; - - // Get the general window class for creating ordinary window. - WindowClass* GetGeneralWindowClass() const { - return general_window_class_.get(); - } - - // Register a window newly created. - // This function adds the hwnd to hwnd-window map. - // It should be called immediately after a window was created. - void RegisterWindow(HWND hwnd, WinNativeWindow* window); - - // Unregister a window that is going to be destroyed. - // This function removes the hwnd from the hwnd-window map. - // It should be called immediately before a window is going to be destroyed, - void UnregisterWindow(HWND hwnd); - - // Return a pointer to the Window object related to the HWND or nullptr if the - // hwnd is not in the map. - WinNativeWindow* FromHandle(HWND hwnd); - - std::vector GetAllWindows() const; - - private: - WinUiApplication* application_; - - std::unique_ptr general_window_class_; - std::map window_map_; -}; -} // namespace cru::platform::native::win diff --git a/src/win/native/window_render_target.cpp b/src/win/native/window_render_target.cpp deleted file mode 100644 index eb6673c6..00000000 --- a/src/win/native/window_render_target.cpp +++ /dev/null @@ -1,78 +0,0 @@ -#include "cru/win/native/window_render_target.hpp" - -#include "cru/win/graph/direct/exception.hpp" -#include "cru/win/graph/direct/factory.hpp" -#include "dpi_util.hpp" - -namespace cru::platform::native::win { -using namespace cru::platform::graph::win::direct; -WindowRenderTarget::WindowRenderTarget(DirectGraphFactory* factory, HWND hwnd) - : factory_(factory) { - Expects(factory); - - const auto d3d11_device = factory->GetD3D11Device(); - const auto dxgi_factory = factory->GetDxgiFactory(); - - d2d1_device_context_ = factory->CreateD2D1DeviceContext(); - - // Allocate a descriptor. - DXGI_SWAP_CHAIN_DESC1 swap_chain_desc; - swap_chain_desc.Width = 0; // use automatic sizing - swap_chain_desc.Height = 0; - swap_chain_desc.Format = - DXGI_FORMAT_B8G8R8A8_UNORM; // this is the most common swapchain format - swap_chain_desc.Stereo = false; - swap_chain_desc.SampleDesc.Count = 1; // don't use multi-sampling - swap_chain_desc.SampleDesc.Quality = 0; - swap_chain_desc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT; - swap_chain_desc.BufferCount = 2; // use double buffering to enable flip - swap_chain_desc.Scaling = DXGI_SCALING_NONE; - swap_chain_desc.SwapEffect = - DXGI_SWAP_EFFECT_FLIP_SEQUENTIAL; // all apps must use this SwapEffect - swap_chain_desc.AlphaMode = DXGI_ALPHA_MODE_UNSPECIFIED; - swap_chain_desc.Flags = 0; - - // Get the final swap chain for this window from the DXGI factory. - ThrowIfFailed(dxgi_factory->CreateSwapChainForHwnd( - d3d11_device, hwnd, &swap_chain_desc, nullptr, nullptr, - &dxgi_swap_chain_)); - - CreateTargetBitmap(); -} - -void WindowRenderTarget::ResizeBuffer(const int width, const int height) { - // In order to resize buffer, we need to untarget the buffer first. - d2d1_device_context_->SetTarget(nullptr); - target_bitmap_ = nullptr; - ThrowIfFailed(dxgi_swap_chain_->ResizeBuffers(0, width, height, - DXGI_FORMAT_UNKNOWN, 0)); - CreateTargetBitmap(); -} - -void WindowRenderTarget::Present() { - ThrowIfFailed(dxgi_swap_chain_->Present(1, 0)); -} - -void WindowRenderTarget::CreateTargetBitmap() { - Expects(target_bitmap_ == nullptr); // target bitmap must not exist. - - // Direct2D needs the dxgi version of the backbuffer surface pointer. - Microsoft::WRL::ComPtr dxgi_back_buffer; - ThrowIfFailed( - dxgi_swap_chain_->GetBuffer(0, IID_PPV_ARGS(&dxgi_back_buffer))); - - const auto dpi = GetDpi(); // TODO! DPI awareness. - - auto bitmap_properties = D2D1::BitmapProperties1( - D2D1_BITMAP_OPTIONS_TARGET | D2D1_BITMAP_OPTIONS_CANNOT_DRAW, - D2D1::PixelFormat(DXGI_FORMAT_B8G8R8A8_UNORM, D2D1_ALPHA_MODE_IGNORE), - dpi.x, dpi.y); - - // Get a D2D surface from the DXGI back buffer to use as the D2D render - // target. - ThrowIfFailed(d2d1_device_context_->CreateBitmapFromDxgiSurface( - dxgi_back_buffer.Get(), &bitmap_properties, &target_bitmap_)); - - d2d1_device_context_->SetTarget(target_bitmap_.Get()); -} -} // namespace cru::platform::native::win diff --git a/src/win/string.cpp b/src/win/string.cpp index 5518e6af..65a280f2 100644 --- a/src/win/string.cpp +++ b/src/win/string.cpp @@ -1,6 +1,6 @@ -#include "cru/win/string.hpp" +#include "cru/win/String.hpp" -#include "cru/win/exception.hpp" +#include "cru/win/Exception.hpp" #include -- cgit v1.2.3