diff options
Diffstat (limited to 'chromium/ui/views/window/frame_caption_button.cc')
-rw-r--r-- | chromium/ui/views/window/frame_caption_button.cc | 297 |
1 files changed, 297 insertions, 0 deletions
diff --git a/chromium/ui/views/window/frame_caption_button.cc b/chromium/ui/views/window/frame_caption_button.cc new file mode 100644 index 00000000000..49026c946d7 --- /dev/null +++ b/chromium/ui/views/window/frame_caption_button.cc @@ -0,0 +1,297 @@ +// 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 "ui/views/window/frame_caption_button.h" + +#include "ui/base/hit_test.h" +#include "ui/gfx/animation/slide_animation.h" +#include "ui/gfx/animation/throb_animation.h" +#include "ui/gfx/canvas.h" +#include "ui/gfx/color_palette.h" +#include "ui/gfx/color_utils.h" +#include "ui/gfx/paint_vector_icon.h" +#include "ui/views/animation/flood_fill_ink_drop_ripple.h" +#include "ui/views/animation/ink_drop_highlight.h" +#include "ui/views/animation/ink_drop_impl.h" +#include "ui/views/animation/ink_drop_mask.h" +#include "ui/views/animation/ink_drop_ripple.h" +#include "ui/views/window/caption_button_layout_constants.h" +#include "ui/views/window/hit_test_utils.h" + +namespace views { + +namespace { + +// Ink drop parameters. +constexpr float kInkDropVisibleOpacity = 0.06f; + +// The duration of the crossfade animation when swapping the button's images. +const int kSwapImagesAnimationDurationMs = 200; + +// The duration of the fade out animation of the old icon during a crossfade +// animation as a ratio of |kSwapImagesAnimationDurationMs|. +const float kFadeOutRatio = 0.5f; + +// The ratio applied to the button's alpha when the button is disabled. +const float kDisabledButtonAlphaRatio = 0.5f; + +} // namespace + +// static +const char FrameCaptionButton::kViewClassName[] = "FrameCaptionButton"; + +FrameCaptionButton::FrameCaptionButton(views::ButtonListener* listener, + CaptionButtonIcon icon, + int hit_test_type) + : Button(listener), + icon_(icon), + background_color_(SK_ColorWHITE), + paint_as_active_(false), + alpha_(255), + ink_drop_corner_radius_(kCaptionButtonInkDropDefaultCornerRadius), + swap_images_animation_(new gfx::SlideAnimation(this)) { + views::SetHitTestComponent(this, hit_test_type); + + set_animate_on_state_change(true); + swap_images_animation_->Reset(1); + + set_has_ink_drop_action_on_click(true); + SetInkDropMode(InkDropMode::ON); + set_ink_drop_visible_opacity(kInkDropVisibleOpacity); + UpdateInkDropBaseColor(); + + // Do not flip the gfx::Canvas passed to the OnPaint() method. The snap left + // and snap right button icons should not be flipped. The other icons are + // horizontally symmetrical. +} + +FrameCaptionButton::~FrameCaptionButton() = default; + +// static +SkColor FrameCaptionButton::GetButtonColor(SkColor background_color) { + // Use IsDark() to change target colors instead of PickContrastingColor(), so + // that DefaultFrameHeader::GetTitleColor() (which uses different target + // colors) can change between light/dark targets at the same time. It looks + // bad when the title and caption buttons disagree about whether to be light + // or dark. + const SkColor source = color_utils::IsDark(background_color) + ? gfx::kGoogleGrey200 + : gfx::kGoogleGrey700; + const SkColor target = color_utils::GetColorWithMaxContrast(background_color); + // Guarantee the caption buttons reach at least contrast ratio 3; this ratio + // matches that used for focus indicators, large text, and other "have to see + // it but perhaps don't have to read fine detail" cases. + const SkAlpha alpha = color_utils::GetBlendValueWithMinimumContrast( + source, target, background_color, 3.0f); + return color_utils::AlphaBlend(target, source, alpha); +} + +// static +float FrameCaptionButton::GetInactiveButtonColorAlphaRatio() { + return 0.38f; +} + +void FrameCaptionButton::SetImage(CaptionButtonIcon icon, + Animate animate, + const gfx::VectorIcon& icon_definition) { + gfx::ImageSkia new_icon_image = + gfx::CreateVectorIcon(icon_definition, GetButtonColor(background_color_)); + + // The early return is dependent on |animate| because callers use SetImage() + // with ANIMATE_NO to progress the crossfade animation to the end. + if (icon == icon_ && + (animate == ANIMATE_YES || !swap_images_animation_->is_animating()) && + new_icon_image.BackedBySameObjectAs(icon_image_)) { + return; + } + + if (animate == ANIMATE_YES) + crossfade_icon_image_ = icon_image_; + + icon_ = icon; + icon_definition_ = &icon_definition; + icon_image_ = new_icon_image; + + if (animate == ANIMATE_YES) { + swap_images_animation_->Reset(0); + swap_images_animation_->SetSlideDuration(kSwapImagesAnimationDurationMs); + swap_images_animation_->Show(); + } else { + swap_images_animation_->Reset(1); + } + + SchedulePaint(); +} + +bool FrameCaptionButton::IsAnimatingImageSwap() const { + return swap_images_animation_->is_animating(); +} + +void FrameCaptionButton::SetAlpha(int alpha) { + if (alpha_ != alpha) { + alpha_ = alpha; + SchedulePaint(); + } +} + +const char* FrameCaptionButton::GetClassName() const { + return kViewClassName; +} + +void FrameCaptionButton::OnGestureEvent(ui::GestureEvent* event) { + // Button 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(); + } + } + Button::OnGestureEvent(event); +} + +views::PaintInfo::ScaleType FrameCaptionButton::GetPaintScaleType() const { + return views::PaintInfo::ScaleType::kUniformScaling; +} + +std::unique_ptr<views::InkDrop> FrameCaptionButton::CreateInkDrop() { + auto ink_drop = std::make_unique<views::InkDropImpl>(this, size()); + ink_drop->SetAutoHighlightMode(views::InkDropImpl::AutoHighlightMode::NONE); + ink_drop->SetShowHighlightOnHover(false); + return ink_drop; +} + +std::unique_ptr<views::InkDropRipple> FrameCaptionButton::CreateInkDropRipple() + const { + return std::make_unique<views::FloodFillInkDropRipple>( + size(), GetInkdropInsets(size()), GetInkDropCenterBasedOnLastEvent(), + GetInkDropBaseColor(), ink_drop_visible_opacity()); +} + +std::unique_ptr<views::InkDropMask> FrameCaptionButton::CreateInkDropMask() + const { + return std::make_unique<views::RoundRectInkDropMask>( + size(), GetInkdropInsets(size()), ink_drop_corner_radius_); +} + +void FrameCaptionButton::SetBackgroundColor(SkColor background_color) { + if (background_color_ == background_color) + return; + + background_color_ = background_color; + // Refresh the icon since the color may have changed. + if (icon_definition_) + SetImage(icon_, ANIMATE_NO, *icon_definition_); + UpdateInkDropBaseColor(); +} + +void FrameCaptionButton::PaintButtonContents(gfx::Canvas* canvas) { + constexpr SkAlpha kHighlightVisibleOpacity = 0x14; + SkAlpha highlight_alpha = SK_AlphaTRANSPARENT; + if (hover_animation().is_animating()) { + highlight_alpha = hover_animation().CurrentValueBetween( + SK_AlphaTRANSPARENT, kHighlightVisibleOpacity); + } else if (state() == STATE_HOVERED || state() == STATE_PRESSED) { + // Painting a circular highlight in both "hovered" and "pressed" states + // simulates and ink drop highlight mode of + // AutoHighlightMode::SHOW_ON_RIPPLE. + highlight_alpha = kHighlightVisibleOpacity; + } + + if (highlight_alpha != SK_AlphaTRANSPARENT) { + // We paint the highlight manually here rather than relying on the ink drop + // highlight as it doesn't work well when the button size is changing while + // the window is moving as a result of the animation from normal to + // maximized state or vice versa. https://crbug.com/840901. + cc::PaintFlags flags; + flags.setColor(GetInkDropBaseColor()); + flags.setAlpha(highlight_alpha); + const gfx::Point center(GetMirroredRect(GetContentsBounds()).CenterPoint()); + canvas->DrawCircle(center, ink_drop_corner_radius_, flags); + } + + int icon_alpha = swap_images_animation_->CurrentValueBetween(0, 255); + int crossfade_icon_alpha = 0; + if (icon_alpha < static_cast<int>(kFadeOutRatio * 255)) + crossfade_icon_alpha = static_cast<int>(255 - icon_alpha / kFadeOutRatio); + + int centered_origin_x = (width() - icon_image_.width()) / 2; + int centered_origin_y = (height() - icon_image_.height()) / 2; + + if (crossfade_icon_alpha > 0 && !crossfade_icon_image_.isNull()) { + canvas->SaveLayerAlpha(GetAlphaForIcon(alpha_)); + cc::PaintFlags flags; + flags.setAlpha(icon_alpha); + canvas->DrawImageInt(icon_image_, centered_origin_x, centered_origin_y, + flags); + + flags.setAlpha(crossfade_icon_alpha); + flags.setBlendMode(SkBlendMode::kPlus); + canvas->DrawImageInt(crossfade_icon_image_, centered_origin_x, + centered_origin_y, flags); + canvas->Restore(); + } else { + if (!swap_images_animation_->is_animating()) + icon_alpha = alpha_; + cc::PaintFlags flags; + flags.setAlpha(GetAlphaForIcon(icon_alpha)); + canvas->DrawImageInt(icon_image_, centered_origin_x, centered_origin_y, + flags); + } +} + +int FrameCaptionButton::GetAlphaForIcon(int base_alpha) const { + if (!enabled()) + return base_alpha * kDisabledButtonAlphaRatio; + + if (paint_as_active_) + return base_alpha; + + // Paint icons as active when they are hovered over or pressed. + double inactive_alpha = GetInactiveButtonColorAlphaRatio(); + + if (hover_animation().is_animating()) { + inactive_alpha = + hover_animation().CurrentValueBetween(inactive_alpha, 1.0f); + } else if (state() == STATE_PRESSED || state() == STATE_HOVERED) { + inactive_alpha = 1.0f; + } + return base_alpha * inactive_alpha; +} + +gfx::Insets FrameCaptionButton::GetInkdropInsets( + const gfx::Size& button_size) const { + const gfx::Size kInkDropHighlightSize{2 * ink_drop_corner_radius_, + 2 * ink_drop_corner_radius_}; + return gfx::Insets( + (button_size.height() - kInkDropHighlightSize.height()) / 2, + (button_size.width() - kInkDropHighlightSize.width()) / 2); +} + +void FrameCaptionButton::UpdateInkDropBaseColor() { + using color_utils::GetColorWithMaxContrast; + // A typical implementation would simply do + // GetColorWithMaxContrast(background_color_). However, this could look odd + // if we use a light button glyph and dark ink drop or vice versa. So + // instead, use the lightest/darkest color in the same direction as the button + // glyph color. + // TODO(pkasting): It would likely be better to make the button glyph always + // be an alpha-blended version of GetColorWithMaxContrast(background_color_). + const SkColor button_color = GetButtonColor(background_color_); + set_ink_drop_base_color( + GetColorWithMaxContrast(GetColorWithMaxContrast(button_color))); +} + +} // namespace views |