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
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
ash / shelf / scrollable_shelf_view.h [blame]
// Copyright 2019 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_SCROLLABLE_SHELF_VIEW_H_
#define ASH_SHELF_SCROLLABLE_SHELF_VIEW_H_
#include <memory>
#include <optional>
#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 "base/functional/callback_helpers.h"
#include "base/memory/raw_ptr.h"
#include "base/memory/weak_ptr.h"
#include "base/time/time.h"
#include "ui/base/metadata/metadata_header_macros.h"
#include "ui/base/mojom/menu_source_type.mojom-forward.h"
#include "ui/compositor/layer.h"
#include "ui/compositor/layer_animation_observer.h"
#include "ui/gfx/geometry/linear_gradient.h"
#include "ui/views/animation/ink_drop_host.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 {
METADATA_HEADER(ScrollableShelfView, views::AccessiblePaneView)
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);
// Called after the widget has been fully initialized to ensure all dependent
// components are available.
void UpdateAccessiblePreviousAndNextFocus();
// 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;
// Returns whether the shelf will be overflown (i.e. it will show one or both
// arrow buttons) if it is given the input length.
bool CalculateShelfOverflowForAvailableLength(int available_length) 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_; }
std::optional<size_t> first_tappable_app_index() const {
return first_tappable_app_index_;
}
std::optional<size_t> 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 views::SizeBounds& available_size) const override;
void Layout(PassKey) override;
void ChildPreferredSizeChanged(views::View* child) override;
void OnScrollEvent(ui::ScrollEvent* event) override;
void OnMouseEvent(ui::MouseEvent* event) override;
void OnGestureEvent(ui::GestureEvent* event) 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;
// 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<ScopedActiveInkDropCount> 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::mojom::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,
views::View* delegate_view) const override;
const std::vector<aura::Window*> 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 the visibility of arrow buttons is updated
// during layout, which may happen asynchronously, 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;
// Updates the bounds of the gradient zone before/after the shelf
// container.
void UpdateGradientMask();
void CalculateHorizontalGradient(gfx::LinearGradient* gradient_mask);
void CalculateVerticalGradient(gfx::LinearGradient* gradient_mask);
// 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();
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<std::optional<size_t>, std::optional<size_t>>
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.
raw_ptr<ScrollArrowView> left_arrow_ = nullptr;
raw_ptr<ScrollArrowView> right_arrow_ = nullptr;
raw_ptr<ShelfContainerView> shelf_container_view_ = nullptr;
// Available space to accommodate child views. It is mirrored for the
// horizontal shelf under RTL.
gfx::Rect available_space_;
raw_ptr<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<views::FocusSearch> focus_search_;
// The index of the first/last tappable app index.
std::optional<size_t> first_tappable_app_index_ = std::nullopt;
std::optional<size_t> last_tappable_app_index_ = std::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_;
raw_ptr<TestObserver> test_observer_ = nullptr;
// If page flip timer is active for shelf item drag, the last known drag item
// bounds in screen coordinates.
std::optional<gfx::Rect> 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<ui::PresentationTimeRecorder> presentation_time_recorder_;
base::ScopedClosureRunner force_show_hotseat_resetter_;
base::WeakPtrFactory<ScrollableShelfView> weak_ptr_factory_{this};
};
} // namespace ash
#endif // ASH_SHELF_SCROLLABLE_SHELF_VIEW_H_