Skip to content

Commit

Permalink
aura: Add an EasyResizeWindowTargeter.
Browse files Browse the repository at this point in the history
Add EasyResizeWindowTargeter to allow easily resizing windows with mouse/touch
with the new event-dispatch code. This will eventually allow getting rid of
Window::SetHitTestBoundsOverrideOuter() once the new event-dispatch code is used
for all event types. This patch installs an EasyResizeWindowTargeter for the
shelf and status-area widgets. Subsequent CLs will install such targeters for the
toplevel windows too.

Collateral change includes adding some dependencies on wm_public (in DEPS and gyp).

BUG=318879
R=ben@chromium.org

Review URL: https://codereview.chromium.org/118553004

git-svn-id: svn://svn.chromium.org/chrome/trunk/src@243177 0039d316-1c4b-4281-b951-d872f2087c98
  • Loading branch information
sadrul@chromium.org committed Jan 6, 2014
1 parent 4ef896f commit 3b66a3c
Show file tree
Hide file tree
Showing 10 changed files with 339 additions and 15 deletions.
1 change: 1 addition & 0 deletions ash/ash.gyp
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@
'../ui/views/controls/webview/webview.gyp:webview',
'../ui/views/views.gyp:views',
'../ui/web_dialogs/web_dialogs.gyp:web_dialogs',
'../ui/wm/wm.gyp:wm_public',
'../url/url.gyp:url_lib',
'ash_strings.gyp:ash_strings',
'ash_resources',
Expand Down
2 changes: 2 additions & 0 deletions ash/shelf/shelf_layout_manager.h
Original file line number Diff line number Diff line change
Expand Up @@ -315,6 +315,7 @@ class ASH_EXPORT ShelfLayoutManager :
ShelfVisibilityState visibility_state) const;

// Updates the hit test bounds override for shelf and status area.
// TODO(sad): Remove this (crbug.com/318879)
void UpdateHitTestBounds();

// Returns true if |window| is a descendant of the shelf.
Expand All @@ -336,6 +337,7 @@ class ASH_EXPORT ShelfLayoutManager :
DockedWindowLayoutManagerObserver::Reason reason) OVERRIDE;

// Generates insets for inward edge based on the current shelf alignment.
// TODO(sad): Remove this (crbug.com/318879)
gfx::Insets GetInsetsForAlignment(int distance) const;

// The RootWindow is cached so that we don't invoke Shell::GetInstance() from
Expand Down
77 changes: 77 additions & 0 deletions ash/shelf/shelf_widget.cc
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
#include "ui/views/accessible_pane_view.h"
#include "ui/views/widget/widget.h"
#include "ui/views/widget/widget_delegate.h"
#include "ui/wm/public/easy_resize_window_targeter.h"

namespace {
// Size of black border at bottom (or side) of shelf.
Expand Down Expand Up @@ -233,6 +234,77 @@ void DimmerView::DimmerEventFilter::OnTouchEvent(ui::TouchEvent* event) {
touch_inside_ = touch_inside;
}

using ash::internal::ShelfLayoutManager;

// ShelfWindowTargeter makes it easier to resize windows with the mouse when the
// window-edge slightly overlaps with the shelf edge. The targeter also makes it
// easier to drag the shelf out with touch while it is hidden.
class ShelfWindowTargeter : public wm::EasyResizeWindowTargeter,
public ash::ShelfLayoutManagerObserver {
public:
ShelfWindowTargeter(aura::Window* container,
ShelfLayoutManager* shelf)
: wm::EasyResizeWindowTargeter(container, gfx::Insets(), gfx::Insets()),
shelf_(shelf) {
WillChangeVisibilityState(shelf_->visibility_state());
shelf_->AddObserver(this);
}

virtual ~ShelfWindowTargeter() {
// |shelf_| may have been destroyed by this time.
if (shelf_)
shelf_->RemoveObserver(this);
}

private:
gfx::Insets GetInsetsForAlignment(int distance,
ash::ShelfAlignment alignment) {
switch (alignment) {
case ash::SHELF_ALIGNMENT_BOTTOM:
return gfx::Insets(distance, 0, 0, 0);
case ash::SHELF_ALIGNMENT_LEFT:
return gfx::Insets(0, 0, 0, distance);
case ash::SHELF_ALIGNMENT_RIGHT:
return gfx::Insets(0, distance, 0, 0);
case ash::SHELF_ALIGNMENT_TOP:
return gfx::Insets(0, 0, distance, 0);
}
NOTREACHED();
return gfx::Insets();
}

// ash::ShelfLayoutManagerObserver:
virtual void WillDeleteShelf() OVERRIDE {
shelf_ = NULL;
}

virtual void WillChangeVisibilityState(
ash::ShelfVisibilityState new_state) OVERRIDE {
gfx::Insets mouse_insets;
gfx::Insets touch_insets;
if (new_state == ash::SHELF_VISIBLE) {
// Let clicks at the very top of the shelf through so windows can be
// resized with the bottom-right corner and bottom edge.
mouse_insets = GetInsetsForAlignment(
ShelfLayoutManager::kWorkspaceAreaVisibleInset,
shelf_->GetAlignment());
} else if (new_state == ash::SHELF_AUTO_HIDE) {
// Extend the touch hit target out a bit to allow users to drag shelf out
// while hidden.
touch_insets = GetInsetsForAlignment(
-ShelfLayoutManager::kWorkspaceAreaAutoHideInset,
shelf_->GetAlignment());
}

set_mouse_extend(mouse_insets);
set_touch_extend(touch_insets);
}

ShelfLayoutManager* shelf_;

DISALLOW_COPY_AND_ASSIGN(ShelfWindowTargeter);
};

} // namespace

