diff options
Diffstat (limited to 'src/ui/window.cpp')
-rw-r--r-- | src/ui/window.cpp | 1291 |
1 files changed, 604 insertions, 687 deletions
diff --git a/src/ui/window.cpp b/src/ui/window.cpp index 86fa4436..91319db7 100644 --- a/src/ui/window.cpp +++ b/src/ui/window.cpp @@ -1,790 +1,707 @@ #include "window.hpp" +#include <d2d1_1.h> +#include <windowsx.h> + #include "application.hpp" -#include "graph/graph.hpp" #include "exception.hpp" -#include "cursor.hpp" - -namespace cru::ui -{ - WindowClass::WindowClass(const String& name, WNDPROC window_proc, HINSTANCE h_instance) - : name_(name) - { - WNDCLASSEX window_class; - window_class.cbSize = sizeof(WNDCLASSEX); - - window_class.style = CS_HREDRAW | CS_VREDRAW; - window_class.lpfnWndProc = window_proc; - window_class.cbClsExtra = 0; - window_class.cbWndExtra = 0; - window_class.hInstance = h_instance; - window_class.hIcon = LoadIcon(NULL, IDI_APPLICATION); - window_class.hCursor = LoadCursor(NULL, IDC_ARROW); - window_class.hbrBackground = GetSysColorBrush(COLOR_BTNFACE); - window_class.lpszMenuName = NULL; - window_class.lpszClassName = name.c_str(); - window_class.hIconSm = NULL; - - atom_ = RegisterClassEx(&window_class); - if (atom_ == 0) - throw std::runtime_error("Failed to create window class."); - } - - LRESULT __stdcall GeneralWndProc(HWND hWnd, UINT Msg, WPARAM wParam, LPARAM lParam) { - auto window = WindowManager::GetInstance()->FromHandle(hWnd); - - LRESULT result; - if (window != nullptr && window->HandleWindowMessage(hWnd, Msg, wParam, lParam, result)) - return result; - - return DefWindowProc(hWnd, Msg, wParam, lParam); - } - - WindowManager* WindowManager::GetInstance() - { - return Application::GetInstance()->ResolveSingleton<WindowManager>([](auto) - { - return new WindowManager{}; - }); - } - - WindowManager::WindowManager() { - general_window_class_ = std::make_unique<WindowClass>( - L"CruUIWindowClass", - GeneralWndProc, - Application::GetInstance()->GetInstanceHandle() - ); - } - - void WindowManager::RegisterWindow(HWND hwnd, Window * window) { - const auto find_result = window_map_.find(hwnd); - if (find_result != window_map_.end()) - throw std::runtime_error("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); - if (find_result == window_map_.end()) - throw std::runtime_error("The hwnd is not in the map."); - window_map_.erase(find_result); - - if (window_map_.empty()) - Application::GetInstance()->Quit(0); - } - - Window* 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<Window*> WindowManager::GetAllWindows() const - { - std::vector<Window*> windows; - for (auto [key, value] : window_map_) - windows.push_back(value); - return windows; - } +#include "graph/graph_manager.hpp" +#include "graph/graph_util.hpp" +#include "graph/window_render_target.hpp" +#include "render/window_render_object.hpp" + +namespace cru::ui { +namespace { +// Dispatch the event. +// +// This will raise routed event of the control and its parent and parent's +// parent ... (until "last_receiver" if it's not nullptr) with appropriate args. +// +// First tunnel from top to bottom possibly stopped by "handled" flag in +// EventArgs. Second bubble from bottom to top possibly stopped by "handled" +// flag in EventArgs. Last direct to each control. +// +// Args is of type "EventArgs". The first init argument is "sender", which is +// automatically bound to each receiving control. The second init argument is +// "original_sender", which is unchanged. And "args" will be perfectly forwarded +// as the rest arguments. +template <typename EventArgs, typename... Args> +void DispatchEvent(Control* const original_sender, + events::RoutedEvent<EventArgs>* (Control::*event_ptr)(), + Control* const last_receiver, Args&&... args) { + std::list<Control*> receive_list; + + auto parent = original_sender; + while (parent != last_receiver) { + receive_list.push_back(parent); + parent = parent->GetParent(); + } + + auto handled = false; + + // tunnel + for (auto i = receive_list.crbegin(); i != receive_list.crend(); ++i) { + EventArgs event_args(*i, original_sender, std::forward<Args>(args)...); + (*i->*event_ptr)()->tunnel.Raise(event_args); + if (event_args.IsHandled()) { + handled = true; + break; + } + } + + // bubble + if (!handled) { + for (auto i : receive_list) { + EventArgs event_args(i, original_sender, std::forward<Args>(args)...); + (i->*event_ptr)()->bubble.Raise(event_args); + if (event_args.IsHandled()) break; + } + } + + // direct + for (auto i : receive_list) { + EventArgs event_args(i, original_sender, std::forward<Args>(args)...); + (i->*event_ptr)()->direct.Raise(event_args); + } +} - inline Point PiToDip(const POINT& pi_point) - { - return Point( - graph::PixelToDipX(pi_point.x), - graph::PixelToDipY(pi_point.y) - ); - } +std::list<Control*> GetAncestorList(Control* control) { + std::list<Control*> l; + while (control != nullptr) { + l.push_front(control); + control = control->GetParent(); + } + return l; +} - inline POINT DipToPi(const Point& dip_point) - { - POINT result; - result.x = graph::DipToPixelX(dip_point.x); - result.y = graph::DipToPixelY(dip_point.y); - return result; - } +Control* FindLowestCommonAncestor(Control* left, Control* right) { + if (left == nullptr || right == nullptr) return nullptr; + + auto&& left_list = GetAncestorList(left); + auto&& right_list = GetAncestorList(right); + + // the root is different + if (left_list.front() != right_list.front()) return nullptr; + + // find the last same control or the last control (one is ancestor of the + // other) + auto left_i = left_list.cbegin(); + auto right_i = right_list.cbegin(); + while (true) { + if (left_i == left_list.cend()) return *(--left_i); + if (right_i == right_list.cend()) return *(--right_i); + if (*left_i != *right_i) return *(--left_i); + ++left_i; + ++right_i; + } +} +} // namespace +LRESULT __stdcall GeneralWndProc(HWND hWnd, UINT Msg, WPARAM wParam, + LPARAM lParam) { + auto window = WindowManager::GetInstance()->FromHandle(hWnd); - namespace - { - Cursor::Ptr GetCursorInherit(Control* control) - { - while (control != nullptr) - { - const auto cursor = control->GetCursor(); - if (cursor != nullptr) - return cursor; - control = control->GetInternalParent(); - } - return cursors::arrow; - } - } + LRESULT result; + if (window != nullptr && + window->HandleWindowMessage(hWnd, Msg, wParam, lParam, result)) + return result; - Window* Window::CreateOverlapped() - { - return new Window(tag_overlapped_constructor{}); - } - - Window* Window::CreatePopup(Window* parent, const bool caption) - { - return new Window(tag_popup_constructor{}, parent, caption); - } + return DefWindowProc(hWnd, Msg, wParam, lParam); +} +WindowManager* WindowManager::GetInstance() { + return Application::GetInstance()->ResolveSingleton<WindowManager>( + [](auto) { return new WindowManager{}; }); +} - Window::Window(tag_overlapped_constructor) - { - BeforeCreateHwnd(); +WindowManager::WindowManager() { + general_window_class_ = std::make_unique<WindowClass>( + L"CruUIWindowClass", GeneralWndProc, + Application::GetInstance()->GetInstanceHandle()); +} - const auto window_manager = WindowManager::GetInstance(); +void WindowManager::RegisterWindow(HWND hwnd, Window* window) { + assert(window_map_.count(hwnd) == 0); // The hwnd is already in the map. + window_map_.emplace(hwnd, window); +} - hwnd_ = CreateWindowEx(0, - window_manager->GetGeneralWindowClass()->GetName(), - L"", WS_OVERLAPPEDWINDOW, - CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, - nullptr, nullptr, Application::GetInstance()->GetInstanceHandle(), nullptr - ); +void WindowManager::UnregisterWindow(HWND hwnd) { + const auto find_result = window_map_.find(hwnd); + assert(find_result != window_map_.end()); // The hwnd is not in the map. + window_map_.erase(find_result); + if (window_map_.empty()) Application::GetInstance()->Quit(0); +} - if (hwnd_ == nullptr) - throw std::runtime_error("Failed to create window."); +Window* 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; +} - AfterCreateHwnd(window_manager); - } +std::vector<Window*> WindowManager::GetAllWindows() const { + std::vector<Window*> windows; + for (auto [key, value] : window_map_) windows.push_back(value); + return windows; +} - Window::Window(tag_popup_constructor, Window* parent, const bool caption) - { - if (parent != nullptr && !parent->IsWindowValid()) - throw std::runtime_error("Parent window is not valid."); +inline Point PiToDip(const POINT& pi_point) { + return Point(graph::PixelToDipX(pi_point.x), graph::PixelToDipY(pi_point.y)); +} - BeforeCreateHwnd(); +inline POINT DipToPi(const Point& dip_point) { + POINT result; + result.x = graph::DipToPixelX(dip_point.x); + result.y = graph::DipToPixelY(dip_point.y); + return result; +} - parent_window_ = parent; +Window* Window::CreateOverlapped() { + return new Window(tag_overlapped_constructor{}); +} - const auto window_manager = WindowManager::GetInstance(); +Window* Window::CreatePopup(Window* parent, const bool caption) { + return new Window(tag_popup_constructor{}, parent, caption); +} - hwnd_ = CreateWindowEx(0, - window_manager->GetGeneralWindowClass()->GetName(), - L"", caption ? (WS_POPUPWINDOW | WS_CAPTION) : WS_POPUP, - CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, - parent == nullptr ? nullptr : parent->GetWindowHandle(), - nullptr, Application::GetInstance()->GetInstanceHandle(), nullptr - ); +Window::Window(tag_overlapped_constructor) { + BeforeCreateHwnd(); - if (hwnd_ == nullptr) - throw std::runtime_error("Failed to create window."); + const auto window_manager = WindowManager::GetInstance(); - AfterCreateHwnd(window_manager); - } + hwnd_ = + CreateWindowEx(0, window_manager->GetGeneralWindowClass()->GetName(), L"", + WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, + CW_USEDEFAULT, CW_USEDEFAULT, nullptr, nullptr, + Application::GetInstance()->GetInstanceHandle(), nullptr); - void Window::BeforeCreateHwnd() - { - window_ = this; - } + if (hwnd_ == nullptr) + throw Win32Error(::GetLastError(), "Failed to create window."); - void Window::AfterCreateHwnd(WindowManager* window_manager) - { - window_manager->RegisterWindow(hwnd_, this); + AfterCreateHwnd(window_manager); +} - render_target_ = graph::GraphManager::GetInstance()->CreateWindowRenderTarget(hwnd_); +Window::Window(tag_popup_constructor, Window* parent, const bool caption) { + assert(parent == nullptr || + parent->IsWindowValid()); // Parent window is not valid. - SetCursor(cursors::arrow); - } + BeforeCreateHwnd(); - Window::~Window() { - if (IsWindowValid()) - { - SetDeleteThisOnDestroy(false); // avoid double delete. - Close(); - } - TraverseDescendants([this](Control* control) { - control->OnDetachToWindow(this); - }); - } + parent_window_ = parent; - StringView Window::GetControlType() const - { - return control_type; - } + const auto window_manager = WindowManager::GetInstance(); - void Window::SetDeleteThisOnDestroy(bool value) - { - delete_this_on_destroy_ = value; - } + hwnd_ = CreateWindowEx( + 0, window_manager->GetGeneralWindowClass()->GetName(), L"", + caption ? (WS_POPUPWINDOW | WS_CAPTION) : WS_POPUP, CW_USEDEFAULT, + CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, + parent == nullptr ? nullptr : parent->GetWindowHandle(), nullptr, + Application::GetInstance()->GetInstanceHandle(), nullptr); - void Window::Close() { - if (IsWindowValid()) - DestroyWindow(hwnd_); - } + if (hwnd_ == nullptr) + throw Win32Error(::GetLastError(), "Failed to create window."); - void Window::InvalidateDraw() { - if (IsWindowValid()) { - InvalidateRect(hwnd_, nullptr, false); - } - } + AfterCreateHwnd(window_manager); +} - void Window::Show() { - if (IsWindowValid()) { - ShowWindow(hwnd_, SW_SHOWNORMAL); - } - } +void Window::BeforeCreateHwnd() { window_ = this; } - void Window::Hide() { - if (IsWindowValid()) { - ShowWindow(hwnd_, SW_HIDE); - } - } +void Window::AfterCreateHwnd(WindowManager* window_manager) { + window_manager->RegisterWindow(hwnd_, this); - Size Window::GetClientSize() { - if (!IsWindowValid()) - return Size(); + render_target_.reset( + new graph::WindowRenderTarget(graph::GraphManager::GetInstance(), hwnd_)); - const auto pixel_rect = GetClientRectPixel(); - return Size( - graph::PixelToDipX(pixel_rect.right), - graph::PixelToDipY(pixel_rect.bottom) - ); - } + render_object_.reset(new render::WindowRenderObject(this)); +} - void Window::SetClientSize(const Size & size) { - if (IsWindowValid()) { - const auto window_style = static_cast<DWORD>(GetWindowLongPtr(hwnd_, GWL_STYLE)); - const auto window_ex_style = static_cast<DWORD>(GetWindowLongPtr(hwnd_, GWL_EXSTYLE)); - - RECT rect; - rect.left = 0; - rect.top = 0; - rect.right = graph::DipToPixelX(size.width); - rect.bottom = graph::DipToPixelY(size.height); - AdjustWindowRectEx(&rect, window_style, FALSE, window_ex_style); - - SetWindowPos( - hwnd_, nullptr, 0, 0, - rect.right - rect.left, - rect.bottom - rect.top, - SWP_NOZORDER | SWP_NOMOVE - ); - } - } +Window::~Window() { + if (IsWindowValid()) { + SetDeleteThisOnDestroy(false); // avoid double delete. + Close(); + } + TraverseDescendants( + [this](Control* control) { control->OnDetachToWindow(this); }); +} - Rect Window::GetWindowRect() { - if (!IsWindowValid()) - return Rect(); +StringView Window::GetControlType() const { return control_type; } - RECT rect; - ::GetWindowRect(hwnd_, &rect); +render::RenderObject* Window::GetRenderObject() const { + return render_object_.get(); +} - return Rect::FromVertices( - graph::PixelToDipX(rect.left), - graph::PixelToDipY(rect.top), - graph::PixelToDipX(rect.right), - graph::PixelToDipY(rect.bottom) - ); - } +void Window::SetDeleteThisOnDestroy(bool value) { + delete_this_on_destroy_ = value; +} - void Window::SetWindowRect(const Rect & rect) { - if (IsWindowValid()) { - SetWindowPos( - hwnd_, nullptr, - graph::DipToPixelX(rect.left), - graph::DipToPixelY(rect.top), - graph::DipToPixelX(rect.GetRight()), - graph::DipToPixelY(rect.GetBottom()), - SWP_NOZORDER - ); - } - } +void Window::Close() { + if (IsWindowValid()) DestroyWindow(hwnd_); +} - void Window::SetWindowPosition(const Point& position) - { - if (IsWindowValid()) { - SetWindowPos( - hwnd_, nullptr, - graph::DipToPixelX(position.x), - graph::DipToPixelY(position.y), - 0, 0, - SWP_NOZORDER | SWP_NOSIZE - ); - } - } +void Window::InvalidateDraw() { + if (IsWindowValid()) { + InvalidateRect(hwnd_, nullptr, false); + } +} - Point Window::PointToScreen(const Point& point) - { - if (!IsWindowValid()) - return Point::Zero(); +void Window::Show() { + if (IsWindowValid()) { + ShowWindow(hwnd_, SW_SHOWNORMAL); + } +} - auto p = DipToPi(point); - if (::ClientToScreen(GetWindowHandle(), &p) == 0) - throw Win32Error(::GetLastError(), "Failed transform point from window to screen."); - return PiToDip(p); - } +void Window::Hide() { + if (IsWindowValid()) { + ShowWindow(hwnd_, SW_HIDE); + } +} - Point Window::PointFromScreen(const Point& point) - { - if (!IsWindowValid()) - return Point::Zero(); +Size Window::GetClientSize() { + if (!IsWindowValid()) return Size(); - auto p = DipToPi(point); - if (::ScreenToClient(GetWindowHandle(), &p) == 0) - throw Win32Error(::GetLastError(), "Failed transform point from screen to window."); - return PiToDip(p); - } + const auto pixel_rect = GetClientRectPixel(); + return Size(graph::PixelToDipX(pixel_rect.right), + graph::PixelToDipY(pixel_rect.bottom)); +} - bool Window::HandleWindowMessage(HWND hwnd, int msg, WPARAM w_param, LPARAM l_param, LRESULT & result) { - - events::WindowNativeMessageEventArgs args(this, this, {hwnd, msg, w_param, l_param}); - native_message_event.Raise(args); - if (args.GetResult().has_value()) - { - result = args.GetResult().value(); - return true; - } - - switch (msg) { - case WM_PAINT: - OnPaintInternal(); - result = 0; - return true; - case WM_ERASEBKGND: - result = 1; - return true; - case WM_SETFOCUS: - OnSetFocusInternal(); - result = 0; - return true; - case WM_KILLFOCUS: - OnKillFocusInternal(); - result = 0; - return true; - case WM_MOUSEMOVE: - { - POINT point; - point.x = GET_X_LPARAM(l_param); - point.y = GET_Y_LPARAM(l_param); - OnMouseMoveInternal(point); - result = 0; - return true; - } - case WM_LBUTTONDOWN: - { - POINT point; - point.x = GET_X_LPARAM(l_param); - point.y = GET_Y_LPARAM(l_param); - OnMouseDownInternal(MouseButton::Left, point); - result = 0; - return true; - } - case WM_LBUTTONUP: - { - POINT point; - point.x = GET_X_LPARAM(l_param); - point.y = GET_Y_LPARAM(l_param); - OnMouseUpInternal(MouseButton::Left, point); - result = 0; - return true; - } - case WM_RBUTTONDOWN: - { - POINT point; - point.x = GET_X_LPARAM(l_param); - point.y = GET_Y_LPARAM(l_param); - OnMouseDownInternal(MouseButton::Right, point); - result = 0; - return true; - } - case WM_RBUTTONUP: - { - POINT point; - point.x = GET_X_LPARAM(l_param); - point.y = GET_Y_LPARAM(l_param); - OnMouseUpInternal(MouseButton::Right, point); - result = 0; - return true; - } - case WM_MBUTTONDOWN: - { - POINT point; - point.x = GET_X_LPARAM(l_param); - point.y = GET_Y_LPARAM(l_param); - OnMouseDownInternal(MouseButton::Middle, point); - result = 0; - return true; - } - case WM_MBUTTONUP: - { - POINT point; - point.x = GET_X_LPARAM(l_param); - point.y = GET_Y_LPARAM(l_param); - OnMouseUpInternal(MouseButton::Middle, point); - result = 0; - return true; - } - case WM_MOUSEWHEEL: - POINT point; - point.x = GET_X_LPARAM(l_param); - point.y = GET_Y_LPARAM(l_param); - ScreenToClient(hwnd, &point); - OnMouseWheelInternal(GET_WHEEL_DELTA_WPARAM(w_param), point); - result = 0; - return true; - case WM_KEYDOWN: - OnKeyDownInternal(static_cast<int>(w_param)); - result = 0; - return true; - case WM_KEYUP: - OnKeyUpInternal(static_cast<int>(w_param)); - result = 0; - return true; - case WM_CHAR: - OnCharInternal(static_cast<wchar_t>(w_param)); - result = 0; - return true; - case WM_SIZE: - OnResizeInternal(LOWORD(l_param), HIWORD(l_param)); - result = 0; - return true; - case WM_ACTIVATE: - if (w_param == WA_ACTIVE || w_param == WA_CLICKACTIVE) - OnActivatedInternal(); - else if (w_param == WA_INACTIVE) - OnDeactivatedInternal(); - result = 0; - return true; - case WM_DESTROY: - OnDestroyInternal(); - result = 0; - return true; - default: - return false; - } - } +void Window::SetClientSize(const Size& size) { + if (IsWindowValid()) { + const auto window_style = + static_cast<DWORD>(GetWindowLongPtr(hwnd_, GWL_STYLE)); + const auto window_ex_style = + static_cast<DWORD>(GetWindowLongPtr(hwnd_, GWL_EXSTYLE)); + + RECT rect; + rect.left = 0; + rect.top = 0; + rect.right = graph::DipToPixelX(size.width); + rect.bottom = graph::DipToPixelY(size.height); + AdjustWindowRectEx(&rect, window_style, FALSE, window_ex_style); + + SetWindowPos(hwnd_, nullptr, 0, 0, rect.right - rect.left, + rect.bottom - rect.top, SWP_NOZORDER | SWP_NOMOVE); + } +} - Point Window::GetMousePosition() - { - if (!IsWindowValid()) - return Point::Zero(); - POINT point; - ::GetCursorPos(&point); - ::ScreenToClient(hwnd_, &point); - return PiToDip(point); - } +Rect Window::GetWindowRect() { + if (!IsWindowValid()) return Rect(); - Point Window::GetOffset() - { - return Point(); - } + RECT rect; + ::GetWindowRect(hwnd_, &rect); - Size Window::GetSize() - { - return GetClientSize(); - } + return Rect::FromVertices( + graph::PixelToDipX(rect.left), graph::PixelToDipY(rect.top), + graph::PixelToDipX(rect.right), graph::PixelToDipY(rect.bottom)); +} - void Window::SetRect(const Rect& size) - { +void Window::SetWindowRect(const Rect& rect) { + if (IsWindowValid()) { + SetWindowPos(hwnd_, nullptr, graph::DipToPixelX(rect.left), + graph::DipToPixelY(rect.top), + graph::DipToPixelX(rect.GetRight()), + graph::DipToPixelY(rect.GetBottom()), SWP_NOZORDER); + } +} - } +void Window::SetWindowPosition(const Point& position) { + if (IsWindowValid()) { + SetWindowPos(hwnd_, nullptr, graph::DipToPixelX(position.x), + graph::DipToPixelY(position.y), 0, 0, + SWP_NOZORDER | SWP_NOSIZE); + } +} - bool Window::IsPointInside(const Point& point) - { - return Rect(Point::Zero(), GetClientSize()).IsPointInside(point); - } +Point Window::PointToScreen(const Point& point) { + if (!IsWindowValid()) return Point::Zero(); - void Window::WindowInvalidateLayout() - { - if (is_layout_invalid_) - return; - - is_layout_invalid_ = true; - InvokeLater([this] - { - if (is_layout_invalid_) - Relayout(); - }); - } + auto p = DipToPi(point); + if (::ClientToScreen(GetWindowHandle(), &p) == 0) + throw Win32Error(::GetLastError(), + "Failed transform point from window to screen."); + return PiToDip(p); +} - void Window::Relayout() - { - Measure(GetSize(), AdditionalMeasureInfo{}); - OnLayoutCore(Rect(Point::Zero(), GetSize()), AdditionalLayoutInfo{}); - is_layout_invalid_ = false; - } +Point Window::PointFromScreen(const Point& point) { + if (!IsWindowValid()) return Point::Zero(); - void Window::SetSizeFitContent(const Size& max_size) - { - Measure(max_size, AdditionalMeasureInfo{}); - SetClientSize(GetDesiredSize()); - OnLayoutCore(Rect(Point::Zero(), GetSize()), AdditionalLayoutInfo{}); - is_layout_invalid_ = false; - } + auto p = DipToPi(point); + if (::ScreenToClient(GetWindowHandle(), &p) == 0) + throw Win32Error(::GetLastError(), + "Failed transform point from screen to window."); + return PiToDip(p); +} - bool Window::RequestFocusFor(Control * control) - { - if (control == nullptr) - throw std::invalid_argument("The control to request focus can't be null. You can set it as the window."); +bool Window::HandleWindowMessage(HWND hwnd, int msg, WPARAM w_param, + LPARAM l_param, LRESULT& result) { + events::WindowNativeMessageEventArgs args(this, this, + {hwnd, msg, w_param, l_param}); + native_message_event_.Raise(args); + if (args.GetResult().has_value()) { + result = args.GetResult().value(); + return true; + } + + switch (msg) { + case WM_PAINT: + OnPaintInternal(); + result = 0; + return true; + case WM_ERASEBKGND: + result = 1; + return true; + case WM_SETFOCUS: + OnSetFocusInternal(); + result = 0; + return true; + case WM_KILLFOCUS: + OnKillFocusInternal(); + result = 0; + return true; + case WM_MOUSEMOVE: { + POINT point; + point.x = GET_X_LPARAM(l_param); + point.y = GET_Y_LPARAM(l_param); + OnMouseMoveInternal(point); + result = 0; + return true; + } + case WM_LBUTTONDOWN: { + POINT point; + point.x = GET_X_LPARAM(l_param); + point.y = GET_Y_LPARAM(l_param); + OnMouseDownInternal(MouseButton::Left, point); + result = 0; + return true; + } + case WM_LBUTTONUP: { + POINT point; + point.x = GET_X_LPARAM(l_param); + point.y = GET_Y_LPARAM(l_param); + OnMouseUpInternal(MouseButton::Left, point); + result = 0; + return true; + } + case WM_RBUTTONDOWN: { + POINT point; + point.x = GET_X_LPARAM(l_param); + point.y = GET_Y_LPARAM(l_param); + OnMouseDownInternal(MouseButton::Right, point); + result = 0; + return true; + } + case WM_RBUTTONUP: { + POINT point; + point.x = GET_X_LPARAM(l_param); + point.y = GET_Y_LPARAM(l_param); + OnMouseUpInternal(MouseButton::Right, point); + result = 0; + return true; + } + case WM_MBUTTONDOWN: { + POINT point; + point.x = GET_X_LPARAM(l_param); + point.y = GET_Y_LPARAM(l_param); + OnMouseDownInternal(MouseButton::Middle, point); + result = 0; + return true; + } + case WM_MBUTTONUP: { + POINT point; + point.x = GET_X_LPARAM(l_param); + point.y = GET_Y_LPARAM(l_param); + OnMouseUpInternal(MouseButton::Middle, point); + result = 0; + return true; + } + case WM_MOUSEWHEEL: + POINT point; + point.x = GET_X_LPARAM(l_param); + point.y = GET_Y_LPARAM(l_param); + ScreenToClient(hwnd, &point); + OnMouseWheelInternal(GET_WHEEL_DELTA_WPARAM(w_param), point); + result = 0; + return true; + case WM_KEYDOWN: + OnKeyDownInternal(static_cast<int>(w_param)); + result = 0; + return true; + case WM_KEYUP: + OnKeyUpInternal(static_cast<int>(w_param)); + result = 0; + return true; + case WM_CHAR: + OnCharInternal(static_cast<wchar_t>(w_param)); + result = 0; + return true; + case WM_SIZE: + OnResizeInternal(LOWORD(l_param), HIWORD(l_param)); + result = 0; + return true; + case WM_ACTIVATE: + if (w_param == WA_ACTIVE || w_param == WA_CLICKACTIVE) + OnActivatedInternal(); + else if (w_param == WA_INACTIVE) + OnDeactivatedInternal(); + result = 0; + return true; + case WM_DESTROY: + OnDestroyInternal(); + result = 0; + return true; + default: + return false; + } +} - if (!IsWindowValid()) - return false; +Point Window::GetMousePosition() { + if (!IsWindowValid()) return Point::Zero(); + POINT point; + ::GetCursorPos(&point); + ::ScreenToClient(hwnd_, &point); + return PiToDip(point); +} - if (!window_focus_) - { - focus_control_ = control; - ::SetFocus(hwnd_); - return true; // event dispatch will be done in window message handling function "OnSetFocusInternal". - } +bool Window::RequestFocusFor(Control* control) { + assert(control != nullptr); // The control to request focus can't be null. + // You can set it as the window. - if (focus_control_ == control) - return true; + if (!IsWindowValid()) return false; - DispatchEvent(focus_control_, &Control::lose_focus_event, nullptr, false); + if (!window_focus_) { + focus_control_ = control; + ::SetFocus(hwnd_); + return true; // event dispatch will be done in window message handling + // function "OnSetFocusInternal". + } - focus_control_ = control; + if (focus_control_ == control) return true; - DispatchEvent(control, &Control::get_focus_event, nullptr, false); + DispatchEvent(focus_control_, &Control::LoseFocusEvent, nullptr, false); - return true; - } + focus_control_ = control; - Control* Window::GetFocusControl() - { - return focus_control_; - } + DispatchEvent(control, &Control::GainFocusEvent, nullptr, false); - Control* Window::CaptureMouseFor(Control* control) - { - if (control != nullptr) - { - ::SetCapture(hwnd_); - std::swap(mouse_capture_control_, control); - DispatchMouseHoverControlChangeEvent(control ? control : mouse_hover_control_, mouse_capture_control_, GetMousePosition()); - return control; - } - else - { - return ReleaseCurrentMouseCapture(); - } - } + return true; +} - Control* Window::ReleaseCurrentMouseCapture() - { - if (mouse_capture_control_) - { - const auto previous = mouse_capture_control_; - mouse_capture_control_ = nullptr; - ::ReleaseCapture(); - DispatchMouseHoverControlChangeEvent(previous, mouse_hover_control_, GetMousePosition()); - return previous; - } - else - { - return nullptr; - } - } +Control* Window::GetFocusControl() { return focus_control_; } + +Control* Window::CaptureMouseFor(Control* control) { + if (control != nullptr) { + ::SetCapture(hwnd_); + std::swap(mouse_capture_control_, control); + DispatchMouseHoverControlChangeEvent( + control ? control : mouse_hover_control_, mouse_capture_control_, + GetMousePosition()); + return control; + } else { + return ReleaseCurrentMouseCapture(); + } +} - void Window::UpdateCursor() - { - if (IsWindowValid() && mouse_hover_control_ != nullptr) - { - SetCursorInternal(GetCursorInherit(mouse_hover_control_)->GetHandle()); - } - } +Control* Window::ReleaseCurrentMouseCapture() { + if (mouse_capture_control_) { + const auto previous = mouse_capture_control_; + mouse_capture_control_ = nullptr; + ::ReleaseCapture(); + DispatchMouseHoverControlChangeEvent(previous, mouse_hover_control_, + GetMousePosition()); + return previous; + } else { + return nullptr; + } +} #ifdef CRU_DEBUG_LAYOUT - void Window::SetDebugLayout(const bool value) - { - if (debug_layout_ != value) - { - debug_layout_ = value; - InvalidateDraw(); - } - } +void Window::SetDebugLayout(const bool value) { + if (debug_layout_ != value) { + debug_layout_ = value; + InvalidateDraw(); + } +} #endif - RECT Window::GetClientRectPixel() { - RECT rect{ }; - GetClientRect(hwnd_, &rect); - return rect; - } - - bool Window::IsMessageInQueue(UINT message) - { - MSG msg; - return ::PeekMessageW(&msg, hwnd_, message, message, PM_NOREMOVE) != 0; - } - - void Window::SetCursorInternal(HCURSOR cursor) - { - if (IsWindowValid()) - { - ::SetClassLongPtrW(GetWindowHandle(), GCLP_HCURSOR, reinterpret_cast<LONG_PTR>(cursor)); - if (mouse_hover_control_ != nullptr) - ::SetCursor(cursor); - } - } - - void Window::OnDestroyInternal() { - WindowManager::GetInstance()->UnregisterWindow(hwnd_); - hwnd_ = nullptr; - if (delete_this_on_destroy_) - InvokeLater([this]{ delete this; }); - } +void Window::OnChildChanged(Control* old_child, Control* new_child) { + if (old_child) render_object_->RemoveChild(0); + if (new_child) render_object_->AddChild(new_child->GetRenderObject(), 0); +} - void Window::OnPaintInternal() { - render_target_->SetAsTarget(); +Control* Window::HitTest(const Point& point) { + return render_object_->HitTest(point)->GetAttachedControl(); +} - auto device_context = render_target_->GetD2DDeviceContext(); +RECT Window::GetClientRectPixel() { + RECT rect{}; + GetClientRect(hwnd_, &rect); + return rect; +} - device_context->BeginDraw(); +bool Window::IsMessageInQueue(UINT message) { + MSG msg; + return ::PeekMessageW(&msg, hwnd_, message, message, PM_NOREMOVE) != 0; +} - //Clear the background. - device_context->Clear(D2D1::ColorF(D2D1::ColorF::White)); +void Window::SetCursorInternal(HCURSOR cursor) { + if (IsWindowValid()) { + ::SetClassLongPtrW(GetWindowHandle(), GCLP_HCURSOR, + reinterpret_cast<LONG_PTR>(cursor)); + if (mouse_hover_control_ != nullptr) ::SetCursor(cursor); + } +} - Draw(device_context.Get()); +void Window::OnDestroyInternal() { + WindowManager::GetInstance()->UnregisterWindow(hwnd_); + hwnd_ = nullptr; + if (delete_this_on_destroy_) InvokeLater([this] { delete this; }); +} - ThrowIfFailed( - device_context->EndDraw(), "Failed to draw window." - ); +void Window::OnPaintInternal() { + render_target_->SetAsTarget(); - render_target_->Present(); + auto device_context = + render_target_->GetGraphManager()->GetD2D1DeviceContext(); + device_context->BeginDraw(); + // Clear the background. + device_context->Clear(D2D1::ColorF(D2D1::ColorF::White)); + render_object_->Draw(device_context); + ThrowIfFailed(device_context->EndDraw(), "Failed to draw window."); + render_target_->Present(); - ValidateRect(hwnd_, nullptr); - } + ValidateRect(hwnd_, nullptr); +} - void Window::OnResizeInternal(const int new_width, const int new_height) { - render_target_->ResizeBuffer(new_width, new_height); - if (!(new_width == 0 && new_height == 0)) - WindowInvalidateLayout(); - } +void Window::OnResizeInternal(const int new_width, const int new_height) { + render_target_->ResizeBuffer(new_width, new_height); + if (!(new_width == 0 && new_height == 0)) render_object_->MeasureAndLayout(); +} - void Window::OnSetFocusInternal() - { - window_focus_ = true; - DispatchEvent(focus_control_, &Control::get_focus_event, nullptr, true); - } +void Window::OnSetFocusInternal() { + window_focus_ = true; + DispatchEvent(focus_control_, &Control::GainFocusEvent, nullptr, true); +} - void Window::OnKillFocusInternal() - { - window_focus_ = false; - DispatchEvent(focus_control_, &Control::lose_focus_event, nullptr, true); - } +void Window::OnKillFocusInternal() { + window_focus_ = false; + DispatchEvent(focus_control_, &Control::LoseFocusEvent, nullptr, true); +} - void Window::OnMouseMoveInternal(const POINT point) - { - const auto dip_point = PiToDip(point); - - //when mouse was previous outside the window - if (mouse_hover_control_ == nullptr) { - //invoke TrackMouseEvent to have WM_MOUSELEAVE sent. - TRACKMOUSEEVENT tme; - tme.cbSize = sizeof tme; - tme.dwFlags = TME_LEAVE; - tme.hwndTrack = hwnd_; - - TrackMouseEvent(&tme); - } - - //Find the first control that hit test succeed. - const auto new_control_mouse_hover = HitTest(dip_point); - const auto old_control_mouse_hover = mouse_hover_control_; - mouse_hover_control_ = new_control_mouse_hover; - - if (mouse_capture_control_) // if mouse is captured - { - DispatchEvent(mouse_capture_control_, &Control::mouse_move_event, nullptr, dip_point); - } - else - { - DispatchMouseHoverControlChangeEvent(old_control_mouse_hover, new_control_mouse_hover, dip_point); - DispatchEvent(new_control_mouse_hover, &Control::mouse_move_event, nullptr, dip_point); - } - } +void Window::OnMouseMoveInternal(const POINT point) { + const auto dip_point = PiToDip(point); + + // when mouse was previous outside the window + if (mouse_hover_control_ == nullptr) { + // invoke TrackMouseEvent to have WM_MOUSELEAVE sent. + TRACKMOUSEEVENT tme; + tme.cbSize = sizeof tme; + tme.dwFlags = TME_LEAVE; + tme.hwndTrack = hwnd_; + + TrackMouseEvent(&tme); + } + + // Find the first control that hit test succeed. + const auto new_control_mouse_hover = HitTest(dip_point); + const auto old_control_mouse_hover = mouse_hover_control_; + mouse_hover_control_ = new_control_mouse_hover; + + if (mouse_capture_control_) // if mouse is captured + { + DispatchEvent(mouse_capture_control_, &Control::MouseMoveEvent, nullptr, + dip_point); + } else { + DispatchMouseHoverControlChangeEvent(old_control_mouse_hover, + new_control_mouse_hover, dip_point); + DispatchEvent(new_control_mouse_hover, &Control::MouseMoveEvent, nullptr, + dip_point); + } +} - void Window::OnMouseLeaveInternal() - { - DispatchEvent(mouse_hover_control_, &Control::mouse_leave_event, nullptr); - mouse_hover_control_ = nullptr; - } +void Window::OnMouseLeaveInternal() { + DispatchEvent(mouse_hover_control_, &Control::MouseLeaveEvent, nullptr); + mouse_hover_control_ = nullptr; +} - void Window::OnMouseDownInternal(MouseButton button, POINT point) - { - const auto dip_point = PiToDip(point); +void Window::OnMouseDownInternal(MouseButton button, POINT point) { + const auto dip_point = PiToDip(point); - Control* control; + Control* control; - if (mouse_capture_control_) - control = mouse_capture_control_; - else - control = HitTest(dip_point); + if (mouse_capture_control_) + control = mouse_capture_control_; + else + control = HitTest(dip_point); - DispatchEvent(control, &Control::mouse_down_event, nullptr, dip_point, button); - } + DispatchEvent(control, &Control::MouseDownEvent, nullptr, dip_point, + button); +} - void Window::OnMouseUpInternal(MouseButton button, POINT point) - { - const auto dip_point = PiToDip(point); +void Window::OnMouseUpInternal(MouseButton button, POINT point) { + const auto dip_point = PiToDip(point); - Control* control; + Control* control; - if (mouse_capture_control_) - control = mouse_capture_control_; - else - control = HitTest(dip_point); + if (mouse_capture_control_) + control = mouse_capture_control_; + else + control = HitTest(dip_point); - DispatchEvent(control, &Control::mouse_up_event, nullptr, dip_point, button); - } + DispatchEvent(control, &Control::MouseUpEvent, nullptr, dip_point, button); +} - void Window::OnMouseWheelInternal(short delta, POINT point) - { - const auto dip_point = PiToDip(point); +void Window::OnMouseWheelInternal(short delta, POINT point) { + const auto dip_point = PiToDip(point); - Control* control; + Control* control; - if (mouse_capture_control_) - control = mouse_capture_control_; - else - control = HitTest(dip_point); + if (mouse_capture_control_) + control = mouse_capture_control_; + else + control = HitTest(dip_point); - DispatchEvent(control, &Control::mouse_wheel_event, nullptr, dip_point, static_cast<float>(delta)); - } + DispatchEvent(control, &Control::MouseWheelEvent, nullptr, dip_point, + static_cast<float>(delta)); +} - void Window::OnKeyDownInternal(int virtual_code) - { - DispatchEvent(focus_control_, &Control::key_down_event, nullptr, virtual_code); - } +void Window::OnKeyDownInternal(int virtual_code) { + DispatchEvent(focus_control_, &Control::KeyDownEvent, nullptr, + virtual_code); +} - void Window::OnKeyUpInternal(int virtual_code) - { - DispatchEvent(focus_control_, &Control::key_up_event, nullptr, virtual_code); - } +void Window::OnKeyUpInternal(int virtual_code) { + DispatchEvent(focus_control_, &Control::KeyUpEvent, nullptr, virtual_code); +} - void Window::OnCharInternal(wchar_t c) - { - DispatchEvent(focus_control_, &Control::char_event, nullptr, c); - } +void Window::OnCharInternal(wchar_t c) { + DispatchEvent(focus_control_, &Control::CharEvent, nullptr, c); +} - void Window::OnActivatedInternal() - { - events::UiEventArgs args(this, this); - activated_event.Raise(args); - } +void Window::OnActivatedInternal() { + events::UiEventArgs args(this, this); + activated_event_.Raise(args); +} - void Window::OnDeactivatedInternal() - { - events::UiEventArgs args(this, this); - deactivated_event.Raise(args); - } +void Window::OnDeactivatedInternal() { + events::UiEventArgs args(this, this); + deactivated_event_.Raise(args); +} - void Window::DispatchMouseHoverControlChangeEvent(Control* old_control, Control* new_control, const Point& point) - { - if (new_control != old_control) //if the mouse-hover-on control changed - { - const auto lowest_common_ancestor = FindLowestCommonAncestor(old_control, new_control); - if (old_control != nullptr) // if last mouse-hover-on control exists - DispatchEvent(old_control, &Control::mouse_leave_event, lowest_common_ancestor); // dispatch mouse leave event. - if (new_control != nullptr) - { - DispatchEvent(new_control, &Control::mouse_enter_event, lowest_common_ancestor, point); // dispatch mouse enter event. - UpdateCursor(); - } - } - } +void Window::DispatchMouseHoverControlChangeEvent(Control* old_control, + Control* new_control, + const Point& point) { + if (new_control != old_control) // if the mouse-hover-on control changed + { + const auto lowest_common_ancestor = + FindLowestCommonAncestor(old_control, new_control); + if (old_control != nullptr) // if last mouse-hover-on control exists + DispatchEvent(old_control, &Control::MouseLeaveEvent, + lowest_common_ancestor); // dispatch mouse leave event. + if (new_control != nullptr) { + DispatchEvent(new_control, &Control::MouseEnterEvent, + lowest_common_ancestor, + point); // dispatch mouse enter event. + } + } } +} // namespace cru::ui |