Commit fa4f6f47 authored by wutao's avatar wutao Committed by Commit Bot

cros: Reduce the search latency for KSV.

This cl reduces the search latency in Keyboard Shortcut Viewer (KSV).
1. Set a time delay to process key events in search mode. In this way,
users can keep typing in the search box.
2. Reuse the KeyboardShortcutItemView between TabbedView and the search
results. This reduces the layout time for |shortcut_label_view_| in
KeyboardShortcutItemView.
3. To highight matched search queries, we cannot avoid layouting
|description_label_view_| in KeyboardShortcutItemView.

Bug: 818882
Test: Tested on Eve and Minnie
Change-Id: I4b36e18fe3f72fba4884c0cc02fea24ff4416630
Reviewed-on: https://chromium-review.googlesource.com/951709Reviewed-by: default avatarMichael Wasserman <msw@chromium.org>
Reviewed-by: default avatarAhmed Fakhry <afakhry@chromium.org>
Commit-Queue: Tao Wu <wutao@chromium.org>
Cr-Commit-Position: refs/heads/master@{#542020}
parent 621948db
...@@ -15,6 +15,7 @@ ...@@ -15,6 +15,7 @@
#include "ash/components/strings/grit/ash_components_strings.h" #include "ash/components/strings/grit/ash_components_strings.h"
#include "ash/public/cpp/shelf_item.h" #include "ash/public/cpp/shelf_item.h"
#include "ash/public/cpp/window_properties.h" #include "ash/public/cpp/window_properties.h"
#include "base/bind.h"
#include "base/i18n/string_search.h" #include "base/i18n/string_search.h"
#include "base/metrics/user_metrics.h" #include "base/metrics/user_metrics.h"
#include "base/metrics/user_metrics_action.h" #include "base/metrics/user_metrics_action.h"
...@@ -174,24 +175,44 @@ void KeyboardShortcutView::InitViews() { ...@@ -174,24 +175,44 @@ void KeyboardShortcutView::InitViews() {
for (const auto& item : GetKeyboardShortcutItemList()) { for (const auto& item : GetKeyboardShortcutItemList()) {
for (auto category : item.categories) { for (auto category : item.categories) {
shortcut_views_.emplace_back( shortcut_views_.emplace_back(
new KeyboardShortcutItemView(item, category)); std::make_unique<KeyboardShortcutItemView>(item, category));
shortcut_views_.back()->set_owned_by_client();
} }
} }
std::sort(shortcut_views_.begin(), shortcut_views_.end(), std::sort(shortcut_views_.begin(), shortcut_views_.end(),
[](KeyboardShortcutItemView* lhs, KeyboardShortcutItemView* rhs) { [](const auto& lhs, const auto& rhs) {
if (lhs->category() != rhs->category()) if (lhs->category() != rhs->category())
return lhs->category() < rhs->category(); return lhs->category() < rhs->category();
return lhs->description_label_view()->text() < return lhs->description_label_view()->text() <
rhs->description_label_view()->text(); rhs->description_label_view()->text();
}); });
// Init views of TabbedPane and KeyboardShortcutItemListView. // Init views of |categories_tabbed_pane_| and KeyboardShortcutItemListViews.
tabbed_pane_ = categories_tabbed_pane_ =
new views::TabbedPane(views::TabbedPane::Orientation::kVertical, new views::TabbedPane(views::TabbedPane::Orientation::kVertical,
views::TabbedPane::TabStripStyle::kHighlight); views::TabbedPane::TabStripStyle::kHighlight);
AddChildView(categories_tabbed_pane_);
InitCategoriesTabbedPane();
}
void KeyboardShortcutView::InitCategoriesTabbedPane() {
// If the tab count is 0, |GetSelectedTabIndex()| will return -1, which we do
// not want to cache.
active_tab_index_ =
std::max(0, categories_tabbed_pane_->GetSelectedTabIndex());
// Although we remove all child views, when the KeyboardShortcutItemView is
// added back to the |categories_tabbed_pane_|, because there is no width
// changes, it will not layout the KeyboardShortcutItemView again due to the
// |MaybeCalculateAndDoLayout()| optimization in KeyboardShortcutItemView.
// Cannot remove |tab_strip_| and |contents_|, child views of the
// |categories_tabbed_pane_|, because they are added in the ctor of
// TabbedPane.
categories_tabbed_pane_->child_at(0)->RemoveAllChildViews(true);
categories_tabbed_pane_->child_at(1)->RemoveAllChildViews(true);
ShortcutCategory current_category = ShortcutCategory::kUnknown; ShortcutCategory current_category = ShortcutCategory::kUnknown;
KeyboardShortcutItemListView* item_list_view; KeyboardShortcutItemListView* item_list_view;
for (auto* item_view : shortcut_views_) { for (const auto& item_view : shortcut_views_) {
const ShortcutCategory category = item_view->category(); const ShortcutCategory category = item_view->category();
DCHECK_NE(ShortcutCategory::kUnknown, category); DCHECK_NE(ShortcutCategory::kUnknown, category);
if (current_category != category) { if (current_category != category) {
...@@ -199,20 +220,26 @@ void KeyboardShortcutView::InitViews() { ...@@ -199,20 +220,26 @@ void KeyboardShortcutView::InitViews() {
item_list_view = new KeyboardShortcutItemListView(); item_list_view = new KeyboardShortcutItemListView();
views::ScrollView* const scroller = CreateScrollView(); views::ScrollView* const scroller = CreateScrollView();
scroller->SetContents(item_list_view); scroller->SetContents(item_list_view);
tabbed_pane_->AddTab(GetStringForCategory(current_category), scroller); categories_tabbed_pane_->AddTab(GetStringForCategory(current_category),
scroller);
} }
if (item_list_view->has_children()) if (item_list_view->has_children())
item_list_view->AddHorizontalSeparator(); item_list_view->AddHorizontalSeparator();
item_list_view->AddChildView(item_view); views::StyledLabel* description_label_view =
item_view->description_label_view();
// Clear any styles used to highlight matched search query in search mode.
description_label_view->ClearStyleRanges();
item_list_view->AddChildView(item_view.get());
// Remove the search query highlight.
description_label_view->Layout();
} }
AddChildView(tabbed_pane_);
} }
void KeyboardShortcutView::RequestFocusForActiveTab() { void KeyboardShortcutView::RequestFocusForActiveTab() {
// Get the |tab_strip_| of the |tabbed_pane_| in order to set focus on // Get the |tab_strip_| of the |categories_tabbed_pane_| in order to set focus
// the selected tab. // on the selected tab.
tabbed_pane_->child_at(0) categories_tabbed_pane_->child_at(0)
->child_at(tabbed_pane_->GetSelectedTabIndex()) ->child_at(active_tab_index_)
->RequestFocus(); ->RequestFocus();
} }
...@@ -252,9 +279,9 @@ void KeyboardShortcutView::Layout() { ...@@ -252,9 +279,9 @@ void KeyboardShortcutView::Layout() {
search_box_bounds.set_y(top + kSearchBoxTopPadding); search_box_bounds.set_y(top + kSearchBoxTopPadding);
search_box_view_->SetBoundsRect(search_box_bounds); search_box_view_->SetBoundsRect(search_box_bounds);
views::View* content_view = views::View* content_view = categories_tabbed_pane_->visible()
tabbed_pane_->visible() ? tabbed_pane_ : search_results_container_; ? categories_tabbed_pane_
: search_results_container_;
const int search_box_used_height = search_box_bounds.height() + const int search_box_used_height = search_box_bounds.height() +
kSearchBoxTopPadding + kSearchBoxTopPadding +
kSearchBoxBottomPadding; kSearchBoxBottomPadding;
...@@ -275,19 +302,71 @@ void KeyboardShortcutView::QueryChanged(search_box::SearchBoxViewBase* sender) { ...@@ -275,19 +302,71 @@ void KeyboardShortcutView::QueryChanged(search_box::SearchBoxViewBase* sender) {
UpdateViewsLayout(/*is_search_box_active=*/true); UpdateViewsLayout(/*is_search_box_active=*/true);
} }
debounce_timer_.Stop();
// If search box is empty, do not show |search_results_container_|. // If search box is empty, do not show |search_results_container_|.
if (query_empty) if (query_empty)
return; return;
// TODO(wutao): This timeout value is chosen based on subjective search
// latency tests on Minnie. Objective method or UMA is desired.
constexpr base::TimeDelta kTimeOut(base::TimeDelta::FromMilliseconds(250));
debounce_timer_.Start(
FROM_HERE, kTimeOut,
base::Bind(&KeyboardShortcutView::ShowSearchResults,
base::Unretained(this), sender->search_box()->text()));
}
void KeyboardShortcutView::ActiveChanged(
search_box::SearchBoxViewBase* sender) {
const bool is_search_box_active = sender->is_search_box_active();
is_search_box_empty_ = sender->IsSearchBoxTrimmedQueryEmpty();
sender->ShowBackOrGoogleIcon(is_search_box_active);
if (is_search_box_active) {
base::RecordAction(
base::UserMetricsAction("KeyboardShortcutViewer.Search"));
}
UpdateViewsLayout(is_search_box_active);
}
void KeyboardShortcutView::UpdateViewsLayout(bool is_search_box_active) {
// 1. Search box is not active: show |categories_tabbed_pane_| and focus on
// active tab.
// 2. Search box is active and empty: show |categories_tabbed_pane_| but focus
// on search box.
// 3. Search box is not empty, show |search_results_container_|. Focus is on
// search box.
const bool should_show_search_results =
is_search_box_active && !is_search_box_empty_;
if (!should_show_search_results) {
// Remove all child views, including horizontal separator lines, to prepare
// for showing search results next time.
search_results_container_->RemoveAllChildViews(true);
if (!categories_tabbed_pane_->visible()) {
// Repopulate |categories_tabbed_pane_| child views, which were removed
// when they were added to |search_results_container_|.
InitCategoriesTabbedPane();
// Select the category that was active before entering search mode.
categories_tabbed_pane_->SelectTabAt(active_tab_index_);
}
if (!is_search_box_active)
RequestFocusForActiveTab();
}
categories_tabbed_pane_->SetVisible(!should_show_search_results);
search_results_container_->SetVisible(should_show_search_results);
Layout();
SchedulePaint();
}
void KeyboardShortcutView::ShowSearchResults(
const base::string16& search_query) {
search_results_container_->RemoveAllChildViews(true); search_results_container_->RemoveAllChildViews(true);
auto* search_container_content_view = search_no_result_view_.get(); auto* search_container_content_view = search_no_result_view_.get();
auto found_items_list_view = std::make_unique<KeyboardShortcutItemListView>(); auto found_items_list_view = std::make_unique<KeyboardShortcutItemListView>();
const base::string16& new_contents = sender->search_box()->text();
base::i18n::FixedPatternStringSearchIgnoringCaseAndAccents finder( base::i18n::FixedPatternStringSearchIgnoringCaseAndAccents finder(
new_contents); search_query);
ShortcutCategory current_category = ShortcutCategory::kUnknown; ShortcutCategory current_category = ShortcutCategory::kUnknown;
bool has_category_item = false; bool has_category_item = false;
for (auto* item_view : shortcut_views_) { for (const auto& item_view : shortcut_views_) {
base::string16 description_text = base::string16 description_text =
item_view->description_label_view()->text(); item_view->description_label_view()->text();
base::string16 shortcut_text = item_view->shortcut_label_view()->text(); base::string16 shortcut_text = item_view->shortcut_label_view()->text();
...@@ -310,20 +389,22 @@ void KeyboardShortcutView::QueryChanged(search_box::SearchBoxViewBase* sender) { ...@@ -310,20 +389,22 @@ void KeyboardShortcutView::QueryChanged(search_box::SearchBoxViewBase* sender) {
found_items_list_view->AddHorizontalSeparator(); found_items_list_view->AddHorizontalSeparator();
else else
has_category_item = true; has_category_item = true;
auto* matched_item_view =
new KeyboardShortcutItemView(*item_view->shortcut_item(), category);
// Highlight matched query in |description_label_view_|. // Highlight matched query in |description_label_view_|.
if (match_length > 0) { if (match_length > 0) {
views::StyledLabel::RangeStyleInfo style; views::StyledLabel::RangeStyleInfo style;
views::StyledLabel* description_label_view = views::StyledLabel* description_label_view =
matched_item_view->description_label_view(); item_view->description_label_view();
// Clear previous styles.
description_label_view->ClearStyleRanges();
style.custom_font = description_label_view->GetDefaultFontList().Derive( style.custom_font = description_label_view->GetDefaultFontList().Derive(
0, gfx::Font::FontStyle::NORMAL, gfx::Font::Weight::BOLD); 0, gfx::Font::FontStyle::NORMAL, gfx::Font::Weight::BOLD);
description_label_view->AddStyleRange( description_label_view->AddStyleRange(
gfx::Range(match_index, match_index + match_length), style); gfx::Range(match_index, match_index + match_length), style);
// Apply new styles to highlight matched search query.
description_label_view->Layout();
} }
found_items_list_view->AddChildView(matched_item_view); found_items_list_view->AddChildView(item_view.get());
} }
} }
...@@ -338,37 +419,8 @@ void KeyboardShortcutView::QueryChanged(search_box::SearchBoxViewBase* sender) { ...@@ -338,37 +419,8 @@ void KeyboardShortcutView::QueryChanged(search_box::SearchBoxViewBase* sender) {
scroller->SetContents(found_items_list_view.release()); scroller->SetContents(found_items_list_view.release());
search_container_content_view = scroller; search_container_content_view = scroller;
} }
search_results_container_->AddChildView(search_container_content_view);
Layout();
SchedulePaint();
}
void KeyboardShortcutView::ActiveChanged( search_results_container_->AddChildView(search_container_content_view);
search_box::SearchBoxViewBase* sender) {
const bool is_search_box_active = sender->is_search_box_active();
is_search_box_empty_ = sender->IsSearchBoxTrimmedQueryEmpty();
sender->ShowBackOrGoogleIcon(is_search_box_active);
if (is_search_box_active) {
base::RecordAction(
base::UserMetricsAction("KeyboardShortcutViewer.Search"));
}
UpdateViewsLayout(is_search_box_active);
}
void KeyboardShortcutView::UpdateViewsLayout(bool is_search_box_active) {
// 1. Search box is not active: show |tabbed_pane_| and focus on active tab.
// 2. Search box is active and empty: show |tabbed_pane_| but focus on search
// box.
// 3. Search box is not empty, show |search_results_container_|. Focus is on
// search box.
const bool should_show_search_results =
is_search_box_active && !is_search_box_empty_;
search_results_container_->SetVisible(should_show_search_results);
tabbed_pane_->SetVisible(!should_show_search_results);
if (!is_search_box_active) {
search_results_container_->RemoveAllChildViews(true);
RequestFocusForActiveTab();
}
Layout(); Layout();
SchedulePaint(); SchedulePaint();
} }
...@@ -378,7 +430,12 @@ KeyboardShortcutView* KeyboardShortcutView::GetInstanceForTesting() { ...@@ -378,7 +430,12 @@ KeyboardShortcutView* KeyboardShortcutView::GetInstanceForTesting() {
} }
int KeyboardShortcutView::GetTabCountForTesting() const { int KeyboardShortcutView::GetTabCountForTesting() const {
return tabbed_pane_->GetTabCount(); return categories_tabbed_pane_->GetTabCount();
}
const std::vector<std::unique_ptr<KeyboardShortcutItemView>>&
KeyboardShortcutView::GetShortcutViewsForTesting() const {
return shortcut_views_;
} }
} // namespace keyboard_shortcut_viewer } // namespace keyboard_shortcut_viewer
...@@ -10,6 +10,7 @@ ...@@ -10,6 +10,7 @@
#include <vector> #include <vector>
#include "base/macros.h" #include "base/macros.h"
#include "base/timer/timer.h"
#include "ui/chromeos/search_box/search_box_view_delegate.h" #include "ui/chromeos/search_box/search_box_view_delegate.h"
#include "ui/views/widget/widget_delegate.h" #include "ui/views/widget/widget_delegate.h"
...@@ -48,6 +49,10 @@ class KeyboardShortcutView : public views::WidgetDelegateView, ...@@ -48,6 +49,10 @@ class KeyboardShortcutView : public views::WidgetDelegateView,
void InitViews(); void InitViews();
// Initialize |categories_tabbed_pane_| with category tabs and containers of
// |shortcut_views_|, called on construction and when exiting search mode.
void InitCategoriesTabbedPane();
// Put focus on the active tab. Used when the first time to show the widget or // Put focus on the active tab. Used when the first time to show the widget or
// after exiting search mode. // after exiting search mode.
void RequestFocusForActiveTab(); void RequestFocusForActiveTab();
...@@ -55,11 +60,8 @@ class KeyboardShortcutView : public views::WidgetDelegateView, ...@@ -55,11 +60,8 @@ class KeyboardShortcutView : public views::WidgetDelegateView,
// Update views' layout based on search box status. // Update views' layout based on search box status.
void UpdateViewsLayout(bool is_search_box_active); void UpdateViewsLayout(bool is_search_box_active);
static KeyboardShortcutView* GetInstanceForTesting(); // Show search results in |search_results_container_|.
int GetTabCountForTesting() const; void ShowSearchResults(const base::string16& search_query);
const std::vector<KeyboardShortcutItemView*>& GetShortcutViewsForTesting() {
return shortcut_views_;
}
// views::WidgetDelegate: // views::WidgetDelegate:
bool CanMaximize() const override; bool CanMaximize() const override;
...@@ -67,17 +69,25 @@ class KeyboardShortcutView : public views::WidgetDelegateView, ...@@ -67,17 +69,25 @@ class KeyboardShortcutView : public views::WidgetDelegateView,
bool CanResize() const override; bool CanResize() const override;
views::ClientView* CreateClientView(views::Widget* widget) override; views::ClientView* CreateClientView(views::Widget* widget) override;
static KeyboardShortcutView* GetInstanceForTesting();
int GetTabCountForTesting() const;
const std::vector<std::unique_ptr<KeyboardShortcutItemView>>&
GetShortcutViewsForTesting() const;
// Owned by views hierarchy. // Owned by views hierarchy.
views::TabbedPane* tabbed_pane_; // The container for category tabs and lists of KeyboardShortcutItemViews.
views::View* search_results_container_; views::TabbedPane* categories_tabbed_pane_ = nullptr;
// The container for KeyboardShortcutItemViews matching a user's query.
views::View* search_results_container_ = nullptr;
// SearchBoxViewBase is a WidgetDelegateView, which owns itself and cannot be // SearchBoxViewBase is a WidgetDelegateView, which owns itself and cannot be
// deleted from the views hierarchy automatically. // deleted from the views hierarchy automatically.
std::unique_ptr<KSVSearchBoxView> search_box_view_; std::unique_ptr<KSVSearchBoxView> search_box_view_;
// Contains all the shortcut item views from all categories. This list is used // Contains all the shortcut item views from all categories. This list is also
// for searching. The views are owned by the Views hierarchy. // used for searching. The views are not owned by the Views hierarchy to avoid
std::vector<KeyboardShortcutItemView*> shortcut_views_; // KeyboardShortcutItemView layout when switching between tabs and search.
std::vector<std::unique_ptr<KeyboardShortcutItemView>> shortcut_views_;
// An illustration to indicate no search results found. Since this view need // An illustration to indicate no search results found. Since this view need
// to be added and removed frequently from the |search_results_container_|, it // to be added and removed frequently from the |search_results_container_|, it
...@@ -88,6 +98,12 @@ class KeyboardShortcutView : public views::WidgetDelegateView, ...@@ -88,6 +98,12 @@ class KeyboardShortcutView : public views::WidgetDelegateView,
// update views' layout. // update views' layout.
bool is_search_box_empty_ = true; bool is_search_box_empty_ = true;
// Cached value of active tab index before entering search mode.
int active_tab_index_ = 0;
// Debounce for search queries.
base::OneShotTimer debounce_timer_;
DISALLOW_COPY_AND_ASSIGN(KeyboardShortcutView); DISALLOW_COPY_AND_ASSIGN(KeyboardShortcutView);
}; };
......
...@@ -25,7 +25,8 @@ class KeyboardShortcutViewTest : public ash::AshTestBase { ...@@ -25,7 +25,8 @@ class KeyboardShortcutViewTest : public ash::AshTestBase {
return GetView()->GetTabCountForTesting(); return GetView()->GetTabCountForTesting();
} }
const std::vector<KeyboardShortcutItemView*>& GetShortcutViews() { const std::vector<std::unique_ptr<KeyboardShortcutItemView>>&
GetShortcutViews() {
DCHECK(GetView()); DCHECK(GetView());
return GetView()->GetShortcutViewsForTesting(); return GetView()->GetShortcutViewsForTesting();
} }
...@@ -55,7 +56,7 @@ TEST_F(KeyboardShortcutViewTest, SideTabsCount) { ...@@ -55,7 +56,7 @@ TEST_F(KeyboardShortcutViewTest, SideTabsCount) {
int category_number = 0; int category_number = 0;
ShortcutCategory current_category = ShortcutCategory::kUnknown; ShortcutCategory current_category = ShortcutCategory::kUnknown;
for (auto* item_view : GetShortcutViews()) { for (const auto& item_view : GetShortcutViews()) {
const ShortcutCategory category = item_view->category(); const ShortcutCategory category = item_view->category();
if (current_category != category) { if (current_category != category) {
DCHECK(current_category < category); DCHECK(current_category < category);
...@@ -74,7 +75,7 @@ TEST_F(KeyboardShortcutViewTest, TopLineCenterAlignedInItemView) { ...@@ -74,7 +75,7 @@ TEST_F(KeyboardShortcutViewTest, TopLineCenterAlignedInItemView) {
// Showing the widget. // Showing the widget.
views::Widget* widget = KeyboardShortcutView::Show(CurrentContext()); views::Widget* widget = KeyboardShortcutView::Show(CurrentContext());
for (auto* item_view : GetShortcutViews()) { for (const auto& item_view : GetShortcutViews()) {
DCHECK(item_view->child_count() == 2); DCHECK(item_view->child_count() == 2);
// The top lines in both |description_label_view_| and // The top lines in both |description_label_view_| and
......
...@@ -274,6 +274,11 @@ void StyledLabel::SetHorizontalAlignment(gfx::HorizontalAlignment alignment) { ...@@ -274,6 +274,11 @@ void StyledLabel::SetHorizontalAlignment(gfx::HorizontalAlignment alignment) {
SchedulePaint(); SchedulePaint();
} }
void StyledLabel::ClearStyleRanges() {
style_ranges_.clear();
PreferredSizeChanged();
}
int StyledLabel::GetDefaultLineHeight() const { int StyledLabel::GetDefaultLineHeight() const {
return specified_line_height_ > 0 return specified_line_height_ > 0
? specified_line_height_ ? specified_line_height_
......
...@@ -139,6 +139,9 @@ class VIEWS_EXPORT StyledLabel : public View, public LinkListener { ...@@ -139,6 +139,9 @@ class VIEWS_EXPORT StyledLabel : public View, public LinkListener {
// Sets the horizontal alignment; the argument value is mirrored in RTL UI. // Sets the horizontal alignment; the argument value is mirrored in RTL UI.
void SetHorizontalAlignment(gfx::HorizontalAlignment alignment); void SetHorizontalAlignment(gfx::HorizontalAlignment alignment);
// Clears all the styles applied to the label.
void ClearStyleRanges();
private: private:
struct StyleRange { struct StyleRange {
StyleRange(const gfx::Range& range, StyleRange(const gfx::Range& range,
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment