// 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 #include "ash/app_list/views/app_list_drag_and_drop_host.h" #include "ash/ash_export.h" #include "ash/controls/gradient_layer_delegate.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/callback_helpers.h" #include "base/cancelable_callback.h" #include "base/time/time.h" #include "third_party/abseil-cpp/absl/types/optional.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 ui { class PresentationTimeRecorder; } namespace ash { class ASH_EXPORT ScrollableShelfView : public views::AccessiblePaneView, public ShelfView::Delegate, public ShellObserver, public ShelfConfig::Observer, public ShelfButtonDelegate, public ShelfTooltipDelegate, public views::ContextMenuController, 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(const ScrollableShelfView&) = delete; ScrollableShelfView& operator=(const ScrollableShelfView&) = delete; ~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. `padding_insets` should adapt to RTL for the // horizontal shelf. 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|. Note // that the returned value is mirrored for the horizontal shelf under RTL. gfx::Insets CalculateMirroredEdgePadding(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; // Returns whether `page_flip_timer_` is running. bool IsPageFlipTimerBusyForTest() 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_; } absl::optional first_tappable_app_index() const { return first_tappable_app_index_; } absl::optional last_tappable_app_index() const { 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; } private: friend class ShelfTestApi; class ScrollableShelfArrowView; class ScopedActiveInkDropCountImpl; 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 RecreateLayer() override; // ShelfView::Delegate: void ScheduleScrollForItemDragIfNeeded( const gfx::Rect& location_in_screen) override; void CancelScrollForItemDrag() override; bool AreBoundsWithinVisibleSpace( const gfx::Rect& bounds_in_screem) const 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 CreateScopedActiveInkDropCount( const ShelfButton* sender) override; void OnButtonWillBeRemoved() override; void OnAppButtonActivated(const ShelfButton* button) 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 GetOpenWindowsForView( views::View* view) override; std::u16string GetTitleForView(const views::View* view) const override; views::View* GetViewForEvent(const ui::Event& event) 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; // 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. The returned // value is mirrored for the horizontal shelf under RTL. gfx::Insets CalculateMirroredPaddingForDisplayCentering( 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. GradientLayerDelegate::FadeZone CalculateStartGradientZone() const; GradientLayerDelegate::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 GradientLayerDelegate::FadeZone& start_gradient_zone, const GradientLayerDelegate::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, absl::optional> 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(); // 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. It is mirrored for the // horizontal shelf under RTL. gfx::Rect available_space_; ShelfView* shelf_view_ = nullptr; // Defines the padding space inside the scrollable shelf. It is decided by the // current padding strategy. Note that `edge_padding_insets_` is mirrored // for the horizontal shelf under RTL. 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 gradient_layer_delegate_; std::unique_ptr focus_search_; // The index of the first/last tappable app index. absl::optional first_tappable_app_index_ = absl::nullopt; absl::optional last_tappable_app_index_ = absl::nullopt; // 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; // If page flip timer is active for shelf item drag, the last known drag item // bounds in screen coordinates. absl::optional drag_item_bounds_in_screen_; 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 presentation_time_recorder_; base::ScopedClosureRunner force_show_hotseat_resetter_; }; } // namespace ash #endif // ASH_SHELF_SCROLLABLE_SHELF_VIEW_H_