1 // Copyright 2014 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.
5 #include "ui/app_list/views/start_page_view.h"
9 #include "base/i18n/rtl.h"
10 #include "base/metrics/histogram_macros.h"
11 #include "base/strings/utf_string_conversions.h"
12 #include "ui/accessibility/ax_view_state.h"
13 #include "ui/app_list/app_list_constants.h"
14 #include "ui/app_list/app_list_item.h"
15 #include "ui/app_list/app_list_model.h"
16 #include "ui/app_list/app_list_view_delegate.h"
17 #include "ui/app_list/search_result.h"
18 #include "ui/app_list/views/all_apps_tile_item_view.h"
19 #include "ui/app_list/views/app_list_main_view.h"
20 #include "ui/app_list/views/contents_view.h"
21 #include "ui/app_list/views/custom_launcher_page_view.h"
22 #include "ui/app_list/views/search_box_view.h"
23 #include "ui/app_list/views/search_result_container_view.h"
24 #include "ui/app_list/views/search_result_tile_item_view.h"
25 #include "ui/app_list/views/tile_item_view.h"
26 #include "ui/gfx/canvas.h"
27 #include "ui/views/background.h"
28 #include "ui/views/controls/image_view.h"
29 #include "ui/views/controls/label.h"
30 #include "ui/views/controls/textfield/textfield.h"
31 #include "ui/views/layout/box_layout.h"
32 #include "ui/views/widget/widget.h"
39 const int kInstantContainerSpacing
= 24;
40 const int kSearchBoxAndTilesSpacing
= 35;
41 const int kStartPageSearchBoxWidth
= 480;
44 const int kWebViewWidth
= 700;
45 const int kWebViewHeight
= 244;
47 // Tile container constants.
48 const size_t kNumStartPageTiles
= 4;
49 const int kTileSpacing
= 7;
51 const int kLauncherPageBackgroundWidth
= 400;
53 // An invisible placeholder view which fills the space for the search box view
54 // in a box layout. The search box view itself is a child of the AppListView
55 // (because it is visible on many different pages).
56 class SearchBoxSpacerView
: public views::View
{
58 explicit SearchBoxSpacerView(const gfx::Size
& search_box_size
)
59 : size_(kStartPageSearchBoxWidth
, search_box_size
.height()) {}
61 ~SearchBoxSpacerView() override
{}
63 // Overridden from views::View:
64 gfx::Size
GetPreferredSize() const override
{ return size_
; }
69 DISALLOW_COPY_AND_ASSIGN(SearchBoxSpacerView
);
74 class CustomLauncherPageBackgroundView
: public views::View
{
76 explicit CustomLauncherPageBackgroundView(
77 const std::string
& custom_launcher_page_name
)
79 custom_launcher_page_name_(custom_launcher_page_name
) {
80 set_background(views::Background::CreateSolidBackground(kSelectedColor
));
82 ~CustomLauncherPageBackgroundView() override
{}
84 void SetSelected(bool selected
) {
88 NotifyAccessibilityEvent(ui::AX_EVENT_FOCUS
, true);
91 bool selected() { return selected_
; }
93 // Overridden from views::View:
94 void GetAccessibleState(ui::AXViewState
* state
) override
{
95 state
->role
= ui::AX_ROLE_BUTTON
;
96 state
->name
= base::UTF8ToUTF16(custom_launcher_page_name_
);
101 std::string custom_launcher_page_name_
;
103 DISALLOW_COPY_AND_ASSIGN(CustomLauncherPageBackgroundView
);
106 // A container that holds the start page recommendation tiles and the all apps
108 class StartPageView::StartPageTilesContainer
109 : public SearchResultContainerView
{
111 StartPageTilesContainer(ContentsView
* contents_view
,
112 AllAppsTileItemView
* all_apps_button
,
113 AppListViewDelegate
* view_delegate
);
114 ~StartPageTilesContainer() override
;
116 TileItemView
* GetTileItemView(int index
);
118 const std::vector
<SearchResultTileItemView
*>& tile_views() const {
119 return search_result_tile_views_
;
122 AllAppsTileItemView
* all_apps_button() { return all_apps_button_
; }
124 // Overridden from SearchResultContainerView:
125 int Update() override
;
126 void UpdateSelectedIndex(int old_selected
, int new_selected
) override
;
127 void OnContainerSelected(bool from_bottom
,
128 bool directional_movement
) override
;
129 void NotifyFirstResultYIndex(int y_index
) override
;
130 int GetYSize() override
;
133 ContentsView
* contents_view_
;
135 std::vector
<SearchResultTileItemView
*> search_result_tile_views_
;
136 AllAppsTileItemView
* all_apps_button_
;
138 DISALLOW_COPY_AND_ASSIGN(StartPageTilesContainer
);
141 StartPageView::StartPageTilesContainer::StartPageTilesContainer(
142 ContentsView
* contents_view
,
143 AllAppsTileItemView
* all_apps_button
,
144 AppListViewDelegate
* view_delegate
)
145 : contents_view_(contents_view
), all_apps_button_(all_apps_button
) {
146 views::BoxLayout
* tiles_layout_manager
=
147 new views::BoxLayout(views::BoxLayout::kHorizontal
, 0, 0, kTileSpacing
);
148 tiles_layout_manager
->set_main_axis_alignment(
149 views::BoxLayout::MAIN_AXIS_ALIGNMENT_CENTER
);
150 SetLayoutManager(tiles_layout_manager
);
152 views::Background::CreateSolidBackground(kLabelBackgroundColor
));
154 // Add SearchResultTileItemViews to the container.
155 for (size_t i
= 0; i
< kNumStartPageTiles
; ++i
) {
156 SearchResultTileItemView
* tile_item
=
157 new SearchResultTileItemView(this, view_delegate
);
158 AddChildView(tile_item
);
159 tile_item
->SetParentBackgroundColor(kLabelBackgroundColor
);
160 tile_item
->SetHoverStyle(TileItemView::HOVER_STYLE_ANIMATE_SHADOW
);
161 search_result_tile_views_
.push_back(tile_item
);
164 // Also add a special "all apps" button to the end of the container.
165 all_apps_button_
->SetHoverStyle(TileItemView::HOVER_STYLE_ANIMATE_SHADOW
);
166 all_apps_button_
->UpdateIcon();
167 all_apps_button_
->SetParentBackgroundColor(kLabelBackgroundColor
);
168 AddChildView(all_apps_button_
);
171 StartPageView::StartPageTilesContainer::~StartPageTilesContainer() {
174 TileItemView
* StartPageView::StartPageTilesContainer::GetTileItemView(
176 DCHECK_GT(num_results(), index
);
177 if (index
== num_results() - 1)
178 return all_apps_button_
;
180 return search_result_tile_views_
[index
];
183 int StartPageView::StartPageTilesContainer::Update() {
184 // Ignore updates and disable buttons when transitioning to a different
186 if (contents_view_
->GetActiveState() != AppListModel::STATE_START
) {
187 for (auto* view
: search_result_tile_views_
)
188 view
->SetEnabled(false);
190 return num_results();
193 std::vector
<SearchResult
*> display_results
=
194 AppListModel::FilterSearchResultsByDisplayType(
195 results(), SearchResult::DISPLAY_RECOMMENDATION
, kNumStartPageTiles
);
197 // Update the tile item results.
198 for (size_t i
= 0; i
< search_result_tile_views_
.size(); ++i
) {
199 SearchResult
* item
= NULL
;
200 if (i
< display_results
.size())
201 item
= display_results
[i
];
202 search_result_tile_views_
[i
]->SetSearchResult(item
);
203 search_result_tile_views_
[i
]->SetEnabled(true);
208 // Add 1 to the results size to account for the all apps button.
209 return display_results
.size() + 1;
212 void StartPageView::StartPageTilesContainer::UpdateSelectedIndex(
215 if (old_selected
>= 0)
216 GetTileItemView(old_selected
)->SetSelected(false);
218 if (new_selected
>= 0)
219 GetTileItemView(new_selected
)->SetSelected(true);
222 void StartPageView::StartPageTilesContainer::OnContainerSelected(
223 bool /*from_bottom*/,
224 bool /*directional_movement*/) {
228 void StartPageView::StartPageTilesContainer::NotifyFirstResultYIndex(
233 int StartPageView::StartPageTilesContainer::GetYSize() {
238 ////////////////////////////////////////////////////////////////////////////////
239 // StartPageView implementation:
240 StartPageView::StartPageView(AppListMainView
* app_list_main_view
,
241 AppListViewDelegate
* view_delegate
)
242 : app_list_main_view_(app_list_main_view
),
243 view_delegate_(view_delegate
),
244 search_box_spacer_view_(new SearchBoxSpacerView(
245 app_list_main_view
->search_box_view()->GetPreferredSize())),
246 instant_container_(new views::View
),
247 custom_launcher_page_background_(new CustomLauncherPageBackgroundView(
248 view_delegate_
->GetModel()->custom_launcher_page_name())),
249 tiles_container_(new StartPageTilesContainer(
250 app_list_main_view
->contents_view(),
251 new AllAppsTileItemView(
252 app_list_main_view_
->contents_view(),
253 view_delegate_
->GetModel()->top_level_item_list()),
255 // The view containing the start page WebContents and SearchBoxSpacerView.
256 InitInstantContainer();
257 AddChildView(instant_container_
);
259 // The view containing the start page tiles.
260 AddChildView(tiles_container_
);
262 AddChildView(custom_launcher_page_background_
);
264 tiles_container_
->SetResults(view_delegate_
->GetModel()->results());
268 StartPageView::~StartPageView() {
271 void StartPageView::InitInstantContainer() {
272 views::BoxLayout
* instant_layout_manager
= new views::BoxLayout(
273 views::BoxLayout::kVertical
, 0, 0, kInstantContainerSpacing
);
274 instant_layout_manager
->set_inside_border_insets(
275 gfx::Insets(0, 0, kSearchBoxAndTilesSpacing
, 0));
276 instant_layout_manager
->set_main_axis_alignment(
277 views::BoxLayout::MAIN_AXIS_ALIGNMENT_END
);
278 instant_layout_manager
->set_cross_axis_alignment(
279 views::BoxLayout::CROSS_AXIS_ALIGNMENT_CENTER
);
280 instant_container_
->SetLayoutManager(instant_layout_manager
);
282 views::View
* web_view
= view_delegate_
->CreateStartPageWebView(
283 gfx::Size(kWebViewWidth
, kWebViewHeight
));
285 web_view
->SetFocusable(false);
286 instant_container_
->AddChildView(web_view
);
289 instant_container_
->AddChildView(search_box_spacer_view_
);
292 void StartPageView::MaybeOpenCustomLauncherPage() {
293 // Switch to the custom page.
294 ContentsView
* contents_view
= app_list_main_view_
->contents_view();
295 if (!app_list_main_view_
->ShouldShowCustomLauncherPage())
298 UMA_HISTOGRAM_ENUMERATION(kPageOpenedHistogram
,
299 AppListModel::STATE_CUSTOM_LAUNCHER_PAGE
,
300 AppListModel::STATE_LAST
);
302 contents_view
->SetActiveState(AppListModel::STATE_CUSTOM_LAUNCHER_PAGE
);
305 void StartPageView::Reset() {
306 tiles_container_
->Update();
309 void StartPageView::UpdateForTesting() {
310 tiles_container_
->Update();
313 const std::vector
<SearchResultTileItemView
*>& StartPageView::tile_views()
315 return tiles_container_
->tile_views();
318 TileItemView
* StartPageView::all_apps_button() const {
319 return tiles_container_
->all_apps_button();
322 void StartPageView::OnShown() {
323 // When the start page is shown, show or hide the custom launcher page
324 // based on whether it is enabled.
325 CustomLauncherPageView
* custom_page_view
=
326 app_list_main_view_
->contents_view()->custom_page_view();
327 if (custom_page_view
) {
328 custom_page_view
->SetVisible(
329 app_list_main_view_
->ShouldShowCustomLauncherPage());
331 tiles_container_
->Update();
332 tiles_container_
->ClearSelectedIndex();
333 custom_launcher_page_background_
->SetSelected(false);
336 gfx::Rect
StartPageView::GetPageBoundsForState(
337 AppListModel::State state
) const {
338 gfx::Rect
onscreen_bounds(GetFullContentsBounds());
339 if (state
== AppListModel::STATE_START
)
340 return onscreen_bounds
;
342 return GetAboveContentsOffscreenBounds(onscreen_bounds
.size());
345 gfx::Rect
StartPageView::GetSearchBoxBounds() const {
346 return search_box_spacer_view_
->bounds() +
347 GetPageBoundsForState(AppListModel::STATE_START
).OffsetFromOrigin();
350 void StartPageView::Layout() {
351 gfx::Rect
bounds(GetContentsBounds());
352 bounds
.set_height(instant_container_
->GetHeightForWidth(bounds
.width()));
353 instant_container_
->SetBoundsRect(bounds
);
355 // Tiles begin where the instant container ends.
356 bounds
.set_y(bounds
.bottom());
357 bounds
.set_height(tiles_container_
->GetHeightForWidth(bounds
.width()));
358 tiles_container_
->SetBoundsRect(bounds
);
360 CustomLauncherPageView
* custom_launcher_page_view
=
361 app_list_main_view_
->contents_view()->custom_page_view();
362 if (!custom_launcher_page_view
)
365 bounds
= app_list_main_view_
->contents_view()
367 ->GetCollapsedLauncherPageBounds();
368 bounds
.Intersect(GetContentsBounds());
369 bounds
.ClampToCenteredSize(
370 gfx::Size(kLauncherPageBackgroundWidth
, bounds
.height()));
371 custom_launcher_page_background_
->SetBoundsRect(bounds
);
374 bool StartPageView::OnKeyPressed(const ui::KeyEvent
& event
) {
375 const int forward_dir
= base::i18n::IsRTL() ? -1 : 1;
376 int selected_index
= tiles_container_
->selected_index();
378 if (custom_launcher_page_background_
->selected()) {
379 selected_index
= tiles_container_
->num_results();
380 switch (event
.key_code()) {
381 case ui::VKEY_RETURN
:
382 MaybeOpenCustomLauncherPage();
387 } else if (selected_index
>= 0 &&
388 tiles_container_
->GetTileItemView(selected_index
)
389 ->OnKeyPressed(event
)) {
394 switch (event
.key_code()) {
399 // Don't go to the custom launcher page from the All apps tile.
400 if (selected_index
!= tiles_container_
->num_results() - 1)
404 // Up selects the first tile if the custom launcher is selected.
405 if (custom_launcher_page_background_
->selected()) {
411 // Down selects the first tile if nothing is selected.
413 // If something is selected, select the custom launcher page.
414 if (tiles_container_
->IsValidSelectionIndex(selected_index
))
415 selected_index
= tiles_container_
->num_results() - 1;
418 dir
= event
.IsShiftDown() ? -1 : 1;
427 if (selected_index
== -1) {
428 custom_launcher_page_background_
->SetSelected(false);
429 tiles_container_
->SetSelectedIndex(
430 dir
== -1 ? tiles_container_
->num_results() - 1 : 0);
434 int selection_index
= selected_index
+ dir
;
435 if (tiles_container_
->IsValidSelectionIndex(selection_index
)) {
436 custom_launcher_page_background_
->SetSelected(false);
437 tiles_container_
->SetSelectedIndex(selection_index
);
441 if (selection_index
== tiles_container_
->num_results() &&
442 app_list_main_view_
->ShouldShowCustomLauncherPage()) {
443 custom_launcher_page_background_
->SetSelected(true);
444 tiles_container_
->ClearSelectedIndex();
448 if (event
.key_code() == ui::VKEY_TAB
&& selection_index
== -1)
449 tiles_container_
->ClearSelectedIndex(); // ContentsView will handle focus.
454 bool StartPageView::OnMousePressed(const ui::MouseEvent
& event
) {
455 ContentsView
* contents_view
= app_list_main_view_
->contents_view();
456 if (contents_view
->custom_page_view() &&
457 !contents_view
->custom_page_view()
458 ->GetCollapsedLauncherPageBounds()
459 .Contains(event
.location())) {
463 MaybeOpenCustomLauncherPage();
467 bool StartPageView::OnMouseWheel(const ui::MouseWheelEvent
& event
) {
468 // Negative y_offset is a downward scroll.
469 if (event
.y_offset() < 0) {
470 MaybeOpenCustomLauncherPage();
477 void StartPageView::OnGestureEvent(ui::GestureEvent
* event
) {
478 if (event
->type() == ui::ET_GESTURE_SCROLL_BEGIN
&&
479 event
->details().scroll_y_hint() < 0) {
480 MaybeOpenCustomLauncherPage();
483 ContentsView
* contents_view
= app_list_main_view_
->contents_view();
484 if (event
->type() == ui::ET_GESTURE_TAP
&&
485 contents_view
->custom_page_view() &&
486 contents_view
->custom_page_view()
487 ->GetCollapsedLauncherPageBounds()
488 .Contains(event
->location())) {
489 MaybeOpenCustomLauncherPage();
493 void StartPageView::OnScrollEvent(ui::ScrollEvent
* event
) {
494 // Negative y_offset is a downward scroll (or upward, if Australian Scrolling
496 if (event
->type() == ui::ET_SCROLL
&& event
->y_offset() < 0)
497 MaybeOpenCustomLauncherPage();
500 TileItemView
* StartPageView::GetTileItemView(size_t index
) {
501 return tiles_container_
->GetTileItemView(index
);
504 } // namespace app_list