1
    2
    3
    4
    5
    6
    7
    8
    9
   10
   11
   12
   13
   14
   15
   16
   17
   18
   19
   20
   21
   22
   23
   24
   25
   26
   27
   28
   29
   30
   31
   32
   33
   34
   35
   36
   37
   38
   39
   40
   41
   42
   43
   44
   45
   46
   47
   48
   49
   50
   51
   52
   53
   54
   55
   56
   57
   58
   59
   60
   61
   62
   63
   64
   65
   66
   67
   68
   69
   70
   71
   72
   73
   74
   75
   76
   77
   78
   79
   80
   81
   82
   83
   84
   85
   86
   87
   88
   89
   90
   91
   92
   93
   94
   95
   96
   97
   98
   99
  100
  101
  102
  103
  104
  105
  106
  107
  108
  109
  110
  111
  112
  113
  114
  115
  116
  117
  118
  119
  120
  121
  122
  123
  124
  125
  126
  127
  128
  129
  130
  131
  132
  133
  134
  135
  136
  137
  138
  139
  140
  141
  142
  143
  144
  145
  146
  147
  148
  149
  150
  151
  152
  153
  154
  155
  156
  157
  158
  159
  160
  161
  162
  163
  164
  165
  166
  167
  168
  169
  170
  171
  172
  173
  174
  175
  176
  177
  178
  179
  180
  181
  182
  183
  184
  185
  186
  187
  188
  189
  190
  191
  192
  193
  194
  195
  196
  197
  198
  199
  200
  201
  202
  203
  204
  205
  206
  207
  208
  209
  210
  211
  212
  213
  214
  215
  216
  217
  218
  219
  220
  221
  222
  223
  224
  225
  226
  227
  228
  229
  230
  231
  232
  233
  234
  235
  236
  237
  238
  239
  240
  241
  242
  243
  244
  245
  246
  247
  248
  249
  250
  251
  252
  253
  254
  255
  256
  257
  258
  259
  260
  261
  262
  263
  264
  265
  266
  267
  268
  269
  270
  271
  272
  273
  274
  275
  276
  277
  278
  279
  280
  281
  282
  283
  284
  285
  286
  287
  288
  289
  290
  291
  292
  293
  294
  295
  296
  297
  298
  299
  300
  301
  302
  303
  304
  305
  306
  307
  308
  309
  310
  311
  312
  313
  314
  315
  316
  317
  318
  319
  320
  321
  322
  323
  324
  325
  326
  327
  328
  329
  330
  331
  332
  333
  334
  335
  336
  337
  338
  339
  340
  341
  342
  343
  344
  345
  346
  347
  348
  349
  350
  351
  352
  353
  354
  355
  356
  357
  358
  359
  360
  361
  362

ash / shelf / shelf_app_button.h [blame]

// Copyright 2013 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#ifndef ASH_SHELF_SHELF_APP_BUTTON_H_
#define ASH_SHELF_SHELF_APP_BUTTON_H_

#include "ash/ash_export.h"
#include "ash/public/cpp/shelf_types.h"
#include "ash/shelf/shelf_button.h"
#include "ash/shelf/shelf_button_delegate.h"
#include "base/memory/raw_ptr.h"
#include "base/memory/weak_ptr.h"
#include "base/timer/timer.h"
#include "ui/base/metadata/metadata_header_macros.h"
#include "ui/base/models/image_model.h"
#include "ui/base/mojom/menu_source_type.mojom-forward.h"
#include "ui/compositor/layer_animation_observer.h"
#include "ui/gfx/shadow_value.h"
#include "ui/views/animation/ink_drop_observer.h"
#include "ui/views/animation/ink_drop_state.h"
#include "ui/views/controls/image_view.h"

