From d86a71f79afe0e4dac768f61d6bff690567aca5b Mon Sep 17 00:00:00 2001 From: crupest Date: Sun, 24 May 2020 01:40:02 +0800 Subject: ... --- 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 -------- 28 files changed, 888 insertions(+), 888 deletions(-) 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/native') 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 -- cgit v1.2.3