namespace ash {
Expand Down Expand Up @@ -564,6 +636,11 @@ ShelfWidget::ShelfWidget(aura::Window* shelf_container,
status_container->SetLayoutManager(
new internal::StatusAreaLayoutManager(this));

shelf_container->set_event_targeter(scoped_ptr<ui::EventTargeter>(new
ShelfWindowTargeter(shelf_container, shelf_layout_manager_)));
status_container->set_event_targeter(scoped_ptr<ui::EventTargeter>(new
ShelfWindowTargeter(status_container, shelf_layout_manager_)));

views::Widget::AddObserver(this);
}

Expand Down
117 changes: 116 additions & 1 deletion ash/shelf/shelf_widget_unittest.cc
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
#include "ash/test/shelf_view_test_api.h"
#include "ash/wm/window_util.h"
#include "ui/aura/root_window.h"
#include "ui/events/event_utils.h"
#include "ui/gfx/display.h"
#include "ui/gfx/screen.h"
#include "ui/views/corewm/corewm_switches.h"
Expand Down Expand Up @@ -189,6 +190,120 @@ TEST_F(ShelfWidgetTest, ShelfInitiallySizedAfterLogin) {
shelf_widget->GetContentsView()->width() -
test::ShelfTestAPI(shelf).shelf_view()->width());
}
#endif
#endif // defined(OS_CHROMEOS)

// Tests that the shelf lets mouse-events close to the edge fall through to the
// window underneath.
TEST_F(ShelfWidgetTest, ShelfEdgeOverlappingWindowHitTestMouse) {
ShelfWidget* shelf_widget = GetShelfWidget();
gfx::Rect shelf_bounds = shelf_widget->GetWindowBoundsInScreen();
EXPECT_TRUE(!shelf_bounds.IsEmpty());
internal::ShelfLayoutManager* shelf_layout_manager =
shelf_widget->shelf_layout_manager();
ASSERT_TRUE(shelf_layout_manager);
EXPECT_EQ(SHELF_VISIBLE, shelf_layout_manager->visibility_state());

// Create a Widget which overlaps with the shelf in the top edge.
const int kOverlapSize = 15;
const int kWindowHeight = 200;
views::Widget* widget = new views::Widget;
views::Widget::InitParams params(views::Widget::InitParams::TYPE_WINDOW);
params.bounds = gfx::Rect(0, shelf_bounds.y() - kWindowHeight + kOverlapSize,
200, kWindowHeight);
params.context = CurrentContext();
// Widget is now owned by the parent window.
widget->Init(params);
widget->Show();
gfx::Rect widget_bounds = widget->GetWindowBoundsInScreen();
EXPECT_TRUE(widget_bounds.Intersects(shelf_bounds));


ui::EventTarget* root = widget->GetNativeWindow()->GetRootWindow();
ui::EventTargeter* targeter = root->GetEventTargeter();
{
// Create a mouse-event targetting the top of the shelf widget. The
// window-targeter should find |widget| as the target (instead of the
// shelf).
gfx::Point event_location(20, shelf_bounds.y() + 1);
ui::MouseEvent mouse(ui::ET_MOUSE_MOVED, event_location, event_location,
ui::EF_NONE, ui::EF_NONE);
ui::EventTarget* target = targeter->FindTargetForEvent(root, &mouse);
EXPECT_EQ(widget->GetNativeWindow(), target);
}

// Now auto-hide (hidden) the shelf.
shelf_layout_manager->SetAutoHideBehavior(SHELF_AUTO_HIDE_BEHAVIOR_ALWAYS);
shelf_layout_manager->LayoutShelf();
EXPECT_EQ(SHELF_AUTO_HIDE, shelf_layout_manager->visibility_state());
EXPECT_EQ(SHELF_AUTO_HIDE_HIDDEN, shelf_layout_manager->auto_hide_state());
shelf_bounds = shelf_widget->GetWindowBoundsInScreen();
EXPECT_TRUE(!shelf_bounds.IsEmpty());

// Move |widget| so it still overlaps the shelf.
widget->SetBounds(gfx::Rect(0, shelf_bounds.y() - kWindowHeight +
kOverlapSize, 200, kWindowHeight));
widget_bounds = widget->GetWindowBoundsInScreen();
EXPECT_TRUE(widget_bounds.Intersects(shelf_bounds));
{
// Create a mouse-event targetting the top of the shelf widget. This time,
// window-target should find the shelf as the target.
gfx::Point event_location(20, shelf_bounds.y() + 1);
ui::MouseEvent mouse(ui::ET_MOUSE_MOVED, event_location, event_location,
ui::EF_NONE, ui::EF_NONE);
ui::EventTarget* target = targeter->FindTargetForEvent(root, &mouse);
EXPECT_EQ(shelf_widget->GetNativeWindow(), target);
}
}