namespace ash {
struct ShelfItem;
class DotIndicator;
class ProgressIndicator;
class ShelfView;

// Button used for app shortcuts on the shelf.
class ASH_EXPORT ShelfAppButton : public ShelfButton,
                                  public views::InkDropObserver,
                                  public ui::ImplicitAnimationObserver {
  METADATA_HEADER(ShelfAppButton, ShelfButton)

 public:

  // Used to indicate the current state of the button.
  enum State {
    // Nothing special. Usually represents an app shortcut item with no running
    // instance.
    STATE_NORMAL = 0,
    // Button has mouse hovering on it.
    STATE_HOVERED = 1 << 0,
    // Underlying ShelfItem has a running instance.
    STATE_RUNNING = 1 << 1,
    // Underlying ShelfItem needs user's attention.
    STATE_ATTENTION = 1 << 2,
    // Hide the status (temporarily for some animations).
    STATE_HIDDEN = 1 << 3,
    // Button is being dragged.
    STATE_DRAGGING = 1 << 4,
    // App has at least 1 notification.
    STATE_NOTIFICATION = 1 << 5,
    // Underlying ShelfItem owns the window that is currently active.
    STATE_ACTIVE = 1 << 6,
  };

  // Returns whether |event| should be handled by a ShelfAppButton if a context
  // menu for the view is shown. Note that the context menu controller will
  // redirect gesture events to the hotseat widget if the context menu was shown
  // for a ShelfAppButton). The hotseat widget uses this method to determine
  // whether such events can/should be dropped without handling.
  static bool ShouldHandleEventFromContextMenu(const ui::GestureEvent* event);

  ShelfAppButton(ShelfView* shelf_view,
                 ShelfButtonDelegate* shelf_button_delegate);

  ShelfAppButton(const ShelfAppButton&) = delete;
  ShelfAppButton& operator=(const ShelfAppButton&) = delete;

  ~ShelfAppButton() override;

  // Updates the icon image and maybe host badge icon image to display for this
  // entry.
  void UpdateMainAndMaybeHostBadgeIconImage();

  // Retrieve the image to show proxy operations.
  gfx::ImageSkia GetImage() const;

  // Gets the resized icon image represented by `icon_image_model_` without the
  // shadow, assuming the provided `icon_scale`.
  gfx::ImageSkia GetIconImage(float icon_scale) const;

  const ui::ImageModel& icon_image_model() const { return icon_image_model_; }

  views::ImageView* icon_view() { return icon_view_; }

  // Returns the badge icon image for the app assuming the provided
  // `icon_scale`. Returns an empty image if the app does not have a badge icon.
  gfx::ImageSkia GetBadgeIconImage(float icon_scale) const;

  // Sets the `icon_image_model_`, and maybe `host_badge_image_` depending on
  // `has_host_badge` for this entry. If |is_placeholder_icon| is true, the
  // |main_image| will be ignored and this entry will be assigned a placeholder
  // vector icon.
  void SetMainAndMaybeHostBadgeImage(const gfx::ImageSkia& main_image,
                                     bool is_placeholder_icon,
                                     const gfx::ImageSkia& host_badge_image);

  // |state| is or'd into the current state.
  void AddState(State state);
  void ClearState(State state);
  int state() const { return state_; }

  // Clears drag drag state that might have been set by gesture handling when a
  // gesture ends. No-op if the drag state has already been cleared.
  void ClearDragStateOnGestureEnd();

  // Returns the bounds of the icon.
  gfx::Rect GetIconBounds() const;

  // Returns the ideal icon bounds within the button view of the provided size,
  // and with the provided icon scale.
  gfx::Rect GetIdealIconBounds(const gfx::Size& button_size,
                               float icon_scale) const;

  views::InkDrop* GetInkDropForTesting();

  // Called when user started dragging the shelf button.
  void OnDragStarted(const ui::LocatedEvent* event);

  // Callback used when a menu for this ShelfAppButton is closed.
  void OnMenuClosed();

  // views::Button overrides:
  void ShowContextMenu(const gfx::Point& p,
                       ui::mojom::MenuSourceType source_type) override;
  bool ShouldEnterPushedState(const ui::Event& event) override;

  // views::View overrides:
  bool OnMousePressed(const ui::MouseEvent& event) override;
  void OnMouseReleased(const ui::MouseEvent& event) override;
  void OnMouseCaptureLost() override;
  bool OnMouseDragged(const ui::MouseEvent& event) override;
  void Layout(PassKey) override;
  void ChildPreferredSizeChanged(views::View* child) override;
  void OnThemeChanged() override;

  // Update button state from ShelfItem.
  void ReflectItemStatus(const ShelfItem& item);

  // Returns whether the icon size is up to date.
  bool IsIconSizeCurrent();

  // Called when the request for the context menu model is canceled.
  void OnContextMenuModelRequestCanceled();

  bool FireDragTimerForTest();
  void FireRippleActivationTimerForTest();

  // Return the bounds in the local coordinates enclosing the small ripple area.
  gfx::Rect CalculateSmallRippleArea() const;

  // Sets up the button to simulate promise app UI (icon scaled down, with
  // progress indicator shown), and animates the button into the normal app UI
  // (hides the progress indicator, and scales the app icon up).
  // Used to animate the button in when it's replacing a promise icon.
  // `fallback_icon` - the promise app icon that should be used while the app
  // button is animating in. The installed app icon is loaded asynchronously, so
  // there is a noticeable delay before the icon becomes available. Using
  // fallback icon during animation prevents jankiness in the time period the
  // app icon is loading. The jankiness manifests itself as the app icon
  // disappearing for a moment after the promise icon is installed.
  // `callback` - callback run when the animation completes.
  void AnimateInFromPromiseApp(const ui::ImageModel& fallback_icon,
                               const base::RepeatingClosure& callback);

  void SetNotificationBadgeColor(SkColor color);

  float progress() { return progress_; }

  AppStatus app_status() { return app_status_; }
  const std::string& package_id() const { return package_id_; }
  bool is_promise_app() const { return is_promise_app_; }

  ProgressIndicator* GetProgressIndicatorForTest() const;

  void UpdateAccessibleName();

 protected:
  gfx::ImageSkia GetHostBadgeImageForTest() { return host_badge_image_; }

  // ui::EventHandler:
  void OnGestureEvent(ui::GestureEvent* event) override;

  // ui::ImplicitAnimationObserver:
  void OnImplicitAnimationsCompleted() override;

  // Sets the icon image with a shadow.
  void SetShadowedImage(const gfx::ImageSkia& bitmap);

 private:
  class AppNotificationIndicatorView;
  class AppStatusIndicatorView;
  friend class ShelfViewWebAppShortcutTest;

  // views::View:
  bool HandleAccessibleAction(const ui::AXActionData& action_data) override;

  // views::InkDropObserver:
  void InkDropAnimationStarted() override;
  void InkDropRippleAnimationEnded(views::InkDropState state) override;

  // Updates the parts of the button to reflect the current |state_| and
  // alignment. This may add or remove views, layout and paint.
  void UpdateState();

  // Invoked when |touch_drag_timer_| fires to show dragging UI.
  void OnTouchDragTimer();

  // Invoked when |ripple_activation_timer_| fires to activate the ink drop.
  void OnRippleTimer();

  // Calculates the preferred size of the icon for the provided `icon_scale`.
  // This is the actual size of the main app icon that is painted in the grid.
  // with the adjusted scale.
  gfx::Size GetPreferredIconSize(const ui::ImageModel& image_model,
                                 float icon_scale) const;

  // Scales up app icon if |scale_up| is true, otherwise scales it back to
  // normal size.
  void ScaleAppIcon(bool scale_up);

  // Calculates the expected icon bounds for an icon view scaled by
  // |icon_scale|.
  gfx::Rect GetIconViewBounds(const gfx::Rect& button_bounds,
                              float icon_scale,
                              bool ignore_shadow_insets) const;

  // Calculates the bounds for either the shortcut icon container or shortcut
  // icon scaled by `icon_scale`.
  gfx::Rect GetShortcutViewBounds(const gfx::Rect& button_bounds,
                                  float icon_scale,
                                  const float icon_size) const;

  // Calculates the notification indicator bounds when scaled by |scale|.
  gfx::Rect GetNotificationIndicatorBounds(float scale);

  // Calculates the transform between the icon scaled by |icon_scale| and the
  // normal size icon.
  gfx::Transform GetScaleTransform(float icon_scale);

  // Marks whether the ink drop animation has started or not.
  void SetInkDropAnimationStarted(bool started);

  // Maybe hides the ink drop at the end of gesture handling.
  void MaybeHideInkDropWhenGestureEnds();

  // Updates the layer bounds for the `progress_indicator_` if any is currently
  // active.
  void UpdateProgressRingBounds();

  // Sets the host badge image to display for this entry
  void SetHostBadgeImage(const gfx::ImageSkia& host_badge_image);

  // Whether the image view has a placeholder icon in place. The placeholder
  // icon is represented as a VectorIcon in the ImageModel. Depending on the
  // case, the icon may use the `icon_image_model` or the
  // `fallback_icon_image_model` (ie, when an animation in for the promise app
  // is happening) for this calceulation.
  bool ImageModelHasPlaceholderIcon() const;

  // Returns the preferred icon size for promise icons depending on this
  // button's `app_state_`. Different from `GetPreferredIconSize()` since
  // `GetIconDimensionByAppState()` is used to adjust padding for the promise
  // ring.
  float GetIconDimensionByAppState() const;

  // Called when the app button completes animating in from a promise app state.
  void OnAnimatedInFromPromiseApp(base::RepeatingClosure callback);

  void UpdateAccessibleDescription();

  // The icon part of a button can be animated independently of the rest.
  raw_ptr<views::ImageView> icon_view_ = nullptr;

  // The ShelfView showing this ShelfAppButton. Owned by RootWindowController.
  const raw_ptr<ShelfView> shelf_view_;

  // Draws an indicator underneath the image to represent the state of the
  // application.
  const raw_ptr<AppStatusIndicatorView> indicator_;

  // Draws an indicator in the top right corner of the image to represent an
  // active notification.
  raw_ptr<DotIndicator> notification_indicator_ = nullptr;

  // The current application state, a bitfield of State enum values.
  int state_ = STATE_NORMAL;

  gfx::ShadowValues icon_shadows_;

  // The model image for this app button.
  ui::ImageModel icon_image_model_;

  // The bitmap image for the host badge icon if this is an App Shortcut.
  gfx::ImageSkia host_badge_image_;

  // The scaling factor for displaying the app icon.
  float icon_scale_ = 1.0f;

  // App status.
  AppStatus app_status_ = AppStatus::kReady;

  // Item progress. Only applicable if `is_promise_app_` is true.
  float progress_ = -1.0f;

  // Indicates whether the ink drop animation starts.
  bool ink_drop_animation_started_ = false;

  // A timer to defer showing drag UI when the shelf button is pressed.
  base::OneShotTimer drag_timer_;

  // A timer to activate the ink drop ripple during a long press.
  base::OneShotTimer ripple_activation_timer_;

  // The target visibility of the shelf app's context menu.
  // NOTE: when `context_menu_target_visibility_` is true, the context menu may
  // not show yet due to the async request for the menu model.
  bool context_menu_target_visibility_ = false;

  // An object that draws and updates the progress ring around promise app
  // icons.
  std::unique_ptr<ProgressIndicator> progress_indicator_;

  std::unique_ptr<ShelfButtonDelegate::ScopedActiveInkDropCount>
      ink_drop_count_;

  // Whether the app is a promise app  (i.e. an app with pending or installing
  // app status).
  bool is_promise_app_ = false;

  // The package id that is associated with this shelf app.
  std::string package_id_;

  // Whether the app has a host badge (i.e. an App Shortcut).
  bool has_host_badge_ = false;

  // The fallback icon used as the app button image when the app is animated in
  // from a promise icon. The fallback icon will be used at least until the
  // actual app icon has been loaded. This prevents a flash of an empty icon
  // when the app icon replaces a promise icon.
  ui::ImageModel fallback_icon_image_model_;

  // Whether the fallback icon should be used even if the actual app icon is
  // available. This will be set animating the app button in from promise app
  // state to prevent app icon changes mid animation.
  bool force_fallback_icon_ = false;

  std::optional<float> forced_progress_indicator_value_;

  // Whether the non-placeholder app icon has been loaded for the app.
  bool has_icon_image_ = false;

  // Used to track whether the menu was deleted while running. Must be last.
  base::WeakPtrFactory<ShelfAppButton> weak_factory_{this};
};

}  // namespace ash

#endif  // ASH_SHELF_SHELF_APP_BUTTON_H_