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 ~StartPageTilesContainer() override
;
115 TileItemView
* GetTileItemView(int index
);
117 const std::vector
<SearchResultTileItemView
*>& tile_views() const {
118 return search_result_tile_views_
;
121 AllAppsTileItemView
* all_apps_button() { return all_apps_button_
; }
123 // Overridden from SearchResultContainerView:
124 int Update() override
;
125 void UpdateSelectedIndex(int old_selected
, int new_selected
) override
;
126 void OnContainerSelected(bool from_bottom
,
127 bool directional_movement
) override
;
130 ContentsView
* contents_view_
;
132 std::vector
<SearchResultTileItemView
*> search_result_tile_views_
;
133 AllAppsTileItemView
* all_apps_button_
;
135 DISALLOW_COPY_AND_ASSIGN(StartPageTilesContainer
);
138 StartPageView::StartPageTilesContainer::StartPageTilesContainer(
139 ContentsView
* contents_view
,
140 AllAppsTileItemView
* all_apps_button
)
141 : contents_view_(contents_view
), all_apps_button_(all_apps_button
) {
142 views::BoxLayout
* tiles_layout_manager
=
143 new views::BoxLayout(views::BoxLayout::kHorizontal
, 0, 0, kTileSpacing
);
144 tiles_layout_manager
->set_main_axis_alignment(
145 views::BoxLayout::MAIN_AXIS_ALIGNMENT_CENTER
);
146 SetLayoutManager(tiles_layout_manager
);
148 views::Background::CreateSolidBackground(kLabelBackgroundColor
));
150 // Add SearchResultTileItemViews to the container.
151 for (size_t i
= 0; i
< kNumStartPageTiles
; ++i
) {
152 SearchResultTileItemView
* tile_item
= new SearchResultTileItemView(this);
153 AddChildView(tile_item
);
154 tile_item
->SetParentBackgroundColor(kLabelBackgroundColor
);
155 search_result_tile_views_
.push_back(tile_item
);
158 // Also add a special "all apps" button to the end of the container.
159 all_apps_button_
->UpdateIcon();
160 all_apps_button_
->SetParentBackgroundColor(kLabelBackgroundColor
);
161 AddChildView(all_apps_button_
);
164 StartPageView::StartPageTilesContainer::~StartPageTilesContainer() {
167 TileItemView
* StartPageView::StartPageTilesContainer::GetTileItemView(
169 DCHECK_GT(num_results(), index
);
170 if (index
== num_results() - 1)
171 return all_apps_button_
;
173 return search_result_tile_views_
[index
];
176 int StartPageView::StartPageTilesContainer::Update() {
177 // Ignore updates and disable buttons when transitioning to a different
179 if (contents_view_
->GetActiveState() != AppListModel::STATE_START
) {
180 for (auto* view
: search_result_tile_views_
)
181 view
->SetEnabled(false);
183 return num_results();
186 std::vector
<SearchResult
*> display_results
=
187 AppListModel::FilterSearchResultsByDisplayType(
188 results(), SearchResult::DISPLAY_RECOMMENDATION
, kNumStartPageTiles
);
190 // Update the tile item results.
191 for (size_t i
= 0; i
< search_result_tile_views_
.size(); ++i
) {
192 SearchResult
* item
= NULL
;
193 if (i
< display_results
.size())
194 item
= display_results
[i
];
195 search_result_tile_views_
[i
]->SetSearchResult(item
);
196 search_result_tile_views_
[i
]->SetEnabled(true);
201 // Add 1 to the results size to account for the all apps button.
202 return display_results
.size() + 1;
205 void StartPageView::StartPageTilesContainer::UpdateSelectedIndex(
208 if (old_selected
>= 0)
209 GetTileItemView(old_selected
)->SetSelected(false);
211 if (new_selected
>= 0)
212 GetTileItemView(new_selected
)->SetSelected(true);
215 void StartPageView::StartPageTilesContainer::OnContainerSelected(
216 bool /*from_bottom*/,
217 bool /*directional_movement*/) {
220 ////////////////////////////////////////////////////////////////////////////////
221 // StartPageView implementation:
222 StartPageView::StartPageView(AppListMainView
* app_list_main_view
,
223 AppListViewDelegate
* view_delegate
)
224 : app_list_main_view_(app_list_main_view
),
225 view_delegate_(view_delegate
),
226 search_box_spacer_view_(new SearchBoxSpacerView(
227 app_list_main_view
->search_box_view()->GetPreferredSize())),
228 instant_container_(new views::View
),
229 custom_launcher_page_background_(new CustomLauncherPageBackgroundView(
230 view_delegate_
->GetModel()->custom_launcher_page_name())),
231 tiles_container_(new StartPageTilesContainer(
232 app_list_main_view
->contents_view(),
233 new AllAppsTileItemView(
234 app_list_main_view_
->contents_view(),
235 view_delegate_
->GetModel()->top_level_item_list()))) {
236 // The view containing the start page WebContents and SearchBoxSpacerView.
237 InitInstantContainer();
238 AddChildView(instant_container_
);
240 // The view containing the start page tiles.
241 AddChildView(tiles_container_
);
243 AddChildView(custom_launcher_page_background_
);
245 tiles_container_
->SetResults(view_delegate_
->GetModel()->results());
249 StartPageView::~StartPageView() {
252 void StartPageView::InitInstantContainer() {
253 views::BoxLayout
* instant_layout_manager
= new views::BoxLayout(
254 views::BoxLayout::kVertical
, 0, 0, kInstantContainerSpacing
);
255 instant_layout_manager
->set_inside_border_insets(
256 gfx::Insets(0, 0, kSearchBoxAndTilesSpacing
, 0));
257 instant_layout_manager
->set_main_axis_alignment(
258 views::BoxLayout::MAIN_AXIS_ALIGNMENT_END
);
259 instant_layout_manager
->set_cross_axis_alignment(
260 views::BoxLayout::CROSS_AXIS_ALIGNMENT_CENTER
);
261 instant_container_
->SetLayoutManager(instant_layout_manager
);
263 views::View
* web_view
= view_delegate_
->CreateStartPageWebView(
264 gfx::Size(kWebViewWidth
, kWebViewHeight
));
266 web_view
->SetFocusable(false);
267 instant_container_
->AddChildView(web_view
);
270 instant_container_
->AddChildView(search_box_spacer_view_
);
273 void StartPageView::MaybeOpenCustomLauncherPage() {
274 // Switch to the custom page.
275 ContentsView
* contents_view
= app_list_main_view_
->contents_view();
276 if (!app_list_main_view_
->ShouldShowCustomLauncherPage())
279 UMA_HISTOGRAM_ENUMERATION(kPageOpenedHistogram
,
280 AppListModel::STATE_CUSTOM_LAUNCHER_PAGE
,
281 AppListModel::STATE_LAST
);
283 contents_view
->SetActiveState(AppListModel::STATE_CUSTOM_LAUNCHER_PAGE
);
286 void StartPageView::Reset() {
287 tiles_container_
->Update();
290 void StartPageView::UpdateForTesting() {
291 tiles_container_
->Update();
294 const std::vector
<SearchResultTileItemView
*>& StartPageView::tile_views()
296 return tiles_container_
->tile_views();
299 TileItemView
* StartPageView::all_apps_button() const {
300 return tiles_container_
->all_apps_button();
303 void StartPageView::OnShown() {
304 // When the start page is shown, show or hide the custom launcher page
305 // based on whether it is enabled.
306 CustomLauncherPageView
* custom_page_view
=
307 app_list_main_view_
->contents_view()->custom_page_view();
308 if (custom_page_view
) {
309 custom_page_view
->SetVisible(
310 app_list_main_view_
->ShouldShowCustomLauncherPage());
312 tiles_container_
->Update();
313 tiles_container_
->ClearSelectedIndex();
314 custom_launcher_page_background_
->SetSelected(false);
317 gfx::Rect
StartPageView::GetPageBoundsForState(
318 AppListModel::State state
) const {
319 gfx::Rect
onscreen_bounds(GetFullContentsBounds());
320 if (state
== AppListModel::STATE_START
)
321 return onscreen_bounds
;
323 return GetAboveContentsOffscreenBounds(onscreen_bounds
.size());
326 gfx::Rect
StartPageView::GetSearchBoxBounds() const {
327 return search_box_spacer_view_
->bounds() +
328 GetPageBoundsForState(AppListModel::STATE_START
).OffsetFromOrigin();
331 void StartPageView::Layout() {
332 gfx::Rect
bounds(GetContentsBounds());
333 bounds
.set_height(instant_container_
->GetHeightForWidth(bounds
.width()));
334 instant_container_
->SetBoundsRect(bounds
);
336 // Tiles begin where the instant container ends.
337 bounds
.set_y(bounds
.bottom());
338 bounds
.set_height(tiles_container_
->GetHeightForWidth(bounds
.width()));
339 tiles_container_
->SetBoundsRect(bounds
);
341 CustomLauncherPageView
* custom_launcher_page_view
=
342 app_list_main_view_
->contents_view()->custom_page_view();
343 if (!custom_launcher_page_view
)
346 bounds
= app_list_main_view_
->contents_view()
348 ->GetCollapsedLauncherPageBounds();
349 bounds
.Intersect(GetContentsBounds());
350 bounds
.ClampToCenteredSize(
351 gfx::Size(kLauncherPageBackgroundWidth
, bounds
.height()));
352 custom_launcher_page_background_
->SetBoundsRect(bounds
);
355 bool StartPageView::OnKeyPressed(const ui::KeyEvent
& event
) {
356 const int forward_dir
= base::i18n::IsRTL() ? -1 : 1;
357 int selected_index
= tiles_container_
->selected_index();
359 if (custom_launcher_page_background_
->selected()) {
360 selected_index
= tiles_container_
->num_results();
361 switch (event
.key_code()) {
362 case ui::VKEY_RETURN
:
363 MaybeOpenCustomLauncherPage();
368 } else if (selected_index
>= 0 &&
369 tiles_container_
->GetTileItemView(selected_index
)
370 ->OnKeyPressed(event
)) {
375 switch (event
.key_code()) {
380 // Don't go to the custom launcher page from the All apps tile.
381 if (selected_index
!= tiles_container_
->num_results() - 1)
385 // Up selects the first tile if the custom launcher is selected.
386 if (custom_launcher_page_background_
->selected()) {
392 // Down selects the first tile if nothing is selected.
394 // If something is selected, select the custom launcher page.
395 if (tiles_container_
->IsValidSelectionIndex(selected_index
))
396 selected_index
= tiles_container_
->num_results() - 1;
399 dir
= event
.IsShiftDown() ? -1 : 1;
408 if (selected_index
== -1) {
409 custom_launcher_page_background_
->SetSelected(false);
410 tiles_container_
->SetSelectedIndex(
411 dir
== -1 ? tiles_container_
->num_results() - 1 : 0);
415 int selection_index
= selected_index
+ dir
;
416 if (tiles_container_
->IsValidSelectionIndex(selection_index
)) {
417 custom_launcher_page_background_
->SetSelected(false);
418 tiles_container_
->SetSelectedIndex(selection_index
);
422 if (selection_index
== tiles_container_
->num_results() &&
423 app_list_main_view_
->ShouldShowCustomLauncherPage()) {
424 custom_launcher_page_background_
->SetSelected(true);
425 tiles_container_
->ClearSelectedIndex();
429 if (event
.key_code() == ui::VKEY_TAB
&& selection_index
== -1)
430 tiles_container_
->ClearSelectedIndex(); // ContentsView will handle focus.
435 bool StartPageView::OnMousePressed(const ui::MouseEvent
& event
) {
436 ContentsView
* contents_view
= app_list_main_view_
->contents_view();
437 if (contents_view
->custom_page_view() &&
438 !contents_view
->custom_page_view()
439 ->GetCollapsedLauncherPageBounds()
440 .Contains(event
.location())) {
444 MaybeOpenCustomLauncherPage();
448 bool StartPageView::OnMouseWheel(const ui::MouseWheelEvent
& event
) {
449 // Negative y_offset is a downward scroll.
450 if (event
.y_offset() < 0) {
451 MaybeOpenCustomLauncherPage();
458 void StartPageView::OnGestureEvent(ui::GestureEvent
* event
) {
459 if (event
->type() == ui::ET_GESTURE_SCROLL_BEGIN
&&
460 event
->details().scroll_y_hint() < 0) {
461 MaybeOpenCustomLauncherPage();
464 ContentsView
* contents_view
= app_list_main_view_
->contents_view();
465 if (event
->type() == ui::ET_GESTURE_TAP
&&
466 contents_view
->custom_page_view() &&
467 contents_view
->custom_page_view()
468 ->GetCollapsedLauncherPageBounds()
469 .Contains(event
->location())) {
470 MaybeOpenCustomLauncherPage();
474 void StartPageView::OnScrollEvent(ui::ScrollEvent
* event
) {
475 // Negative y_offset is a downward scroll (or upward, if Australian Scrolling
477 if (event
->type() == ui::ET_SCROLL
&& event
->y_offset() < 0)
478 MaybeOpenCustomLauncherPage();
481 TileItemView
* StartPageView::GetTileItemView(size_t index
) {
482 return tiles_container_
->GetTileItemView(index
);
485 } // namespace app_list