diff options
author | pkotwicz@chromium.org <pkotwicz@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2013-12-07 21:54:02 +0000 |
---|---|---|
committer | pkotwicz@chromium.org <pkotwicz@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2013-12-07 21:54:02 +0000 |
commit | d2a0359240097acae9f5e7da6b0595f7d3c68c98 (patch) | |
tree | b5ab301f9ec75802011977b4cfd3f5beacadb360 /ash/wm | |
parent | 2a10756f8c045bd8e1e31c901fc3127881a211c3 (diff) | |
download | chromium_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')
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_ |