diff options
author | pkotwicz@chromium.org <pkotwicz@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2013-09-12 02:04:53 +0000 |
---|---|---|
committer | pkotwicz@chromium.org <pkotwicz@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2013-09-12 02:04:53 +0000 |
commit | 0e60de7a85d03497a3dffab2391324aaca317b42 (patch) | |
tree | 5c31e2d6870e0437ad0663dbf64adf5daf0fa1da /ash/wm | |
parent | 56f10afdd3053c149af1b893d8363ebe13e858cb (diff) | |
download | chromium_src-0e60de7a85d03497a3dffab2391324aaca317b42.zip chromium_src-0e60de7a85d03497a3dffab2391324aaca317b42.tar.gz chromium_src-0e60de7a85d03497a3dffab2391324aaca317b42.tar.bz2 |
Basic implementation of the bubble burst effect for the new caption button style.
BUG=271304
TEST=Visual, see bug
Review URL: https://chromiumcodereview.appspot.com/23532030
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@222700 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'ash/wm')
-rw-r--r-- | ash/wm/custom_frame_view_ash_unittest.cc | 4 | ||||
-rw-r--r-- | ash/wm/workspace/alternate_frame_caption_button.cc | 230 | ||||
-rw-r--r-- | ash/wm/workspace/alternate_frame_caption_button.h | 69 | ||||
-rw-r--r-- | ash/wm/workspace/frame_caption_button_container_view.cc | 251 | ||||
-rw-r--r-- | ash/wm/workspace/frame_caption_button_container_view.h | 44 | ||||
-rw-r--r-- | ash/wm/workspace/frame_caption_button_container_view_unittest.cc | 144 |
6 files changed, 618 insertions, 124 deletions
diff --git a/ash/wm/custom_frame_view_ash_unittest.cc b/ash/wm/custom_frame_view_ash_unittest.cc index 7c81b05..0499f29 100644 --- a/ash/wm/custom_frame_view_ash_unittest.cc +++ b/ash/wm/custom_frame_view_ash_unittest.cc @@ -4,6 +4,7 @@ #include "ash/wm/custom_frame_view_ash.h" +#include "ash/ash_switches.h" #include "ash/shell.h" #include "ash/test/ash_test_base.h" #include "ash/wm/maximize_bubble_controller.h" @@ -114,6 +115,9 @@ class CustomFrameViewAshTest : public ash::test::AshTestBase { virtual void SetUp() OVERRIDE { AshTestBase::SetUp(); + CommandLine::ForCurrentProcess()->AppendSwitch( + switches::kAshDisableAlternateFrameCaptionButtonStyle); + widget_ = CreateWidget(); CustomFrameViewAsh* frame = static_cast<CustomFrameViewAsh*>( widget()->non_client_view()->frame_view()); diff --git a/ash/wm/workspace/alternate_frame_caption_button.cc b/ash/wm/workspace/alternate_frame_caption_button.cc new file mode 100644 index 0000000..ffdb539 --- /dev/null +++ b/ash/wm/workspace/alternate_frame_caption_button.cc @@ -0,0 +1,230 @@ +// 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/workspace/alternate_frame_caption_button.h" + +#include "ui/base/animation/slide_animation.h" +#include "ui/gfx/canvas.h" + +namespace ash { + +namespace { + +// The width and height of the region of the button which does not overlap +// with other buttons. +const int kSize = 32; + +// A bubble is painted in the background when the mouse button is pressed. +// When the button is pressed: +// - The bubble is faded in from opacity 0 to |kShownBubbleOpacity|. +// - The bubble is expanded from |kInitialGrowBubbleRadius| to +// |kFullyGrownBubbleRadius|. +// When the button is unpressed (STATE_NORMAL) +// - The bubble is faded out from its current opacity back to 0. +// - The bubble is further expanded from its current radius to +// |kFinalBurstBubbleRadius|. +const int kInitialGrowBubbleRadius = 16; +const int kFullyGrownBubbleRadius = 22; +const int kFinalBurstBubbleRadius = 26; +const int kShownBubbleOpacity = 100; + +// The duraton of the animations for hiding and showing the bubble. +const int kBubbleAnimationDuration = 100; + +// TODO(pkotwicz): Replace these colors with colors from UX. +const SkColor kPathColor = SK_ColorDKGRAY; +const SkColor kPressedHoveredPathColor = SK_ColorBLACK; +const SkColor kBubbleColor = SK_ColorWHITE; + +struct Line { + int x1, y1, x2, y2; +}; + +// Line segments for the button icons. The line segments are painted in a 12x12 +// centered box. +const Line kMinimizeLineSegments[] = { + {1, 11, 11, 11} +}; +const Line kMaximizeRestoreLineSegments[] = { + {1, 1, 11, 1}, + {11, 1, 11, 11}, + {11, 11, 1, 11}, + {1, 11, 1, 1} +}; +const Line kCloseLineSegments[] = { + {1, 1, 11, 11}, + {1, 11, 11, 1} +}; + +// The amount that the origin of the icon's 12x12 box should be offset from the +// center of the button. +const int kIconOffsetFromCenter = -6; + +// Sets |line_segments| to the line segments for the icon for |action|. +void GetLineSegmentsForAction(AlternateFrameCaptionButton::Action action, + const Line** line_segments, + size_t* num_line_segments) { + switch(action) { + case AlternateFrameCaptionButton::ACTION_MINIMIZE: + *line_segments = kMinimizeLineSegments; + *num_line_segments = arraysize(kMinimizeLineSegments); + break; + case AlternateFrameCaptionButton::ACTION_MAXIMIZE_RESTORE: + *line_segments = kMaximizeRestoreLineSegments; + *num_line_segments = arraysize(kMaximizeRestoreLineSegments); + break; + case AlternateFrameCaptionButton::ACTION_CLOSE: + *line_segments = kCloseLineSegments; + *num_line_segments = arraysize(kCloseLineSegments); + break; + default: + NOTREACHED(); + break; + } +} + +} // namespace + +const char AlternateFrameCaptionButton::kViewClassName[] = + "AlternateFrameCaptionButton"; + +AlternateFrameCaptionButton::AlternateFrameCaptionButton( + views::ButtonListener* listener, + Action action) + : views::CustomButton(listener), + action_(action), + hidden_bubble_radius_(0), + shown_bubble_radius_(0), + bubble_animation_(new ui::SlideAnimation(this)) { +} + +AlternateFrameCaptionButton::~AlternateFrameCaptionButton() { +} + +// static +int AlternateFrameCaptionButton::GetXOverlap() { + return kFinalBurstBubbleRadius - kSize / 2; +} + +gfx::Size AlternateFrameCaptionButton::GetPreferredSize() { + gfx::Insets insets(GetInsets()); + return gfx::Size( + std::max(kFinalBurstBubbleRadius * 2, kSize + insets.width()), + kSize + insets.height()); +} + +const char* AlternateFrameCaptionButton::GetClassName() const { + return kViewClassName; +} + +bool AlternateFrameCaptionButton::HitTestRect(const gfx::Rect& rect) const { + gfx::Rect bounds(GetLocalBounds()); + if (state_ == STATE_PRESSED) + return bounds.Intersects(rect); + + int x_overlap = GetXOverlap(); + bounds.set_x(x_overlap); + bounds.set_width(width() - x_overlap * 2); + return bounds.Intersects(rect); +} + +void AlternateFrameCaptionButton::OnPaint(gfx::Canvas* canvas) { + gfx::Point content_bounds_center(GetContentsBounds().CenterPoint()); + + int bubble_alpha = bubble_animation_->CurrentValueBetween( + 0, kShownBubbleOpacity); + if (bubble_alpha != 0) { + int bubble_radius = bubble_animation_->CurrentValueBetween( + hidden_bubble_radius_, shown_bubble_radius_); + + SkPaint paint; + paint.setAntiAlias(true); + paint.setStyle(SkPaint::kFill_Style); + paint.setColor(SkColorSetA(kBubbleColor, bubble_alpha)); + canvas->DrawCircle(content_bounds_center, bubble_radius, paint); + } + + SkColor color = kPathColor; + if (state_ == STATE_HOVERED || state_ == STATE_PRESSED) + color = kPressedHoveredPathColor; + + const Line* line_segments = NULL; + size_t num_line_segments = 0; + GetLineSegmentsForAction(action_, &line_segments, &num_line_segments); + + gfx::Vector2d top_left_offset( + content_bounds_center.x() + kIconOffsetFromCenter, + content_bounds_center.y() + kIconOffsetFromCenter); + canvas->Translate(top_left_offset); + SkPaint paint; + paint.setStyle(SkPaint::kStroke_Style); + paint.setStrokeWidth(SkIntToScalar(2)); + paint.setStrokeCap(SkPaint::kSquare_Cap); + paint.setColor(color); + for (size_t i = 0; i < num_line_segments; ++i) { + canvas->DrawLine(gfx::Point(line_segments[i].x1, line_segments[i].y1), + gfx::Point(line_segments[i].x2, line_segments[i].y2), + paint); + } + canvas->Translate(-top_left_offset); +} + +void AlternateFrameCaptionButton::MaybeStartNewBubbleAnimation() { + bool should_show = (state_ == STATE_PRESSED); + if (should_show == bubble_animation_->IsShowing()) + return; + + if (!bubble_animation_->is_animating()) { + if (should_show) + hidden_bubble_radius_ = kInitialGrowBubbleRadius; + else + hidden_bubble_radius_ = kFinalBurstBubbleRadius; + shown_bubble_radius_ = kFullyGrownBubbleRadius; + + bubble_animation_->SetSlideDuration(kBubbleAnimationDuration); + if (should_show) + bubble_animation_->Show(); + else + bubble_animation_->Hide(); + } else { + if (!should_show) { + // The change in radius during a hide animation if there was no currently + // running animation. + int normal_radius_change = + kFinalBurstBubbleRadius - kFullyGrownBubbleRadius; + + // Start a fade out animation from the bubble's current radius and + // opacity. Update the bubble radius and opacity at the same rate that it + // gets updated during a normal hide animation. + int current_bubble_radius = bubble_animation_->CurrentValueBetween( + kInitialGrowBubbleRadius, kFullyGrownBubbleRadius); + hidden_bubble_radius_ = current_bubble_radius + + bubble_animation_->GetCurrentValue() * normal_radius_change; + shown_bubble_radius_ = hidden_bubble_radius_ - normal_radius_change; + bubble_animation_->SetSlideDuration( + bubble_animation_->GetCurrentValue() * kBubbleAnimationDuration); + bubble_animation_->Hide(); + } + // Else: The bubble is currently fading out. Wait till the hide animation + // completes before starting an animation to show a new bubble. + } +} + +void AlternateFrameCaptionButton::StateChanged() { + MaybeStartNewBubbleAnimation(); +} + +void AlternateFrameCaptionButton::AnimationProgressed( + const ui::Animation* animation) { + SchedulePaint(); +} + +void AlternateFrameCaptionButton::AnimationEnded( + const ui::Animation* animation) { + // The bubble animation was postponed if the button became pressed when the + // bubble was fading out. Do the animation now. + MaybeStartNewBubbleAnimation(); +} + +} // namespace ash diff --git a/ash/wm/workspace/alternate_frame_caption_button.h b/ash/wm/workspace/alternate_frame_caption_button.h new file mode 100644 index 0000000..b0f9581 --- /dev/null +++ b/ash/wm/workspace/alternate_frame_caption_button.h @@ -0,0 +1,69 @@ +// 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_WORKSPACE_ALTERNATE_FRAME_CAPTION_BUTTON_H_ +#define ASH_WM_WORKSPACE_ALTERNATE_FRAME_CAPTION_BUTTON_H_ + +#include "ash/ash_export.h" +#include "base/memory/scoped_ptr.h" +#include "ui/views/controls/button/custom_button.h" + +namespace ui { +class SlideAnimation; +} + +namespace ash { + +// Base class for buttons using the alternate button style. +class ASH_EXPORT AlternateFrameCaptionButton : public views::CustomButton { + public: + static const char kViewClassName[]; + + enum Action { + ACTION_MINIMIZE, + ACTION_MAXIMIZE_RESTORE, + ACTION_CLOSE + }; + + AlternateFrameCaptionButton(views::ButtonListener* listener, Action action); + virtual ~AlternateFrameCaptionButton(); + + // Returns the amount in pixels that the button should overlap with the button + // on the left and right of it. + static int GetXOverlap(); + + // views::View overrides: + virtual gfx::Size GetPreferredSize() OVERRIDE; + virtual const char* GetClassName() const OVERRIDE; + virtual bool HitTestRect(const gfx::Rect& rect) const OVERRIDE; + virtual void OnPaint(gfx::Canvas* canvas) OVERRIDE; + + private: + // Animates the background bubble for the current views::ButtonState. + void MaybeStartNewBubbleAnimation(); + + // views::CustomButton override: + virtual void StateChanged() OVERRIDE; + + // ui::AnimateDelegate overrides. (views::CustomButton inherits from + // ui::AnimationDelegate). + virtual void AnimationProgressed(const ui::Animation* animation) OVERRIDE; + virtual void AnimationEnded(const ui::Animation* animation) OVERRIDE; + + Action action_; + + // The radius of the background bubble when it is hidden. + double hidden_bubble_radius_; + + // The radius of the background bubble when it is visible. + double shown_bubble_radius_; + + scoped_ptr<ui::SlideAnimation> bubble_animation_; + + DISALLOW_COPY_AND_ASSIGN(AlternateFrameCaptionButton); +}; + +} // namespace ash + +#endif // ASH_WM_WORKSPACE_ALTERNATE_FRAME_CAPTION_BUTTON_H_ diff --git a/ash/wm/workspace/frame_caption_button_container_view.cc b/ash/wm/workspace/frame_caption_button_container_view.cc index ddf602f..264c57a 100644 --- a/ash/wm/workspace/frame_caption_button_container_view.cc +++ b/ash/wm/workspace/frame_caption_button_container_view.cc @@ -4,9 +4,11 @@ #include "ash/wm/workspace/frame_caption_button_container_view.h" +#include "ash/ash_switches.h" #include "ash/shell.h" #include "ash/shell_delegate.h" #include "ash/wm/window_settings.h" +#include "ash/wm/workspace/alternate_frame_caption_button.h" #include "ash/wm/workspace/frame_maximize_button.h" #include "grit/ash_resources.h" #include "grit/ui_strings.h" // Accessibility names @@ -15,6 +17,7 @@ #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/views/controls/button/image_button.h" #include "ui/views/widget/widget.h" #include "ui/views/widget/widget_delegate.h" @@ -24,8 +27,36 @@ namespace ash { namespace { -// The space between the buttons. -const int kButtonGap = -1; +// Constants for normal button style ------------------------------------------- + +// The distance between buttons. AlternateFrameCaptionButton::GetXOverlap() is +// used to compute the distance between buttons for the alternate button style. +const int kDistanceBetweenButtons = -1; + +// Constants for alternate button style ---------------------------------------- + +// Spacings between the buttons and the view's top and bottom borders (if any). +const int kAlternateStyleShortHeaderTopInset = 0; +const int kAlternateStyleTallHeaderTopInset = 2; +const int kAlternateStyleShortHeaderBottomInset = 0; +const int kAlternateStyleTallHeaderBottomInset = 1; + +// Ideal spacing between: +// - Right edge of the leftmost button's overlappable region and the view's left +// edge. +// - Left edge of the rightmost button's overlappable region and the view's +// right edge. +// Used in GetLeftInset() and GetRightInset(). +const int kAlternateStyleSideInset = 5; + +// Converts |point| from |src| to |dst| and hittests against |dst|. +bool ConvertPointToViewAndHitTest(const views::View* src, + const views::View* dst, + const gfx::Point& point) { + gfx::Point converted(point); + views::View::ConvertPointToTarget(src, dst, &converted); + return dst->HitTestPoint(converted); +} } // namespace @@ -39,18 +70,34 @@ FrameCaptionButtonContainerView::FrameCaptionButtonContainerView( MinimizeAllowed minimize_allowed) : frame_(frame), header_style_(HEADER_STYLE_SHORT), - minimize_button_(new views::ImageButton(this)), - size_button_(new FrameMaximizeButton(this, frame_view)), - close_button_(new views::ImageButton(this)) { + minimize_button_(NULL), + size_button_(NULL), + close_button_(NULL) { + bool alternate_style = switches::UseAlternateFrameCaptionButtonStyle(); + // Insert the buttons left to right. + if (alternate_style) { + minimize_button_ = new AlternateFrameCaptionButton(this, + AlternateFrameCaptionButton::ACTION_MINIMIZE); + size_button_ = new AlternateFrameCaptionButton(this, + AlternateFrameCaptionButton::ACTION_MAXIMIZE_RESTORE); + close_button_ = new AlternateFrameCaptionButton(this, + AlternateFrameCaptionButton::ACTION_CLOSE); + } else { + minimize_button_ = new views::ImageButton(this); + size_button_ = new FrameMaximizeButton(this, frame_view); + close_button_ = new views::ImageButton(this); + } + minimize_button_->SetAccessibleName( l10n_util::GetStringUTF16(IDS_APP_ACCNAME_MINIMIZE)); - // Hide |minimize_button_| when |size_button_| is visible because - // |size_button_| is capable of minimizing. + // Hide |minimize_button_| when using the non-alternate button style because + // |size_button_| is capable of minimizing in this case. // TODO(pkotwicz): We should probably show the minimize button when in // "always maximized" mode. - minimize_button_->SetVisible(minimize_allowed == MINIMIZE_ALLOWED && - !frame_->widget_delegate()->CanMaximize()); + minimize_button_->SetVisible( + minimize_allowed == MINIMIZE_ALLOWED && + (alternate_style || !frame_->widget_delegate()->CanMaximize())); AddChildView(minimize_button_); size_button_->SetAccessibleName( @@ -78,19 +125,21 @@ void FrameCaptionButtonContainerView::ResetWindowControls() { int FrameCaptionButtonContainerView::NonClientHitTest( const gfx::Point& point) const { if (close_button_->visible() && - close_button_->GetMirroredBounds().Contains(point)) { + ConvertPointToViewAndHitTest(this, close_button_, point)) { return HTCLOSE; } else if (size_button_->visible() && - size_button_->GetMirroredBounds().Contains(point)) { + ConvertPointToViewAndHitTest(this, size_button_, point)) { return HTMAXBUTTON; } else if (minimize_button_->visible() && - minimize_button_->GetMirroredBounds().Contains(point)) { + ConvertPointToViewAndHitTest(this, minimize_button_, point)) { return HTMINBUTTON; } return HTNOWHERE; } gfx::Size FrameCaptionButtonContainerView::GetPreferredSize() { + int button_separation = GetDistanceBetweenButtons(); + int width = 0; bool first_visible = true; for (int i = 0; i < child_count(); ++i) { @@ -100,68 +149,85 @@ gfx::Size FrameCaptionButtonContainerView::GetPreferredSize() { width += child_at(i)->GetPreferredSize().width(); if (!first_visible) - width += kButtonGap; + width += button_separation; first_visible = false; } gfx::Insets insets(GetInsets()); return gfx::Size( - width + insets.width(), + width + insets.width() + GetLeftInset() + GetRightInset(), close_button_->GetPreferredSize().height() + insets.height()); } void FrameCaptionButtonContainerView::Layout() { - SetButtonImages(minimize_button_, - IDR_AURA_WINDOW_MINIMIZE_SHORT, - IDR_AURA_WINDOW_MINIMIZE_SHORT_H, - IDR_AURA_WINDOW_MINIMIZE_SHORT_P); - - if (header_style_ == HEADER_STYLE_MAXIMIZED_HOSTED_APP) { - SetButtonImages(size_button_, - IDR_AURA_WINDOW_FULLSCREEN_RESTORE, - IDR_AURA_WINDOW_FULLSCREEN_RESTORE_H, - IDR_AURA_WINDOW_FULLSCREEN_RESTORE_P); - SetButtonImages(close_button_, - IDR_AURA_WINDOW_FULLSCREEN_CLOSE, - IDR_AURA_WINDOW_FULLSCREEN_CLOSE_H, - IDR_AURA_WINDOW_FULLSCREEN_CLOSE_P); - } else 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()) && - wm::GetWindowSettings( - frame_->GetNativeWindow())->tracked_by_workspace()) { + if (switches::UseAlternateFrameCaptionButtonStyle()) { + int top_inset = kAlternateStyleShortHeaderTopInset; + int bottom_inset = kAlternateStyleShortHeaderBottomInset; + if (header_style_ == HEADER_STYLE_TALL) { + top_inset = kAlternateStyleTallHeaderTopInset; + bottom_inset = kAlternateStyleTallHeaderBottomInset; + } + + minimize_button_->set_border( + views::Border::CreateEmptyBorder(top_inset, 0, bottom_inset, 0)); + size_button_->set_border( + views::Border::CreateEmptyBorder(top_inset, 0, bottom_inset, 0)); + close_button_->set_border( + views::Border::CreateEmptyBorder(top_inset, 0, bottom_inset, 0)); + } else { + SetButtonImages(minimize_button_, + IDR_AURA_WINDOW_MINIMIZE_SHORT, + IDR_AURA_WINDOW_MINIMIZE_SHORT_H, + IDR_AURA_WINDOW_MINIMIZE_SHORT_P); + + if (header_style_ == HEADER_STYLE_MAXIMIZED_HOSTED_APP) { SetButtonImages(size_button_, - IDR_AURA_WINDOW_MAXIMIZED_RESTORE2, - IDR_AURA_WINDOW_MAXIMIZED_RESTORE2_H, - IDR_AURA_WINDOW_MAXIMIZED_RESTORE2_P); + IDR_AURA_WINDOW_FULLSCREEN_RESTORE, + IDR_AURA_WINDOW_FULLSCREEN_RESTORE_H, + IDR_AURA_WINDOW_FULLSCREEN_RESTORE_P); SetButtonImages(close_button_, - IDR_AURA_WINDOW_MAXIMIZED_CLOSE2, - IDR_AURA_WINDOW_MAXIMIZED_CLOSE2_H, - IDR_AURA_WINDOW_MAXIMIZED_CLOSE2_P); + IDR_AURA_WINDOW_FULLSCREEN_CLOSE, + IDR_AURA_WINDOW_FULLSCREEN_CLOSE_H, + IDR_AURA_WINDOW_FULLSCREEN_CLOSE_P); + } else 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()) && + wm::GetWindowSettings( + frame_->GetNativeWindow())->tracked_by_workspace()) { + 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); + } } else { SetButtonImages(size_button_, - IDR_AURA_WINDOW_MAXIMIZED_RESTORE, - IDR_AURA_WINDOW_MAXIMIZED_RESTORE_H, - IDR_AURA_WINDOW_MAXIMIZED_RESTORE_P); + IDR_AURA_WINDOW_MAXIMIZE, + IDR_AURA_WINDOW_MAXIMIZE_H, + IDR_AURA_WINDOW_MAXIMIZE_P); SetButtonImages(close_button_, - IDR_AURA_WINDOW_MAXIMIZED_CLOSE, - IDR_AURA_WINDOW_MAXIMIZED_CLOSE_H, - IDR_AURA_WINDOW_MAXIMIZED_CLOSE_P); + IDR_AURA_WINDOW_CLOSE, + IDR_AURA_WINDOW_CLOSE_H, + IDR_AURA_WINDOW_CLOSE_P); } - } 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); } gfx::Insets insets(GetInsets()); - int x = insets.left(); + int x = insets.left() + GetLeftInset(); int y_inset = insets.top(); + int button_separation = GetDistanceBetweenButtons(); for (int i = 0; i < child_count(); ++i) { views::View* child = child_at(i); if (!child->visible()) @@ -169,7 +235,12 @@ void FrameCaptionButtonContainerView::Layout() { gfx::Size size = child->GetPreferredSize(); child->SetBounds(x, y_inset, size.width(), size.height()); - x += size.width() + kButtonGap; + + // Do not allow |child| to paint over the left border. + child->set_clip_insets( + gfx::Insets(0, std::max(0, insets.left() - x), 0, 0)); + + x += size.width() + button_separation; } } @@ -180,8 +251,10 @@ const char* FrameCaptionButtonContainerView::GetClassName() const { void FrameCaptionButtonContainerView::OnPaint(gfx::Canvas* canvas) { views::View::OnPaint(canvas); - // AppNonClientFrameViewAsh does not paint the button separator. - if (header_style_ != HEADER_STYLE_MAXIMIZED_HOSTED_APP) { + // The alternate button style and AppNonClientFrameViewAsh do not paint the + // button separator. + if (header_style_ != HEADER_STYLE_MAXIMIZED_HOSTED_APP && + !switches::UseAlternateFrameCaptionButtonStyle()) { // We should have at most two visible buttons. The button separator is // always painted underneath the close button regardless of whether a // button other than the close button is visible. @@ -193,18 +266,36 @@ void FrameCaptionButtonContainerView::OnPaint(gfx::Canvas* canvas) { } } -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)); +int FrameCaptionButtonContainerView::GetDistanceBetweenButtons() const { + if (switches::UseAlternateFrameCaptionButtonStyle()) + return AlternateFrameCaptionButton::GetXOverlap() * -2; + return kDistanceBetweenButtons; +} + +int FrameCaptionButtonContainerView::GetLeftInset() const { + if (switches::UseAlternateFrameCaptionButtonStyle()) { + // If using the alternate button style and there is a border, clip the + // left overlappable region of the leftmost button to + // |kAlternateStyleSideInset|. + // Otherwise, allow enough room for the entire left overlappable region of + // the leftmost button to fit in the view. + if (border() && border()->GetInsets().left()) { + return kAlternateStyleSideInset - + AlternateFrameCaptionButton::GetXOverlap(); + } + } + return 0; +} + +int FrameCaptionButtonContainerView::GetRightInset() const { + if (switches::UseAlternateFrameCaptionButtonStyle()) { + // Always clip the right overlappable region of the rightmost button to + // |kAlternateStyleSideInset| because the caption buttons are always + // at the right edge of the screen. (The left edge in RTL mode). + return kAlternateStyleSideInset - + AlternateFrameCaptionButton::GetXOverlap(); + } + return 0; } void FrameCaptionButtonContainerView::ButtonPressed(views::Button* sender, @@ -246,4 +337,22 @@ void FrameCaptionButtonContainerView::ButtonPressed(views::Button* sender, ash::Shell::GetInstance()->delegate()->RecordUserMetricsAction(action); } +void FrameCaptionButtonContainerView::SetButtonImages( + views::CustomButton* button, + int normal_image_id, + int hot_image_id, + int pushed_image_id) { + // When using the alternate button style, |button| does not inherit from + // views::ImageButton. + DCHECK(!switches::UseAlternateFrameCaptionButtonStyle()); + views::ImageButton* image_button = static_cast<views::ImageButton*>(button); + ui::ResourceBundle& resource_bundle = ui::ResourceBundle::GetSharedInstance(); + image_button->SetImage(views::CustomButton::STATE_NORMAL, + resource_bundle.GetImageSkiaNamed(normal_image_id)); + image_button->SetImage(views::CustomButton::STATE_HOVERED, + resource_bundle.GetImageSkiaNamed(hot_image_id)); + image_button->SetImage(views::CustomButton::STATE_PRESSED, + resource_bundle.GetImageSkiaNamed(pushed_image_id)); +} + } // namespace ash diff --git a/ash/wm/workspace/frame_caption_button_container_view.h b/ash/wm/workspace/frame_caption_button_container_view.h index b1bb377..be76e77 100644 --- a/ash/wm/workspace/frame_caption_button_container_view.h +++ b/ash/wm/workspace/frame_caption_button_container_view.h @@ -11,7 +11,7 @@ #include "ui/views/view.h" namespace views { -class ImageButton; +class CustomButton; class NonClientFrameView; class Widget; } @@ -63,15 +63,15 @@ class ASH_EXPORT FrameCaptionButtonContainerView : container_view_(container_view) { } - views::ImageButton* minimize_button() const { + views::CustomButton* minimize_button() const { return container_view_->minimize_button_; } - views::ImageButton* size_button() const { + views::CustomButton* size_button() const { return container_view_->size_button_; } - views::ImageButton* close_button() const { + views::CustomButton* close_button() const { return container_view_->close_button_; } @@ -101,16 +101,32 @@ class ASH_EXPORT FrameCaptionButtonContainerView virtual void OnPaint(gfx::Canvas* canvas) OVERRIDE; private: - // 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); + friend class FrameCaptionButtonContainerViewTest; + + // Returns the distance between buttons which are next to each other. A + // negative value is returned if the buttons overlap. + int GetDistanceBetweenButtons() const; + + // Returns the inset of the leftmost visible button from the view's border + // (if any). + int GetLeftInset() const; + + // Returns the inset of the rightmost visible button from the view's border + // (if any). + int GetRightInset() const; // views::ButtonListener override: virtual void ButtonPressed(views::Button* sender, const ui::Event& event) OVERRIDE; + // Methods specific to normal button style ----------------------------------- + // + // Sets the images for a button based on the given ids. + void SetButtonImages(views::CustomButton* button, + int normal_image_id, + int hot_image_id, + int pushed_image_id); + // The widget that the buttons act on. views::Widget* frame_; @@ -119,11 +135,11 @@ class ASH_EXPORT FrameCaptionButtonContainerView HeaderStyle header_style_; - // The buttons. At most one of |minimize_button_| and |size_button_| is - // visible. - views::ImageButton* minimize_button_; - views::ImageButton* size_button_; - views::ImageButton* close_button_; + // The buttons. In the normal button style, at most one of |minimize_button_| + // and |size_button_| is visible. + views::CustomButton* minimize_button_; + views::CustomButton* size_button_; + views::CustomButton* close_button_; DISALLOW_COPY_AND_ASSIGN(FrameCaptionButtonContainerView); }; diff --git a/ash/wm/workspace/frame_caption_button_container_view_unittest.cc b/ash/wm/workspace/frame_caption_button_container_view_unittest.cc index 2f520cf..cb732bf 100644 --- a/ash/wm/workspace/frame_caption_button_container_view_unittest.cc +++ b/ash/wm/workspace/frame_caption_button_container_view_unittest.cc @@ -11,6 +11,7 @@ #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" @@ -19,42 +20,6 @@ namespace ash { namespace { -// Returns true if the images for |button|'s states match the passed in ids. -bool ImagesMatch(views::ImageButton* button, - int normal_image_id, - int hovered_image_id, - int pressed_image_id) { - ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance(); - gfx::ImageSkia* normal = rb.GetImageSkiaNamed(normal_image_id); - gfx::ImageSkia* hovered = rb.GetImageSkiaNamed(hovered_image_id); - gfx::ImageSkia* pressed = rb.GetImageSkiaNamed(pressed_image_id); - using views::Button; - return button->GetImage(Button::STATE_NORMAL).BackedBySameObjectAs(*normal) && - button->GetImage(Button::STATE_HOVERED).BackedBySameObjectAs(*hovered) && - button->GetImage(Button::STATE_PRESSED).BackedBySameObjectAs(*pressed); -} - -// Returns true if |leftmost| and |rightmost| are flush with |container|'s -// edges. -bool CheckButtonsAtEdges(FrameCaptionButtonContainerView* container, - const views::ImageButton& leftmost, - const views::ImageButton& rightmost) { - gfx::Rect container_size(container->GetPreferredSize()); - if (leftmost.y() == rightmost.y() && - leftmost.height() == rightmost.height() && - leftmost.x() == 0 && - leftmost.y() == 0 && - leftmost.height() == container_size.height() && - rightmost.bounds().right() == container_size.width()) { - return true; - } - - LOG(ERROR) << "Buttons " << leftmost.bounds().ToString() << " " - << rightmost.bounds().ToString() << " not at edges of " - << gfx::Rect(container_size).ToString(); - return false; -} - class TestWidgetDelegate : public views::WidgetDelegateView { public: TestWidgetDelegate(bool can_maximize) : can_maximize_(can_maximize) { @@ -101,12 +66,74 @@ class FrameCaptionButtonContainerViewTest : public ash::test::AshTestBase { return widget; } + // Tests that |leftmost| and |rightmost| are at |container|'s edges. + bool CheckButtonsAtEdges(FrameCaptionButtonContainerView* container, + const views::CustomButton& leftmost, + const views::CustomButton& rightmost) { + gfx::Rect expected(container->GetPreferredSize()); + expected.Inset(container->GetLeftInset(), 0, container->GetRightInset(), 0); + + gfx::Rect container_size(container->GetPreferredSize()); + if (leftmost.y() == rightmost.y() && + leftmost.height() == rightmost.height() && + leftmost.x() == expected.x() && + leftmost.y() == expected.y() && + leftmost.height() == expected.height() && + rightmost.bounds().right() == expected.right()) { + return true; + } + + LOG(ERROR) << "Buttons " << leftmost.bounds().ToString() << " " + << rightmost.bounds().ToString() << " not at edges of " + << expected.ToString(); + return false; + } + private: DISALLOW_COPY_AND_ASSIGN(FrameCaptionButtonContainerViewTest); }; +class FrameCaptionButtonContainerViewTestOldStyle + : public FrameCaptionButtonContainerViewTest { + public: + FrameCaptionButtonContainerViewTestOldStyle() { + } + + virtual ~FrameCaptionButtonContainerViewTestOldStyle() { + } + + // Returns true if the images for |button|'s states match the passed in ids. + bool ImagesMatch(views::CustomButton* custom_button, + int normal_image_id, + int hovered_image_id, + int pressed_image_id) { + views::ImageButton* button = + static_cast<views::ImageButton*>(custom_button); + ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance(); + gfx::ImageSkia* normal = rb.GetImageSkiaNamed(normal_image_id); + gfx::ImageSkia* hovered = rb.GetImageSkiaNamed(hovered_image_id); + gfx::ImageSkia* pressed = rb.GetImageSkiaNamed(pressed_image_id); + using views::Button; + gfx::ImageSkia actual_normal = button->GetImage(Button::STATE_NORMAL); + gfx::ImageSkia actual_hovered = button->GetImage(Button::STATE_HOVERED); + gfx::ImageSkia actual_pressed = button->GetImage(Button::STATE_PRESSED); + return actual_normal.BackedBySameObjectAs(*normal) && + actual_hovered.BackedBySameObjectAs(*hovered) && + actual_pressed.BackedBySameObjectAs(*pressed); + } + + virtual void SetUp() OVERRIDE { + FrameCaptionButtonContainerViewTest::SetUp(); + CommandLine::ForCurrentProcess()->AppendSwitch( + switches::kAshDisableAlternateFrameCaptionButtonStyle); + } + + private: + DISALLOW_COPY_AND_ASSIGN(FrameCaptionButtonContainerViewTestOldStyle); +}; + // Test how the allowed actions affect which caption buttons are visible. -TEST_F(FrameCaptionButtonContainerViewTest, ButtonVisibility) { +TEST_F(FrameCaptionButtonContainerViewTestOldStyle, ButtonVisibility) { // The minimize button should be hidden when both minimizing and maximizing // are allowed because the size button can do both. scoped_ptr<views::Widget> widget_can_maximize( @@ -162,7 +189,7 @@ TEST_F(FrameCaptionButtonContainerViewTest, ButtonVisibility) { } // Test the layout when a border is set on the container. -TEST_F(FrameCaptionButtonContainerViewTest, LayoutBorder) { +TEST_F(FrameCaptionButtonContainerViewTestOldStyle, LayoutBorder) { const int kTopInset = 1; const int kLeftInset = 2; const int kBottomInset = 3; @@ -185,7 +212,7 @@ TEST_F(FrameCaptionButtonContainerViewTest, LayoutBorder) { } // Test how the header style affects which images are used for the buttons. -TEST_F(FrameCaptionButtonContainerViewTest, HeaderStyle) { +TEST_F(FrameCaptionButtonContainerViewTestOldStyle, HeaderStyle) { scoped_ptr<views::Widget> widget(CreateTestWidget(MAXIMIZE_ALLOWED)); FrameCaptionButtonContainerView container(NULL, widget.get(), FrameCaptionButtonContainerView::MINIMIZE_ALLOWED); @@ -256,4 +283,43 @@ TEST_F(FrameCaptionButtonContainerViewTest, HeaderStyle) { IDR_AURA_WINDOW_FULLSCREEN_CLOSE_P)); } +class FrameCaptionButtonContainerViewTestAlternateStyle + : public FrameCaptionButtonContainerViewTest { + public: + FrameCaptionButtonContainerViewTestAlternateStyle() { + } + + virtual ~FrameCaptionButtonContainerViewTestAlternateStyle() { + } + + virtual void SetUp() OVERRIDE { + FrameCaptionButtonContainerViewTest::SetUp(); + CommandLine::ForCurrentProcess()->AppendSwitch( + switches::kAshEnableAlternateFrameCaptionButtonStyle); + ASSERT_TRUE(switches::UseAlternateFrameCaptionButtonStyle()); + } + + private: + DISALLOW_COPY_AND_ASSIGN(FrameCaptionButtonContainerViewTestAlternateStyle); +}; + +// Test how the alternate button style affects which buttons are visible in the +// default case. +TEST_F(FrameCaptionButtonContainerViewTestAlternateStyle, ButtonVisibility) { + // Both the minimize button and the maximize button should be visible when + // both minimizing and maximizing are allowed when using the alternate + // button style. + scoped_ptr<views::Widget> widget_can_maximize( + CreateTestWidget(MAXIMIZE_ALLOWED)); + FrameCaptionButtonContainerView container(NULL, widget_can_maximize.get(), + FrameCaptionButtonContainerView::MINIMIZE_ALLOWED); + container.Layout(); + FrameCaptionButtonContainerView::TestApi t(&container); + EXPECT_TRUE(t.minimize_button()->visible()); + EXPECT_TRUE(t.size_button()->visible()); + EXPECT_TRUE(t.close_button()->visible()); + EXPECT_TRUE(CheckButtonsAtEdges( + &container, *t.minimize_button(), *t.close_button())); +} + } // namespace ash |