apps_grid_view.cc 117 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081208220832084208520862087208820892090209120922093209420952096209720982099210021012102210321042105210621072108210921102111211221132114211521162117211821192120212121222123212421252126212721282129213021312132213321342135213621372138213921402141214221432144214521462147214821492150215121522153215421552156215721582159216021612162216321642165216621672168216921702171217221732174217521762177217821792180218121822183218421852186218721882189219021912192219321942195219621972198219922002201220222032204220522062207220822092210221122122213221422152216221722182219222022212222222322242225222622272228222922302231223222332234223522362237223822392240224122422243224422452246224722482249225022512252225322542255225622572258225922602261226222632264226522662267226822692270227122722273227422752276227722782279228022812282228322842285228622872288228922902291229222932294229522962297229822992300230123022303230423052306230723082309231023112312231323142315231623172318231923202321232223232324232523262327232823292330233123322333233423352336233723382339234023412342234323442345234623472348234923502351235223532354235523562357235823592360236123622363236423652366236723682369237023712372237323742375237623772378237923802381238223832384238523862387238823892390239123922393239423952396239723982399240024012402240324042405240624072408240924102411241224132414241524162417241824192420242124222423242424252426242724282429243024312432243324342435243624372438243924402441244224432444244524462447244824492450245124522453245424552456245724582459246024612462246324642465246624672468246924702471247224732474247524762477247824792480248124822483248424852486248724882489249024912492249324942495249624972498249925002501250225032504250525062507250825092510251125122513251425152516251725182519252025212522252325242525252625272528252925302531253225332534253525362537253825392540254125422543254425452546254725482549255025512552255325542555255625572558255925602561256225632564256525662567256825692570257125722573257425752576257725782579258025812582258325842585258625872588258925902591259225932594259525962597259825992600260126022603260426052606260726082609261026112612261326142615261626172618261926202621262226232624262526262627262826292630263126322633263426352636263726382639264026412642264326442645264626472648264926502651265226532654265526562657265826592660266126622663266426652666266726682669267026712672267326742675267626772678267926802681268226832684268526862687268826892690269126922693269426952696269726982699270027012702270327042705270627072708270927102711271227132714271527162717271827192720272127222723272427252726272727282729273027312732273327342735273627372738273927402741274227432744274527462747274827492750275127522753275427552756275727582759276027612762276327642765276627672768276927702771277227732774277527762777277827792780278127822783278427852786278727882789279027912792279327942795279627972798279928002801280228032804280528062807280828092810281128122813281428152816281728182819282028212822282328242825282628272828282928302831283228332834283528362837283828392840284128422843284428452846284728482849285028512852285328542855285628572858285928602861286228632864286528662867286828692870287128722873287428752876287728782879288028812882288328842885288628872888288928902891289228932894289528962897289828992900290129022903290429052906290729082909291029112912291329142915291629172918291929202921292229232924292529262927292829292930293129322933293429352936293729382939294029412942294329442945294629472948294929502951295229532954295529562957295829592960296129622963296429652966296729682969297029712972297329742975297629772978297929802981298229832984298529862987298829892990299129922993299429952996299729982999300030013002300330043005300630073008300930103011301230133014301530163017301830193020302130223023302430253026302730283029303030313032303330343035303630373038303930403041304230433044304530463047304830493050305130523053305430553056305730583059306030613062306330643065306630673068306930703071307230733074307530763077307830793080308130823083308430853086308730883089309030913092309330943095309630973098309931003101310231033104310531063107310831093110311131123113311431153116311731183119312031213122312331243125312631273128312931303131313231333134313531363137313831393140314131423143314431453146314731483149315031513152315331543155315631573158315931603161316231633164316531663167316831693170317131723173317431753176317731783179318031813182318331843185318631873188318931903191319231933194319531963197319831993200320132023203320432053206320732083209321032113212321332143215321632173218321932203221322232233224322532263227322832293230323132323233323432353236323732383239324032413242324332443245324632473248324932503251325232533254325532563257325832593260326132623263326432653266326732683269327032713272327332743275327632773278327932803281328232833284328532863287328832893290329132923293329432953296329732983299330033013302330333043305330633073308330933103311
  1. // Copyright (c) 2012 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/app_list/views/apps_grid_view.h"
  5. #include <algorithm>
  6. #include <memory>
  7. #include <set>
  8. #include <string>
  9. #include <utility>
  10. #include <vector>
  11. #include "ash/app_list/app_list_metrics.h"
  12. #include "ash/app_list/app_list_util.h"
  13. #include "ash/app_list/app_list_view_delegate.h"
  14. #include "ash/app_list/apps_grid_row_change_animator.h"
  15. #include "ash/app_list/model/app_list_folder_item.h"
  16. #include "ash/app_list/model/app_list_item.h"
  17. #include "ash/app_list/model/app_list_model.h"
  18. #include "ash/app_list/paged_view_structure.h"
  19. #include "ash/app_list/views/app_drag_icon_proxy.h"
  20. #include "ash/app_list/views/app_list_a11y_announcer.h"
  21. #include "ash/app_list/views/app_list_drag_and_drop_host.h"
  22. #include "ash/app_list/views/app_list_folder_controller.h"
  23. #include "ash/app_list/views/app_list_item_view.h"
  24. #include "ash/app_list/views/app_list_keyboard_controller.h"
  25. #include "ash/app_list/views/app_list_view_util.h"
  26. #include "ash/app_list/views/apps_grid_context_menu.h"
  27. #include "ash/app_list/views/apps_grid_view_folder_delegate.h"
  28. #include "ash/app_list/views/ghost_image_view.h"
  29. #include "ash/app_list/views/pulsing_block_view.h"
  30. #include "ash/constants/ash_features.h"
  31. #include "ash/public/cpp/app_list/app_list_config.h"
  32. #include "ash/public/cpp/app_list/app_list_types.h"
  33. #include "ash/public/cpp/metrics_util.h"
  34. #include "ash/strings/grit/ash_strings.h"
  35. #include "ash/utility/haptics_util.h"
  36. #include "base/bind.h"
  37. #include "base/callback_helpers.h"
  38. #include "base/cxx17_backports.h"
  39. #include "base/metrics/user_metrics.h"
  40. #include "base/metrics/user_metrics_action.h"
  41. #include "ui/aura/window.h"
  42. #include "ui/base/dragdrop/drag_drop_types.h"
  43. #include "ui/base/l10n/l10n_util.h"
  44. #include "ui/base/metadata/metadata_impl_macros.h"
  45. #include "ui/compositor/compositor.h"
  46. #include "ui/compositor/layer.h"
  47. #include "ui/events/devices/haptic_touchpad_effects.h"
  48. #include "ui/events/event.h"
  49. #include "ui/gfx/geometry/transform_util.h"
  50. #include "ui/gfx/geometry/vector2d.h"
  51. #include "ui/views/accessibility/view_accessibility.h"
  52. #include "ui/views/animation/animation_builder.h"
  53. #include "ui/views/animation/bounds_animator.h"
  54. #include "ui/views/controls/label.h"
  55. #include "ui/views/view_observer.h"
  56. #include "ui/views/widget/widget.h"
  57. #include "ui/wm/core/coordinate_conversion.h"
  58. namespace ash {
  59. namespace {
  60. // Distance a drag needs to be from the app grid to be considered 'outside', at
  61. // which point we rearrange the apps to their pre-drag configuration, as a drop
  62. // then would be canceled. We have a buffer to make it easier to drag apps to
  63. // other pages.
  64. constexpr int kDragBufferPx = 20;
  65. // Time delay before shelf starts to handle icon drag operation,
  66. // such as shelf icons re-layout.
  67. constexpr base::TimeDelta kShelfHandleIconDragDelay = base::Milliseconds(500);
  68. // The drag and drop proxy should get scaled by this factor.
  69. constexpr float kDragAndDropProxyScale = 1.2f;
  70. // Delays in milliseconds to show re-order preview.
  71. constexpr int kReorderDelay = 120;
  72. // Delays in milliseconds to show folder item reparent UI.
  73. constexpr int kFolderItemReparentDelay = 50;
  74. // Maximum vertical and horizontal spacing between tiles.
  75. constexpr int kMaximumTileSpacing = 96;
  76. // Maximum horizontal spacing between tiles for productivity launcher.
  77. constexpr int kMaximumHorizontalTileSpacingForProductivityLauncher = 128;
  78. // The ratio of the slide offset to the tile size.
  79. constexpr float kFadeAnimationOffsetRatio = 0.25f;
  80. // The time duration of the fade in animation used for apps grid reorder.
  81. constexpr base::TimeDelta kFadeInAnimationDuration = base::Milliseconds(400);
  82. // The time duration of the fade out animation used for apps grid reorder.
  83. constexpr base::TimeDelta kFadeOutAnimationDuration = base::Milliseconds(100);
  84. // Constants for folder item view relocation animation - the animation runs
  85. // after closing a folder view if the shown folder item view location within the
  86. // apps grid changed while the folder view was open.
  87. // The folder view animates in the old folder item location, then the folder
  88. // item view animates out at the old location, other items move into their
  89. // correct spot, and after a delay, the folder item view animates into its new
  90. // location.
  91. //
  92. // The duration of the folder item view fade out animation.
  93. constexpr base::TimeDelta kFolderItemFadeOutDuration = base::Milliseconds(100);
  94. // The duraction of the folder item view fade in animation.
  95. constexpr base::TimeDelta kFolderItemFadeInDuration = base::Milliseconds(300);
  96. // The delay for starting the folder item view fade in after the item view was
  97. // faded out.
  98. constexpr base::TimeDelta kFolderItemFadeInDelay = base::Milliseconds(300);
  99. // The time duration for item bounds animations.
  100. constexpr base::TimeDelta kItemBoundsAnimationDuration =
  101. base::Milliseconds(300);
  102. bool IsOEMFolderItem(AppListItem* item) {
  103. return IsFolderItem(item) &&
  104. (static_cast<AppListFolderItem*>(item))->folder_type() ==
  105. AppListFolderItem::FOLDER_TYPE_OEM;
  106. }
  107. } // namespace
  108. // static
  109. constexpr int AppsGridView::kDefaultAnimationDuration;
  110. // AppsGridView::VisibleItemIndexRange -----------------------------------------
  111. AppsGridView::VisibleItemIndexRange::VisibleItemIndexRange() = default;
  112. AppsGridView::VisibleItemIndexRange::VisibleItemIndexRange(size_t first_index,
  113. size_t last_index)
  114. : first_index(first_index), last_index(last_index) {}
  115. AppsGridView::VisibleItemIndexRange::~VisibleItemIndexRange() = default;
  116. // AppsGridView::FolderIconItemHider -------------------------------------------
  117. // Class used to hide an icon depicting an app list item from an folder item
  118. // icon image (which contains images of top app items in the folder).
  119. // Used during drag icon drop animation to hide the dragged item from the folder
  120. // icon (if the item is being dropped into a folder) while the drag icon is
  121. // still visible.
  122. // It gracefully handles the folder item getting deleted before the
  123. // `FolderIconItemHider` instance gets reset, so it should be safe to use in
  124. // asynchronous manner without extra folder item existence checks.
  125. class AppsGridView::FolderIconItemHider : public AppListItemObserver {
  126. public:
  127. FolderIconItemHider(AppListFolderItem* folder_item,
  128. AppListItem* item_icon_to_hide)
  129. : folder_item_(folder_item) {
  130. // Notify the folder item that `item_icon_to_hide` is being dragged, so the
  131. // dragged item is ignored while generating the folder icon image. This
  132. // effectively hides the drag item image from the overall folder icon.
  133. folder_item_->NotifyOfDraggedItem(item_icon_to_hide);
  134. folder_item_observer_.Observe(folder_item_);
  135. }
  136. ~FolderIconItemHider() override {
  137. if (folder_item_)
  138. folder_item_->NotifyOfDraggedItem(nullptr);
  139. }
  140. // AppListItemObserver:
  141. void ItemBeingDestroyed() override {
  142. folder_item_ = nullptr;
  143. folder_item_observer_.Reset();
  144. }
  145. private:
  146. AppListFolderItem* folder_item_;
  147. base::ScopedObservation<AppListItem, AppListItemObserver>
  148. folder_item_observer_{this};
  149. };
  150. // Class that while in scope hides a drag view in such way that the drag view
  151. // keeps receiving mouse/gesture events. Used to hide the dragged view while a
  152. // drag icon proxy for the drag item is shown. It gracefully handles the case
  153. // where it outlives the hidden dragged view, so it should be safe to be used
  154. // asynchronously without extra view existence checks.
  155. class AppsGridView::DragViewHider : public views::ViewObserver {
  156. public:
  157. explicit DragViewHider(AppListItemView* drag_view) : drag_view_(drag_view) {
  158. DCHECK(drag_view_->layer());
  159. drag_view_->layer()->SetOpacity(0.0f);
  160. view_observer_.Observe(drag_view_);
  161. }
  162. ~DragViewHider() override {
  163. if (drag_view_ && drag_view_->layer())
  164. drag_view_->layer()->SetOpacity(1.0f);
  165. }
  166. // views::ViewObserver:
  167. void OnViewIsDeleting(views::View* view) override {
  168. drag_view_ = nullptr;
  169. view_observer_.Reset();
  170. }
  171. const views::View* drag_view() const { return drag_view_; }
  172. private:
  173. AppListItemView* drag_view_;
  174. base::ScopedObservation<views::View, views::ViewObserver> view_observer_{
  175. this};
  176. };
  177. // Class used by AppsGridView to track whether app list model is being updated
  178. // by the AppsGridView (by setting `updating_model_`). While this is in scope:
  179. // (1) Do not cancel in progress drag due to app list model changes, and
  180. // (2) Delay `view_structure_` sanitization until the app list model update
  181. // finishes, and
  182. // (3) Ignore apps grid layout
  183. class AppsGridView::ScopedModelUpdate {
  184. public:
  185. explicit ScopedModelUpdate(AppsGridView* apps_grid_view)
  186. : apps_grid_view_(apps_grid_view),
  187. initial_grid_size_(apps_grid_view_->GetTileGridSize()) {
  188. DCHECK(!apps_grid_view_->updating_model_);
  189. apps_grid_view_->updating_model_ = true;
  190. // One model update may elicit multiple changes on apps grid layout. For
  191. // example, moving one item out of a folder may empty the parent folder then
  192. // have the folder deleted. Therefore ignore layout when `ScopedModelUpdate`
  193. // is in the scope to avoid handling temporary layout.
  194. DCHECK(!apps_grid_view_->ignore_layout_);
  195. apps_grid_view_->ignore_layout_ = true;
  196. view_structure_sanitize_lock_ =
  197. apps_grid_view_->view_structure_.GetSanitizeLock();
  198. }
  199. ScopedModelUpdate(const ScopedModelUpdate&) = delete;
  200. ScopedModelUpdate& operator=(const ScopedModelUpdate&) = delete;
  201. ~ScopedModelUpdate() {
  202. DCHECK(apps_grid_view_->updating_model_);
  203. apps_grid_view_->updating_model_ = false;
  204. DCHECK(apps_grid_view_->ignore_layout_);
  205. apps_grid_view_->ignore_layout_ = false;
  206. // Perform update for the final layout.
  207. apps_grid_view_->ScheduleLayout(initial_grid_size_);
  208. }
  209. private:
  210. AppsGridView* const apps_grid_view_;
  211. const gfx::Size initial_grid_size_;
  212. std::unique_ptr<PagedViewStructure::ScopedSanitizeLock>
  213. view_structure_sanitize_lock_;
  214. };
  215. AppsGridView::AppsGridView(AppListA11yAnnouncer* a11y_announcer,
  216. AppListViewDelegate* app_list_view_delegate,
  217. AppsGridViewFolderDelegate* folder_delegate,
  218. AppListFolderController* folder_controller,
  219. AppListKeyboardController* keyboard_controller)
  220. : folder_delegate_(folder_delegate),
  221. folder_controller_(folder_controller),
  222. a11y_announcer_(a11y_announcer),
  223. app_list_view_delegate_(app_list_view_delegate),
  224. keyboard_controller_(keyboard_controller) {
  225. DCHECK(a11y_announcer_);
  226. DCHECK(app_list_view_delegate_);
  227. // Top-level grids must have a folder controller.
  228. if (!folder_delegate_)
  229. DCHECK(folder_controller_);
  230. SetPaintToLayer(ui::LAYER_NOT_DRAWN);
  231. items_container_ = AddChildView(std::make_unique<views::View>());
  232. items_container_->SetPaintToLayer();
  233. items_container_->layer()->SetFillsBoundsOpaquely(false);
  234. bounds_animator_ = std::make_unique<views::BoundsAnimator>(
  235. items_container_, /*use_transforms=*/true);
  236. bounds_animator_->AddObserver(this);
  237. bounds_animator_->SetAnimationDuration(kItemBoundsAnimationDuration);
  238. if (features::IsProductivityLauncherEnabled()) {
  239. bounds_animator_->set_tween_type(gfx::Tween::ACCEL_40_DECEL_100_3);
  240. GetViewAccessibility().OverrideRole(ax::mojom::Role::kGroup);
  241. // Override the a11y name of top level apps grid.
  242. if (!folder_delegate) {
  243. GetViewAccessibility().OverrideName(
  244. l10n_util::GetStringUTF16(IDS_ASH_LAUNCHER_APPS_GRID_A11Y_NAME));
  245. }
  246. }
  247. if (!IsTabletMode()) {
  248. // `context_menu_` is only set in clamshell mode. The sort options in tablet
  249. // mode are handled in RootWindowController with ShelfContextMenuModel.
  250. context_menu_ = std::make_unique<AppsGridContextMenu>();
  251. set_context_menu_controller(context_menu_.get());
  252. }
  253. row_change_animator_ = std::make_unique<AppsGridRowChangeAnimator>(this);
  254. }
  255. AppsGridView::~AppsGridView() {
  256. bounds_animator_->RemoveObserver(this);
  257. // Coming here |drag_view_| should already be canceled since otherwise the
  258. // drag would disappear after the app list got animated away and closed,
  259. // which would look odd.
  260. DCHECK(!drag_item_);
  261. if (drag_item_)
  262. EndDrag(true);
  263. if (model_)
  264. model_->RemoveObserver(this);
  265. if (item_list_)
  266. item_list_->RemoveObserver(this);
  267. // Cancel animations now, otherwise RemoveAllChildViews() may call back to
  268. // ViewHierarchyChanged() during removal, which can lead to double deletes
  269. // (because ViewHierarchyChanged() may attempt to delete a view that is part
  270. // way through deletion). Note that cancelling animations may cause
  271. // AppListItemView to Layout(), which may call back into this object.
  272. bounds_animator_->Cancel();
  273. // Abort reorder animation before `view_model_` is cleared.
  274. MaybeAbortWholeGridAnimation();
  275. view_model_.Clear();
  276. pulsing_blocks_model_.Clear();
  277. RemoveAllChildViews();
  278. // `OnBoundsAnimatorDone`, which uses `bounds_animator_`, is called on
  279. // `drag_icon_proxy_` destruction. Reset `drag_icon_proxy_` early, while
  280. // `bounds_animator_` is still around.
  281. folder_to_open_after_drag_icon_animation_.clear();
  282. drag_icon_proxy_.reset();
  283. }
  284. void AppsGridView::Init() {
  285. UpdateBorder();
  286. }
  287. void AppsGridView::UpdateAppListConfig(const AppListConfig* app_list_config) {
  288. app_list_config_ = app_list_config;
  289. // The app list item view icon sizes depend on the app list config, so they
  290. // have to be refreshed.
  291. for (size_t i = 0; i < view_model_.view_size(); ++i)
  292. view_model_.view_at(i)->UpdateAppListConfig(app_list_config);
  293. if (current_ghost_view_)
  294. CreateGhostImageView();
  295. }
  296. void AppsGridView::SetFixedTilePadding(int horizontal_padding,
  297. int vertical_padding) {
  298. has_fixed_tile_padding_ = true;
  299. horizontal_tile_padding_ = horizontal_padding;
  300. vertical_tile_padding_ = vertical_padding;
  301. }
  302. gfx::Size AppsGridView::GetTotalTileSize(int page) const {
  303. gfx::Rect rect(GetTileViewSize());
  304. rect.Inset(GetTilePadding(page));
  305. return rect.size();
  306. }
  307. gfx::Size AppsGridView::GetMinimumTileGridSize(int cols,
  308. int rows_per_page) const {
  309. const gfx::Size tile_size = GetTileViewSize();
  310. return gfx::Size(tile_size.width() * cols,
  311. tile_size.height() * rows_per_page);
  312. }
  313. gfx::Size AppsGridView::GetMaximumTileGridSize(int cols,
  314. int rows_per_page) const {
  315. const gfx::Size tile_size = GetTileViewSize();
  316. const int max_horizontal_spacing =
  317. features::IsProductivityLauncherEnabled()
  318. ? kMaximumHorizontalTileSpacingForProductivityLauncher
  319. : kMaximumTileSpacing;
  320. return gfx::Size(
  321. tile_size.width() * cols + max_horizontal_spacing * (cols - 1),
  322. tile_size.height() * rows_per_page +
  323. kMaximumTileSpacing * (rows_per_page - 1));
  324. }
  325. void AppsGridView::ResetForShowApps() {
  326. CancelDragWithNoDropAnimation();
  327. layer()->SetOpacity(1.0f);
  328. SetVisible(true);
  329. // The number of non-page-break-items should be the same as item views.
  330. if (item_list_) {
  331. size_t item_count = 0;
  332. for (size_t i = 0; i < item_list_->item_count(); ++i) {
  333. if (!item_list_->item_at(i)->is_page_break())
  334. ++item_count;
  335. }
  336. CHECK_EQ(item_count, view_model_.view_size());
  337. }
  338. }
  339. void AppsGridView::CancelDragWithNoDropAnimation() {
  340. EndDrag(/*cancel=*/true);
  341. drag_view_hider_.reset();
  342. folder_icon_item_hider_.reset();
  343. folder_to_open_after_drag_icon_animation_.clear();
  344. drag_icon_proxy_.reset();
  345. }
  346. void AppsGridView::DisableFocusForShowingActiveFolder(bool disabled) {
  347. for (const auto& entry : view_model_.entries())
  348. entry.view->SetEnabled(!disabled);
  349. // Ignore the grid view in accessibility tree so that items inside it will not
  350. // be accessed by ChromeVox.
  351. SetViewIgnoredForAccessibility(this, disabled);
  352. }
  353. void AppsGridView::SetModel(AppListModel* model) {
  354. if (model_)
  355. model_->RemoveObserver(this);
  356. model_ = model;
  357. if (model_)
  358. model_->AddObserver(this);
  359. Update();
  360. }
  361. void AppsGridView::SetItemList(AppListItemList* item_list) {
  362. DCHECK_GT(cols_, 0);
  363. DCHECK(app_list_config_);
  364. if (item_list_)
  365. item_list_->RemoveObserver(this);
  366. item_list_ = item_list;
  367. if (item_list_)
  368. item_list_->AddObserver(this);
  369. Update();
  370. }
  371. bool AppsGridView::IsInFolder() const {
  372. return !!folder_delegate_;
  373. }
  374. void AppsGridView::SetSelectedView(AppListItemView* view) {
  375. if (IsSelectedView(view) || IsDraggedView(view))
  376. return;
  377. GridIndex index = GetIndexOfView(view);
  378. if (IsValidIndex(index))
  379. SetSelectedItemByIndex(index);
  380. }
  381. void AppsGridView::ClearSelectedView() {
  382. selected_view_ = nullptr;
  383. }
  384. bool AppsGridView::IsSelectedView(const AppListItemView* view) const {
  385. return selected_view_ == view;
  386. }
  387. bool AppsGridView::InitiateDrag(AppListItemView* view,
  388. const gfx::Point& location,
  389. const gfx::Point& root_location,
  390. base::OnceClosure drag_start_callback,
  391. base::OnceClosure drag_end_callback) {
  392. DCHECK(view);
  393. if (drag_item_ || pulsing_blocks_model_.view_size())
  394. return false;
  395. DVLOG(1) << "Initiate drag";
  396. drag_start_callback_ = std::move(drag_start_callback);
  397. drag_end_callback_ = std::move(drag_end_callback);
  398. // Finalize previous drag icon animation if it's still in progress.
  399. drag_view_hider_.reset();
  400. folder_icon_item_hider_.reset();
  401. folder_to_open_after_drag_icon_animation_.clear();
  402. drag_icon_proxy_.reset();
  403. PrepareItemsForBoundsAnimation();
  404. drag_view_ = view;
  405. drag_item_ = view->item();
  406. // Dragged view should have focus. This also fixed the issue
  407. // https://crbug.com/834682.
  408. drag_view_->RequestFocus();
  409. drag_view_init_index_ = GetIndexOfView(drag_view_);
  410. reorder_placeholder_ = drag_view_init_index_;
  411. ExtractDragLocation(root_location, &drag_start_grid_view_);
  412. return true;
  413. }
  414. void AppsGridView::StartDragAndDropHostDragAfterLongPress() {
  415. TryStartDragAndDropHostDrag(TOUCH);
  416. }
  417. void AppsGridView::TryStartDragAndDropHostDrag(Pointer pointer) {
  418. // Stopping the animation may have invalidated our drag view due to the
  419. // view hierarchy changing.
  420. if (!drag_item_)
  421. return;
  422. drag_pointer_ = pointer;
  423. if (!dragging_for_reparent_item_) {
  424. StartDragAndDropHostDrag();
  425. if (pointer == MOUSE) {
  426. haptics_util::PlayHapticTouchpadEffect(
  427. ui::HapticTouchpadEffect::kTick,
  428. ui::HapticTouchpadEffectStrength::kMedium);
  429. }
  430. }
  431. if (drag_start_callback_)
  432. std::move(drag_start_callback_).Run();
  433. }
  434. bool AppsGridView::UpdateDragFromItem(bool is_touch,
  435. const ui::LocatedEvent& event) {
  436. if (!drag_item_)
  437. return false; // Drag canceled.
  438. gfx::Point drag_point_in_grid_view;
  439. ExtractDragLocation(event.root_location(), &drag_point_in_grid_view);
  440. const Pointer pointer = is_touch ? TOUCH : MOUSE;
  441. UpdateDrag(pointer, drag_point_in_grid_view);
  442. if (!IsDragging())
  443. return false;
  444. // If a drag and drop host is provided, see if the drag operation needs to be
  445. // forwarded.
  446. gfx::Point drag_point_in_screen = event.root_location();
  447. ::wm::ConvertPointToScreen(GetWidget()->GetNativeWindow()->GetRootWindow(),
  448. &drag_point_in_screen);
  449. DispatchDragEventToDragAndDropHost(drag_point_in_screen);
  450. if (drag_icon_proxy_)
  451. drag_icon_proxy_->UpdatePosition(drag_point_in_screen);
  452. return true;
  453. }
  454. void AppsGridView::UpdateDrag(Pointer pointer, const gfx::Point& point) {
  455. if (folder_delegate_)
  456. UpdateDragStateInsideFolder(pointer, point);
  457. if (!drag_item_)
  458. return; // Drag canceled.
  459. // If folder is currently open from the grid, delay drag updates until the
  460. // folder finishes closing.
  461. if (open_folder_info_) {
  462. // Only handle pointers that initiated the drag - e.g. ignore drag events
  463. // that come from touch if a mouse drag is currently in progress.
  464. if (drag_pointer_ == pointer)
  465. last_drag_point_ = point;
  466. return;
  467. }
  468. gfx::Vector2d drag_vector(point - drag_start_grid_view_);
  469. if (ExceededDragThreshold(drag_vector)) {
  470. if (!IsDragging())
  471. TryStartDragAndDropHostDrag(pointer);
  472. MaybeStartCardifiedView();
  473. }
  474. if (drag_pointer_ != pointer)
  475. return;
  476. last_drag_point_ = point;
  477. const GridIndex last_drop_target = drop_target_;
  478. DropTargetRegion last_drop_target_region = drop_target_region_;
  479. UpdateDropTargetRegion();
  480. MaybeStartPageFlip();
  481. bool is_scrolling = MaybeAutoScroll();
  482. if (is_scrolling) {
  483. // Don't do reordering while auto-scrolling, otherwise there is too much
  484. // motion during the drag.
  485. reorder_timer_.Stop();
  486. // Reset the previous drop target.
  487. if (last_drop_target_region == ON_ITEM)
  488. SetAsFolderDroppingTarget(last_drop_target, false);
  489. return;
  490. }
  491. if (last_drop_target != drop_target_ ||
  492. last_drop_target_region != drop_target_region_) {
  493. if (last_drop_target_region == ON_ITEM)
  494. SetAsFolderDroppingTarget(last_drop_target, false);
  495. if (drop_target_region_ == ON_ITEM && DraggedItemCanEnterFolder() &&
  496. DropTargetIsValidFolder()) {
  497. reorder_timer_.Stop();
  498. MaybeCreateFolderDroppingAccessibilityEvent();
  499. SetAsFolderDroppingTarget(drop_target_, true);
  500. BeginHideCurrentGhostImageView();
  501. } else if ((drop_target_region_ == ON_ITEM ||
  502. drop_target_region_ == NEAR_ITEM) &&
  503. !folder_delegate_) {
  504. // If the drag changes regions from |BETWEEN_ITEMS| to |NEAR_ITEM| the
  505. // timer should reset, so that we gain the extra time from hovering near
  506. // the item
  507. if (last_drop_target_region == BETWEEN_ITEMS)
  508. reorder_timer_.Stop();
  509. reorder_timer_.Start(FROM_HERE, base::Milliseconds(kReorderDelay * 5),
  510. this, &AppsGridView::OnReorderTimer);
  511. } else if (drop_target_region_ != NO_TARGET) {
  512. // If none of the above cases evaluated true, then all of the possible
  513. // drop regions should result in a fast reorder.
  514. reorder_timer_.Start(FROM_HERE, base::Milliseconds(kReorderDelay), this,
  515. &AppsGridView::OnReorderTimer);
  516. }
  517. }
  518. }
  519. void AppsGridView::EndDrag(bool cancel) {
  520. DVLOG(1) << "EndDrag cancel=" << cancel;
  521. // EndDrag was called before if |drag_view_| is nullptr.
  522. if (!drag_item_)
  523. return;
  524. AppListItem* drag_item = drag_item_;
  525. // Whether an icon was actually dragged (and not just clicked).
  526. const bool was_dragging = IsDragging();
  527. // Coming here a drag and drop was in progress.
  528. const bool landed_in_drag_and_drop_host =
  529. forward_events_to_drag_and_drop_host_;
  530. // The ID of the folder to which the item gets dropped. It will get set when
  531. // the item is moved to a folder.
  532. std::string target_folder_id;
  533. if (forward_events_to_drag_and_drop_host_) {
  534. DCHECK(!IsDraggingForReparentInRootLevelGridView());
  535. forward_events_to_drag_and_drop_host_ = false;
  536. // Pass the drag icon proxy on to the drag and drop host, so the drag and
  537. // drop host handles the animation to drop the icon proxy into correct spot.
  538. drag_and_drop_host_->EndDrag(cancel, std::move(drag_icon_proxy_));
  539. if (IsDraggingForReparentInHiddenGridView()) {
  540. EndDragForReparentInHiddenFolderGridView();
  541. folder_delegate_->DispatchEndDragEventForReparent(
  542. true /* events_forwarded_to_drag_drop_host */,
  543. cancel /* cancel_drag */, std::move(drag_icon_proxy_));
  544. return;
  545. }
  546. } else {
  547. if (IsDraggingForReparentInHiddenGridView()) {
  548. EndDragForReparentInHiddenFolderGridView();
  549. // Forward the EndDrag event to the root level grid view.
  550. folder_delegate_->DispatchEndDragEventForReparent(
  551. false /* events_forwarded_to_drag_drop_host */,
  552. cancel /* cancel_drag */, std::move(drag_icon_proxy_));
  553. return;
  554. }
  555. if (IsDraggingForReparentInRootLevelGridView()) {
  556. // An EndDrag can be received during a reparent via a model change. This
  557. // is always a cancel and needs to be forwarded to the folder.
  558. DCHECK(cancel);
  559. if (reparent_drag_cancellation_)
  560. std::move(reparent_drag_cancellation_).Run();
  561. return;
  562. }
  563. if (!cancel && was_dragging) {
  564. // Regular drag ending path, ie, not for reparenting.
  565. UpdateDropTargetRegion();
  566. if (drop_target_region_ == ON_ITEM && DraggedItemCanEnterFolder() &&
  567. DropTargetIsValidFolder()) {
  568. bool is_new_folder = false;
  569. if (MoveItemToFolder(drag_item_, drop_target_, kMoveByDragIntoFolder,
  570. &target_folder_id, &is_new_folder)) {
  571. MaybeCreateFolderDroppingAccessibilityEvent();
  572. if (is_new_folder && features::IsProductivityLauncherEnabled()) {
  573. folder_to_open_after_drag_icon_animation_ = target_folder_id;
  574. SetOpenFolderInfo(target_folder_id, drop_target_,
  575. reorder_placeholder_);
  576. }
  577. // If item drag created a folder, layout the grid to ensure the
  578. // created folder's bounds are correct. Note that `open_folder_info_`
  579. // affects ideal item bounds, so `Layout()` needs to be callsed after
  580. // `SetOpenFolderInfo()`.
  581. Layout();
  582. }
  583. } else if (IsValidReorderTargetIndex(drop_target_)) {
  584. // Ensure reorder event has already been announced by the end of drag.
  585. MaybeCreateDragReorderAccessibilityEvent();
  586. MoveItemInModel(drag_item_, drop_target_);
  587. RecordAppMovingTypeMetrics(folder_delegate_ ? kReorderByDragInFolder
  588. : kReorderByDragInTopLevel);
  589. }
  590. }
  591. }
  592. // Issue 439055: MoveItemToFolder() can sometimes delete |drag_view_|
  593. if (drag_view_ && landed_in_drag_and_drop_host) {
  594. // Move the item directly to the target location, avoiding the
  595. // "zip back" animation if the user was pinning it to the shelf.
  596. int i = drop_target_.slot;
  597. gfx::Rect bounds = view_model_.ideal_bounds(i);
  598. drag_view_->SetBoundsRect(bounds);
  599. drag_view_hider_.reset();
  600. }
  601. SetAsFolderDroppingTarget(drop_target_, false);
  602. ClearDragState();
  603. UpdatePaging();
  604. if (GetWidget()) {
  605. // Normally Layout() cancels any animations. At this point there may be a
  606. // pending Layout(), force it now so that one isn't triggered part way
  607. // through the animation. Further, ignore this layout so that the position
  608. // isn't reset.
  609. DCHECK(!ignore_layout_);
  610. base::AutoReset<bool> auto_reset(&ignore_layout_, true);
  611. GetWidget()->LayoutRootViewIfNecessary();
  612. }
  613. if (cardified_state_)
  614. MaybeEndCardifiedView();
  615. else
  616. AnimateToIdealBounds();
  617. if (!cancel)
  618. view_structure_.SaveToMetadata();
  619. if (!cancel) {
  620. // Select the page where dragged item is dropped. Avoid doing so when the
  621. // dragged item ends up in a folder.
  622. const size_t model_index = GetModelIndexOfItem(drag_item);
  623. if (model_index < view_model_.view_size())
  624. EnsureViewVisible(view_structure_.GetIndexFromModelIndex(model_index));
  625. }
  626. // Hide the |current_ghost_view_| for item drag that started
  627. // within |apps_grid_view_|.
  628. BeginHideCurrentGhostImageView();
  629. if (was_dragging)
  630. SetFocusAfterEndDrag(drag_item); // Maybe focus the search box.
  631. AnimateDragIconToTargetPosition(drag_item, target_folder_id);
  632. }
  633. AppListItemView* AppsGridView::GetItemViewForItem(const std::string& item_id) {
  634. const AppListItem* const item = item_list_->FindItem(item_id);
  635. if (!item)
  636. return nullptr;
  637. return GetItemViewAt(GetModelIndexOfItem(item));
  638. }
  639. AppListItemView* AppsGridView::GetItemViewAt(size_t index) const {
  640. return (index < view_model_.view_size()) ? view_model_.view_at(index)
  641. : nullptr;
  642. }
  643. void AppsGridView::InitiateDragFromReparentItemInRootLevelGridView(
  644. Pointer pointer,
  645. AppListItemView* original_drag_view,
  646. const gfx::Point& drag_point,
  647. base::OnceClosure cancellation_callback) {
  648. DVLOG(1) << __FUNCTION__;
  649. DCHECK(original_drag_view && !drag_view_);
  650. DCHECK(!dragging_for_reparent_item_);
  651. // Since the item is new, its placeholder is conceptually at the back of the
  652. // entire apps grid.
  653. reorder_placeholder_ = view_structure_.GetLastTargetIndex();
  654. PrepareItemsForBoundsAnimation();
  655. drag_pointer_ = pointer;
  656. drag_item_ = original_drag_view->item();
  657. drag_start_grid_view_ = drag_point;
  658. // Set the flag in root level grid view.
  659. dragging_for_reparent_item_ = true;
  660. reparent_drag_cancellation_ = std::move(cancellation_callback);
  661. }
  662. void AppsGridView::UpdateDragFromReparentItem(Pointer pointer,
  663. const gfx::Point& drag_point) {
  664. // Note that if a cancel ocurrs while reparenting, the |drag_view_| in both
  665. // root and folder grid views is cleared, so the check in UpdateDragFromItem()
  666. // for |drag_view_| being nullptr (in the folder grid) is sufficient.
  667. DCHECK(drag_item_);
  668. DCHECK(IsDraggingForReparentInRootLevelGridView());
  669. UpdateDrag(pointer, drag_point);
  670. }
  671. void AppsGridView::SetOpenFolderInfo(const std::string& folder_id,
  672. const GridIndex& target_folder_position,
  673. const GridIndex& position_to_skip) {
  674. GridIndex expected_folder_position = target_folder_position;
  675. // If the target view is positioned after `position_to_skip`, move the
  676. // target one slot earlier, as `position_to_skip` is assumed about to be
  677. // emptied.
  678. if (position_to_skip.IsValid() &&
  679. position_to_skip < expected_folder_position &&
  680. expected_folder_position.slot > 0) {
  681. --expected_folder_position.slot;
  682. }
  683. open_folder_info_ = {.item_id = folder_id,
  684. .grid_index = expected_folder_position};
  685. }
  686. void AppsGridView::ShowFolderForView(AppListItemView* folder_view,
  687. bool new_folder) {
  688. DCHECK(open_folder_info_);
  689. // Guard against invalid folder view.
  690. if (!folder_view || !folder_view->is_folder()) {
  691. open_folder_info_.reset();
  692. return;
  693. }
  694. folder_controller_->ShowFolderForItemView(
  695. folder_view,
  696. /*focus_name_input=*/new_folder,
  697. base::BindOnce(&AppsGridView::FolderHidden, weak_factory_.GetWeakPtr(),
  698. folder_view->item()->id()));
  699. }
  700. void AppsGridView::FolderHidden(const std::string& item_id) {
  701. if (!open_folder_info_ || open_folder_info_->item_id != item_id)
  702. return;
  703. // Find the folder item location in the app list model to determine whether
  704. // the item view location changed while the folder was closed (in which case
  705. // the folder location change should be animated).
  706. AppListItemView* item_view = nullptr;
  707. int model_index = -1;
  708. for (size_t i = 0; i < view_model_.view_size(); ++i) {
  709. AppListItemView* view = view_model_.view_at(i);
  710. if (view == drag_view_)
  711. continue;
  712. ++model_index;
  713. if (view->item()->id() == item_id) {
  714. item_view = view;
  715. break;
  716. }
  717. }
  718. // If the item view is gone, or the location in the grid did not change,
  719. // the folder item should not be animated - immediately update apps grid state
  720. // for folder hide.
  721. if (!item_view || view_structure_.GetIndexFromModelIndex(model_index) ==
  722. open_folder_info_->grid_index) {
  723. open_folder_info_.reset();
  724. OnFolderHideAnimationDone();
  725. return;
  726. }
  727. // When folder animates out, remaining items will animate to their ideal
  728. // bounds - ensure their layers are created (and marked not to fill bounds
  729. // opaquely).
  730. PrepareItemsForBoundsAnimation();
  731. // Animate the folder item view out from its original location.
  732. reordering_folder_view_ = item_view;
  733. views::AnimationBuilder animation;
  734. animation.OnEnded(base::BindOnce(&AppsGridView::AnimateFolderItemViewIn,
  735. weak_factory_.GetWeakPtr()));
  736. animation.OnAborted(base::BindOnce(&AppsGridView::AnimateFolderItemViewIn,
  737. weak_factory_.GetWeakPtr()));
  738. gfx::Transform scale;
  739. scale.Scale(0.5, 0.5);
  740. scale = gfx::TransformAboutPivot(item_view->GetLocalBounds().CenterPoint(),
  741. scale);
  742. animation.Once()
  743. .SetDuration(kFolderItemFadeOutDuration)
  744. .SetTransform(item_view->layer(), scale, gfx::Tween::FAST_OUT_LINEAR_IN)
  745. .SetOpacity(item_view->layer(), 0.0f, gfx::Tween::FAST_OUT_LINEAR_IN);
  746. }
  747. void AppsGridView::AnimateFolderItemViewIn() {
  748. // Once folder item view fades out, animate remaining items into their target
  749. // location, and schedule the folder item view fade-in (note that
  750. // `AnimateToIdealBounds()` updates `reordering_folder_view_` bounds without
  751. // animation).
  752. open_folder_info_.reset();
  753. AnimateToIdealBounds();
  754. if (!reordering_folder_view_)
  755. return;
  756. views::AnimationBuilder()
  757. .OnEnded(base::BindOnce(&AppsGridView::OnFolderHideAnimationDone,
  758. weak_factory_.GetWeakPtr()))
  759. .OnAborted(base::BindOnce(&AppsGridView::OnFolderHideAnimationDone,
  760. weak_factory_.GetWeakPtr()))
  761. .Once()
  762. .At(kFolderItemFadeInDelay)
  763. .SetDuration(kFolderItemFadeInDuration)
  764. .SetTransform(reordering_folder_view_.value()->layer(), gfx::Transform(),
  765. gfx::Tween::ACCEL_LIN_DECEL_100_3)
  766. .SetOpacity(reordering_folder_view_.value()->layer(), 1.0f,
  767. gfx::Tween::ACCEL_LIN_DECEL_100_3);
  768. }
  769. void AppsGridView::OnFolderHideAnimationDone() {
  770. reordering_folder_view_.reset();
  771. DestroyLayerItemsIfNotNeeded();
  772. if (IsDraggingForReparentInRootLevelGridView()) {
  773. MaybeStartCardifiedView();
  774. UpdateDrag(drag_pointer_, last_drag_point_);
  775. }
  776. }
  777. bool AppsGridView::IsDragging() const {
  778. return drag_pointer_ != NONE;
  779. }
  780. bool AppsGridView::IsDraggedView(const AppListItemView* view) const {
  781. return drag_item_ == view->item();
  782. }
  783. void AppsGridView::ClearDragState() {
  784. current_ghost_location_ = GridIndex();
  785. last_folder_dropping_a11y_event_location_ = GridIndex();
  786. last_reorder_a11y_event_location_ = GridIndex();
  787. drop_target_region_ = NO_TARGET;
  788. drag_pointer_ = NONE;
  789. drop_target_ = GridIndex();
  790. reorder_placeholder_ = GridIndex();
  791. drag_start_grid_view_ = gfx::Point();
  792. // Drag may end before |host_drag_start_timer_| gets fired.
  793. if (host_drag_start_timer_.IsRunning())
  794. host_drag_start_timer_.AbandonAndStop();
  795. if (folder_item_reparent_timer_.IsRunning())
  796. folder_item_reparent_timer_.Stop();
  797. MaybeStopPageFlip();
  798. StopAutoScroll();
  799. drag_view_ = nullptr;
  800. drag_item_ = nullptr;
  801. drag_out_of_folder_container_ = false;
  802. dragging_for_reparent_item_ = false;
  803. extra_page_opened_ = false;
  804. reparent_drag_cancellation_.Reset();
  805. drag_start_callback_.Reset();
  806. if (drag_end_callback_)
  807. std::move(drag_end_callback_).Run();
  808. }
  809. void AppsGridView::SetDragAndDropHostOfCurrentAppList(
  810. ApplicationDragAndDropHost* drag_and_drop_host) {
  811. if (drag_and_drop_host_ == drag_and_drop_host)
  812. return;
  813. drag_and_drop_host_ = drag_and_drop_host;
  814. forward_events_to_drag_and_drop_host_ = false;
  815. if (host_drag_start_timer_.IsRunning())
  816. host_drag_start_timer_.AbandonAndStop();
  817. }
  818. bool AppsGridView::IsAnimatingView(AppListItemView* view) {
  819. return bounds_animator_->IsAnimating(view);
  820. }
  821. gfx::Size AppsGridView::CalculatePreferredSize() const {
  822. return GetTileGridSize();
  823. }
  824. bool AppsGridView::GetDropFormats(
  825. int* formats,
  826. std::set<ui::ClipboardFormatType>* format_types) {
  827. // TODO(koz): Only accept a specific drag type for app shortcuts.
  828. *formats = OSExchangeData::FILE_NAME;
  829. return true;
  830. }
  831. bool AppsGridView::CanDrop(const OSExchangeData& data) {
  832. return true;
  833. }
  834. int AppsGridView::OnDragUpdated(const ui::DropTargetEvent& event) {
  835. return ui::DragDropTypes::DRAG_MOVE;
  836. }
  837. void AppsGridView::UpdateControlVisibility(AppListViewState app_list_state,
  838. bool is_in_drag) {
  839. const bool fullscreen_or_in_drag =
  840. is_in_drag || app_list_state == AppListViewState::kFullscreenAllApps ||
  841. app_list_state == AppListViewState::kFullscreenSearch;
  842. SetVisible(fullscreen_or_in_drag);
  843. }
  844. bool AppsGridView::OnKeyPressed(const ui::KeyEvent& event) {
  845. // The user may press VKEY_CONTROL before an arrow key when intending to do an
  846. // app move with control+arrow.
  847. if (event.key_code() == ui::VKEY_CONTROL)
  848. return true;
  849. if (selected_view_ && IsArrowKeyEvent(event) && event.IsControlDown()) {
  850. HandleKeyboardAppOperations(event.key_code(), event.IsShiftDown());
  851. return true;
  852. }
  853. // Let the FocusManager handle Left/Right keys.
  854. if (!IsUnhandledUpDownKeyEvent(event))
  855. return false;
  856. const bool arrow_up = event.key_code() == ui::VKEY_UP;
  857. return HandleVerticalFocusMovement(arrow_up);
  858. }
  859. bool AppsGridView::OnKeyReleased(const ui::KeyEvent& event) {
  860. if (event.IsControlDown() || !handling_keyboard_move_)
  861. return false;
  862. handling_keyboard_move_ = false;
  863. RecordAppMovingTypeMetrics(folder_delegate_ ? kReorderByKeyboardInFolder
  864. : kReorderByKeyboardInTopLevel);
  865. return false;
  866. }
  867. void AppsGridView::ViewHierarchyChanged(
  868. const views::ViewHierarchyChangedDetails& details) {
  869. if (!details.is_add && details.parent == items_container_) {
  870. // The view being delete should not have reference in |view_model_|.
  871. CHECK(!view_model_.GetIndexOfView(details.child).has_value());
  872. if (selected_view_ == details.child)
  873. selected_view_ = nullptr;
  874. if (drag_view_ == details.child)
  875. drag_view_ = nullptr;
  876. if (features::IsProductivityLauncherEnabled()) {
  877. if (current_ghost_view_ == details.child)
  878. current_ghost_view_ = nullptr;
  879. if (last_ghost_view_ == details.child)
  880. last_ghost_view_ = nullptr;
  881. }
  882. if (reordering_folder_view_ && *reordering_folder_view_ == details.child)
  883. reordering_folder_view_.reset();
  884. bounds_animator_->StopAnimatingView(details.child);
  885. row_change_animator_->CancelAnimation(details.child);
  886. }
  887. }
  888. bool AppsGridView::EventIsBetweenOccupiedTiles(const ui::LocatedEvent* event) {
  889. gfx::Point mirrored_point(GetMirroredXInView(event->location().x()),
  890. event->location().y());
  891. return IsValidIndex(GetNearestTileIndexForPoint(mirrored_point));
  892. }
  893. void AppsGridView::Update() {
  894. UpdateBorder();
  895. // Abort reorder animation before `view_model_` is cleared.
  896. MaybeAbortWholeGridAnimation();
  897. view_model_.Clear();
  898. pulsing_blocks_model_.Clear();
  899. items_container_->RemoveAllChildViews();
  900. DCHECK(!selected_view_);
  901. DCHECK(!drag_view_);
  902. std::vector<AppListItemView*> item_views;
  903. if (item_list_ && item_list_->item_count()) {
  904. for (size_t i = 0; i < item_list_->item_count(); ++i) {
  905. // Skip "page break" items.
  906. if (item_list_->item_at(i)->is_page_break())
  907. continue;
  908. std::unique_ptr<AppListItemView> view = CreateViewForItemAtIndex(i);
  909. view_model_.Add(view.get(), view_model_.view_size());
  910. item_views.push_back(items_container_->AddChildView(std::move(view)));
  911. }
  912. }
  913. view_structure_.LoadFromMetadata();
  914. UpdateColsAndRowsForFolder();
  915. UpdatePaging();
  916. UpdatePulsingBlockViews();
  917. InvalidateLayout();
  918. // Icon load can change the item position in the view model, so don't iterate
  919. // over view model to get items to update.
  920. for (auto* item_view : item_views)
  921. item_view->InitializeIconLoader();
  922. if (!folder_delegate_)
  923. RecordPageMetrics();
  924. }
  925. base::TimeDelta AppsGridView::GetPulsingBlockAnimationDelayForIndex(
  926. int block_index) {
  927. // The column in which the last AppListItemViews is located.
  928. // |view_model_| only contains synced AppListItemViews and not
  929. // PulsingBlockViews.
  930. const int last_non_block_view_column = view_model_.view_size() % cols_;
  931. // The index of the pulsing block view related to the |view_model_|.
  932. const int block_index_in_view_model = view_model_.view_size() + block_index;
  933. const base::TimeDelta staging_step_delay = base::Milliseconds(100);
  934. // Depending of the row and column for the pulsing block, we stage the pulsing
  935. // animation so it sweeps at a 45 degree angle from the upper left to the
  936. // lower right.
  937. return staging_step_delay *
  938. ((last_non_block_view_column + block_index) / cols_) +
  939. staging_step_delay * (block_index_in_view_model % cols_);
  940. }
  941. void AppsGridView::OnSwapAnimationDone(views::View* placeholder,
  942. AppListItemView* app_view) {
  943. delete placeholder;
  944. if (view_model_.GetIndexOfView(app_view).has_value() &&
  945. !ItemViewsRequireLayers())
  946. app_view->DestroyLayer();
  947. UpdatePulsingBlockViews();
  948. }
  949. AppListItemView* AppsGridView::MaybeSwapPlaceholderAsset(size_t index) {
  950. size_t model_index = GetTargetModelIndexFromItemIndex(index);
  951. AppListItemView* view = items_container_->AddChildViewAt(
  952. CreateViewForItemAtIndex(index), model_index);
  953. view_model_.Add(view, model_index);
  954. const bool placeholder_in_view_index =
  955. model_index == (view_model_.view_size() - 1);
  956. const bool is_syncing =
  957. model_ && model_->status() == AppListModelStatus::kStatusSyncing;
  958. const bool should_animate_placeholder_swap =
  959. ash::features::IsProductivityLauncherEnabled() &&
  960. pulsing_blocks_model_.view_size() > 0 && is_syncing &&
  961. placeholder_in_view_index;
  962. if (should_animate_placeholder_swap) {
  963. PulsingBlockView* placeholder =
  964. items_container_->AddChildView(std::make_unique<PulsingBlockView>(
  965. app_list_config_->grid_icon_size(), base::TimeDelta()));
  966. placeholder->SetBoundsRect(view->bounds());
  967. placeholder->SetPaintToLayer();
  968. view->EnsureLayer();
  969. view->layer()->SetOpacity(0);
  970. views::AnimationBuilder()
  971. .SetPreemptionStrategy(
  972. ui::LayerAnimator::IMMEDIATELY_ANIMATE_TO_NEW_TARGET)
  973. .OnEnded(base::BindOnce(&AppsGridView::OnSwapAnimationDone,
  974. weak_factory_.GetWeakPtr(), placeholder, view))
  975. .OnAborted(base::BindOnce(&AppsGridView::OnSwapAnimationDone,
  976. weak_factory_.GetWeakPtr(), placeholder,
  977. view))
  978. .Once()
  979. .SetDuration(base::Milliseconds(200))
  980. .SetOpacity(placeholder->layer(), 0.0f, gfx::Tween::LINEAR)
  981. .SetOpacity(view->layer(), 1.0f, gfx::Tween::LINEAR);
  982. } else {
  983. UpdatePulsingBlockViews();
  984. }
  985. return view;
  986. }
  987. void AppsGridView::UpdatePulsingBlockViews() {
  988. int existing_items = item_list_ ? item_list_->item_count() : 0;
  989. const int tablet_page_size =
  990. SharedAppListConfig::instance().GetMaxNumOfItemsPerPage();
  991. // For scrolling app list, the "page size" is very large, so cap the number of
  992. // pulsing blocks to the size of the tablet mode page (~20 items).
  993. const int tiles_per_page = std::min(TilesPerPage(1), tablet_page_size);
  994. if (view_structure_.mode() != PagedViewStructure::Mode::kSinglePage) {
  995. if (existing_items > TilesPerPage(0))
  996. existing_items -= TilesPerPage(0);
  997. }
  998. const size_t available_slots =
  999. tiles_per_page - (existing_items % tiles_per_page);
  1000. const size_t desired =
  1001. model_ && model_->status() == AppListModelStatus::kStatusSyncing
  1002. ? available_slots
  1003. : 0;
  1004. if (pulsing_blocks_model_.view_size() == desired)
  1005. return;
  1006. pulsing_blocks_model_.Clear();
  1007. while (pulsing_blocks_model_.view_size() < desired) {
  1008. base::TimeDelta time = GetPulsingBlockAnimationDelayForIndex(
  1009. pulsing_blocks_model_.view_size());
  1010. auto view = std::make_unique<PulsingBlockView>(
  1011. app_list_config_->grid_icon_size(), time);
  1012. pulsing_blocks_model_.Add(view.get(), pulsing_blocks_model_.view_size());
  1013. items_container_->AddChildView(std::move(view));
  1014. }
  1015. }
  1016. std::unique_ptr<AppListItemView> AppsGridView::CreateViewForItemAtIndex(
  1017. size_t index) {
  1018. // The |drag_view_| might be pending for deletion, therefore |view_model_|
  1019. // may have one more item than |item_list_|.
  1020. DCHECK_LE(index, item_list_->item_count());
  1021. auto view = std::make_unique<AppListItemView>(
  1022. app_list_config_, this, item_list_->item_at(index),
  1023. app_list_view_delegate_, AppListItemView::Context::kAppsGridView);
  1024. if (ItemViewsRequireLayers())
  1025. view->EnsureLayer();
  1026. if (cardified_state_)
  1027. view->EnterCardifyState();
  1028. return view;
  1029. }
  1030. void AppsGridView::SetSelectedItemByIndex(const GridIndex& index) {
  1031. if (GetIndexOfView(selected_view_) == index)
  1032. return;
  1033. AppListItemView* new_selection = GetViewAtIndex(index);
  1034. if (!new_selection)
  1035. return; // Keep current selection.
  1036. if (selected_view_)
  1037. selected_view_->SchedulePaint();
  1038. EnsureViewVisible(index);
  1039. selected_view_ = new_selection;
  1040. selected_view_->SchedulePaint();
  1041. selected_view_->NotifyAccessibilityEvent(ax::mojom::Event::kFocus, true);
  1042. if (selected_view_->HasNotificationBadge()) {
  1043. a11y_announcer_->AnnounceItemNotificationBadge(
  1044. selected_view_->title()->GetText());
  1045. }
  1046. }
  1047. GridIndex AppsGridView::GetIndexOfView(const AppListItemView* view) const {
  1048. const auto model_index = view_model_.GetIndexOfView(view);
  1049. if (!model_index.has_value())
  1050. return GridIndex();
  1051. return view_structure_.GetIndexFromModelIndex(model_index.value());
  1052. }
  1053. AppListItemView* AppsGridView::GetViewAtIndex(const GridIndex& index) const {
  1054. if (!IsValidIndex(index))
  1055. return nullptr;
  1056. const size_t model_index = view_structure_.GetModelIndexFromIndex(index);
  1057. return GetItemViewAt(model_index);
  1058. }
  1059. int AppsGridView::TilesPerPage(int page) const {
  1060. const int max_rows = GetMaxRowsInPage(page);
  1061. // In folders, the grid size depends on the number of items in the page.
  1062. if (IsInFolder()) {
  1063. // Leave room for at least one item.
  1064. if (!view_model()->view_size())
  1065. return 1;
  1066. int rows = (view_model()->view_size() - 1) / cols() + 1;
  1067. return std::min(max_rows, rows) * cols();
  1068. }
  1069. return max_rows * cols();
  1070. }
  1071. void AppsGridView::SetMaxColumnsInternal(int max_cols) {
  1072. if (max_cols_ == max_cols)
  1073. return;
  1074. max_cols_ = max_cols;
  1075. if (IsInFolder()) {
  1076. UpdateColsAndRowsForFolder();
  1077. } else {
  1078. cols_ = max_cols_;
  1079. }
  1080. }
  1081. void AppsGridView::SetIdealBoundsForViewToGridIndex(
  1082. size_t view_index_in_model,
  1083. const GridIndex& view_grid_index) {
  1084. gfx::Rect tile_bounds = GetExpectedTileBounds(view_grid_index);
  1085. tile_bounds.Offset(CalculateTransitionOffset(view_grid_index.page));
  1086. if (view_index_in_model < view_model_.view_size()) {
  1087. view_model_.set_ideal_bounds(view_index_in_model, tile_bounds);
  1088. } else {
  1089. pulsing_blocks_model_.set_ideal_bounds(
  1090. view_index_in_model - view_model_.view_size(), tile_bounds);
  1091. }
  1092. }
  1093. void AppsGridView::CalculateIdealBounds() {
  1094. if (view_structure_.mode() == PagedViewStructure::Mode::kPartialPages) {
  1095. CalculateIdealBoundsForPageStructureWithPartialPages();
  1096. return;
  1097. }
  1098. AppListItemView* view_with_locked_position = nullptr;
  1099. if (open_folder_info_)
  1100. view_with_locked_position = GetItemViewForItem(open_folder_info_->item_id);
  1101. std::set<GridIndex> reserved_slots;
  1102. reserved_slots.insert(reorder_placeholder_);
  1103. if (open_folder_info_) {
  1104. reserved_slots.insert(open_folder_info_->grid_index);
  1105. }
  1106. const size_t total_views =
  1107. view_model_.view_size() + pulsing_blocks_model_.view_size();
  1108. int slot_index = 0;
  1109. for (size_t i = 0; i < total_views; ++i) {
  1110. // NOTE: Because of pulsing blocks, `i` can count up to a value higher than
  1111. // the view model size. So verify that `i` is less than the view model size
  1112. // before fetching at index `i` from the view model.
  1113. if (i < view_model_.view_size() && view_model_.view_at(i) == drag_view_) {
  1114. continue;
  1115. }
  1116. if (i < view_model_.view_size() &&
  1117. view_model_.view_at(i) == view_with_locked_position) {
  1118. SetIdealBoundsForViewToGridIndex(i, open_folder_info_->grid_index);
  1119. continue;
  1120. }
  1121. GridIndex view_index = view_structure_.GetIndexFromModelIndex(slot_index);
  1122. // Leaves a blank space in the grid for the current reorder placeholder.
  1123. while (reserved_slots.count(view_index)) {
  1124. ++slot_index;
  1125. view_index = view_structure_.GetIndexFromModelIndex(slot_index);
  1126. }
  1127. if (i < view_model_.view_size())
  1128. view_model_.view_at(i)->SetMostRecentGridIndex(view_index, cols_);
  1129. SetIdealBoundsForViewToGridIndex(i, view_index);
  1130. ++slot_index;
  1131. }
  1132. }
  1133. void AppsGridView::CalculateIdealBoundsForPageStructureWithPartialPages() {
  1134. DCHECK(!IsInFolder());
  1135. DCHECK_EQ(view_structure_.mode(), PagedViewStructure::Mode::kPartialPages);
  1136. // |view_structure_| should only be updated at the end of drag. So make a
  1137. // copy of it and only change the copy for calculating the ideal bounds of
  1138. // each item view.
  1139. PagedViewStructure copied_view_structure(this);
  1140. // Allow empty pages in the copied view structure so an app list page does
  1141. // not get removed when dragging the last item in the page.
  1142. copied_view_structure.AllowEmptyPages();
  1143. {
  1144. // Delay page overflow sanitization until both drag view was removed, and
  1145. // reorder placeholder was added to the view structure.
  1146. std::unique_ptr<PagedViewStructure::ScopedSanitizeLock> sanitize_lock =
  1147. copied_view_structure.GetSanitizeLock();
  1148. copied_view_structure.LoadFromOther(view_structure_);
  1149. // Remove the item view being dragged.
  1150. if (drag_view_)
  1151. copied_view_structure.Remove(drag_view_);
  1152. // Leave a blank space in the grid for the current reorder placeholder.
  1153. if (IsValidIndex(reorder_placeholder()))
  1154. copied_view_structure.Add(nullptr, reorder_placeholder());
  1155. }
  1156. // Convert visual index to ideal bounds.
  1157. const auto& pages = copied_view_structure.pages();
  1158. int model_index = 0;
  1159. for (size_t i = 0; i < pages.size(); ++i) {
  1160. auto& page = pages[i];
  1161. for (size_t j = 0; j < page.size(); ++j) {
  1162. if (page[j] == nullptr)
  1163. continue;
  1164. // Skip the dragged view
  1165. if (view_model()->view_at(model_index) == drag_view_)
  1166. ++model_index;
  1167. gfx::Rect tile_slot = GetExpectedTileBounds(GridIndex(i, j));
  1168. tile_slot.Offset(CalculateTransitionOffset(i));
  1169. view_model()
  1170. ->view_at(model_index)
  1171. ->SetMostRecentGridIndex(GridIndex(i, j), cols_);
  1172. view_model()->set_ideal_bounds(model_index, tile_slot);
  1173. ++model_index;
  1174. }
  1175. }
  1176. // All pulsing blocks come after item views.
  1177. GridIndex pulsing_block_index = copied_view_structure.GetLastTargetIndex();
  1178. for (size_t i = 0; i < pulsing_blocks_model().view_size(); ++i) {
  1179. if (pulsing_block_index.slot == TilesPerPage(pulsing_block_index.page)) {
  1180. ++pulsing_block_index.page;
  1181. pulsing_block_index.slot = 0;
  1182. }
  1183. gfx::Rect tile_slot = GetExpectedTileBounds(pulsing_block_index);
  1184. tile_slot.Offset(CalculateTransitionOffset(pulsing_block_index.page));
  1185. pulsing_blocks_model().set_ideal_bounds(i, tile_slot);
  1186. ++pulsing_block_index.slot;
  1187. }
  1188. }
  1189. void AppsGridView::AnimateToIdealBounds() {
  1190. gfx::Rect visible_bounds(GetVisibleBounds());
  1191. gfx::Point visible_origin = visible_bounds.origin();
  1192. ConvertPointToTarget(this, items_container_, &visible_origin);
  1193. visible_bounds.set_origin(visible_origin);
  1194. CalculateIdealBounds();
  1195. for (size_t i = 0; i < view_model_.view_size(); ++i) {
  1196. AppListItemView* view = GetItemViewAt(i);
  1197. const gfx::Rect& target = view_model_.ideal_bounds(i);
  1198. const gfx::Rect& current = view->bounds();
  1199. if (bounds_animator_->GetTargetBounds(view) == target)
  1200. continue;
  1201. const bool current_visible = visible_bounds.Intersects(current);
  1202. const bool target_visible = visible_bounds.Intersects(target);
  1203. const bool visible =
  1204. !IsViewExplicitlyHidden(view) && (current_visible || target_visible);
  1205. if (visible && view->has_pending_row_change()) {
  1206. view->reset_has_pending_row_change();
  1207. row_change_animator_->AnimateBetweenRows(view, current, target);
  1208. } else if (visible || bounds_animator_->IsAnimating(view)) {
  1209. view->EnsureLayer();
  1210. bounds_animator_->AnimateViewTo(view, target);
  1211. bounds_animator_->SetAnimationDelegate(view, nullptr);
  1212. } else {
  1213. view->SetBoundsRect(target);
  1214. }
  1215. }
  1216. // Destroy layers created for drag if they're not longer necessary.
  1217. if (!bounds_animator_->IsAnimating())
  1218. OnBoundsAnimatorDone(bounds_animator_.get());
  1219. }
  1220. void AppsGridView::ExtractDragLocation(const gfx::Point& root_location,
  1221. gfx::Point* drag_point) {
  1222. // Use root location of |event| instead of location in |drag_view_|'s
  1223. // coordinates because |drag_view_| has a scale transform and location
  1224. // could have integer round error and causes jitter.
  1225. *drag_point = root_location;
  1226. DCHECK(GetWidget());
  1227. aura::Window::ConvertPointToTarget(
  1228. GetWidget()->GetNativeWindow()->GetRootWindow(),
  1229. GetWidget()->GetNativeWindow(), drag_point);
  1230. views::View::ConvertPointFromWidget(this, drag_point);
  1231. }
  1232. void AppsGridView::UpdateDropTargetRegion() {
  1233. DCHECK(drag_item_);
  1234. gfx::Point point = last_drag_point_;
  1235. point.set_x(GetMirroredXInView(point.x()));
  1236. if (IsPointWithinDragBuffer(point)) {
  1237. if (DragPointIsOverItem(point)) {
  1238. drop_target_region_ = ON_ITEM;
  1239. drop_target_ = GetNearestTileIndexForPoint(point);
  1240. return;
  1241. }
  1242. UpdateDropTargetForReorder(point);
  1243. drop_target_region_ = DragIsCloseToItem(point) ? NEAR_ITEM : BETWEEN_ITEMS;
  1244. return;
  1245. }
  1246. // Reset the reorder target to the original position if the cursor is outside
  1247. // the drag buffer or an item is dragged to a full page either from a folder
  1248. // or another page.
  1249. if (IsDraggingForReparentInRootLevelGridView()) {
  1250. drop_target_region_ = NO_TARGET;
  1251. return;
  1252. }
  1253. drop_target_ = drag_view_init_index_;
  1254. drop_target_region_ = DragIsCloseToItem(point) ? NEAR_ITEM : BETWEEN_ITEMS;
  1255. }
  1256. bool AppsGridView::DropTargetIsValidFolder() {
  1257. AppListItemView* target_view =
  1258. GetViewDisplayedAtSlotOnCurrentPage(drop_target_.slot);
  1259. if (!target_view)
  1260. return false;
  1261. AppListItem* target_item = target_view->item();
  1262. // Items can only be dropped into non-folders (which have no children) or
  1263. // folders that have fewer than the max allowed items.
  1264. // The OEM folder does not allow drag/drop of other items into it.
  1265. if (target_item->IsFolderFull() || IsOEMFolderItem(target_item))
  1266. return false;
  1267. if (!IsValidIndex(drop_target_))
  1268. return false;
  1269. return true;
  1270. }
  1271. bool AppsGridView::DragPointIsOverItem(const gfx::Point& point) {
  1272. // The reorder placeholder shouldn't count as a unique item
  1273. GridIndex nearest_tile_index(GetNearestTileIndexForPoint(point));
  1274. if (!IsValidIndex(nearest_tile_index) ||
  1275. nearest_tile_index == reorder_placeholder_) {
  1276. return false;
  1277. }
  1278. int distance_to_tile_center =
  1279. (point - GetExpectedTileBounds(nearest_tile_index).CenterPoint())
  1280. .Length();
  1281. if (distance_to_tile_center >
  1282. (app_list_config_->folder_dropping_circle_radius() *
  1283. (cardified_state_ ? GetAppsGridCardifiedScale() : 1.0f))) {
  1284. return false;
  1285. }
  1286. return true;
  1287. }
  1288. void AppsGridView::AnimateDragIconToTargetPosition(
  1289. AppListItem* drag_item,
  1290. const std::string& target_folder_id) {
  1291. // If drag icon proxy had not been created, just reshow the drag view.
  1292. if (!drag_icon_proxy_) {
  1293. OnDragIconDropDone();
  1294. return;
  1295. }
  1296. AppListItemView* target_folder_view =
  1297. !target_folder_id.empty() ? GetItemViewForItem(target_folder_id)
  1298. : nullptr;
  1299. // Calculate target item bounds.
  1300. gfx::Rect drag_icon_drop_bounds;
  1301. if (target_folder_id.empty()) {
  1302. // Find the view for drag item, and use its ideal bounds to calculate target
  1303. // drop bounds.
  1304. for (size_t i = 0; i < view_model_.view_size(); ++i) {
  1305. if (view_model_.view_at(i)->item() != drag_item)
  1306. continue;
  1307. auto* drag_view = view_model_.view_at(i);
  1308. // Get icon bounds in the drag view coordinates.
  1309. drag_icon_drop_bounds = drag_view->GetIconBounds();
  1310. // Get the expected drag item view location.
  1311. const gfx::Rect drag_view_ideal_bounds = view_model_.ideal_bounds(i);
  1312. // Position target icon bounds relative to the ideal drag view bounds.
  1313. drag_icon_drop_bounds.Offset(drag_view_ideal_bounds.x(),
  1314. drag_view_ideal_bounds.y());
  1315. break;
  1316. }
  1317. } else if (target_folder_view) {
  1318. // Calculate target bounds of dragged item.
  1319. drag_icon_drop_bounds =
  1320. GetTargetIconRectInFolder(drag_item, target_folder_view);
  1321. }
  1322. // Unable to calculate target bounds - bail out and reshow the drag view.
  1323. if (drag_icon_drop_bounds.IsEmpty()) {
  1324. OnDragIconDropDone();
  1325. return;
  1326. }
  1327. if (target_folder_view) {
  1328. DCHECK(target_folder_view->is_folder());
  1329. folder_icon_item_hider_ = std::make_unique<FolderIconItemHider>(
  1330. static_cast<AppListFolderItem*>(target_folder_view->item()), drag_item);
  1331. }
  1332. drag_icon_drop_bounds =
  1333. items_container_->GetMirroredRect(drag_icon_drop_bounds);
  1334. // Convert target bounds to in screen coordinates expected by drag icon proxy.
  1335. views::View::ConvertRectToScreen(items_container_, &drag_icon_drop_bounds);
  1336. drag_icon_proxy_->AnimateToBoundsAndCloseWidget(
  1337. drag_icon_drop_bounds, base::BindOnce(&AppsGridView::OnDragIconDropDone,
  1338. base::Unretained(this)));
  1339. }
  1340. void AppsGridView::OnDragIconDropDone() {
  1341. drag_view_hider_.reset();
  1342. folder_icon_item_hider_.reset();
  1343. drag_icon_proxy_.reset();
  1344. DestroyLayerItemsIfNotNeeded();
  1345. if (!folder_to_open_after_drag_icon_animation_.empty()) {
  1346. AppListItemView* folder_view =
  1347. GetItemViewForItem(folder_to_open_after_drag_icon_animation_);
  1348. folder_to_open_after_drag_icon_animation_.clear();
  1349. ShowFolderForView(folder_view, /*new_folder=*/true);
  1350. }
  1351. }
  1352. bool AppsGridView::DraggedItemCanEnterFolder() {
  1353. if (!IsFolderItem(drag_item_) && !folder_delegate_)
  1354. return true;
  1355. return false;
  1356. }
  1357. void AppsGridView::UpdateDropTargetForReorder(const gfx::Point& point) {
  1358. gfx::Rect bounds = GetContentsBounds();
  1359. bounds.Inset(GetTilePadding(GetSelectedPage()));
  1360. GridIndex nearest_tile_index = GetNearestTileIndexForPoint(point);
  1361. gfx::Point reorder_placeholder_center =
  1362. GetExpectedTileBounds(reorder_placeholder_).CenterPoint();
  1363. int x_offset_direction = 0;
  1364. if (nearest_tile_index == reorder_placeholder_) {
  1365. x_offset_direction = reorder_placeholder_center.x() <= point.x() ? -1 : 1;
  1366. } else {
  1367. x_offset_direction = reorder_placeholder_ < nearest_tile_index ? -1 : 1;
  1368. }
  1369. const gfx::Size total_tile_size = GetTotalTileSize(GetSelectedPage());
  1370. int row = nearest_tile_index.slot / cols_;
  1371. // Offset the target column based on the direction of the target. This will
  1372. // result in earlier targets getting their reorder zone shifted backwards
  1373. // and later targets getting their reorder zones shifted forwards.
  1374. //
  1375. // This makes reordering feel like the user is slotting items into the spaces
  1376. // between apps.
  1377. int x_offset = x_offset_direction *
  1378. (total_tile_size.width() / 2 -
  1379. app_list_config_->folder_dropping_circle_radius() *
  1380. (cardified_state_ ? GetAppsGridCardifiedScale() : 1.0f));
  1381. const int selected_page = GetSelectedPage();
  1382. int col = (point.x() - bounds.x() + x_offset -
  1383. GetGridCenteringOffset(selected_page).x()) /
  1384. total_tile_size.width();
  1385. col = base::clamp(col, 0, cols_ - 1);
  1386. drop_target_ =
  1387. std::min(GridIndex(selected_page, row * cols_ + col),
  1388. view_structure_.GetLastTargetIndexOfPage(selected_page));
  1389. DCHECK(IsValidReorderTargetIndex(drop_target_))
  1390. << drop_target_.ToString() << " selected page " << selected_page
  1391. << " row " << row << " col " << col << " "
  1392. << view_structure_.GetLastTargetIndexOfPage(drop_target_.page).ToString();
  1393. }
  1394. bool AppsGridView::DragIsCloseToItem(const gfx::Point& point) {
  1395. DCHECK(drag_item_);
  1396. GridIndex nearest_tile_index = GetNearestTileIndexForPoint(point);
  1397. if (nearest_tile_index == reorder_placeholder_)
  1398. return false;
  1399. const int distance_to_tile_center =
  1400. (point - GetExpectedTileBounds(nearest_tile_index).CenterPoint())
  1401. .Length();
  1402. // The minimum of |forty_percent_icon_spacing| and |double_icon_radius| is
  1403. // chosen to give an acceptable spacing on displays of any resolution: when
  1404. // items are very close together, using |forty_percent_icon_spacing| will
  1405. // prevent overlap and leave a reasonable gap, whereas when icons are very far
  1406. // apart, using |double_icon_radius| will prevent us from juding an overly
  1407. // large region as 'nearby'
  1408. const int forty_percent_icon_spacing =
  1409. (app_list_config_->grid_tile_width() + horizontal_tile_padding_ * 2) *
  1410. 0.4 * (cardified_state_ ? GetAppsGridCardifiedScale() : 1.0f);
  1411. const int double_icon_radius =
  1412. app_list_config_->folder_dropping_circle_radius() * 2 *
  1413. (cardified_state_ ? GetAppsGridCardifiedScale() : 1.0f);
  1414. const int minimum_drag_distance_for_reorder =
  1415. std::min(forty_percent_icon_spacing, double_icon_radius);
  1416. if (distance_to_tile_center < minimum_drag_distance_for_reorder)
  1417. return true;
  1418. return false;
  1419. }
  1420. void AppsGridView::OnReorderTimer() {
  1421. reorder_placeholder_ = drop_target_;
  1422. MaybeCreateDragReorderAccessibilityEvent();
  1423. AnimateToIdealBounds();
  1424. CreateGhostImageView();
  1425. }
  1426. void AppsGridView::OnFolderItemReparentTimer(Pointer pointer) {
  1427. DCHECK(folder_delegate_);
  1428. if (drag_out_of_folder_container_ && drag_view_) {
  1429. folder_delegate_->ReparentItem(pointer, drag_view_, last_drag_point_);
  1430. // Set the flag in the folder's grid view.
  1431. dragging_for_reparent_item_ = true;
  1432. // Do not observe any data change since it is going to be hidden.
  1433. item_list_->RemoveObserver(this);
  1434. item_list_ = nullptr;
  1435. }
  1436. }
  1437. void AppsGridView::UpdateDragStateInsideFolder(Pointer pointer,
  1438. const gfx::Point& drag_point) {
  1439. if (IsUnderOEMFolder())
  1440. return;
  1441. if (IsDraggingForReparentInHiddenGridView()) {
  1442. // Dispatch drag event to root level grid view for re-parenting folder
  1443. // folder item purpose.
  1444. DispatchDragEventForReparent(pointer, drag_point);
  1445. return;
  1446. }
  1447. // Calculate if the drag_view_ is dragged out of the folder's container
  1448. // ink bubble.
  1449. bool is_item_dragged_out_of_folder =
  1450. folder_delegate_->IsDragPointOutsideOfFolder(drag_point);
  1451. if (is_item_dragged_out_of_folder) {
  1452. if (!drag_out_of_folder_container_) {
  1453. folder_item_reparent_timer_.Start(
  1454. FROM_HERE, base::Milliseconds(kFolderItemReparentDelay),
  1455. base::BindOnce(&AppsGridView::OnFolderItemReparentTimer,
  1456. base::Unretained(this), pointer));
  1457. drag_out_of_folder_container_ = true;
  1458. }
  1459. } else {
  1460. folder_item_reparent_timer_.Stop();
  1461. drag_out_of_folder_container_ = false;
  1462. }
  1463. }
  1464. bool AppsGridView::IsDraggingForReparentInRootLevelGridView() const {
  1465. return (!folder_delegate_ && dragging_for_reparent_item_);
  1466. }
  1467. bool AppsGridView::IsDraggingForReparentInHiddenGridView() const {
  1468. return (folder_delegate_ && dragging_for_reparent_item_);
  1469. }
  1470. gfx::Rect AppsGridView::GetTargetIconRectInFolder(
  1471. AppListItem* drag_item,
  1472. AppListItemView* folder_item_view) {
  1473. const gfx::Rect view_ideal_bounds = view_model_.ideal_bounds(
  1474. view_model_.GetIndexOfView(folder_item_view).value());
  1475. const gfx::Rect icon_ideal_bounds =
  1476. folder_item_view->GetIconBoundsForTargetViewBounds(
  1477. app_list_config_, view_ideal_bounds,
  1478. folder_item_view->GetIconImage().size(), /*icon_scale=*/1.0f);
  1479. AppListFolderItem* folder_item =
  1480. static_cast<AppListFolderItem*>(folder_item_view->item());
  1481. return folder_item->GetTargetIconRectInFolderForItem(
  1482. *app_list_config_, drag_item, icon_ideal_bounds);
  1483. }
  1484. bool AppsGridView::IsUnderOEMFolder() {
  1485. if (!folder_delegate_)
  1486. return false;
  1487. return folder_delegate_->IsOEMFolder();
  1488. }
  1489. void AppsGridView::HandleKeyboardAppOperations(ui::KeyboardCode key_code,
  1490. bool folder) {
  1491. DCHECK(selected_view_);
  1492. if (folder) {
  1493. if (folder_delegate_)
  1494. folder_delegate_->HandleKeyboardReparent(selected_view_, key_code);
  1495. else
  1496. HandleKeyboardFoldering(key_code);
  1497. } else {
  1498. HandleKeyboardMove(key_code);
  1499. }
  1500. }
  1501. void AppsGridView::HandleKeyboardFoldering(ui::KeyboardCode key_code) {
  1502. const GridIndex source_index = GetIndexOfView(selected_view_);
  1503. const GridIndex target_index = GetTargetGridIndexForKeyboardMove(key_code);
  1504. if (!CanMoveSelectedToTargetForKeyboardFoldering(target_index))
  1505. return;
  1506. const std::u16string moving_view_title = selected_view_->title()->GetText();
  1507. AppListItemView* target_view =
  1508. GetViewDisplayedAtSlotOnCurrentPage(target_index.slot);
  1509. const std::u16string target_view_title = target_view->title()->GetText();
  1510. const bool target_view_is_folder = target_view->is_folder();
  1511. std::string folder_id;
  1512. bool is_new_folder = false;
  1513. if (MoveItemToFolder(selected_view_->item(), target_index,
  1514. kMoveByKeyboardIntoFolder, &folder_id, &is_new_folder)) {
  1515. a11y_announcer_->AnnounceKeyboardFoldering(
  1516. moving_view_title, target_view_title, target_view_is_folder);
  1517. AppListItemView* folder_view = GetItemViewForItem(folder_id);
  1518. if (folder_view) {
  1519. if (is_new_folder && features::IsProductivityLauncherEnabled()) {
  1520. SetOpenFolderInfo(folder_id, target_index, source_index);
  1521. ShowFolderForView(folder_view, /*new_folder=*/true);
  1522. } else {
  1523. folder_view->RequestFocus();
  1524. }
  1525. }
  1526. // Layout the grid to ensure the created folder's bounds are correct.
  1527. // Note that `open_folder_info_` affects ideal item bounds, so `Layout()`
  1528. // needs to be callsed after `SetOpenFolderInfo()`.
  1529. Layout();
  1530. }
  1531. }
  1532. bool AppsGridView::CanMoveSelectedToTargetForKeyboardFoldering(
  1533. const GridIndex& target_index) const {
  1534. DCHECK(selected_view_);
  1535. // To folder an item, the item must be moved into the folder, not the folder
  1536. // moved over the item.
  1537. const AppListItem* selected_item = selected_view_->item();
  1538. if (selected_item->is_folder())
  1539. return false;
  1540. // Do not allow foldering across pages because the destination folder cannot
  1541. // be seen.
  1542. if (target_index.page != GetIndexOfView(selected_view_).page)
  1543. return false;
  1544. return true;
  1545. }
  1546. bool AppsGridView::HandleVerticalFocusMovement(bool arrow_up) {
  1547. views::View* focused = GetFocusManager()->GetFocusedView();
  1548. if (focused->GetClassName() != AppListItemView::kViewClassName)
  1549. return false;
  1550. const GridIndex source_index =
  1551. GetIndexOfView(static_cast<const AppListItemView*>(focused));
  1552. int target_page = source_index.page;
  1553. int target_row = source_index.slot / cols_ + (arrow_up ? -1 : 1);
  1554. int target_col = source_index.slot % cols_;
  1555. if (target_row < 0) {
  1556. // Move focus to the last row of previous page if target row is negative.
  1557. --target_page;
  1558. // |target_page| may be invalid which makes |target_row| invalid, but
  1559. // |target_row| will not be used if |target_page| is invalid.
  1560. target_row = (GetNumberOfItemsOnPage(target_page) - 1) / cols_;
  1561. } else if (target_row > (GetNumberOfItemsOnPage(target_page) - 1) / cols_) {
  1562. // Move focus to the first row of next page if target row is beyond range.
  1563. ++target_page;
  1564. target_row = 0;
  1565. }
  1566. if (target_page < 0) {
  1567. // Move focus up outside the apps grid if target page is negative.
  1568. if (keyboard_controller_ &&
  1569. keyboard_controller_->MoveFocusUpFromAppsGrid(target_col)) {
  1570. // The delegate handled the focus move.
  1571. return true;
  1572. }
  1573. // Move focus backwards from the first item in the grid.
  1574. views::View* v = GetFocusManager()->GetNextFocusableView(
  1575. view_model_.view_at(0), /*starting_widget=*/nullptr, /*reverse=*/true,
  1576. /*dont_loop=*/false);
  1577. DCHECK(v);
  1578. v->RequestFocus();
  1579. return true;
  1580. }
  1581. if (target_page >= GetTotalPages()) {
  1582. // Move focus down outside the apps grid if target page is beyond range.
  1583. views::View* v = GetFocusManager()->GetNextFocusableView(
  1584. view_model_.view_at(view_model_.view_size() - 1),
  1585. /*starting_widget=*/nullptr, /*reverse=*/false,
  1586. /*dont_loop=*/false);
  1587. DCHECK(v);
  1588. v->RequestFocus();
  1589. return true;
  1590. }
  1591. GridIndex target_index(target_page, target_row * cols_ + target_col);
  1592. // Ensure the focus is within the range of the target page.
  1593. target_index.slot =
  1594. std::min(GetNumberOfItemsOnPage(target_page) - 1, target_index.slot);
  1595. if (IsValidIndex(target_index)) {
  1596. GetViewAtIndex(target_index)->RequestFocus();
  1597. return true;
  1598. }
  1599. return false;
  1600. }
  1601. void AppsGridView::UpdateColsAndRowsForFolder() {
  1602. if (!folder_delegate_)
  1603. return;
  1604. const int item_count = item_list_ ? item_list_->item_count() : 0;
  1605. // Ensure that there is always at least one column.
  1606. if (item_count == 0) {
  1607. cols_ = 1;
  1608. } else {
  1609. int preferred_cols = std::sqrt(item_list_->item_count() - 1) + 1;
  1610. cols_ = base::clamp(preferred_cols, 1, max_cols_);
  1611. }
  1612. PreferredSizeChanged();
  1613. }
  1614. void AppsGridView::DispatchDragEventForReparent(Pointer pointer,
  1615. const gfx::Point& drag_point) {
  1616. folder_delegate_->DispatchDragEventForReparent(pointer, drag_point);
  1617. }
  1618. void AppsGridView::EndDragFromReparentItemInRootLevel(
  1619. AppListItemView* original_parent_item_view,
  1620. bool events_forwarded_to_drag_drop_host,
  1621. bool cancel_drag,
  1622. std::unique_ptr<AppDragIconProxy> drag_icon_proxy) {
  1623. DCHECK(!IsInFolder());
  1624. DCHECK(view_model_.GetIndexOfView(original_parent_item_view).has_value());
  1625. // EndDrag was called before if |drag_view_| is nullptr.
  1626. if (!drag_item_)
  1627. return;
  1628. drag_icon_proxy_ = std::move(drag_icon_proxy);
  1629. AppListItem* drag_item = drag_item_;
  1630. DCHECK(IsDraggingForReparentInRootLevelGridView());
  1631. bool cancel_reparent = cancel_drag || drop_target_region_ == NO_TARGET;
  1632. // The ID of the folder to which the item gets dropped. It will get set when
  1633. // the item is moved to a folder. It will be set the to original folder ID if
  1634. // reparent is canceled.
  1635. std::string target_folder_id;
  1636. // Cache the original item folder id, as model updates may destroy the
  1637. // original folder item.
  1638. const std::string original_folder_id =
  1639. original_parent_item_view->item()->id();
  1640. if (!events_forwarded_to_drag_drop_host && !cancel_reparent) {
  1641. UpdateDropTargetRegion();
  1642. if (drop_target_region_ == ON_ITEM && DropTargetIsValidFolder() &&
  1643. DraggedItemCanEnterFolder()) {
  1644. bool is_new_folder = false;
  1645. if (MoveItemToFolder(drag_item, drop_target_, kMoveIntoAnotherFolder,
  1646. &target_folder_id, &is_new_folder)) {
  1647. // Announce folder dropping event before end of drag of reparented item.
  1648. MaybeCreateFolderDroppingAccessibilityEvent();
  1649. // If move to folder created a folder, layout the grid to ensure the
  1650. // created folder's bounds are correct.
  1651. Layout();
  1652. if (is_new_folder && features::IsProductivityLauncherEnabled()) {
  1653. folder_to_open_after_drag_icon_animation_ = target_folder_id;
  1654. SetOpenFolderInfo(target_folder_id, drop_target_,
  1655. reorder_placeholder_);
  1656. }
  1657. } else {
  1658. cancel_reparent = true;
  1659. }
  1660. } else if (drop_target_region_ != NO_TARGET &&
  1661. IsValidReorderTargetIndex(drop_target_)) {
  1662. ReparentItemForReorder(drag_item, drop_target_);
  1663. RecordAppMovingTypeMetrics(kMoveByDragOutOfFolder);
  1664. // Announce accessibility event before the end of drag for reparented
  1665. // item.
  1666. MaybeCreateDragReorderAccessibilityEvent();
  1667. } else {
  1668. NOTREACHED();
  1669. }
  1670. }
  1671. if (cancel_reparent)
  1672. target_folder_id = original_folder_id;
  1673. SetAsFolderDroppingTarget(drop_target_, false);
  1674. UpdatePaging();
  1675. ClearDragState();
  1676. if (cardified_state_)
  1677. MaybeEndCardifiedView();
  1678. else
  1679. AnimateToIdealBounds();
  1680. if (!cancel_reparent)
  1681. view_structure_.SaveToMetadata();
  1682. // Hide the |current_ghost_view_| after completed drag from within
  1683. // folder to |apps_grid_view_|.
  1684. BeginHideCurrentGhostImageView();
  1685. SetFocusAfterEndDrag(drag_item); // Maybe focus the search box.
  1686. AnimateDragIconToTargetPosition(drag_item, target_folder_id);
  1687. }
  1688. void AppsGridView::EndDragForReparentInHiddenFolderGridView() {
  1689. SetAsFolderDroppingTarget(drop_target_, false);
  1690. ClearDragState();
  1691. // Hide |current_ghost_view_| in the hidden folder grid view.
  1692. BeginHideCurrentGhostImageView();
  1693. }
  1694. void AppsGridView::HandleKeyboardReparent(
  1695. AppListItemView* reparented_view,
  1696. AppListItemView* original_parent_item_view,
  1697. ui::KeyboardCode key_code) {
  1698. DCHECK(key_code == ui::VKEY_LEFT || key_code == ui::VKEY_RIGHT ||
  1699. key_code == ui::VKEY_UP || key_code == ui::VKEY_DOWN);
  1700. DCHECK(!folder_delegate_);
  1701. DCHECK(view_model_.GetIndexOfView(original_parent_item_view).has_value());
  1702. // Set |original_parent_item_view| selected so |target_index| will be
  1703. // computed relative to the open folder.
  1704. SetSelectedView(original_parent_item_view);
  1705. const GridIndex target_index = GetTargetGridIndexForKeyboardReparent(
  1706. GetIndexOfView(original_parent_item_view), key_code);
  1707. ReparentItemForReorder(reparented_view->item(), target_index);
  1708. view_structure_.SaveToMetadata();
  1709. // Update paging because the move could have resulted in a
  1710. // page getting created.
  1711. UpdatePaging();
  1712. Layout();
  1713. EnsureViewVisible(target_index);
  1714. GetViewAtIndex(target_index)->RequestFocus();
  1715. AnnounceReorder(target_index);
  1716. RecordAppMovingTypeMetrics(kMoveByKeyboardOutOfFolder);
  1717. }
  1718. void AppsGridView::UpdatePagedViewStructure() {
  1719. view_structure_.SaveToMetadata();
  1720. }
  1721. bool AppsGridView::IsTabletMode() const {
  1722. return app_list_view_delegate_->IsInTabletMode();
  1723. }
  1724. views::AnimationBuilder AppsGridView::FadeOutVisibleItemsForReorder(
  1725. ReorderAnimationCallback done_callback) {
  1726. // The caller of this function is responsible for aborting the old reorder
  1727. // process before starting a new one.
  1728. DCHECK(!IsUnderWholeGridAnimation());
  1729. // Cancel the active bounds animations on item views if any.
  1730. bounds_animator_->Cancel();
  1731. grid_animation_status_ = AppListGridAnimationStatus::kReorderFadeOut;
  1732. reorder_animation_tracker_.emplace(
  1733. layer()->GetCompositor()->RequestNewThroughputTracker());
  1734. reorder_animation_tracker_->Start(metrics_util::ForSmoothness(
  1735. base::BindRepeating(&ReportReorderAnimationSmoothness, IsTabletMode())));
  1736. views::AnimationBuilder animation_builder;
  1737. grid_animation_abort_handle_ = animation_builder.GetAbortHandle();
  1738. if (fade_out_start_closure_for_test_)
  1739. animation_builder.OnStarted(std::move(fade_out_start_closure_for_test_));
  1740. // Set the preemption strategy to be `IMMEDIATELY_ANIMATE_TO_NEW_TARGET` so
  1741. // that if there is an existing apps grid animation, fade out animation for
  1742. // reorder is still going to run.
  1743. animation_builder
  1744. .SetPreemptionStrategy(
  1745. ui::LayerAnimator::IMMEDIATELY_ANIMATE_TO_NEW_TARGET)
  1746. .OnEnded(base::BindOnce(&AppsGridView::OnFadeOutAnimationEnded,
  1747. weak_factory_.GetWeakPtr(), done_callback,
  1748. /*abort=*/false))
  1749. .OnAborted(base::BindOnce(&AppsGridView::OnFadeOutAnimationEnded,
  1750. weak_factory_.GetWeakPtr(), done_callback,
  1751. /*abort=*/true))
  1752. .Once()
  1753. .SetDuration(kFadeOutAnimationDuration)
  1754. .SetOpacity(layer(), 0.f, gfx::Tween::LINEAR);
  1755. return animation_builder;
  1756. }
  1757. views::AnimationBuilder AppsGridView::FadeInVisibleItemsForReorder(
  1758. ReorderAnimationCallback done_callback) {
  1759. DCHECK_EQ(AppListGridAnimationStatus::kReorderIntermediaryState,
  1760. grid_animation_status_);
  1761. DCHECK(!bounds_animator_->IsAnimating());
  1762. // When `AppsGridView::OnListItemMoved()` is called due to item reorder,
  1763. // the layout updates asynchronously. Meanwhile, calculating the visible item
  1764. // range needs the up-to-date layout. Therefore update the layout explicitly
  1765. // before calculating `range`.
  1766. if (needs_layout())
  1767. Layout();
  1768. grid_animation_status_ = AppListGridAnimationStatus::kReorderFadeIn;
  1769. const absl::optional<VisibleItemIndexRange> range =
  1770. GetVisibleItemIndexRange();
  1771. views::AnimationBuilder animation_builder;
  1772. // No items to be sorted are visible - return an empty animation builder that
  1773. // ends immediately.
  1774. if (!range) {
  1775. animation_builder
  1776. .OnEnded(base::BindOnce(&AppsGridView::OnFadeInAnimationEnded,
  1777. weak_factory_.GetWeakPtr(), done_callback,
  1778. /*abort=*/true))
  1779. .OnAborted(base::BindOnce(&AppsGridView::OnFadeInAnimationEnded,
  1780. weak_factory_.GetWeakPtr(), done_callback,
  1781. /*abort=*/true))
  1782. .Once()
  1783. .SetDuration(base::TimeDelta());
  1784. return animation_builder;
  1785. }
  1786. // Only show the visible items during animation to reduce the cost of painting
  1787. // that is triggered by view bounds changes due to reorder.
  1788. for (size_t visible_view_index = range->first_index;
  1789. visible_view_index <= range->last_index; ++visible_view_index) {
  1790. view_model_.view_at(visible_view_index)->SetVisible(true);
  1791. }
  1792. grid_animation_abort_handle_ = animation_builder.GetAbortHandle();
  1793. animation_builder
  1794. .OnEnded(base::BindOnce(&AppsGridView::OnFadeInAnimationEnded,
  1795. weak_factory_.GetWeakPtr(), done_callback,
  1796. /*abort=*/false))
  1797. .OnAborted(base::BindOnce(&AppsGridView::OnFadeInAnimationEnded,
  1798. weak_factory_.GetWeakPtr(), done_callback,
  1799. /*abort=*/true))
  1800. .Once()
  1801. .SetDuration(kFadeInAnimationDuration)
  1802. .SetOpacity(layer(), 1.f, gfx::Tween::ACCEL_5_70_DECEL_90);
  1803. // Assume all the items matched by the indices in `range` are
  1804. // placed on the same page.
  1805. const int page_index =
  1806. view_structure_.GetIndexFromModelIndex(range->first_index).page;
  1807. const int base_offset =
  1808. kFadeAnimationOffsetRatio * GetTotalTileSize(page_index).height();
  1809. // The row of the first visible item.
  1810. const int base_row = range->first_index / cols_;
  1811. for (size_t visible_view_index = range->first_index;
  1812. visible_view_index <= range->last_index; ++visible_view_index) {
  1813. // Calculate translate offset for each view. NOTE: The items on the
  1814. // different rows have different fade in offsets. The ratio between the
  1815. // offset and `base_offset` is (relative_row_index + 2).
  1816. const int relative_row_index = visible_view_index / cols_ - base_row;
  1817. const int offset = (relative_row_index + 2) * base_offset;
  1818. views::View* animated_view = GetItemViewAt(visible_view_index);
  1819. PrepareForLayerAnimation(animated_view);
  1820. // Create a slide animation on `animted_view` using `sequence_block`'s
  1821. // existing time duration.
  1822. SlideViewIntoPositionWithSequenceBlock(
  1823. animated_view, offset,
  1824. /*time_delta=*/absl::nullopt, gfx::Tween::ACCEL_5_70_DECEL_90,
  1825. &animation_builder.GetCurrentSequence());
  1826. }
  1827. return animation_builder;
  1828. }
  1829. void AppsGridView::SlideVisibleItemsForHideContinueSection(int base_offset) {
  1830. DCHECK(IsTabletMode()); // This animation is only used in tablet mode.
  1831. if (needs_layout())
  1832. Layout();
  1833. const absl::optional<VisibleItemIndexRange> range =
  1834. GetVisibleItemIndexRange();
  1835. // Safety check, unlikely in production.
  1836. if (!range)
  1837. return;
  1838. // The continue section is on the 0th page. Don't animate if a different page
  1839. // is selected.
  1840. if (view_structure_.GetIndexFromModelIndex(range->first_index).page != 0)
  1841. return;
  1842. grid_animation_status_ = AppListGridAnimationStatus::kHideContinueSection;
  1843. views::AnimationBuilder animation_builder;
  1844. grid_animation_abort_handle_ = animation_builder.GetAbortHandle();
  1845. animation_builder
  1846. .SetPreemptionStrategy(
  1847. ui::LayerAnimator::IMMEDIATELY_ANIMATE_TO_NEW_TARGET)
  1848. .OnEnded(
  1849. base::BindOnce(&AppsGridView::OnHideContinueSectionAnimationEnded,
  1850. weak_factory_.GetWeakPtr()))
  1851. .OnAborted(
  1852. base::BindOnce(&AppsGridView::OnHideContinueSectionAnimationEnded,
  1853. weak_factory_.GetWeakPtr()))
  1854. .Once()
  1855. .SetDuration(base::Milliseconds(300));
  1856. // Animate each row of app icons with a different offset.
  1857. for (size_t item_index = range->first_index; item_index <= range->last_index;
  1858. ++item_index) {
  1859. const int row_index = item_index / cols_;
  1860. // The 0th row animates base_offset * 3 / 4
  1861. // The 1st row animates base_offset * 2 / 4
  1862. // The 2nd row animates base_offset * 1 / 4
  1863. const int vertical_offset = std::max(0, base_offset * (3 - row_index) / 4);
  1864. // Ensure each icon view has a layer. These are cleaned up on animation end.
  1865. views::View* icon = GetItemViewAt(item_index);
  1866. PrepareForLayerAnimation(icon);
  1867. // Slide each icon into position.
  1868. SlideViewIntoPositionWithSequenceBlock(
  1869. icon, vertical_offset, /*time_delta=*/absl::nullopt,
  1870. gfx::Tween::ACCEL_LIN_DECEL_100_3,
  1871. &animation_builder.GetCurrentSequence());
  1872. }
  1873. }
  1874. void AppsGridView::OnHideContinueSectionAnimationEnded() {
  1875. grid_animation_status_ = AppListGridAnimationStatus::kEmpty;
  1876. // Clean up the layers created for the app icon views.
  1877. DestroyLayerItemsIfNotNeeded();
  1878. }
  1879. bool AppsGridView::IsAnimationRunningForTest() {
  1880. return bounds_animator_->IsAnimating() ||
  1881. bounds_animation_for_cardified_state_in_progress_;
  1882. }
  1883. bool AppsGridView::FireFolderItemReparentTimerForTest() {
  1884. if (!folder_item_reparent_timer_.IsRunning())
  1885. return false;
  1886. folder_item_reparent_timer_.FireNow();
  1887. return true;
  1888. }
  1889. bool AppsGridView::FireDragToShelfTimerForTest() {
  1890. if (!host_drag_start_timer_.IsRunning())
  1891. return false;
  1892. host_drag_start_timer_.FireNow();
  1893. return true;
  1894. }
  1895. void AppsGridView::AddReorderCallbackForTest(
  1896. TestReorderDoneCallbackType done_callback) {
  1897. DCHECK(done_callback);
  1898. reorder_animation_callback_queue_for_test_.push(std::move(done_callback));
  1899. }
  1900. void AppsGridView::AddFadeOutAnimationStartClosureForTest(
  1901. base::OnceClosure start_closure) {
  1902. DCHECK(start_closure);
  1903. DCHECK(!fade_out_done_closure_for_test_);
  1904. fade_out_start_closure_for_test_ = std::move(start_closure);
  1905. }
  1906. void AppsGridView::AddFadeOutAnimationDoneClosureForTest(
  1907. base::OnceClosure done_closure) {
  1908. DCHECK(done_closure);
  1909. DCHECK(!fade_out_done_closure_for_test_);
  1910. fade_out_done_closure_for_test_ = std::move(done_closure);
  1911. }
  1912. bool AppsGridView::HasAnyWaitingReorderDoneCallbackForTest() const {
  1913. return !reorder_animation_callback_queue_for_test_.empty();
  1914. }
  1915. void AppsGridView::StartDragAndDropHostDrag() {
  1916. // When a drag and drop host is given, the item can be dragged out of the app
  1917. // list window. In that case a proxy widget needs to be used.
  1918. if (!drag_view_)
  1919. return;
  1920. // We have to hide the original item since the drag and drop host will do
  1921. // the OS dependent code to "lift off the dragged item". Apply the scale
  1922. // factor of this view's transform to the dragged view as well.
  1923. DCHECK(!IsDraggingForReparentInRootLevelGridView());
  1924. gfx::Point location_in_screen = drag_start_grid_view_;
  1925. views::View::ConvertPointToScreen(this, &location_in_screen);
  1926. const gfx::Point icon_location_in_screen =
  1927. drag_view_->GetIconBoundsInScreen().CenterPoint();
  1928. const bool use_blurred_background =
  1929. drag_view_->item()->is_folder() && IsTabletMode();
  1930. drag_icon_proxy_ = std::make_unique<AppDragIconProxy>(
  1931. GetWidget()->GetNativeWindow()->GetRootWindow(),
  1932. drag_view_->GetIconImage(), location_in_screen,
  1933. location_in_screen - icon_location_in_screen,
  1934. drag_view_->item()->is_folder() ? kDragAndDropProxyScale : 1.0f,
  1935. use_blurred_background);
  1936. drag_view_hider_ = std::make_unique<DragViewHider>(drag_view_);
  1937. }
  1938. void AppsGridView::DispatchDragEventToDragAndDropHost(
  1939. const gfx::Point& location_in_screen_coordinates) {
  1940. if (!drag_view_ || !drag_and_drop_host_)
  1941. return;
  1942. const bool should_host_handle_drag = drag_and_drop_host_->ShouldHandleDrag(
  1943. drag_view_->item()->id(), location_in_screen_coordinates);
  1944. if (!should_host_handle_drag) {
  1945. if (host_drag_start_timer_.IsRunning())
  1946. host_drag_start_timer_.AbandonAndStop();
  1947. // The event was issued inside the app menu and we should get all events.
  1948. if (forward_events_to_drag_and_drop_host_) {
  1949. // The DnD host was previously called and needs to be informed that the
  1950. // session returns to the owner.
  1951. forward_events_to_drag_and_drop_host_ = false;
  1952. // NOTE: Not passing the drag icon proxy to the drag and drop host because
  1953. // the drag operation is still in progress, and remains being handled by
  1954. // the apps grid view.
  1955. drag_and_drop_host_->EndDrag(true, /*drag_icon_proxy=*/nullptr);
  1956. }
  1957. return;
  1958. }
  1959. if (IsFolderItem(drag_view_->item()))
  1960. return;
  1961. // NOTE: Drag events are forwarded to drag and drop host whenever drag and
  1962. // drop host can handle them. At the time of writing, drag and drop host
  1963. // bounds and apps grid view bounds are not expected to overlap - if that
  1964. // changes, the logic for determining when to forward events to the host
  1965. // should be re-evaluated.
  1966. DCHECK(should_host_handle_drag);
  1967. // If the drag and drop host is not already handling drag events, make sure a
  1968. // drag and drop host start timer gets scheduled.
  1969. if (!forward_events_to_drag_and_drop_host_) {
  1970. if (!host_drag_start_timer_.IsRunning()) {
  1971. host_drag_start_timer_.Start(FROM_HERE, kShelfHandleIconDragDelay, this,
  1972. &AppsGridView::OnHostDragStartTimerFired);
  1973. MaybeStopPageFlip();
  1974. StopAutoScroll();
  1975. }
  1976. return;
  1977. }
  1978. DCHECK(forward_events_to_drag_and_drop_host_);
  1979. if (!drag_and_drop_host_->Drag(location_in_screen_coordinates,
  1980. drag_icon_proxy_->GetBoundsInScreen())) {
  1981. // The host is not active any longer and we cancel the operation.
  1982. forward_events_to_drag_and_drop_host_ = false;
  1983. // NOTE: Not passing the drag icon proxy to the drag and drop host because
  1984. // the drag operation is still in progress, and remains being handled by
  1985. // the apps grid view.
  1986. drag_and_drop_host_->EndDrag(true, /*drag_icon_proxy=*/nullptr);
  1987. }
  1988. }
  1989. bool AppsGridView::IsMoveTargetOnNewPage(const GridIndex& target) const {
  1990. // This is used to determine whether move should create a page break item,
  1991. // which is only relevant for page structure with partial pages.
  1992. DCHECK_EQ(view_structure_.mode(), PagedViewStructure::Mode::kPartialPages);
  1993. return target.page == GetTotalPages() ||
  1994. (target.page == GetTotalPages() - 1 &&
  1995. view_structure_.GetLastTargetIndexOfPage(target.page).slot == 0);
  1996. }
  1997. void AppsGridView::EnsurePageBreakBeforeItem(const std::string& item_id) {
  1998. DCHECK_EQ(view_structure_.mode(), PagedViewStructure::Mode::kPartialPages);
  1999. size_t item_list_index = 0;
  2000. if (item_list_->FindItemIndex(item_id, &item_list_index) &&
  2001. item_list_index > 0 &&
  2002. !item_list_->item_at(item_list_index - 1)->is_page_break()) {
  2003. model_->AddPageBreakItemAfter(item_list_->item_at(item_list_index - 1));
  2004. }
  2005. }
  2006. void AppsGridView::MoveItemInModel(AppListItem* item, const GridIndex& target) {
  2007. const std::string item_id = item->id();
  2008. size_t current_item_list_index = 0;
  2009. bool found = item_list_->FindItemIndex(item_id, &current_item_list_index);
  2010. CHECK(found);
  2011. size_t target_item_list_index =
  2012. view_structure_.GetTargetItemListIndexForMove(item, target);
  2013. const bool moving_to_new_page =
  2014. view_structure_.mode() == PagedViewStructure::Mode::kPartialPages &&
  2015. IsMoveTargetOnNewPage(target);
  2016. {
  2017. ScopedModelUpdate update(this);
  2018. item_list_->MoveItem(current_item_list_index, target_item_list_index);
  2019. // If the item is being moved to a new page, ensure that it's preceded by a
  2020. // page break.
  2021. if (moving_to_new_page)
  2022. EnsurePageBreakBeforeItem(item_id);
  2023. }
  2024. }
  2025. bool AppsGridView::MoveItemToFolder(AppListItem* item,
  2026. const GridIndex& target,
  2027. AppListAppMovingType move_type,
  2028. std::string* folder_id,
  2029. bool* is_new_folder) {
  2030. const std::string source_item_id = item->id();
  2031. const std::string source_folder_id = item->folder_id();
  2032. AppListItemView* target_view =
  2033. GetViewDisplayedAtSlotOnCurrentPage(target.slot);
  2034. DCHECK(target_view);
  2035. const std::string target_view_item_id = target_view->item()->id();
  2036. // An app is being reparented to its original folder. Just cancel the
  2037. // reparent.
  2038. if (target_view_item_id == source_folder_id)
  2039. return false;
  2040. *is_new_folder = !target_view->is_folder();
  2041. {
  2042. ScopedModelUpdate update(this);
  2043. *folder_id = model_->MergeItems(target_view_item_id, source_item_id);
  2044. }
  2045. if (folder_id->empty()) {
  2046. LOG(ERROR) << "Unable to merge into item id: " << target_view_item_id;
  2047. return false;
  2048. }
  2049. if (*is_new_folder)
  2050. base::RecordAction(base::UserMetricsAction("AppList_CreateFolder"));
  2051. MaybeRecordFolderDeleteUserAction(source_folder_id);
  2052. RecordAppMovingTypeMetrics(move_type);
  2053. return true;
  2054. }
  2055. void AppsGridView::ReparentItemForReorder(AppListItem* item,
  2056. const GridIndex& target) {
  2057. DCHECK(item->IsInFolder());
  2058. const std::string item_id = item->id();
  2059. const std::string source_folder_id = item->folder_id();
  2060. int target_item_index =
  2061. view_structure_.GetTargetItemListIndexForMove(item, target);
  2062. // Move the item from its parent folder to top level item list. Calculate the
  2063. // target position in the top level list.
  2064. syncer::StringOrdinal target_position;
  2065. if (target_item_index < static_cast<int>(item_list_->item_count()))
  2066. target_position = item_list_->item_at(target_item_index)->position();
  2067. const bool moving_to_new_page =
  2068. view_structure_.mode() == PagedViewStructure::Mode::kPartialPages &&
  2069. IsMoveTargetOnNewPage(target);
  2070. {
  2071. ScopedModelUpdate update(this);
  2072. model_->MoveItemToRootAt(item, target_position);
  2073. // If the item is being moved to a new page, ensure that it's preceded by a
  2074. // page break.
  2075. if (moving_to_new_page)
  2076. EnsurePageBreakBeforeItem(item_id);
  2077. }
  2078. MaybeRecordFolderDeleteUserAction(source_folder_id);
  2079. }
  2080. void AppsGridView::MaybeRecordFolderDeleteUserAction(
  2081. const std::string& folder_id) {
  2082. // Ignore the top-level grid (which isn't a folder and can't be deleted).
  2083. if (folder_id.empty())
  2084. return;
  2085. // If the folder disappeared from the model, record a user action.
  2086. if (!model_->FindFolderItem(folder_id))
  2087. base::RecordAction(base::UserMetricsAction("AppList_DeleteFolder"));
  2088. }
  2089. void AppsGridView::CancelContextMenusOnCurrentPage() {
  2090. GridIndex start_index(GetSelectedPage(), 0);
  2091. if (!IsValidIndex(start_index))
  2092. return;
  2093. size_t start = view_structure_.GetModelIndexFromIndex(start_index);
  2094. size_t end =
  2095. std::min(view_model_.view_size(), start + TilesPerPage(start_index.page));
  2096. for (size_t i = start; i < end; ++i)
  2097. GetItemViewAt(i)->CancelContextMenu();
  2098. }
  2099. void AppsGridView::DeleteItemViewAtIndex(size_t index) {
  2100. AppListItemView* item_view = GetItemViewAt(index);
  2101. view_model_.Remove(index);
  2102. view_structure_.Remove(item_view);
  2103. if (item_view == drag_view_)
  2104. drag_view_ = nullptr;
  2105. if (open_folder_info_ &&
  2106. open_folder_info_->item_id == item_view->item()->id()) {
  2107. open_folder_info_.reset();
  2108. }
  2109. delete item_view;
  2110. }
  2111. bool AppsGridView::IsPointWithinDragBuffer(const gfx::Point& point) const {
  2112. gfx::Rect rect(GetLocalBounds());
  2113. rect.Inset(-kDragBufferPx);
  2114. return rect.Contains(point);
  2115. }
  2116. void AppsGridView::ScheduleLayout(const gfx::Size& previous_grid_size) {
  2117. if (GetTileGridSize() != previous_grid_size) {
  2118. PreferredSizeChanged(); // Calls InvalidateLayout() internally.
  2119. } else {
  2120. InvalidateLayout();
  2121. }
  2122. DCHECK(needs_layout());
  2123. }
  2124. void AppsGridView::OnListItemAdded(size_t index, AppListItem* item) {
  2125. const gfx::Size initial_grid_size = GetTileGridSize();
  2126. if (!updating_model_)
  2127. EndDrag(true);
  2128. // Abort reorder animation before a view is added to `view_model_`.
  2129. MaybeAbortWholeGridAnimation();
  2130. if (!item->is_page_break()) {
  2131. AppListItemView* view = MaybeSwapPlaceholderAsset(index);
  2132. if (item == drag_item_) {
  2133. drag_view_ = view;
  2134. drag_view_hider_ = std::make_unique<DragViewHider>(drag_view_);
  2135. }
  2136. view->InitializeIconLoader();
  2137. }
  2138. view_structure_.LoadFromMetadata();
  2139. // If model update is in progress, paging should be updated when the operation
  2140. // that caused the model update completes.
  2141. if (!updating_model_) {
  2142. UpdatePaging();
  2143. UpdateColsAndRowsForFolder();
  2144. UpdatePulsingBlockViews();
  2145. }
  2146. // Schedule a layout, since the grid items may need their bounds updated.
  2147. ScheduleLayout(initial_grid_size);
  2148. items_container_->NotifyAccessibilityEvent(ax::mojom::Event::kChildrenChanged,
  2149. /*send_native_event=*/true);
  2150. }
  2151. void AppsGridView::OnListItemRemoved(size_t index, AppListItem* item) {
  2152. const gfx::Size initial_grid_size = GetTileGridSize();
  2153. if (!updating_model_)
  2154. EndDrag(true);
  2155. // Abort reorder animation before a view is deleted from `view_model_`.
  2156. MaybeAbortWholeGridAnimation();
  2157. if (!item->is_page_break())
  2158. DeleteItemViewAtIndex(GetModelIndexOfItem(item));
  2159. view_structure_.LoadFromMetadata();
  2160. // If model update is in progress, paging should be updated when the operation
  2161. // that caused the model update completes.
  2162. if (!updating_model_) {
  2163. UpdatePaging();
  2164. UpdateColsAndRowsForFolder();
  2165. UpdatePulsingBlockViews();
  2166. }
  2167. // Schedule a layout, since the grid items may need their bounds updated.
  2168. ScheduleLayout(initial_grid_size);
  2169. items_container_->NotifyAccessibilityEvent(ax::mojom::Event::kChildrenChanged,
  2170. /*send_native_event=*/true);
  2171. }
  2172. void AppsGridView::OnListItemMoved(size_t from_index,
  2173. size_t to_index,
  2174. AppListItem* item) {
  2175. // Abort reorder animation if the apps grid is updated by the user.
  2176. if (!updating_model_) {
  2177. MaybeAbortWholeGridAnimation();
  2178. EndDrag(true);
  2179. }
  2180. if (item->is_page_break()) {
  2181. LOG(ERROR) << "Page break item is moved: " << item->id();
  2182. } else {
  2183. // The item is updated in the item list but the view_model is not updated,
  2184. // so get current model index by looking up view_model and predict the
  2185. // target model index based on its current item index.
  2186. size_t from_model_index = GetModelIndexOfItem(item);
  2187. size_t to_model_index = GetTargetModelIndexFromItemIndex(to_index);
  2188. view_model_.Move(from_model_index, to_model_index);
  2189. items_container_->ReorderChildView(view_model_.view_at(to_model_index),
  2190. to_model_index);
  2191. items_container_->NotifyAccessibilityEvent(
  2192. ax::mojom::Event::kChildrenChanged, true /* send_native_event */);
  2193. }
  2194. view_structure_.LoadFromMetadata();
  2195. // If model update is in progress, paging should be updated when the operation
  2196. // that caused the model update completes.
  2197. if (!updating_model_) {
  2198. UpdatePaging();
  2199. UpdateColsAndRowsForFolder();
  2200. UpdatePulsingBlockViews();
  2201. }
  2202. if (!updating_model_ && GetWidget() && GetWidget()->IsVisible() &&
  2203. enable_item_move_animation_) {
  2204. AnimateToIdealBounds();
  2205. } else if (IsUnderWholeGridAnimation()) {
  2206. // During reorder animation, multiple items could be moved subsequently so
  2207. // use the asynchronous layout to reduce painting cost.
  2208. InvalidateLayout();
  2209. } else {
  2210. Layout();
  2211. }
  2212. }
  2213. void AppsGridView::OnAppListModelStatusChanged() {
  2214. UpdatePulsingBlockViews();
  2215. Layout();
  2216. SchedulePaint();
  2217. }
  2218. void AppsGridView::OnBoundsAnimatorProgressed(views::BoundsAnimator* animator) {
  2219. }
  2220. void AppsGridView::OnBoundsAnimatorDone(views::BoundsAnimator* animator) {
  2221. row_change_animator_->OnBoundsAnimatorDone();
  2222. DestroyLayerItemsIfNotNeeded();
  2223. }
  2224. void AppsGridView::DestroyLayerItemsIfNotNeeded() {
  2225. if (ItemViewsRequireLayers())
  2226. return;
  2227. for (const auto& entry : view_model_.entries())
  2228. entry.view->DestroyLayer();
  2229. }
  2230. bool AppsGridView::ItemViewsRequireLayers() const {
  2231. // Layers required for app list item move animations during drag (to make room
  2232. // for the current placeholder).
  2233. if (drag_item_ || drag_icon_proxy_)
  2234. return true;
  2235. // Bounds animations are in progress, which use layers to animate transforms.
  2236. if (bounds_animation_for_cardified_state_in_progress_ ||
  2237. (bounds_animator_ && bounds_animator_->IsAnimating()) ||
  2238. row_change_animator_->IsAnimating()) {
  2239. return true;
  2240. }
  2241. // Reorder animation animate app list item layers.
  2242. if (IsUnderWholeGridAnimation())
  2243. return true;
  2244. // Folder position is changing after folder closure - this involves animating
  2245. // folder item view layer out and in, and changing other view's bounds.
  2246. if (reordering_folder_view_)
  2247. return true;
  2248. return false;
  2249. }
  2250. GridIndex AppsGridView::GetNearestTileIndexForPoint(
  2251. const gfx::Point& point) const {
  2252. gfx::Rect bounds = GetContentsBounds();
  2253. const int current_page = GetSelectedPage();
  2254. bounds.Inset(GetTilePadding(current_page));
  2255. const gfx::Size total_tile_size = GetTotalTileSize(current_page);
  2256. const gfx::Vector2d grid_offset = GetGridCenteringOffset(current_page);
  2257. DCHECK_GT(total_tile_size.width(), 0);
  2258. int col = base::clamp(
  2259. (point.x() - bounds.x() - grid_offset.x()) / total_tile_size.width(), 0,
  2260. cols_ - 1);
  2261. DCHECK_GT(total_tile_size.height(), 0);
  2262. int max_row = TilesPerPage(current_page) / cols_ - 1;
  2263. int row = base::clamp(
  2264. (point.y() - bounds.y() - grid_offset.y()) / total_tile_size.height(), 0,
  2265. max_row);
  2266. return GridIndex(current_page, row * cols_ + col);
  2267. }
  2268. gfx::Rect AppsGridView::GetExpectedTileBounds(const GridIndex& index) const {
  2269. if (!cols_)
  2270. return gfx::Rect();
  2271. gfx::Rect bounds(GetContentsBounds());
  2272. bounds.Inset(GetTilePadding(index.page));
  2273. int row = index.slot / cols_;
  2274. int col = index.slot % cols_;
  2275. const gfx::Size total_tile_size = GetTotalTileSize(index.page);
  2276. gfx::Rect tile_bounds(gfx::Point(bounds.x() + col * total_tile_size.width(),
  2277. bounds.y() + row * total_tile_size.height()),
  2278. total_tile_size);
  2279. tile_bounds.Offset(GetGridCenteringOffset(index.page));
  2280. tile_bounds.Inset(-GetTilePadding(index.page));
  2281. return tile_bounds;
  2282. }
  2283. bool AppsGridView::IsViewHiddenForDrag(const views::View* view) const {
  2284. return drag_view_hider_ && drag_view_hider_->drag_view() == view;
  2285. }
  2286. bool AppsGridView::IsViewHiddenForFolderReorder(const views::View* view) const {
  2287. return reordering_folder_view_ && *reordering_folder_view_ == view;
  2288. }
  2289. bool AppsGridView::IsUnderWholeGridAnimation() const {
  2290. return grid_animation_status_ != AppListGridAnimationStatus::kEmpty;
  2291. }
  2292. bool AppsGridView::IsViewExplicitlyHidden(const views::View* view) const {
  2293. return IsViewHiddenForDrag(view) || IsViewHiddenForFolderReorder(view);
  2294. }
  2295. void AppsGridView::MaybeAbortWholeGridAnimation() {
  2296. switch (grid_animation_status_) {
  2297. case AppListGridAnimationStatus::kEmpty:
  2298. case AppListGridAnimationStatus::kReorderIntermediaryState:
  2299. // No active whole-grid animation so nothing to do.
  2300. break;
  2301. case AppListGridAnimationStatus::kReorderFadeOut:
  2302. case AppListGridAnimationStatus::kReorderFadeIn:
  2303. case AppListGridAnimationStatus::kHideContinueSection:
  2304. DCHECK(grid_animation_abort_handle_);
  2305. grid_animation_abort_handle_.reset();
  2306. break;
  2307. }
  2308. }
  2309. AppListItemView* AppsGridView::GetViewDisplayedAtSlotOnCurrentPage(
  2310. int slot) const {
  2311. if (slot < 0)
  2312. return nullptr;
  2313. // Calculate the original bound of the tile at |index|.
  2314. gfx::Rect tile_rect =
  2315. GetExpectedTileBounds(GridIndex(GetSelectedPage(), slot));
  2316. tile_rect.Offset(CalculateTransitionOffset(GetSelectedPage()));
  2317. const auto& entries = view_model_.entries();
  2318. const auto iter =
  2319. std::find_if(entries.begin(), entries.end(), [&](const auto& entry) {
  2320. return entry.view->bounds() == tile_rect && entry.view != drag_view_;
  2321. });
  2322. return iter == entries.end() ? nullptr
  2323. : static_cast<AppListItemView*>(iter->view);
  2324. }
  2325. void AppsGridView::SetAsFolderDroppingTarget(const GridIndex& target_index,
  2326. bool is_target_folder) {
  2327. AppListItemView* target_view =
  2328. GetViewDisplayedAtSlotOnCurrentPage(target_index.slot);
  2329. if (target_view) {
  2330. target_view->SetAsAttemptedFolderTarget(is_target_folder);
  2331. if (is_target_folder)
  2332. target_view->OnDraggedViewEnter();
  2333. else
  2334. target_view->OnDraggedViewExit();
  2335. }
  2336. }
  2337. GridIndex AppsGridView::GetTargetGridIndexForKeyboardMove(
  2338. ui::KeyboardCode key_code) const {
  2339. DCHECK(key_code == ui::VKEY_LEFT || key_code == ui::VKEY_RIGHT ||
  2340. key_code == ui::VKEY_UP || key_code == ui::VKEY_DOWN);
  2341. DCHECK(selected_view_);
  2342. const GridIndex source_index = GetIndexOfView(selected_view_);
  2343. GridIndex target_index;
  2344. if (key_code == ui::VKEY_LEFT || key_code == ui::VKEY_RIGHT) {
  2345. // Define backward key for traversal based on RTL.
  2346. const ui::KeyboardCode backward =
  2347. base::i18n::IsRTL() ? ui::VKEY_RIGHT : ui::VKEY_LEFT;
  2348. size_t target_model_index =
  2349. view_model_.GetIndexOfView(selected_view_).value();
  2350. if (target_model_index > 0 || key_code != backward)
  2351. target_model_index += (key_code == backward) ? -1 : 1;
  2352. // A forward move on the last item in |view_model_| should result in page
  2353. // creation.
  2354. if (target_model_index == view_model_.view_size()) {
  2355. // Only grid structure with partial pages supports page creation by
  2356. // keyboard move.
  2357. if (view_structure_.mode() != PagedViewStructure::Mode::kPartialPages)
  2358. return source_index;
  2359. // If |source_index| is the last item in the grid on a page by itself,
  2360. // moving right to a new page should be a no-op.
  2361. if (view_structure_.items_on_page(source_index.page) == 1)
  2362. return source_index;
  2363. return GridIndex(GetTotalPages(), 0);
  2364. }
  2365. target_index = GetIndexOfView(
  2366. static_cast<const AppListItemView*>(GetItemViewAt(target_model_index)));
  2367. if (view_structure_.mode() == PagedViewStructure::Mode::kPartialPages &&
  2368. key_code == backward && target_index.page < source_index.page &&
  2369. !view_structure_.IsFullPage(target_index.page)) {
  2370. // Apps swap positions if the target page is the same as the
  2371. // destination page, or the target page is full. If the page is not
  2372. // full the app is dumped on the page. Increase the slot in this case
  2373. // to account for the new available spot.
  2374. ++target_index.slot;
  2375. }
  2376. return target_index;
  2377. }
  2378. // Handle the vertical move. Attempt to place the app in the same column.
  2379. int target_page = source_index.page;
  2380. int target_row =
  2381. source_index.slot / cols_ + (key_code == ui::VKEY_UP ? -1 : 1);
  2382. if (target_row < 0) {
  2383. // The app will move to the last row of the previous page.
  2384. --target_page;
  2385. if (target_page < 0)
  2386. return source_index;
  2387. // When moving up, place the app in the last row.
  2388. target_row = (GetNumberOfItemsOnPage(target_page) - 1) / cols_;
  2389. } else if (target_row > (GetNumberOfItemsOnPage(target_page) - 1) / cols_) {
  2390. // The app will move to the first row of the next page.
  2391. ++target_page;
  2392. if (folder_delegate_) {
  2393. if (target_page >= GetTotalPages())
  2394. return source_index;
  2395. } else {
  2396. if (target_page >= view_structure_.total_pages()) {
  2397. // If |source_index| page only has one item, moving down to a new page
  2398. // should be a no-op.
  2399. if (view_structure_.items_on_page(source_index.page) == 1)
  2400. return source_index;
  2401. return GridIndex(target_page, 0);
  2402. }
  2403. }
  2404. target_row = 0;
  2405. }
  2406. // The ideal slot shares a column with |source_index|.
  2407. const int ideal_slot = target_row * cols_ + source_index.slot % cols_;
  2408. if (folder_delegate_) {
  2409. return GridIndex(
  2410. target_page,
  2411. std::min(GetNumberOfItemsOnPage(target_page) - 1, ideal_slot));
  2412. }
  2413. // If the app is being moved to a new page there is 1 extra slot available.
  2414. const int last_slot_in_target_page =
  2415. view_structure_.items_on_page(target_page) -
  2416. (source_index.page != target_page ? 0 : 1);
  2417. return GridIndex(target_page, std::min(last_slot_in_target_page, ideal_slot));
  2418. }
  2419. GridIndex AppsGridView::GetTargetGridIndexForKeyboardReparent(
  2420. const GridIndex& folder_index,
  2421. ui::KeyboardCode key_code) const {
  2422. DCHECK(!folder_delegate_) << "Reparenting target calculations occur from the "
  2423. "root AppsGridView, not the folder AppsGridView";
  2424. // A backward move means the item will be placed previous to the folder. To do
  2425. // this without displacing other items, place the item in the folders slot.
  2426. // The folder will then shift forward.
  2427. const ui::KeyboardCode backward =
  2428. base::i18n::IsRTL() ? ui::VKEY_RIGHT : ui::VKEY_LEFT;
  2429. if (key_code == backward)
  2430. return folder_index;
  2431. GridIndex target_index = GetTargetGridIndexForKeyboardMove(key_code);
  2432. // If the item is expected to be positioned after the parent view,
  2433. // `GetTargetGridIndexForKeyboardMove()` may return folder index to indicate
  2434. // no-op operation for move (e.g. if the folder is the last item), assuming
  2435. // that there are no slots available. Reparent is an insertion operation, so
  2436. // creating an extra trailing slot is allowed.
  2437. if (target_index == folder_index &&
  2438. (key_code != ui::VKEY_UP && key_code != backward)) {
  2439. if (view_structure_.IsFullPage(target_index.page)) {
  2440. return GridIndex(target_index.page + 1, 0);
  2441. }
  2442. return GridIndex(target_index.page, target_index.slot + 1);
  2443. }
  2444. // Ensure the item is placed on the same page as the folder when possible.
  2445. if (target_index.page < folder_index.page)
  2446. return folder_index;
  2447. const int folder_page_size = TilesPerPage(folder_index.page);
  2448. if (target_index.page > folder_index.page &&
  2449. folder_index.slot + 1 < folder_page_size) {
  2450. return GridIndex(folder_index.page, folder_index.slot + 1);
  2451. }
  2452. return target_index;
  2453. }
  2454. void AppsGridView::HandleKeyboardMove(ui::KeyboardCode key_code) {
  2455. DCHECK(selected_view_);
  2456. const GridIndex target_index = GetTargetGridIndexForKeyboardMove(key_code);
  2457. const GridIndex starting_index = GetIndexOfView(selected_view_);
  2458. if (target_index == starting_index ||
  2459. !IsValidReorderTargetIndex(target_index)) {
  2460. return;
  2461. }
  2462. handling_keyboard_move_ = true;
  2463. AppListItemView* original_selected_view = selected_view_;
  2464. const GridIndex original_selected_view_index =
  2465. GetIndexOfView(original_selected_view);
  2466. // Moving an AppListItemView is either a swap within the origin page, a swap
  2467. // to a full page, or a dump to a page with room. A move within a folder is
  2468. // always a swap because there are no gaps.
  2469. const bool swap_items =
  2470. folder_delegate_ || view_structure_.IsFullPage(target_index.page) ||
  2471. target_index.page == original_selected_view_index.page;
  2472. AppListItemView* target_view = GetViewAtIndex(target_index);
  2473. {
  2474. // If the move is a two part operation (swap) do not clear the overflow
  2475. // during the initial move. Clearing the overflow when |target_index| is on
  2476. // a full page results in the last item being pushed to the next page.
  2477. std::unique_ptr<PagedViewStructure::ScopedSanitizeLock> sanitize_lock =
  2478. view_structure_.GetSanitizeLock();
  2479. MoveItemInModel(selected_view_->item(), target_index);
  2480. if (swap_items) {
  2481. DCHECK(target_view);
  2482. MoveItemInModel(target_view->item(), original_selected_view_index);
  2483. }
  2484. }
  2485. view_structure_.SaveToMetadata();
  2486. int target_page = target_index.page;
  2487. if (!folder_delegate_) {
  2488. // Update paging because the move could have resulted in a
  2489. // page getting collapsed or created.
  2490. UpdatePaging();
  2491. // |target_page| may change due to a page collapsing.
  2492. target_page = std::min(GetTotalPages() - 1, target_index.page);
  2493. }
  2494. Layout();
  2495. EnsureViewVisible(GridIndex(target_page, target_index.slot));
  2496. SetSelectedView(original_selected_view);
  2497. AnnounceReorder(target_index);
  2498. if (target_index.page != original_selected_view_index.page &&
  2499. !folder_delegate_) {
  2500. RecordPageSwitcherSource(kMoveAppWithKeyboard, IsTabletMode());
  2501. }
  2502. }
  2503. bool AppsGridView::IsValidIndex(const GridIndex& index) const {
  2504. return index.page >= 0 && index.page < GetTotalPages() && index.slot >= 0 &&
  2505. index.slot < TilesPerPage(index.page) &&
  2506. static_cast<size_t>(view_structure_.GetModelIndexFromIndex(index)) <
  2507. view_model_.view_size();
  2508. }
  2509. bool AppsGridView::IsValidReorderTargetIndex(const GridIndex& index) const {
  2510. return view_structure_.IsValidReorderTargetIndex(index);
  2511. }
  2512. size_t AppsGridView::GetModelIndexOfItem(const AppListItem* item) const {
  2513. const auto& entries = view_model_.entries();
  2514. const auto iter =
  2515. std::find_if(entries.begin(), entries.end(), [item](const auto& entry) {
  2516. return static_cast<AppListItemView*>(entry.view)->item() == item;
  2517. });
  2518. return static_cast<size_t>(std::distance(entries.begin(), iter));
  2519. }
  2520. size_t AppsGridView::GetTargetModelIndexFromItemIndex(size_t item_index) {
  2521. if (folder_delegate_)
  2522. return item_index;
  2523. CHECK(item_index <= item_list_->item_count());
  2524. size_t target_model_index = 0;
  2525. for (size_t i = 0; i < item_index; ++i) {
  2526. if (!item_list_->item_at(i)->is_page_break())
  2527. ++target_model_index;
  2528. }
  2529. return target_model_index;
  2530. }
  2531. int AppsGridView::GetNumberOfItemsOnPage(int page) const {
  2532. if (page < 0 || page >= GetTotalPages())
  2533. return 0;
  2534. if (!folder_delegate_ && !features::IsProductivityLauncherEnabled())
  2535. return view_structure_.items_on_page(page);
  2536. // We are guaranteed not on the last page, so the page must be full.
  2537. if (page < GetTotalPages() - 1)
  2538. return TilesPerPage(page);
  2539. // We are on the last page, so calculate the number of items on the page.
  2540. size_t item_count = view_model_.view_size();
  2541. int current_page = 0;
  2542. while (current_page < GetTotalPages() - 1) {
  2543. item_count -= TilesPerPage(current_page);
  2544. ++current_page;
  2545. }
  2546. return item_count;
  2547. }
  2548. void AppsGridView::MaybeCreateFolderDroppingAccessibilityEvent() {
  2549. if (!drag_item_ || !drag_view_)
  2550. return;
  2551. if (drop_target_region_ != ON_ITEM || !DropTargetIsValidFolder() ||
  2552. IsFolderItem(drag_item_) || folder_delegate_ ||
  2553. drop_target_ == last_folder_dropping_a11y_event_location_) {
  2554. return;
  2555. }
  2556. last_folder_dropping_a11y_event_location_ = drop_target_;
  2557. last_reorder_a11y_event_location_ = GridIndex();
  2558. AppListItemView* drop_view =
  2559. GetViewDisplayedAtSlotOnCurrentPage(drop_target_.slot);
  2560. DCHECK(drop_view);
  2561. a11y_announcer_->AnnounceFolderDrop(drag_view_->title()->GetText(),
  2562. drop_view->title()->GetText(),
  2563. drop_view->is_folder());
  2564. }
  2565. void AppsGridView::MaybeCreateDragReorderAccessibilityEvent() {
  2566. if (drop_target_region_ == ON_ITEM && !IsFolderItem(drag_item_))
  2567. return;
  2568. // If app was dragged out of folder, no need to announce location for the
  2569. // now closed folder.
  2570. if (drag_out_of_folder_container_)
  2571. return;
  2572. // If drop_target is not set or was already reset, then return.
  2573. if (drop_target_ == GridIndex())
  2574. return;
  2575. // Don't create a11y event if |drop_target| has not changed.
  2576. if (last_reorder_a11y_event_location_ == drop_target_)
  2577. return;
  2578. last_folder_dropping_a11y_event_location_ = GridIndex();
  2579. last_reorder_a11y_event_location_ = drop_target_;
  2580. AnnounceReorder(last_reorder_a11y_event_location_);
  2581. }
  2582. void AppsGridView::AnnounceReorder(const GridIndex& target_index) {
  2583. const int page = target_index.page + 1;
  2584. const int row =
  2585. ((target_index.slot - (target_index.slot % cols_)) / cols_) + 1;
  2586. const int col = (target_index.slot % cols_) + 1;
  2587. if (view_structure_.mode() == PagedViewStructure::Mode::kSinglePage) {
  2588. // Don't announce the page for single-page grids (e.g. scrollable grids).
  2589. a11y_announcer_->AnnounceAppsGridReorder(row, col);
  2590. } else {
  2591. // Announce the page for paged grids.
  2592. a11y_announcer_->AnnounceAppsGridReorder(page, row, col);
  2593. }
  2594. }
  2595. void AppsGridView::CreateGhostImageView() {
  2596. if (!features::IsProductivityLauncherEnabled())
  2597. return;
  2598. if (!drag_item_)
  2599. return;
  2600. // OnReorderTimer() can trigger this function even when the
  2601. // |reorder_placeholder_| does not change, no need to set a new GhostImageView
  2602. // in this case.
  2603. if (reorder_placeholder_ == current_ghost_location_)
  2604. return;
  2605. // When the item is dragged outside the boundaries of the app grid, if the
  2606. // |reorder_placeholder_| moves to another page, then do not show a ghost.
  2607. if (GetSelectedPage() != reorder_placeholder_.page) {
  2608. BeginHideCurrentGhostImageView();
  2609. return;
  2610. }
  2611. BeginHideCurrentGhostImageView();
  2612. current_ghost_location_ = reorder_placeholder_;
  2613. if (last_ghost_view_)
  2614. delete last_ghost_view_;
  2615. // Preserve |current_ghost_view_| while it fades out and instantiate a new
  2616. // GhostImageView that will fade in.
  2617. last_ghost_view_ = current_ghost_view_;
  2618. auto current_ghost_view =
  2619. std::make_unique<GhostImageView>(reorder_placeholder_);
  2620. gfx::Rect ghost_view_bounds = GetExpectedTileBounds(reorder_placeholder_);
  2621. ghost_view_bounds.Offset(
  2622. CalculateTransitionOffset(reorder_placeholder_.page));
  2623. current_ghost_view->Init(ghost_view_bounds,
  2624. app_list_config_->grid_focus_corner_radius());
  2625. current_ghost_view_ =
  2626. items_container_->AddChildView(std::move(current_ghost_view));
  2627. current_ghost_view_->FadeIn();
  2628. // Adding the ghost view can reorder the child layers of the
  2629. // |items_container_| so make sure the background cards remain at the bottom.
  2630. StackCardsAtBottom();
  2631. }
  2632. void AppsGridView::BeginHideCurrentGhostImageView() {
  2633. if (!features::IsProductivityLauncherEnabled())
  2634. return;
  2635. current_ghost_location_ = GridIndex();
  2636. if (current_ghost_view_)
  2637. current_ghost_view_->FadeOut();
  2638. }
  2639. void AppsGridView::PrepareItemsForBoundsAnimation() {
  2640. for (size_t i = 0; i < view_model_.view_size(); ++i)
  2641. view_model_.view_at(i)->EnsureLayer();
  2642. }
  2643. void AppsGridView::OnAppListItemViewActivated(
  2644. AppListItemView* pressed_item_view,
  2645. const ui::Event& event) {
  2646. if (IsDragging())
  2647. return;
  2648. if (IsFolderItem(pressed_item_view->item())) {
  2649. // Note that `folder_controller_` will be null inside a folder apps grid,
  2650. // but those grid are not expected to contain folder items.
  2651. DCHECK(folder_controller_);
  2652. SetOpenFolderInfo(pressed_item_view->item()->id(),
  2653. GetIndexOfView(pressed_item_view), GridIndex());
  2654. ShowFolderForView(pressed_item_view, /*new_folder=*/false);
  2655. return;
  2656. }
  2657. base::RecordAction(base::UserMetricsAction("AppList_ClickOnApp"));
  2658. // Avoid using |item->id()| as the parameter. In some rare situations,
  2659. // activating the item may destruct it. Using the reference to an object
  2660. // which may be destroyed during the procedure as the function parameter
  2661. // may bring the crash like https://crbug.com/990282.
  2662. const std::string id = pressed_item_view->item()->id();
  2663. app_list_view_delegate()->ActivateItem(
  2664. id, event.flags(), AppListLaunchedFrom::kLaunchedFromGrid);
  2665. }
  2666. void AppsGridView::OnHostDragStartTimerFired() {
  2667. DCHECK(drag_and_drop_host_);
  2668. gfx::Point last_drag_point_in_screen = last_drag_point_;
  2669. views::View::ConvertPointToScreen(this, &last_drag_point_in_screen);
  2670. if (drag_and_drop_host_->StartDrag(drag_view_->item()->id(),
  2671. last_drag_point_in_screen,
  2672. drag_icon_proxy_->GetBoundsInScreen())) {
  2673. // From now on we forward the drag events.
  2674. forward_events_to_drag_and_drop_host_ = true;
  2675. }
  2676. }
  2677. void AppsGridView::OnFadeOutAnimationEnded(ReorderAnimationCallback callback,
  2678. bool aborted) {
  2679. grid_animation_status_ =
  2680. AppListGridAnimationStatus::kReorderIntermediaryState;
  2681. // Reset with the identical transformation. Because the apps grid view is
  2682. // translucent now, setting the layer transform does not bring noticeable
  2683. // differences.
  2684. layer()->SetTransform(gfx::Transform());
  2685. if (aborted) {
  2686. // If the fade out animation is aborted, show the apps grid because the fade
  2687. // in animation should not be called when the fade out animation is aborted.
  2688. layer()->SetOpacity(1.f);
  2689. } else {
  2690. // Hide all item views before the fade in animation in order to reduce the
  2691. // painting cost incurred by the bounds changes because of reorder. The
  2692. // fade in animation should be responsible for reshowing the item views that
  2693. // are within the visible view port after reorder.
  2694. for (size_t view_index = 0; view_index < view_model_.view_size();
  2695. ++view_index) {
  2696. view_model_.view_at(view_index)->SetVisible(false);
  2697. }
  2698. }
  2699. // Before starting the fade in animation, the reordered items should be at
  2700. // their final positions instantly.
  2701. base::AutoReset auto_reset(&enable_item_move_animation_, false);
  2702. // Prevent the opacity from changing before starting the fade in animation.
  2703. // It is necessary because `PagedAppsGridView::UpdateOpacity()` updates
  2704. // the apps grid opacity based on the app list state.
  2705. // TODO(https://crbug.com/1289380): remove this line when a better solution
  2706. // is came up with.
  2707. base::ScopedClosureRunner runner = LockAppsGridOpacity();
  2708. callback.Run(aborted);
  2709. if (fade_out_done_closure_for_test_)
  2710. std::move(fade_out_done_closure_for_test_).Run();
  2711. // When the fade out animation is abortted, the fade in animation should not
  2712. // run. Hence, the reorder animation ends. The aborted animation's smoothness
  2713. // is not reported.
  2714. if (aborted) {
  2715. grid_animation_status_ = AppListGridAnimationStatus::kEmpty;
  2716. MaybeRunNextReorderAnimationCallbackForTest(
  2717. /*aborted=*/true, AppListGridAnimationStatus::kReorderFadeOut);
  2718. // Reset `reorder_animation_tracker_` without calling Stop() because the
  2719. // aborted animation's smoothness is not reported.
  2720. reorder_animation_tracker_.reset();
  2721. }
  2722. }
  2723. void AppsGridView::OnFadeInAnimationEnded(ReorderAnimationCallback callback,
  2724. bool aborted) {
  2725. // If the animation is aborted, reset the apps grid's layer.
  2726. if (aborted)
  2727. layer()->SetOpacity(1.f);
  2728. // Ensure that all item views are visible after fade in animation completes.
  2729. for (size_t view_index = 0; view_index < view_model_.view_size();
  2730. ++view_index) {
  2731. view_model_.view_at(view_index)->SetVisible(true);
  2732. }
  2733. grid_animation_status_ = AppListGridAnimationStatus::kEmpty;
  2734. // Do not report the smoothness data for the aborted animation.
  2735. if (!aborted)
  2736. reorder_animation_tracker_->Stop();
  2737. reorder_animation_tracker_.reset();
  2738. // Clean app list items' layers.
  2739. DestroyLayerItemsIfNotNeeded();
  2740. if (!callback.is_null())
  2741. callback.Run(aborted);
  2742. MaybeRunNextReorderAnimationCallbackForTest(
  2743. aborted, AppListGridAnimationStatus::kReorderFadeIn);
  2744. }
  2745. void AppsGridView::MaybeRunNextReorderAnimationCallbackForTest(
  2746. bool aborted,
  2747. AppListGridAnimationStatus animation_source) {
  2748. if (reorder_animation_callback_queue_for_test_.empty())
  2749. return;
  2750. TestReorderDoneCallbackType front_callback =
  2751. std::move(reorder_animation_callback_queue_for_test_.front());
  2752. reorder_animation_callback_queue_for_test_.pop();
  2753. std::move(front_callback).Run(aborted, animation_source);
  2754. }
  2755. BEGIN_METADATA(AppsGridView, views::View)
  2756. END_METADATA
  2757. } // namespace ash