WebKit merge 94748:94770
[chromium-blink-merge.git] / views / mouse_watcher.cc
blob83fe75d2f3ab32be141b1ec008b1db5928e50fb9
1 // Copyright (c) 2011 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #include "views/mouse_watcher.h"
7 #include "base/compiler_specific.h"
8 #include "base/message_loop.h"
9 #include "base/task.h"
10 #include "ui/gfx/screen.h"
11 #include "views/view.h"
12 #include "views/widget/widget.h"
14 #if defined(USE_WAYLAND)
15 #include "ui/wayland/events/wayland_event.h"
16 #endif
18 namespace views {
20 // Amount of time between when the mouse moves outside the view's zone and when
21 // the listener is notified.
22 static const int kNotifyListenerTimeMs = 300;
24 class MouseWatcher::Observer : public MessageLoopForUI::Observer {
25 public:
26 explicit Observer(MouseWatcher* mouse_watcher)
27 : mouse_watcher_(mouse_watcher),
28 ALLOW_THIS_IN_INITIALIZER_LIST(notify_listener_factory_(this)) {
29 MessageLoopForUI::current()->AddObserver(this);
32 ~Observer() {
33 MessageLoopForUI::current()->RemoveObserver(this);
36 // MessageLoop::Observer implementation:
37 #if defined(OS_WIN)
38 void WillProcessMessage(const MSG& msg) {
41 void DidProcessMessage(const MSG& msg) {
42 // We spy on three different Windows messages here to see if the mouse has
43 // moved out of the bounds of the view. The messages are:
45 // WM_MOUSEMOVE:
46 // For when the mouse moves from the view into the rest of the browser UI,
47 // i.e. within the bounds of the same windows HWND.
48 // WM_MOUSELEAVE:
49 // For when the mouse moves out of the bounds of the view's HWND.
50 // WM_NCMOUSELEAVE:
51 // For notification when the mouse leaves the _non-client_ area.
53 switch (msg.message) {
54 case WM_MOUSEMOVE:
55 HandleGlobalMouseMoveEvent(false);
56 break;
57 case WM_MOUSELEAVE:
58 case WM_NCMOUSELEAVE:
59 HandleGlobalMouseMoveEvent(true);
60 break;
63 #elif defined(USE_WAYLAND)
64 MessageLoopForUI::Observer::EventStatus WillProcessEvent(
65 ui::WaylandEvent* event) {
66 switch (event->type) {
67 case ui::WAYLAND_MOTION:
68 HandleGlobalMouseMoveEvent(false);
69 break;
70 case ui::WAYLAND_POINTER_FOCUS:
71 if (!event->pointer_focus.state)
72 HandleGlobalMouseMoveEvent(true);
73 break;
74 default:
75 break;
77 return EVENT_CONTINUE;
79 #else
80 void WillProcessEvent(GdkEvent* event) {
83 void DidProcessEvent(GdkEvent* event) {
84 switch (event->type) {
85 case GDK_MOTION_NOTIFY:
86 HandleGlobalMouseMoveEvent(false);
87 break;
88 case GDK_LEAVE_NOTIFY:
89 HandleGlobalMouseMoveEvent(true);
90 break;
91 default:
92 break;
95 #endif
97 private:
98 View* view() const { return mouse_watcher_->host_; }
100 // Returns whether or not the cursor is currently in the view's "zone" which
101 // is defined as a slightly larger region than the view.
102 bool IsCursorInViewZone() {
103 gfx::Rect bounds = view()->GetLocalBounds();
104 gfx::Point view_topleft(bounds.origin());
105 View::ConvertPointToScreen(view(), &view_topleft);
106 bounds.set_origin(view_topleft);
107 bounds.SetRect(view_topleft.x() - mouse_watcher_->hot_zone_insets_.left(),
108 view_topleft.y() - mouse_watcher_->hot_zone_insets_.top(),
109 bounds.width() + mouse_watcher_->hot_zone_insets_.width(),
110 bounds.height() + mouse_watcher_->hot_zone_insets_.height());
112 gfx::Point cursor_point = gfx::Screen::GetCursorScreenPoint();
114 return bounds.Contains(cursor_point.x(), cursor_point.y());
117 // Returns true if the mouse is over the view's window.
118 bool IsMouseOverWindow() {
119 Widget* widget = view()->GetWidget();
120 if (!widget)
121 return false;
123 return gfx::Screen::GetWindowAtCursorScreenPoint() ==
124 widget->GetNativeWindow();
127 // Called from the message loop observer when a mouse movement has occurred.
128 void HandleGlobalMouseMoveEvent(bool check_window) {
129 bool in_view = IsCursorInViewZone();
130 if (!in_view || (check_window && !IsMouseOverWindow())) {
131 // Mouse moved outside the view's zone, start a timer to notify the
132 // listener.
133 if (notify_listener_factory_.empty()) {
134 MessageLoop::current()->PostDelayedTask(
135 FROM_HERE,
136 notify_listener_factory_.NewRunnableMethod(
137 &Observer::NotifyListener),
138 !in_view ? kNotifyListenerTimeMs :
139 mouse_watcher_->notify_on_exit_time_ms_);
141 } else {
142 // Mouse moved quickly out of the view and then into it again, so cancel
143 // the timer.
144 notify_listener_factory_.RevokeAll();
148 void NotifyListener() {
149 mouse_watcher_->NotifyListener();
150 // WARNING: we've been deleted.
153 private:
154 MouseWatcher* mouse_watcher_;
156 // A factory that is used to construct a delayed callback to the listener.
157 ScopedRunnableMethodFactory<Observer> notify_listener_factory_;
159 DISALLOW_COPY_AND_ASSIGN(Observer);
162 MouseWatcherListener::~MouseWatcherListener() {
165 MouseWatcher::MouseWatcher(View* host,
166 MouseWatcherListener* listener,
167 const gfx::Insets& hot_zone_insets)
168 : host_(host),
169 listener_(listener),
170 hot_zone_insets_(hot_zone_insets),
171 notify_on_exit_time_ms_(kNotifyListenerTimeMs) {
174 MouseWatcher::~MouseWatcher() {
177 void MouseWatcher::Start() {
178 if (!is_observing())
179 observer_.reset(new Observer(this));
182 void MouseWatcher::Stop() {
183 observer_.reset(NULL);
186 void MouseWatcher::NotifyListener() {
187 observer_.reset(NULL);
188 listener_->MouseMovedOutOfView();
191 } // namespace views