From b8642ec4593f6daf22cd4c70d422f5cc40c16e7f Mon Sep 17 00:00:00 2001 From: "tdanderson@chromium.org" Date: Thu, 17 Apr 2014 05:20:39 +0000 Subject: Disallow touch hit-testing along attached edges of panels Introduce AttachedPanelWindowTargeter, a new derived class of EasyResizeWindowTargeter. When installed as the EventTargeter of a panel container, the extended touch hit-testing inset is set to 0 for the edges of panels adjacent to the shelf. This makes it significantly easier to correctly target shelf buttons with touch because shelf-adjacent edges of panels are prevented from themselves being targets. BUG=351348 TEST=WorkspaceControllerTest.WindowEdgeTouchHitTestPanel,PanelLayoutManagerTest.TouchHitTestPanel Review URL: https://codereview.chromium.org/232703006 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@264429 0039d316-1c4b-4281-b951-d872f2087c98 --- ash/ash.gyp | 2 + ash/root_window_controller.cc | 19 +++++- ash/shell.cc | 6 ++ ash/shell.h | 3 + ash/shell_observer.h | 3 + ash/wm/panels/attached_panel_window_targeter.cc | 75 ++++++++++++++++++++++++ ash/wm/panels/attached_panel_window_targeter.h | 43 ++++++++++++++ ash/wm/panels/panel_layout_manager_unittest.cc | 77 ++++++++++++++++++++++++- ash/wm/workspace_controller_unittest.cc | 34 ++++++++++- 9 files changed, 256 insertions(+), 6 deletions(-) create mode 100644 ash/wm/panels/attached_panel_window_targeter.cc create mode 100644 ash/wm/panels/attached_panel_window_targeter.h (limited to 'ash') diff --git a/ash/ash.gyp b/ash/ash.gyp index 5b6350c..690605f 100644 --- a/ash/ash.gyp +++ b/ash/ash.gyp @@ -606,6 +606,8 @@ 'wm/overview/window_selector_panels.h', 'wm/overview/window_selector_window.cc', 'wm/overview/window_selector_window.h', + 'wm/panels/attached_panel_window_targeter.cc', + 'wm/panels/attached_panel_window_targeter.h', 'wm/panels/panel_frame_view.cc', 'wm/panels/panel_frame_view.h', 'wm/panels/panel_layout_manager.cc', diff --git a/ash/root_window_controller.cc b/ash/root_window_controller.cc index 3d4a839..6dd3b67 100644 --- a/ash/root_window_controller.cc +++ b/ash/root_window_controller.cc @@ -33,6 +33,7 @@ #include "ash/touch/touch_observer_hud.h" #include "ash/wm/always_on_top_controller.h" #include "ash/wm/dock/docked_window_layout_manager.h" +#include "ash/wm/panels/attached_panel_window_targeter.h" #include "ash/wm/panels/panel_layout_manager.h" #include "ash/wm/panels/panel_window_event_handler.h" #include "ash/wm/root_window_layout_manager.h" @@ -415,6 +416,9 @@ void RootWindowController::OnShelfCreated() { if (shelf_->shelf_layout_manager()) docked_layout_manager_->AddObserver(shelf_->shelf_layout_manager()); } + + // Notify shell observers that the shelf has been created. + Shell::GetInstance()->OnShelfCreatedForRootWindow(GetRootWindow()); } void RootWindowController::UpdateAfterLoginStatusChange( @@ -785,6 +789,20 @@ void RootWindowController::InitLayoutManagers() { panel_container->SetLayoutManager(panel_layout_manager_); panel_container_handler_.reset(new PanelWindowEventHandler); panel_container->AddPreTargetHandler(panel_container_handler_.get()); + + // Install an AttachedPanelWindowTargeter on the panel container to make it + // easier to correctly target shelf buttons with touch. + gfx::Insets mouse_extend(-kResizeOutsideBoundsSize, + -kResizeOutsideBoundsSize, + -kResizeOutsideBoundsSize, + -kResizeOutsideBoundsSize); + gfx::Insets touch_extend = mouse_extend.Scale( + kResizeOutsideBoundsScaleForTouch); + panel_container->SetEventTargeter(scoped_ptr( + new AttachedPanelWindowTargeter(panel_container, + mouse_extend, + touch_extend, + panel_layout_manager_))); } void RootWindowController::InitTouchHuds() { @@ -894,7 +912,6 @@ void RootWindowController::CreateContainersInRootWindow( "PanelContainer", non_lock_screen_containers); SetUsesScreenCoordinates(panel_container); - SetUsesEasyResizeTargeter(panel_container); aura::Window* shelf_bubble_container = CreateContainer(kShellWindowId_ShelfBubbleContainer, diff --git a/ash/shell.cc b/ash/shell.cc index 2c0277f..e694630 100644 --- a/ash/shell.cc +++ b/ash/shell.cc @@ -407,6 +407,12 @@ void Shell::CreateShelf() { (*iter)->shelf()->CreateShelf(); } +void Shell::OnShelfCreatedForRootWindow(aura::Window* root_window) { + FOR_EACH_OBSERVER(ShellObserver, + observers_, + OnShelfCreatedForRootWindow(root_window)); +} + void Shell::CreateKeyboard() { // TODO(bshe): Primary root window controller may not be the controller to // attach virtual keyboard. See http://crbug.com/303429 diff --git a/ash/shell.h b/ash/shell.h index 90746c8..6f88e1d 100644 --- a/ash/shell.h +++ b/ash/shell.h @@ -307,6 +307,9 @@ class ASH_EXPORT Shell : public SystemModalContainerEventFilterDelegate, // Initializes |shelf_|. Does nothing if it's already initialized. void CreateShelf(); + // Called when the shelf is created for |root_window|. + void OnShelfCreatedForRootWindow(aura::Window* root_window); + // Creates a virtual keyboard. Deletes the old virtual keyboard if it already // exists. void CreateKeyboard(); diff --git a/ash/shell_observer.h b/ash/shell_observer.h index 8c393d3..35b2555 100644 --- a/ash/shell_observer.h +++ b/ash/shell_observer.h @@ -32,6 +32,9 @@ class ASH_EXPORT ShellObserver { // Invoked after a non-primary root window is created. virtual void OnRootWindowAdded(aura::Window* root_window) {} + // Invoked after the shelf has been created for |root_window|. + virtual void OnShelfCreatedForRootWindow(aura::Window* root_window) {} + // Invoked when the shelf alignment in |root_window| is changed. virtual void OnShelfAlignmentChanged(aura::Window* root_window) {} diff --git a/ash/wm/panels/attached_panel_window_targeter.cc b/ash/wm/panels/attached_panel_window_targeter.cc new file mode 100644 index 0000000..4e8b8f7 --- /dev/null +++ b/ash/wm/panels/attached_panel_window_targeter.cc @@ -0,0 +1,75 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "ash/wm/panels/attached_panel_window_targeter.h" + +#include "ash/shelf/shelf.h" +#include "ash/shell.h" +#include "ash/wm/panels/panel_layout_manager.h" + +namespace ash { + +AttachedPanelWindowTargeter::AttachedPanelWindowTargeter( + aura::Window* container, + const gfx::Insets& default_mouse_extend, + const gfx::Insets& default_touch_extend, + PanelLayoutManager* panel_layout_manager) + : ::wm::EasyResizeWindowTargeter(container, + default_mouse_extend, + default_touch_extend), + panel_container_(container), + panel_layout_manager_(panel_layout_manager), + default_touch_extend_(default_touch_extend) { + Shell::GetInstance()->AddShellObserver(this); +} + +AttachedPanelWindowTargeter::~AttachedPanelWindowTargeter() { + Shell::GetInstance()->RemoveShellObserver(this); +} + +void AttachedPanelWindowTargeter::OnShelfCreatedForRootWindow( + aura::Window* root_window) { + UpdateTouchExtend(root_window); +} + +void AttachedPanelWindowTargeter::OnShelfAlignmentChanged( + aura::Window* root_window) { + // Don't update the touch insets if the shelf has not yet been created. + if (!panel_layout_manager_->shelf()) + return; + + UpdateTouchExtend(root_window); +} + +void AttachedPanelWindowTargeter::UpdateTouchExtend(aura::Window* root_window) { + // Only update the touch insets for panels if they are attached to the shelf + // in |root_window|. + if (panel_container_->GetRootWindow() != root_window) + return; + + DCHECK(panel_layout_manager_->shelf()); + + gfx::Insets touch(default_touch_extend_); + switch (panel_layout_manager_->shelf()->alignment()) { + case SHELF_ALIGNMENT_BOTTOM: + touch = gfx::Insets(touch.top(), touch.left(), 0, touch.right()); + break; + case SHELF_ALIGNMENT_LEFT: + touch = gfx::Insets(touch.top(), 0, touch.bottom(), touch.right()); + break; + case SHELF_ALIGNMENT_RIGHT: + touch = gfx::Insets(touch.top(), touch.left(), touch.bottom(), 0); + break; + case SHELF_ALIGNMENT_TOP: + touch = gfx::Insets(0, touch.left(), touch.bottom(), touch.right()); + break; + default: + NOTREACHED(); + return; + } + + set_touch_extend(touch); +} + +} // namespace ash diff --git a/ash/wm/panels/attached_panel_window_targeter.h b/ash/wm/panels/attached_panel_window_targeter.h new file mode 100644 index 0000000..d5e8196 --- /dev/null +++ b/ash/wm/panels/attached_panel_window_targeter.h @@ -0,0 +1,43 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef ASH_WM_PANELS_ATTACHED_PANEL_WINDOW_TARGETER_H_ +#define ASH_WM_PANELS_ATTACHED_PANEL_WINDOW_TARGETER_H_ + +#include "ash/shell_observer.h" +#include "ui/wm/core/easy_resize_window_targeter.h" + +namespace ash { + +class PanelLayoutManager; + +// A window targeter installed on a panel container to disallow touch +// hit-testing of attached panel edges that are adjacent to the shelf. This +// makes it significantly easier to correctly target shelf buttons with touch. +class AttachedPanelWindowTargeter : public ::wm::EasyResizeWindowTargeter, + public ShellObserver { + public: + AttachedPanelWindowTargeter(aura::Window* container, + const gfx::Insets& default_mouse_extend, + const gfx::Insets& default_touch_extend, + PanelLayoutManager* panel_layout_manager); + virtual ~AttachedPanelWindowTargeter(); + + // ShellObserver: + virtual void OnShelfCreatedForRootWindow(aura::Window* root_window) OVERRIDE; + virtual void OnShelfAlignmentChanged(aura::Window* root_window) OVERRIDE; + + private: + void UpdateTouchExtend(aura::Window* root_window); + + aura::Window* panel_container_; + PanelLayoutManager* panel_layout_manager_; + gfx::Insets default_touch_extend_; + + DISALLOW_COPY_AND_ASSIGN(AttachedPanelWindowTargeter); +}; + +} // namespace ash + +#endif // ASH_WM_PANELS_ATTACHED_PANEL_WINDOW_TARGETER_H_ diff --git a/ash/wm/panels/panel_layout_manager_unittest.cc b/ash/wm/panels/panel_layout_manager_unittest.cc index 853f648..d32eb82 100644 --- a/ash/wm/panels/panel_layout_manager_unittest.cc +++ b/ash/wm/panels/panel_layout_manager_unittest.cc @@ -35,6 +35,7 @@ #include "ui/aura/window.h" #include "ui/aura/window_event_dispatcher.h" #include "ui/base/l10n/l10n_util.h" +#include "ui/events/event_utils.h" #include "ui/views/widget/widget.h" namespace ash { @@ -59,9 +60,10 @@ class PanelLayoutManagerTest : public test::AshTestBase { return CreateTestWindowInShellWithBounds(bounds); } - aura::Window* CreatePanelWindow(const gfx::Rect& bounds) { + aura::Window* CreatePanelWindowWithDelegate(aura::WindowDelegate* delegate, + const gfx::Rect& bounds) { aura::Window* window = CreateTestWindowInShellWithDelegateAndType( - NULL, ui::wm::WINDOW_TYPE_PANEL, 0, bounds); + delegate, ui::wm::WINDOW_TYPE_PANEL, 0, bounds); test::TestShelfDelegate* shelf_delegate = test::TestShelfDelegate::instance(); shelf_delegate->AddShelfItem(window); @@ -72,6 +74,10 @@ class PanelLayoutManagerTest : public test::AshTestBase { return window; } + aura::Window* CreatePanelWindow(const gfx::Rect& bounds) { + return CreatePanelWindowWithDelegate(NULL, bounds); + } + aura::Window* GetPanelContainer(aura::Window* panel) { return Shell::GetContainer(panel->GetRootWindow(), kShellWindowId_PanelContainer); @@ -784,6 +790,73 @@ TEST_F(PanelLayoutManagerTest, PanelsHideAndRestoreWithShelf) { EXPECT_TRUE(w3->IsVisible()); } +// Verifies that touches along the attached edge of a panel do not +// target the panel itself. +TEST_F(PanelLayoutManagerTest, TouchHitTestPanel) { + aura::test::TestWindowDelegate delegate; + scoped_ptr w( + CreatePanelWindowWithDelegate(&delegate, gfx::Rect(0, 0, 200, 200))); + ui::EventTarget* root = w->GetRootWindow(); + ui::EventTargeter* targeter = root->GetEventTargeter(); + + // Note that the constants used in the touch locations below are + // arbitrarily-selected small numbers which will ensure the point is + // within the default extended region surrounding the panel. This value + // is calculated as + // kResizeOutsideBoundsSize * kResizeOutsideBoundsScaleForTouch + // in src/ash/root_window_controller.cc. + + // Hit test outside the right edge with a bottom-aligned shelf. + SetAlignment(Shell::GetPrimaryRootWindow(), SHELF_ALIGNMENT_BOTTOM); + gfx::Rect bounds(w->bounds()); + ui::TouchEvent touch(ui::ET_TOUCH_PRESSED, + gfx::Point(bounds.right() + 3, bounds.y() + 2), + 0, ui::EventTimeForNow()); + ui::EventTarget* target = targeter->FindTargetForEvent(root, &touch); + EXPECT_EQ(w.get(), target); + + // Hit test outside the bottom edge with a bottom-aligned shelf. + touch.set_location(gfx::Point(bounds.x() + 6, bounds.bottom() + 5)); + target = targeter->FindTargetForEvent(root, &touch); + EXPECT_NE(w.get(), target); + + // Hit test outside the bottom edge with a right-aligned shelf. + SetAlignment(Shell::GetPrimaryRootWindow(), SHELF_ALIGNMENT_RIGHT); + bounds = w->bounds(); + touch.set_location(gfx::Point(bounds.x() + 6, bounds.bottom() + 5)); + target = targeter->FindTargetForEvent(root, &touch); + EXPECT_EQ(w.get(), target); + + // Hit test outside the right edge with a right-aligned shelf. + touch.set_location(gfx::Point(bounds.right() + 3, bounds.y() + 2)); + target = targeter->FindTargetForEvent(root, &touch); + EXPECT_NE(w.get(), target); + + // Hit test outside the top edge with a left-aligned shelf. + SetAlignment(Shell::GetPrimaryRootWindow(), SHELF_ALIGNMENT_LEFT); + bounds = w->bounds(); + touch.set_location(gfx::Point(bounds.x() + 4, bounds.y() - 6)); + target = targeter->FindTargetForEvent(root, &touch); + EXPECT_EQ(w.get(), target); + + // Hit test outside the left edge with a left-aligned shelf. + touch.set_location(gfx::Point(bounds.x() - 1, bounds.y() + 5)); + target = targeter->FindTargetForEvent(root, &touch); + EXPECT_NE(w.get(), target); + + // Hit test outside the left edge with a top-aligned shelf. + SetAlignment(Shell::GetPrimaryRootWindow(), SHELF_ALIGNMENT_TOP); + bounds = w->bounds(); + touch.set_location(gfx::Point(bounds.x() - 1, bounds.y() + 5)); + target = targeter->FindTargetForEvent(root, &touch); + EXPECT_EQ(w.get(), target); + + // Hit test outside the top edge with a top-aligned shelf. + touch.set_location(gfx::Point(bounds.x() + 4, bounds.y() - 6)); + target = targeter->FindTargetForEvent(root, &touch); + EXPECT_NE(w.get(), target); +} + INSTANTIATE_TEST_CASE_P(LtrRtl, PanelLayoutManagerTextDirectionTest, testing::Bool()); diff --git a/ash/wm/workspace_controller_unittest.cc b/ash/wm/workspace_controller_unittest.cc index 6056a2f..a2556cf 100644 --- a/ash/wm/workspace_controller_unittest.cc +++ b/ash/wm/workspace_controller_unittest.cc @@ -1461,8 +1461,8 @@ TEST_F(WorkspaceControllerTest, WindowEdgeHitTest) { } } -// Verifies events targeting just outside the window edges for panels. -TEST_F(WorkspaceControllerTest, WindowEdgeHitTestPanel) { +// Verifies mouse event targeting just outside the window edges for panels. +TEST_F(WorkspaceControllerTest, WindowEdgeMouseHitTestPanel) { aura::test::TestWindowDelegate delegate; scoped_ptr window(CreateTestPanel(&delegate, gfx::Rect(20, 10, 100, 50))); @@ -1491,10 +1491,38 @@ TEST_F(WorkspaceControllerTest, WindowEdgeHitTestPanel) { EXPECT_EQ(window.get(), target); else EXPECT_NE(window.get(), target); + } +} +// Verifies touch event targeting just outside the window edges for panels. +// The shelf is aligned to the bottom by default, and so touches just below +// the bottom edge of the panel should not target the panel itself because +// an AttachedPanelWindowTargeter is installed on the panel container. +TEST_F(WorkspaceControllerTest, WindowEdgeTouchHitTestPanel) { + aura::test::TestWindowDelegate delegate; + scoped_ptr window(CreateTestPanel(&delegate, + gfx::Rect(20, 10, 100, 50))); + ui::EventTarget* root = window->GetRootWindow(); + ui::EventTargeter* targeter = root->GetEventTargeter(); + const gfx::Rect bounds = window->bounds(); + const int kNumPoints = 5; + struct { + const char* direction; + gfx::Point location; + bool is_target_hit; + } points[kNumPoints] = { + { "left", gfx::Point(bounds.x() - 2, bounds.y() + 10), true }, + { "top", gfx::Point(bounds.x() + 10, bounds.y() - 2), true }, + { "right", gfx::Point(bounds.right() + 2, bounds.y() + 10), true }, + { "bottom", gfx::Point(bounds.x() + 10, bounds.bottom() + 2), false }, + { "outside", gfx::Point(bounds.x() + 10, bounds.y() - 31), false }, + }; + for (int i = 0; i < kNumPoints; ++i) { + SCOPED_TRACE(points[i].direction); + const gfx::Point& location = points[i].location; ui::TouchEvent touch(ui::ET_TOUCH_PRESSED, location, 0, ui::EventTimeForNow()); - target = targeter->FindTargetForEvent(root, &touch); + ui::EventTarget* target = targeter->FindTargetForEvent(root, &touch); if (points[i].is_target_hit) EXPECT_EQ(window.get(), target); else -- cgit v1.1