1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045 |
- // 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/shelf/shelf_app_button.h"
- #include <algorithm>
- #include <memory>
- #include "ash/public/cpp/shelf_config.h"
- #include "ash/public/cpp/shelf_model.h"
- #include "ash/shelf/scrollable_shelf_view.h"
- #include "ash/shelf/shelf.h"
- #include "ash/shelf/shelf_button_delegate.h"
- #include "ash/shelf/shelf_view.h"
- #include "ash/strings/grit/ash_strings.h"
- #include "ash/style/default_color_constants.h"
- #include "ash/style/default_colors.h"
- #include "ash/style/dot_indicator.h"
- #include "ash/wm/tablet_mode/tablet_mode_controller.h"
- #include "base/bind.h"
- #include "base/i18n/rtl.h"
- #include "base/metrics/histogram_macros.h"
- #include "base/time/time.h"
- #include "skia/ext/image_operations.h"
- #include "ui/accessibility/ax_action_data.h"
- #include "ui/accessibility/ax_node_data.h"
- #include "ui/base/metadata/metadata_header_macros.h"
- #include "ui/base/metadata/metadata_impl_macros.h"
- #include "ui/base/resource/resource_bundle.h"
- #include "ui/color/color_id.h"
- #include "ui/compositor/layer.h"
- #include "ui/compositor/scoped_layer_animation_settings.h"
- #include "ui/gfx/animation/animation_delegate.h"
- #include "ui/gfx/animation/throb_animation.h"
- #include "ui/gfx/canvas.h"
- #include "ui/gfx/color_analysis.h"
- #include "ui/gfx/geometry/transform_util.h"
- #include "ui/gfx/geometry/vector2d.h"
- #include "ui/gfx/image/image_skia_operations.h"
- #include "ui/gfx/scoped_canvas.h"
- #include "ui/gfx/skbitmap_operations.h"
- #include "ui/views/accessibility/accessibility_paint_checks.h"
- #include "ui/views/animation/ink_drop.h"
- #include "ui/views/animation/ink_drop_impl.h"
- #include "ui/views/animation/square_ink_drop_ripple.h"
- #include "ui/views/controls/focus_ring.h"
- #include "ui/views/controls/highlight_path_generator.h"
- #include "ui/views/controls/image_view.h"
- #include "ui/views/painter.h"
- namespace {
- constexpr int kStatusIndicatorRadiusDip = 2;
- constexpr int kStatusIndicatorMaxSize = 10;
- constexpr int kStatusIndicatorActiveSize = 8;
- constexpr int kStatusIndicatorRunningSize = 4;
- constexpr int kStatusIndicatorThickness = 2;
- // The size of the notification indicator circle over the size of the icon.
- constexpr float kNotificationIndicatorWidthRatio = 14.0f / 64.0f;
- // The size of the notification indicator circle padding over the size of the
- // icon.
- constexpr float kNotificationIndicatorPaddingRatio = 5.0f / 64.0f;
- constexpr SkColor kDefaultIndicatorColor = SK_ColorWHITE;
- constexpr SkAlpha kInactiveIndicatorOpacity = 0x80;
- // The time threshold before an item can be dragged.
- constexpr int kDragTimeThresholdMs = 300;
- // The time threshold before the ink drop should activate on a long press.
- constexpr int kInkDropRippleActivationTimeMs = 650;
- // The drag and drop app icon should get scaled by this factor.
- constexpr float kAppIconScale = 1.2f;
- // The drag and drop app icon scaling up or down animation transition duration.
- constexpr int kDragDropAppIconScaleTransitionMs = 200;
- // Simple AnimationDelegate that owns a single ThrobAnimation instance to
- // keep all Draw Attention animations in sync.
- class ShelfAppButtonAnimation : public gfx::AnimationDelegate {
- public:
- class Observer {
- public:
- virtual void AnimationProgressed() = 0;
- protected:
- virtual ~Observer() = default;
- };
- ShelfAppButtonAnimation(const ShelfAppButtonAnimation&) = delete;
- ShelfAppButtonAnimation& operator=(const ShelfAppButtonAnimation&) = delete;
- static ShelfAppButtonAnimation* GetInstance() {
- static ShelfAppButtonAnimation* s_instance = new ShelfAppButtonAnimation();
- return s_instance;
- }
- void AddObserver(Observer* observer) { observers_.AddObserver(observer); }
- void RemoveObserver(Observer* observer) {
- observers_.RemoveObserver(observer);
- if (observers_.empty())
- animation_.Stop();
- }
- bool HasObserver(Observer* observer) const {
- return observers_.HasObserver(observer);
- }
- SkAlpha GetAlpha() {
- return GetThrobAnimation().CurrentValueBetween(SK_AlphaTRANSPARENT,
- SK_AlphaOPAQUE);
- }
- double GetAnimation() { return GetThrobAnimation().GetCurrentValue(); }
- private:
- ShelfAppButtonAnimation() : animation_(this) {
- animation_.SetThrobDuration(base::Milliseconds(800));
- animation_.SetTweenType(gfx::Tween::SMOOTH_IN_OUT);
- }
- ~ShelfAppButtonAnimation() override = default;
- gfx::ThrobAnimation& GetThrobAnimation() {
- if (!animation_.is_animating()) {
- animation_.Reset();
- animation_.StartThrobbing(-1 /*throb indefinitely*/);
- }
- return animation_;
- }
- // gfx::AnimationDelegate
- void AnimationProgressed(const gfx::Animation* animation) override {
- if (animation != &animation_)
- return;
- if (!animation_.is_animating())
- return;
- for (auto& observer : observers_)
- observer.AnimationProgressed();
- }
- gfx::ThrobAnimation animation_;
- base::ObserverList<Observer>::Unchecked observers_;
- };
- } // namespace
- namespace ash {
- ////////////////////////////////////////////////////////////////////////////////
- // ShelfAppButton::AppStatusIndicatorView
- class ShelfAppButton::AppStatusIndicatorView
- : public gfx::AnimationDelegate,
- public views::View,
- public ShelfAppButtonAnimation::Observer {
- public:
- METADATA_HEADER(AppStatusIndicatorView);
- AppStatusIndicatorView() {
- // Make sure the events reach the parent view for handling.
- SetCanProcessEventsWithinSubtree(false);
- status_change_animation_ = std::make_unique<gfx::SlideAnimation>(this);
- status_change_animation_->SetSlideDuration(base::Milliseconds(250));
- status_change_animation_->SetTweenType(gfx::Tween::FAST_OUT_SLOW_IN);
- }
- AppStatusIndicatorView(const AppStatusIndicatorView&) = delete;
- AppStatusIndicatorView& operator=(const AppStatusIndicatorView&) = delete;
- ~AppStatusIndicatorView() override {
- ShelfAppButtonAnimation::GetInstance()->RemoveObserver(this);
- }
- // views::View:
- void OnThemeChanged() override {
- views::View::OnThemeChanged();
- SchedulePaint();
- }
- void OnPaint(gfx::Canvas* canvas) override {
- gfx::ScopedCanvas scoped(canvas);
- canvas->SaveLayerAlpha(GetAlpha());
- const float dsf = canvas->UndoDeviceScaleFactor();
- gfx::PointF center = gfx::RectF(GetLocalBounds()).CenterPoint();
- cc::PaintFlags flags;
- // Active and running indicators look a little different in the new UI.
- flags.setColor(AshColorProvider::Get()->GetContentLayerColor(
- AshColorProvider::ContentLayerType::kAppStateIndicatorColor));
- flags.setAntiAlias(true);
- flags.setStrokeCap(cc::PaintFlags::Cap::kRound_Cap);
- flags.setStrokeJoin(cc::PaintFlags::Join::kRound_Join);
- flags.setStrokeWidth(kStatusIndicatorThickness);
- flags.setStyle(cc::PaintFlags::kStroke_Style);
- float stroke_length = GetStrokeLength();
- gfx::PointF start;
- gfx::PointF end;
- if (horizontal_shelf_) {
- start = gfx::PointF(center.x() - stroke_length / 2, center.y());
- end = start;
- end.Offset(stroke_length, 0);
- } else {
- start = gfx::PointF(center.x(), center.y() - stroke_length / 2);
- end = start;
- end.Offset(0, stroke_length);
- }
- SkPath path;
- path.moveTo(start.x() * dsf, start.y() * dsf);
- path.lineTo(end.x() * dsf, end.y() * dsf);
- canvas->DrawPath(path, flags);
- }
- float GetStrokeLength() {
- if (status_change_animation_->is_animating()) {
- return status_change_animation_->CurrentValueBetween(
- kStatusIndicatorRunningSize, kStatusIndicatorActiveSize);
- }
- return active_ ? kStatusIndicatorActiveSize : kStatusIndicatorRunningSize;
- }
- SkAlpha GetAlpha() {
- if (show_attention_) {
- return ShelfAppButtonAnimation::GetInstance()->HasObserver(this)
- ? ShelfAppButtonAnimation::GetInstance()->GetAlpha()
- : SK_AlphaOPAQUE;
- }
- if (status_change_animation_->is_animating()) {
- return status_change_animation_->CurrentValueBetween(
- kInactiveIndicatorOpacity, SK_AlphaOPAQUE);
- }
- return active_ ? SK_AlphaOPAQUE : kInactiveIndicatorOpacity;
- }
- // ShelfAppButtonAnimation::Observer
- void AnimationProgressed() override {
- UpdateAnimating();
- SchedulePaint();
- }
- void ShowAttention(bool show) {
- if (show_attention_ == show)
- return;
- show_attention_ = show;
- if (status_change_animation_->is_animating())
- status_change_animation_->End();
- if (show_attention_) {
- animation_end_time_ = base::TimeTicks::Now() + base::Seconds(10);
- ShelfAppButtonAnimation::GetInstance()->AddObserver(this);
- } else {
- ShelfAppButtonAnimation::GetInstance()->RemoveObserver(this);
- }
- }
- // gfx::AnimationDelegate
- void AnimationProgressed(const gfx::Animation* animation) override {
- if (animation != status_change_animation_.get())
- return;
- SchedulePaint();
- }
- void ShowActiveStatus(bool active) {
- if (active_ == active)
- return;
- active_ = active;
- if (active_)
- status_change_animation_->Show();
- else
- status_change_animation_->Hide();
- }
- void SetHorizontalShelf(bool horizontal_shelf) {
- if (horizontal_shelf_ == horizontal_shelf)
- return;
- horizontal_shelf_ = horizontal_shelf;
- SchedulePaint();
- }
- private:
- void UpdateAnimating() {
- if (base::TimeTicks::Now() > animation_end_time_)
- ShelfAppButtonAnimation::GetInstance()->RemoveObserver(this);
- }
- bool show_attention_ = false;
- bool active_ = false;
- bool horizontal_shelf_ = true;
- std::unique_ptr<gfx::SlideAnimation> status_change_animation_;
- base::TimeTicks animation_end_time_; // For attention throbbing underline.
- };
- BEGIN_METADATA(ShelfAppButton, AppStatusIndicatorView, views::View)
- END_METADATA
- ////////////////////////////////////////////////////////////////////////////////
- // ShelfAppButton
- // static
- const char ShelfAppButton::kViewClassName[] = "ash/ShelfAppButton";
- // static
- bool ShelfAppButton::ShouldHandleEventFromContextMenu(
- const ui::GestureEvent* event) {
- switch (event->type()) {
- case ui::ET_GESTURE_END:
- case ui::ET_GESTURE_TAP_CANCEL:
- case ui::ET_GESTURE_SCROLL_BEGIN:
- case ui::ET_GESTURE_SCROLL_UPDATE:
- case ui::ET_GESTURE_SCROLL_END:
- case ui::ET_SCROLL_FLING_START:
- return true;
- default:
- return false;
- }
- }
- ShelfAppButton::ShelfAppButton(ShelfView* shelf_view,
- ShelfButtonDelegate* shelf_button_delegate)
- : ShelfButton(shelf_view->shelf(), shelf_button_delegate),
- icon_view_(new views::ImageView()),
- shelf_view_(shelf_view),
- indicator_(new AppStatusIndicatorView()) {
- const gfx::ShadowValue kShadows[] = {
- gfx::ShadowValue(gfx::Vector2d(0, 2), 0, SkColorSetARGB(0x1A, 0, 0, 0)),
- gfx::ShadowValue(gfx::Vector2d(0, 3), 1, SkColorSetARGB(0x1A, 0, 0, 0)),
- gfx::ShadowValue(gfx::Vector2d(0, 0), 1, SkColorSetARGB(0x54, 0, 0, 0)),
- };
- icon_shadows_.assign(kShadows, kShadows + std::size(kShadows));
- // TODO(crbug.com/1218186): Remove this, this is in place temporarily to be
- // able to submit accessibility checks. This crashes if fetching a11y node
- // data during paint because message_view_ is null.
- SetProperty(views::kSkipAccessibilityPaintChecks, true);
- views::InkDrop::Get(this)->SetCreateRippleCallback(base::BindRepeating(
- [](ShelfAppButton* host) -> std::unique_ptr<views::InkDropRipple> {
- const gfx::Rect small_ripple_area = host->CalculateSmallRippleArea();
- const int ripple_size = host->shelf_view_->GetShelfItemRippleSize();
- return std::make_unique<views::SquareInkDropRipple>(
- gfx::Size(ripple_size, ripple_size),
- views::InkDrop::Get(host)->GetLargeCornerRadius(),
- small_ripple_area.size(),
- views::InkDrop::Get(host)->GetSmallCornerRadius(),
- small_ripple_area.CenterPoint(),
- views::InkDrop::Get(host)->GetBaseColor(),
- views::InkDrop::Get(host)->GetVisibleOpacity());
- },
- this));
- // TODO: refactor the layers so each button doesn't require 3.
- // |icon_view_| needs its own layer so it can be scaled up independently of
- // the ink drop ripple.
- icon_view_->SetPaintToLayer();
- icon_view_->layer()->SetFillsBoundsOpaquely(false);
- icon_view_->SetHorizontalAlignment(views::ImageView::Alignment::kCenter);
- icon_view_->SetVerticalAlignment(views::ImageView::Alignment::kLeading);
- // Do not make this interactive, so that events are sent to ShelfView.
- icon_view_->SetCanProcessEventsWithinSubtree(false);
- indicator_->SetPaintToLayer();
- indicator_->layer()->SetFillsBoundsOpaquely(false);
- AddChildView(indicator_);
- AddChildView(icon_view_);
- notification_indicator_ =
- AddChildView(std::make_unique<DotIndicator>(kDefaultIndicatorColor));
- views::InkDrop::Get(this)->GetInkDrop()->AddObserver(this);
- // Do not set a clip, allow the ink drop to burst out.
- views::InstallEmptyHighlightPathGenerator(this);
- SetFocusBehavior(FocusBehavior::ALWAYS);
- SetInstallFocusRingOnFocus(true);
- views::FocusRing::Get(this)->SetColorId(ui::kColorAshFocusRing);
- // The focus ring should have an inset of half the focus border thickness, so
- // the parent view won't clip it.
- views::FocusRing::Get(this)->SetPathGenerator(
- std::make_unique<views::RoundRectHighlightPathGenerator>(
- gfx::Insets::VH(views::FocusRing::kDefaultHaloThickness / 2, 0), 0));
- }
- ShelfAppButton::~ShelfAppButton() {
- views::InkDrop::Get(this)->GetInkDrop()->RemoveObserver(this);
- }
- void ShelfAppButton::SetShadowedImage(const gfx::ImageSkia& image) {
- icon_view_->SetImage(gfx::ImageSkiaOperations::CreateImageWithDropShadow(
- image, icon_shadows_));
- }
- void ShelfAppButton::SetImage(const gfx::ImageSkia& image) {
- if (image.isNull()) {
- // TODO: need an empty image.
- icon_view_->SetImage(image);
- icon_image_ = gfx::ImageSkia();
- return;
- }
- icon_image_ = image;
- gfx::Size preferred_size = GetPreferredIconSize();
- if (image.size() == preferred_size) {
- SetShadowedImage(image);
- return;
- }
- SetShadowedImage(gfx::ImageSkiaOperations::CreateResizedImage(
- image, skia::ImageOperations::RESIZE_BEST, preferred_size));
- }
- gfx::ImageSkia ShelfAppButton::GetImage() const {
- return icon_view_->GetImage();
- }
- gfx::ImageSkia ShelfAppButton::GetIconImage() const {
- const gfx::Size preferred_size = GetPreferredSize();
- if (icon_image_.size() == preferred_size)
- return icon_image_;
- return gfx::ImageSkiaOperations::CreateResizedImage(
- icon_image_, skia::ImageOperations::RESIZE_BEST, GetPreferredIconSize());
- }
- void ShelfAppButton::AddState(State state) {
- if (!(state_ & state)) {
- state_ |= state;
- InvalidateLayout();
- if (state & STATE_ATTENTION)
- indicator_->ShowAttention(true);
- if (state & STATE_ACTIVE)
- indicator_->ShowActiveStatus(true);
- if (state & STATE_NOTIFICATION)
- notification_indicator_->SetVisible(true);
- if (state & STATE_DRAGGING)
- ScaleAppIcon(true);
- }
- }
- void ShelfAppButton::ClearState(State state) {
- if (state_ & state) {
- state_ &= ~state;
- Layout();
- if (state & STATE_ATTENTION)
- indicator_->ShowAttention(false);
- if (state & STATE_ACTIVE)
- indicator_->ShowActiveStatus(false);
- if (state & STATE_NOTIFICATION)
- notification_indicator_->SetVisible(false);
- if (state & STATE_DRAGGING)
- ScaleAppIcon(false);
- }
- }
- void ShelfAppButton::ClearDragStateOnGestureEnd() {
- drag_timer_.Stop();
- ClearState(STATE_HOVERED);
- ClearState(STATE_DRAGGING);
- }
- gfx::Rect ShelfAppButton::GetIconBounds() const {
- return icon_view_->bounds();
- }
- gfx::Rect ShelfAppButton::GetIdealIconBounds(const gfx::Size& button_size,
- float icon_scale) const {
- return GetIconViewBounds(gfx::Rect(button_size), icon_scale);
- }
- views::InkDrop* ShelfAppButton::GetInkDropForTesting() {
- return views::InkDrop::Get(this)->GetInkDrop();
- }
- void ShelfAppButton::OnDragStarted(const ui::LocatedEvent* event) {
- views::InkDrop::Get(this)->AnimateToState(views::InkDropState::HIDDEN, event);
- }
- void ShelfAppButton::OnMenuClosed() {
- DCHECK_EQ(views::InkDropState::ACTIVATED,
- views::InkDrop::Get(this)->GetInkDrop()->GetTargetInkDropState());
- views::InkDrop::Get(this)->GetInkDrop()->AnimateToState(
- views::InkDropState::DEACTIVATED);
- context_menu_target_visibility_ = false;
- }
- void ShelfAppButton::ShowContextMenu(const gfx::Point& p,
- ui::MenuSourceType source_type) {
- // Return early if:
- // 1. the context menu controller is not set; or
- // 2. `context_menu_target_visibility_` is already true.
- if (!context_menu_controller() || context_menu_target_visibility_)
- return;
- context_menu_target_visibility_ = true;
- auto weak_this = weak_factory_.GetWeakPtr();
- ShelfButton::ShowContextMenu(p, source_type);
- // This object may have been destroyed by ShowContextMenu.
- if (weak_this) {
- // The menu will not propagate mouse events while it's shown. To address,
- // the hover state gets cleared once the menu was shown (and this was not
- // destroyed). In case context menu is shown target view does not receive
- // OnMouseReleased events and we need to cancel capture manually.
- if (shelf_view_->IsDraggedView(this))
- OnMouseCaptureLost();
- else
- ClearState(STATE_HOVERED);
- }
- }
- void ShelfAppButton::GetAccessibleNodeData(ui::AXNodeData* node_data) {
- ShelfButton::GetAccessibleNodeData(node_data);
- const std::u16string title = shelf_view_->GetTitleForView(this);
- node_data->SetName(title.empty() ? GetAccessibleName() : title);
- switch (app_status_) {
- case AppStatus::kBlocked:
- node_data->SetDescription(
- ui::ResourceBundle::GetSharedInstance().GetLocalizedString(
- IDS_SHELF_ITEM_BLOCKED_APP));
- break;
- case AppStatus::kPaused:
- node_data->SetDescription(
- ui::ResourceBundle::GetSharedInstance().GetLocalizedString(
- IDS_SHELF_ITEM_PAUSED_APP));
- break;
- default:
- break;
- }
- }
- bool ShelfAppButton::ShouldEnterPushedState(const ui::Event& event) {
- if (!shelf_view_->ShouldEventActivateButton(this, event))
- return false;
- return Button::ShouldEnterPushedState(event);
- }
- void ShelfAppButton::ReflectItemStatus(const ShelfItem& item) {
- if (item.has_notification)
- AddState(ShelfAppButton::STATE_NOTIFICATION);
- else
- ClearState(ShelfAppButton::STATE_NOTIFICATION);
- app_status_ = item.app_status;
- const ShelfID active_id = shelf_view_->model()->active_shelf_id();
- if (!active_id.IsNull() && item.id == active_id) {
- // The active status trumps all other statuses.
- AddState(ShelfAppButton::STATE_ACTIVE);
- ClearState(ShelfAppButton::STATE_RUNNING);
- ClearState(ShelfAppButton::STATE_ATTENTION);
- // Notify the parent scrollable shelf view to show the current active app.
- shelf_button_delegate()->OnAppButtonActivated(this);
- return;
- }
- ClearState(ShelfAppButton::STATE_ACTIVE);
- switch (item.status) {
- case STATUS_CLOSED:
- ClearState(ShelfAppButton::STATE_RUNNING);
- ClearState(ShelfAppButton::STATE_ATTENTION);
- break;
- case STATUS_RUNNING:
- AddState(ShelfAppButton::STATE_RUNNING);
- ClearState(ShelfAppButton::STATE_ATTENTION);
- break;
- case STATUS_ATTENTION:
- ClearState(ShelfAppButton::STATE_RUNNING);
- AddState(ShelfAppButton::STATE_ATTENTION);
- break;
- }
- }
- bool ShelfAppButton::IsIconSizeCurrent() {
- gfx::Insets insets_shadows = gfx::ShadowValue::GetMargin(icon_shadows_);
- int icon_width =
- GetIconBounds().width() + insets_shadows.left() + insets_shadows.right();
- return icon_width == shelf_view_->GetButtonIconSize();
- }
- void ShelfAppButton::OnContextMenuModelRequestCanceled() {
- // The request for the context menu model gets canceled so reset the context
- // menu target visibility.
- context_menu_target_visibility_ = false;
- }
- bool ShelfAppButton::FireDragTimerForTest() {
- if (!drag_timer_.IsRunning())
- return false;
- drag_timer_.FireNow();
- return true;
- }
- void ShelfAppButton::FireRippleActivationTimerForTest() {
- ripple_activation_timer_.FireNow();
- }
- gfx::Rect ShelfAppButton::CalculateSmallRippleArea() const {
- int ink_drop_small_size = shelf_view_->GetButtonSize();
- gfx::Point center_point = GetLocalBounds().CenterPoint();
- const int padding = ShelfConfig::Get()->GetAppIconEndPadding();
- // Add padding to the ink drop for the left-most and right-most app buttons in
- // the shelf when there is a non-zero padding between the app icon and the
- // end of scrollable shelf.
- if (TabletModeController::Get()->InTabletMode() && padding > 0) {
- const size_t current_index =
- shelf_view_->view_model()->GetIndexOfView(this).value();
- int left_padding =
- (shelf_view_->visible_views_indices().front() == current_index)
- ? padding
- : 0;
- int right_padding =
- (shelf_view_->visible_views_indices().back() == current_index) ? padding
- : 0;
- if (base::i18n::IsRTL())
- std::swap(left_padding, right_padding);
- ink_drop_small_size += left_padding + right_padding;
- const int x_offset = (-left_padding / 2) + (right_padding / 2);
- center_point.Offset(x_offset, 0);
- }
- gfx::Rect small_ripple_area(
- gfx::Size(ink_drop_small_size, ink_drop_small_size));
- small_ripple_area.Offset(center_point.x() - ink_drop_small_size / 2,
- center_point.y() - ink_drop_small_size / 2);
- return small_ripple_area;
- }
- const char* ShelfAppButton::GetClassName() const {
- return kViewClassName;
- }
- bool ShelfAppButton::OnMousePressed(const ui::MouseEvent& event) {
- // TODO: This call should probably live somewhere else (such as inside
- // |ShelfView.PointerPressedOnButton|.
- // No need to scale up the app for mouse right click since the app can't be
- // dragged through right button.
- if (!(event.flags() & ui::EF_LEFT_MOUSE_BUTTON)) {
- Button::OnMousePressed(event);
- return true;
- }
- ShelfButton::OnMousePressed(event);
- shelf_view_->PointerPressedOnButton(this, ShelfView::MOUSE, event);
- if (shelf_view_->IsDraggedView(this)) {
- drag_timer_.Start(FROM_HERE, base::Milliseconds(kDragTimeThresholdMs),
- base::BindOnce(&ShelfAppButton::OnTouchDragTimer,
- base::Unretained(this)));
- }
- return true;
- }
- void ShelfAppButton::OnMouseReleased(const ui::MouseEvent& event) {
- drag_timer_.Stop();
- ClearState(STATE_DRAGGING);
- ShelfButton::OnMouseReleased(event);
- // PointerReleasedOnButton deletes the ShelfAppButton when user drags a pinned
- // running app from shelf.
- shelf_view_->PointerReleasedOnButton(this, ShelfView::MOUSE, false);
- // WARNING: we may have been deleted.
- }
- void ShelfAppButton::OnMouseCaptureLost() {
- ClearState(STATE_HOVERED);
- shelf_view_->PointerReleasedOnButton(this, ShelfView::MOUSE, true);
- ShelfButton::OnMouseCaptureLost();
- }
- bool ShelfAppButton::OnMouseDragged(const ui::MouseEvent& event) {
- ShelfButton::OnMouseDragged(event);
- shelf_view_->PointerDraggedOnButton(this, ShelfView::MOUSE, event);
- return true;
- }
- gfx::Rect ShelfAppButton::GetIconViewBounds(const gfx::Rect& button_bounds,
- float icon_scale) const {
- const float icon_size = shelf_view_->GetButtonIconSize() * icon_scale;
- const float icon_padding = (shelf_view_->GetButtonSize() - icon_size) / 2;
- const Shelf* shelf = shelf_view_->shelf();
- const bool is_horizontal_shelf = shelf->IsHorizontalAlignment();
- float x_offset = is_horizontal_shelf ? 0 : icon_padding;
- float y_offset = is_horizontal_shelf ? icon_padding : 0;
- const float icon_width =
- std::min(icon_size, button_bounds.width() - x_offset);
- const float icon_height =
- std::min(icon_size, button_bounds.height() - y_offset);
- // If on the left or top 'invert' the inset so the constant gap is on
- // the interior (towards the center of display) edge of the shelf.
- if (ShelfAlignment::kLeft == shelf->alignment())
- x_offset = button_bounds.width() - (icon_size + icon_padding);
- // Expand bounds to include shadows.
- gfx::Insets insets_shadows = gfx::ShadowValue::GetMargin(icon_shadows_);
- // Center icon with respect to the secondary axis.
- if (is_horizontal_shelf)
- x_offset = std::max(0.0f, button_bounds.width() - icon_width + 1) / 2;
- else
- y_offset = std::max(0.0f, button_bounds.height() - icon_height) / 2;
- gfx::RectF icon_view_bounds =
- gfx::RectF(button_bounds.x() + x_offset, button_bounds.y() + y_offset,
- icon_width, icon_height);
- icon_view_bounds.Inset(gfx::InsetsF(insets_shadows));
- // Icon size has been incorrect when running
- // PanelLayoutManagerTest.PanelAlignmentSecondDisplay on valgrind bot, see
- // http://crbug.com/234854.
- DCHECK_LE(icon_width, icon_size);
- DCHECK_LE(icon_height, icon_size);
- return gfx::ToRoundedRect(icon_view_bounds);
- }
- gfx::Rect ShelfAppButton::GetNotificationIndicatorBounds(float icon_scale) {
- gfx::Rect scaled_icon_view_bounds =
- GetIconViewBounds(GetContentsBounds(), icon_scale);
- float diameter =
- kNotificationIndicatorWidthRatio * scaled_icon_view_bounds.width();
- float padding =
- kNotificationIndicatorPaddingRatio * scaled_icon_view_bounds.width();
- return gfx::ToRoundedRect(
- gfx::RectF(scaled_icon_view_bounds.right() - diameter - padding,
- scaled_icon_view_bounds.y() + padding, diameter, diameter));
- }
- void ShelfAppButton::Layout() {
- Shelf* shelf = shelf_view_->shelf();
- gfx::Rect icon_view_bounds =
- GetIconViewBounds(GetContentsBounds(), icon_scale_);
- const gfx::Rect button_bounds(GetContentsBounds());
- const int status_indicator_offet_from_shelf_edge =
- ShelfConfig::Get()->status_indicator_offset_from_shelf_edge();
- icon_view_->SetBoundsRect(icon_view_bounds);
- notification_indicator_->SetIndicatorBounds(
- GetNotificationIndicatorBounds(icon_scale_));
- // The indicators should be aligned with the icon, not the icon + shadow.
- // Use 1.0 as icon scale for |indicator_midpoint|, otherwise integer rounding
- // can incorrectly move the midpoint.
- gfx::Point indicator_midpoint =
- GetIconViewBounds(GetContentsBounds(), 1.0).CenterPoint();
- switch (shelf->alignment()) {
- case ShelfAlignment::kBottom:
- case ShelfAlignment::kBottomLocked:
- indicator_midpoint.set_y(button_bounds.bottom() -
- kStatusIndicatorRadiusDip -
- status_indicator_offet_from_shelf_edge);
- break;
- case ShelfAlignment::kLeft:
- indicator_midpoint.set_x(button_bounds.x() + kStatusIndicatorRadiusDip +
- status_indicator_offet_from_shelf_edge);
- break;
- case ShelfAlignment::kRight:
- indicator_midpoint.set_x(button_bounds.right() -
- kStatusIndicatorRadiusDip -
- status_indicator_offet_from_shelf_edge);
- break;
- }
- gfx::Rect indicator_bounds(indicator_midpoint, gfx::Size());
- indicator_bounds.Inset(gfx::Insets(-kStatusIndicatorMaxSize));
- indicator_->SetBoundsRect(indicator_bounds);
- UpdateState();
- views::FocusRing::Get(this)->Layout();
- }
- void ShelfAppButton::ChildPreferredSizeChanged(views::View* child) {
- Layout();
- }
- void ShelfAppButton::OnGestureEvent(ui::GestureEvent* event) {
- switch (event->type()) {
- case ui::ET_GESTURE_TAP_DOWN:
- if (shelf_view_->shelf()->IsVisible()) {
- AddState(STATE_HOVERED);
- drag_timer_.Start(FROM_HERE, base::Milliseconds(kDragTimeThresholdMs),
- base::BindOnce(&ShelfAppButton::OnTouchDragTimer,
- base::Unretained(this)));
- ripple_activation_timer_.Start(
- FROM_HERE, base::Milliseconds(kInkDropRippleActivationTimeMs),
- base::BindOnce(&ShelfAppButton::OnRippleTimer,
- base::Unretained(this)));
- views::InkDrop::Get(this)->GetInkDrop()->AnimateToState(
- views::InkDropState::ACTION_PENDING);
- event->SetHandled();
- }
- break;
- case ui::ET_GESTURE_TAP:
- [[fallthrough]]; // Ensure tapped items are not enlarged for drag.
- case ui::ET_GESTURE_END:
- // If the button is being dragged, or there is an active context menu,
- // for this ShelfAppButton, don't deactivate the ink drop.
- if (!(state_ & STATE_DRAGGING) &&
- !shelf_view_->IsShowingMenuForView(this) &&
- (views::InkDrop::Get(this)->GetInkDrop()->GetTargetInkDropState() ==
- views::InkDropState::ACTIVATED)) {
- views::InkDrop::Get(this)->GetInkDrop()->AnimateToState(
- views::InkDropState::DEACTIVATED);
- } else if (event->type() == ui::ET_GESTURE_END) {
- // When the gesture ends, we may need to deactivate the button's
- // inkdrop. For example, when a mouse event interputs the gesture press
- // on a shelf app button, the button's inkdrop could be in the pending
- // state while the button's context menu is hidden. In this case, we
- // have to hide the inkdrop explicitly.
- MaybeHideInkDropWhenGestureEnds();
- }
- ClearDragStateOnGestureEnd();
- break;
- case ui::ET_GESTURE_SCROLL_BEGIN:
- if (state_ & STATE_DRAGGING) {
- shelf_view_->PointerPressedOnButton(this, ShelfView::TOUCH, *event);
- event->SetHandled();
- } else {
- // The drag went to the bezel and is about to be passed to
- // ShelfLayoutManager.
- drag_timer_.Stop();
- views::InkDrop::Get(this)->GetInkDrop()->AnimateToState(
- views::InkDropState::HIDDEN);
- }
- break;
- case ui::ET_GESTURE_SCROLL_UPDATE:
- if ((state_ & STATE_DRAGGING) && shelf_view_->IsDraggedView(this)) {
- shelf_view_->PointerDraggedOnButton(this, ShelfView::TOUCH, *event);
- event->SetHandled();
- }
- break;
- case ui::ET_GESTURE_SCROLL_END:
- case ui::ET_SCROLL_FLING_START:
- if (state_ & STATE_DRAGGING) {
- ClearState(STATE_DRAGGING);
- shelf_view_->PointerReleasedOnButton(this, ShelfView::TOUCH, false);
- event->SetHandled();
- }
- break;
- case ui::ET_GESTURE_LONG_TAP:
- views::InkDrop::Get(this)->GetInkDrop()->AnimateToState(
- views::InkDropState::ACTIVATED);
- // The context menu may not show (for example, a mouse click which occurs
- // before the end of gesture could close the context menu). In this case,
- // let the overridden function handles the event to show the context menu
- // (see https://crbug.com/1126491).
- if (shelf_view_->IsShowingMenu()) {
- // Handle LONG_TAP to avoid opening the context menu twice.
- event->SetHandled();
- }
- break;
- case ui::ET_GESTURE_TWO_FINGER_TAP:
- views::InkDrop::Get(this)->GetInkDrop()->AnimateToState(
- views::InkDropState::ACTIVATED);
- break;
- default:
- break;
- }
- if (!event->handled())
- return Button::OnGestureEvent(event);
- }
- bool ShelfAppButton::HandleAccessibleAction(
- const ui::AXActionData& action_data) {
- if (notification_indicator_ && notification_indicator_->GetVisible())
- shelf_view_->AnnounceShelfItemNotificationBadge(this);
- if (action_data.action == ax::mojom::Action::kScrollToMakeVisible)
- shelf_button_delegate()->HandleAccessibleActionScrollToMakeVisible(this);
- return views::View::HandleAccessibleAction(action_data);
- }
- void ShelfAppButton::InkDropAnimationStarted() {
- SetInkDropAnimationStarted(/*started=*/true);
- }
- void ShelfAppButton::InkDropRippleAnimationEnded(views::InkDropState state) {
- // Notify the host view of the ink drop to be hidden at the end of ink drop
- // animation.
- if (state == views::InkDropState::HIDDEN)
- SetInkDropAnimationStarted(/*started=*/false);
- }
- void ShelfAppButton::UpdateState() {
- indicator_->SetVisible(!(state_ & STATE_HIDDEN) &&
- (state_ & STATE_ATTENTION || state_ & STATE_RUNNING ||
- state_ & STATE_ACTIVE));
- const bool is_horizontal_shelf =
- shelf_view_->shelf()->IsHorizontalAlignment();
- indicator_->SetHorizontalShelf(is_horizontal_shelf);
- icon_view_->SetHorizontalAlignment(
- is_horizontal_shelf ? views::ImageView::Alignment::kCenter
- : views::ImageView::Alignment::kLeading);
- icon_view_->SetVerticalAlignment(is_horizontal_shelf
- ? views::ImageView::Alignment::kLeading
- : views::ImageView::Alignment::kCenter);
- SchedulePaint();
- }
- void ShelfAppButton::OnTouchDragTimer() {
- AddState(STATE_DRAGGING);
- }
- void ShelfAppButton::OnRippleTimer() {
- if (views::InkDrop::Get(this)->GetInkDrop()->GetTargetInkDropState() !=
- views::InkDropState::ACTION_PENDING) {
- return;
- }
- views::InkDrop::Get(this)->GetInkDrop()->AnimateToState(
- views::InkDropState::ACTIVATED);
- }
- gfx::Transform ShelfAppButton::GetScaleTransform(float icon_scale) {
- gfx::RectF pre_scaling_bounds(
- GetMirroredRect(GetIconViewBounds(GetContentsBounds(), 1.0f)));
- gfx::RectF target_bounds(
- GetMirroredRect(GetIconViewBounds(GetContentsBounds(), icon_scale)));
- return gfx::TransformBetweenRects(target_bounds, pre_scaling_bounds);
- }
- gfx::Size ShelfAppButton::GetPreferredIconSize() const {
- const int icon_size = shelf_view_->GetButtonIconSize() * icon_scale_;
- // Resize the image maintaining our aspect ratio.
- float aspect_ratio = static_cast<float>(icon_image_.width()) /
- static_cast<float>(icon_image_.height());
- int height = icon_size;
- int width = static_cast<int>(aspect_ratio * height);
- if (width > icon_size) {
- width = icon_size;
- height = static_cast<int>(width / aspect_ratio);
- }
- return gfx::Size(width, height);
- }
- void ShelfAppButton::ScaleAppIcon(bool scale_up) {
- StopObservingImplicitAnimations();
- if (scale_up) {
- icon_scale_ = kAppIconScale;
- SetImage(icon_image_);
- icon_view_->layer()->SetTransform(GetScaleTransform(kAppIconScale));
- }
- ui::ScopedLayerAnimationSettings settings(icon_view_->layer()->GetAnimator());
- settings.SetTransitionDuration(
- base::Milliseconds(kDragDropAppIconScaleTransitionMs));
- if (scale_up) {
- icon_view_->layer()->SetTransform(gfx::Transform());
- } else {
- // To avoid poor quality icons, update icon image with the correct scale
- // after the transform animation is completed.
- settings.AddObserver(this);
- icon_view_->layer()->SetTransform(GetScaleTransform(kAppIconScale));
- }
- // Animate the notification indicator alongside the |icon_view_|.
- if (notification_indicator_) {
- gfx::RectF pre_scale(GetMirroredRect(GetNotificationIndicatorBounds(1.0)));
- gfx::RectF post_scale(
- GetMirroredRect(GetNotificationIndicatorBounds(kAppIconScale)));
- gfx::Transform scale_transform =
- gfx::TransformBetweenRects(post_scale, pre_scale);
- if (scale_up)
- notification_indicator_->layer()->SetTransform(scale_transform);
- ui::ScopedLayerAnimationSettings notification_settings(
- notification_indicator_->layer()->GetAnimator());
- notification_settings.SetTransitionDuration(
- base::Milliseconds(kDragDropAppIconScaleTransitionMs));
- notification_indicator_->layer()->SetTransform(scale_up ? gfx::Transform()
- : scale_transform);
- }
- }
- void ShelfAppButton::OnImplicitAnimationsCompleted() {
- icon_scale_ = 1.0f;
- SetImage(icon_image_);
- icon_view_->layer()->SetTransform(gfx::Transform());
- if (notification_indicator_)
- notification_indicator_->layer()->SetTransform(gfx::Transform());
- }
- void ShelfAppButton::SetInkDropAnimationStarted(bool started) {
- if (ink_drop_animation_started_ == started)
- return;
- ink_drop_animation_started_ = started;
- if (started) {
- ink_drop_count_ = shelf_button_delegate()->CreateScopedActiveInkDropCount(
- /*sender=*/this);
- } else {
- ink_drop_count_.reset(nullptr);
- }
- }
- void ShelfAppButton::SetNotificationBadgeColor(SkColor color) {
- if (notification_indicator_)
- notification_indicator_->SetColor(color);
- }
- void ShelfAppButton::MaybeHideInkDropWhenGestureEnds() {
- if (context_menu_target_visibility_ ||
- views::InkDrop::Get(this)->GetInkDrop()->GetTargetInkDropState() ==
- views::InkDropState::HIDDEN) {
- // Return early if the shelf app button's context menu should show or
- // the button's inkdrop has already been hidden.
- return;
- }
- views::InkDrop::Get(this)->GetInkDrop()->AnimateToState(
- views::InkDropState::HIDDEN);
- }
- } // namespace ash
|