text: // Copyright 2019 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_SHELF_SCROLLABLE_SHELF_VIEW_H_
  #define ASH_SHELF_SCROLLABLE_SHELF_VIEW_H_
  
  #include <memory>
  
  #include "ash/app_list/views/app_list_drag_and_drop_host.h"
  #include "ash/ash_export.h"
  #include "ash/drag_drop/drag_image_view.h"
  #include "ash/public/cpp/shelf_config.h"
  #include "ash/public/cpp/shelf_model.h"
  #include "ash/shelf/scroll_arrow_view.h"
  #include "ash/shelf/shelf.h"
  #include "ash/shelf/shelf_button_delegate.h"
  #include "ash/shelf/shelf_container_view.h"
  #include "ash/shelf/shelf_tooltip_delegate.h"
  #include "ash/shelf/shelf_view.h"
  #include "base/cancelable_callback.h"
  #include "ui/compositor/layer_animation_observer.h"
  #include "ui/views/animation/ink_drop_host_view.h"
  #include "ui/views/context_menu_controller.h"
  #include "ui/views/controls/button/button.h"
  #include "ui/views/widget/unique_widget_ptr.h"
  #include "ui/views/widget/widget.h"
  
  namespace views {
  class FocusSearch;
  }
  
  namespace ash {
  class PresentationTimeRecorder;
  
  class ASH_EXPORT ScrollableShelfView : public views::AccessiblePaneView,
                                         public ShellObserver,
                                         public ShelfConfig::Observer,
                                         public ShelfButtonDelegate,
                                         public ShelfTooltipDelegate,
                                         public views::ContextMenuController,
                                         public ApplicationDragAndDropHost,
                                         public ui::ImplicitAnimationObserver {
   public:
    class TestObserver {
     public:
      virtual ~TestObserver() = default;
      virtual void OnPageFlipTimerFired() = 0;
    };
  
    enum LayoutStrategy {
      // The arrow buttons are not shown. It means that there is enough space to
      // accommodate all of shelf icons.
      kNotShowArrowButtons,
  
      // Only the left arrow button is shown.
      kShowLeftArrowButton,
  
      // Only the right arrow button is shown.
      kShowRightArrowButton,
  
      // Both buttons are shown.
      kShowButtons
    };
  
    ScrollableShelfView(ShelfModel* model, Shelf* shelf);
    ~ScrollableShelfView() override;
  
    void Init();
  
    // Called when the focus ring for ScrollableShelfView is enabled/disabled.
    // |activated| is true when enabling the focus ring.
    void OnFocusRingActivationChanged(bool activated);
  
    // Scrolls to a new page of shelf icons. |forward| indicates whether the next
    // page or previous page is shown.
    void ScrollToNewPage(bool forward);
  
    // AccessiblePaneView:
    views::FocusSearch* GetFocusSearch() override;
    views::FocusTraversable* GetFocusTraversableParent() override;
    views::View* GetFocusTraversableParentView() override;
    views::View* GetDefaultFocusableChild() override;
  
    // Returns the |available_space_|.
    gfx::Rect GetHotseatBackgroundBounds() const;
  
    // Returns whether the view should adapt to RTL.
    bool ShouldAdaptToRTL() const;
  
    // Returns whether the scrollable shelf's current size is equal to the target
    // size.
    bool NeedUpdateToTargetBounds() const;
  
    // Returns the icon's target bounds in screen. The returned bounds are
    // calculated with the hotseat's target bounds instead of the actual bounds.
    // It helps to get the icon's final location before the bounds animation on
    // hotseat ends.
    gfx::Rect GetTargetScreenBoundsOfItemIcon(const ShelfID& id) const;
  
    // Returns whether scrollable shelf should show app buttons with scrolling
    // when the view size is |target_size| and app button size is |button_size|.
    bool RequiresScrollingForItemSize(const gfx::Size& target_size,
                                      int button_size) const;
  
    // Sets padding insets.
    void SetEdgePaddingInsets(const gfx::Insets& padding_insets);
  
    // Returns the edge padding insets based on the scrollable shelf view's
    // target bounds or the current bounds, indicated by |use_target_bounds|.
    gfx::Insets CalculateEdgePadding(bool use_target_bounds) const;
  
    views::View* GetShelfContainerViewForTest();
    bool ShouldAdjustForTest() const;
  
    void SetTestObserver(TestObserver* test_observer);
  
    // Returns true if any shelf corner button has ripple ring activated.
    bool IsAnyCornerButtonInkDropActivatedForTest() const;
  
    // Returns the maximum scroll distance for the current layout.
    float GetScrollUpperBoundForTest() const;
  
    ShelfView* shelf_view() { return shelf_view_; }
    ShelfContainerView* shelf_container_view() { return shelf_container_view_; }
    const ShelfContainerView* shelf_container_view() const {
      return shelf_container_view_;
    }
    ScrollArrowView* left_arrow() { return left_arrow_; }
    const ScrollArrowView* left_arrow() const { return left_arrow_; }
    ScrollArrowView* right_arrow() { return right_arrow_; }
    const ScrollArrowView* right_arrow() const { return right_arrow_; }
  
    LayoutStrategy layout_strategy_for_test() const { return layout_strategy_; }
    gfx::Vector2dF scroll_offset_for_test() const { return scroll_offset_; }
  
    const DragImageView* drag_icon_for_test() const {
      return drag_icon_widget_ ? static_cast<DragImageView*>(
                                     drag_icon_widget_->GetContentsView())
                               : nullptr;
    }
  
    int first_tappable_app_index() { return first_tappable_app_index_; }
    int last_tappable_app_index() { return last_tappable_app_index_; }
  
    void set_default_last_focusable_child(bool default_last_focusable_child) {
      default_last_focusable_child_ = default_last_focusable_child;
    }
  
    void set_page_flip_time_threshold(base::TimeDelta page_flip_time_threshold) {
      page_flip_time_threshold_ = page_flip_time_threshold;
    }
  
    const gfx::Rect& visible_space() const { return visible_space_; }
  
    const gfx::Insets& edge_padding_insets() const {
      return edge_padding_insets_;
    }
  
    void set_is_padding_configured_externally(
        bool is_padding_configured_externally) {
      is_padding_configured_externally_ = is_padding_configured_externally;
    }
  
    // Size of the arrow button.
    static int GetArrowButtonSize();
  
    // Padding at the two ends of the shelf.
    static constexpr int kEndPadding = 4;
  
    // The mouse wheel event (including touchpad scrolling) with the main axis
    // offset smaller than the threshold will be ignored.
    static constexpr int KScrollOffsetThreshold = 20;
  
   private:
    friend class ShelfTestApi;
  
    class GradientLayerDelegate;
    class ScrollableShelfArrowView;
    class DragIconDropAnimationDelegate;
    class ScopedActiveInkDropCountImpl;
  
    struct FadeZone {
      // Bounds of the fade in/out zone.
      gfx::Rect zone_rect;
  
      // Specifies the type of FadeZone: fade in or fade out.
      bool fade_in = false;
  
      // Indicates the drawing direction.
      bool is_horizontal = false;
    };
  
    enum ScrollStatus {
      // Indicates whether the gesture scrolling is across the main axis.
      // That is, whether it is scrolling vertically for bottom shelf, or
      // whether it is scrolling horizontally for left/right shelf.
      kAcrossMainAxisScroll,
  
      // Indicates whether the gesture scrolling is along the main axis.
      // That is, whether it is scrolling horizontally for bottom shelf, or
      // whether it is scrolling vertically for left/right shelf.
      kAlongMainAxisScroll,
  
      // Not in scrolling.
      kNotInScroll
    };
  
    // Sum of the shelf button size and the gap between shelf buttons.
    int GetSumOfButtonSizeAndSpacing() const;
  
    // Decides whether the current first visible shelf icon of the scrollable
    // shelf should be hidden or fully shown when gesture scroll ends.
    int GetGestureDragThreshold() const;
  
    // Returns the maximum scroll distance based on the given space for icons.
    float CalculateScrollUpperBound(int available_space_for_icons) const;
  
    // Returns the clamped scroll offset.
    float CalculateClampedScrollOffset(float scroll,
                                       int available_space_for_icons) const;
  
    // Creates the animation for scrolling shelf by |scroll_distance|.
    void StartShelfScrollAnimation(float scroll_distance);
  
    // Calculates the layout strategy based on:
    // (1) scroll offset on the main axis.
    // (2) length of the available space to accommodate shelf icons.
    LayoutStrategy CalculateLayoutStrategy(float scroll_distance_on_main_axis,
                                           int available_length) const;
  
    Shelf* GetShelf();
    const Shelf* GetShelf() const;
  
    // views::View:
    gfx::Size CalculatePreferredSize() const override;
    void Layout() override;
    void ChildPreferredSizeChanged(views::View* child) override;
    const char* GetClassName() const override;
    void OnScrollEvent(ui::ScrollEvent* event) override;
    void OnMouseEvent(ui::MouseEvent* event) override;
    void OnGestureEvent(ui::GestureEvent* event) override;
    void GetAccessibleNodeData(ui::AXNodeData* node_data) override;
    void OnBoundsChanged(const gfx::Rect& previous_bounds) override;
    void ViewHierarchyChanged(
        const views::ViewHierarchyChangedDetails& details) override;
    void ScrollRectToVisible(const gfx::Rect& rect) override;
    std::unique_ptr<ui::Layer> RecreateLayer() override;
  
    // ShelfButtonDelegate:
    void OnShelfButtonAboutToRequestFocusFromTabTraversal(ShelfButton* button,
                                                          bool reverse) override;
    void ButtonPressed(views::Button* sender,
                       const ui::Event& event,
                       views::InkDrop* ink_drop) override;
    void HandleAccessibleActionScrollToMakeVisible(ShelfButton* button) override;
    std::unique_ptr<ScopedActiveInkDropCount> CreateScopedActiveInkDropCount(
        const ShelfButton* sender) override;
  
    // ContextMenuController:
    void ShowContextMenuForViewImpl(views::View* source,
                                    const gfx::Point& point,
                                    ui::MenuSourceType source_type) override;
  
    // ShellObserver:
    void OnShelfAlignmentChanged(aura::Window* root_window,
                                 ShelfAlignment old_alignment) override;
  
    // ShelfConfig::Observer:
    void OnShelfConfigUpdated() override;
  
    // ShelfTooltipDelegate:
    bool ShouldShowTooltipForView(const views::View* view) const override;
    bool ShouldHideTooltip(const gfx::Point& cursor_location) const override;
    const std::vector<aura::Window*> GetOpenWindowsForView(
        views::View* view) override;
    base::string16 GetTitleForView(const views::View* view) const override;
    views::View* GetViewForEvent(const ui::Event& event) override;
  
    // ApplicationDragAndDropHost:
    bool ShouldStartDrag(
        const std::string& app_id,
        const gfx::Point& location_in_screen_coordinates) const override;
    void CreateDragIconProxyByLocationWithNoAnimation(
        const gfx::Point& origin_in_screen_coordinates,
        const gfx::ImageSkia& icon,
        views::View* replaced_view,
        float scale_factor,
        int blur_radius) override;
    void UpdateDragIconProxy(
        const gfx::Point& location_in_screen_coordinates) override;
    void DestroyDragIconProxy() override;
    bool StartDrag(const std::string& app_id,
                   const gfx::Point& location_in_screen_coordinates) override;
    bool Drag(const gfx::Point& location_in_screen_coordinates) override;
  
    // ui::ImplicitAnimationObserver:
    void OnImplicitAnimationsCompleted() override;
  
    // Returns whether the left/right arrow button should show based on the
    // current layout strategy. Because Layout(), where the visibility of arrow
    // buttons is updated, may be called in asynchronous way, we should not use
    // arrow buttons' visibility directly.
    bool ShouldShowLeftArrow() const;
    bool ShouldShowRightArrow() const;
  
    int GetStatusWidgetSizeOnPrimaryAxis(bool use_target_bounds) const;
  
    // Returns the local bounds depending on which view bounds are used: actual
    // view bounds or target view bounds.
    gfx::Rect GetAvailableLocalBounds(bool use_target_bounds) const;
  
    // Calculates padding for display centering alignment depending on which view
    // bounds are used: actual view bounds or target view bounds.
    gfx::Insets CalculatePaddingForDisplayCentering(bool use_target_bounds) const;
  
    // Returns whether the received gesture event should be handled here.
    bool ShouldHandleGestures(const ui::GestureEvent& event);
  
    // Resets the attributes related with gesture scroll to their default values.
    void ResetScrollStatus();
  
    // Handles events for scrolling the shelf. Returns whether the event has been
    // consumed.
    bool ProcessGestureEvent(const ui::GestureEvent& event);
  
    void HandleMouseWheelEvent(ui::MouseWheelEvent* event);
  
    // Scrolls the view by distance of |x_offset| or |y_offset|. |animating|
    // indicates whether the animation displays. |x_offset| or |y_offset| has to
    // be float. Otherwise the slow gesture drag is neglected.
    void ScrollByXOffset(float x_offset, bool animating);
    void ScrollByYOffset(float y_offset, bool animating);
  
    // Scrolls the view to the target offset. |animating| indicates whether the
    // scroll animation is needed. Note that |target_offset| may be an illegal
    // value and may get adjusted in CalculateClampedScrollOffset function.
    void ScrollToMainOffset(float target_offset, bool animating);
  
    // Calculates the scroll distance to show a new page of shelf icons for
    // the given layout strategy. |forward| indicates whether the next page or
    // previous page is shown.
    float CalculatePageScrollingOffset(bool forward,
                                       LayoutStrategy layout_strategy) const;
  
    // Calculates the absolute value of page scroll distance.
    float CalculatePageScrollingOffsetInAbs(LayoutStrategy layout_strategy) const;
  
    // Calculates the target offset on the main axis after scrolling by
    // |scroll_distance| while the offset before scroll is |start_offset|.
    float CalculateTargetOffsetAfterScroll(float start_offset,
                                           float scroll_distance) const;
  
    // Calculates the bounds of the gradient zone before/after the shelf
    // container.
    FadeZone CalculateStartGradientZone() const;
    FadeZone CalculateEndGradientZone() const;
  
    // Updates the visibility of gradient zones.
    void UpdateGradientZoneState();
  
    // Updates the gradient zone if the gradient zone's target bounds are
    // different from the actual values.
    void MaybeUpdateGradientZone();
  
    void PaintGradientZone(const FadeZone& start_gradient_zone,
                           const FadeZone& end_gradient_zone);
  
    bool ShouldApplyMaskLayerGradientZone() const;
  
    // Returns the actual scroll offset for the given scroll distance along the
    // main axis under the specific layout strategy. When the left arrow button
    // shows, |shelf_view_| is translated due to the change in
    // |shelf_container_view_|'s bounds. That translation offset is not included
    // in |scroll_offset_|.
    float GetActualScrollOffset(float main_axis_scroll_distance,
                                LayoutStrategy layout_strategy) const;
  
    // Updates |first_tappable_app_index_| and |last_tappable_app_index_|.
    void UpdateTappableIconIndices();
  
    // Calculates the indices of the first/last tappable app under the given
    // layout strategy and offset along the main axis (that is the x-axis when
    // shelf is horizontally aligned or the y-axis if the shelf is vertically
    // aligned).
    std::pair<int, int> CalculateTappableIconIndices(
        LayoutStrategy layout_strategy,
        int scroll_distance_on_main_axis) const;
  
    views::View* FindFirstFocusableChild();
    views::View* FindLastFocusableChild();
  
    // Returns the available space on the main axis for shelf icons.
    int GetSpaceForIcons() const;
  
    // Returns whether |available_size| is able to accommodate all shelf icons
    // without scrolling. |icons_preferred_size| is the space required by shelf
    // icons.
    bool CanFitAllAppsWithoutScrolling(
        const gfx::Size& available_size,
        const gfx::Size& icons_preferred_size) const;
  
    // Returns whether scrolling should be handled. |is_gesture_fling| is true
    // when the scrolling is triggered by gesture fling event; when it is false,
    // the scrolling is triggered by touch pad or mouse wheel event.
    bool ShouldHandleScroll(const gfx::Vector2dF& offset,
                            bool is_gesture_fling) const;
  
    // May initiate the scroll animation to ensure that the app icons are shown
    // correctly. Returns whether the animation is created.
    bool AdjustOffset();
  
    // Returns the offset by which the scroll distance along the main axis should
    // be adjusted to ensure the correct UI under the specific layout strategy.
    // Three parameters are needed: (1) scroll offset on the main axis (2) layout
    // strategy (3) available space for shelf icons.
    float CalculateAdjustmentOffset(int main_axis_scroll_distance,
                                    LayoutStrategy layout_strategy,
                                    int available_space_for_icons) const;
  
    int CalculateScrollDistanceAfterAdjustment(
        int main_axis_scroll_distance,
        LayoutStrategy layout_strategy) const;
  
    // Updates the available space for child views (such as the arrow button,
    // shelf view) which is smaller than the view's bounds due to paddings.
    void UpdateAvailableSpace();
  
    // Returns the clip rectangle of |shelf_container_view_| for the given layout
    // strategy. Note that |shelf_container_view_|'s bounds are the same with
    // ScrollableShelfView's. It is why we can use |visible_space_| directly
    // without coordinate transformation.
    gfx::Rect CalculateVisibleSpace(LayoutStrategy layout_strategy) const;
  
    // Calculates the padding insets which help to show the edging app icon's
    // ripple ring correctly.
    gfx::Insets CalculateRipplePaddingInsets() const;
  
    // Calculates the rounded corners for |shelf_container_view_|. It contributes
    // to cut off the child view out of the scrollable shelf's bounds, such as
    // ripple ring.
    gfx::RoundedCornersF CalculateShelfContainerRoundedCorners() const;
  
    // Scrolls to a new page if |drag_icon_| is dragged out of |visible_space_|
    // for enough time. The function is called when |page_flip_timer_| is fired.
    void OnPageFlipTimer();
  
    bool IsDragIconWithinVisibleSpace() const;
  
    // Returns whether a scroll event should be handled by this view or delegated
    // to the shelf.
    bool ShouldDelegateScrollToShelf(const ui::ScrollEvent& event) const;
  
    // Calculates the scroll distance along the main axis.
    float CalculateMainAxisScrollDistance() const;
  
    // Updates |scroll_offset_| from |target_offset_| using shelf alignment.
    // |scroll_offset_| may need to update in following cases: (1) View bounds are
    // changed. (2) View is scrolled. (3) A shelf icon is added/removed.
    void UpdateScrollOffset(float target_offset);
  
    // Updates the available space, which may also trigger the change in scroll
    // offset and layout strategy.
    void UpdateAvailableSpaceAndScroll();
  
    // Returns the scroll offset assuming view bounds being the target bounds.
    int CalculateScrollOffsetForTargetAvailableSpace(
        const gfx::Rect& target_space) const;
  
    // Returns whether |sender|'s activated ink drop should be counted.
    bool ShouldCountActivatedInkDrop(const views::View* sender) const;
  
    // Enable/disable the rounded corners of the shelf container.
    void EnableShelfRoundedCorners(bool enable);
  
    // Update the number of corner buttons with ripple ring activated. |increase|
    // indicates whether the number increases or decreases.
    void OnActiveInkDropChange(bool increase);
  
    // Returns whether layer clip should be enabled.
    bool ShouldEnableLayerClip() const;
  
    // Enable/disable the layer clip on |shelf_container_view_|.
    void EnableLayerClipOnShelfContainerView(bool enable);
  
    // Calculates the length of space required by shelf icons to show without
    // scroll. Note that the return value includes the padding space between the
    // app icon and the end of scrollable shelf.
    int CalculateShelfIconsPreferredLength() const;
  
    LayoutStrategy layout_strategy_ = kNotShowArrowButtons;
  
    // Child views Owned by views hierarchy.
    ScrollArrowView* left_arrow_ = nullptr;
    ScrollArrowView* right_arrow_ = nullptr;
    ShelfContainerView* shelf_container_view_ = nullptr;
  
    // Available space to accommodate child views.
    gfx::Rect available_space_;
  
    ShelfView* shelf_view_ = nullptr;
  
    // Defines the padding space inside the scrollable shelf. It is decided by the
    // current padding strategy.
    gfx::Insets edge_padding_insets_;
  
    // Indicates whether |edge_padding_insets_| is configured externally.
    // Usually |edge_padding_insets_| is calculated by ScrollableShelfView's
    // member function. However, in some animations, |edge_padding_insets_|
    // is set by animation progress to ensure the smooth bounds transition.
    bool is_padding_configured_externally_ = false;
  
    // Visible space of |shelf_container_view| in ScrollableShelfView's local
    // coordinates. Different from |available_space_|, |visible_space_| only
    // contains app icons and is mirrored for horizontal shelf under RTL.
    gfx::Rect visible_space_;
  
    gfx::Vector2dF scroll_offset_;
  
    ScrollStatus scroll_status_ = kNotInScroll;
  
    // Gesture states are preserved when the gesture scrolling along the main axis
    // (that is, whether it is scrolling horizontally for bottom shelf, or whether
    // it is scrolling horizontally for left/right shelf) gets started. They help
    // to handle the gesture fling event.
    gfx::Vector2dF scroll_offset_before_main_axis_scrolling_;
    LayoutStrategy layout_strategy_before_main_axis_scrolling_ =
        kNotShowArrowButtons;
  
    std::unique_ptr<GradientLayerDelegate> gradient_layer_delegate_;
  
    std::unique_ptr<views::FocusSearch> focus_search_;
  
    // The index of the first/last tappable app index.
    int first_tappable_app_index_ = -1;
    int last_tappable_app_index_ = -1;
  
    // The number of corner buttons whose ink drop is activated.
    int activated_corner_buttons_ = 0;
  
    // Whether this view should focus its last focusable child (instead of its
    // first) when focused.
    bool default_last_focusable_child_ = false;
  
    // Indicates whether the focus ring on shelf items contained by
    // ScrollableShelfView is enabled.
    bool focus_ring_activated_ = false;
  
    // Indicates that the view is during the scroll animation.
    bool during_scroll_animation_ = false;
  
    // Indicates whether the gradient zone before/after the shelf container view
    // should show.
    bool should_show_start_gradient_zone_ = false;
    bool should_show_end_gradient_zone_ = false;
  
    // Waiting time before flipping the page.
    base::TimeDelta page_flip_time_threshold_;
  
    TestObserver* test_observer_ = nullptr;
  
    // Replaces the dragged app icon during drag procedure. It ensures that the
    // app icon can be dragged out of the shelf view.
    views::UniqueWidgetPtr drag_icon_widget_;
  
    // The delegate to create the animation of moving the dropped icon to the
    // ideal place after drag release.
    std::unique_ptr<DragIconDropAnimationDelegate>
        drag_icon_drop_animation_delegate_;
  
    base::OneShotTimer page_flip_timer_;
  
    // Indicates whether the layer clip should be applied to
    // |shelf_container_view_| in non-overflow mode.
    bool layer_clip_in_non_overflow_ = false;
  
    // Records the presentation time for the scrollable shelf dragging.
    std::unique_ptr<PresentationTimeRecorder> presentation_time_recorder_;
  
    DISALLOW_COPY_AND_ASSIGN(ScrollableShelfView);
  };
  
  }  // namespace ash
  
  #endif  // ASH_SHELF_SCROLLABLE_SHELF_VIEW_H_
  