Commit 59556033 authored by pkotwicz@chromium.org's avatar pkotwicz@chromium.org

This CL keeps the alternate style size button pressed during a drag when:

- the user has dragged to the snap left / snap right buttons
- the user has dragged back to the size button

BUG=344411
TEST=AlternateFrameSizeButtonTest.*

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

git-svn-id: svn://svn.chromium.org/chrome/trunk/src@252859 0039d316-1c4b-4281-b951-d872f2087c98
parent 68e7cb52
......@@ -21,10 +21,22 @@ namespace {
// right.
const int kSetButtonsToSnapModeDelayMs = 150;
// The amount that a user can overshoot the snap left / snap right button and
// keep the snap left / snap right button pressed.
const int kPressedHitBoundsExpandX = 200;
const int kPressedHitBoundsExpandY = 50;
// The amount that a user can overshoot one of the caption buttons while in
// "snap mode" and keep the button hovered/pressed.
const int kMaxOvershootX = 200;
const int kMaxOvershootY = 50;
// Returns true if a mouse drag while in "snap mode" at |location_in_screen|
// would hover/press |button| or keep it hovered/pressed.
bool HitTestButton(const ash::FrameCaptionButton* button,
const gfx::Point& location_in_screen) {
gfx::Rect expanded_bounds_in_screen = button->GetBoundsInScreen();
if (button->state() == views::Button::STATE_HOVERED ||
button->state() == views::Button::STATE_PRESSED) {
expanded_bounds_in_screen.Inset(-kMaxOvershootX, -kMaxOvershootY);
}
return expanded_bounds_in_screen.Contains(location_in_screen);
}
} // namespace
......@@ -59,8 +71,12 @@ bool AlternateFrameSizeButton::OnMousePressed(const ui::MouseEvent& event) {
}
bool AlternateFrameSizeButton::OnMouseDragged(const ui::MouseEvent& event) {
UpdatePressedButton(event);
FrameCaptionButton::OnMouseDragged(event);
UpdateSnapType(event);
// By default a FrameCaptionButton reverts to STATE_NORMAL once the mouse
// leaves its bounds. Skip FrameCaptionButton's handling when
// |in_snap_mode_| == true because we want different behavior.
if (!in_snap_mode_)
FrameCaptionButton::OnMouseDragged(event);
return true;
}
......@@ -74,6 +90,12 @@ void AlternateFrameSizeButton::OnMouseCaptureLost() {
FrameCaptionButton::OnMouseCaptureLost();
}
void AlternateFrameSizeButton::OnMouseMoved(const ui::MouseEvent& event) {
// Ignore any synthetic mouse moves during a drag.
if (!in_snap_mode_)
FrameCaptionButton::OnMouseMoved(event);
}
void AlternateFrameSizeButton::OnGestureEvent(ui::GestureEvent* event) {
if (event->details().touch_points() > 1) {
SetButtonsToNormalMode(AlternateFrameSizeButtonDelegate::ANIMATE_YES);
......@@ -89,7 +111,7 @@ void AlternateFrameSizeButton::OnGestureEvent(ui::GestureEvent* event) {
if (event->type() == ui::ET_GESTURE_SCROLL_BEGIN ||
event->type() == ui::ET_GESTURE_SCROLL_UPDATE) {
UpdatePressedButton(*event);
UpdateSnapType(*event);
event->SetHandled();
return;
}
......@@ -134,8 +156,7 @@ void AlternateFrameSizeButton::SetButtonsToSnapMode() {
AlternateFrameSizeButtonDelegate::ANIMATE_YES);
}
void AlternateFrameSizeButton::UpdatePressedButton(
const ui::LocatedEvent& event) {
void AlternateFrameSizeButton::UpdateSnapType(const ui::LocatedEvent& event) {
if (!in_snap_mode_) {
// Set the buttons adjacent to the size button to snap left and right early
// if the user drags past the drag threshold.
......@@ -153,16 +174,16 @@ void AlternateFrameSizeButton::UpdatePressedButton(
gfx::Point event_location_in_screen(event.location());
views::View::ConvertPointToScreen(this, &event_location_in_screen);
const FrameCaptionButton* to_hover =
GetButtonToHover(event_location_in_screen);
bool press_size_button =
to_hover || HitTestButton(this, event_location_in_screen);
delegate_->SetHoveredAndPressedButtons(
to_hover, press_size_button ? this : NULL);
gfx::Insets pressed_button_hittest_insets(-kPressedHitBoundsExpandY,
-kPressedHitBoundsExpandX,
-kPressedHitBoundsExpandY,
-kPressedHitBoundsExpandX);
const FrameCaptionButton* pressed_button = delegate_->PressButtonAt(
event_location_in_screen, pressed_button_hittest_insets);
snap_type_ = SNAP_NONE;
if (pressed_button) {
switch (pressed_button->icon()) {
if (to_hover) {
switch (to_hover->icon()) {
case CAPTION_BUTTON_ICON_LEFT_SNAPPED:
snap_type_ = SNAP_LEFT;
break;
......@@ -170,8 +191,6 @@ void AlternateFrameSizeButton::UpdatePressedButton(
snap_type_ = SNAP_RIGHT;
break;
case CAPTION_BUTTON_ICON_MAXIMIZE_RESTORE:
// snap_type_ = SNAP_NONE
break;
case CAPTION_BUTTON_ICON_MINIMIZE:
case CAPTION_BUTTON_ICON_CLOSE:
case CAPTION_BUTTON_ICON_COUNT:
......@@ -200,10 +219,22 @@ void AlternateFrameSizeButton::UpdatePressedButton(
}
}
const FrameCaptionButton* AlternateFrameSizeButton::GetButtonToHover(
const gfx::Point& event_location_in_screen) const {
const FrameCaptionButton* closest_button = delegate_->GetButtonClosestTo(
event_location_in_screen);
if ((closest_button->icon() == CAPTION_BUTTON_ICON_LEFT_SNAPPED ||
closest_button->icon() == CAPTION_BUTTON_ICON_RIGHT_SNAPPED) &&
HitTestButton(closest_button, event_location_in_screen)) {
return closest_button;
}
return NULL;
}
bool AlternateFrameSizeButton::CommitSnap(const ui::LocatedEvent& event) {
// The position of |event| may be different than the position of the previous
// event.
UpdatePressedButton(event);
UpdateSnapType(event);
if (in_snap_mode_ &&
(snap_type_ == SNAP_LEFT || snap_type_ == SNAP_RIGHT)) {
......
......@@ -26,12 +26,11 @@ class PhantomWindowController;
// When the mouse is pressed over the size button or the size button is touched:
// - The minimize and close buttons are set to snap left and snap right
// respectively.
// - The pressed button is updated during the drag to reflect the button
// underneath the mouse cursor. (The size button is potentially unpressed).
// When the drag terminates, the action for the pressed button is executed.
// For the sake of simplicity, the size button is the event handler for a click
// starting on the size button and the entire drag (including when the size
// button is unpressed).
// - The size button stays pressed while the mouse is over the buttons to snap
// left and to snap right. The button underneath the mouse is hovered.
// When the drag terminates, the action for the button underneath the mouse
// is executed. For the sake of simplicity, the size button is the event
// handler for a click starting on the size button and the entire drag.
class ASH_EXPORT AlternateFrameSizeButton : public FrameCaptionButton {
public:
AlternateFrameSizeButton(views::ButtonListener* listener,
......@@ -45,6 +44,7 @@ class ASH_EXPORT AlternateFrameSizeButton : public FrameCaptionButton {
virtual bool OnMouseDragged(const ui::MouseEvent& event) OVERRIDE;
virtual void OnMouseReleased(const ui::MouseEvent& event) OVERRIDE;
virtual void OnMouseCaptureLost() OVERRIDE;
virtual void OnMouseMoved(const ui::MouseEvent& event) OVERRIDE;
virtual void OnGestureEvent(ui::GestureEvent* event) OVERRIDE;
void set_delay_to_set_buttons_to_snap_mode(int delay_ms) {
......@@ -58,8 +58,14 @@ class ASH_EXPORT AlternateFrameSizeButton : public FrameCaptionButton {
// Sets the buttons adjacent to the size button to snap left and right.
void SetButtonsToSnapMode();
// Updates the pressed button based on |event_location|.
void UpdatePressedButton(const ui::LocatedEvent& event);
// Updates |snap_type_|, whether the size button is pressed and whether any
// other buttons are hovered.
void UpdateSnapType(const ui::LocatedEvent& event);
// Returns the button which should be hovered (if any) while in "snap mode"
// for |event_location_in_screen|.
const FrameCaptionButton* GetButtonToHover(
const gfx::Point& event_location_in_screen) const;
// Snaps |frame_| according to |snap_type_|. Returns true if |frame_| was
// snapped.
......@@ -92,8 +98,9 @@ class ASH_EXPORT AlternateFrameSizeButton : public FrameCaptionButton {
// right.
bool in_snap_mode_;
// The action of the currently pressed button. If |snap_type_| == SNAP_NONE,
// the size button's default action is run when clicked.
// The action to execute when the drag/click is ended. If
// |snap_type_| == SNAP_NONE, the size button's default action is run when the
// drag/click is ended.
SnapType snap_type_;
// Displays a preview of how the window's bounds will change as a result of
......
......@@ -11,6 +11,7 @@
namespace gfx {
class Insets;
class Point;
class Vector2d;
}
namespace ash {
......@@ -38,15 +39,15 @@ class ASH_EXPORT AlternateFrameSizeButtonDelegate {
CaptionButtonIcon right_button_action,
Animate animate) = 0;
// Presses the button at |position_in_screen| and unpresses any other pressed
// caption buttons.
// |pressed_button_hittest_insets| indicates how much the hittest insets for
// the currently pressed button should be expanded if no button was found at
// |position_in_screen| using the normal button hittest insets.
// Returns the button which was pressed.
virtual const FrameCaptionButton* PressButtonAt(
const gfx::Point& position_in_screen,
const gfx::Insets& pressed_button_hittest_insets) const = 0;
// Returns the button closest to |position_in_screen|.
virtual const FrameCaptionButton* GetButtonClosestTo(
const gfx::Point& position_in_screen) const = 0;
// Sets |to_hover| and |to_pressed| to STATE_HOVERED and STATE_PRESSED
// respectively. All other buttons are to set to STATE_NORMAL.
virtual void SetHoveredAndPressedButtons(
const FrameCaptionButton* to_hover,
const FrameCaptionButton* to_press) = 0;
protected:
virtual ~AlternateFrameSizeButtonDelegate() {}
......
......@@ -322,11 +322,11 @@ TEST_F(AlternateFrameSizeButtonTest, ResetButtonsAfterClick) {
EXPECT_EQ(CAPTION_BUTTON_ICON_LEFT_SNAPPED, minimize_button()->icon());
EXPECT_EQ(CAPTION_BUTTON_ICON_RIGHT_SNAPPED, close_button()->icon());
// Dragging the mouse over the minimize button should press the minimize
// Dragging the mouse over the minimize button should hover the minimize
// button and the minimize and close button icons should stay changed.
generator.MoveMouseTo(CenterPointInScreen(minimize_button()));
EXPECT_EQ(views::Button::STATE_PRESSED, minimize_button()->state());
EXPECT_EQ(views::Button::STATE_NORMAL, size_button()->state());
EXPECT_EQ(views::Button::STATE_HOVERED, minimize_button()->state());
EXPECT_EQ(views::Button::STATE_PRESSED, size_button()->state());
EXPECT_EQ(views::Button::STATE_NORMAL, close_button()->state());
EXPECT_EQ(CAPTION_BUTTON_ICON_LEFT_SNAPPED, minimize_button()->icon());
EXPECT_EQ(CAPTION_BUTTON_ICON_RIGHT_SNAPPED, close_button()->icon());
......@@ -375,5 +375,43 @@ TEST_F(AlternateFrameSizeButtonTest, ResetButtonsAfterClick) {
EXPECT_EQ(CAPTION_BUTTON_ICON_CLOSE, close_button()->icon());
}
// Test that the size button is pressed whenever the snap left/right buttons
// are hovered.
TEST_F(AlternateFrameSizeButtonTest, SizeButtonPressedWhenSnapButtonHovered) {
EXPECT_EQ(CAPTION_BUTTON_ICON_MINIMIZE, minimize_button()->icon());
EXPECT_EQ(CAPTION_BUTTON_ICON_CLOSE, close_button()->icon());
EXPECT_TRUE(AllButtonsInNormalState());
// Pressing the size button should result in the size button being pressed and
// the minimize and close button icons changing.
aura::test::EventGenerator& generator = GetEventGenerator();
generator.MoveMouseTo(CenterPointInScreen(size_button()));
generator.PressLeftButton();
EXPECT_EQ(views::Button::STATE_NORMAL, minimize_button()->state());
EXPECT_EQ(views::Button::STATE_PRESSED, size_button()->state());
EXPECT_EQ(views::Button::STATE_NORMAL, close_button()->state());
EXPECT_EQ(CAPTION_BUTTON_ICON_LEFT_SNAPPED, minimize_button()->icon());
EXPECT_EQ(CAPTION_BUTTON_ICON_RIGHT_SNAPPED, close_button()->icon());
// Dragging the mouse over the minimize button (snap left button) should hover
// the minimize button and keep the size button pressed.
generator.MoveMouseTo(CenterPointInScreen(minimize_button()));
EXPECT_EQ(views::Button::STATE_HOVERED, minimize_button()->state());
EXPECT_EQ(views::Button::STATE_PRESSED, size_button()->state());
EXPECT_EQ(views::Button::STATE_NORMAL, close_button()->state());
// Moving the mouse far away from the caption buttons and then moving it over
// the close button (snap right button) should hover the close button and
// keep the size button pressed.
const gfx::Rect& kWorkAreaBoundsInScreen =
ash::Shell::GetScreen()->GetPrimaryDisplay().work_area();
generator.MoveMouseTo(kWorkAreaBoundsInScreen.bottom_left());
EXPECT_TRUE(AllButtonsInNormalState());
generator.MoveMouseTo(CenterPointInScreen(close_button()));
EXPECT_EQ(views::Button::STATE_NORMAL, minimize_button()->state());
EXPECT_EQ(views::Button::STATE_PRESSED, size_button()->state());
EXPECT_EQ(views::Button::STATE_HOVERED, close_button()->state());
}
} // namespace test
} // namespace ash
......@@ -4,6 +4,8 @@
#include "ash/wm/caption_buttons/frame_caption_button_container_view.h"
#include <cmath>
#include "ash/ash_switches.h"
#include "ash/metrics/user_metrics_recorder.h"
#include "ash/shell.h"
......@@ -262,43 +264,51 @@ void FrameCaptionButtonContainerView::SetButtonIcons(
SetButtonIcon(close_button_, close_button_icon, animate);
}
const FrameCaptionButton*
FrameCaptionButtonContainerView::PressButtonAt(
const gfx::Point& position_in_screen,
const gfx::Insets& pressed_hittest_outer_insets) const {
DCHECK(switches::UseAlternateFrameCaptionButtonStyle());
const FrameCaptionButton* FrameCaptionButtonContainerView::GetButtonClosestTo(
const gfx::Point& position_in_screen) const {
// Since the buttons all have the same size, the closest button is the button
// with the center point closest to |position_in_screen|.
// TODO(pkotwicz): Make the caption buttons not overlap.
gfx::Point position(position_in_screen);
views::View::ConvertPointFromScreen(this, &position);
FrameCaptionButton* buttons[] = {
close_button_, size_button_, minimize_button_
minimize_button_, size_button_, close_button_
};
FrameCaptionButton* pressed_button = NULL;
int min_squared_distance = INT_MAX;
FrameCaptionButton* closest_button = NULL;
for (size_t i = 0; i < arraysize(buttons); ++i) {
FrameCaptionButton* button = buttons[i];
if (!button->visible())
continue;
if (button->state() == views::Button::STATE_PRESSED) {
gfx::Rect expanded_bounds = button->bounds();
expanded_bounds.Inset(pressed_hittest_outer_insets);
if (expanded_bounds.Contains(position)) {
pressed_button = button;
// Do not break in order to give preference to buttons which are
// closer to |position_in_screen| than the currently pressed button.
// TODO(pkotwicz): Make the caption buttons not overlap.
}
} else if (ConvertPointToViewAndHitTest(this, button, position)) {
pressed_button = button;
break;
gfx::Point center_point = button->bounds().CenterPoint();
int squared_distance = static_cast<int>(
pow(static_cast<double>(position.x() - center_point.x()), 2) +
pow(static_cast<double>(position.y() - center_point.y()), 2));
if (squared_distance < min_squared_distance) {
min_squared_distance = squared_distance;
closest_button = button;
}
}
return closest_button;
}
void FrameCaptionButtonContainerView::SetHoveredAndPressedButtons(
const FrameCaptionButton* to_hover,
const FrameCaptionButton* to_press) {
FrameCaptionButton* buttons[] = {
minimize_button_, size_button_, close_button_
};
for (size_t i = 0; i < arraysize(buttons); ++i) {
buttons[i]->SetState(buttons[i] == pressed_button ?
views::Button::STATE_PRESSED : views::Button::STATE_NORMAL);
FrameCaptionButton* button = buttons[i];
views::Button::ButtonState new_state = views::Button::STATE_NORMAL;
if (button == to_hover)
new_state = views::Button::STATE_HOVERED;
else if (button == to_press)
new_state = views::Button::STATE_PRESSED;
button->SetState(new_state);
}
return pressed_button;
}
FrameCaptionButtonContainerView::ButtonIconIds::ButtonIconIds()
......
......@@ -124,9 +124,11 @@ class ASH_EXPORT FrameCaptionButtonContainerView
virtual void SetButtonIcons(CaptionButtonIcon minimize_button_icon,
CaptionButtonIcon close_button_icon,
Animate animate) OVERRIDE;
virtual const FrameCaptionButton* PressButtonAt(
const gfx::Point& position_in_screen,
const gfx::Insets& pressed_hittest_outer_insets) const OVERRIDE;
virtual const FrameCaptionButton* GetButtonClosestTo(
const gfx::Point& position_in_screen) const OVERRIDE;
virtual void SetHoveredAndPressedButtons(
const FrameCaptionButton* to_hover,
const FrameCaptionButton* to_press) OVERRIDE;
// The widget that the buttons act on.
views::Widget* frame_;
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment