summaryrefslogtreecommitdiffstats
path: root/ash/wm
diff options
context:
space:
mode:
authorpkotwicz@chromium.org <pkotwicz@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2013-12-07 21:54:02 +0000
committerpkotwicz@chromium.org <pkotwicz@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2013-12-07 21:54:02 +0000
commitd2a0359240097acae9f5e7da6b0595f7d3c68c98 (patch)
treeb5ab301f9ec75802011977b4cfd3f5beacadb360 /ash/wm
parent2a10756f8c045bd8e1e31c901fc3127881a211c3 (diff)
downloadchromium_src-d2a0359240097acae9f5e7da6b0595f7d3c68c98.zip
chromium_src-d2a0359240097acae9f5e7da6b0595f7d3c68c98.tar.gz
chromium_src-d2a0359240097acae9f5e7da6b0595f7d3c68c98.tar.bz2
Implement new maximize/restore button for CrOS behind the --ash-enable-alternate-caption-button flag
Notable changes: - The new maximize/restore button does not show the help bubble when the mouse is hovered over the maximize/restore button - The minimize and close buttons crossfade into buttons for snapping the window left and right respectively when a user clicks and holds the maximize/restore button BUG=324596 TEST=AlternateFrameSizeButtonTest.* Review URL: https://codereview.chromium.org/97613002 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@239346 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'ash/wm')
-rw-r--r--ash/wm/caption_buttons/alternate_frame_size_button.cc211
-rw-r--r--ash/wm/caption_buttons/alternate_frame_size_button.h100
-rw-r--r--ash/wm/caption_buttons/alternate_frame_size_button_delegate.h57
-rw-r--r--ash/wm/caption_buttons/alternate_frame_size_button_unittest.cc367
-rw-r--r--ash/wm/caption_buttons/caption_button_types.h30
-rw-r--r--ash/wm/caption_buttons/frame_caption_button.cc177
-rw-r--r--ash/wm/caption_buttons/frame_caption_button.h94
-rw-r--r--ash/wm/caption_buttons/frame_caption_button_container_view.cc134
-rw-r--r--ash/wm/caption_buttons/frame_caption_button_container_view.h32
-rw-r--r--ash/wm/caption_buttons/frame_caption_button_container_view_unittest.cc15
-rw-r--r--ash/wm/caption_buttons/frame_maximize_button.cc3
-rw-r--r--ash/wm/caption_buttons/frame_maximize_button.h6
-rw-r--r--ash/wm/caption_buttons/maximize_bubble_controller.h2
-rw-r--r--ash/wm/caption_buttons/maximize_bubble_frame_state.h20
14 files changed, 1148 insertions, 100 deletions
diff --git a/ash/wm/caption_buttons/alternate_frame_size_button.cc b/ash/wm/caption_buttons/alternate_frame_size_button.cc
new file mode 100644
index 0000000..3c5669f
--- /dev/null
+++ b/ash/wm/caption_buttons/alternate_frame_size_button.cc
@@ -0,0 +1,211 @@
+// Copyright 2013 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/caption_buttons/alternate_frame_size_button.h"
+
+#include "ash/shell.h"
+#include "ash/shell_delegate.h"
+#include "ash/touch/touch_uma.h"
+#include "ash/wm/window_state.h"
+#include "ash/wm/workspace/snap_sizer.h"
+#include "ui/gfx/vector2d.h"
+#include "ui/views/widget/widget.h"
+
+namespace {
+
+// The default delay between the user pressing the size button and the buttons
+// adjacent to the size button morphing into buttons for snapping left and
+// 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;
+
+} // namespace
+
+namespace ash {
+
+AlternateFrameSizeButton::AlternateFrameSizeButton(
+ views::ButtonListener* listener,
+ views::Widget* frame,
+ AlternateFrameSizeButtonDelegate* delegate)
+ : FrameCaptionButton(listener, CAPTION_BUTTON_ICON_MAXIMIZE_RESTORE),
+ frame_(frame),
+ delegate_(delegate),
+ set_buttons_to_snap_mode_delay_ms_(kSetButtonsToSnapModeDelayMs),
+ in_snap_mode_(false),
+ snap_type_(SNAP_NONE) {
+}
+
+AlternateFrameSizeButton::~AlternateFrameSizeButton() {
+}
+
+bool AlternateFrameSizeButton::OnMousePressed(const ui::MouseEvent& event) {
+ // The minimize and close buttons are set to snap left and right when snapping
+ // is enabled. Do not enable snapping if the minimize button is not visible.
+ // The close button is always visible.
+ if (IsTriggerableEvent(event) &&
+ !in_snap_mode_ &&
+ delegate_->IsMinimizeButtonVisible()) {
+ StartSetButtonsToSnapModeTimer(event);
+ }
+ FrameCaptionButton::OnMousePressed(event);
+ return true;
+}
+
+bool AlternateFrameSizeButton::OnMouseDragged(const ui::MouseEvent& event) {
+ UpdatePressedButton(event);
+ FrameCaptionButton::OnMouseDragged(event);
+ return true;
+}
+
+void AlternateFrameSizeButton::OnMouseReleased(const ui::MouseEvent& event) {
+ if (!IsTriggerableEvent(event) || !CommitSnap(event))
+ FrameCaptionButton::OnMouseReleased(event);
+}
+
+void AlternateFrameSizeButton::OnMouseCaptureLost() {
+ SetButtonsToNormalMode(AlternateFrameSizeButtonDelegate::ANIMATE_YES);
+ FrameCaptionButton::OnMouseCaptureLost();
+}
+
+void AlternateFrameSizeButton::OnGestureEvent(ui::GestureEvent* event) {
+ if (event->details().touch_points() > 1) {
+ SetButtonsToNormalMode(AlternateFrameSizeButtonDelegate::ANIMATE_YES);
+ return;
+ }
+
+ if (event->type() == ui::ET_GESTURE_TAP_DOWN) {
+ StartSetButtonsToSnapModeTimer(*event);
+ // Go through FrameCaptionButton's handling so that the button gets pressed.
+ FrameCaptionButton::OnGestureEvent(event);
+ return;
+ }
+
+ if (event->type() == ui::ET_GESTURE_SCROLL_BEGIN ||
+ event->type() == ui::ET_GESTURE_SCROLL_UPDATE) {
+ UpdatePressedButton(*event);
+ event->SetHandled();
+ return;
+ }
+
+ if (event->type() == ui::ET_GESTURE_TAP ||
+ event->type() == ui::ET_GESTURE_SCROLL_END ||
+ event->type() == ui::ET_SCROLL_FLING_START ||
+ event->type() == ui::ET_GESTURE_END) {
+ if (CommitSnap(*event)) {
+ if (event->type() == ui::ET_GESTURE_TAP) {
+ TouchUMA::GetInstance()->RecordGestureAction(
+ TouchUMA::GESTURE_FRAMEMAXIMIZE_TAP);
+ }
+ event->SetHandled();
+ return;
+ }
+ }
+
+ FrameCaptionButton::OnGestureEvent(event);
+}
+
+void AlternateFrameSizeButton::StartSetButtonsToSnapModeTimer(
+ const ui::LocatedEvent& event) {
+ set_buttons_to_snap_mode_timer_event_location_ = event.location();
+ if (set_buttons_to_snap_mode_delay_ms_ == 0) {
+ SetButtonsToSnapMode();
+ } else {
+ set_buttons_to_snap_mode_timer_.Start(
+ FROM_HERE,
+ base::TimeDelta::FromMilliseconds(set_buttons_to_snap_mode_delay_ms_),
+ this,
+ &AlternateFrameSizeButton::SetButtonsToSnapMode);
+ }
+}
+
+void AlternateFrameSizeButton::SetButtonsToSnapMode() {
+ if (in_snap_mode_)
+ return;
+ in_snap_mode_ = true;
+ delegate_->SetButtonIcons(CAPTION_BUTTON_ICON_LEFT_SNAPPED,
+ CAPTION_BUTTON_ICON_RIGHT_SNAPPED,
+ AlternateFrameSizeButtonDelegate::ANIMATE_YES);
+}
+
+void AlternateFrameSizeButton::UpdatePressedButton(
+ 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.
+ // |set_buttons_to_snap_mode_timer_| is checked to avoid entering the snap
+ // mode as a result of an unsupported drag type (e.g. only the right mouse
+ // button is pressed).
+ gfx::Vector2d delta(
+ event.location() - set_buttons_to_snap_mode_timer_event_location_);
+ if (!set_buttons_to_snap_mode_timer_.IsRunning() ||
+ !views::View::ExceededDragThreshold(delta)) {
+ return;
+ }
+ SetButtonsToSnapMode();
+ }
+
+ gfx::Point event_location_in_screen(event.location());
+ views::View::ConvertPointToScreen(this, &event_location_in_screen);
+
+ 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()) {
+ case CAPTION_BUTTON_ICON_LEFT_SNAPPED:
+ snap_type_ = SNAP_LEFT;
+ break;
+ case CAPTION_BUTTON_ICON_RIGHT_SNAPPED:
+ 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:
+ NOTREACHED();
+ break;
+ }
+ }
+}
+
+bool AlternateFrameSizeButton::CommitSnap(const ui::LocatedEvent& event) {
+ // The position of |event| may be different than the position of the previous
+ // event.
+ UpdatePressedButton(event);
+
+ if (in_snap_mode_ &&
+ (snap_type_ == SNAP_LEFT || snap_type_ == SNAP_RIGHT)) {
+ using internal::SnapSizer;
+ SnapSizer::SnapWindow(ash::wm::GetWindowState(frame_->GetNativeWindow()),
+ snap_type_ == SNAP_LEFT ?
+ SnapSizer::LEFT_EDGE : SnapSizer::RIGHT_EDGE);
+ ash::Shell::GetInstance()->delegate()->RecordUserMetricsAction(
+ snap_type_ == SNAP_LEFT ?
+ ash::UMA_WINDOW_MAXIMIZE_BUTTON_MAXIMIZE_LEFT :
+ ash::UMA_WINDOW_MAXIMIZE_BUTTON_MAXIMIZE_RIGHT);
+ SetButtonsToNormalMode(AlternateFrameSizeButtonDelegate::ANIMATE_NO);
+ return true;
+ }
+ SetButtonsToNormalMode(AlternateFrameSizeButtonDelegate::ANIMATE_YES);
+ return false;
+}
+
+void AlternateFrameSizeButton::SetButtonsToNormalMode(
+ AlternateFrameSizeButtonDelegate::Animate animate) {
+ in_snap_mode_ = false;
+ snap_type_ = SNAP_NONE;
+ set_buttons_to_snap_mode_timer_.Stop();
+ delegate_->SetButtonsToNormal(animate);
+}
+
+} // namespace ash
diff --git a/ash/wm/caption_buttons/alternate_frame_size_button.h b/ash/wm/caption_buttons/alternate_frame_size_button.h
new file mode 100644
index 0000000..586eabb
--- /dev/null
+++ b/ash/wm/caption_buttons/alternate_frame_size_button.h
@@ -0,0 +1,100 @@
+// Copyright 2013 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_CAPTION_BUTTONS_ALTERNATE_FRAME_SIZE_BUTTON_H_
+#define ASH_WM_CAPTION_BUTTONS_ALTERNATE_FRAME_SIZE_BUTTON_H_
+
+#include "ash/ash_export.h"
+#include "ash/wm/caption_buttons/alternate_frame_size_button_delegate.h"
+#include "ash/wm/caption_buttons/frame_caption_button.h"
+#include "ash/wm/workspace/snap_types.h"
+#include "base/timer/timer.h"
+
+namespace views {
+class Widget;
+}
+
+namespace ash {
+class AlternateFrameSizeButtonDelegate;
+
+// The maximize/restore button when using the alternate button style.
+// 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).
+class ASH_EXPORT AlternateFrameSizeButton : public FrameCaptionButton {
+ public:
+ AlternateFrameSizeButton(views::ButtonListener* listener,
+ views::Widget* frame,
+ AlternateFrameSizeButtonDelegate* delegate);
+
+ virtual ~AlternateFrameSizeButton();
+
+ // views::CustomButton overrides:
+ virtual bool OnMousePressed(const ui::MouseEvent& event) OVERRIDE;
+ virtual bool OnMouseDragged(const ui::MouseEvent& event) OVERRIDE;
+ virtual void OnMouseReleased(const ui::MouseEvent& event) OVERRIDE;
+ virtual void OnMouseCaptureLost() OVERRIDE;
+ virtual void OnGestureEvent(ui::GestureEvent* event) OVERRIDE;
+
+ void set_delay_to_set_buttons_to_snap_mode(int delay_ms) {
+ set_buttons_to_snap_mode_delay_ms_ = delay_ms;
+ }
+
+ private:
+ // Starts |set_buttons_to_snap_mode_timer_|.
+ void StartSetButtonsToSnapModeTimer(const ui::LocatedEvent& event);
+
+ // 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);
+
+ // Snaps |frame_| according to |snap_type_|. Returns true if |frame_| was
+ // snapped.
+ bool CommitSnap(const ui::LocatedEvent& event);
+
+ // Sets the buttons adjacent to the size button to minimize and close again.
+ // Clears any state set while snapping was enabled. |animate| indicates
+ // whether the buttons should animate back to their original icons.
+ void SetButtonsToNormalMode(
+ AlternateFrameSizeButtonDelegate::Animate animate);
+
+ // Widget that the size button acts on.
+ views::Widget* frame_;
+
+ // Not owned.
+ AlternateFrameSizeButtonDelegate* delegate_;
+
+ // Location of the event which started |set_buttons_to_snap_mode_timer_| in
+ // view coordinates.
+ gfx::Point set_buttons_to_snap_mode_timer_event_location_;
+
+ // The delay between the user pressing the size button and the buttons
+ // adjacent to the size button morphing into buttons for snapping left and
+ // right.
+ int set_buttons_to_snap_mode_delay_ms_;
+
+ base::OneShotTimer<AlternateFrameSizeButton> set_buttons_to_snap_mode_timer_;
+
+ // Whether the buttons adjacent to the size button snap the window left and
+ // 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.
+ SnapType snap_type_;
+
+ DISALLOW_COPY_AND_ASSIGN(AlternateFrameSizeButton);
+};
+
+} // namespace ash
+
+#endif // ASH_WM_CAPTION_BUTTONS_ALTERNATE_FRAME_SIZE_BUTTON_H_
diff --git a/ash/wm/caption_buttons/alternate_frame_size_button_delegate.h b/ash/wm/caption_buttons/alternate_frame_size_button_delegate.h
new file mode 100644
index 0000000..c1472b2
--- /dev/null
+++ b/ash/wm/caption_buttons/alternate_frame_size_button_delegate.h
@@ -0,0 +1,57 @@
+// Copyright 2013 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_CAPTION_BUTTONS_ALTERNATE_FRAME_SIZE_BUTTON_DELEGATE_H_
+#define ASH_WM_CAPTION_BUTTONS_ALTERNATE_FRAME_SIZE_BUTTON_DELEGATE_H_
+
+#include "ash/ash_export.h"
+#include "ash/wm/caption_buttons/caption_button_types.h"
+
+namespace gfx {
+class Insets;
+class Point;
+}
+
+namespace ash {
+class FrameCaptionButton;
+
+// Delegate interface for AlternateFrameSizeButton.
+class ASH_EXPORT AlternateFrameSizeButtonDelegate {
+ public:
+ enum Animate {
+ ANIMATE_YES,
+ ANIMATE_NO
+ };
+
+ // Returns whether the minimize button is visible.
+ virtual bool IsMinimizeButtonVisible() const = 0;
+
+ // Reset the caption button views::Button::ButtonState back to normal. If
+ // |animate| is ANIMATE_YES, the buttons will crossfade back to their
+ // original icons.
+ virtual void SetButtonsToNormal(Animate animate) = 0;
+
+ // Sets the minimize and close button icons. The buttons will crossfade to
+ // their new icons if |animate| is ANIMATE_YES.
+ virtual void SetButtonIcons(CaptionButtonIcon left_button_action,
+ 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;
+
+ protected:
+ virtual ~AlternateFrameSizeButtonDelegate() {}
+};
+
+} // namespace ash
+
+#endif // ASH_WM_CAPTION_BUTTONS_ALTERNATE_FRAME_SIZE_BUTTON_DELEGATE_H_
diff --git a/ash/wm/caption_buttons/alternate_frame_size_button_unittest.cc b/ash/wm/caption_buttons/alternate_frame_size_button_unittest.cc
new file mode 100644
index 0000000..75c0f50
--- /dev/null
+++ b/ash/wm/caption_buttons/alternate_frame_size_button_unittest.cc
@@ -0,0 +1,367 @@
+// Copyright 2013 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/caption_buttons/alternate_frame_size_button.h"
+
+#include "ash/ash_switches.h"
+#include "ash/shell.h"
+#include "ash/test/ash_test_base.h"
+#include "ash/wm/caption_buttons/frame_caption_button.h"
+#include "ash/wm/caption_buttons/frame_caption_button_container_view.h"
+#include "ash/wm/window_state.h"
+#include "ash/wm/workspace/snap_sizer.h"
+#include "base/command_line.h"
+#include "ui/aura/test/event_generator.h"
+#include "ui/aura/window.h"
+#include "ui/events/gestures/gesture_configuration.h"
+#include "ui/gfx/display.h"
+#include "ui/gfx/screen.h"
+#include "ui/views/widget/widget.h"
+#include "ui/views/widget/widget_delegate.h"
+
+namespace ash {
+namespace test {
+
+namespace {
+
+class TestWidgetDelegate : public views::WidgetDelegateView {
+ public:
+ TestWidgetDelegate() {}
+ virtual ~TestWidgetDelegate() {}
+
+ // Overridden from views::WidgetDelegate:
+ virtual views::View* GetContentsView() OVERRIDE {
+ return this;
+ }
+ virtual bool CanResize() const OVERRIDE {
+ return true;
+ }
+ virtual bool CanMaximize() const OVERRIDE {
+ return true;
+ }
+
+ ash::FrameCaptionButtonContainerView* caption_button_container() {
+ return caption_button_container_;
+ }
+
+ private:
+ // Overridden from views::View:
+ virtual void Layout() OVERRIDE {
+ caption_button_container_->Layout();
+
+ // Right align the caption button container.
+ gfx::Size preferred_size = caption_button_container_->GetPreferredSize();
+ caption_button_container_->SetBounds(width() - preferred_size.width(), 0,
+ preferred_size.width(), preferred_size.height());
+ }
+
+ virtual void ViewHierarchyChanged(
+ const ViewHierarchyChangedDetails& details) OVERRIDE {
+ if (details.is_add && details.child == this) {
+ caption_button_container_ = new FrameCaptionButtonContainerView(
+ GetWidget(), FrameCaptionButtonContainerView::MINIMIZE_ALLOWED);
+ AddChildView(caption_button_container_);
+ }
+ }
+
+ // Not owned.
+ ash::FrameCaptionButtonContainerView* caption_button_container_;
+
+ DISALLOW_COPY_AND_ASSIGN(TestWidgetDelegate);
+};
+
+} // namespace
+
+class AlternateFrameSizeButtonTest : public AshTestBase {
+ public:
+ AlternateFrameSizeButtonTest() {}
+ virtual ~AlternateFrameSizeButtonTest() {}
+
+ // Returns the center point of |view| in screen coordinates.
+ gfx::Point CenterPointInScreen(views::View* view) {
+ return view->GetBoundsInScreen().CenterPoint();
+ }
+
+ // Returns true if the window is snapped to |edge|.
+ bool IsSnapped(internal::SnapSizer::Edge edge) const {
+ ash::wm::WindowShowType show_type = window_state()->window_show_type();
+ if (edge == internal::SnapSizer::LEFT_EDGE)
+ return show_type == ash::wm::SHOW_TYPE_LEFT_SNAPPED;
+ else
+ return show_type == ash::wm::SHOW_TYPE_RIGHT_SNAPPED;
+ }
+
+ // Returns true if all three buttons are in the normal state.
+ bool AllButtonsInNormalState() const {
+ return minimize_button_->state() == views::Button::STATE_NORMAL &&
+ size_button_->state() == views::Button::STATE_NORMAL &&
+ close_button_->state() == views::Button::STATE_NORMAL;
+ }
+
+ // Creates a widget with |delegate|. The returned widget takes ownership of
+ // |delegate|.
+ views::Widget* CreateWidget(views::WidgetDelegate* delegate) {
+ views::Widget* widget = new views::Widget;
+ views::Widget::InitParams params(
+ views::Widget::InitParams::TYPE_WINDOW_FRAMELESS);
+ params.context = CurrentContext();
+ params.delegate = delegate;
+ params.bounds = gfx::Rect(10, 10, 100, 100);
+ widget->Init(params);
+ widget->Show();
+ return widget;
+ }
+
+ // AshTestBase overrides:
+ virtual void SetUp() OVERRIDE {
+ AshTestBase::SetUp();
+
+ CommandLine* command_line = CommandLine::ForCurrentProcess();
+ command_line->AppendSwitch(
+ switches::kAshEnableAlternateFrameCaptionButtonStyle);
+ CHECK(!command_line->HasSwitch(switches::kAshMultipleSnapWindowWidths));
+
+ TestWidgetDelegate* delegate = new TestWidgetDelegate();
+ window_state_ = ash::wm::GetWindowState(
+ CreateWidget(delegate)->GetNativeWindow());
+
+ FrameCaptionButtonContainerView::TestApi test(
+ delegate->caption_button_container());
+
+ minimize_button_ = test.minimize_button();
+ size_button_ = test.size_button();
+ static_cast<AlternateFrameSizeButton*>(
+ size_button_)->set_delay_to_set_buttons_to_snap_mode(0);
+ close_button_ = test.close_button();
+ }
+
+ ash::wm::WindowState* window_state() { return window_state_; }
+ const ash::wm::WindowState* window_state() const { return window_state_; }
+
+ FrameCaptionButton* minimize_button() { return minimize_button_; }
+ FrameCaptionButton* size_button() { return size_button_; }
+ FrameCaptionButton* close_button() { return close_button_; }
+
+ private:
+ // Not owned.
+ ash::wm::WindowState* window_state_;
+ FrameCaptionButton* minimize_button_;
+ FrameCaptionButton* size_button_;
+ FrameCaptionButton* close_button_;
+
+ DISALLOW_COPY_AND_ASSIGN(AlternateFrameSizeButtonTest);
+};
+
+// Tests that pressing the left mouse button or tapping down on the size button
+// puts the button into the pressed state.
+TEST_F(AlternateFrameSizeButtonTest, PressedState) {
+ aura::test::EventGenerator& generator = GetEventGenerator();
+ generator.MoveMouseTo(CenterPointInScreen(size_button()));
+ generator.PressLeftButton();
+ EXPECT_EQ(views::Button::STATE_PRESSED, size_button()->state());
+ generator.ReleaseLeftButton();
+ RunAllPendingInMessageLoop();
+ EXPECT_EQ(views::Button::STATE_NORMAL, size_button()->state());
+
+ generator.MoveMouseTo(CenterPointInScreen(size_button()));
+ generator.PressTouchId(3);
+ EXPECT_EQ(views::Button::STATE_PRESSED, size_button()->state());
+ generator.ReleaseTouchId(3);
+ RunAllPendingInMessageLoop();
+ EXPECT_EQ(views::Button::STATE_NORMAL, size_button()->state());
+}
+
+// Tests that clicking on the size button toggles between the maximized and
+// normal state.
+TEST_F(AlternateFrameSizeButtonTest, ClickSizeButtonTogglesMaximize) {
+ EXPECT_FALSE(window_state()->IsMaximized());
+
+ aura::test::EventGenerator& generator = GetEventGenerator();
+ generator.MoveMouseTo(CenterPointInScreen(size_button()));
+ generator.ClickLeftButton();
+ RunAllPendingInMessageLoop();
+ EXPECT_TRUE(window_state()->IsMaximized());
+
+ generator.MoveMouseTo(CenterPointInScreen(size_button()));
+ generator.ClickLeftButton();
+ RunAllPendingInMessageLoop();
+ EXPECT_FALSE(window_state()->IsMaximized());
+
+ generator.GestureTapAt(CenterPointInScreen(size_button()));
+ RunAllPendingInMessageLoop();
+ EXPECT_TRUE(window_state()->IsMaximized());
+
+ generator.GestureTapAt(CenterPointInScreen(size_button()));
+ RunAllPendingInMessageLoop();
+ EXPECT_FALSE(window_state()->IsMaximized());
+}
+
+// Test that clicking + dragging to a button adjacent to the size button snaps
+// the window left or right.
+TEST_F(AlternateFrameSizeButtonTest, ButtonDrag) {
+ EXPECT_TRUE(window_state()->IsNormalShowState());
+ EXPECT_FALSE(window_state()->IsSnapped());
+
+ // 1) Test by dragging the mouse.
+ // Snap right.
+ aura::test::EventGenerator& generator = GetEventGenerator();
+ generator.MoveMouseTo(CenterPointInScreen(size_button()));
+ generator.PressLeftButton();
+ generator.MoveMouseTo(CenterPointInScreen(close_button()));
+ generator.ReleaseLeftButton();
+ RunAllPendingInMessageLoop();
+ EXPECT_TRUE(IsSnapped(internal::SnapSizer::RIGHT_EDGE));
+
+ // Snap left.
+ generator.MoveMouseTo(CenterPointInScreen(size_button()));
+ generator.PressLeftButton();
+ generator.MoveMouseTo(CenterPointInScreen(minimize_button()));
+ generator.ReleaseLeftButton();
+ RunAllPendingInMessageLoop();
+ EXPECT_TRUE(IsSnapped(internal::SnapSizer::LEFT_EDGE));
+
+ // 2) Test with scroll gestures.
+ // Snap right.
+ generator.GestureScrollSequence(
+ CenterPointInScreen(size_button()),
+ CenterPointInScreen(close_button()),
+ base::TimeDelta::FromMilliseconds(100),
+ 3);
+ RunAllPendingInMessageLoop();
+ EXPECT_TRUE(IsSnapped(internal::SnapSizer::RIGHT_EDGE));
+
+ // Snap left.
+ generator.GestureScrollSequence(
+ CenterPointInScreen(size_button()),
+ CenterPointInScreen(minimize_button()),
+ base::TimeDelta::FromMilliseconds(100),
+ 3);
+ RunAllPendingInMessageLoop();
+ EXPECT_TRUE(IsSnapped(internal::SnapSizer::LEFT_EDGE));
+
+ // 3) Test with tap gestures.
+ const int touch_default_radius =
+ ui::GestureConfiguration::default_radius();
+ ui::GestureConfiguration::set_default_radius(0);
+ // Snap right.
+ generator.MoveMouseTo(CenterPointInScreen(size_button()));
+ generator.PressMoveAndReleaseTouchTo(CenterPointInScreen(close_button()));
+ RunAllPendingInMessageLoop();
+ EXPECT_TRUE(IsSnapped(internal::SnapSizer::RIGHT_EDGE));
+ // Snap left.
+ generator.MoveMouseTo(CenterPointInScreen(size_button()));
+ generator.PressMoveAndReleaseTouchTo(CenterPointInScreen(minimize_button()));
+ RunAllPendingInMessageLoop();
+ EXPECT_TRUE(IsSnapped(internal::SnapSizer::LEFT_EDGE));
+ ui::GestureConfiguration::set_default_radius(touch_default_radius);
+}
+
+// Test that clicking, dragging, and overshooting the minimize button a bit
+// horizontally still snaps the window left.
+TEST_F(AlternateFrameSizeButtonTest, SnapLeftOvershootMinimize) {
+ EXPECT_TRUE(window_state()->IsNormalShowState());
+ EXPECT_FALSE(window_state()->IsSnapped());
+
+ aura::test::EventGenerator& generator = GetEventGenerator();
+ generator.MoveMouseTo(CenterPointInScreen(size_button()));
+
+ generator.PressLeftButton();
+ // Move to the minimize button.
+ generator.MoveMouseTo(CenterPointInScreen(minimize_button()));
+ // Overshoot the minimize button.
+ generator.MoveMouseBy(-minimize_button()->width(), 0);
+ generator.ReleaseLeftButton();
+ RunAllPendingInMessageLoop();
+ EXPECT_TRUE(IsSnapped(internal::SnapSizer::LEFT_EDGE));
+}
+
+// Test that right clicking the size button has no effect.
+TEST_F(AlternateFrameSizeButtonTest, RightMouseButton) {
+ EXPECT_TRUE(window_state()->IsNormalShowState());
+ EXPECT_FALSE(window_state()->IsSnapped());
+
+ aura::test::EventGenerator& generator = GetEventGenerator();
+ generator.MoveMouseTo(CenterPointInScreen(size_button()));
+ generator.PressRightButton();
+ generator.ReleaseRightButton();
+ RunAllPendingInMessageLoop();
+ EXPECT_TRUE(window_state()->IsNormalShowState());
+ EXPECT_FALSE(window_state()->IsSnapped());
+}
+
+// Test that upon releasing the mouse button after having pressed the size
+// button
+// - The state of all the caption buttons is reset.
+// - The icon displayed by all of the caption buttons is reset.
+TEST_F(AlternateFrameSizeButtonTest, ResetButtonsAfterClick) {
+ 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 should press 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_NORMAL, close_button()->state());
+ EXPECT_EQ(CAPTION_BUTTON_ICON_LEFT_SNAPPED, minimize_button()->icon());
+ EXPECT_EQ(CAPTION_BUTTON_ICON_RIGHT_SNAPPED, close_button()->icon());
+
+ // Release the mouse, snapping the window left.
+ generator.ReleaseLeftButton();
+ RunAllPendingInMessageLoop();
+ EXPECT_TRUE(IsSnapped(internal::SnapSizer::LEFT_EDGE));
+
+ // None of the buttons should stay pressed and the buttons should have their
+ // regular icons.
+ EXPECT_TRUE(AllButtonsInNormalState());
+ EXPECT_EQ(CAPTION_BUTTON_ICON_MINIMIZE, minimize_button()->icon());
+ EXPECT_EQ(CAPTION_BUTTON_ICON_CLOSE, close_button()->icon());
+
+ // Repeat test but release button where it does not affect the window's state
+ // because the code path is different.
+ 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());
+
+ const gfx::Rect& kWorkAreaBoundsInScreen =
+ ash::Shell::GetScreen()->GetPrimaryDisplay().work_area();
+ generator.MoveMouseTo(kWorkAreaBoundsInScreen.bottom_left());
+
+ // None of the buttons should be pressed because we are really far away from
+ // any of the caption buttons. The minimize and close button icons should
+ // be changed because the mouse is pressed.
+ EXPECT_TRUE(AllButtonsInNormalState());
+ EXPECT_EQ(CAPTION_BUTTON_ICON_LEFT_SNAPPED, minimize_button()->icon());
+ EXPECT_EQ(CAPTION_BUTTON_ICON_RIGHT_SNAPPED, close_button()->icon());
+
+ // Release the mouse. The window should stay snapped left.
+ generator.ReleaseLeftButton();
+ RunAllPendingInMessageLoop();
+ EXPECT_TRUE(IsSnapped(internal::SnapSizer::LEFT_EDGE));
+
+ // The buttons should stay unpressed and the buttons should now have their
+ // regular icons.
+ EXPECT_TRUE(AllButtonsInNormalState());
+ EXPECT_EQ(CAPTION_BUTTON_ICON_MINIMIZE, minimize_button()->icon());
+ EXPECT_EQ(CAPTION_BUTTON_ICON_CLOSE, close_button()->icon());
+}
+
+} // namespace test
+} // namespace ash
diff --git a/ash/wm/caption_buttons/caption_button_types.h b/ash/wm/caption_buttons/caption_button_types.h
new file mode 100644
index 0000000..b34a861
--- /dev/null
+++ b/ash/wm/caption_buttons/caption_button_types.h
@@ -0,0 +1,30 @@
+// Copyright 2013 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_CAPTION_BUTTONS_CAPTION_BUTTON_TYPES_H_
+#define ASH_WM_CAPTION_BUTTONS_CAPTION_BUTTON_TYPES_H_
+
+namespace ash {
+
+// These are the types of maximization we know.
+enum MaximizeBubbleFrameState {
+ FRAME_STATE_NONE = 0,
+ FRAME_STATE_FULL = 1, // This is the full maximized state.
+ FRAME_STATE_SNAP_LEFT = 2,
+ FRAME_STATE_SNAP_RIGHT = 3
+};
+
+// These are the icon types that a caption button can have. The size button's
+// action (SnapType) can be different from its icon.
+enum CaptionButtonIcon {
+ CAPTION_BUTTON_ICON_MINIMIZE,
+ CAPTION_BUTTON_ICON_MAXIMIZE_RESTORE,
+ CAPTION_BUTTON_ICON_CLOSE,
+ CAPTION_BUTTON_ICON_LEFT_SNAPPED,
+ CAPTION_BUTTON_ICON_RIGHT_SNAPPED,
+};
+
+} // namespace ash
+
+#endif // ASH_WM_CAPTION_BUTTONS_CAPTION_BUTTON_TYPES_H_
diff --git a/ash/wm/caption_buttons/frame_caption_button.cc b/ash/wm/caption_buttons/frame_caption_button.cc
new file mode 100644
index 0000000..5a6a835
--- /dev/null
+++ b/ash/wm/caption_buttons/frame_caption_button.cc
@@ -0,0 +1,177 @@
+// Copyright 2013 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/caption_buttons/frame_caption_button.h"
+
+#include "grit/ash_resources.h"
+#include "ui/base/resource/resource_bundle.h"
+#include "ui/gfx/animation/slide_animation.h"
+#include "ui/gfx/canvas.h"
+
+namespace ash {
+
+namespace {
+
+// The duration of the crossfade animation when swapping the button's icon.
+const int kCrossfadeDurationMs = 200;
+
+} // namespace
+
+// static
+const char FrameCaptionButton::kViewClassName[] = "FrameCaptionButton";
+
+FrameCaptionButton::FrameCaptionButton(views::ButtonListener* listener,
+ CaptionButtonIcon icon)
+ : ImageButton(listener),
+ icon_(icon),
+ style_(STYLE_SHORT_RESTORED),
+ last_paint_scale_(1.0f),
+ animation_(new gfx::SlideAnimation(this)) {
+ animation_->Reset(1);
+ UpdateImages();
+}
+
+FrameCaptionButton::~FrameCaptionButton() {
+}
+
+void FrameCaptionButton::SetIcon(CaptionButtonIcon icon, Animate animate) {
+ if (icon_ == icon)
+ return;
+
+ if (animate == ANIMATE_YES) {
+ gfx::Canvas canvas(size(), last_paint_scale_, false);
+ OnPaint(&canvas);
+ crossfade_image_ = gfx::ImageSkia(canvas.ExtractImageRep());
+
+ icon_ = icon;
+ UpdateImages();
+
+ animation_->Reset(0);
+ animation_->SetSlideDuration(kCrossfadeDurationMs);
+ animation_->Show();
+ } else {
+ animation_->Reset(1);
+ icon_ = icon;
+ UpdateImages();
+ }
+}
+
+void FrameCaptionButton::SetStyle(Style style) {
+ if (style_ == style)
+ return;
+ animation_->Reset(1);
+ style_ = style;
+ UpdateImages();
+}
+
+const char* FrameCaptionButton::GetClassName() const {
+ return kViewClassName;
+}
+
+void FrameCaptionButton::OnPaint(gfx::Canvas* canvas) {
+ last_paint_scale_ = canvas->image_scale();
+ int alpha = static_cast<int>(animation_->GetCurrentValue() * 255);
+ int crossfade_alpha = 255 - alpha;
+ if (crossfade_alpha > 0 && !crossfade_image_.isNull()) {
+ gfx::Canvas composed_canvas(size(), last_paint_scale_, false);
+ SkPaint paint;
+ paint.setAlpha(crossfade_alpha);
+ paint.setXfermodeMode(SkXfermode::kPlus_Mode);
+ composed_canvas.DrawImageInt(crossfade_image_, 0, 0, paint);
+ paint.setAlpha(alpha);
+ ImageButton::OnPaint(&composed_canvas);
+
+ canvas->DrawImageInt(
+ gfx::ImageSkia(composed_canvas.ExtractImageRep()), 0, 0);
+ } else {
+ ImageButton::OnPaint(canvas);
+ }
+}
+
+void FrameCaptionButton::OnGestureEvent(ui::GestureEvent* event) {
+ // ImageButton does not become pressed when the user drags off and then back
+ // onto the button. Make FrameCaptionButton pressed in this case because this
+ // behavior is more consistent with AlternateFrameSizeButton.
+ if (event->type() == ui::ET_GESTURE_SCROLL_BEGIN ||
+ event->type() == ui::ET_GESTURE_SCROLL_UPDATE) {
+ if (HitTestPoint(event->location())) {
+ SetState(STATE_PRESSED);
+ RequestFocus();
+ event->StopPropagation();
+ } else {
+ SetState(STATE_NORMAL);
+ }
+ } else if (event->type() == ui::ET_GESTURE_SCROLL_END) {
+ if (HitTestPoint(event->location())) {
+ SetState(STATE_HOVERED);
+ NotifyClick(*event);
+ event->StopPropagation();
+ }
+ }
+ ImageButton::OnGestureEvent(event);
+}
+
+void FrameCaptionButton::StateChanged() {
+ if (state_ == STATE_HOVERED || state_ == STATE_PRESSED)
+ animation_->Reset(1);
+}
+
+void FrameCaptionButton::UpdateImages() {
+ switch (icon_) {
+ case CAPTION_BUTTON_ICON_MINIMIZE:
+ SetImages(IDR_AURA_WINDOW_MINIMIZE_SHORT,
+ IDR_AURA_WINDOW_MINIMIZE_SHORT_H,
+ IDR_AURA_WINDOW_MINIMIZE_SHORT_P);
+ break;
+ case CAPTION_BUTTON_ICON_MAXIMIZE_RESTORE:
+ case CAPTION_BUTTON_ICON_LEFT_SNAPPED:
+ case CAPTION_BUTTON_ICON_RIGHT_SNAPPED:
+ if (style_ == STYLE_SHORT_MAXIMIZED_OR_FULLSCREEN) {
+ SetImages(IDR_AURA_WINDOW_MAXIMIZED_RESTORE2,
+ IDR_AURA_WINDOW_MAXIMIZED_RESTORE2_H,
+ IDR_AURA_WINDOW_MAXIMIZED_RESTORE2_P);
+ } else if (style_ == STYLE_SHORT_RESTORED) {
+ SetImages(IDR_AURA_WINDOW_MAXIMIZED_RESTORE,
+ IDR_AURA_WINDOW_MAXIMIZED_RESTORE_H,
+ IDR_AURA_WINDOW_MAXIMIZED_RESTORE_P);
+ } else {
+ SetImages(IDR_AURA_WINDOW_MAXIMIZE,
+ IDR_AURA_WINDOW_MAXIMIZE_H,
+ IDR_AURA_WINDOW_MAXIMIZE_P);
+ }
+ break;
+ case CAPTION_BUTTON_ICON_CLOSE:
+ if (style_ == STYLE_SHORT_MAXIMIZED_OR_FULLSCREEN) {
+ SetImages(IDR_AURA_WINDOW_MAXIMIZED_CLOSE2,
+ IDR_AURA_WINDOW_MAXIMIZED_CLOSE2_H,
+ IDR_AURA_WINDOW_MAXIMIZED_CLOSE2_P);
+ } else if (style_ == STYLE_SHORT_RESTORED) {
+ SetImages(IDR_AURA_WINDOW_MAXIMIZED_CLOSE,
+ IDR_AURA_WINDOW_MAXIMIZED_CLOSE_H,
+ IDR_AURA_WINDOW_MAXIMIZED_CLOSE_P);
+ } else {
+ SetImages(IDR_AURA_WINDOW_CLOSE,
+ IDR_AURA_WINDOW_CLOSE_H,
+ IDR_AURA_WINDOW_CLOSE_P);
+ }
+ break;
+ }
+
+ SchedulePaint();
+}
+
+void FrameCaptionButton::SetImages(int normal_image_id,
+ int hot_image_id,
+ int pushed_image_id) {
+ ui::ResourceBundle& resource_bundle = ui::ResourceBundle::GetSharedInstance();
+ SetImage(STATE_NORMAL, resource_bundle.GetImageSkiaNamed(normal_image_id));
+ SetImage(STATE_HOVERED, resource_bundle.GetImageSkiaNamed(hot_image_id));
+ SetImage(STATE_PRESSED, resource_bundle.GetImageSkiaNamed(pushed_image_id));
+}
+
+void FrameCaptionButton::AnimationProgressed(const gfx::Animation* animation) {
+ SchedulePaint();
+}
+
+} // namespace ash
diff --git a/ash/wm/caption_buttons/frame_caption_button.h b/ash/wm/caption_buttons/frame_caption_button.h
new file mode 100644
index 0000000..61ed922
--- /dev/null
+++ b/ash/wm/caption_buttons/frame_caption_button.h
@@ -0,0 +1,94 @@
+// Copyright 2013 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_CAPTION_BUTTONS_FRAME_CAPTION_BUTTON_H_
+#define ASH_WM_CAPTION_BUTTONS_FRAME_CAPTION_BUTTON_H_
+
+#include "ash/ash_export.h"
+#include "ash/wm/caption_buttons/caption_button_types.h"
+#include "base/memory/scoped_ptr.h"
+#include "ui/gfx/image/image_skia.h"
+#include "ui/views/controls/button/image_button.h"
+
+namespace gfx {
+class SlideAnimation;
+}
+
+namespace ash {
+
+// Base class for the window caption buttons (minimize, maximize, restore,
+// close).
+class ASH_EXPORT FrameCaptionButton : public views::ImageButton {
+ public:
+ enum Animate {
+ ANIMATE_YES,
+ ANIMATE_NO
+ };
+
+ enum Style {
+ // Restored tabbed browser windows.
+ STYLE_TALL_RESTORED,
+
+ // All other restored windows.
+ STYLE_SHORT_RESTORED,
+
+ // Maximized or fullscreen windows.
+ STYLE_SHORT_MAXIMIZED_OR_FULLSCREEN
+ };
+
+ static const char kViewClassName[];
+
+ FrameCaptionButton(views::ButtonListener* listener, CaptionButtonIcon icon);
+ virtual ~FrameCaptionButton();
+
+ // Sets the button's icon. If |animate| is ANIMATE_YES, the button crossfades
+ // to the new icon.
+ void SetIcon(CaptionButtonIcon icon, Animate animate);
+
+ // Sets the button's style. The transition to the new style is not animated.
+ void SetStyle(Style style);
+
+ // views::View overrides:
+ virtual const char* GetClassName() const OVERRIDE;
+ virtual void OnPaint(gfx::Canvas* canvas) OVERRIDE;
+
+ CaptionButtonIcon icon() const {
+ return icon_;
+ }
+
+ protected:
+ // views::CustomButton overrides:
+ virtual void OnGestureEvent(ui::GestureEvent* event) OVERRIDE;
+ virtual void StateChanged() OVERRIDE;
+
+ private:
+ // Updates the button's images based on the current icon and style.
+ void UpdateImages();
+
+ // Sets the button's images based on the given ids.
+ void SetImages(int normal_image_id, int hot_image_id, int pushed_image_id);
+
+ // gfx::AnimationDelegate override:
+ virtual void AnimationProgressed(const gfx::Animation* animation) OVERRIDE;
+
+ // The button's current icon.
+ CaptionButtonIcon icon_;
+
+ // The button's current style.
+ Style style_;
+
+ // The scale at which the button was previously painted.
+ float last_paint_scale_;
+
+ // The image to crossfade from.
+ gfx::ImageSkia crossfade_image_;
+
+ scoped_ptr<gfx::SlideAnimation> animation_;
+
+ DISALLOW_COPY_AND_ASSIGN(FrameCaptionButton);
+};
+
+} // namespace ash
+
+#endif // ASH_WM_CAPTION_BUTTONS_FRAME_CAPTION_BUTTON_H_
diff --git a/ash/wm/caption_buttons/frame_caption_button_container_view.cc b/ash/wm/caption_buttons/frame_caption_button_container_view.cc
index 0ed5d12..2e4d323 100644
--- a/ash/wm/caption_buttons/frame_caption_button_container_view.cc
+++ b/ash/wm/caption_buttons/frame_caption_button_container_view.cc
@@ -7,6 +7,8 @@
#include "ash/ash_switches.h"
#include "ash/shell.h"
#include "ash/shell_delegate.h"
+#include "ash/wm/caption_buttons/alternate_frame_size_button.h"
+#include "ash/wm/caption_buttons/frame_caption_button.h"
#include "ash/wm/caption_buttons/frame_maximize_button.h"
#include "grit/ash_resources.h"
#include "grit/ui_strings.h" // Accessibility names
@@ -15,7 +17,8 @@
#include "ui/base/resource/resource_bundle.h"
#include "ui/compositor/scoped_animation_duration_scale_mode.h"
#include "ui/gfx/canvas.h"
-#include "ui/views/border.h"
+#include "ui/gfx/insets.h"
+#include "ui/gfx/point.h"
#include "ui/views/controls/button/image_button.h"
#include "ui/views/widget/widget.h"
#include "ui/views/widget/widget_delegate.h"
@@ -53,7 +56,7 @@ FrameCaptionButtonContainerView::FrameCaptionButtonContainerView(
bool alternate_style = switches::UseAlternateFrameCaptionButtonStyle();
// Insert the buttons left to right.
- minimize_button_ = new views::ImageButton(this);
+ minimize_button_ = new FrameCaptionButton(this, CAPTION_BUTTON_ICON_MINIMIZE);
minimize_button_->SetAccessibleName(
l10n_util::GetStringUTF16(IDS_APP_ACCNAME_MINIMIZE));
// Hide |minimize_button_| when using the non-alternate button style because
@@ -64,7 +67,7 @@ FrameCaptionButtonContainerView::FrameCaptionButtonContainerView(
AddChildView(minimize_button_);
if (alternate_style)
- size_button_ = new views::ImageButton(this);
+ size_button_ = new AlternateFrameSizeButton(this, frame, this);
else
size_button_ = new FrameMaximizeButton(this, frame);
size_button_->SetAccessibleName(
@@ -72,7 +75,7 @@ FrameCaptionButtonContainerView::FrameCaptionButtonContainerView(
size_button_->SetVisible(frame_->widget_delegate()->CanMaximize());
AddChildView(size_button_);
- close_button_ = new views::ImageButton(this);
+ close_button_ = new FrameCaptionButton(this, CAPTION_BUTTON_ICON_CLOSE);
close_button_->SetAccessibleName(
l10n_util::GetStringUTF16(IDS_APP_ACCNAME_CLOSE));
AddChildView(close_button_);
@@ -91,8 +94,7 @@ FrameCaptionButtonContainerView::GetOldStyleSizeButton() {
}
void FrameCaptionButtonContainerView::ResetWindowControls() {
- minimize_button_->SetState(views::CustomButton::STATE_NORMAL);
- size_button_->SetState(views::CustomButton::STATE_NORMAL);
+ SetButtonsToNormal(ANIMATE_NO);
}
int FrameCaptionButtonContainerView::NonClientHitTest(
@@ -127,43 +129,19 @@ gfx::Size FrameCaptionButtonContainerView::GetPreferredSize() {
}
void FrameCaptionButtonContainerView::Layout() {
- SetButtonImages(minimize_button_,
- IDR_AURA_WINDOW_MINIMIZE_SHORT,
- IDR_AURA_WINDOW_MINIMIZE_SHORT_H,
- IDR_AURA_WINDOW_MINIMIZE_SHORT_P);
+ FrameCaptionButton::Style style = FrameCaptionButton::STYLE_SHORT_RESTORED;
if (header_style_ == HEADER_STYLE_SHORT) {
- // The new assets only make sense if the window is maximized or fullscreen
- // because we usually use a black header in this case.
- if (frame_->IsMaximized() || frame_->IsFullscreen()) {
- SetButtonImages(size_button_,
- IDR_AURA_WINDOW_MAXIMIZED_RESTORE2,
- IDR_AURA_WINDOW_MAXIMIZED_RESTORE2_H,
- IDR_AURA_WINDOW_MAXIMIZED_RESTORE2_P);
- SetButtonImages(close_button_,
- IDR_AURA_WINDOW_MAXIMIZED_CLOSE2,
- IDR_AURA_WINDOW_MAXIMIZED_CLOSE2_H,
- IDR_AURA_WINDOW_MAXIMIZED_CLOSE2_P);
- } else {
- SetButtonImages(size_button_,
- IDR_AURA_WINDOW_MAXIMIZED_RESTORE,
- IDR_AURA_WINDOW_MAXIMIZED_RESTORE_H,
- IDR_AURA_WINDOW_MAXIMIZED_RESTORE_P);
- SetButtonImages(close_button_,
- IDR_AURA_WINDOW_MAXIMIZED_CLOSE,
- IDR_AURA_WINDOW_MAXIMIZED_CLOSE_H,
- IDR_AURA_WINDOW_MAXIMIZED_CLOSE_P);
- }
+ if (frame_->IsMaximized() || frame_->IsFullscreen())
+ style = FrameCaptionButton::STYLE_SHORT_MAXIMIZED_OR_FULLSCREEN;
+ // Else: FrameCaptionButton::STYLE_SHORT_RESTORED;
} else {
- SetButtonImages(size_button_,
- IDR_AURA_WINDOW_MAXIMIZE,
- IDR_AURA_WINDOW_MAXIMIZE_H,
- IDR_AURA_WINDOW_MAXIMIZE_P);
- SetButtonImages(close_button_,
- IDR_AURA_WINDOW_CLOSE,
- IDR_AURA_WINDOW_CLOSE_H,
- IDR_AURA_WINDOW_CLOSE_P);
+ style = FrameCaptionButton::STYLE_TALL_RESTORED;
}
+ minimize_button_->SetStyle(style);
+ size_button_->SetStyle(style);
+ close_button_->SetStyle(style);
+
int x = 0;
for (int i = 0; i < child_count(); ++i) {
views::View* child = child_at(i);
@@ -207,15 +185,14 @@ void FrameCaptionButtonContainerView::ButtonPressed(views::Button* sender,
ui::ScopedAnimationDurationScaleMode::SLOW_DURATION));
}
+ // Abort any animations of the button icons.
+ SetButtonsToNormal(ANIMATE_NO);
+
ash::UserMetricsAction action =
ash::UMA_WINDOW_MAXIMIZE_BUTTON_CLICK_MINIMIZE;
if (sender == minimize_button_) {
- // The minimize button may move out from under the cursor.
- ResetWindowControls();
frame_->Minimize();
} else if (sender == size_button_) {
- // The size button may move out from under the cursor.
- ResetWindowControls();
if (frame_->IsFullscreen()) { // Can be clicked in immersive fullscreen.
frame_->SetFullscreen(false);
action = ash::UMA_WINDOW_MAXIMIZE_BUTTON_CLICK_EXIT_FULLSCREEN;
@@ -235,18 +212,65 @@ void FrameCaptionButtonContainerView::ButtonPressed(views::Button* sender,
ash::Shell::GetInstance()->delegate()->RecordUserMetricsAction(action);
}
-void FrameCaptionButtonContainerView::SetButtonImages(
- views::ImageButton* button,
- int normal_image_id,
- int hot_image_id,
- int pushed_image_id) {
- ui::ResourceBundle& resource_bundle = ui::ResourceBundle::GetSharedInstance();
- button->SetImage(views::CustomButton::STATE_NORMAL,
- resource_bundle.GetImageSkiaNamed(normal_image_id));
- button->SetImage(views::CustomButton::STATE_HOVERED,
- resource_bundle.GetImageSkiaNamed(hot_image_id));
- button->SetImage(views::CustomButton::STATE_PRESSED,
- resource_bundle.GetImageSkiaNamed(pushed_image_id));
+bool FrameCaptionButtonContainerView::IsMinimizeButtonVisible() const {
+ return minimize_button_->visible();
+}
+
+void FrameCaptionButtonContainerView::SetButtonsToNormal(Animate animate) {
+ SetButtonIcons(CAPTION_BUTTON_ICON_MINIMIZE, CAPTION_BUTTON_ICON_CLOSE,
+ animate);
+ minimize_button_->SetState(views::Button::STATE_NORMAL);
+ size_button_->SetState(views::Button::STATE_NORMAL);
+ close_button_->SetState(views::Button::STATE_NORMAL);
+}
+
+void FrameCaptionButtonContainerView::SetButtonIcons(
+ CaptionButtonIcon minimize_button_icon,
+ CaptionButtonIcon close_button_icon,
+ Animate animate) {
+ FrameCaptionButton::Animate fcb_animate = (animate == ANIMATE_YES) ?
+ FrameCaptionButton::ANIMATE_YES : FrameCaptionButton::ANIMATE_NO;
+ minimize_button_->SetIcon(minimize_button_icon, fcb_animate);
+ close_button_->SetIcon(close_button_icon, fcb_animate);
+}
+
+const FrameCaptionButton*
+FrameCaptionButtonContainerView::PressButtonAt(
+ const gfx::Point& position_in_screen,
+ const gfx::Insets& pressed_hittest_outer_insets) const {
+ DCHECK(switches::UseAlternateFrameCaptionButtonStyle());
+ gfx::Point position(position_in_screen);
+ views::View::ConvertPointFromScreen(this, &position);
+
+ FrameCaptionButton* buttons[] = {
+ close_button_, size_button_, minimize_button_
+ };
+ FrameCaptionButton* pressed_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;
+ }
+ }
+
+ for (size_t i = 0; i < arraysize(buttons); ++i) {
+ buttons[i]->SetState(buttons[i] == pressed_button ?
+ views::Button::STATE_PRESSED : views::Button::STATE_NORMAL);
+ }
+ return pressed_button;
}
} // namespace ash
diff --git a/ash/wm/caption_buttons/frame_caption_button_container_view.h b/ash/wm/caption_buttons/frame_caption_button_container_view.h
index f8e1a92..4fd663b 100644
--- a/ash/wm/caption_buttons/frame_caption_button_container_view.h
+++ b/ash/wm/caption_buttons/frame_caption_button_container_view.h
@@ -6,23 +6,25 @@
#define ASH_WM_CAPTION_BUTTONS_FRAME_CAPTION_BUTTON_CONTAINER_VIEW_H_
#include "ash/ash_export.h"
+#include "ash/wm/caption_buttons/alternate_frame_size_button_delegate.h"
#include "ui/gfx/image/image_skia.h"
#include "ui/views/controls/button/button.h"
#include "ui/views/view.h"
namespace views {
-class ImageButton;
class Widget;
}
namespace ash {
+class FrameCaptionButton;
class FrameMaximizeButton;
// Container view for the frame caption buttons. It performs the appropriate
// action when a caption button is clicked.
class ASH_EXPORT FrameCaptionButtonContainerView
: public views::View,
- public views::ButtonListener {
+ public views::ButtonListener,
+ public AlternateFrameSizeButtonDelegate {
public:
static const char kViewClassName[];
@@ -54,15 +56,15 @@ class ASH_EXPORT FrameCaptionButtonContainerView
: container_view_(container_view) {
}
- views::ImageButton* minimize_button() const {
+ FrameCaptionButton* minimize_button() const {
return container_view_->minimize_button_;
}
- views::ImageButton* size_button() const {
+ FrameCaptionButton* size_button() const {
return container_view_->size_button_;
}
- views::ImageButton* close_button() const {
+ FrameCaptionButton* close_button() const {
return container_view_->close_button_;
}
@@ -102,11 +104,15 @@ class ASH_EXPORT FrameCaptionButtonContainerView
virtual void ButtonPressed(views::Button* sender,
const ui::Event& event) OVERRIDE;
- // Sets the images for a button based on the given ids.
- void SetButtonImages(views::ImageButton* button,
- int normal_image_id,
- int hot_image_id,
- int pushed_image_id);
+ // AlternateFrameSizeButton::Delegate overrides:
+ virtual bool IsMinimizeButtonVisible() const OVERRIDE;
+ virtual void SetButtonsToNormal(Animate animate) OVERRIDE;
+ 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;
// The widget that the buttons act on.
views::Widget* frame_;
@@ -118,9 +124,9 @@ class ASH_EXPORT FrameCaptionButtonContainerView
// The buttons. In the normal button style, at most one of |minimize_button_|
// and |size_button_| is visible.
- views::ImageButton* minimize_button_;
- views::ImageButton* size_button_;
- views::ImageButton* close_button_;
+ FrameCaptionButton* minimize_button_;
+ FrameCaptionButton* size_button_;
+ FrameCaptionButton* close_button_;
DISALLOW_COPY_AND_ASSIGN(FrameCaptionButtonContainerView);
};
diff --git a/ash/wm/caption_buttons/frame_caption_button_container_view_unittest.cc b/ash/wm/caption_buttons/frame_caption_button_container_view_unittest.cc
index 6737b92..5891231 100644
--- a/ash/wm/caption_buttons/frame_caption_button_container_view_unittest.cc
+++ b/ash/wm/caption_buttons/frame_caption_button_container_view_unittest.cc
@@ -6,13 +6,12 @@
#include "ash/ash_switches.h"
#include "ash/test/ash_test_base.h"
+#include "ash/wm/caption_buttons/frame_caption_button.h"
#include "base/command_line.h"
#include "grit/ash_resources.h"
#include "ui/aura/root_window.h"
#include "ui/base/resource/resource_bundle.h"
#include "ui/views/border.h"
-#include "ui/views/controls/button/custom_button.h"
-#include "ui/views/controls/button/image_button.h"
#include "ui/views/widget/widget.h"
#include "ui/views/widget/widget_delegate.h"
@@ -68,8 +67,8 @@ class FrameCaptionButtonContainerViewTest : public ash::test::AshTestBase {
// Tests that |leftmost| and |rightmost| are at |container|'s edges.
bool CheckButtonsAtEdges(FrameCaptionButtonContainerView* container,
- const views::CustomButton& leftmost,
- const views::CustomButton& rightmost) {
+ const ash::FrameCaptionButton& leftmost,
+ const ash::FrameCaptionButton& rightmost) {
gfx::Rect expected(container->GetPreferredSize());
gfx::Rect container_size(container->GetPreferredSize());
@@ -89,7 +88,7 @@ class FrameCaptionButtonContainerViewTest : public ash::test::AshTestBase {
}
// Returns true if the images for |button|'s states match the passed in ids.
- bool ImagesMatch(views::ImageButton* button,
+ bool ImagesMatch(ash::FrameCaptionButton* button,
int normal_image_id,
int hovered_image_id,
int pressed_image_id) {
@@ -244,7 +243,6 @@ class FrameCaptionButtonContainerViewTestAlternateStyle
FrameCaptionButtonContainerViewTest::SetUp();
CommandLine::ForCurrentProcess()->AppendSwitch(
switches::kAshEnableAlternateFrameCaptionButtonStyle);
- ASSERT_TRUE(switches::UseAlternateFrameCaptionButtonStyle());
}
private:
@@ -254,6 +252,11 @@ class FrameCaptionButtonContainerViewTestAlternateStyle
// Test how the alternate button style affects which buttons are visible in the
// default case.
TEST_F(FrameCaptionButtonContainerViewTestAlternateStyle, ButtonVisibility) {
+ // Using the alternate caption button style is dependant on all snapped
+ // windows being 50% of the screen's width.
+ if (!switches::UseAlternateFrameCaptionButtonStyle())
+ return;
+
// Both the minimize button and the maximize button should be visible when
// both minimizing and maximizing are allowed when using the alternate
// button style.
diff --git a/ash/wm/caption_buttons/frame_maximize_button.cc b/ash/wm/caption_buttons/frame_maximize_button.cc
index d30cebb..5a610e5 100644
--- a/ash/wm/caption_buttons/frame_maximize_button.cc
+++ b/ash/wm/caption_buttons/frame_maximize_button.cc
@@ -4,7 +4,6 @@
#include "ash/wm/caption_buttons/frame_maximize_button.h"
-#include "ash/launcher/launcher.h"
#include "ash/screen_ash.h"
#include "ash/shelf/shelf_widget.h"
#include "ash/shell.h"
@@ -82,7 +81,7 @@ void FrameMaximizeButton::EscapeEventFilter::OnKeyEvent(
FrameMaximizeButton::FrameMaximizeButton(views::ButtonListener* listener,
views::Widget* frame)
- : ImageButton(listener),
+ : FrameCaptionButton(listener, CAPTION_BUTTON_ICON_MAXIMIZE_RESTORE),
frame_(frame),
observing_frame_(false),
is_snap_enabled_(false),
diff --git a/ash/wm/caption_buttons/frame_maximize_button.h b/ash/wm/caption_buttons/frame_maximize_button.h
index c60f7a5..a1bb991 100644
--- a/ash/wm/caption_buttons/frame_maximize_button.h
+++ b/ash/wm/caption_buttons/frame_maximize_button.h
@@ -6,13 +6,13 @@
#define ASH_WM_CAPTION_BUTTONS_FRAME_MAXIMIZE_BUTTON_H_
#include "ash/ash_export.h"
-#include "ash/wm/caption_buttons/maximize_bubble_frame_state.h"
+#include "ash/wm/caption_buttons/caption_button_types.h"
+#include "ash/wm/caption_buttons/frame_caption_button.h"
#include "ash/wm/workspace/snap_types.h"
#include "base/memory/scoped_ptr.h"
#include "base/observer_list.h"
#include "base/timer/timer.h"
#include "ui/aura/window_observer.h"
-#include "ui/views/controls/button/image_button.h"
#include "ui/views/widget/widget_observer.h"
namespace views {
@@ -30,7 +30,7 @@ class SnapSizer;
class MaximizeBubbleController;
// Button used for the maximize control on the frame. Handles snapping logic.
-class ASH_EXPORT FrameMaximizeButton : public views::ImageButton,
+class ASH_EXPORT FrameMaximizeButton : public FrameCaptionButton,
public views::WidgetObserver,
public aura::WindowObserver {
public:
diff --git a/ash/wm/caption_buttons/maximize_bubble_controller.h b/ash/wm/caption_buttons/maximize_bubble_controller.h
index 9a842e5..53a07dd 100644
--- a/ash/wm/caption_buttons/maximize_bubble_controller.h
+++ b/ash/wm/caption_buttons/maximize_bubble_controller.h
@@ -6,7 +6,7 @@
#define ASH_WM_CAPTION_BUTTONS_MAXIMIZE_BUBBLE_CONTROLLER_H_
#include "ash/ash_export.h"
-#include "ash/wm/caption_buttons/maximize_bubble_frame_state.h"
+#include "ash/wm/caption_buttons/caption_button_types.h"
#include "ash/wm/workspace/snap_types.h"
#include "base/memory/scoped_ptr.h"
diff --git a/ash/wm/caption_buttons/maximize_bubble_frame_state.h b/ash/wm/caption_buttons/maximize_bubble_frame_state.h
deleted file mode 100644
index 1737818..0000000
--- a/ash/wm/caption_buttons/maximize_bubble_frame_state.h
+++ /dev/null
@@ -1,20 +0,0 @@
-// Copyright 2013 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_CAPTION_BUTTONS_MAXIMIZE_BUBBLE_FRAME_STATE_H_
-#define ASH_WM_CAPTION_BUTTONS_MAXIMIZE_BUBBLE_FRAME_STATE_H_
-
-namespace ash {
-
-// These are the types of maximization we know.
-enum MaximizeBubbleFrameState {
- FRAME_STATE_NONE = 0,
- FRAME_STATE_FULL = 1, // This is the full maximized state.
- FRAME_STATE_SNAP_LEFT = 2,
- FRAME_STATE_SNAP_RIGHT = 3
-};
-
-} // namespace views
-
-#endif // ASH_WM_CAPTION_BUTTONS_MAXIMIZE_BUBBLE_FRAME_STATE_H_