scrollable_shelf_view.cc 85 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220222122222223222422252226222722282229223022312232223322342235223622372238223922402241224222432244224522462247224822492250225122522253225422552256225722582259226022612262226322642265226622672268226922702271
  1. // Copyright 2019 The Chromium Authors. All rights reserved.
  2. // Use of this source code is governed by a BSD-style license that can be
  3. // found in the LICENSE file.
  4. #include "ash/shelf/scrollable_shelf_view.h"
  5. #include <algorithm>
  6. #include "ash/app_list/app_list_controller_impl.h"
  7. #include "ash/public/cpp/shelf_config.h"
  8. #include "ash/screen_util.h"
  9. #include "ash/shelf/scrollable_shelf_constants.h"
  10. #include "ash/shelf/shelf_app_button.h"
  11. #include "ash/shelf/shelf_focus_cycler.h"
  12. #include "ash/shelf/shelf_navigation_widget.h"
  13. #include "ash/shelf/shelf_tooltip_manager.h"
  14. #include "ash/shelf/shelf_widget.h"
  15. #include "ash/shell.h"
  16. #include "ash/strings/grit/ash_strings.h"
  17. #include "ash/system/status_area_widget.h"
  18. #include "ash/wm/tablet_mode/tablet_mode_controller.h"
  19. #include "base/bind.h"
  20. #include "base/cxx17_backports.h"
  21. #include "base/i18n/rtl.h"
  22. #include "base/metrics/histogram_functions.h"
  23. #include "ui/base/l10n/l10n_util.h"
  24. #include "ui/compositor/animation_throughput_reporter.h"
  25. #include "ui/compositor/presentation_time_recorder.h"
  26. #include "ui/compositor/scoped_layer_animation_settings.h"
  27. #include "ui/gfx/geometry/insets.h"
  28. #include "ui/gfx/geometry/rect_f.h"
  29. #include "ui/gfx/geometry/transform_util.h"
  30. #include "ui/gfx/geometry/vector2d_conversions.h"
  31. #include "ui/views/animation/ink_drop.h"
  32. #include "ui/views/focus/focus_search.h"
  33. #include "ui/views/view_targeter_delegate.h"
  34. namespace ash {
  35. namespace {
  36. // Returns the display id for the display that shows the shelf for |view|.
  37. int64_t GetDisplayIdForView(const views::View* view) {
  38. aura::Window* window = view->GetWidget()->GetNativeWindow();
  39. return display::Screen::GetScreen()->GetDisplayNearestWindow(window).id();
  40. }
  41. void ReportSmoothness(bool tablet_mode, bool launcher_visible, int smoothness) {
  42. base::UmaHistogramPercentage(
  43. scrollable_shelf_constants::kAnimationSmoothnessHistogram, smoothness);
  44. if (tablet_mode) {
  45. if (launcher_visible) {
  46. base::UmaHistogramPercentage(
  47. scrollable_shelf_constants::
  48. kAnimationSmoothnessTabletLauncherVisibleHistogram,
  49. smoothness);
  50. } else {
  51. base::UmaHistogramPercentage(
  52. scrollable_shelf_constants::
  53. kAnimationSmoothnessTabletLauncherHiddenHistogram,
  54. smoothness);
  55. }
  56. } else {
  57. if (launcher_visible) {
  58. base::UmaHistogramPercentage(
  59. scrollable_shelf_constants::
  60. kAnimationSmoothnessClamshellLauncherVisibleHistogram,
  61. smoothness);
  62. } else {
  63. base::UmaHistogramPercentage(
  64. scrollable_shelf_constants::
  65. kAnimationSmoothnessClamshellLauncherHiddenHistogram,
  66. smoothness);
  67. }
  68. }
  69. }
  70. gfx::Insets GetMirroredInsets(const gfx::Insets& insets) {
  71. return gfx::Insets::TLBR(insets.top(), insets.right(), insets.bottom(),
  72. insets.left());
  73. }
  74. } // namespace
  75. ////////////////////////////////////////////////////////////////////////////////
  76. // ScrollableShelfArrowView
  77. class ScrollableShelfView::ScrollableShelfArrowView
  78. : public ScrollArrowView,
  79. public views::ViewTargeterDelegate {
  80. public:
  81. explicit ScrollableShelfArrowView(ArrowType arrow_type,
  82. bool is_horizontal_alignment,
  83. ShelfView* shelf_view,
  84. ShelfButtonDelegate* shelf_button_delegate)
  85. : ScrollArrowView(arrow_type,
  86. is_horizontal_alignment,
  87. shelf_view->shelf(),
  88. shelf_button_delegate),
  89. shelf_(shelf_view->shelf()) {
  90. views::InkDrop::Get(this)->SetMode(views::InkDropHost::InkDropMode::OFF);
  91. SetEventTargeter(std::make_unique<views::ViewTargeter>(this));
  92. SetPaintToLayer();
  93. layer()->SetFillsBoundsOpaquely(false);
  94. set_context_menu_controller(shelf_view);
  95. // When the spoken feedback is enabled, scrollable shelf should ensure that
  96. // the hidden icon which receives the accessibility focus shows through
  97. // scroll animation. So the arrow button is not useful for the spoken
  98. // feedback users. The spoken feedback should ignore the arrow button.
  99. GetViewAccessibility().OverrideIsIgnored(/*value=*/true);
  100. }
  101. ~ScrollableShelfArrowView() override = default;
  102. // views::ViewTargeterDelegate:
  103. bool DoesIntersectRect(const views::View* target,
  104. const gfx::Rect& rect) const override {
  105. DCHECK_EQ(target, this);
  106. const gfx::Rect bounds = gfx::Rect(size());
  107. // Calculates the tapping area. Note that tapping area is bigger than the
  108. // arrow button's bounds.
  109. gfx::Rect tap_rect(
  110. shelf_->PrimaryAxisValue(
  111. scrollable_shelf_constants::kArrowButtonTapAreaHorizontal,
  112. shelf_->hotseat_widget()->GetHotseatSize()),
  113. shelf_->PrimaryAxisValue(
  114. shelf_->hotseat_widget()->GetHotseatSize(),
  115. scrollable_shelf_constants::kArrowButtonTapAreaHorizontal));
  116. tap_rect -= gfx::Vector2d((tap_rect.width() - bounds.width()) / 2,
  117. (tap_rect.height() - bounds.height()) / 2);
  118. DCHECK(tap_rect.Contains(bounds));
  119. return tap_rect.Intersects(rect);
  120. }
  121. // Make ScrollRectToVisible a no-op because ScrollableShelfArrowView is
  122. // always visible/invisible depending on the layout strategy at fixed
  123. // locations. So it does not need to be scrolled to show.
  124. // TODO (andrewxu): Moves all of functions related with scrolling into
  125. // ScrollableShelfContainerView. Then erase this empty function.
  126. void ScrollRectToVisible(const gfx::Rect& rect) override {}
  127. const char* GetClassName() const override {
  128. return "ScrollableShelfArrowView";
  129. }
  130. private:
  131. Shelf* const shelf_;
  132. };
  133. ////////////////////////////////////////////////////////////////////////////////
  134. // ScopedActiveInkDropCountImpl
  135. class ScrollableShelfView::ScopedActiveInkDropCountImpl
  136. : public ScrollableShelfView::ScopedActiveInkDropCount {
  137. public:
  138. explicit ScopedActiveInkDropCountImpl(ScrollableShelfView* owner)
  139. : owner_(owner) {
  140. owner_->OnActiveInkDropChange(/*increase=*/true);
  141. }
  142. ~ScopedActiveInkDropCountImpl() override {
  143. owner_->OnActiveInkDropChange(/*increase=*/false);
  144. }
  145. ScopedActiveInkDropCountImpl(const ScopedActiveInkDropCountImpl& rhs) =
  146. delete;
  147. ScopedActiveInkDropCountImpl& operator=(
  148. const ScopedActiveInkDropCountImpl& rhs) = delete;
  149. private:
  150. ScrollableShelfView* owner_ = nullptr;
  151. };
  152. ////////////////////////////////////////////////////////////////////////////////
  153. // ScrollableShelfContainerView
  154. class ScrollableShelfContainerView : public ShelfContainerView,
  155. public views::ViewTargeterDelegate {
  156. public:
  157. explicit ScrollableShelfContainerView(
  158. ScrollableShelfView* scrollable_shelf_view)
  159. : ShelfContainerView(scrollable_shelf_view->shelf_view()),
  160. scrollable_shelf_view_(scrollable_shelf_view) {
  161. SetEventTargeter(std::make_unique<views::ViewTargeter>(this));
  162. }
  163. ScrollableShelfContainerView(const ScrollableShelfContainerView&) = delete;
  164. ScrollableShelfContainerView& operator=(const ScrollableShelfContainerView&) =
  165. delete;
  166. ~ScrollableShelfContainerView() override = default;
  167. // ShelfContainerView:
  168. void TranslateShelfView(const gfx::Vector2dF& offset) override;
  169. private:
  170. // views::View:
  171. void Layout() override;
  172. // views::ViewTargeterDelegate:
  173. bool DoesIntersectRect(const views::View* target,
  174. const gfx::Rect& rect) const override;
  175. ScrollableShelfView* scrollable_shelf_view_ = nullptr;
  176. };
  177. void ScrollableShelfContainerView::TranslateShelfView(
  178. const gfx::Vector2dF& offset) {
  179. ShelfContainerView::TranslateShelfView(
  180. scrollable_shelf_view_->ShouldAdaptToRTL() ? -offset : offset);
  181. }
  182. void ScrollableShelfContainerView::Layout() {
  183. // Should not use ShelfView::GetPreferredSize in replace of
  184. // CalculateIdealSize. Because ShelfView::CalculatePreferredSize relies on the
  185. // bounds of app icon. Meanwhile, the icon's bounds may be updated by
  186. // animation.
  187. const gfx::Rect ideal_bounds = gfx::Rect(CalculatePreferredSize());
  188. const gfx::Rect local_bounds = GetLocalBounds();
  189. gfx::Rect shelf_view_bounds =
  190. local_bounds.Contains(ideal_bounds) ? local_bounds : ideal_bounds;
  191. if (shelf_view_->shelf()->IsHorizontalAlignment())
  192. shelf_view_bounds.set_x(ShelfConfig::Get()->GetAppIconEndPadding());
  193. else
  194. shelf_view_bounds.set_y(ShelfConfig::Get()->GetAppIconEndPadding());
  195. shelf_view_->SetBoundsRect(shelf_view_bounds);
  196. }
  197. bool ScrollableShelfContainerView::DoesIntersectRect(
  198. const views::View* target,
  199. const gfx::Rect& rect) const {
  200. // This view's layer is clipped. So the view should only handle the events
  201. // within the area after cilp.
  202. gfx::RectF bounds(scrollable_shelf_view_->visible_space());
  203. views::View::ConvertRectToTarget(scrollable_shelf_view_, this, &bounds);
  204. return ToEnclosedRect(bounds).Contains(rect);
  205. }
  206. ////////////////////////////////////////////////////////////////////////////////
  207. // ScrollableShelfFocusSearch
  208. class ScrollableShelfFocusSearch : public views::FocusSearch {
  209. public:
  210. explicit ScrollableShelfFocusSearch(
  211. ScrollableShelfView* scrollable_shelf_view)
  212. : FocusSearch(/*root=*/nullptr,
  213. /*cycle=*/true,
  214. /*accessibility_mode=*/true),
  215. scrollable_shelf_view_(scrollable_shelf_view) {}
  216. ScrollableShelfFocusSearch(const ScrollableShelfFocusSearch&) = delete;
  217. ScrollableShelfFocusSearch& operator=(const ScrollableShelfFocusSearch&) =
  218. delete;
  219. ~ScrollableShelfFocusSearch() override = default;
  220. // views::FocusSearch
  221. views::View* FindNextFocusableView(
  222. views::View* starting_view,
  223. FocusSearch::SearchDirection search_direction,
  224. FocusSearch::TraversalDirection traversal_direction,
  225. FocusSearch::StartingViewPolicy check_starting_view,
  226. FocusSearch::AnchoredDialogPolicy can_go_into_anchored_dialog,
  227. views::FocusTraversable** focus_traversable,
  228. views::View** focus_traversable_view) override {
  229. std::vector<views::View*> focusable_views;
  230. ShelfView* shelf_view = scrollable_shelf_view_->shelf_view();
  231. for (int i : shelf_view->visible_views_indices())
  232. focusable_views.push_back(shelf_view->view_model()->view_at(i));
  233. int start_index = 0;
  234. for (size_t i = 0; i < focusable_views.size(); ++i) {
  235. if (focusable_views[i] == starting_view) {
  236. start_index = i;
  237. break;
  238. }
  239. }
  240. int new_index =
  241. start_index +
  242. (search_direction == FocusSearch::SearchDirection::kBackwards ? -1 : 1);
  243. // Scrolls to the new page if the focused shelf item is not tappable
  244. // on the current page.
  245. if (new_index < 0) {
  246. new_index = focusable_views.size() - 1;
  247. } else if (static_cast<size_t>(new_index) >= focusable_views.size()) {
  248. new_index = 0;
  249. } else if (static_cast<size_t>(new_index) <
  250. scrollable_shelf_view_->first_tappable_app_index()) {
  251. scrollable_shelf_view_->ScrollToNewPage(/*forward=*/false);
  252. } else if (static_cast<size_t>(new_index) >
  253. scrollable_shelf_view_->last_tappable_app_index()) {
  254. scrollable_shelf_view_->ScrollToNewPage(/*forward=*/true);
  255. }
  256. return focusable_views[new_index];
  257. }
  258. private:
  259. ScrollableShelfView* scrollable_shelf_view_ = nullptr;
  260. };
  261. ////////////////////////////////////////////////////////////////////////////////
  262. // ScrollableShelfView
  263. ScrollableShelfView::ScrollableShelfView(ShelfModel* model, Shelf* shelf)
  264. : shelf_view_(new ShelfView(model,
  265. shelf,
  266. /*drag_and_drop_host=*/this,
  267. /*shelf_button_delegate=*/this)),
  268. page_flip_time_threshold_(
  269. scrollable_shelf_constants::kShelfPageFlipDelay) {
  270. Shell::Get()->AddShellObserver(this);
  271. ShelfConfig::Get()->AddObserver(this);
  272. set_allow_deactivate_on_esc(true);
  273. }
  274. ScrollableShelfView::~ScrollableShelfView() {
  275. ShelfConfig::Get()->RemoveObserver(this);
  276. Shell::Get()->RemoveShellObserver(this);
  277. GetShelf()->tooltip()->set_shelf_tooltip_delegate(nullptr);
  278. }
  279. void ScrollableShelfView::Init() {
  280. // Although there is no animation for ScrollableShelfView, a layer is still
  281. // needed. Otherwise, the child view without its own layer will be painted on
  282. // RootView which is beneath |translucent_background_| in ShelfWidget.
  283. // As a result, the child view will not show.
  284. SetPaintToLayer(ui::LAYER_NOT_DRAWN);
  285. layer()->SetFillsBoundsOpaquely(false);
  286. // Initialize the shelf container view.
  287. // Note that |shelf_container_view_| should be under the arrow buttons. It
  288. // ensures that the arrow button receives the tapping events which happen
  289. // within the overlapping zone between the arrow button's tapping area and
  290. // the bounds of |shelf_container_view_|.
  291. shelf_container_view_ =
  292. AddChildView(std::make_unique<ScrollableShelfContainerView>(this));
  293. shelf_container_view_->Initialize();
  294. // Initialize the left arrow button.
  295. left_arrow_ = AddChildView(std::make_unique<ScrollableShelfArrowView>(
  296. ScrollArrowView::kLeft, GetShelf()->IsHorizontalAlignment(), shelf_view_,
  297. this));
  298. // Initialize the right arrow button.
  299. right_arrow_ = AddChildView(std::make_unique<ScrollableShelfArrowView>(
  300. ScrollArrowView::kRight, GetShelf()->IsHorizontalAlignment(), shelf_view_,
  301. this));
  302. gradient_layer_delegate_ =
  303. std::make_unique<GradientLayerDelegate>(/*animate_in=*/false);
  304. layer()->SetMaskLayer(gradient_layer_delegate_->layer());
  305. focus_search_ = std::make_unique<ScrollableShelfFocusSearch>(this);
  306. GetShelf()->tooltip()->set_shelf_tooltip_delegate(this);
  307. set_context_menu_controller(this);
  308. // Initializes |shelf_view_| after scrollable shelf view's children are
  309. // initialized.
  310. shelf_view_->Init(focus_search_.get());
  311. }
  312. void ScrollableShelfView::OnFocusRingActivationChanged(bool activated) {
  313. if (activated) {
  314. focus_ring_activated_ = true;
  315. SetPaneFocusAndFocusDefault();
  316. force_show_hotseat_resetter_ =
  317. GetShelf()->shelf_widget()->ForceShowHotseatInTabletMode();
  318. } else {
  319. // Shows the gradient shader when the focus ring is disabled.
  320. focus_ring_activated_ = false;
  321. if (force_show_hotseat_resetter_)
  322. force_show_hotseat_resetter_.RunAndReset();
  323. }
  324. MaybeUpdateGradientZone();
  325. }
  326. void ScrollableShelfView::ScrollToNewPage(bool forward) {
  327. const float offset = CalculatePageScrollingOffset(forward, layout_strategy_);
  328. if (GetShelf()->IsHorizontalAlignment())
  329. ScrollByXOffset(offset, /*animating=*/true);
  330. else
  331. ScrollByYOffset(offset, /*animating=*/true);
  332. }
  333. views::FocusSearch* ScrollableShelfView::GetFocusSearch() {
  334. return focus_search_.get();
  335. }
  336. views::FocusTraversable* ScrollableShelfView::GetFocusTraversableParent() {
  337. return parent()->GetFocusTraversable();
  338. }
  339. views::View* ScrollableShelfView::GetFocusTraversableParentView() {
  340. return this;
  341. }
  342. views::View* ScrollableShelfView::GetDefaultFocusableChild() {
  343. // Adapts |scroll_offset_| to show the view properly right after the focus
  344. // ring is enabled.
  345. if (default_last_focusable_child_) {
  346. ScrollToMainOffset(CalculateScrollUpperBound(GetSpaceForIcons()),
  347. /*animating=*/true);
  348. return FindLastFocusableChild();
  349. }
  350. ScrollToMainOffset(/*target_offset=*/0.f, /*animating=*/true);
  351. return FindFirstFocusableChild();
  352. }
  353. gfx::Rect ScrollableShelfView::GetHotseatBackgroundBounds() const {
  354. return available_space_;
  355. }
  356. bool ScrollableShelfView::ShouldAdaptToRTL() const {
  357. return base::i18n::IsRTL() && GetShelf()->IsHorizontalAlignment();
  358. }
  359. bool ScrollableShelfView::NeedUpdateToTargetBounds() const {
  360. return GetAvailableLocalBounds(/*use_target_bounds=*/true) !=
  361. GetAvailableLocalBounds(/*use_target_bounds=*/false);
  362. }
  363. gfx::Rect ScrollableShelfView::GetTargetScreenBoundsOfItemIcon(
  364. const ShelfID& id) const {
  365. const int item_index_in_model = shelf_view_->model()->ItemIndexByID(id);
  366. // Return a dummy value if the item specified by `id` does not exist in the
  367. // shelf model.
  368. // TODO(https://crbug.com/1270498): it is a quick fixing. We should
  369. // investigate the root cause.
  370. if (item_index_in_model < 0)
  371. return gfx::Rect();
  372. // Calculates the available space for child views based on the target bounds.
  373. // To ease coding, we use the variables before mirroring in computation.
  374. const gfx::Insets target_edge_padding_RTL_mirrored =
  375. CalculateMirroredEdgePadding(/*use_target_bounds=*/true);
  376. const gfx::Insets target_edge_padding_before_RTL_mirror =
  377. ShouldAdaptToRTL() ? GetMirroredInsets(target_edge_padding_RTL_mirrored)
  378. : target_edge_padding_RTL_mirrored;
  379. gfx::Rect target_space_before_RTL_mirror =
  380. GetAvailableLocalBounds(/*use_target_bounds=*/true);
  381. target_space_before_RTL_mirror.Inset(target_edge_padding_before_RTL_mirror);
  382. const gfx::Insets current_edge_padding_RTL_mirrored = edge_padding_insets_;
  383. const gfx::Insets current_edge_padding_before_RTL_mirror =
  384. ShouldAdaptToRTL() ? GetMirroredInsets(current_edge_padding_RTL_mirrored)
  385. : current_edge_padding_RTL_mirrored;
  386. gfx::Rect icon_bounds =
  387. shelf_view_->view_model()->ideal_bounds(item_index_in_model);
  388. icon_bounds.Offset(target_edge_padding_before_RTL_mirror.left() -
  389. current_edge_padding_before_RTL_mirror.left(),
  390. 0);
  391. // Transforms |icon_bounds| from shelf view's coordinates to scrollable shelf
  392. // view's coordinates manually.
  393. const bool is_horizontal_alignment = GetShelf()->IsHorizontalAlignment();
  394. const int shelf_view_offset = ShelfConfig::Get()->GetAppIconEndPadding();
  395. const int shelf_view_container_offset =
  396. is_horizontal_alignment ? shelf_container_view_->bounds().x()
  397. : shelf_container_view_->bounds().y();
  398. const int target_scroll_offset = CalculateScrollOffsetForTargetAvailableSpace(
  399. target_space_before_RTL_mirror);
  400. const int delta =
  401. -target_scroll_offset + shelf_view_container_offset + shelf_view_offset;
  402. const gfx::Vector2d bounds_offset = is_horizontal_alignment
  403. ? gfx::Vector2d(delta, 0)
  404. : gfx::Vector2d(0, delta);
  405. icon_bounds.Offset(bounds_offset);
  406. // If the icon is invisible under the target view bounds, replaces the actual
  407. // icon's bounds with the rectangle centering on the edge of |target_space|.
  408. const gfx::Point icon_bounds_center = icon_bounds.CenterPoint();
  409. if (icon_bounds_center.x() > target_space_before_RTL_mirror.right()) {
  410. icon_bounds.Offset(
  411. target_space_before_RTL_mirror.right_center().OffsetFromOrigin() -
  412. icon_bounds_center.OffsetFromOrigin());
  413. } else if (icon_bounds_center.x() < target_space_before_RTL_mirror.x()) {
  414. icon_bounds.Offset(
  415. target_space_before_RTL_mirror.left_center().OffsetFromOrigin() -
  416. icon_bounds_center.OffsetFromOrigin());
  417. }
  418. // Hotseat's target bounds may differ from the actual bounds. So it has to
  419. // transform the bounds manually from view's local coordinates to screen.
  420. // Notes that the target bounds stored in shelf layout manager are adapted to
  421. // RTL already while |icon_bounds| are not adjusted to RTL yet.
  422. gfx::Rect hotseat_bounds_in_screen =
  423. GetShelf()->hotseat_widget()->GetTargetBounds();
  424. if (ShouldAdaptToRTL()) {
  425. // One simple way for transformation under RTL is: (1) Transforms hotseat
  426. // target bounds from RTL to LTR. (2) Calculates the icon's bounds in screen
  427. // under LTR. (3) Transforms the icon's bounds to RTL.
  428. gfx::Rect display_bounds =
  429. display::Screen::GetScreen()
  430. ->GetDisplayNearestWindow(GetWidget()->GetNativeView())
  431. .bounds();
  432. hotseat_bounds_in_screen.set_x(display_bounds.right() -
  433. hotseat_bounds_in_screen.right());
  434. icon_bounds.Offset(hotseat_bounds_in_screen.OffsetFromOrigin());
  435. icon_bounds.set_x(display_bounds.right() - icon_bounds.right());
  436. } else {
  437. icon_bounds.Offset(hotseat_bounds_in_screen.OffsetFromOrigin());
  438. }
  439. return icon_bounds;
  440. }
  441. bool ScrollableShelfView::RequiresScrollingForItemSize(
  442. const gfx::Size& target_size,
  443. int button_size) const {
  444. const gfx::Size icons_preferred_size =
  445. shelf_container_view_->CalculateIdealSize(button_size);
  446. return !CanFitAllAppsWithoutScrolling(target_size, icons_preferred_size);
  447. }
  448. void ScrollableShelfView::SetEdgePaddingInsets(
  449. const gfx::Insets& padding_insets) {
  450. edge_padding_insets_ = padding_insets;
  451. shelf_view_->LayoutIfAppIconsOffsetUpdates();
  452. }
  453. gfx::Insets ScrollableShelfView::CalculateMirroredEdgePadding(
  454. bool use_target_bounds) const {
  455. // Tries display centering strategy.
  456. const gfx::Insets display_centering_edge_padding =
  457. CalculateMirroredPaddingForDisplayCentering(use_target_bounds);
  458. if (!display_centering_edge_padding.IsEmpty()) {
  459. // Returns early if the value is legal.
  460. return display_centering_edge_padding;
  461. }
  462. const int icons_size =
  463. shelf_view_->GetSizeOfAppButtons(shelf_view_->number_of_visible_apps(),
  464. shelf_view_->GetButtonSize()) +
  465. 2 * ShelfConfig::Get()->GetAppIconEndPadding();
  466. const gfx::Rect available_local_bounds =
  467. GetAvailableLocalBounds(use_target_bounds);
  468. const int available_size_for_app_icons = GetShelf()->PrimaryAxisValue(
  469. available_local_bounds.width(), available_local_bounds.height());
  470. int gap = CanFitAllAppsWithoutScrolling(available_local_bounds.size(),
  471. CalculatePreferredSize())
  472. ? available_size_for_app_icons - icons_size
  473. : 0; // overflow
  474. // Calculates the paddings before/after the visible area of scrollable shelf.
  475. // |after_padding| being zero ensures that the available space after the
  476. // visible area is filled first.
  477. const int before_padding = gap;
  478. const int after_padding = 0;
  479. gfx::Insets padding_insets;
  480. if (GetShelf()->IsHorizontalAlignment()) {
  481. padding_insets = gfx::Insets::TLBR(0, before_padding, 0, after_padding);
  482. if (ShouldAdaptToRTL())
  483. padding_insets = GetMirroredInsets(padding_insets);
  484. } else {
  485. padding_insets = gfx::Insets::TLBR(before_padding, 0, after_padding, 0);
  486. }
  487. return padding_insets;
  488. }
  489. views::View* ScrollableShelfView::GetShelfContainerViewForTest() {
  490. return shelf_container_view_;
  491. }
  492. bool ScrollableShelfView::ShouldAdjustForTest() const {
  493. return CalculateAdjustmentOffset(CalculateMainAxisScrollDistance(),
  494. layout_strategy_, GetSpaceForIcons());
  495. }
  496. void ScrollableShelfView::SetTestObserver(TestObserver* test_observer) {
  497. DCHECK(!(test_observer && test_observer_));
  498. test_observer_ = test_observer;
  499. }
  500. bool ScrollableShelfView::IsAnyCornerButtonInkDropActivatedForTest() const {
  501. return activated_corner_buttons_ > 0;
  502. }
  503. float ScrollableShelfView::GetScrollUpperBoundForTest() const {
  504. return CalculateScrollUpperBound(GetSpaceForIcons());
  505. }
  506. bool ScrollableShelfView::IsPageFlipTimerBusyForTest() const {
  507. return page_flip_timer_.IsRunning();
  508. }
  509. int ScrollableShelfView::GetSumOfButtonSizeAndSpacing() const {
  510. return shelf_view_->GetButtonSize() + ShelfConfig::Get()->button_spacing();
  511. }
  512. int ScrollableShelfView::GetGestureDragThreshold() const {
  513. return shelf_view_->GetButtonSize() / 2;
  514. }
  515. float ScrollableShelfView::CalculateScrollUpperBound(
  516. int available_space_for_icons) const {
  517. if (layout_strategy_ == kNotShowArrowButtons)
  518. return 0.f;
  519. return std::max(
  520. 0, CalculateShelfIconsPreferredLength() - available_space_for_icons);
  521. }
  522. float ScrollableShelfView::CalculateClampedScrollOffset(
  523. float scroll,
  524. int available_space_for_icons) const {
  525. const float scroll_upper_bound =
  526. CalculateScrollUpperBound(available_space_for_icons);
  527. scroll = base::clamp(scroll, 0.0f, scroll_upper_bound);
  528. return scroll;
  529. }
  530. void ScrollableShelfView::StartShelfScrollAnimation(float scroll_distance) {
  531. const gfx::Vector2dF scroll_offset_before_update = scroll_offset_;
  532. UpdateScrollOffset(scroll_distance);
  533. if (scroll_offset_before_update == scroll_offset_)
  534. return;
  535. StopObservingImplicitAnimations();
  536. during_scroll_animation_ = true;
  537. MaybeUpdateGradientZone();
  538. // In tablet mode, if the target layout only has one arrow button, enable the
  539. // rounded corners of the shelf container layer in order to cut off the icons
  540. // outside of the hotseat background.
  541. const bool one_arrow_in_target_state =
  542. (layout_strategy_ == LayoutStrategy::kShowLeftArrowButton ||
  543. layout_strategy_ == LayoutStrategy::kShowRightArrowButton);
  544. if (one_arrow_in_target_state)
  545. EnableShelfRoundedCorners(/*enable=*/true);
  546. ui::ScopedLayerAnimationSettings animation_settings(
  547. shelf_view_->layer()->GetAnimator());
  548. animation_settings.SetTweenType(gfx::Tween::EASE_OUT);
  549. animation_settings.SetPreemptionStrategy(
  550. ui::LayerAnimator::IMMEDIATELY_SET_NEW_TARGET);
  551. animation_settings.AddObserver(this);
  552. ui::AnimationThroughputReporter reporter(
  553. animation_settings.GetAnimator(),
  554. metrics_util::ForSmoothness(
  555. base::BindRepeating(&ReportSmoothness, Shell::Get()->IsInTabletMode(),
  556. Shell::Get()->app_list_controller()->IsVisible(
  557. GetDisplayIdForView(this)))));
  558. shelf_container_view_->TranslateShelfView(scroll_offset_);
  559. }
  560. ScrollableShelfView::LayoutStrategy
  561. ScrollableShelfView::CalculateLayoutStrategy(float scroll_distance_on_main_axis,
  562. int available_length) const {
  563. if (available_length >= CalculateShelfIconsPreferredLength()) {
  564. return kNotShowArrowButtons;
  565. }
  566. if (scroll_distance_on_main_axis == 0.f) {
  567. // No invisible shelf buttons at the left side. So hide the left button.
  568. return kShowRightArrowButton;
  569. }
  570. if (scroll_distance_on_main_axis ==
  571. CalculateScrollUpperBound(available_length)) {
  572. // If there is no invisible shelf button at the right side, hide the right
  573. // button.
  574. return kShowLeftArrowButton;
  575. }
  576. // There are invisible shelf buttons at both sides. So show two buttons.
  577. return kShowButtons;
  578. }
  579. Shelf* ScrollableShelfView::GetShelf() {
  580. return const_cast<Shelf*>(
  581. const_cast<const ScrollableShelfView*>(this)->GetShelf());
  582. }
  583. const Shelf* ScrollableShelfView::GetShelf() const {
  584. return shelf_view_->shelf();
  585. }
  586. gfx::Size ScrollableShelfView::CalculatePreferredSize() const {
  587. return shelf_container_view_->GetPreferredSize();
  588. }
  589. void ScrollableShelfView::Layout() {
  590. gfx::Rect shelf_container_bounds = gfx::Rect(size());
  591. // Transpose and layout as if it is horizontal.
  592. const bool is_horizontal = GetShelf()->IsHorizontalAlignment();
  593. if (!is_horizontal)
  594. shelf_container_bounds.Transpose();
  595. gfx::Size arrow_button_size(scrollable_shelf_constants::kArrowButtonSize,
  596. shelf_container_bounds.height());
  597. gfx::Size arrow_button_group_size(
  598. scrollable_shelf_constants::kArrowButtonGroupWidth,
  599. shelf_container_bounds.height());
  600. // The bounds of |left_arrow_| and |right_arrow_| are in the
  601. // ScrollableShelfView's local coordinates.
  602. gfx::Rect left_arrow_bounds;
  603. gfx::Rect right_arrow_bounds;
  604. int before_padding;
  605. if (ShouldAdaptToRTL()) {
  606. before_padding = edge_padding_insets_.right();
  607. } else {
  608. before_padding = is_horizontal ? edge_padding_insets_.left()
  609. : edge_padding_insets_.top();
  610. }
  611. int after_padding;
  612. if (ShouldAdaptToRTL()) {
  613. after_padding = edge_padding_insets_.left();
  614. } else {
  615. after_padding = is_horizontal ? edge_padding_insets_.right()
  616. : edge_padding_insets_.bottom();
  617. }
  618. // Calculates the bounds of the left arrow button. If the left arrow button
  619. // should not show, |left_arrow_bounds| should be empty.
  620. if (layout_strategy_ == kShowLeftArrowButton ||
  621. layout_strategy_ == kShowButtons) {
  622. gfx::Point left_arrow_start_point(shelf_container_bounds.x(), 0);
  623. left_arrow_bounds =
  624. gfx::Rect(left_arrow_start_point, arrow_button_group_size);
  625. left_arrow_bounds.Offset(before_padding, 0);
  626. left_arrow_bounds.Inset(gfx::Insets::TLBR(
  627. 0, scrollable_shelf_constants::kArrowButtonEndPadding, 0,
  628. scrollable_shelf_constants::kDistanceToArrowButton));
  629. left_arrow_bounds.ClampToCenteredSize(arrow_button_size);
  630. }
  631. if (layout_strategy_ == kShowRightArrowButton ||
  632. layout_strategy_ == kShowButtons) {
  633. gfx::Point right_arrow_start_point(
  634. shelf_container_bounds.right() - after_padding -
  635. scrollable_shelf_constants::kArrowButtonGroupWidth,
  636. 0);
  637. right_arrow_bounds =
  638. gfx::Rect(right_arrow_start_point, arrow_button_group_size);
  639. right_arrow_bounds.Inset(gfx::Insets::TLBR(
  640. 0, scrollable_shelf_constants::kDistanceToArrowButton, 0,
  641. scrollable_shelf_constants::kArrowButtonEndPadding));
  642. right_arrow_bounds.ClampToCenteredSize(arrow_button_size);
  643. }
  644. // Adjust the bounds when not showing in the horizontal
  645. // alignment.tShelf()->IsHorizontalAlignment()) {
  646. if (!is_horizontal) {
  647. left_arrow_bounds.Transpose();
  648. right_arrow_bounds.Transpose();
  649. shelf_container_bounds.Transpose();
  650. }
  651. // Layout |left_arrow_| if it should show.
  652. left_arrow_->SetVisible(!left_arrow_bounds.IsEmpty());
  653. left_arrow_->SetBoundsRect(left_arrow_bounds);
  654. // Layout |right_arrow_| if it should show.
  655. right_arrow_->SetVisible(!right_arrow_bounds.IsEmpty());
  656. right_arrow_->SetBoundsRect(right_arrow_bounds);
  657. // Layer::Clone(), which may be triggered by screen rotation, does not copy
  658. // the mask layer. So we may need to reset the mask layer.
  659. if (ShouldApplyMaskLayerGradientZone() && !layer()->layer_mask_layer()) {
  660. DCHECK(!gradient_layer_delegate_->layer()->layer_mask_back_link());
  661. layer()->SetMaskLayer(gradient_layer_delegate_->layer());
  662. }
  663. MaybeUpdateGradientZone();
  664. // Layout |shelf_container_view_|.
  665. shelf_container_view_->SetBoundsRect(shelf_container_bounds);
  666. EnableLayerClipOnShelfContainerView(ShouldEnableLayerClip());
  667. }
  668. void ScrollableShelfView::ChildPreferredSizeChanged(views::View* child) {
  669. // Add/remove a shelf icon may change the layout strategy.
  670. UpdateAvailableSpaceAndScroll();
  671. shelf_container_view_->TranslateShelfView(scroll_offset_);
  672. Layout();
  673. }
  674. void ScrollableShelfView::OnScrollEvent(ui::ScrollEvent* event) {
  675. if (event->finger_count() != 2)
  676. return;
  677. if (ShouldDelegateScrollToShelf(*event)) {
  678. GetShelf()->ProcessScrollEvent(event);
  679. event->StopPropagation();
  680. }
  681. }
  682. void ScrollableShelfView::OnMouseEvent(ui::MouseEvent* event) {
  683. if (event->IsMouseWheelEvent()) {
  684. HandleMouseWheelEvent(event->AsMouseWheelEvent());
  685. return;
  686. }
  687. // The mouse event's location may be outside of ShelfView but within the
  688. // bounds of the ScrollableShelfView. Meanwhile, ScrollableShelfView should
  689. // handle the mouse event consistently with ShelfView. To achieve this,
  690. // we simply redirect |event| to ShelfView.
  691. gfx::Point location_in_shelf_view = event->location();
  692. View::ConvertPointToTarget(this, shelf_view_, &location_in_shelf_view);
  693. event->set_location(location_in_shelf_view);
  694. shelf_view_->OnMouseEvent(event);
  695. }
  696. void ScrollableShelfView::OnGestureEvent(ui::GestureEvent* event) {
  697. if (ShouldHandleGestures(*event) && ProcessGestureEvent(*event)) {
  698. // |event| is consumed by ScrollableShelfView.
  699. event->SetHandled();
  700. } else if (shelf_view_->HandleGestureEvent(event)) {
  701. // |event| is consumed by ShelfView.
  702. event->StopPropagation();
  703. } else if (event->type() == ui::ET_GESTURE_SCROLL_BEGIN) {
  704. // |event| is consumed by neither ScrollableShelfView nor ShelfView. So the
  705. // gesture end event will not be propagated to this view. Then we need to
  706. // reset the class members related with scroll status explicitly.
  707. ResetScrollStatus();
  708. }
  709. }
  710. void ScrollableShelfView::GetAccessibleNodeData(ui::AXNodeData* node_data) {
  711. GetViewAccessibility().OverrideNextFocus(GetShelf()->GetStatusAreaWidget());
  712. GetViewAccessibility().OverridePreviousFocus(
  713. GetShelf()->shelf_widget()->navigation_widget());
  714. }
  715. void ScrollableShelfView::OnBoundsChanged(const gfx::Rect& previous_bounds) {
  716. if (ShouldApplyMaskLayerGradientZone() &&
  717. gradient_layer_delegate_->layer()->bounds() != layer()->bounds()) {
  718. gradient_layer_delegate_->layer()->SetBounds(layer()->bounds());
  719. }
  720. const gfx::Insets old_edge_padding_insets = edge_padding_insets_;
  721. const gfx::Vector2dF old_scroll_offset = scroll_offset_;
  722. // The changed view bounds may lead to update on the available space.
  723. UpdateAvailableSpaceAndScroll();
  724. // Relayout shelf items if the preferred padding changed.
  725. if (old_edge_padding_insets != edge_padding_insets_)
  726. shelf_view_->OnBoundsChanged(shelf_view_->GetBoundsInScreen());
  727. // Avoids calling AdjustOffset() when the scrollable shelf view is
  728. // under scroll along the main axis. Otherwise, animation will conflict with
  729. // scroll gesture. Meanwhile, translates the shelf view
  730. // if AdjustOffset() returns false since when AdjustOffset() returns true,
  731. // shelf view is scrolled by animation.
  732. const bool should_translate_shelf_view =
  733. scroll_status_ == kAlongMainAxisScroll || !AdjustOffset();
  734. if (should_translate_shelf_view && old_scroll_offset != scroll_offset_)
  735. shelf_container_view_->TranslateShelfView(scroll_offset_);
  736. }
  737. void ScrollableShelfView::ViewHierarchyChanged(
  738. const views::ViewHierarchyChangedDetails& details) {
  739. if (details.parent != shelf_view_)
  740. return;
  741. shelf_view_->UpdateShelfItemViewsVisibility();
  742. // When app scaling state needs update, hotseat bounds should change. Then
  743. // it is not meaningful to do further work in the current view bounds. So
  744. // returns early.
  745. if (GetShelf()->hotseat_widget()->UpdateTargetHotseatDensityIfNeeded())
  746. return;
  747. const gfx::Vector2dF old_scroll_offset = scroll_offset_;
  748. // Adding/removing an icon may change the padding then affect the available
  749. // space.
  750. UpdateAvailableSpaceAndScroll();
  751. if (old_scroll_offset != scroll_offset_)
  752. shelf_container_view_->TranslateShelfView(scroll_offset_);
  753. }
  754. void ScrollableShelfView::ScrollRectToVisible(const gfx::Rect& rect) {
  755. // Transform |rect| to local view coordinates taking |scroll_offset_| into
  756. // consideration.
  757. const bool is_horizontal_alignment = GetShelf()->IsHorizontalAlignment();
  758. gfx::Rect rect_after_adjustment = rect;
  759. if (is_horizontal_alignment)
  760. rect_after_adjustment.Offset(-scroll_offset_.x(), 0);
  761. else
  762. rect_after_adjustment.Offset(0, -scroll_offset_.y());
  763. // Notes that |rect| is not mirrored under RTL while |visible_space_| has been
  764. // mirrored. It is easier for coding if we mirror |visible_space_| back and
  765. // then do the calculation.
  766. const gfx::Rect visible_space_without_RTL = GetMirroredRect(visible_space_);
  767. // |rect_after_adjustment| is already shown completely. So scroll is not
  768. // needed.
  769. if (visible_space_without_RTL.Contains(rect_after_adjustment)) {
  770. AdjustOffset();
  771. return;
  772. }
  773. const float original_offset = CalculateMainAxisScrollDistance();
  774. // |forward| indicates the scroll direction.
  775. const bool forward =
  776. is_horizontal_alignment
  777. ? rect_after_adjustment.right() > visible_space_without_RTL.right()
  778. : rect_after_adjustment.bottom() > visible_space_without_RTL.bottom();
  779. // Scrolling |shelf_view_| has the following side-effects:
  780. // (1) May change the layout strategy.
  781. // (2) May change the visible space.
  782. // (3) Must change the scrolling offset.
  783. // (4) Must change |rect_after_adjustment|'s coordinates after adjusting the
  784. // scroll.
  785. LayoutStrategy layout_strategy_after_scroll = layout_strategy_;
  786. float main_axis_offset_after_scroll = original_offset;
  787. gfx::Rect visible_space_after_scroll = visible_space_without_RTL;
  788. gfx::Rect rect_after_scroll = rect_after_adjustment;
  789. // In each iteration, it scrolls |shelf_view_| to the neighboring page.
  790. // Terminating the loop iteration if:
  791. // (1) Find the suitable page which shows |rect| completely.
  792. // (2) Cannot scroll |shelf_view_| anymore (it may happen with ChromeVox
  793. // enabled).
  794. while (!visible_space_after_scroll.Contains(rect_after_scroll)) {
  795. int page_scroll_distance =
  796. CalculatePageScrollingOffset(forward, layout_strategy_after_scroll);
  797. // Breaking the while loop if it cannot scroll anymore.
  798. if (!page_scroll_distance)
  799. break;
  800. main_axis_offset_after_scroll = CalculateTargetOffsetAfterScroll(
  801. main_axis_offset_after_scroll, page_scroll_distance);
  802. layout_strategy_after_scroll = CalculateLayoutStrategy(
  803. main_axis_offset_after_scroll, GetSpaceForIcons());
  804. visible_space_after_scroll =
  805. GetMirroredRect(CalculateVisibleSpace(layout_strategy_after_scroll));
  806. rect_after_scroll = rect_after_adjustment;
  807. const int offset_delta = main_axis_offset_after_scroll - original_offset;
  808. if (is_horizontal_alignment)
  809. rect_after_scroll.Offset(-offset_delta, 0);
  810. else
  811. rect_after_scroll.Offset(0, -offset_delta);
  812. }
  813. if (!visible_space_after_scroll.Contains(rect_after_scroll))
  814. return;
  815. ScrollToMainOffset(main_axis_offset_after_scroll, /*animating=*/true);
  816. }
  817. std::unique_ptr<ui::Layer> ScrollableShelfView::RecreateLayer() {
  818. layer()->SetMaskLayer(nullptr);
  819. return views::View::RecreateLayer();
  820. }
  821. const char* ScrollableShelfView::GetClassName() const {
  822. return "ScrollableShelfView";
  823. }
  824. void ScrollableShelfView::OnShelfButtonAboutToRequestFocusFromTabTraversal(
  825. ShelfButton* button,
  826. bool reverse) {
  827. if ((button == left_arrow_) || (button == right_arrow_))
  828. return;
  829. shelf_view_->OnShelfButtonAboutToRequestFocusFromTabTraversal(button,
  830. reverse);
  831. ShelfWidget* shelf_widget = GetShelf()->shelf_widget();
  832. // In tablet mode, when the hotseat is not extended but one of the buttons
  833. // gets focused, it should update the visibility of the hotseat.
  834. if (Shell::Get()->IsInTabletMode() &&
  835. !shelf_widget->hotseat_widget()->IsExtended()) {
  836. shelf_widget->shelf_layout_manager()->UpdateVisibilityState();
  837. }
  838. }
  839. void ScrollableShelfView::ButtonPressed(views::Button* sender,
  840. const ui::Event& event,
  841. views::InkDrop* ink_drop) {
  842. if ((sender == left_arrow_) || (sender == right_arrow_)) {
  843. ScrollToNewPage(sender == right_arrow_);
  844. return;
  845. }
  846. shelf_view_->ButtonPressed(sender, event, ink_drop);
  847. }
  848. void ScrollableShelfView::HandleAccessibleActionScrollToMakeVisible(
  849. ShelfButton* button) {
  850. // Scrollable shelf can only be hidden in tablet mode.
  851. GetShelf()->hotseat_widget()->set_manually_extended(true);
  852. GetShelf()->shelf_widget()->shelf_layout_manager()->UpdateVisibilityState();
  853. }
  854. void ScrollableShelfView::OnButtonWillBeRemoved() {
  855. const int view_size_before_removal = shelf_view_->view_model()->view_size();
  856. DCHECK_GT(view_size_before_removal, 0);
  857. // Ensure `last_tappable_app_index_` to be valid after removal. Normally
  858. // `last_tappable_app_index_` updates when the shelf button is removed. But
  859. // button removal could be performed at the end of the button fade out
  860. // animation, which means that incorrect `last_tappable_app_index_` could be
  861. // accessed during the animation. To handle this issue, update
  862. // `last_tappable_app_index_` before removal finishes.
  863. // The code block also covers the edge case that the only shelf item is going
  864. // to be removed, i.e. `view_size_before_removal_` is one. In this case,
  865. // both `first_tappable_app_index_` and `last_tappable_app_index_` are reset
  866. // to invalid values (see https://crbug.com/1300561).
  867. if (view_size_before_removal < 2) {
  868. last_tappable_app_index_ = absl::nullopt;
  869. } else {
  870. last_tappable_app_index_ = std::min(
  871. last_tappable_app_index_,
  872. absl::make_optional(static_cast<size_t>(view_size_before_removal - 2)));
  873. }
  874. first_tappable_app_index_ =
  875. std::min(first_tappable_app_index_, last_tappable_app_index_);
  876. }
  877. void ScrollableShelfView::OnAppButtonActivated(const ShelfButton* button) {
  878. ScrollRectToVisible(button->bounds());
  879. }
  880. std::unique_ptr<ScrollableShelfView::ScopedActiveInkDropCount>
  881. ScrollableShelfView::CreateScopedActiveInkDropCount(const ShelfButton* sender) {
  882. if (!ShouldCountActivatedInkDrop(sender))
  883. return nullptr;
  884. return std::make_unique<ScopedActiveInkDropCountImpl>(this);
  885. }
  886. void ScrollableShelfView::ShowContextMenuForViewImpl(
  887. views::View* source,
  888. const gfx::Point& point,
  889. ui::MenuSourceType source_type) {
  890. // |point| is in screen coordinates. So it does not need to transform.
  891. shelf_view_->ShowContextMenuForViewImpl(shelf_view_, point, source_type);
  892. }
  893. void ScrollableShelfView::OnShelfAlignmentChanged(
  894. aura::Window* root_window,
  895. ShelfAlignment old_alignment) {
  896. const bool is_horizontal_alignment = GetShelf()->IsHorizontalAlignment();
  897. left_arrow_->set_is_horizontal_alignment(is_horizontal_alignment);
  898. right_arrow_->set_is_horizontal_alignment(is_horizontal_alignment);
  899. scroll_offset_ = gfx::Vector2dF();
  900. ScrollToMainOffset(CalculateMainAxisScrollDistance(), /*animating=*/false);
  901. Layout();
  902. }
  903. void ScrollableShelfView::OnShelfConfigUpdated() {
  904. UpdateAvailableSpaceAndScroll();
  905. shelf_view_->OnShelfConfigUpdated();
  906. }
  907. bool ScrollableShelfView::ShouldShowTooltipForView(
  908. const views::View* view) const {
  909. if (!view || !view->parent())
  910. return false;
  911. if (view == left_arrow_ || view == right_arrow_)
  912. return true;
  913. if (view->parent() != shelf_view_)
  914. return false;
  915. // The shelf item corresponding to |view| may have been removed from the
  916. // model.
  917. if (!shelf_view_->ShouldShowTooltipForChildView(view))
  918. return false;
  919. const gfx::Rect screen_bounds = view->GetBoundsInScreen();
  920. gfx::Rect visible_bounds_in_screen = visible_space_;
  921. views::View::ConvertRectToScreen(this, &visible_bounds_in_screen);
  922. return visible_bounds_in_screen.Contains(screen_bounds);
  923. }
  924. bool ScrollableShelfView::ShouldHideTooltip(
  925. const gfx::Point& cursor_location) const {
  926. if ((ShouldShowLeftArrow() &&
  927. left_arrow_->GetMirroredBounds().Contains(cursor_location)) ||
  928. (ShouldShowRightArrow() &&
  929. right_arrow_->GetMirroredBounds().Contains(cursor_location))) {
  930. return false;
  931. }
  932. // Should hide the tooltip if |cursor_location| is not in |visible_space_|.
  933. if (!visible_space_.Contains(cursor_location))
  934. return true;
  935. gfx::Point location_in_shelf_view = cursor_location;
  936. views::View::ConvertPointToTarget(this, shelf_view_, &location_in_shelf_view);
  937. return shelf_view_->ShouldHideTooltip(location_in_shelf_view);
  938. }
  939. const std::vector<aura::Window*> ScrollableShelfView::GetOpenWindowsForView(
  940. views::View* view) {
  941. if (!view || view->parent() != shelf_view_)
  942. return std::vector<aura::Window*>();
  943. return shelf_view_->GetOpenWindowsForView(view);
  944. }
  945. std::u16string ScrollableShelfView::GetTitleForView(
  946. const views::View* view) const {
  947. if (!view || !view->parent())
  948. return std::u16string();
  949. if (view->parent() == shelf_view_)
  950. return shelf_view_->GetTitleForView(view);
  951. if (view == left_arrow_)
  952. return l10n_util::GetStringUTF16(IDS_SHELF_PREVIOUS);
  953. if (view == right_arrow_)
  954. return l10n_util::GetStringUTF16(IDS_SHELF_NEXT);
  955. return std::u16string();
  956. }
  957. views::View* ScrollableShelfView::GetViewForEvent(const ui::Event& event) {
  958. if (event.target() == GetWidget()->GetNativeWindow())
  959. return this;
  960. return nullptr;
  961. }
  962. void ScrollableShelfView::ScheduleScrollForItemDragIfNeeded(
  963. const gfx::Rect& item_bounds_in_screen) {
  964. gfx::Rect visible_space_in_screen = visible_space_;
  965. views::View::ConvertRectToScreen(this, &visible_space_in_screen);
  966. drag_item_bounds_in_screen_.emplace(item_bounds_in_screen);
  967. if (AreBoundsWithinVisibleSpace(*drag_item_bounds_in_screen_)) {
  968. page_flip_timer_.AbandonAndStop();
  969. return;
  970. }
  971. if (!page_flip_timer_.IsRunning()) {
  972. page_flip_timer_.Start(FROM_HERE, page_flip_time_threshold_, this,
  973. &ScrollableShelfView::OnPageFlipTimer);
  974. }
  975. }
  976. void ScrollableShelfView::CancelScrollForItemDrag() {
  977. drag_item_bounds_in_screen_.reset();
  978. if (page_flip_timer_.IsRunning())
  979. page_flip_timer_.AbandonAndStop();
  980. }
  981. void ScrollableShelfView::OnImplicitAnimationsCompleted() {
  982. during_scroll_animation_ = false;
  983. Layout();
  984. EnableShelfRoundedCorners(/*enable=*/false);
  985. if (scroll_status_ != kAlongMainAxisScroll)
  986. UpdateTappableIconIndices();
  987. // Notifies ChromeVox of the changed location at the end of animation.
  988. shelf_view_->NotifyAccessibilityEvent(ax::mojom::Event::kLocationChanged,
  989. /*send_native_event=*/true);
  990. if (!drag_item_bounds_in_screen_ ||
  991. AreBoundsWithinVisibleSpace(*drag_item_bounds_in_screen_)) {
  992. return;
  993. }
  994. // Keep scrolling if the dragged shelf item is outside of the visible space.
  995. page_flip_timer_.Start(FROM_HERE, page_flip_time_threshold_, this,
  996. &ScrollableShelfView::OnPageFlipTimer);
  997. }
  998. bool ScrollableShelfView::ShouldShowLeftArrow() const {
  999. return (layout_strategy_ == kShowLeftArrowButton) ||
  1000. (layout_strategy_ == kShowButtons);
  1001. }
  1002. bool ScrollableShelfView::ShouldShowRightArrow() const {
  1003. return (layout_strategy_ == kShowRightArrowButton) ||
  1004. (layout_strategy_ == kShowButtons);
  1005. }
  1006. gfx::Rect ScrollableShelfView::GetAvailableLocalBounds(
  1007. bool use_target_bounds) const {
  1008. return use_target_bounds
  1009. ? gfx::Rect(GetShelf()->hotseat_widget()->GetTargetBounds().size())
  1010. : GetLocalBounds();
  1011. }
  1012. gfx::Insets ScrollableShelfView::CalculateMirroredPaddingForDisplayCentering(
  1013. bool use_target_bounds) const {
  1014. const int icons_size =
  1015. shelf_view_->GetSizeOfAppButtons(shelf_view_->number_of_visible_apps(),
  1016. shelf_view_->GetButtonSize()) +
  1017. 2 * ShelfConfig::Get()->GetAppIconEndPadding();
  1018. const gfx::Rect display_bounds =
  1019. screen_util::GetDisplayBoundsWithShelf(GetWidget()->GetNativeWindow());
  1020. const int display_size_primary = GetShelf()->PrimaryAxisValue(
  1021. display_bounds.width(), display_bounds.height());
  1022. const int gap = (display_size_primary - icons_size) / 2;
  1023. // Calculates paddings in view coordinates.
  1024. const gfx::Rect screen_bounds =
  1025. use_target_bounds ? GetShelf()->hotseat_widget()->GetTargetBounds()
  1026. : GetBoundsInScreen();
  1027. int before_padding =
  1028. gap - GetShelf()->PrimaryAxisValue(
  1029. ShouldAdaptToRTL()
  1030. ? display_bounds.right() - screen_bounds.right()
  1031. : screen_bounds.x() - display_bounds.x(),
  1032. screen_bounds.y() - display_bounds.y());
  1033. int after_padding =
  1034. gap - GetShelf()->PrimaryAxisValue(
  1035. ShouldAdaptToRTL()
  1036. ? screen_bounds.x() - display_bounds.x()
  1037. : display_bounds.right() - screen_bounds.right(),
  1038. display_bounds.bottom() - screen_bounds.bottom());
  1039. // Checks whether there is enough space to ensure |base_padding_|. Returns
  1040. // empty insets if not.
  1041. if (before_padding < 0 || after_padding < 0)
  1042. return gfx::Insets();
  1043. gfx::Insets padding_insets;
  1044. if (GetShelf()->IsHorizontalAlignment()) {
  1045. padding_insets = gfx::Insets::TLBR(0, before_padding, 0, after_padding);
  1046. if (ShouldAdaptToRTL())
  1047. padding_insets = GetMirroredInsets(padding_insets);
  1048. } else {
  1049. padding_insets = gfx::Insets::TLBR(before_padding, 0, after_padding, 0);
  1050. }
  1051. return padding_insets;
  1052. }
  1053. bool ScrollableShelfView::ShouldHandleGestures(const ui::GestureEvent& event) {
  1054. // ScrollableShelfView only handles the gesture scrolling along the main axis.
  1055. // For other gesture events, including the scrolling across the main axis,
  1056. // they are handled by ShelfView.
  1057. if (scroll_status_ == kNotInScroll && !event.IsScrollGestureEvent())
  1058. return false;
  1059. if (event.type() == ui::ET_GESTURE_SCROLL_BEGIN) {
  1060. CHECK_EQ(scroll_status_, kNotInScroll);
  1061. float main_offset = event.details().scroll_x_hint();
  1062. float cross_offset = event.details().scroll_y_hint();
  1063. if (!GetShelf()->IsHorizontalAlignment())
  1064. std::swap(main_offset, cross_offset);
  1065. if (std::abs(main_offset) < std::abs(cross_offset)) {
  1066. scroll_status_ = kAcrossMainAxisScroll;
  1067. } else if (layout_strategy_ != kNotShowArrowButtons) {
  1068. // Note that if the scrollable shelf is not in overflow mode, scroll along
  1069. // the main axis should not make any UI differences. Do not handle scroll
  1070. // in this scenario.
  1071. scroll_status_ = kAlongMainAxisScroll;
  1072. }
  1073. }
  1074. bool should_handle_gestures = scroll_status_ == kAlongMainAxisScroll;
  1075. if (scroll_status_ == kAlongMainAxisScroll &&
  1076. event.type() == ui::ET_GESTURE_SCROLL_BEGIN) {
  1077. scroll_offset_before_main_axis_scrolling_ = scroll_offset_;
  1078. layout_strategy_before_main_axis_scrolling_ = layout_strategy_;
  1079. // The change in |scroll_status_| may lead to update on the gradient zone.
  1080. MaybeUpdateGradientZone();
  1081. }
  1082. if (event.type() == ui::ET_GESTURE_END)
  1083. ResetScrollStatus();
  1084. return should_handle_gestures;
  1085. }
  1086. void ScrollableShelfView::ResetScrollStatus() {
  1087. scroll_status_ = kNotInScroll;
  1088. scroll_offset_before_main_axis_scrolling_ = gfx::Vector2dF();
  1089. layout_strategy_before_main_axis_scrolling_ = kNotShowArrowButtons;
  1090. // The change in |scroll_status_| may lead to update on the gradient zone.
  1091. MaybeUpdateGradientZone();
  1092. }
  1093. bool ScrollableShelfView::ProcessGestureEvent(const ui::GestureEvent& event) {
  1094. if (layout_strategy_ == kNotShowArrowButtons)
  1095. return true;
  1096. // Handle scroll-related events, but don't do anything special for begin and
  1097. // end.
  1098. if (event.type() == ui::ET_GESTURE_SCROLL_BEGIN) {
  1099. DCHECK(!presentation_time_recorder_);
  1100. if (Shell::Get()->IsInTabletMode()) {
  1101. if (Shell::Get()->app_list_controller()->IsVisible(
  1102. GetDisplayIdForView(this))) {
  1103. presentation_time_recorder_ = CreatePresentationTimeHistogramRecorder(
  1104. GetWidget()->GetCompositor(),
  1105. scrollable_shelf_constants::
  1106. kScrollDraggingTabletLauncherVisibleHistogram,
  1107. scrollable_shelf_constants::
  1108. kScrollDraggingTabletLauncherVisibleMaxLatencyHistogram);
  1109. } else {
  1110. presentation_time_recorder_ = CreatePresentationTimeHistogramRecorder(
  1111. GetWidget()->GetCompositor(),
  1112. scrollable_shelf_constants::
  1113. kScrollDraggingTabletLauncherHiddenHistogram,
  1114. scrollable_shelf_constants::
  1115. kScrollDraggingTabletLauncherHiddenMaxLatencyHistogram);
  1116. }
  1117. } else {
  1118. if (Shell::Get()->app_list_controller()->IsVisible(
  1119. GetDisplayIdForView(this))) {
  1120. presentation_time_recorder_ = CreatePresentationTimeHistogramRecorder(
  1121. GetWidget()->GetCompositor(),
  1122. scrollable_shelf_constants::
  1123. kScrollDraggingClamshellLauncherVisibleHistogram,
  1124. scrollable_shelf_constants::
  1125. kScrollDraggingClamshellLauncherVisibleMaxLatencyHistogram);
  1126. } else {
  1127. presentation_time_recorder_ = CreatePresentationTimeHistogramRecorder(
  1128. GetWidget()->GetCompositor(),
  1129. scrollable_shelf_constants::
  1130. kScrollDraggingClamshellLauncherHiddenHistogram,
  1131. scrollable_shelf_constants::
  1132. kScrollDraggingClamshellLauncherHiddenMaxLatencyHistogram);
  1133. }
  1134. }
  1135. return true;
  1136. }
  1137. if (event.type() == ui::ET_GESTURE_END) {
  1138. // Do not reset |presentation_time_recorder_| in ui::ET_GESTURE_SCROLL_END
  1139. // event because it may not exist due to gesture fling.
  1140. presentation_time_recorder_.reset();
  1141. // The type of scrolling offset is float to ensure that ScrollableShelfView
  1142. // is responsive to slow gesture scrolling. However, after offset
  1143. // adjustment, the scrolling offset should be floored.
  1144. scroll_offset_ = gfx::ToFlooredVector2d(scroll_offset_);
  1145. // If the scroll animation is created, tappable icon indices are updated
  1146. // at the end of animation.
  1147. if (!AdjustOffset() && !during_scroll_animation_)
  1148. UpdateTappableIconIndices();
  1149. return true;
  1150. }
  1151. if (event.type() == ui::ET_SCROLL_FLING_START) {
  1152. const bool is_horizontal_alignment = GetShelf()->IsHorizontalAlignment();
  1153. if (!ShouldHandleScroll(gfx::Vector2dF(event.details().velocity_x(),
  1154. event.details().velocity_y()),
  1155. /*is_gesture_fling=*/true)) {
  1156. return false;
  1157. }
  1158. int scroll_velocity = is_horizontal_alignment
  1159. ? event.details().velocity_x()
  1160. : event.details().velocity_y();
  1161. if (ShouldAdaptToRTL())
  1162. scroll_velocity = -scroll_velocity;
  1163. float page_scrolling_offset = CalculatePageScrollingOffset(
  1164. scroll_velocity < 0, layout_strategy_before_main_axis_scrolling_);
  1165. // Only starts animation when scroll distance is greater than zero.
  1166. if (std::fabs(page_scrolling_offset) > 0.f) {
  1167. ScrollToMainOffset((is_horizontal_alignment
  1168. ? scroll_offset_before_main_axis_scrolling_.x()
  1169. : scroll_offset_before_main_axis_scrolling_.y()) +
  1170. page_scrolling_offset,
  1171. /*animating=*/true);
  1172. }
  1173. return true;
  1174. }
  1175. if (event.type() != ui::ET_GESTURE_SCROLL_UPDATE)
  1176. return false;
  1177. float scroll_delta = 0.f;
  1178. const bool is_horizontal = GetShelf()->IsHorizontalAlignment();
  1179. if (is_horizontal) {
  1180. scroll_delta = -event.details().scroll_x();
  1181. scroll_delta = ShouldAdaptToRTL() ? -scroll_delta : scroll_delta;
  1182. } else {
  1183. scroll_delta = -event.details().scroll_y();
  1184. }
  1185. // Return early if scrollable shelf cannot be scrolled anymore because it has
  1186. // reached to the end.
  1187. const float current_scroll_offset = CalculateMainAxisScrollDistance();
  1188. if ((current_scroll_offset == 0.f && scroll_delta <= 0.f) ||
  1189. (current_scroll_offset == CalculateScrollUpperBound(GetSpaceForIcons()) &&
  1190. scroll_delta >= 0.f)) {
  1191. return true;
  1192. }
  1193. DCHECK(presentation_time_recorder_);
  1194. presentation_time_recorder_->RequestNext();
  1195. if (is_horizontal)
  1196. ScrollByXOffset(scroll_delta, /*animate=*/false);
  1197. else
  1198. ScrollByYOffset(scroll_delta, /*animate=*/false);
  1199. return true;
  1200. }
  1201. void ScrollableShelfView::HandleMouseWheelEvent(ui::MouseWheelEvent* event) {
  1202. // Note that the scrolling from touchpad is propagated as mouse wheel event.
  1203. // Let the shelf handle mouse wheel events over the empty area of the shelf
  1204. // view, as these events would be ignored by the scrollable shelf view.
  1205. gfx::Point location_in_shelf_view = event->location();
  1206. View::ConvertPointToTarget(this, shelf_view_, &location_in_shelf_view);
  1207. if (!shelf_view_->LocationInsideVisibleShelfItemBounds(
  1208. location_in_shelf_view)) {
  1209. GetShelf()->ProcessMouseWheelEvent(event);
  1210. return;
  1211. }
  1212. if (!ShouldHandleScroll(gfx::Vector2dF(event->x_offset(), event->y_offset()),
  1213. /*is_gesture_fling=*/false)) {
  1214. return;
  1215. }
  1216. event->SetHandled();
  1217. // Scrolling the mouse wheel may create multiple mouse wheel events at the
  1218. // same time. If the scrollable shelf view is during scrolling animation at
  1219. // this moment, do not handle the mouse wheel event.
  1220. if (shelf_view_->layer()->GetAnimator()->is_animating())
  1221. return;
  1222. if (GetShelf()->IsHorizontalAlignment()) {
  1223. const float x_offset = event->x_offset();
  1224. const float y_offset = event->y_offset();
  1225. // If the shelf is bottom aligned, we can scroll over the shelf contents if
  1226. // the scroll is horizontal or vertical (in the case of a mousewheel
  1227. // scroll). We take the biggest offset difference of the vertical and
  1228. // horizontal components to determine the offset to scroll over the
  1229. // contents.
  1230. float max_absolute_offset =
  1231. abs(x_offset) > abs(y_offset) ? x_offset : y_offset;
  1232. ScrollByXOffset(
  1233. CalculatePageScrollingOffset(max_absolute_offset < 0, layout_strategy_),
  1234. /*animating=*/true);
  1235. } else {
  1236. ScrollByYOffset(
  1237. CalculatePageScrollingOffset(event->y_offset() < 0, layout_strategy_),
  1238. /*animating=*/true);
  1239. }
  1240. }
  1241. void ScrollableShelfView::ScrollByXOffset(float x_offset, bool animating) {
  1242. ScrollToMainOffset(scroll_offset_.x() + x_offset, animating);
  1243. }
  1244. void ScrollableShelfView::ScrollByYOffset(float y_offset, bool animating) {
  1245. ScrollToMainOffset(scroll_offset_.y() + y_offset, animating);
  1246. }
  1247. void ScrollableShelfView::ScrollToMainOffset(float target_offset,
  1248. bool animating) {
  1249. if (animating) {
  1250. StartShelfScrollAnimation(target_offset);
  1251. } else {
  1252. UpdateScrollOffset(target_offset);
  1253. shelf_container_view_->TranslateShelfView(scroll_offset_);
  1254. }
  1255. }
  1256. float ScrollableShelfView::CalculatePageScrollingOffset(
  1257. bool forward,
  1258. LayoutStrategy layout_strategy) const {
  1259. // Returns zero if inputs are invalid.
  1260. const bool invalid = (layout_strategy == kNotShowArrowButtons) ||
  1261. (layout_strategy == kShowLeftArrowButton && forward) ||
  1262. (layout_strategy == kShowRightArrowButton && !forward);
  1263. if (invalid)
  1264. return 0;
  1265. float offset = CalculatePageScrollingOffsetInAbs(layout_strategy);
  1266. if (!forward)
  1267. offset = -offset;
  1268. return offset;
  1269. }
  1270. float ScrollableShelfView::CalculatePageScrollingOffsetInAbs(
  1271. LayoutStrategy layout_strategy) const {
  1272. // Implement the arrow button handler in the same way with the gesture
  1273. // scrolling. The key is to calculate the suitable scroll distance.
  1274. float offset = 0.f;
  1275. // The available space for icons excluding the area taken by arrow button(s).
  1276. int space_excluding_arrow;
  1277. const int space_needed_for_button = GetSumOfButtonSizeAndSpacing();
  1278. if (layout_strategy == kShowRightArrowButton) {
  1279. space_excluding_arrow =
  1280. GetSpaceForIcons() - scrollable_shelf_constants::kArrowButtonGroupWidth;
  1281. // After scrolling, the left arrow button will show. Adapts the offset
  1282. // to the extra arrow button.
  1283. const int offset_for_extra_arrow =
  1284. scrollable_shelf_constants::kArrowButtonGroupWidth -
  1285. ShelfConfig::Get()->GetAppIconEndPadding();
  1286. const int mod = space_excluding_arrow % space_needed_for_button;
  1287. offset = space_excluding_arrow - mod - offset_for_extra_arrow;
  1288. } else if (layout_strategy == kShowButtons ||
  1289. layout_strategy == kShowLeftArrowButton) {
  1290. space_excluding_arrow =
  1291. GetSpaceForIcons() -
  1292. 2 * scrollable_shelf_constants::kArrowButtonGroupWidth;
  1293. const int mod = space_excluding_arrow % space_needed_for_button;
  1294. offset = space_excluding_arrow - mod;
  1295. // Layout of kShowLeftArrowButton can be regarded as the layout of
  1296. // kShowButtons with extra offset.
  1297. if (layout_strategy == kShowLeftArrowButton) {
  1298. const int extra_offset =
  1299. -ShelfConfig::Get()->button_spacing() -
  1300. (GetSpaceForIcons() -
  1301. scrollable_shelf_constants::kArrowButtonGroupWidth) %
  1302. space_needed_for_button +
  1303. ShelfConfig::Get()->GetAppIconEndPadding();
  1304. offset += extra_offset;
  1305. }
  1306. }
  1307. // Ensure the return value to be non-negative. Note that if the screen is too
  1308. // small (usually on the Linux emulator), `offset` may be negative.
  1309. return std::fmax(offset, 0.f);
  1310. }
  1311. float ScrollableShelfView::CalculateTargetOffsetAfterScroll(
  1312. float start_offset,
  1313. float scroll_distance) const {
  1314. float target_offset = start_offset;
  1315. target_offset += scroll_distance;
  1316. target_offset =
  1317. CalculateClampedScrollOffset(target_offset, GetSpaceForIcons());
  1318. LayoutStrategy layout_strategy_after_scroll =
  1319. CalculateLayoutStrategy(target_offset, GetSpaceForIcons());
  1320. target_offset = CalculateScrollDistanceAfterAdjustment(
  1321. target_offset, layout_strategy_after_scroll);
  1322. return target_offset;
  1323. }
  1324. GradientLayerDelegate::FadeZone
  1325. ScrollableShelfView::CalculateStartGradientZone() const {
  1326. if (!should_show_start_gradient_zone_)
  1327. return GradientLayerDelegate::FadeZone();
  1328. gfx::Rect zone_rect;
  1329. bool fade_in = false;
  1330. const bool is_horizontal_alignment = GetShelf()->IsHorizontalAlignment();
  1331. if (is_horizontal_alignment) {
  1332. int gradient_start;
  1333. int gradient_end;
  1334. // Calculates the bounds of the gradient zone. Enlarge the gradient zone by
  1335. // one-pixel to offset the potential rounding error during rendering (we
  1336. // also do it in CalculateEndGradientZone()).
  1337. if (ShouldAdaptToRTL()) {
  1338. const int border = visible_space_.right();
  1339. gradient_start = border - scrollable_shelf_constants::kGradientZoneLength;
  1340. gradient_end = border + 1;
  1341. } else {
  1342. const int border = visible_space_.x();
  1343. gradient_start = border - 1;
  1344. gradient_end = border + scrollable_shelf_constants::kGradientZoneLength;
  1345. }
  1346. zone_rect =
  1347. gfx::Rect(gradient_start, 0, gradient_end - gradient_start, height());
  1348. } else {
  1349. zone_rect = gfx::Rect(0, visible_space_.y() - 1, width(),
  1350. scrollable_shelf_constants::kGradientZoneLength + 1);
  1351. }
  1352. fade_in = !ShouldAdaptToRTL();
  1353. return {zone_rect, fade_in, is_horizontal_alignment};
  1354. }
  1355. GradientLayerDelegate::FadeZone ScrollableShelfView::CalculateEndGradientZone()
  1356. const {
  1357. if (!should_show_end_gradient_zone_)
  1358. return GradientLayerDelegate::FadeZone();
  1359. gfx::Rect zone_rect;
  1360. bool fade_in = false;
  1361. const bool is_horizontal_alignment = GetShelf()->IsHorizontalAlignment();
  1362. if (is_horizontal_alignment) {
  1363. int gradient_start;
  1364. int gradient_end;
  1365. if (ShouldAdaptToRTL()) {
  1366. const int border = visible_space_.x();
  1367. gradient_start = border - 1;
  1368. gradient_end = border + scrollable_shelf_constants::kGradientZoneLength;
  1369. } else {
  1370. const int border = visible_space_.right();
  1371. gradient_start = border - scrollable_shelf_constants::kGradientZoneLength;
  1372. gradient_end = border + 1;
  1373. }
  1374. zone_rect =
  1375. gfx::Rect(gradient_start, 0, gradient_end - gradient_start, height());
  1376. } else {
  1377. zone_rect =
  1378. gfx::Rect(0,
  1379. visible_space_.bottom() -
  1380. scrollable_shelf_constants::kGradientZoneLength,
  1381. width(), scrollable_shelf_constants::kGradientZoneLength + 1);
  1382. }
  1383. fade_in = ShouldAdaptToRTL();
  1384. return {zone_rect, fade_in, is_horizontal_alignment};
  1385. }
  1386. void ScrollableShelfView::UpdateGradientZoneState() {
  1387. // The gradient zone is not painted when the focus ring shows in order to
  1388. // display the focus ring correctly.
  1389. if (focus_ring_activated_) {
  1390. should_show_start_gradient_zone_ = false;
  1391. should_show_end_gradient_zone_ = false;
  1392. return;
  1393. }
  1394. if (during_scroll_animation_) {
  1395. should_show_start_gradient_zone_ = true;
  1396. should_show_end_gradient_zone_ = true;
  1397. return;
  1398. }
  1399. should_show_start_gradient_zone_ = layout_strategy_ == kShowLeftArrowButton ||
  1400. (layout_strategy_ == kShowButtons &&
  1401. scroll_status_ == kAlongMainAxisScroll);
  1402. should_show_end_gradient_zone_ = ShouldShowRightArrow();
  1403. }
  1404. void ScrollableShelfView::MaybeUpdateGradientZone() {
  1405. if (!ShouldApplyMaskLayerGradientZone())
  1406. return;
  1407. // Fade zones should be updated if:
  1408. // (1) Fade zone's visibility changes.
  1409. // (2) Fade zone should show and the arrow button's location changes.
  1410. UpdateGradientZoneState();
  1411. const GradientLayerDelegate::FadeZone target_start_fade_zone =
  1412. CalculateStartGradientZone();
  1413. const GradientLayerDelegate::FadeZone target_end_fade_zone =
  1414. CalculateEndGradientZone();
  1415. const bool should_update_start_fade_zone =
  1416. target_start_fade_zone.zone_rect !=
  1417. gradient_layer_delegate_->start_fade_zone_bounds();
  1418. const bool should_update_end_fade_zone =
  1419. target_end_fade_zone.zone_rect !=
  1420. gradient_layer_delegate_->end_fade_zone_bounds();
  1421. if (!should_update_start_fade_zone && !should_update_end_fade_zone)
  1422. return;
  1423. PaintGradientZone(CalculateStartGradientZone(), CalculateEndGradientZone());
  1424. }
  1425. void ScrollableShelfView::PaintGradientZone(
  1426. const GradientLayerDelegate::FadeZone& start_rect,
  1427. const GradientLayerDelegate::FadeZone& end_rect) {
  1428. gradient_layer_delegate_->set_start_fade_zone(start_rect);
  1429. gradient_layer_delegate_->set_end_fade_zone(end_rect);
  1430. SchedulePaint();
  1431. }
  1432. bool ScrollableShelfView::ShouldApplyMaskLayerGradientZone() const {
  1433. return layout_strategy_ != LayoutStrategy::kNotShowArrowButtons;
  1434. }
  1435. float ScrollableShelfView::GetActualScrollOffset(
  1436. float main_axis_scroll_distance,
  1437. LayoutStrategy layout_strategy) const {
  1438. return (layout_strategy == kShowButtons ||
  1439. layout_strategy == kShowLeftArrowButton)
  1440. ? (main_axis_scroll_distance +
  1441. scrollable_shelf_constants::kArrowButtonGroupWidth -
  1442. ShelfConfig::Get()->GetAppIconEndPadding())
  1443. : main_axis_scroll_distance;
  1444. }
  1445. void ScrollableShelfView::UpdateTappableIconIndices() {
  1446. // Scrollable shelf should be not under the scroll along the main axis, which
  1447. // means that the decimal part of the main scroll offset should be zero.
  1448. DCHECK(scroll_status_ != kAlongMainAxisScroll);
  1449. // The value returned by CalculateMainAxisScrollDistance() can be casted into
  1450. // an integer without losing precision since the decimal part is zero.
  1451. const auto tappable_indices = CalculateTappableIconIndices(
  1452. layout_strategy_, CalculateMainAxisScrollDistance());
  1453. first_tappable_app_index_ = tappable_indices.first;
  1454. last_tappable_app_index_ = tappable_indices.second;
  1455. }
  1456. std::pair<absl::optional<size_t>, absl::optional<size_t>>
  1457. ScrollableShelfView::CalculateTappableIconIndices(
  1458. ScrollableShelfView::LayoutStrategy layout_strategy,
  1459. int scroll_distance_on_main_axis) const {
  1460. const auto& visible_views_indices = shelf_view_->visible_views_indices();
  1461. if (visible_views_indices.empty() || visible_space_.IsEmpty())
  1462. return {absl::nullopt, absl::nullopt};
  1463. if (layout_strategy == ScrollableShelfView::kNotShowArrowButtons) {
  1464. return {visible_views_indices.front(), visible_views_indices.back()};
  1465. }
  1466. const int visible_size = GetShelf()->IsHorizontalAlignment()
  1467. ? visible_space_.width()
  1468. : visible_space_.height();
  1469. const int space_needed_for_button = GetSumOfButtonSizeAndSpacing();
  1470. // Note that some apps may have their |ShelfAppButton| views hidden, when they
  1471. // are on an inactive desk. Therefore, the indices of tappable apps may not be
  1472. // contiguous, so we need to map from a visible view index back to an app
  1473. // index. The below are indices into the |visible_views_indices| vector.
  1474. size_t first_visible_view_index;
  1475. size_t last_visible_view_index;
  1476. if (layout_strategy == kShowRightArrowButton ||
  1477. layout_strategy == kShowButtons) {
  1478. first_visible_view_index =
  1479. scroll_distance_on_main_axis / space_needed_for_button +
  1480. (layout_strategy == kShowButtons ? 1 : 0);
  1481. last_visible_view_index =
  1482. first_visible_view_index + visible_size / space_needed_for_button;
  1483. const int end_of_last_visible_view =
  1484. last_visible_view_index * space_needed_for_button +
  1485. shelf_view_->GetButtonSize() - scroll_distance_on_main_axis;
  1486. // It is very rare but |visible_size| may be smaller than
  1487. // |space_needed_for_button| as reported in https://crbug.com/1094363.
  1488. if (end_of_last_visible_view > visible_size &&
  1489. last_visible_view_index > first_visible_view_index) {
  1490. last_visible_view_index--;
  1491. }
  1492. } else {
  1493. DCHECK_EQ(layout_strategy, kShowLeftArrowButton);
  1494. last_visible_view_index = visible_views_indices.size() - 1;
  1495. // In fuzz tests, `visible_size` may be smaller than
  1496. // `space_needed_for_button` although it never happens on real devices.
  1497. first_visible_view_index =
  1498. visible_size >= space_needed_for_button
  1499. ? last_visible_view_index - visible_size / space_needed_for_button +
  1500. 1
  1501. : last_visible_view_index;
  1502. }
  1503. DCHECK_LT(first_visible_view_index, visible_views_indices.size());
  1504. DCHECK_LT(last_visible_view_index, visible_views_indices.size());
  1505. return {visible_views_indices[first_visible_view_index],
  1506. visible_views_indices[last_visible_view_index]};
  1507. }
  1508. views::View* ScrollableShelfView::FindFirstFocusableChild() {
  1509. return shelf_view_->FindFirstFocusableChild();
  1510. }
  1511. views::View* ScrollableShelfView::FindLastFocusableChild() {
  1512. return shelf_view_->FindLastFocusableChild();
  1513. }
  1514. int ScrollableShelfView::GetSpaceForIcons() const {
  1515. return GetShelf()->IsHorizontalAlignment() ? available_space_.width()
  1516. : available_space_.height();
  1517. }
  1518. bool ScrollableShelfView::CanFitAllAppsWithoutScrolling(
  1519. const gfx::Size& available_size,
  1520. const gfx::Size& icons_preferred_size) const {
  1521. const int available_length =
  1522. (GetShelf()->IsHorizontalAlignment() ? available_size.width()
  1523. : available_size.height());
  1524. int preferred_length = GetShelf()->IsHorizontalAlignment()
  1525. ? icons_preferred_size.width()
  1526. : icons_preferred_size.height();
  1527. preferred_length += 2 * ShelfConfig::Get()->GetAppIconEndPadding();
  1528. return available_length >= preferred_length;
  1529. }
  1530. bool ScrollableShelfView::ShouldHandleScroll(const gfx::Vector2dF& offset,
  1531. bool is_gesture_scrolling) const {
  1532. // When the shelf is aligned at the bottom, a horizontal mousewheel scroll may
  1533. // also be handled by the ScrollableShelf if the offset along the main axis is
  1534. // 0. This case is mainly triggered by an event generated in the MouseWheel,
  1535. // but not in the touchpad, as touchpads events are caught on ScrollEvent.
  1536. // If there is an x component to the scroll, consider this instead of the y
  1537. // axis because the horizontal scroll could move the scrollable shelf.
  1538. const float main_axis_offset =
  1539. GetShelf()->IsHorizontalAlignment() && offset.x() != 0 ? offset.x()
  1540. : offset.y();
  1541. const int threshold =
  1542. is_gesture_scrolling
  1543. ? scrollable_shelf_constants::kGestureFlingVelocityThreshold
  1544. : scrollable_shelf_constants::kScrollOffsetThreshold;
  1545. return abs(main_axis_offset) > threshold;
  1546. }
  1547. bool ScrollableShelfView::AdjustOffset() {
  1548. const float offset = CalculateAdjustmentOffset(
  1549. CalculateMainAxisScrollDistance(), layout_strategy_, GetSpaceForIcons());
  1550. // Returns early when it does not need to adjust the shelf view's location.
  1551. if (!offset)
  1552. return false;
  1553. if (GetShelf()->IsHorizontalAlignment())
  1554. ScrollByXOffset(offset, /*animate=*/true);
  1555. else
  1556. ScrollByYOffset(offset, /*animate=*/true);
  1557. return true;
  1558. }
  1559. float ScrollableShelfView::CalculateAdjustmentOffset(
  1560. int main_axis_scroll_distance,
  1561. LayoutStrategy layout_strategy,
  1562. int available_space_for_icons) const {
  1563. // Scrollable shelf should be not under the scroll along the main axis, which
  1564. // means that the decimal part of the main scroll offset should be zero.
  1565. DCHECK(scroll_status_ != kAlongMainAxisScroll);
  1566. // Returns early when it does not need to adjust the shelf view's location.
  1567. if (layout_strategy == kNotShowArrowButtons ||
  1568. main_axis_scroll_distance >=
  1569. CalculateScrollUpperBound(available_space_for_icons)) {
  1570. return 0;
  1571. }
  1572. // Because the decimal part of the scroll offset is zero, it is meaningful
  1573. // to use modulo operation here.
  1574. const int remainder = static_cast<int>(GetActualScrollOffset(
  1575. main_axis_scroll_distance, layout_strategy)) %
  1576. GetSumOfButtonSizeAndSpacing();
  1577. int offset = remainder > GetGestureDragThreshold()
  1578. ? GetSumOfButtonSizeAndSpacing() - remainder
  1579. : -remainder;
  1580. return offset;
  1581. }
  1582. int ScrollableShelfView::CalculateScrollDistanceAfterAdjustment(
  1583. int main_axis_scroll_distance,
  1584. LayoutStrategy layout_strategy) const {
  1585. return main_axis_scroll_distance +
  1586. CalculateAdjustmentOffset(main_axis_scroll_distance, layout_strategy,
  1587. GetSpaceForIcons());
  1588. }
  1589. void ScrollableShelfView::UpdateAvailableSpace() {
  1590. if (!is_padding_configured_externally_) {
  1591. edge_padding_insets_ =
  1592. CalculateMirroredEdgePadding(/*use_target_bounds=*/false);
  1593. }
  1594. available_space_ = GetLocalBounds();
  1595. available_space_.Inset(edge_padding_insets_);
  1596. // The hotseat uses |available_space_| to determine where to show its
  1597. // background, so notify it when it is recalculated.
  1598. if (HotseatWidget::ShouldShowHotseatBackground())
  1599. GetShelf()->hotseat_widget()->UpdateTranslucentBackground();
  1600. }
  1601. gfx::Rect ScrollableShelfView::CalculateVisibleSpace(
  1602. LayoutStrategy layout_strategy) const {
  1603. const bool in_tablet_mode = Shell::Get()->IsInTabletMode();
  1604. if (layout_strategy == kNotShowArrowButtons && !in_tablet_mode)
  1605. return GetAvailableLocalBounds(/*use_target_bounds=*/false);
  1606. const bool should_show_left_arrow =
  1607. (layout_strategy == kShowLeftArrowButton) ||
  1608. (layout_strategy == kShowButtons);
  1609. const bool should_show_right_arrow =
  1610. (layout_strategy == kShowRightArrowButton) ||
  1611. (layout_strategy == kShowButtons);
  1612. const int before_padding =
  1613. (should_show_left_arrow
  1614. ? scrollable_shelf_constants::kArrowButtonGroupWidth
  1615. : 0);
  1616. const int after_padding =
  1617. (should_show_right_arrow
  1618. ? scrollable_shelf_constants::kArrowButtonGroupWidth
  1619. : 0);
  1620. gfx::Insets visible_space_insets;
  1621. if (ShouldAdaptToRTL()) {
  1622. visible_space_insets =
  1623. gfx::Insets::TLBR(0, after_padding, 0, before_padding);
  1624. } else {
  1625. visible_space_insets =
  1626. GetShelf()->IsHorizontalAlignment()
  1627. ? gfx::Insets::TLBR(0, before_padding, 0, after_padding)
  1628. : gfx::Insets::TLBR(before_padding, 0, after_padding, 0);
  1629. }
  1630. visible_space_insets -= CalculateRipplePaddingInsets();
  1631. gfx::Rect visible_space = available_space_;
  1632. visible_space.Inset(visible_space_insets);
  1633. return visible_space;
  1634. }
  1635. gfx::Insets ScrollableShelfView::CalculateRipplePaddingInsets() const {
  1636. // Indicates whether it is in tablet mode with hotseat enabled.
  1637. const bool in_tablet_mode = Shell::Get()->IsInTabletMode();
  1638. const int ripple_padding =
  1639. ShelfConfig::Get()->scrollable_shelf_ripple_padding();
  1640. const int before_padding =
  1641. (in_tablet_mode && !ShouldShowLeftArrow()) ? 0 : ripple_padding;
  1642. const int after_padding =
  1643. (in_tablet_mode && !ShouldShowRightArrow()) ? 0 : ripple_padding;
  1644. if (ShouldAdaptToRTL())
  1645. return gfx::Insets::TLBR(0, after_padding, 0, before_padding);
  1646. return GetShelf()->IsHorizontalAlignment()
  1647. ? gfx::Insets::TLBR(0, before_padding, 0, after_padding)
  1648. : gfx::Insets::TLBR(before_padding, 0, after_padding, 0);
  1649. }
  1650. gfx::RoundedCornersF
  1651. ScrollableShelfView::CalculateShelfContainerRoundedCorners() const {
  1652. // This function may access TabletModeController during destruction of
  1653. // Hotseat. However, TabletModeController is destructed before Hotseat. So
  1654. // check the pointer explicitly here.
  1655. // TODO(https://crbug.com/1067490): reorder the destruction order in
  1656. // Shell::~Shell then remove the explicit check.
  1657. const bool is_in_tablet_mode =
  1658. Shell::Get()->tablet_mode_controller() && Shell::Get()->IsInTabletMode();
  1659. if (!is_in_tablet_mode)
  1660. return gfx::RoundedCornersF();
  1661. const bool is_horizontal_alignment = GetShelf()->IsHorizontalAlignment();
  1662. const float radius = (is_horizontal_alignment ? height() : width()) / 2.f;
  1663. int upper_left = ShouldShowLeftArrow() ? 0 : radius;
  1664. int upper_right;
  1665. if (is_horizontal_alignment)
  1666. upper_right = ShouldShowRightArrow() ? 0 : radius;
  1667. else
  1668. upper_right = ShouldShowLeftArrow() ? 0 : radius;
  1669. int lower_right = ShouldShowRightArrow() ? 0 : radius;
  1670. int lower_left;
  1671. if (is_horizontal_alignment)
  1672. lower_left = ShouldShowLeftArrow() ? 0 : radius;
  1673. else
  1674. lower_left = ShouldShowRightArrow() ? 0 : radius;
  1675. if (ShouldAdaptToRTL()) {
  1676. std::swap(upper_left, upper_right);
  1677. std::swap(lower_left, lower_right);
  1678. }
  1679. return gfx::RoundedCornersF(upper_left, upper_right, lower_right, lower_left);
  1680. }
  1681. void ScrollableShelfView::OnPageFlipTimer() {
  1682. gfx::Rect visible_space_in_screen = visible_space_;
  1683. views::View::ConvertRectToScreen(this, &visible_space_in_screen);
  1684. // Calculates the page scrolling direction based on the drag item bounds and
  1685. // the bounds of the visible space.
  1686. bool should_scroll_to_next;
  1687. if (ShouldAdaptToRTL()) {
  1688. should_scroll_to_next =
  1689. drag_item_bounds_in_screen_->x() < visible_space_in_screen.x();
  1690. } else {
  1691. should_scroll_to_next = GetShelf()->IsHorizontalAlignment()
  1692. ? drag_item_bounds_in_screen_->right() >
  1693. visible_space_in_screen.right()
  1694. : drag_item_bounds_in_screen_->bottom() >
  1695. visible_space_in_screen.bottom();
  1696. }
  1697. ScrollToNewPage(/*forward=*/should_scroll_to_next);
  1698. if (test_observer_)
  1699. test_observer_->OnPageFlipTimerFired();
  1700. }
  1701. bool ScrollableShelfView::AreBoundsWithinVisibleSpace(
  1702. const gfx::Rect& bounds_in_screen) const {
  1703. if (bounds_in_screen.IsEmpty())
  1704. return false;
  1705. gfx::Rect visible_space_in_screen = visible_space_;
  1706. views::View::ConvertRectToScreen(this, &visible_space_in_screen);
  1707. if (GetShelf()->IsHorizontalAlignment()) {
  1708. return bounds_in_screen.x() >= visible_space_in_screen.x() &&
  1709. bounds_in_screen.right() <= visible_space_in_screen.right();
  1710. }
  1711. return bounds_in_screen.y() >= visible_space_in_screen.y() &&
  1712. bounds_in_screen.bottom() <= visible_space_in_screen.bottom();
  1713. }
  1714. bool ScrollableShelfView::ShouldDelegateScrollToShelf(
  1715. const ui::ScrollEvent& event) const {
  1716. // When the shelf is not aligned in the bottom, the events should be
  1717. // propagated and handled as MouseWheel events.
  1718. if (event.type() != ui::ET_SCROLL)
  1719. return false;
  1720. const float main_offset =
  1721. GetShelf()->IsHorizontalAlignment() ? event.x_offset() : event.y_offset();
  1722. const float cross_offset =
  1723. GetShelf()->IsHorizontalAlignment() ? event.y_offset() : event.x_offset();
  1724. // We only delegate to the shelf scroll events across the main axis,
  1725. // otherwise, let them propagate and be handled as MouseWheel Events.
  1726. return std::abs(main_offset) < std::abs(cross_offset);
  1727. }
  1728. float ScrollableShelfView::CalculateMainAxisScrollDistance() const {
  1729. return GetShelf()->IsHorizontalAlignment() ? scroll_offset_.x()
  1730. : scroll_offset_.y();
  1731. }
  1732. void ScrollableShelfView::UpdateScrollOffset(float target_offset) {
  1733. target_offset =
  1734. CalculateClampedScrollOffset(target_offset, GetSpaceForIcons());
  1735. if (GetShelf()->IsHorizontalAlignment())
  1736. scroll_offset_.set_x(target_offset);
  1737. else
  1738. scroll_offset_.set_y(target_offset);
  1739. // Calculating the layout strategy relies on |scroll_offset_|.
  1740. LayoutStrategy new_strategy = CalculateLayoutStrategy(
  1741. CalculateMainAxisScrollDistance(), GetSpaceForIcons());
  1742. const bool strategy_needs_update = (layout_strategy_ != new_strategy);
  1743. if (strategy_needs_update) {
  1744. layout_strategy_ = new_strategy;
  1745. const bool has_gradient_zone = layer()->layer_mask_layer();
  1746. const bool should_have_gradient_zone = ShouldApplyMaskLayerGradientZone();
  1747. if (has_gradient_zone && !should_have_gradient_zone) {
  1748. PaintGradientZone(GradientLayerDelegate::FadeZone(),
  1749. GradientLayerDelegate::FadeZone());
  1750. layer()->SetMaskLayer(nullptr);
  1751. } else if (!has_gradient_zone && should_have_gradient_zone) {
  1752. gradient_layer_delegate_->layer()->SetBounds(layer()->bounds());
  1753. layer()->SetMaskLayer(gradient_layer_delegate_->layer());
  1754. }
  1755. InvalidateLayout();
  1756. }
  1757. visible_space_ = CalculateVisibleSpace(layout_strategy_);
  1758. if (scroll_status_ != kAlongMainAxisScroll)
  1759. UpdateTappableIconIndices();
  1760. }
  1761. void ScrollableShelfView::UpdateAvailableSpaceAndScroll() {
  1762. UpdateAvailableSpace();
  1763. UpdateScrollOffset(CalculateMainAxisScrollDistance());
  1764. }
  1765. int ScrollableShelfView::CalculateScrollOffsetForTargetAvailableSpace(
  1766. const gfx::Rect& target_space) const {
  1767. // Ensures that the scroll offset is legal under the updated available space.
  1768. const int available_space_for_icons =
  1769. GetShelf()->PrimaryAxisValue(target_space.width(), target_space.height());
  1770. int target_scroll_offset = CalculateClampedScrollOffset(
  1771. CalculateMainAxisScrollDistance(), available_space_for_icons);
  1772. // Calculates the layout strategy based on the new scroll offset.
  1773. LayoutStrategy new_strategy =
  1774. CalculateLayoutStrategy(target_scroll_offset, available_space_for_icons);
  1775. // Adjusts the scroll offset with the new strategy.
  1776. target_scroll_offset += CalculateAdjustmentOffset(
  1777. target_scroll_offset, new_strategy, available_space_for_icons);
  1778. return target_scroll_offset;
  1779. }
  1780. bool ScrollableShelfView::ShouldCountActivatedInkDrop(
  1781. const views::View* sender) const {
  1782. bool should_count = false;
  1783. // When scrolling shelf by gestures, the shelf icon's ink drop ripple may be
  1784. // activated accidentally. So ignore the ink drop activity during animation.
  1785. if (during_scroll_animation_)
  1786. return should_count;
  1787. if (!first_tappable_app_index_.has_value() ||
  1788. !last_tappable_app_index_.has_value()) {
  1789. // Verify that `first_tappable_app_index_` and `last_tappable_app_index_`
  1790. // are both illegal. In that case, return early.
  1791. DCHECK(first_tappable_app_index_ == last_tappable_app_index_);
  1792. return false;
  1793. }
  1794. // The ink drop needs to be clipped only if |sender| is the app at one of the
  1795. // corners of the shelf. This happens if it is either the first or the last
  1796. // tappable app and no arrow is showing on its side.
  1797. if (shelf_view_->view_model()->view_at(first_tappable_app_index_.value()) ==
  1798. sender) {
  1799. should_count = !(layout_strategy_ == kShowButtons ||
  1800. layout_strategy_ == kShowLeftArrowButton);
  1801. } else if (shelf_view_->view_model()->view_at(
  1802. last_tappable_app_index_.value()) == sender) {
  1803. should_count = !(layout_strategy_ == kShowButtons ||
  1804. layout_strategy_ == kShowRightArrowButton);
  1805. }
  1806. return should_count;
  1807. }
  1808. void ScrollableShelfView::EnableShelfRoundedCorners(bool enable) {
  1809. // Only enable shelf rounded corners in tablet mode. Note that we allow
  1810. // disabling rounded corners in clamshell. Because when switching to clamshell
  1811. // from tablet, this method may be called after tablet mode ends.
  1812. // TODO(https://crbug.com/1067490): reorder the destruction order in
  1813. // Shell::~Shell then remove the explicit check.
  1814. const bool is_in_tablet_mode =
  1815. Shell::Get()->tablet_mode_controller() && Shell::Get()->IsInTabletMode();
  1816. if (enable && !is_in_tablet_mode)
  1817. return;
  1818. ui::Layer* layer = shelf_container_view_->layer();
  1819. const bool has_rounded_corners = !(layer->rounded_corner_radii().IsEmpty());
  1820. if (enable == has_rounded_corners)
  1821. return;
  1822. // In non-overflow mode, only apply layer clip on |shelf_container_view_|
  1823. // when the ripple ring of the first/last shelf icon shows.
  1824. // Note that |layout_strategy_| may update while EnableShelfRoundedCorners()
  1825. // is not called. For example, the first icon's context menu shows, then the
  1826. // system notification makes shelf view enter overflow mode. So
  1827. // |layer_clip_in_non_overflow_| updates regardless of |layout_strategy_|.
  1828. layer_clip_in_non_overflow_ = enable;
  1829. if (layout_strategy_ == kNotShowArrowButtons)
  1830. EnableLayerClipOnShelfContainerView(layer_clip_in_non_overflow_);
  1831. layer->SetRoundedCornerRadius(enable ? CalculateShelfContainerRoundedCorners()
  1832. : gfx::RoundedCornersF());
  1833. if (!layer->is_fast_rounded_corner())
  1834. layer->SetIsFastRoundedCorner(/*enable=*/true);
  1835. }
  1836. void ScrollableShelfView::OnActiveInkDropChange(bool increase) {
  1837. if (increase)
  1838. ++activated_corner_buttons_;
  1839. else
  1840. --activated_corner_buttons_;
  1841. // When long pressing icons, sometimes there are more ripple animations
  1842. // pending over others buttons. Only activate rounded corners when at least
  1843. // one button needs them.
  1844. // NOTE: `last_tappable_app_index_` is used to compute whether a button is
  1845. // at the corner or not. Meanwhile, `last_tappable_app_index_` could update
  1846. // before the button fade out animation ends. As a result, in edge cases
  1847. // `activated_corner_buttons_` could be greater than 2.
  1848. CHECK_GE(activated_corner_buttons_, 0);
  1849. EnableShelfRoundedCorners(activated_corner_buttons_ > 0);
  1850. }
  1851. bool ScrollableShelfView::ShouldEnableLayerClip() const {
  1852. // Always use layer clip in overflow mode.
  1853. if (layout_strategy_ != LayoutStrategy::kNotShowArrowButtons)
  1854. return true;
  1855. // TODO(https://crbug.com/1067490): reorder the destruction order in
  1856. // Shell::~Shell then remove the explicit check.
  1857. const bool is_in_tablet_mode =
  1858. Shell::Get()->tablet_mode_controller() && Shell::Get()->IsInTabletMode();
  1859. // In clamshell, only use layer clip in overflow mode.
  1860. if (!is_in_tablet_mode)
  1861. return false;
  1862. // In tablet mode, whether using layer clip in non-overflow mode depends on
  1863. // |layer_clip_in_non_overflow_|.
  1864. return layer_clip_in_non_overflow_;
  1865. }
  1866. void ScrollableShelfView::EnableLayerClipOnShelfContainerView(bool enable) {
  1867. if (!enable) {
  1868. shelf_container_view_->layer()->SetClipRect(gfx::Rect());
  1869. return;
  1870. }
  1871. // |visible_space_| is in local coordinates. It should be transformed into
  1872. // |shelf_container_view_|'s coordinates for layer clip.
  1873. gfx::RectF visible_space_in_shelf_container_coordinates(visible_space_);
  1874. views::View::ConvertRectToTarget(
  1875. this, shelf_container_view_,
  1876. &visible_space_in_shelf_container_coordinates);
  1877. shelf_container_view_->layer()->SetClipRect(
  1878. gfx::ToEnclosedRect(visible_space_in_shelf_container_coordinates));
  1879. }
  1880. int ScrollableShelfView::CalculateShelfIconsPreferredLength() const {
  1881. const gfx::Size shelf_preferred_size(
  1882. shelf_container_view_->GetPreferredSize());
  1883. const int preferred_length =
  1884. (GetShelf()->IsHorizontalAlignment() ? shelf_preferred_size.width()
  1885. : shelf_preferred_size.height());
  1886. return preferred_length + 2 * ShelfConfig::Get()->GetAppIconEndPadding();
  1887. }
  1888. } // namespace ash