aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--include/cru/platform/native/native_window.hpp6
-rw-r--r--include/cru/ui/window.hpp11
-rw-r--r--include/cru/win/native/native_window.hpp5
-rw-r--r--src/ui/routed_event_dispatch.hpp2
-rw-r--r--src/ui/window.cpp97
-rw-r--r--src/win/native/native_window.cpp49
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_) {