123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220222122222223222422252226222722282229223022312232223322342235223622372238223922402241224222432244224522462247224822492250225122522253225422552256225722582259226022612262226322642265226622672268226922702271 |
- // Copyright 2019 The Chromium Authors. All rights reserved.
- // Use of this source code is governed by a BSD-style license that can be
- // found in the LICENSE file.
- #include "ash/shelf/scrollable_shelf_view.h"
- #include <algorithm>
- #include "ash/app_list/app_list_controller_impl.h"
- #include "ash/public/cpp/shelf_config.h"
- #include "ash/screen_util.h"
- #include "ash/shelf/scrollable_shelf_constants.h"
- #include "ash/shelf/shelf_app_button.h"
- #include "ash/shelf/shelf_focus_cycler.h"
- #include "ash/shelf/shelf_navigation_widget.h"
- #include "ash/shelf/shelf_tooltip_manager.h"
- #include "ash/shelf/shelf_widget.h"
- #include "ash/shell.h"
- #include "ash/strings/grit/ash_strings.h"
- #include "ash/system/status_area_widget.h"
- #include "ash/wm/tablet_mode/tablet_mode_controller.h"
- #include "base/bind.h"
- #include "base/cxx17_backports.h"
- #include "base/i18n/rtl.h"
- #include "base/metrics/histogram_functions.h"
- #include "ui/base/l10n/l10n_util.h"
- #include "ui/compositor/animation_throughput_reporter.h"
- #include "ui/compositor/presentation_time_recorder.h"
- #include "ui/compositor/scoped_layer_animation_settings.h"
- #include "ui/gfx/geometry/insets.h"
- #include "ui/gfx/geometry/rect_f.h"
- #include "ui/gfx/geometry/transform_util.h"
- #include "ui/gfx/geometry/vector2d_conversions.h"
- #include "ui/views/animation/ink_drop.h"
- #include "ui/views/focus/focus_search.h"
- #include "ui/views/view_targeter_delegate.h"
- namespace ash {
- namespace {
- // Returns the display id for the display that shows the shelf for |view|.
- int64_t GetDisplayIdForView(const views::View* view) {
- aura::Window* window = view->GetWidget()->GetNativeWindow();
- return display::Screen::GetScreen()->GetDisplayNearestWindow(window).id();
- }
- void ReportSmoothness(bool tablet_mode, bool launcher_visible, int smoothness) {
- base::UmaHistogramPercentage(
- scrollable_shelf_constants::kAnimationSmoothnessHistogram, smoothness);
- if (tablet_mode) {
- if (launcher_visible) {
- base::UmaHistogramPercentage(
- scrollable_shelf_constants::
- kAnimationSmoothnessTabletLauncherVisibleHistogram,
- smoothness);
- } else {
- base::UmaHistogramPercentage(
- scrollable_shelf_constants::
- kAnimationSmoothnessTabletLauncherHiddenHistogram,
- smoothness);
- }
- } else {
- if (launcher_visible) {
- base::UmaHistogramPercentage(
- scrollable_shelf_constants::
- kAnimationSmoothnessClamshellLauncherVisibleHistogram,
- smoothness);
- } else {
- base::UmaHistogramPercentage(
- scrollable_shelf_constants::
- kAnimationSmoothnessClamshellLauncherHiddenHistogram,
- smoothness);
- }
- }
- }
- gfx::Insets GetMirroredInsets(const gfx::Insets& insets) {
- return gfx::Insets::TLBR(insets.top(), insets.right(), insets.bottom(),
- insets.left());
- }
- } // namespace
- ////////////////////////////////////////////////////////////////////////////////
- // ScrollableShelfArrowView
- class ScrollableShelfView::ScrollableShelfArrowView
- : public ScrollArrowView,
- public views::ViewTargeterDelegate {
- public:
- explicit ScrollableShelfArrowView(ArrowType arrow_type,
- bool is_horizontal_alignment,
- ShelfView* shelf_view,
- ShelfButtonDelegate* shelf_button_delegate)
- : ScrollArrowView(arrow_type,
- is_horizontal_alignment,
- shelf_view->shelf(),
- shelf_button_delegate),
- shelf_(shelf_view->shelf()) {
- views::InkDrop::Get(this)->SetMode(views::InkDropHost::InkDropMode::OFF);
- SetEventTargeter(std::make_unique<views::ViewTargeter>(this));
- SetPaintToLayer();
- layer()->SetFillsBoundsOpaquely(false);
- set_context_menu_controller(shelf_view);
- // When the spoken feedback is enabled, scrollable shelf should ensure that
- // the hidden icon which receives the accessibility focus shows through
- // scroll animation. So the arrow button is not useful for the spoken
- // feedback users. The spoken feedback should ignore the arrow button.
- GetViewAccessibility().OverrideIsIgnored(/*value=*/true);
- }
- ~ScrollableShelfArrowView() override = default;
- // views::ViewTargeterDelegate:
- bool DoesIntersectRect(const views::View* target,
- const gfx::Rect& rect) const override {
- DCHECK_EQ(target, this);
- const gfx::Rect bounds = gfx::Rect(size());
- // Calculates the tapping area. Note that tapping area is bigger than the
- // arrow button's bounds.
- gfx::Rect tap_rect(
- shelf_->PrimaryAxisValue(
- scrollable_shelf_constants::kArrowButtonTapAreaHorizontal,
- shelf_->hotseat_widget()->GetHotseatSize()),
- shelf_->PrimaryAxisValue(
- shelf_->hotseat_widget()->GetHotseatSize(),
- scrollable_shelf_constants::kArrowButtonTapAreaHorizontal));
- tap_rect -= gfx::Vector2d((tap_rect.width() - bounds.width()) / 2,
- (tap_rect.height() - bounds.height()) / 2);
- DCHECK(tap_rect.Contains(bounds));
- return tap_rect.Intersects(rect);
- }
- // Make ScrollRectToVisible a no-op because ScrollableShelfArrowView is
- // always visible/invisible depending on the layout strategy at fixed
- // locations. So it does not need to be scrolled to show.
- // TODO (andrewxu): Moves all of functions related with scrolling into
- // ScrollableShelfContainerView. Then erase this empty function.
- void ScrollRectToVisible(const gfx::Rect& rect) override {}
- const char* GetClassName() const override {
- return "ScrollableShelfArrowView";
- }
- private:
- Shelf* const shelf_;
- };
- ////////////////////////////////////////////////////////////////////////////////
- // ScopedActiveInkDropCountImpl
- class ScrollableShelfView::ScopedActiveInkDropCountImpl
- : public ScrollableShelfView::ScopedActiveInkDropCount {
- public:
- explicit ScopedActiveInkDropCountImpl(ScrollableShelfView* owner)
- : owner_(owner) {
- owner_->OnActiveInkDropChange(/*increase=*/true);
- }
- ~ScopedActiveInkDropCountImpl() override {
- owner_->OnActiveInkDropChange(/*increase=*/false);
- }
- ScopedActiveInkDropCountImpl(const ScopedActiveInkDropCountImpl& rhs) =
- delete;
- ScopedActiveInkDropCountImpl& operator=(
- const ScopedActiveInkDropCountImpl& rhs) = delete;
- private:
- ScrollableShelfView* owner_ = nullptr;
- };
- ////////////////////////////////////////////////////////////////////////////////
- // ScrollableShelfContainerView
- class ScrollableShelfContainerView : public ShelfContainerView,
- public views::ViewTargeterDelegate {
- public:
- explicit ScrollableShelfContainerView(
- ScrollableShelfView* scrollable_shelf_view)
- : ShelfContainerView(scrollable_shelf_view->shelf_view()),
- scrollable_shelf_view_(scrollable_shelf_view) {
- SetEventTargeter(std::make_unique<views::ViewTargeter>(this));
- }
- ScrollableShelfContainerView(const ScrollableShelfContainerView&) = delete;
- ScrollableShelfContainerView& operator=(const ScrollableShelfContainerView&) =
- delete;
- ~ScrollableShelfContainerView() override = default;
- // ShelfContainerView:
- void TranslateShelfView(const gfx::Vector2dF& offset) override;
- private:
- // views::View:
- void Layout() override;
- // views::ViewTargeterDelegate:
- bool DoesIntersectRect(const views::View* target,
- const gfx::Rect& rect) const override;
- ScrollableShelfView* scrollable_shelf_view_ = nullptr;
- };
- void ScrollableShelfContainerView::TranslateShelfView(
- const gfx::Vector2dF& offset) {
- ShelfContainerView::TranslateShelfView(
- scrollable_shelf_view_->ShouldAdaptToRTL() ? -offset : offset);
- }
- void ScrollableShelfContainerView::Layout() {
- // Should not use ShelfView::GetPreferredSize in replace of
- // CalculateIdealSize. Because ShelfView::CalculatePreferredSize relies on the
- // bounds of app icon. Meanwhile, the icon's bounds may be updated by
- // animation.
- const gfx::Rect ideal_bounds = gfx::Rect(CalculatePreferredSize());
- const gfx::Rect local_bounds = GetLocalBounds();
- gfx::Rect shelf_view_bounds =
- local_bounds.Contains(ideal_bounds) ? local_bounds : ideal_bounds;
- if (shelf_view_->shelf()->IsHorizontalAlignment())
- shelf_view_bounds.set_x(ShelfConfig::Get()->GetAppIconEndPadding());
- else
- shelf_view_bounds.set_y(ShelfConfig::Get()->GetAppIconEndPadding());
- shelf_view_->SetBoundsRect(shelf_view_bounds);
- }
- bool ScrollableShelfContainerView::DoesIntersectRect(
- const views::View* target,
- const gfx::Rect& rect) const {
- // This view's layer is clipped. So the view should only handle the events
- // within the area after cilp.
- gfx::RectF bounds(scrollable_shelf_view_->visible_space());
- views::View::ConvertRectToTarget(scrollable_shelf_view_, this, &bounds);
- return ToEnclosedRect(bounds).Contains(rect);
- }
- ////////////////////////////////////////////////////////////////////////////////
- // ScrollableShelfFocusSearch
- class ScrollableShelfFocusSearch : public views::FocusSearch {
- public:
- explicit ScrollableShelfFocusSearch(
- ScrollableShelfView* scrollable_shelf_view)
- : FocusSearch(/*root=*/nullptr,
- /*cycle=*/true,
- /*accessibility_mode=*/true),
- scrollable_shelf_view_(scrollable_shelf_view) {}
- ScrollableShelfFocusSearch(const ScrollableShelfFocusSearch&) = delete;
- ScrollableShelfFocusSearch& operator=(const ScrollableShelfFocusSearch&) =
- delete;
- ~ScrollableShelfFocusSearch() override = default;
- // views::FocusSearch
- views::View* FindNextFocusableView(
- views::View* starting_view,
- FocusSearch::SearchDirection search_direction,
- FocusSearch::TraversalDirection traversal_direction,
- FocusSearch::StartingViewPolicy check_starting_view,
- FocusSearch::AnchoredDialogPolicy can_go_into_anchored_dialog,
- views::FocusTraversable** focus_traversable,
- views::View** focus_traversable_view) override {
- std::vector<views::View*> focusable_views;
- ShelfView* shelf_view = scrollable_shelf_view_->shelf_view();
- for (int i : shelf_view->visible_views_indices())
- focusable_views.push_back(shelf_view->view_model()->view_at(i));
- int start_index = 0;
- for (size_t i = 0; i < focusable_views.size(); ++i) {
- if (focusable_views[i] == starting_view) {
- start_index = i;
- break;
- }
- }
- int new_index =
- start_index +
- (search_direction == FocusSearch::SearchDirection::kBackwards ? -1 : 1);
- // Scrolls to the new page if the focused shelf item is not tappable
- // on the current page.
- if (new_index < 0) {
- new_index = focusable_views.size() - 1;
- } else if (static_cast<size_t>(new_index) >= focusable_views.size()) {
- new_index = 0;
- } else if (static_cast<size_t>(new_index) <
- scrollable_shelf_view_->first_tappable_app_index()) {
- scrollable_shelf_view_->ScrollToNewPage(/*forward=*/false);
- } else if (static_cast<size_t>(new_index) >
- scrollable_shelf_view_->last_tappable_app_index()) {
- scrollable_shelf_view_->ScrollToNewPage(/*forward=*/true);
- }
- return focusable_views[new_index];
- }
- private:
- ScrollableShelfView* scrollable_shelf_view_ = nullptr;
- };
- ////////////////////////////////////////////////////////////////////////////////
- // ScrollableShelfView
- ScrollableShelfView::ScrollableShelfView(ShelfModel* model, Shelf* shelf)
- : shelf_view_(new ShelfView(model,
- shelf,
- /*drag_and_drop_host=*/this,
- /*shelf_button_delegate=*/this)),
- page_flip_time_threshold_(
- scrollable_shelf_constants::kShelfPageFlipDelay) {
- Shell::Get()->AddShellObserver(this);
- ShelfConfig::Get()->AddObserver(this);
- set_allow_deactivate_on_esc(true);
- }
- ScrollableShelfView::~ScrollableShelfView() {
- ShelfConfig::Get()->RemoveObserver(this);
- Shell::Get()->RemoveShellObserver(this);
- GetShelf()->tooltip()->set_shelf_tooltip_delegate(nullptr);
- }
- void ScrollableShelfView::Init() {
- // Although there is no animation for ScrollableShelfView, a layer is still
- // needed. Otherwise, the child view without its own layer will be painted on
- // RootView which is beneath |translucent_background_| in ShelfWidget.
- // As a result, the child view will not show.
- SetPaintToLayer(ui::LAYER_NOT_DRAWN);
- layer()->SetFillsBoundsOpaquely(false);
- // Initialize the shelf container view.
- // Note that |shelf_container_view_| should be under the arrow buttons. It
- // ensures that the arrow button receives the tapping events which happen
- // within the overlapping zone between the arrow button's tapping area and
- // the bounds of |shelf_container_view_|.
- shelf_container_view_ =
- AddChildView(std::make_unique<ScrollableShelfContainerView>(this));
- shelf_container_view_->Initialize();
- // Initialize the left arrow button.
- left_arrow_ = AddChildView(std::make_unique<ScrollableShelfArrowView>(
- ScrollArrowView::kLeft, GetShelf()->IsHorizontalAlignment(), shelf_view_,
- this));
- // Initialize the right arrow button.
- right_arrow_ = AddChildView(std::make_unique<ScrollableShelfArrowView>(
- ScrollArrowView::kRight, GetShelf()->IsHorizontalAlignment(), shelf_view_,
- this));
- gradient_layer_delegate_ =
- std::make_unique<GradientLayerDelegate>(/*animate_in=*/false);
- layer()->SetMaskLayer(gradient_layer_delegate_->layer());
- focus_search_ = std::make_unique<ScrollableShelfFocusSearch>(this);
- GetShelf()->tooltip()->set_shelf_tooltip_delegate(this);
- set_context_menu_controller(this);
- // Initializes |shelf_view_| after scrollable shelf view's children are
- // initialized.
- shelf_view_->Init(focus_search_.get());
- }
- void ScrollableShelfView::OnFocusRingActivationChanged(bool activated) {
- if (activated) {
- focus_ring_activated_ = true;
- SetPaneFocusAndFocusDefault();
- force_show_hotseat_resetter_ =
- GetShelf()->shelf_widget()->ForceShowHotseatInTabletMode();
- } else {
- // Shows the gradient shader when the focus ring is disabled.
- focus_ring_activated_ = false;
- if (force_show_hotseat_resetter_)
- force_show_hotseat_resetter_.RunAndReset();
- }
- MaybeUpdateGradientZone();
- }
- void ScrollableShelfView::ScrollToNewPage(bool forward) {
- const float offset = CalculatePageScrollingOffset(forward, layout_strategy_);
- if (GetShelf()->IsHorizontalAlignment())
- ScrollByXOffset(offset, /*animating=*/true);
- else
- ScrollByYOffset(offset, /*animating=*/true);
- }
- views::FocusSearch* ScrollableShelfView::GetFocusSearch() {
- return focus_search_.get();
- }
- views::FocusTraversable* ScrollableShelfView::GetFocusTraversableParent() {
- return parent()->GetFocusTraversable();
- }
- views::View* ScrollableShelfView::GetFocusTraversableParentView() {
- return this;
- }
- views::View* ScrollableShelfView::GetDefaultFocusableChild() {
- // Adapts |scroll_offset_| to show the view properly right after the focus
- // ring is enabled.
- if (default_last_focusable_child_) {
- ScrollToMainOffset(CalculateScrollUpperBound(GetSpaceForIcons()),
- /*animating=*/true);
- return FindLastFocusableChild();
- }
- ScrollToMainOffset(/*target_offset=*/0.f, /*animating=*/true);
- return FindFirstFocusableChild();
- }
- gfx::Rect ScrollableShelfView::GetHotseatBackgroundBounds() const {
- return available_space_;
- }
- bool ScrollableShelfView::ShouldAdaptToRTL() const {
- return base::i18n::IsRTL() && GetShelf()->IsHorizontalAlignment();
- }
- bool ScrollableShelfView::NeedUpdateToTargetBounds() const {
- return GetAvailableLocalBounds(/*use_target_bounds=*/true) !=
- GetAvailableLocalBounds(/*use_target_bounds=*/false);
- }
- gfx::Rect ScrollableShelfView::GetTargetScreenBoundsOfItemIcon(
- const ShelfID& id) const {
- const int item_index_in_model = shelf_view_->model()->ItemIndexByID(id);
- // Return a dummy value if the item specified by `id` does not exist in the
- // shelf model.
- // TODO(https://crbug.com/1270498): it is a quick fixing. We should
- // investigate the root cause.
- if (item_index_in_model < 0)
- return gfx::Rect();
- // Calculates the available space for child views based on the target bounds.
- // To ease coding, we use the variables before mirroring in computation.
- const gfx::Insets target_edge_padding_RTL_mirrored =
- CalculateMirroredEdgePadding(/*use_target_bounds=*/true);
- const gfx::Insets target_edge_padding_before_RTL_mirror =
- ShouldAdaptToRTL() ? GetMirroredInsets(target_edge_padding_RTL_mirrored)
- : target_edge_padding_RTL_mirrored;
- gfx::Rect target_space_before_RTL_mirror =
- GetAvailableLocalBounds(/*use_target_bounds=*/true);
- target_space_before_RTL_mirror.Inset(target_edge_padding_before_RTL_mirror);
- const gfx::Insets current_edge_padding_RTL_mirrored = edge_padding_insets_;
- const gfx::Insets current_edge_padding_before_RTL_mirror =
- ShouldAdaptToRTL() ? GetMirroredInsets(current_edge_padding_RTL_mirrored)
- : current_edge_padding_RTL_mirrored;
- gfx::Rect icon_bounds =
- shelf_view_->view_model()->ideal_bounds(item_index_in_model);
- icon_bounds.Offset(target_edge_padding_before_RTL_mirror.left() -
- current_edge_padding_before_RTL_mirror.left(),
- 0);
- // Transforms |icon_bounds| from shelf view's coordinates to scrollable shelf
- // view's coordinates manually.
- const bool is_horizontal_alignment = GetShelf()->IsHorizontalAlignment();
- const int shelf_view_offset = ShelfConfig::Get()->GetAppIconEndPadding();
- const int shelf_view_container_offset =
- is_horizontal_alignment ? shelf_container_view_->bounds().x()
- : shelf_container_view_->bounds().y();
- const int target_scroll_offset = CalculateScrollOffsetForTargetAvailableSpace(
- target_space_before_RTL_mirror);
- const int delta =
- -target_scroll_offset + shelf_view_container_offset + shelf_view_offset;
- const gfx::Vector2d bounds_offset = is_horizontal_alignment
- ? gfx::Vector2d(delta, 0)
- : gfx::Vector2d(0, delta);
- icon_bounds.Offset(bounds_offset);
- // If the icon is invisible under the target view bounds, replaces the actual
- // icon's bounds with the rectangle centering on the edge of |target_space|.
- const gfx::Point icon_bounds_center = icon_bounds.CenterPoint();
- if (icon_bounds_center.x() > target_space_before_RTL_mirror.right()) {
- icon_bounds.Offset(
- target_space_before_RTL_mirror.right_center().OffsetFromOrigin() -
- icon_bounds_center.OffsetFromOrigin());
- } else if (icon_bounds_center.x() < target_space_before_RTL_mirror.x()) {
- icon_bounds.Offset(
- target_space_before_RTL_mirror.left_center().OffsetFromOrigin() -
- icon_bounds_center.OffsetFromOrigin());
- }
- // Hotseat's target bounds may differ from the actual bounds. So it has to
- // transform the bounds manually from view's local coordinates to screen.
- // Notes that the target bounds stored in shelf layout manager are adapted to
- // RTL already while |icon_bounds| are not adjusted to RTL yet.
- gfx::Rect hotseat_bounds_in_screen =
- GetShelf()->hotseat_widget()->GetTargetBounds();
- if (ShouldAdaptToRTL()) {
- // One simple way for transformation under RTL is: (1) Transforms hotseat
- // target bounds from RTL to LTR. (2) Calculates the icon's bounds in screen
- // under LTR. (3) Transforms the icon's bounds to RTL.
- gfx::Rect display_bounds =
- display::Screen::GetScreen()
- ->GetDisplayNearestWindow(GetWidget()->GetNativeView())
- .bounds();
- hotseat_bounds_in_screen.set_x(display_bounds.right() -
- hotseat_bounds_in_screen.right());
- icon_bounds.Offset(hotseat_bounds_in_screen.OffsetFromOrigin());
- icon_bounds.set_x(display_bounds.right() - icon_bounds.right());
- } else {
- icon_bounds.Offset(hotseat_bounds_in_screen.OffsetFromOrigin());
- }
- return icon_bounds;
- }
- bool ScrollableShelfView::RequiresScrollingForItemSize(
- const gfx::Size& target_size,
- int button_size) const {
- const gfx::Size icons_preferred_size =
- shelf_container_view_->CalculateIdealSize(button_size);
- return !CanFitAllAppsWithoutScrolling(target_size, icons_preferred_size);
- }
- void ScrollableShelfView::SetEdgePaddingInsets(
- const gfx::Insets& padding_insets) {
- edge_padding_insets_ = padding_insets;
- shelf_view_->LayoutIfAppIconsOffsetUpdates();
- }
- gfx::Insets ScrollableShelfView::CalculateMirroredEdgePadding(
- bool use_target_bounds) const {
- // Tries display centering strategy.
- const gfx::Insets display_centering_edge_padding =
- CalculateMirroredPaddingForDisplayCentering(use_target_bounds);
- if (!display_centering_edge_padding.IsEmpty()) {
- // Returns early if the value is legal.
- return display_centering_edge_padding;
- }
- const int icons_size =
- shelf_view_->GetSizeOfAppButtons(shelf_view_->number_of_visible_apps(),
- shelf_view_->GetButtonSize()) +
- 2 * ShelfConfig::Get()->GetAppIconEndPadding();
- const gfx::Rect available_local_bounds =
- GetAvailableLocalBounds(use_target_bounds);
- const int available_size_for_app_icons = GetShelf()->PrimaryAxisValue(
- available_local_bounds.width(), available_local_bounds.height());
- int gap = CanFitAllAppsWithoutScrolling(available_local_bounds.size(),
- CalculatePreferredSize())
- ? available_size_for_app_icons - icons_size
- : 0; // overflow
- // Calculates the paddings before/after the visible area of scrollable shelf.
- // |after_padding| being zero ensures that the available space after the
- // visible area is filled first.
- const int before_padding = gap;
- const int after_padding = 0;
- gfx::Insets padding_insets;
- if (GetShelf()->IsHorizontalAlignment()) {
- padding_insets = gfx::Insets::TLBR(0, before_padding, 0, after_padding);
- if (ShouldAdaptToRTL())
- padding_insets = GetMirroredInsets(padding_insets);
- } else {
- padding_insets = gfx::Insets::TLBR(before_padding, 0, after_padding, 0);
- }
- return padding_insets;
- }
- views::View* ScrollableShelfView::GetShelfContainerViewForTest() {
- return shelf_container_view_;
- }
- bool ScrollableShelfView::ShouldAdjustForTest() const {
- return CalculateAdjustmentOffset(CalculateMainAxisScrollDistance(),
- layout_strategy_, GetSpaceForIcons());
- }
- void ScrollableShelfView::SetTestObserver(TestObserver* test_observer) {
- DCHECK(!(test_observer && test_observer_));
- test_observer_ = test_observer;
- }
- bool ScrollableShelfView::IsAnyCornerButtonInkDropActivatedForTest() const {
- return activated_corner_buttons_ > 0;
- }
- float ScrollableShelfView::GetScrollUpperBoundForTest() const {
- return CalculateScrollUpperBound(GetSpaceForIcons());
- }
- bool ScrollableShelfView::IsPageFlipTimerBusyForTest() const {
- return page_flip_timer_.IsRunning();
- }
- int ScrollableShelfView::GetSumOfButtonSizeAndSpacing() const {
- return shelf_view_->GetButtonSize() + ShelfConfig::Get()->button_spacing();
- }
- int ScrollableShelfView::GetGestureDragThreshold() const {
- return shelf_view_->GetButtonSize() / 2;
- }
- float ScrollableShelfView::CalculateScrollUpperBound(
- int available_space_for_icons) const {
- if (layout_strategy_ == kNotShowArrowButtons)
- return 0.f;
- return std::max(
- 0, CalculateShelfIconsPreferredLength() - available_space_for_icons);
- }
- float ScrollableShelfView::CalculateClampedScrollOffset(
- float scroll,
- int available_space_for_icons) const {
- const float scroll_upper_bound =
- CalculateScrollUpperBound(available_space_for_icons);
- scroll = base::clamp(scroll, 0.0f, scroll_upper_bound);
- return scroll;
- }
- void ScrollableShelfView::StartShelfScrollAnimation(float scroll_distance) {
- const gfx::Vector2dF scroll_offset_before_update = scroll_offset_;
- UpdateScrollOffset(scroll_distance);
- if (scroll_offset_before_update == scroll_offset_)
- return;
- StopObservingImplicitAnimations();
- during_scroll_animation_ = true;
- MaybeUpdateGradientZone();
- // In tablet mode, if the target layout only has one arrow button, enable the
- // rounded corners of the shelf container layer in order to cut off the icons
- // outside of the hotseat background.
- const bool one_arrow_in_target_state =
- (layout_strategy_ == LayoutStrategy::kShowLeftArrowButton ||
- layout_strategy_ == LayoutStrategy::kShowRightArrowButton);
- if (one_arrow_in_target_state)
- EnableShelfRoundedCorners(/*enable=*/true);
- ui::ScopedLayerAnimationSettings animation_settings(
- shelf_view_->layer()->GetAnimator());
- animation_settings.SetTweenType(gfx::Tween::EASE_OUT);
- animation_settings.SetPreemptionStrategy(
- ui::LayerAnimator::IMMEDIATELY_SET_NEW_TARGET);
- animation_settings.AddObserver(this);
- ui::AnimationThroughputReporter reporter(
- animation_settings.GetAnimator(),
- metrics_util::ForSmoothness(
- base::BindRepeating(&ReportSmoothness, Shell::Get()->IsInTabletMode(),
- Shell::Get()->app_list_controller()->IsVisible(
- GetDisplayIdForView(this)))));
- shelf_container_view_->TranslateShelfView(scroll_offset_);
- }
- ScrollableShelfView::LayoutStrategy
- ScrollableShelfView::CalculateLayoutStrategy(float scroll_distance_on_main_axis,
- int available_length) const {
- if (available_length >= CalculateShelfIconsPreferredLength()) {
- return kNotShowArrowButtons;
- }
- if (scroll_distance_on_main_axis == 0.f) {
- // No invisible shelf buttons at the left side. So hide the left button.
- return kShowRightArrowButton;
- }
- if (scroll_distance_on_main_axis ==
- CalculateScrollUpperBound(available_length)) {
- // If there is no invisible shelf button at the right side, hide the right
- // button.
- return kShowLeftArrowButton;
- }
- // There are invisible shelf buttons at both sides. So show two buttons.
- return kShowButtons;
- }
- Shelf* ScrollableShelfView::GetShelf() {
- return const_cast<Shelf*>(
- const_cast<const ScrollableShelfView*>(this)->GetShelf());
- }
- const Shelf* ScrollableShelfView::GetShelf() const {
- return shelf_view_->shelf();
- }
- gfx::Size ScrollableShelfView::CalculatePreferredSize() const {
- return shelf_container_view_->GetPreferredSize();
- }
- void ScrollableShelfView::Layout() {
- gfx::Rect shelf_container_bounds = gfx::Rect(size());
- // Transpose and layout as if it is horizontal.
- const bool is_horizontal = GetShelf()->IsHorizontalAlignment();
- if (!is_horizontal)
- shelf_container_bounds.Transpose();
- gfx::Size arrow_button_size(scrollable_shelf_constants::kArrowButtonSize,
- shelf_container_bounds.height());
- gfx::Size arrow_button_group_size(
- scrollable_shelf_constants::kArrowButtonGroupWidth,
- shelf_container_bounds.height());
- // The bounds of |left_arrow_| and |right_arrow_| are in the
- // ScrollableShelfView's local coordinates.
- gfx::Rect left_arrow_bounds;
- gfx::Rect right_arrow_bounds;
- int before_padding;
- if (ShouldAdaptToRTL()) {
- before_padding = edge_padding_insets_.right();
- } else {
- before_padding = is_horizontal ? edge_padding_insets_.left()
- : edge_padding_insets_.top();
- }
- int after_padding;
- if (ShouldAdaptToRTL()) {
- after_padding = edge_padding_insets_.left();
- } else {
- after_padding = is_horizontal ? edge_padding_insets_.right()
- : edge_padding_insets_.bottom();
- }
- // Calculates the bounds of the left arrow button. If the left arrow button
- // should not show, |left_arrow_bounds| should be empty.
- if (layout_strategy_ == kShowLeftArrowButton ||
- layout_strategy_ == kShowButtons) {
- gfx::Point left_arrow_start_point(shelf_container_bounds.x(), 0);
- left_arrow_bounds =
- gfx::Rect(left_arrow_start_point, arrow_button_group_size);
- left_arrow_bounds.Offset(before_padding, 0);
- left_arrow_bounds.Inset(gfx::Insets::TLBR(
- 0, scrollable_shelf_constants::kArrowButtonEndPadding, 0,
- scrollable_shelf_constants::kDistanceToArrowButton));
- left_arrow_bounds.ClampToCenteredSize(arrow_button_size);
- }
- if (layout_strategy_ == kShowRightArrowButton ||
- layout_strategy_ == kShowButtons) {
- gfx::Point right_arrow_start_point(
- shelf_container_bounds.right() - after_padding -
- scrollable_shelf_constants::kArrowButtonGroupWidth,
- 0);
- right_arrow_bounds =
- gfx::Rect(right_arrow_start_point, arrow_button_group_size);
- right_arrow_bounds.Inset(gfx::Insets::TLBR(
- 0, scrollable_shelf_constants::kDistanceToArrowButton, 0,
- scrollable_shelf_constants::kArrowButtonEndPadding));
- right_arrow_bounds.ClampToCenteredSize(arrow_button_size);
- }
- // Adjust the bounds when not showing in the horizontal
- // alignment.tShelf()->IsHorizontalAlignment()) {
- if (!is_horizontal) {
- left_arrow_bounds.Transpose();
- right_arrow_bounds.Transpose();
- shelf_container_bounds.Transpose();
- }
- // Layout |left_arrow_| if it should show.
- left_arrow_->SetVisible(!left_arrow_bounds.IsEmpty());
- left_arrow_->SetBoundsRect(left_arrow_bounds);
- // Layout |right_arrow_| if it should show.
- right_arrow_->SetVisible(!right_arrow_bounds.IsEmpty());
- right_arrow_->SetBoundsRect(right_arrow_bounds);
- // Layer::Clone(), which may be triggered by screen rotation, does not copy
- // the mask layer. So we may need to reset the mask layer.
- if (ShouldApplyMaskLayerGradientZone() && !layer()->layer_mask_layer()) {
- DCHECK(!gradient_layer_delegate_->layer()->layer_mask_back_link());
- layer()->SetMaskLayer(gradient_layer_delegate_->layer());
- }
- MaybeUpdateGradientZone();
- // Layout |shelf_container_view_|.
- shelf_container_view_->SetBoundsRect(shelf_container_bounds);
- EnableLayerClipOnShelfContainerView(ShouldEnableLayerClip());
- }
- void ScrollableShelfView::ChildPreferredSizeChanged(views::View* child) {
- // Add/remove a shelf icon may change the layout strategy.
- UpdateAvailableSpaceAndScroll();
- shelf_container_view_->TranslateShelfView(scroll_offset_);
- Layout();
- }
- void ScrollableShelfView::OnScrollEvent(ui::ScrollEvent* event) {
- if (event->finger_count() != 2)
- return;
- if (ShouldDelegateScrollToShelf(*event)) {
- GetShelf()->ProcessScrollEvent(event);
- event->StopPropagation();
- }
- }
- void ScrollableShelfView::OnMouseEvent(ui::MouseEvent* event) {
- if (event->IsMouseWheelEvent()) {
- HandleMouseWheelEvent(event->AsMouseWheelEvent());
- return;
- }
- // The mouse event's location may be outside of ShelfView but within the
- // bounds of the ScrollableShelfView. Meanwhile, ScrollableShelfView should
- // handle the mouse event consistently with ShelfView. To achieve this,
- // we simply redirect |event| to ShelfView.
- gfx::Point location_in_shelf_view = event->location();
- View::ConvertPointToTarget(this, shelf_view_, &location_in_shelf_view);
- event->set_location(location_in_shelf_view);
- shelf_view_->OnMouseEvent(event);
- }
- void ScrollableShelfView::OnGestureEvent(ui::GestureEvent* event) {
- if (ShouldHandleGestures(*event) && ProcessGestureEvent(*event)) {
- // |event| is consumed by ScrollableShelfView.
- event->SetHandled();
- } else if (shelf_view_->HandleGestureEvent(event)) {
- // |event| is consumed by ShelfView.
- event->StopPropagation();
- } else if (event->type() == ui::ET_GESTURE_SCROLL_BEGIN) {
- // |event| is consumed by neither ScrollableShelfView nor ShelfView. So the
- // gesture end event will not be propagated to this view. Then we need to
- // reset the class members related with scroll status explicitly.
- ResetScrollStatus();
- }
- }
- void ScrollableShelfView::GetAccessibleNodeData(ui::AXNodeData* node_data) {
- GetViewAccessibility().OverrideNextFocus(GetShelf()->GetStatusAreaWidget());
- GetViewAccessibility().OverridePreviousFocus(
- GetShelf()->shelf_widget()->navigation_widget());
- }
- void ScrollableShelfView::OnBoundsChanged(const gfx::Rect& previous_bounds) {
- if (ShouldApplyMaskLayerGradientZone() &&
- gradient_layer_delegate_->layer()->bounds() != layer()->bounds()) {
- gradient_layer_delegate_->layer()->SetBounds(layer()->bounds());
- }
- const gfx::Insets old_edge_padding_insets = edge_padding_insets_;
- const gfx::Vector2dF old_scroll_offset = scroll_offset_;
- // The changed view bounds may lead to update on the available space.
- UpdateAvailableSpaceAndScroll();
- // Relayout shelf items if the preferred padding changed.
- if (old_edge_padding_insets != edge_padding_insets_)
- shelf_view_->OnBoundsChanged(shelf_view_->GetBoundsInScreen());
- // Avoids calling AdjustOffset() when the scrollable shelf view is
- // under scroll along the main axis. Otherwise, animation will conflict with
- // scroll gesture. Meanwhile, translates the shelf view
- // if AdjustOffset() returns false since when AdjustOffset() returns true,
- // shelf view is scrolled by animation.
- const bool should_translate_shelf_view =
- scroll_status_ == kAlongMainAxisScroll || !AdjustOffset();
- if (should_translate_shelf_view && old_scroll_offset != scroll_offset_)
- shelf_container_view_->TranslateShelfView(scroll_offset_);
- }
- void ScrollableShelfView::ViewHierarchyChanged(
- const views::ViewHierarchyChangedDetails& details) {
- if (details.parent != shelf_view_)
- return;
- shelf_view_->UpdateShelfItemViewsVisibility();
- // When app scaling state needs update, hotseat bounds should change. Then
- // it is not meaningful to do further work in the current view bounds. So
- // returns early.
- if (GetShelf()->hotseat_widget()->UpdateTargetHotseatDensityIfNeeded())
- return;
- const gfx::Vector2dF old_scroll_offset = scroll_offset_;
- // Adding/removing an icon may change the padding then affect the available
- // space.
- UpdateAvailableSpaceAndScroll();
- if (old_scroll_offset != scroll_offset_)
- shelf_container_view_->TranslateShelfView(scroll_offset_);
- }
- void ScrollableShelfView::ScrollRectToVisible(const gfx::Rect& rect) {
- // Transform |rect| to local view coordinates taking |scroll_offset_| into
- // consideration.
- const bool is_horizontal_alignment = GetShelf()->IsHorizontalAlignment();
- gfx::Rect rect_after_adjustment = rect;
- if (is_horizontal_alignment)
- rect_after_adjustment.Offset(-scroll_offset_.x(), 0);
- else
- rect_after_adjustment.Offset(0, -scroll_offset_.y());
- // Notes that |rect| is not mirrored under RTL while |visible_space_| has been
- // mirrored. It is easier for coding if we mirror |visible_space_| back and
- // then do the calculation.
- const gfx::Rect visible_space_without_RTL = GetMirroredRect(visible_space_);
- // |rect_after_adjustment| is already shown completely. So scroll is not
- // needed.
- if (visible_space_without_RTL.Contains(rect_after_adjustment)) {
- AdjustOffset();
- return;
- }
- const float original_offset = CalculateMainAxisScrollDistance();
- // |forward| indicates the scroll direction.
- const bool forward =
- is_horizontal_alignment
- ? rect_after_adjustment.right() > visible_space_without_RTL.right()
- : rect_after_adjustment.bottom() > visible_space_without_RTL.bottom();
- // Scrolling |shelf_view_| has the following side-effects:
- // (1) May change the layout strategy.
- // (2) May change the visible space.
- // (3) Must change the scrolling offset.
- // (4) Must change |rect_after_adjustment|'s coordinates after adjusting the
- // scroll.
- LayoutStrategy layout_strategy_after_scroll = layout_strategy_;
- float main_axis_offset_after_scroll = original_offset;
- gfx::Rect visible_space_after_scroll = visible_space_without_RTL;
- gfx::Rect rect_after_scroll = rect_after_adjustment;
- // In each iteration, it scrolls |shelf_view_| to the neighboring page.
- // Terminating the loop iteration if:
- // (1) Find the suitable page which shows |rect| completely.
- // (2) Cannot scroll |shelf_view_| anymore (it may happen with ChromeVox
- // enabled).
- while (!visible_space_after_scroll.Contains(rect_after_scroll)) {
- int page_scroll_distance =
- CalculatePageScrollingOffset(forward, layout_strategy_after_scroll);
- // Breaking the while loop if it cannot scroll anymore.
- if (!page_scroll_distance)
- break;
- main_axis_offset_after_scroll = CalculateTargetOffsetAfterScroll(
- main_axis_offset_after_scroll, page_scroll_distance);
- layout_strategy_after_scroll = CalculateLayoutStrategy(
- main_axis_offset_after_scroll, GetSpaceForIcons());
- visible_space_after_scroll =
- GetMirroredRect(CalculateVisibleSpace(layout_strategy_after_scroll));
- rect_after_scroll = rect_after_adjustment;
- const int offset_delta = main_axis_offset_after_scroll - original_offset;
- if (is_horizontal_alignment)
- rect_after_scroll.Offset(-offset_delta, 0);
- else
- rect_after_scroll.Offset(0, -offset_delta);
- }
- if (!visible_space_after_scroll.Contains(rect_after_scroll))
- return;
- ScrollToMainOffset(main_axis_offset_after_scroll, /*animating=*/true);
- }
- std::unique_ptr<ui::Layer> ScrollableShelfView::RecreateLayer() {
- layer()->SetMaskLayer(nullptr);
- return views::View::RecreateLayer();
- }
- const char* ScrollableShelfView::GetClassName() const {
- return "ScrollableShelfView";
- }
- void ScrollableShelfView::OnShelfButtonAboutToRequestFocusFromTabTraversal(
- ShelfButton* button,
- bool reverse) {
- if ((button == left_arrow_) || (button == right_arrow_))
- return;
- shelf_view_->OnShelfButtonAboutToRequestFocusFromTabTraversal(button,
- reverse);
- ShelfWidget* shelf_widget = GetShelf()->shelf_widget();
- // In tablet mode, when the hotseat is not extended but one of the buttons
- // gets focused, it should update the visibility of the hotseat.
- if (Shell::Get()->IsInTabletMode() &&
- !shelf_widget->hotseat_widget()->IsExtended()) {
- shelf_widget->shelf_layout_manager()->UpdateVisibilityState();
- }
- }
- void ScrollableShelfView::ButtonPressed(views::Button* sender,
- const ui::Event& event,
- views::InkDrop* ink_drop) {
- if ((sender == left_arrow_) || (sender == right_arrow_)) {
- ScrollToNewPage(sender == right_arrow_);
- return;
- }
- shelf_view_->ButtonPressed(sender, event, ink_drop);
- }
- void ScrollableShelfView::HandleAccessibleActionScrollToMakeVisible(
- ShelfButton* button) {
- // Scrollable shelf can only be hidden in tablet mode.
- GetShelf()->hotseat_widget()->set_manually_extended(true);
- GetShelf()->shelf_widget()->shelf_layout_manager()->UpdateVisibilityState();
- }
- void ScrollableShelfView::OnButtonWillBeRemoved() {
- const int view_size_before_removal = shelf_view_->view_model()->view_size();
- DCHECK_GT(view_size_before_removal, 0);
- // Ensure `last_tappable_app_index_` to be valid after removal. Normally
- // `last_tappable_app_index_` updates when the shelf button is removed. But
- // button removal could be performed at the end of the button fade out
- // animation, which means that incorrect `last_tappable_app_index_` could be
- // accessed during the animation. To handle this issue, update
- // `last_tappable_app_index_` before removal finishes.
- // The code block also covers the edge case that the only shelf item is going
- // to be removed, i.e. `view_size_before_removal_` is one. In this case,
- // both `first_tappable_app_index_` and `last_tappable_app_index_` are reset
- // to invalid values (see https://crbug.com/1300561).
- if (view_size_before_removal < 2) {
- last_tappable_app_index_ = absl::nullopt;
- } else {
- last_tappable_app_index_ = std::min(
- last_tappable_app_index_,
- absl::make_optional(static_cast<size_t>(view_size_before_removal - 2)));
- }
- first_tappable_app_index_ =
- std::min(first_tappable_app_index_, last_tappable_app_index_);
- }
- void ScrollableShelfView::OnAppButtonActivated(const ShelfButton* button) {
- ScrollRectToVisible(button->bounds());
- }
- std::unique_ptr<ScrollableShelfView::ScopedActiveInkDropCount>
- ScrollableShelfView::CreateScopedActiveInkDropCount(const ShelfButton* sender) {
- if (!ShouldCountActivatedInkDrop(sender))
- return nullptr;
- return std::make_unique<ScopedActiveInkDropCountImpl>(this);
- }
- void ScrollableShelfView::ShowContextMenuForViewImpl(
- views::View* source,
- const gfx::Point& point,
- ui::MenuSourceType source_type) {
- // |point| is in screen coordinates. So it does not need to transform.
- shelf_view_->ShowContextMenuForViewImpl(shelf_view_, point, source_type);
- }
- void ScrollableShelfView::OnShelfAlignmentChanged(
- aura::Window* root_window,
- ShelfAlignment old_alignment) {
- const bool is_horizontal_alignment = GetShelf()->IsHorizontalAlignment();
- left_arrow_->set_is_horizontal_alignment(is_horizontal_alignment);
- right_arrow_->set_is_horizontal_alignment(is_horizontal_alignment);
- scroll_offset_ = gfx::Vector2dF();
- ScrollToMainOffset(CalculateMainAxisScrollDistance(), /*animating=*/false);
- Layout();
- }
- void ScrollableShelfView::OnShelfConfigUpdated() {
- UpdateAvailableSpaceAndScroll();
- shelf_view_->OnShelfConfigUpdated();
- }
- bool ScrollableShelfView::ShouldShowTooltipForView(
- const views::View* view) const {
- if (!view || !view->parent())
- return false;
- if (view == left_arrow_ || view == right_arrow_)
- return true;
- if (view->parent() != shelf_view_)
- return false;
- // The shelf item corresponding to |view| may have been removed from the
- // model.
- if (!shelf_view_->ShouldShowTooltipForChildView(view))
- return false;
- const gfx::Rect screen_bounds = view->GetBoundsInScreen();
- gfx::Rect visible_bounds_in_screen = visible_space_;
- views::View::ConvertRectToScreen(this, &visible_bounds_in_screen);
- return visible_bounds_in_screen.Contains(screen_bounds);
- }
- bool ScrollableShelfView::ShouldHideTooltip(
- const gfx::Point& cursor_location) const {
- if ((ShouldShowLeftArrow() &&
- left_arrow_->GetMirroredBounds().Contains(cursor_location)) ||
- (ShouldShowRightArrow() &&
- right_arrow_->GetMirroredBounds().Contains(cursor_location))) {
- return false;
- }
- // Should hide the tooltip if |cursor_location| is not in |visible_space_|.
- if (!visible_space_.Contains(cursor_location))
- return true;
- gfx::Point location_in_shelf_view = cursor_location;
- views::View::ConvertPointToTarget(this, shelf_view_, &location_in_shelf_view);
- return shelf_view_->ShouldHideTooltip(location_in_shelf_view);
- }
- const std::vector<aura::Window*> ScrollableShelfView::GetOpenWindowsForView(
- views::View* view) {
- if (!view || view->parent() != shelf_view_)
- return std::vector<aura::Window*>();
- return shelf_view_->GetOpenWindowsForView(view);
- }
- std::u16string ScrollableShelfView::GetTitleForView(
- const views::View* view) const {
- if (!view || !view->parent())
- return std::u16string();
- if (view->parent() == shelf_view_)
- return shelf_view_->GetTitleForView(view);
- if (view == left_arrow_)
- return l10n_util::GetStringUTF16(IDS_SHELF_PREVIOUS);
- if (view == right_arrow_)
- return l10n_util::GetStringUTF16(IDS_SHELF_NEXT);
- return std::u16string();
- }
- views::View* ScrollableShelfView::GetViewForEvent(const ui::Event& event) {
- if (event.target() == GetWidget()->GetNativeWindow())
- return this;
- return nullptr;
- }
- void ScrollableShelfView::ScheduleScrollForItemDragIfNeeded(
- const gfx::Rect& item_bounds_in_screen) {
- gfx::Rect visible_space_in_screen = visible_space_;
- views::View::ConvertRectToScreen(this, &visible_space_in_screen);
- drag_item_bounds_in_screen_.emplace(item_bounds_in_screen);
- if (AreBoundsWithinVisibleSpace(*drag_item_bounds_in_screen_)) {
- page_flip_timer_.AbandonAndStop();
- return;
- }
- if (!page_flip_timer_.IsRunning()) {
- page_flip_timer_.Start(FROM_HERE, page_flip_time_threshold_, this,
- &ScrollableShelfView::OnPageFlipTimer);
- }
- }
- void ScrollableShelfView::CancelScrollForItemDrag() {
- drag_item_bounds_in_screen_.reset();
- if (page_flip_timer_.IsRunning())
- page_flip_timer_.AbandonAndStop();
- }
- void ScrollableShelfView::OnImplicitAnimationsCompleted() {
- during_scroll_animation_ = false;
- Layout();
- EnableShelfRoundedCorners(/*enable=*/false);
- if (scroll_status_ != kAlongMainAxisScroll)
- UpdateTappableIconIndices();
- // Notifies ChromeVox of the changed location at the end of animation.
- shelf_view_->NotifyAccessibilityEvent(ax::mojom::Event::kLocationChanged,
- /*send_native_event=*/true);
- if (!drag_item_bounds_in_screen_ ||
- AreBoundsWithinVisibleSpace(*drag_item_bounds_in_screen_)) {
- return;
- }
- // Keep scrolling if the dragged shelf item is outside of the visible space.
- page_flip_timer_.Start(FROM_HERE, page_flip_time_threshold_, this,
- &ScrollableShelfView::OnPageFlipTimer);
- }
- bool ScrollableShelfView::ShouldShowLeftArrow() const {
- return (layout_strategy_ == kShowLeftArrowButton) ||
- (layout_strategy_ == kShowButtons);
- }
- bool ScrollableShelfView::ShouldShowRightArrow() const {
- return (layout_strategy_ == kShowRightArrowButton) ||
- (layout_strategy_ == kShowButtons);
- }
- gfx::Rect ScrollableShelfView::GetAvailableLocalBounds(
- bool use_target_bounds) const {
- return use_target_bounds
- ? gfx::Rect(GetShelf()->hotseat_widget()->GetTargetBounds().size())
- : GetLocalBounds();
- }
- gfx::Insets ScrollableShelfView::CalculateMirroredPaddingForDisplayCentering(
- bool use_target_bounds) const {
- const int icons_size =
- shelf_view_->GetSizeOfAppButtons(shelf_view_->number_of_visible_apps(),
- shelf_view_->GetButtonSize()) +
- 2 * ShelfConfig::Get()->GetAppIconEndPadding();
- const gfx::Rect display_bounds =
- screen_util::GetDisplayBoundsWithShelf(GetWidget()->GetNativeWindow());
- const int display_size_primary = GetShelf()->PrimaryAxisValue(
- display_bounds.width(), display_bounds.height());
- const int gap = (display_size_primary - icons_size) / 2;
- // Calculates paddings in view coordinates.
- const gfx::Rect screen_bounds =
- use_target_bounds ? GetShelf()->hotseat_widget()->GetTargetBounds()
- : GetBoundsInScreen();
- int before_padding =
- gap - GetShelf()->PrimaryAxisValue(
- ShouldAdaptToRTL()
- ? display_bounds.right() - screen_bounds.right()
- : screen_bounds.x() - display_bounds.x(),
- screen_bounds.y() - display_bounds.y());
- int after_padding =
- gap - GetShelf()->PrimaryAxisValue(
- ShouldAdaptToRTL()
- ? screen_bounds.x() - display_bounds.x()
- : display_bounds.right() - screen_bounds.right(),
- display_bounds.bottom() - screen_bounds.bottom());
- // Checks whether there is enough space to ensure |base_padding_|. Returns
- // empty insets if not.
- if (before_padding < 0 || after_padding < 0)
- return gfx::Insets();
- gfx::Insets padding_insets;
- if (GetShelf()->IsHorizontalAlignment()) {
- padding_insets = gfx::Insets::TLBR(0, before_padding, 0, after_padding);
- if (ShouldAdaptToRTL())
- padding_insets = GetMirroredInsets(padding_insets);
- } else {
- padding_insets = gfx::Insets::TLBR(before_padding, 0, after_padding, 0);
- }
- return padding_insets;
- }
- bool ScrollableShelfView::ShouldHandleGestures(const ui::GestureEvent& event) {
- // ScrollableShelfView only handles the gesture scrolling along the main axis.
- // For other gesture events, including the scrolling across the main axis,
- // they are handled by ShelfView.
- if (scroll_status_ == kNotInScroll && !event.IsScrollGestureEvent())
- return false;
- if (event.type() == ui::ET_GESTURE_SCROLL_BEGIN) {
- CHECK_EQ(scroll_status_, kNotInScroll);
- float main_offset = event.details().scroll_x_hint();
- float cross_offset = event.details().scroll_y_hint();
- if (!GetShelf()->IsHorizontalAlignment())
- std::swap(main_offset, cross_offset);
- if (std::abs(main_offset) < std::abs(cross_offset)) {
- scroll_status_ = kAcrossMainAxisScroll;
- } else if (layout_strategy_ != kNotShowArrowButtons) {
- // Note that if the scrollable shelf is not in overflow mode, scroll along
- // the main axis should not make any UI differences. Do not handle scroll
- // in this scenario.
- scroll_status_ = kAlongMainAxisScroll;
- }
- }
- bool should_handle_gestures = scroll_status_ == kAlongMainAxisScroll;
- if (scroll_status_ == kAlongMainAxisScroll &&
- event.type() == ui::ET_GESTURE_SCROLL_BEGIN) {
- scroll_offset_before_main_axis_scrolling_ = scroll_offset_;
- layout_strategy_before_main_axis_scrolling_ = layout_strategy_;
- // The change in |scroll_status_| may lead to update on the gradient zone.
- MaybeUpdateGradientZone();
- }
- if (event.type() == ui::ET_GESTURE_END)
- ResetScrollStatus();
- return should_handle_gestures;
- }
- void ScrollableShelfView::ResetScrollStatus() {
- scroll_status_ = kNotInScroll;
- scroll_offset_before_main_axis_scrolling_ = gfx::Vector2dF();
- layout_strategy_before_main_axis_scrolling_ = kNotShowArrowButtons;
- // The change in |scroll_status_| may lead to update on the gradient zone.
- MaybeUpdateGradientZone();
- }
- bool ScrollableShelfView::ProcessGestureEvent(const ui::GestureEvent& event) {
- if (layout_strategy_ == kNotShowArrowButtons)
- return true;
- // Handle scroll-related events, but don't do anything special for begin and
- // end.
- if (event.type() == ui::ET_GESTURE_SCROLL_BEGIN) {
- DCHECK(!presentation_time_recorder_);
- if (Shell::Get()->IsInTabletMode()) {
- if (Shell::Get()->app_list_controller()->IsVisible(
- GetDisplayIdForView(this))) {
- presentation_time_recorder_ = CreatePresentationTimeHistogramRecorder(
- GetWidget()->GetCompositor(),
- scrollable_shelf_constants::
- kScrollDraggingTabletLauncherVisibleHistogram,
- scrollable_shelf_constants::
- kScrollDraggingTabletLauncherVisibleMaxLatencyHistogram);
- } else {
- presentation_time_recorder_ = CreatePresentationTimeHistogramRecorder(
- GetWidget()->GetCompositor(),
- scrollable_shelf_constants::
- kScrollDraggingTabletLauncherHiddenHistogram,
- scrollable_shelf_constants::
- kScrollDraggingTabletLauncherHiddenMaxLatencyHistogram);
- }
- } else {
- if (Shell::Get()->app_list_controller()->IsVisible(
- GetDisplayIdForView(this))) {
- presentation_time_recorder_ = CreatePresentationTimeHistogramRecorder(
- GetWidget()->GetCompositor(),
- scrollable_shelf_constants::
- kScrollDraggingClamshellLauncherVisibleHistogram,
- scrollable_shelf_constants::
- kScrollDraggingClamshellLauncherVisibleMaxLatencyHistogram);
- } else {
- presentation_time_recorder_ = CreatePresentationTimeHistogramRecorder(
- GetWidget()->GetCompositor(),
- scrollable_shelf_constants::
- kScrollDraggingClamshellLauncherHiddenHistogram,
- scrollable_shelf_constants::
- kScrollDraggingClamshellLauncherHiddenMaxLatencyHistogram);
- }
- }
- return true;
- }
- if (event.type() == ui::ET_GESTURE_END) {
- // Do not reset |presentation_time_recorder_| in ui::ET_GESTURE_SCROLL_END
- // event because it may not exist due to gesture fling.
- presentation_time_recorder_.reset();
- // The type of scrolling offset is float to ensure that ScrollableShelfView
- // is responsive to slow gesture scrolling. However, after offset
- // adjustment, the scrolling offset should be floored.
- scroll_offset_ = gfx::ToFlooredVector2d(scroll_offset_);
- // If the scroll animation is created, tappable icon indices are updated
- // at the end of animation.
- if (!AdjustOffset() && !during_scroll_animation_)
- UpdateTappableIconIndices();
- return true;
- }
- if (event.type() == ui::ET_SCROLL_FLING_START) {
- const bool is_horizontal_alignment = GetShelf()->IsHorizontalAlignment();
- if (!ShouldHandleScroll(gfx::Vector2dF(event.details().velocity_x(),
- event.details().velocity_y()),
- /*is_gesture_fling=*/true)) {
- return false;
- }
- int scroll_velocity = is_horizontal_alignment
- ? event.details().velocity_x()
- : event.details().velocity_y();
- if (ShouldAdaptToRTL())
- scroll_velocity = -scroll_velocity;
- float page_scrolling_offset = CalculatePageScrollingOffset(
- scroll_velocity < 0, layout_strategy_before_main_axis_scrolling_);
- // Only starts animation when scroll distance is greater than zero.
- if (std::fabs(page_scrolling_offset) > 0.f) {
- ScrollToMainOffset((is_horizontal_alignment
- ? scroll_offset_before_main_axis_scrolling_.x()
- : scroll_offset_before_main_axis_scrolling_.y()) +
- page_scrolling_offset,
- /*animating=*/true);
- }
- return true;
- }
- if (event.type() != ui::ET_GESTURE_SCROLL_UPDATE)
- return false;
- float scroll_delta = 0.f;
- const bool is_horizontal = GetShelf()->IsHorizontalAlignment();
- if (is_horizontal) {
- scroll_delta = -event.details().scroll_x();
- scroll_delta = ShouldAdaptToRTL() ? -scroll_delta : scroll_delta;
- } else {
- scroll_delta = -event.details().scroll_y();
- }
- // Return early if scrollable shelf cannot be scrolled anymore because it has
- // reached to the end.
- const float current_scroll_offset = CalculateMainAxisScrollDistance();
- if ((current_scroll_offset == 0.f && scroll_delta <= 0.f) ||
- (current_scroll_offset == CalculateScrollUpperBound(GetSpaceForIcons()) &&
- scroll_delta >= 0.f)) {
- return true;
- }
- DCHECK(presentation_time_recorder_);
- presentation_time_recorder_->RequestNext();
- if (is_horizontal)
- ScrollByXOffset(scroll_delta, /*animate=*/false);
- else
- ScrollByYOffset(scroll_delta, /*animate=*/false);
- return true;
- }
- void ScrollableShelfView::HandleMouseWheelEvent(ui::MouseWheelEvent* event) {
- // Note that the scrolling from touchpad is propagated as mouse wheel event.
- // Let the shelf handle mouse wheel events over the empty area of the shelf
- // view, as these events would be ignored by the scrollable shelf view.
- gfx::Point location_in_shelf_view = event->location();
- View::ConvertPointToTarget(this, shelf_view_, &location_in_shelf_view);
- if (!shelf_view_->LocationInsideVisibleShelfItemBounds(
- location_in_shelf_view)) {
- GetShelf()->ProcessMouseWheelEvent(event);
- return;
- }
- if (!ShouldHandleScroll(gfx::Vector2dF(event->x_offset(), event->y_offset()),
- /*is_gesture_fling=*/false)) {
- return;
- }
- event->SetHandled();
- // Scrolling the mouse wheel may create multiple mouse wheel events at the
- // same time. If the scrollable shelf view is during scrolling animation at
- // this moment, do not handle the mouse wheel event.
- if (shelf_view_->layer()->GetAnimator()->is_animating())
- return;
- if (GetShelf()->IsHorizontalAlignment()) {
- const float x_offset = event->x_offset();
- const float y_offset = event->y_offset();
- // If the shelf is bottom aligned, we can scroll over the shelf contents if
- // the scroll is horizontal or vertical (in the case of a mousewheel
- // scroll). We take the biggest offset difference of the vertical and
- // horizontal components to determine the offset to scroll over the
- // contents.
- float max_absolute_offset =
- abs(x_offset) > abs(y_offset) ? x_offset : y_offset;
- ScrollByXOffset(
- CalculatePageScrollingOffset(max_absolute_offset < 0, layout_strategy_),
- /*animating=*/true);
- } else {
- ScrollByYOffset(
- CalculatePageScrollingOffset(event->y_offset() < 0, layout_strategy_),
- /*animating=*/true);
- }
- }
- void ScrollableShelfView::ScrollByXOffset(float x_offset, bool animating) {
- ScrollToMainOffset(scroll_offset_.x() + x_offset, animating);
- }
- void ScrollableShelfView::ScrollByYOffset(float y_offset, bool animating) {
- ScrollToMainOffset(scroll_offset_.y() + y_offset, animating);
- }
- void ScrollableShelfView::ScrollToMainOffset(float target_offset,
- bool animating) {
- if (animating) {
- StartShelfScrollAnimation(target_offset);
- } else {
- UpdateScrollOffset(target_offset);
- shelf_container_view_->TranslateShelfView(scroll_offset_);
- }
- }
- float ScrollableShelfView::CalculatePageScrollingOffset(
- bool forward,
- LayoutStrategy layout_strategy) const {
- // Returns zero if inputs are invalid.
- const bool invalid = (layout_strategy == kNotShowArrowButtons) ||
- (layout_strategy == kShowLeftArrowButton && forward) ||
- (layout_strategy == kShowRightArrowButton && !forward);
- if (invalid)
- return 0;
- float offset = CalculatePageScrollingOffsetInAbs(layout_strategy);
- if (!forward)
- offset = -offset;
- return offset;
- }
- float ScrollableShelfView::CalculatePageScrollingOffsetInAbs(
- LayoutStrategy layout_strategy) const {
- // Implement the arrow button handler in the same way with the gesture
- // scrolling. The key is to calculate the suitable scroll distance.
- float offset = 0.f;
- // The available space for icons excluding the area taken by arrow button(s).
- int space_excluding_arrow;
- const int space_needed_for_button = GetSumOfButtonSizeAndSpacing();
- if (layout_strategy == kShowRightArrowButton) {
- space_excluding_arrow =
- GetSpaceForIcons() - scrollable_shelf_constants::kArrowButtonGroupWidth;
- // After scrolling, the left arrow button will show. Adapts the offset
- // to the extra arrow button.
- const int offset_for_extra_arrow =
- scrollable_shelf_constants::kArrowButtonGroupWidth -
- ShelfConfig::Get()->GetAppIconEndPadding();
- const int mod = space_excluding_arrow % space_needed_for_button;
- offset = space_excluding_arrow - mod - offset_for_extra_arrow;
- } else if (layout_strategy == kShowButtons ||
- layout_strategy == kShowLeftArrowButton) {
- space_excluding_arrow =
- GetSpaceForIcons() -
- 2 * scrollable_shelf_constants::kArrowButtonGroupWidth;
- const int mod = space_excluding_arrow % space_needed_for_button;
- offset = space_excluding_arrow - mod;
- // Layout of kShowLeftArrowButton can be regarded as the layout of
- // kShowButtons with extra offset.
- if (layout_strategy == kShowLeftArrowButton) {
- const int extra_offset =
- -ShelfConfig::Get()->button_spacing() -
- (GetSpaceForIcons() -
- scrollable_shelf_constants::kArrowButtonGroupWidth) %
- space_needed_for_button +
- ShelfConfig::Get()->GetAppIconEndPadding();
- offset += extra_offset;
- }
- }
- // Ensure the return value to be non-negative. Note that if the screen is too
- // small (usually on the Linux emulator), `offset` may be negative.
- return std::fmax(offset, 0.f);
- }
- float ScrollableShelfView::CalculateTargetOffsetAfterScroll(
- float start_offset,
- float scroll_distance) const {
- float target_offset = start_offset;
- target_offset += scroll_distance;
- target_offset =
- CalculateClampedScrollOffset(target_offset, GetSpaceForIcons());
- LayoutStrategy layout_strategy_after_scroll =
- CalculateLayoutStrategy(target_offset, GetSpaceForIcons());
- target_offset = CalculateScrollDistanceAfterAdjustment(
- target_offset, layout_strategy_after_scroll);
- return target_offset;
- }
- GradientLayerDelegate::FadeZone
- ScrollableShelfView::CalculateStartGradientZone() const {
- if (!should_show_start_gradient_zone_)
- return GradientLayerDelegate::FadeZone();
- gfx::Rect zone_rect;
- bool fade_in = false;
- const bool is_horizontal_alignment = GetShelf()->IsHorizontalAlignment();
- if (is_horizontal_alignment) {
- int gradient_start;
- int gradient_end;
- // Calculates the bounds of the gradient zone. Enlarge the gradient zone by
- // one-pixel to offset the potential rounding error during rendering (we
- // also do it in CalculateEndGradientZone()).
- if (ShouldAdaptToRTL()) {
- const int border = visible_space_.right();
- gradient_start = border - scrollable_shelf_constants::kGradientZoneLength;
- gradient_end = border + 1;
- } else {
- const int border = visible_space_.x();
- gradient_start = border - 1;
- gradient_end = border + scrollable_shelf_constants::kGradientZoneLength;
- }
- zone_rect =
- gfx::Rect(gradient_start, 0, gradient_end - gradient_start, height());
- } else {
- zone_rect = gfx::Rect(0, visible_space_.y() - 1, width(),
- scrollable_shelf_constants::kGradientZoneLength + 1);
- }
- fade_in = !ShouldAdaptToRTL();
- return {zone_rect, fade_in, is_horizontal_alignment};
- }
- GradientLayerDelegate::FadeZone ScrollableShelfView::CalculateEndGradientZone()
- const {
- if (!should_show_end_gradient_zone_)
- return GradientLayerDelegate::FadeZone();
- gfx::Rect zone_rect;
- bool fade_in = false;
- const bool is_horizontal_alignment = GetShelf()->IsHorizontalAlignment();
- if (is_horizontal_alignment) {
- int gradient_start;
- int gradient_end;
- if (ShouldAdaptToRTL()) {
- const int border = visible_space_.x();
- gradient_start = border - 1;
- gradient_end = border + scrollable_shelf_constants::kGradientZoneLength;
- } else {
- const int border = visible_space_.right();
- gradient_start = border - scrollable_shelf_constants::kGradientZoneLength;
- gradient_end = border + 1;
- }
- zone_rect =
- gfx::Rect(gradient_start, 0, gradient_end - gradient_start, height());
- } else {
- zone_rect =
- gfx::Rect(0,
- visible_space_.bottom() -
- scrollable_shelf_constants::kGradientZoneLength,
- width(), scrollable_shelf_constants::kGradientZoneLength + 1);
- }
- fade_in = ShouldAdaptToRTL();
- return {zone_rect, fade_in, is_horizontal_alignment};
- }
- void ScrollableShelfView::UpdateGradientZoneState() {
- // The gradient zone is not painted when the focus ring shows in order to
- // display the focus ring correctly.
- if (focus_ring_activated_) {
- should_show_start_gradient_zone_ = false;
- should_show_end_gradient_zone_ = false;
- return;
- }
- if (during_scroll_animation_) {
- should_show_start_gradient_zone_ = true;
- should_show_end_gradient_zone_ = true;
- return;
- }
- should_show_start_gradient_zone_ = layout_strategy_ == kShowLeftArrowButton ||
- (layout_strategy_ == kShowButtons &&
- scroll_status_ == kAlongMainAxisScroll);
- should_show_end_gradient_zone_ = ShouldShowRightArrow();
- }
- void ScrollableShelfView::MaybeUpdateGradientZone() {
- if (!ShouldApplyMaskLayerGradientZone())
- return;
- // Fade zones should be updated if:
- // (1) Fade zone's visibility changes.
- // (2) Fade zone should show and the arrow button's location changes.
- UpdateGradientZoneState();
- const GradientLayerDelegate::FadeZone target_start_fade_zone =
- CalculateStartGradientZone();
- const GradientLayerDelegate::FadeZone target_end_fade_zone =
- CalculateEndGradientZone();
- const bool should_update_start_fade_zone =
- target_start_fade_zone.zone_rect !=
- gradient_layer_delegate_->start_fade_zone_bounds();
- const bool should_update_end_fade_zone =
- target_end_fade_zone.zone_rect !=
- gradient_layer_delegate_->end_fade_zone_bounds();
- if (!should_update_start_fade_zone && !should_update_end_fade_zone)
- return;
- PaintGradientZone(CalculateStartGradientZone(), CalculateEndGradientZone());
- }
- void ScrollableShelfView::PaintGradientZone(
- const GradientLayerDelegate::FadeZone& start_rect,
- const GradientLayerDelegate::FadeZone& end_rect) {
- gradient_layer_delegate_->set_start_fade_zone(start_rect);
- gradient_layer_delegate_->set_end_fade_zone(end_rect);
- SchedulePaint();
- }
- bool ScrollableShelfView::ShouldApplyMaskLayerGradientZone() const {
- return layout_strategy_ != LayoutStrategy::kNotShowArrowButtons;
- }
- float ScrollableShelfView::GetActualScrollOffset(
- float main_axis_scroll_distance,
- LayoutStrategy layout_strategy) const {
- return (layout_strategy == kShowButtons ||
- layout_strategy == kShowLeftArrowButton)
- ? (main_axis_scroll_distance +
- scrollable_shelf_constants::kArrowButtonGroupWidth -
- ShelfConfig::Get()->GetAppIconEndPadding())
- : main_axis_scroll_distance;
- }
- void ScrollableShelfView::UpdateTappableIconIndices() {
- // Scrollable shelf should be not under the scroll along the main axis, which
- // means that the decimal part of the main scroll offset should be zero.
- DCHECK(scroll_status_ != kAlongMainAxisScroll);
- // The value returned by CalculateMainAxisScrollDistance() can be casted into
- // an integer without losing precision since the decimal part is zero.
- const auto tappable_indices = CalculateTappableIconIndices(
- layout_strategy_, CalculateMainAxisScrollDistance());
- first_tappable_app_index_ = tappable_indices.first;
- last_tappable_app_index_ = tappable_indices.second;
- }
- std::pair<absl::optional<size_t>, absl::optional<size_t>>
- ScrollableShelfView::CalculateTappableIconIndices(
- ScrollableShelfView::LayoutStrategy layout_strategy,
- int scroll_distance_on_main_axis) const {
- const auto& visible_views_indices = shelf_view_->visible_views_indices();
- if (visible_views_indices.empty() || visible_space_.IsEmpty())
- return {absl::nullopt, absl::nullopt};
- if (layout_strategy == ScrollableShelfView::kNotShowArrowButtons) {
- return {visible_views_indices.front(), visible_views_indices.back()};
- }
- const int visible_size = GetShelf()->IsHorizontalAlignment()
- ? visible_space_.width()
- : visible_space_.height();
- const int space_needed_for_button = GetSumOfButtonSizeAndSpacing();
- // Note that some apps may have their |ShelfAppButton| views hidden, when they
- // are on an inactive desk. Therefore, the indices of tappable apps may not be
- // contiguous, so we need to map from a visible view index back to an app
- // index. The below are indices into the |visible_views_indices| vector.
- size_t first_visible_view_index;
- size_t last_visible_view_index;
- if (layout_strategy == kShowRightArrowButton ||
- layout_strategy == kShowButtons) {
- first_visible_view_index =
- scroll_distance_on_main_axis / space_needed_for_button +
- (layout_strategy == kShowButtons ? 1 : 0);
- last_visible_view_index =
- first_visible_view_index + visible_size / space_needed_for_button;
- const int end_of_last_visible_view =
- last_visible_view_index * space_needed_for_button +
- shelf_view_->GetButtonSize() - scroll_distance_on_main_axis;
- // It is very rare but |visible_size| may be smaller than
- // |space_needed_for_button| as reported in https://crbug.com/1094363.
- if (end_of_last_visible_view > visible_size &&
- last_visible_view_index > first_visible_view_index) {
- last_visible_view_index--;
- }
- } else {
- DCHECK_EQ(layout_strategy, kShowLeftArrowButton);
- last_visible_view_index = visible_views_indices.size() - 1;
- // In fuzz tests, `visible_size` may be smaller than
- // `space_needed_for_button` although it never happens on real devices.
- first_visible_view_index =
- visible_size >= space_needed_for_button
- ? last_visible_view_index - visible_size / space_needed_for_button +
- 1
- : last_visible_view_index;
- }
- DCHECK_LT(first_visible_view_index, visible_views_indices.size());
- DCHECK_LT(last_visible_view_index, visible_views_indices.size());
- return {visible_views_indices[first_visible_view_index],
- visible_views_indices[last_visible_view_index]};
- }
- views::View* ScrollableShelfView::FindFirstFocusableChild() {
- return shelf_view_->FindFirstFocusableChild();
- }
- views::View* ScrollableShelfView::FindLastFocusableChild() {
- return shelf_view_->FindLastFocusableChild();
- }
- int ScrollableShelfView::GetSpaceForIcons() const {
- return GetShelf()->IsHorizontalAlignment() ? available_space_.width()
- : available_space_.height();
- }
- bool ScrollableShelfView::CanFitAllAppsWithoutScrolling(
- const gfx::Size& available_size,
- const gfx::Size& icons_preferred_size) const {
- const int available_length =
- (GetShelf()->IsHorizontalAlignment() ? available_size.width()
- : available_size.height());
- int preferred_length = GetShelf()->IsHorizontalAlignment()
- ? icons_preferred_size.width()
- : icons_preferred_size.height();
- preferred_length += 2 * ShelfConfig::Get()->GetAppIconEndPadding();
- return available_length >= preferred_length;
- }
- bool ScrollableShelfView::ShouldHandleScroll(const gfx::Vector2dF& offset,
- bool is_gesture_scrolling) const {
- // When the shelf is aligned at the bottom, a horizontal mousewheel scroll may
- // also be handled by the ScrollableShelf if the offset along the main axis is
- // 0. This case is mainly triggered by an event generated in the MouseWheel,
- // but not in the touchpad, as touchpads events are caught on ScrollEvent.
- // If there is an x component to the scroll, consider this instead of the y
- // axis because the horizontal scroll could move the scrollable shelf.
- const float main_axis_offset =
- GetShelf()->IsHorizontalAlignment() && offset.x() != 0 ? offset.x()
- : offset.y();
- const int threshold =
- is_gesture_scrolling
- ? scrollable_shelf_constants::kGestureFlingVelocityThreshold
- : scrollable_shelf_constants::kScrollOffsetThreshold;
- return abs(main_axis_offset) > threshold;
- }
- bool ScrollableShelfView::AdjustOffset() {
- const float offset = CalculateAdjustmentOffset(
- CalculateMainAxisScrollDistance(), layout_strategy_, GetSpaceForIcons());
- // Returns early when it does not need to adjust the shelf view's location.
- if (!offset)
- return false;
- if (GetShelf()->IsHorizontalAlignment())
- ScrollByXOffset(offset, /*animate=*/true);
- else
- ScrollByYOffset(offset, /*animate=*/true);
- return true;
- }
- float ScrollableShelfView::CalculateAdjustmentOffset(
- int main_axis_scroll_distance,
- LayoutStrategy layout_strategy,
- int available_space_for_icons) const {
- // Scrollable shelf should be not under the scroll along the main axis, which
- // means that the decimal part of the main scroll offset should be zero.
- DCHECK(scroll_status_ != kAlongMainAxisScroll);
- // Returns early when it does not need to adjust the shelf view's location.
- if (layout_strategy == kNotShowArrowButtons ||
- main_axis_scroll_distance >=
- CalculateScrollUpperBound(available_space_for_icons)) {
- return 0;
- }
- // Because the decimal part of the scroll offset is zero, it is meaningful
- // to use modulo operation here.
- const int remainder = static_cast<int>(GetActualScrollOffset(
- main_axis_scroll_distance, layout_strategy)) %
- GetSumOfButtonSizeAndSpacing();
- int offset = remainder > GetGestureDragThreshold()
- ? GetSumOfButtonSizeAndSpacing() - remainder
- : -remainder;
- return offset;
- }
- int ScrollableShelfView::CalculateScrollDistanceAfterAdjustment(
- int main_axis_scroll_distance,
- LayoutStrategy layout_strategy) const {
- return main_axis_scroll_distance +
- CalculateAdjustmentOffset(main_axis_scroll_distance, layout_strategy,
- GetSpaceForIcons());
- }
- void ScrollableShelfView::UpdateAvailableSpace() {
- if (!is_padding_configured_externally_) {
- edge_padding_insets_ =
- CalculateMirroredEdgePadding(/*use_target_bounds=*/false);
- }
- available_space_ = GetLocalBounds();
- available_space_.Inset(edge_padding_insets_);
- // The hotseat uses |available_space_| to determine where to show its
- // background, so notify it when it is recalculated.
- if (HotseatWidget::ShouldShowHotseatBackground())
- GetShelf()->hotseat_widget()->UpdateTranslucentBackground();
- }
- gfx::Rect ScrollableShelfView::CalculateVisibleSpace(
- LayoutStrategy layout_strategy) const {
- const bool in_tablet_mode = Shell::Get()->IsInTabletMode();
- if (layout_strategy == kNotShowArrowButtons && !in_tablet_mode)
- return GetAvailableLocalBounds(/*use_target_bounds=*/false);
- const bool should_show_left_arrow =
- (layout_strategy == kShowLeftArrowButton) ||
- (layout_strategy == kShowButtons);
- const bool should_show_right_arrow =
- (layout_strategy == kShowRightArrowButton) ||
- (layout_strategy == kShowButtons);
- const int before_padding =
- (should_show_left_arrow
- ? scrollable_shelf_constants::kArrowButtonGroupWidth
- : 0);
- const int after_padding =
- (should_show_right_arrow
- ? scrollable_shelf_constants::kArrowButtonGroupWidth
- : 0);
- gfx::Insets visible_space_insets;
- if (ShouldAdaptToRTL()) {
- visible_space_insets =
- gfx::Insets::TLBR(0, after_padding, 0, before_padding);
- } else {
- visible_space_insets =
- GetShelf()->IsHorizontalAlignment()
- ? gfx::Insets::TLBR(0, before_padding, 0, after_padding)
- : gfx::Insets::TLBR(before_padding, 0, after_padding, 0);
- }
- visible_space_insets -= CalculateRipplePaddingInsets();
- gfx::Rect visible_space = available_space_;
- visible_space.Inset(visible_space_insets);
- return visible_space;
- }
- gfx::Insets ScrollableShelfView::CalculateRipplePaddingInsets() const {
- // Indicates whether it is in tablet mode with hotseat enabled.
- const bool in_tablet_mode = Shell::Get()->IsInTabletMode();
- const int ripple_padding =
- ShelfConfig::Get()->scrollable_shelf_ripple_padding();
- const int before_padding =
- (in_tablet_mode && !ShouldShowLeftArrow()) ? 0 : ripple_padding;
- const int after_padding =
- (in_tablet_mode && !ShouldShowRightArrow()) ? 0 : ripple_padding;
- if (ShouldAdaptToRTL())
- return gfx::Insets::TLBR(0, after_padding, 0, before_padding);
- return GetShelf()->IsHorizontalAlignment()
- ? gfx::Insets::TLBR(0, before_padding, 0, after_padding)
- : gfx::Insets::TLBR(before_padding, 0, after_padding, 0);
- }
- gfx::RoundedCornersF
- ScrollableShelfView::CalculateShelfContainerRoundedCorners() const {
- // This function may access TabletModeController during destruction of
- // Hotseat. However, TabletModeController is destructed before Hotseat. So
- // check the pointer explicitly here.
- // TODO(https://crbug.com/1067490): reorder the destruction order in
- // Shell::~Shell then remove the explicit check.
- const bool is_in_tablet_mode =
- Shell::Get()->tablet_mode_controller() && Shell::Get()->IsInTabletMode();
- if (!is_in_tablet_mode)
- return gfx::RoundedCornersF();
- const bool is_horizontal_alignment = GetShelf()->IsHorizontalAlignment();
- const float radius = (is_horizontal_alignment ? height() : width()) / 2.f;
- int upper_left = ShouldShowLeftArrow() ? 0 : radius;
- int upper_right;
- if (is_horizontal_alignment)
- upper_right = ShouldShowRightArrow() ? 0 : radius;
- else
- upper_right = ShouldShowLeftArrow() ? 0 : radius;
- int lower_right = ShouldShowRightArrow() ? 0 : radius;
- int lower_left;
- if (is_horizontal_alignment)
- lower_left = ShouldShowLeftArrow() ? 0 : radius;
- else
- lower_left = ShouldShowRightArrow() ? 0 : radius;
- if (ShouldAdaptToRTL()) {
- std::swap(upper_left, upper_right);
- std::swap(lower_left, lower_right);
- }
- return gfx::RoundedCornersF(upper_left, upper_right, lower_right, lower_left);
- }
- void ScrollableShelfView::OnPageFlipTimer() {
- gfx::Rect visible_space_in_screen = visible_space_;
- views::View::ConvertRectToScreen(this, &visible_space_in_screen);
- // Calculates the page scrolling direction based on the drag item bounds and
- // the bounds of the visible space.
- bool should_scroll_to_next;
- if (ShouldAdaptToRTL()) {
- should_scroll_to_next =
- drag_item_bounds_in_screen_->x() < visible_space_in_screen.x();
- } else {
- should_scroll_to_next = GetShelf()->IsHorizontalAlignment()
- ? drag_item_bounds_in_screen_->right() >
- visible_space_in_screen.right()
- : drag_item_bounds_in_screen_->bottom() >
- visible_space_in_screen.bottom();
- }
- ScrollToNewPage(/*forward=*/should_scroll_to_next);
- if (test_observer_)
- test_observer_->OnPageFlipTimerFired();
- }
- bool ScrollableShelfView::AreBoundsWithinVisibleSpace(
- const gfx::Rect& bounds_in_screen) const {
- if (bounds_in_screen.IsEmpty())
- return false;
- gfx::Rect visible_space_in_screen = visible_space_;
- views::View::ConvertRectToScreen(this, &visible_space_in_screen);
- if (GetShelf()->IsHorizontalAlignment()) {
- return bounds_in_screen.x() >= visible_space_in_screen.x() &&
- bounds_in_screen.right() <= visible_space_in_screen.right();
- }
- return bounds_in_screen.y() >= visible_space_in_screen.y() &&
- bounds_in_screen.bottom() <= visible_space_in_screen.bottom();
- }
- bool ScrollableShelfView::ShouldDelegateScrollToShelf(
- const ui::ScrollEvent& event) const {
- // When the shelf is not aligned in the bottom, the events should be
- // propagated and handled as MouseWheel events.
- if (event.type() != ui::ET_SCROLL)
- return false;
- const float main_offset =
- GetShelf()->IsHorizontalAlignment() ? event.x_offset() : event.y_offset();
- const float cross_offset =
- GetShelf()->IsHorizontalAlignment() ? event.y_offset() : event.x_offset();
- // We only delegate to the shelf scroll events across the main axis,
- // otherwise, let them propagate and be handled as MouseWheel Events.
- return std::abs(main_offset) < std::abs(cross_offset);
- }
- float ScrollableShelfView::CalculateMainAxisScrollDistance() const {
- return GetShelf()->IsHorizontalAlignment() ? scroll_offset_.x()
- : scroll_offset_.y();
- }
- void ScrollableShelfView::UpdateScrollOffset(float target_offset) {
- target_offset =
- CalculateClampedScrollOffset(target_offset, GetSpaceForIcons());
- if (GetShelf()->IsHorizontalAlignment())
- scroll_offset_.set_x(target_offset);
- else
- scroll_offset_.set_y(target_offset);
- // Calculating the layout strategy relies on |scroll_offset_|.
- LayoutStrategy new_strategy = CalculateLayoutStrategy(
- CalculateMainAxisScrollDistance(), GetSpaceForIcons());
- const bool strategy_needs_update = (layout_strategy_ != new_strategy);
- if (strategy_needs_update) {
- layout_strategy_ = new_strategy;
- const bool has_gradient_zone = layer()->layer_mask_layer();
- const bool should_have_gradient_zone = ShouldApplyMaskLayerGradientZone();
- if (has_gradient_zone && !should_have_gradient_zone) {
- PaintGradientZone(GradientLayerDelegate::FadeZone(),
- GradientLayerDelegate::FadeZone());
- layer()->SetMaskLayer(nullptr);
- } else if (!has_gradient_zone && should_have_gradient_zone) {
- gradient_layer_delegate_->layer()->SetBounds(layer()->bounds());
- layer()->SetMaskLayer(gradient_layer_delegate_->layer());
- }
- InvalidateLayout();
- }
- visible_space_ = CalculateVisibleSpace(layout_strategy_);
- if (scroll_status_ != kAlongMainAxisScroll)
- UpdateTappableIconIndices();
- }
- void ScrollableShelfView::UpdateAvailableSpaceAndScroll() {
- UpdateAvailableSpace();
- UpdateScrollOffset(CalculateMainAxisScrollDistance());
- }
- int ScrollableShelfView::CalculateScrollOffsetForTargetAvailableSpace(
- const gfx::Rect& target_space) const {
- // Ensures that the scroll offset is legal under the updated available space.
- const int available_space_for_icons =
- GetShelf()->PrimaryAxisValue(target_space.width(), target_space.height());
- int target_scroll_offset = CalculateClampedScrollOffset(
- CalculateMainAxisScrollDistance(), available_space_for_icons);
- // Calculates the layout strategy based on the new scroll offset.
- LayoutStrategy new_strategy =
- CalculateLayoutStrategy(target_scroll_offset, available_space_for_icons);
- // Adjusts the scroll offset with the new strategy.
- target_scroll_offset += CalculateAdjustmentOffset(
- target_scroll_offset, new_strategy, available_space_for_icons);
- return target_scroll_offset;
- }
- bool ScrollableShelfView::ShouldCountActivatedInkDrop(
- const views::View* sender) const {
- bool should_count = false;
- // When scrolling shelf by gestures, the shelf icon's ink drop ripple may be
- // activated accidentally. So ignore the ink drop activity during animation.
- if (during_scroll_animation_)
- return should_count;
- if (!first_tappable_app_index_.has_value() ||
- !last_tappable_app_index_.has_value()) {
- // Verify that `first_tappable_app_index_` and `last_tappable_app_index_`
- // are both illegal. In that case, return early.
- DCHECK(first_tappable_app_index_ == last_tappable_app_index_);
- return false;
- }
- // The ink drop needs to be clipped only if |sender| is the app at one of the
- // corners of the shelf. This happens if it is either the first or the last
- // tappable app and no arrow is showing on its side.
- if (shelf_view_->view_model()->view_at(first_tappable_app_index_.value()) ==
- sender) {
- should_count = !(layout_strategy_ == kShowButtons ||
- layout_strategy_ == kShowLeftArrowButton);
- } else if (shelf_view_->view_model()->view_at(
- last_tappable_app_index_.value()) == sender) {
- should_count = !(layout_strategy_ == kShowButtons ||
- layout_strategy_ == kShowRightArrowButton);
- }
- return should_count;
- }
- void ScrollableShelfView::EnableShelfRoundedCorners(bool enable) {
- // Only enable shelf rounded corners in tablet mode. Note that we allow
- // disabling rounded corners in clamshell. Because when switching to clamshell
- // from tablet, this method may be called after tablet mode ends.
- // TODO(https://crbug.com/1067490): reorder the destruction order in
- // Shell::~Shell then remove the explicit check.
- const bool is_in_tablet_mode =
- Shell::Get()->tablet_mode_controller() && Shell::Get()->IsInTabletMode();
- if (enable && !is_in_tablet_mode)
- return;
- ui::Layer* layer = shelf_container_view_->layer();
- const bool has_rounded_corners = !(layer->rounded_corner_radii().IsEmpty());
- if (enable == has_rounded_corners)
- return;
- // In non-overflow mode, only apply layer clip on |shelf_container_view_|
- // when the ripple ring of the first/last shelf icon shows.
- // Note that |layout_strategy_| may update while EnableShelfRoundedCorners()
- // is not called. For example, the first icon's context menu shows, then the
- // system notification makes shelf view enter overflow mode. So
- // |layer_clip_in_non_overflow_| updates regardless of |layout_strategy_|.
- layer_clip_in_non_overflow_ = enable;
- if (layout_strategy_ == kNotShowArrowButtons)
- EnableLayerClipOnShelfContainerView(layer_clip_in_non_overflow_);
- layer->SetRoundedCornerRadius(enable ? CalculateShelfContainerRoundedCorners()
- : gfx::RoundedCornersF());
- if (!layer->is_fast_rounded_corner())
- layer->SetIsFastRoundedCorner(/*enable=*/true);
- }
- void ScrollableShelfView::OnActiveInkDropChange(bool increase) {
- if (increase)
- ++activated_corner_buttons_;
- else
- --activated_corner_buttons_;
- // When long pressing icons, sometimes there are more ripple animations
- // pending over others buttons. Only activate rounded corners when at least
- // one button needs them.
- // NOTE: `last_tappable_app_index_` is used to compute whether a button is
- // at the corner or not. Meanwhile, `last_tappable_app_index_` could update
- // before the button fade out animation ends. As a result, in edge cases
- // `activated_corner_buttons_` could be greater than 2.
- CHECK_GE(activated_corner_buttons_, 0);
- EnableShelfRoundedCorners(activated_corner_buttons_ > 0);
- }
- bool ScrollableShelfView::ShouldEnableLayerClip() const {
- // Always use layer clip in overflow mode.
- if (layout_strategy_ != LayoutStrategy::kNotShowArrowButtons)
- return true;
- // TODO(https://crbug.com/1067490): reorder the destruction order in
- // Shell::~Shell then remove the explicit check.
- const bool is_in_tablet_mode =
- Shell::Get()->tablet_mode_controller() && Shell::Get()->IsInTabletMode();
- // In clamshell, only use layer clip in overflow mode.
- if (!is_in_tablet_mode)
- return false;
- // In tablet mode, whether using layer clip in non-overflow mode depends on
- // |layer_clip_in_non_overflow_|.
- return layer_clip_in_non_overflow_;
- }
- void ScrollableShelfView::EnableLayerClipOnShelfContainerView(bool enable) {
- if (!enable) {
- shelf_container_view_->layer()->SetClipRect(gfx::Rect());
- return;
- }
- // |visible_space_| is in local coordinates. It should be transformed into
- // |shelf_container_view_|'s coordinates for layer clip.
- gfx::RectF visible_space_in_shelf_container_coordinates(visible_space_);
- views::View::ConvertRectToTarget(
- this, shelf_container_view_,
- &visible_space_in_shelf_container_coordinates);
- shelf_container_view_->layer()->SetClipRect(
- gfx::ToEnclosedRect(visible_space_in_shelf_container_coordinates));
- }
- int ScrollableShelfView::CalculateShelfIconsPreferredLength() const {
- const gfx::Size shelf_preferred_size(
- shelf_container_view_->GetPreferredSize());
- const int preferred_length =
- (GetShelf()->IsHorizontalAlignment() ? shelf_preferred_size.width()
- : shelf_preferred_size.height());
- return preferred_length + 2 * ShelfConfig::Get()->GetAppIconEndPadding();
- }
- } // namespace ash
|