diff options
-rw-r--r-- | include/cru/platform/native/native_window.hpp | 6 | ||||
-rw-r--r-- | include/cru/ui/window.hpp | 11 | ||||
-rw-r--r-- | include/cru/win/native/native_window.hpp | 5 | ||||
-rw-r--r-- | src/ui/routed_event_dispatch.hpp | 2 | ||||
-rw-r--r-- | src/ui/window.cpp | 97 | ||||
-rw-r--r-- | src/win/native/native_window.cpp | 49 |
6 files changed, 147 insertions, 23 deletions
diff --git a/include/cru/platform/native/native_window.hpp b/include/cru/platform/native/native_window.hpp index 22a2dce0..b557a0dd 100644 --- a/include/cru/platform/native/native_window.hpp +++ b/include/cru/platform/native/native_window.hpp @@ -56,6 +56,12 @@ class NativeWindow : public NativeResource { // The lefttop of the rect is relative to screen lefttop. virtual void SetWindowRect(const Rect& rect) = 0; + // Relative to client lefttop. + virtual Point GetMousePosition() = 0; + + virtual bool CaptureMouse() = 0; + virtual bool ReleaseMouse() = 0; + virtual graph::Painter* BeginPaint() = 0; virtual IEvent<std::nullptr_t>* DestroyEvent() = 0; diff --git a/include/cru/ui/window.hpp b/include/cru/ui/window.hpp index 197198d2..ee6d2176 100644 --- a/include/cru/ui/window.hpp +++ b/include/cru/ui/window.hpp @@ -60,6 +60,14 @@ class Window final : public ContentControl, public SelfResolvable<Window> { // Get the control that has focus. Control* GetFocusControl(); + //*************** region: focus *************** + + // Pass nullptr to release capture. + bool CaptureMouseFor(Control* control); + + // Return null if not captured. + Control* GetMouseCaptureControl(); + protected: void OnChildChanged(Control* old_child, Control* new_child) override; @@ -86,6 +94,7 @@ class Window final : public ContentControl, public SelfResolvable<Window> { //*************** region: event dispatcher helper *************** + // dispatch enter is useful when mouse is captured. void DispatchMouseHoverControlChangeEvent(Control* old_control, Control* new_control, const Point& point); @@ -100,6 +109,8 @@ class Window final : public ContentControl, public SelfResolvable<Window> { Control* focus_control_; // "focus_control_" can't be nullptr + Control* mouse_captured_control_; + bool need_layout_ = false; }; } // namespace cru::ui diff --git a/include/cru/win/native/native_window.hpp b/include/cru/win/native/native_window.hpp index ed678591..b044aef8 100644 --- a/include/cru/win/native/native_window.hpp +++ b/include/cru/win/native/native_window.hpp @@ -48,6 +48,11 @@ class WinNativeWindow : public NativeWindow { // The lefttop of the rect is relative to screen lefttop. void SetWindowRect(const Rect& rect) override; + Point GetMousePosition() override; + + bool CaptureMouse() override; + bool ReleaseMouse() override; + graph::Painter* BeginPaint() override; IEvent<std::nullptr_t>* DestroyEvent() override { return &destroy_event_; } diff --git a/src/ui/routed_event_dispatch.hpp b/src/ui/routed_event_dispatch.hpp index e9430676..83702005 100644 --- a/src/ui/routed_event_dispatch.hpp +++ b/src/ui/routed_event_dispatch.hpp @@ -1,6 +1,8 @@ #pragma once #include "cru/ui/control.hpp" +#include <list> + namespace cru::ui { // Dispatch the event. // diff --git a/src/ui/window.cpp b/src/ui/window.cpp index e17d603c..47509749 100644 --- a/src/ui/window.cpp +++ b/src/ui/window.cpp @@ -19,7 +19,20 @@ std::list<Control*> GetAncestorList(Control* control) { return l; } -Control* FindLowestCommonAncestor(Control* left, Control* right) { +constexpr int in_neither = 0; +constexpr int in_left = 1; +constexpr int in_right = 2; + +// if find_control is not nullptr, then find_result will be set as where the +// find_control is located. in_neither means mouse hover state of it is not +// changed. in_left means mouse move out it. in_right means mouse move in it. +// This is useful for mouse capture. +Control* FindLowestCommonAncestor(Control* left, Control* right, + Control* find_control, int* find_result) { + if (find_control) { + *find_result = in_neither; + } + if (left == nullptr || right == nullptr) return nullptr; auto&& left_list = GetAncestorList(left); @@ -32,10 +45,44 @@ Control* FindLowestCommonAncestor(Control* left, Control* right) { // 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); + if (left_i == left_list.cend()) { + Control* result = *(--left_i); + while (right_i != right_list.cend()) { + if (*right_i == find_control) { + *find_result = in_right; + return result; + } + } + return result; + } + if (right_i == right_list.cend()) { + Control* result = *(--right_i); + while (left_i != left_list.cend()) { + if (*left_i == find_control) { + *find_result = in_left; + return result; + } + } + return result; + } + if (*left_i != *right_i) { + Control* result = *(--left_i); + ++left_i; + while (right_i != right_list.cend()) { + if (*right_i == find_control) { + *find_result = in_right; + return result; + } + } + while (left_i != left_list.cend()) { + if (*left_i == find_control) { + *find_result = in_left; + return result; + } + } + } ++left_i; ++right_i; } @@ -57,7 +104,9 @@ void BindNativeEvent(Window* window, IEvent<T>* event, } // namespace Window::Window(tag_overlapped_constructor) - : mouse_hover_control_(nullptr), focus_control_(this) { + : mouse_hover_control_(nullptr), + focus_control_(this), + mouse_captured_control_(nullptr) { native_window_ = platform::native::UiApplication::GetInstance()->CreateWindow(nullptr); render_object_.reset(new render::WindowRenderObject(this)); @@ -127,6 +176,25 @@ bool Window::RequestFocusFor(Control* control) { Control* Window::GetFocusControl() { return focus_control_; } +bool Window::CaptureMouseFor(Control* control) { + if (control == nullptr) { + if (mouse_captured_control_) { + mouse_captured_control_ = nullptr; + OnNativeMouseMove(GetNativeWindow()->GetMousePosition()); + } + return true; + } + + if (mouse_captured_control_) return false; + mouse_captured_control_ = control; + const auto c = + FindLowestCommonAncestor(control, mouse_hover_control_, nullptr, nullptr); + DispatchEvent(mouse_hover_control_, &Control::MouseLeaveEvent, c); + return true; +} + +Control* Window::GetMouseCaptureControl() { return mouse_captured_control_; } + 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); @@ -176,14 +244,16 @@ void Window::OnNativeMouseMove(const Point& point) { void Window::OnNativeMouseDown( const platform::native::NativeMouseButtonEventArgs& args) { - Control* control = HitTest(args.point); + Control* control = + mouse_captured_control_ ? mouse_captured_control_ : HitTest(args.point); DispatchEvent(control, &Control::MouseDownEvent, nullptr, args.point, args.button); } void Window::OnNativeMouseUp( const platform::native::NativeMouseButtonEventArgs& args) { - Control* control = HitTest(args.point); + Control* control = + mouse_captured_control_ ? mouse_captured_control_ : HitTest(args.point); DispatchEvent(control, &Control::MouseUpEvent, nullptr, args.point, args.button); } @@ -201,12 +271,17 @@ void Window::DispatchMouseHoverControlChangeEvent(Control* old_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 + int find_result; + const auto lowest_common_ancestor = FindLowestCommonAncestor( + old_control, new_control, mouse_captured_control_, &find_result); + if (old_control != nullptr && // if last mouse-hover-on control exists + (mouse_captured_control_ == nullptr || // and if mouse is not captured + find_result == in_left)) // or mouse is captured andc mouse is move + // out of capturing control DispatchEvent(old_control, &Control::MouseLeaveEvent, lowest_common_ancestor); // dispatch mouse leave event. - if (new_control != nullptr) { + if (new_control != nullptr && + (mouse_captured_control_ == nullptr || find_result == in_right)) { DispatchEvent(new_control, &Control::MouseEnterEvent, lowest_common_ancestor, point); // dispatch mouse enter event. diff --git a/src/win/native/native_window.cpp b/src/win/native/native_window.cpp index fd29f9c5..5da121cf 100644 --- a/src/win/native/native_window.cpp +++ b/src/win/native/native_window.cpp @@ -13,6 +13,10 @@ #include <windowsx.h> namespace cru::platform::native::win { +inline Point PiToDip(const POINT& pi_point) { + return Point(PixelToDipX(pi_point.x), PixelToDipY(pi_point.y)); +} + WinNativeWindow::WinNativeWindow(WinUiApplication* application, std::shared_ptr<WindowClass> window_class, DWORD window_style, WinNativeWindow* parent) { @@ -36,8 +40,8 @@ WinNativeWindow::WinNativeWindow(WinUiApplication* application, window_manager->RegisterWindow(hwnd_, this); - window_render_target_.reset( - new WindowRenderTarget(graph::win::direct::DirectGraphFactory::GetInstance(), hwnd_)); + window_render_target_.reset(new WindowRenderTarget( + graph::win::direct::DirectGraphFactory::GetInstance(), hwnd_)); } WinNativeWindow::~WinNativeWindow() { @@ -70,8 +74,7 @@ Size WinNativeWindow::GetClientSize() { if (!IsValid()) return Size{}; const auto pixel_rect = GetClientRectPixel(); - return Size(PixelToDipX(pixel_rect.right), - PixelToDipY(pixel_rect.bottom)); + return Size(PixelToDipX(pixel_rect.right), PixelToDipY(pixel_rect.bottom)); } void WinNativeWindow::SetClientSize(const Size& size) { @@ -104,8 +107,7 @@ Rect WinNativeWindow::GetWindowRect() { throw Win32Error(::GetLastError(), "Failed to invoke GetWindowRect."); return Rect::FromVertices(PixelToDipX(rect.left), PixelToDipY(rect.top), - PixelToDipX(rect.right), - PixelToDipY(rect.bottom)); + PixelToDipX(rect.right), PixelToDipY(rect.bottom)); } void WinNativeWindow::SetWindowRect(const Rect& rect) { @@ -117,6 +119,34 @@ void WinNativeWindow::SetWindowRect(const Rect& rect) { } } +Point WinNativeWindow::GetMousePosition() { + if (IsValid()) { + POINT p; + if (!::GetCursorPos(&p)) + throw Win32Error(::GetLastError(), "Failed to get cursor position."); + if (!::ScreenToClient(hwnd_, &p)) + throw Win32Error(::GetLastError(), "Failed to call ScreenToClient."); + return PiToDip(p); + } + return Point{}; +} + +bool WinNativeWindow::CaptureMouse() { + if (IsValid()) { + ::SetCapture(hwnd_); + return true; + } + return false; +} + +bool WinNativeWindow::ReleaseMouse() { + if (IsValid()) { + const auto result = ::ReleaseCapture(); + return result != 0; + } + return false; +} + graph::Painter* WinNativeWindow::BeginPaint() { return new WindowD2DPainter(this); } @@ -268,8 +298,7 @@ void WinNativeWindow::OnResizeInternal(const int new_width, const int new_height) { if (!(new_width == 0 && new_height == 0)) { window_render_target_->ResizeBuffer(new_width, new_height); - resize_event_.Raise( - Size{PixelToDipX(new_width), PixelToDipY(new_height)}); + resize_event_.Raise(Size{PixelToDipX(new_width), PixelToDipY(new_height)}); } } @@ -283,10 +312,6 @@ void WinNativeWindow::OnKillFocusInternal() { focus_event_.Raise(false); } -inline Point PiToDip(const POINT& pi_point) { - return Point(PixelToDipX(pi_point.x), PixelToDipY(pi_point.y)); -} - void WinNativeWindow::OnMouseMoveInternal(const POINT point) { // when mouse was previous outside the window if (!is_mouse_in_) { |