// Tests that the shelf has a slightly larger hit-region for touch-events when
// it's in the auto-hidden state.
TEST_F(ShelfWidgetTest, HiddenShelfHitTestTouch) {
ShelfWidget* shelf_widget = GetShelfWidget();
gfx::Rect shelf_bounds = shelf_widget->GetWindowBoundsInScreen();
EXPECT_TRUE(!shelf_bounds.IsEmpty());
internal::ShelfLayoutManager* shelf_layout_manager =
shelf_widget->shelf_layout_manager();
ASSERT_TRUE(shelf_layout_manager);
EXPECT_EQ(SHELF_VISIBLE, shelf_layout_manager->visibility_state());

// Create a widget to make sure that the shelf does auto-hide.
views::Widget* widget = new views::Widget;
views::Widget::InitParams params(views::Widget::InitParams::TYPE_WINDOW);
params.bounds = gfx::Rect(0, 0, 200, 200);
params.context = CurrentContext();
// Widget is now owned by the parent window.
widget->Init(params);
widget->Show();

ui::EventTarget* root = shelf_widget->GetNativeWindow()->GetRootWindow();
ui::EventTargeter* targeter = root->GetEventTargeter();
// Touch just over the shelf. Since the shelf is visible, the window-targeter
// should not find the shelf as the target.
{
gfx::Point event_location(20, shelf_bounds.y() - 1);
ui::TouchEvent touch(ui::ET_TOUCH_PRESSED, event_location, 0,
ui::EventTimeForNow());
EXPECT_NE(shelf_widget->GetNativeWindow(),
targeter->FindTargetForEvent(root, &touch));
}

// Now auto-hide (hidden) the shelf.
shelf_layout_manager->SetAutoHideBehavior(SHELF_AUTO_HIDE_BEHAVIOR_ALWAYS);
shelf_layout_manager->LayoutShelf();
EXPECT_EQ(SHELF_AUTO_HIDE, shelf_layout_manager->visibility_state());
EXPECT_EQ(SHELF_AUTO_HIDE_HIDDEN, shelf_layout_manager->auto_hide_state());
shelf_bounds = shelf_widget->GetWindowBoundsInScreen();
EXPECT_TRUE(!shelf_bounds.IsEmpty());

// Touch just over the shelf again. This time, the targeter should find the
// shelf as the target.
{
gfx::Point event_location(20, shelf_bounds.y() - 1);
ui::TouchEvent touch(ui::ET_TOUCH_PRESSED, event_location, 0,
ui::EventTimeForNow());
EXPECT_EQ(shelf_widget->GetNativeWindow(),
targeter->FindTargetForEvent(root, &touch));
}
}

} // namespace ash
37 changes: 25 additions & 12 deletions ui/aura/window_targeter.cc
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,29 @@ namespace aura {
WindowTargeter::WindowTargeter() {}
WindowTargeter::~WindowTargeter() {}

bool WindowTargeter::WindowCanAcceptEvent(aura::Window* window,
const ui::LocatedEvent& event) const {
if (!window->IsVisible())
return false;
if (window->ignore_events())
return false;
client::EventClient* client = client::GetEventClient(window->GetRootWindow());
if (client && !client->CanProcessEventsWithinSubtree(window))
return false;

Window* parent = window->parent();
if (parent && parent->delegate_ && !parent->delegate_->
ShouldDescendIntoChildForEventHandling(window, event.location())) {
return false;
}
return true;
}

bool WindowTargeter::EventLocationInsideBounds(
aura::Window* window, const ui::LocatedEvent& event) const {
return window->bounds().Contains(event.location());
}

ui::EventTarget* WindowTargeter::FindTargetForEvent(ui::EventTarget* root,
ui::Event* event) {
if (event->IsKeyEvent()) {
Expand All @@ -42,20 +65,10 @@ bool WindowTargeter::SubtreeShouldBeExploredForEvent(
ui::EventTarget* root,
const ui::LocatedEvent& event) {
Window* window = static_cast<Window*>(root);
if (!window->IsVisible())
return false;
if (window->ignore_events())
return false;
client::EventClient* client = client::GetEventClient(window->GetRootWindow());
if (client && !client->CanProcessEventsWithinSubtree(window))
if (!WindowCanAcceptEvent(window, event))
return false;

Window* parent = window->parent();
if (parent && parent->delegate_ && !parent->delegate_->
ShouldDescendIntoChildForEventHandling(window, event.location())) {
return false;
}
return window->bounds().Contains(event.location());
return EventLocationInsideBounds(window, event);
}

ui::EventTarget* WindowTargeter::FindTargetForLocatedEvent(
Expand Down
12 changes: 11 additions & 1 deletion ui/aura/window_targeter.h
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,28 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "ui/aura/aura_export.h"
#include "ui/events/event_targeter.h"

namespace aura {

class Window;

class WindowTargeter : public ui::EventTargeter {
class AURA_EXPORT WindowTargeter : public ui::EventTargeter {
public:
WindowTargeter();
virtual ~WindowTargeter();

protected:
bool WindowCanAcceptEvent(aura::Window* window,
const ui::LocatedEvent& event) const;

// Returns whether the location of the event is in an actionable region of the
// window. Note that the location etc. of |event| is in the |window|'s
// parent's coordinate system.
virtual bool EventLocationInsideBounds(aura::Window* window,
const ui::LocatedEvent& event) const;

// ui::EventTargeter:
virtual ui::EventTarget* FindTargetForEvent(ui::EventTarget* root,
ui::Event* event) OVERRIDE;
Expand Down
4 changes: 4 additions & 0 deletions ui/wm/DEPS
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
include_rules = [
"+ui/aura",
"+ui/gfx",
]
48 changes: 48 additions & 0 deletions ui/wm/core/easy_resize_window_targeter.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
// 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 "ui/wm/public/easy_resize_window_targeter.h"

#include "ui/aura/window.h"
#include "ui/gfx/geometry/insets_f.h"
#include "ui/gfx/geometry/rect.h"

namespace wm {

EasyResizeWindowTargeter::EasyResizeWindowTargeter(
aura::Window* container,
const gfx::Insets& mouse_extend,
const gfx::Insets& touch_extend)
: container_(container),
mouse_extend_(mouse_extend),
touch_extend_(touch_extend) {
}

EasyResizeWindowTargeter::~EasyResizeWindowTargeter() {
}

bool EasyResizeWindowTargeter::EventLocationInsideBounds(
aura::Window* window,
const ui::LocatedEvent& event) const {
// Use the extended bounds only for immediate child windows of |container_|.
// Use the default targetter otherwise.
if (window->parent() == container_ && (!window->transient_parent() ||
window->transient_parent() == container_)) {
gfx::RectF bounds(window->bounds());
gfx::Transform transform = window->layer()->transform();
transform.TransformRect(&bounds);
if (event.IsTouchEvent() || event.IsGestureEvent()) {
bounds.Inset(touch_extend_);
} else {
bounds.Inset(mouse_extend_);
}

// Note that |event|'s location is in the |container_|'s coordinate system,
// as is |bounds|.
return bounds.Contains(event.location());
}
return WindowTargeter::EventLocationInsideBounds(window, event);
}

} // namespace wm
Loading

0 comments on commit 3b66a3c

Please sign in to comment.