diff options
author | nsatragno@chromium.org <nsatragno@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2014-07-04 22:51:06 +0000 |
---|---|---|
committer | nsatragno@chromium.org <nsatragno@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2014-07-04 22:51:06 +0000 |
commit | 8847487c1ce73b282e34b205b0ed816db3aafc04 (patch) | |
tree | 489fbe9c9fb67db788b8cccb9b1b53ed2190a870 | |
parent | 804e400f6a89cc5cdf23cedba9505a46616ac644 (diff) | |
download | chromium_src-8847487c1ce73b282e34b205b0ed816db3aafc04.zip chromium_src-8847487c1ce73b282e34b205b0ed816db3aafc04.tar.gz chromium_src-8847487c1ce73b282e34b205b0ed816db3aafc04.tar.bz2 |
Added text filtering to Overview Mode
This patch adds a text widget to the window overview in Ash that allows the user
to filter the windows, making it easier to select among multiple active items.
Windows that do not match the pattern are set to 0.5 transparency and their
selection is prevented. The pretarget event handler in WindowSelector has been
removed in favour of handling the key events in the widget, which produces
cleaner code.
BUG=388726
TEST=WindowSelectorTest.BasicTextFiltering
TBR=oshima@chromium.org
Review URL: https://codereview.chromium.org/358553004
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@281450 0039d316-1c4b-4281-b951-d872f2087c98
-rw-r--r-- | ash/accelerators/accelerator_controller_unittest.cc | 10 | ||||
-rw-r--r-- | ash/ash_switches.cc | 4 | ||||
-rw-r--r-- | ash/ash_switches.h | 1 | ||||
-rw-r--r-- | ash/wm/overview/scoped_transform_overview_window.cc | 5 | ||||
-rw-r--r-- | ash/wm/overview/scoped_transform_overview_window.h | 3 | ||||
-rw-r--r-- | ash/wm/overview/window_grid.cc | 32 | ||||
-rw-r--r-- | ash/wm/overview/window_grid.h | 11 | ||||
-rw-r--r-- | ash/wm/overview/window_selector.cc | 146 | ||||
-rw-r--r-- | ash/wm/overview/window_selector.h | 31 | ||||
-rw-r--r-- | ash/wm/overview/window_selector_item.cc | 49 | ||||
-rw-r--r-- | ash/wm/overview/window_selector_item.h | 12 | ||||
-rw-r--r-- | ash/wm/overview/window_selector_panels.cc | 10 | ||||
-rw-r--r-- | ash/wm/overview/window_selector_panels.h | 1 | ||||
-rw-r--r-- | ash/wm/overview/window_selector_unittest.cc | 119 | ||||
-rw-r--r-- | ash/wm/overview/window_selector_window.cc | 5 | ||||
-rw-r--r-- | ash/wm/overview/window_selector_window.h | 1 | ||||
-rw-r--r-- | chrome/app/generated_resources.grd | 6 | ||||
-rw-r--r-- | chrome/browser/about_flags.cc | 6 | ||||
-rw-r--r-- | ui/views/controls/textfield/textfield.cc | 7 | ||||
-rw-r--r-- | ui/views/controls/textfield/textfield.h | 3 |
20 files changed, 408 insertions, 54 deletions
diff --git a/ash/accelerators/accelerator_controller_unittest.cc b/ash/accelerators/accelerator_controller_unittest.cc index ab3ec88..4fecc24 100644 --- a/ash/accelerators/accelerator_controller_unittest.cc +++ b/ash/accelerators/accelerator_controller_unittest.cc @@ -1230,18 +1230,20 @@ TEST_F(AcceleratorControllerTest, DisallowedWithNoWindow) { } // Make sure we don't alert if we do have a window. - scoped_ptr<aura::Window> window( - CreateTestWindowInShellWithBounds(gfx::Rect(5, 5, 20, 20))); - wm::ActivateWindow(window.get()); + scoped_ptr<aura::Window> window; for (size_t i = 0; i < kActionsNeedingWindowLength; ++i) { + window.reset(CreateTestWindowInShellWithBounds(gfx::Rect(5, 5, 20, 20))); + wm::ActivateWindow(window.get()); delegate->TriggerAccessibilityAlert(A11Y_ALERT_NONE); GetController()->PerformAction(kActionsNeedingWindow[i], dummy); EXPECT_NE(delegate->GetLastAccessibilityAlert(), A11Y_ALERT_WINDOW_NEEDED); } // Don't alert if we have a minimized window either. - GetController()->PerformAction(WINDOW_MINIMIZE, dummy); for (size_t i = 0; i < kActionsNeedingWindowLength; ++i) { + window.reset(CreateTestWindowInShellWithBounds(gfx::Rect(5, 5, 20, 20))); + wm::ActivateWindow(window.get()); + GetController()->PerformAction(WINDOW_MINIMIZE, dummy); delegate->TriggerAccessibilityAlert(A11Y_ALERT_NONE); GetController()->PerformAction(kActionsNeedingWindow[i], dummy); EXPECT_NE(delegate->GetLastAccessibilityAlert(), A11Y_ALERT_WINDOW_NEEDED); diff --git a/ash/ash_switches.cc b/ash/ash_switches.cc index 48b0e93..87ffcc4 100644 --- a/ash/ash_switches.cc +++ b/ash/ash_switches.cc @@ -54,6 +54,10 @@ const char kAshEnableMagnifierKeyScroller[] = "ash-enable-magnifier-key-scroller"; #endif +// Enables text filtering with the keyboard in Overview Mode. +const char kAshDisableTextFilteringInOverviewMode[] = + "ash-disable-text-filtering-in-overview-mode"; + // Enables software based mirroring. const char kAshEnableSoftwareMirroring[] = "ash-enable-software-mirroring"; diff --git a/ash/ash_switches.h b/ash/ash_switches.h index 0736a33..6edcdb0 100644 --- a/ash/ash_switches.h +++ b/ash/ash_switches.h @@ -29,6 +29,7 @@ ASH_EXPORT extern const char kAshDisableTouchExplorationMode[]; #if defined(OS_CHROMEOS) ASH_EXPORT extern const char kAshEnableMagnifierKeyScroller[]; #endif +ASH_EXPORT extern const char kAshDisableTextFilteringInOverviewMode[]; ASH_EXPORT extern const char kAshEnableSoftwareMirroring[]; ASH_EXPORT extern const char kAshEnableSystemSounds[]; ASH_EXPORT extern const char kAshEnableTouchViewTesting[]; diff --git a/ash/wm/overview/scoped_transform_overview_window.cc b/ash/wm/overview/scoped_transform_overview_window.cc index afc5b00..4e334d7 100644 --- a/ash/wm/overview/scoped_transform_overview_window.cc +++ b/ash/wm/overview/scoped_transform_overview_window.cc @@ -97,7 +97,8 @@ ScopedTransformOverviewWindow::ScopedTransformOverviewWindow( ui::SHOW_STATE_MINIMIZED), ignored_by_shelf_(ash::wm::GetWindowState(window)->ignored_by_shelf()), overview_started_(false), - original_transform_(window->layer()->GetTargetTransform()) { + original_transform_(window->layer()->GetTargetTransform()), + opacity_(window->layer()->GetTargetOpacity()) { } ScopedTransformOverviewWindow::~ScopedTransformOverviewWindow() { @@ -119,6 +120,7 @@ ScopedTransformOverviewWindow::~ScopedTransformOverviewWindow() { ui::SHOW_STATE_MINIMIZED); } ash::wm::GetWindowState(window_)->set_ignored_by_shelf(ignored_by_shelf_); + window_->layer()->SetOpacity(opacity_); } } @@ -158,6 +160,7 @@ void ScopedTransformOverviewWindow::RestoreWindow() { void ScopedTransformOverviewWindow::RestoreWindowOnExit() { minimized_ = false; original_transform_ = gfx::Transform(); + opacity_ = 1; } void ScopedTransformOverviewWindow::OnWindowDestroyed() { diff --git a/ash/wm/overview/scoped_transform_overview_window.h b/ash/wm/overview/scoped_transform_overview_window.h index 7338754..9c31dbf 100644 --- a/ash/wm/overview/scoped_transform_overview_window.h +++ b/ash/wm/overview/scoped_transform_overview_window.h @@ -112,6 +112,9 @@ class ScopedTransformOverviewWindow { // The original transform of the window before entering overview mode. gfx::Transform original_transform_; + // The original opacity of the window before entering overview mode. + float opacity_; + DISALLOW_COPY_AND_ASSIGN(ScopedTransformOverviewWindow); }; diff --git a/ash/wm/overview/window_grid.cc b/ash/wm/overview/window_grid.cc index 99ea37c..04cb593 100644 --- a/ash/wm/overview/window_grid.cc +++ b/ash/wm/overview/window_grid.cc @@ -13,6 +13,7 @@ #include "ash/wm/overview/window_selector_panels.h" #include "ash/wm/overview/window_selector_window.h" #include "ash/wm/window_state.h" +#include "base/i18n/string_search.h" #include "base/memory/scoped_vector.h" #include "third_party/skia/include/core/SkColor.h" #include "ui/aura/window.h" @@ -197,6 +198,7 @@ void WindowGrid::PositionWindows(bool animate) { (total_bounds.width() - num_columns_ * window_size.width())) / 2; int y_offset = total_bounds.y() + (total_bounds.height() - num_rows * window_size.height()) / 2; + for (size_t i = 0; i < window_list_.size(); ++i) { gfx::Transform transform; int column = i % num_columns_; @@ -218,7 +220,7 @@ void WindowGrid::PositionWindows(bool animate) { MoveSelectionWidgetToTarget(animate); } -bool WindowGrid::Move(WindowSelector::Direction direction) { +bool WindowGrid::Move(WindowSelector::Direction direction, bool animate) { bool recreate_selection_widget = false; bool out_of_bounds = false; if (!selection_widget_) { @@ -235,7 +237,8 @@ bool WindowGrid::Move(WindowSelector::Direction direction) { selected_index_ = 0; break; } - } else { + } + while (SelectedWindow()->dimmed() || selection_widget_) { switch (direction) { case WindowSelector::RIGHT: if (selected_index_ >= window_list_.size() - 1) @@ -272,9 +275,13 @@ bool WindowGrid::Move(WindowSelector::Direction direction) { } break; } + // Exit the loop if we broke free from the grid or found an active item. + if (out_of_bounds || !SelectedWindow()->dimmed()) + break; } - MoveSelectionWidget(direction, recreate_selection_widget, out_of_bounds); + MoveSelectionWidget(direction, recreate_selection_widget, + out_of_bounds, animate); return out_of_bounds; } @@ -289,6 +296,20 @@ bool WindowGrid::Contains(const aura::Window* window) const { window_list_.end(); } +void WindowGrid::FilterItems(const base::string16& pattern) { + base::i18n::FixedPatternStringSearchIgnoringCaseAndAccents finder(pattern); + for (ScopedVector<WindowSelectorItem>::iterator iter = window_list_.begin(); + iter != window_list_.end(); iter++) { + if (finder.Search((*iter)->SelectionWindow()->title(), NULL, NULL)) { + (*iter)->SetDimmed(false); + } else { + (*iter)->SetDimmed(true); + if (selection_widget_ && SelectedWindow() == *iter) + selection_widget_.reset(); + } + } +} + void WindowGrid::OnWindowDestroying(aura::Window* window) { window->RemoveObserver(this); observed_windows_.erase(window); @@ -383,7 +404,8 @@ void WindowGrid::InitSelectionWidget(WindowSelector::Direction direction) { void WindowGrid::MoveSelectionWidget(WindowSelector::Direction direction, bool recreate_selection_widget, - bool out_of_bounds) { + bool out_of_bounds, + bool animate) { // If the selection widget is already active, fade it out in the selection // direction. if (selection_widget_ && (recreate_selection_widget || out_of_bounds)) { @@ -420,7 +442,7 @@ void WindowGrid::MoveSelectionWidget(WindowSelector::Direction direction, SelectedWindow()->SendFocusAlert(); // The selection widget is moved to the newly selected item in the same // grid. - MoveSelectionWidgetToTarget(true); + MoveSelectionWidgetToTarget(animate); } void WindowGrid::MoveSelectionWidgetToTarget(bool animate) { diff --git a/ash/wm/overview/window_grid.h b/ash/wm/overview/window_grid.h index fe4bf78..0b1797b 100644 --- a/ash/wm/overview/window_grid.h +++ b/ash/wm/overview/window_grid.h @@ -57,7 +57,7 @@ class ASH_EXPORT WindowGrid : public aura::WindowObserver { // Updates |selected_index_| according to the specified |direction| and calls // MoveSelectionWidget(). Returns |true| if the new selection index is out of // this window grid bounds. - bool Move(WindowSelector::Direction direction); + bool Move(WindowSelector::Direction direction, bool animate); // Returns the target selected window, or NULL if there is none selected. WindowSelectorItem* SelectedWindow() const; @@ -66,6 +66,12 @@ class ASH_EXPORT WindowGrid : public aura::WindowObserver { // this grid owns. bool Contains(const aura::Window* window) const; + // Dims the items whose titles do not contain |pattern| and prevents their + // selection. The pattern has its accents removed and is converted to + // lowercase in a l10n sensitive context. + // If |pattern| is empty, no item is dimmed. + void FilterItems(const base::string16& pattern); + // Returns true if the grid has no more windows. bool empty() const { return window_list_.empty(); } @@ -98,7 +104,8 @@ class ASH_EXPORT WindowGrid : public aura::WindowObserver { // Moves the selection widget to the specified |direction|. void MoveSelectionWidget(WindowSelector::Direction direction, bool recreate_selection_widget, - bool out_of_bounds); + bool out_of_bounds, + bool animate); // Moves the selection widget to the targeted window. void MoveSelectionWidgetToTarget(bool animate); diff --git a/ash/wm/overview/window_selector.cc b/ash/wm/overview/window_selector.cc index 467aca6..fd8604e 100644 --- a/ash/wm/overview/window_selector.cc +++ b/ash/wm/overview/window_selector.cc @@ -19,6 +19,7 @@ #include "ash/wm/overview/window_selector_item.h" #include "ash/wm/window_state.h" #include "base/auto_reset.h" +#include "base/command_line.h" #include "base/metrics/histogram.h" #include "ui/aura/client/focus_client.h" #include "ui/aura/window.h" @@ -27,6 +28,8 @@ #include "ui/compositor/scoped_layer_animation_settings.h" #include "ui/events/event.h" #include "ui/gfx/screen.h" +#include "ui/views/border.h" +#include "ui/views/controls/textfield/textfield.h" #include "ui/wm/core/window_util.h" #include "ui/wm/public/activation_client.h" @@ -34,6 +37,21 @@ namespace ash { namespace { +// The proportion of screen width that the text filter takes. +const float kTextFilterScreenProportion = 0.5; + +// The height of the text filter. +const int kTextFilterHeight = 50; + +// Solid shadow length from the text filter. +const int kVerticalShadowOffset = 1; + +// Amount of blur applied to the text filter shadow. +const int kShadowBlur = 10; + +// Text filter shadow color. +const SkColor kTextFilterShadow = 0xB0000000; + // A comparator for locating a grid with a given root window. struct RootWindowGridComparator : public std::unary_function<WindowGrid*, bool> { @@ -87,6 +105,42 @@ void UpdateShelfVisibility() { } } +// Initializes the text filter on the top of the main root window and requests +// focus on its textfield. +views::Widget* CreateTextFilter(views::TextfieldController* controller, + aura::Window* root_window) { + views::Widget* widget = new views::Widget; + views::Widget::InitParams params; + params.type = views::Widget::InitParams::TYPE_WINDOW_FRAMELESS; + params.ownership = views::Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET; + params.opacity = views::Widget::InitParams::TRANSLUCENT_WINDOW; + params.parent = + Shell::GetContainer(root_window, ash::kShellWindowId_OverlayContainer); + params.accept_events = true; + params.bounds = gfx::Rect( + root_window->bounds().width() / 2 * (1 - kTextFilterScreenProportion), 0, + root_window->bounds().width() * kTextFilterScreenProportion, + kTextFilterHeight); + widget->Init(params); + + views::Textfield* textfield = new views::Textfield; + textfield->set_controller(controller); + textfield->SetBackgroundColor(SK_ColorTRANSPARENT); + textfield->SetBorder(views::Border::NullBorder()); + textfield->SetTextColor(SK_ColorWHITE); + textfield->SetShadows(gfx::ShadowValues(1, gfx::ShadowValue( + gfx::Point(0, kVerticalShadowOffset), kShadowBlur, kTextFilterShadow))); + widget->SetContentsView(textfield); + + gfx::Transform transform; + transform.Translate(0, -kTextFilterHeight); + widget->GetNativeWindow()->SetTransform(transform); + widget->Show(); + textfield->RequestFocus(); + + return widget; +} + } // namespace WindowSelector::WindowSelector(const WindowList& windows, @@ -98,7 +152,8 @@ WindowSelector::WindowSelector(const WindowList& windows, selected_grid_index_(0), overview_start_time_(base::Time::Now()), num_key_presses_(0), - num_items_(0) { + num_items_(0), + showing_selection_widget_(false) { DCHECK(delegate_); Shell* shell = Shell::GetInstance(); shell->OnOverviewModeStarting(); @@ -127,13 +182,11 @@ WindowSelector::WindowSelector(const WindowList& windows, DCHECK(!grid_list_.empty()); UMA_HISTOGRAM_COUNTS_100("Ash.WindowSelector.Items", num_items_); - shell->activation_client()->AddObserver(this); + text_filter_widget_.reset( + CreateTextFilter(this, Shell::GetPrimaryRootWindow())); - // Remove focus from active window before entering overview. - aura::client::GetFocusClient( - Shell::GetPrimaryRootWindow())->FocusWindow(NULL); + shell->activation_client()->AddObserver(this); - shell->PrependPreTargetHandler(this); shell->GetScreen()->AddObserver(this); shell->metrics()->RecordUserMetricsAction(UMA_WINDOW_OVERVIEW); HideAndTrackNonOverviewWindows(); @@ -168,7 +221,6 @@ WindowSelector::~WindowSelector() { (*iter)->Show(); } - shell->RemovePreTargetHandler(this); shell->GetScreen()->RemoveObserver(this); size_t remaining_items = 0; @@ -207,34 +259,35 @@ void WindowSelector::OnGridEmpty(WindowGrid* grid) { CancelSelection(); } -void WindowSelector::OnKeyEvent(ui::KeyEvent* event) { - if (event->type() != ui::ET_KEY_PRESSED) - return; +bool WindowSelector::HandleKeyEvent(views::Textfield* sender, + const ui::KeyEvent& key_event) { + if (key_event.type() != ui::ET_KEY_PRESSED) + return false; - switch (event->key_code()) { + switch (key_event.key_code()) { case ui::VKEY_ESCAPE: CancelSelection(); break; case ui::VKEY_UP: num_key_presses_++; - Move(WindowSelector::UP); + Move(WindowSelector::UP, true); break; case ui::VKEY_DOWN: num_key_presses_++; - Move(WindowSelector::DOWN); + Move(WindowSelector::DOWN, true); break; case ui::VKEY_RIGHT: num_key_presses_++; - Move(WindowSelector::RIGHT); + Move(WindowSelector::RIGHT, true); break; case ui::VKEY_LEFT: num_key_presses_++; - Move(WindowSelector::LEFT); + Move(WindowSelector::LEFT, true); break; case ui::VKEY_RETURN: // Ignore if no item is selected. if (!grid_list_[selected_grid_index_]->is_selecting()) - return; + return false; UMA_HISTOGRAM_COUNTS_100("Ash.WindowSelector.ArrowKeyPresses", num_key_presses_); UMA_HISTOGRAM_CUSTOM_COUNTS( @@ -246,10 +299,10 @@ void WindowSelector::OnKeyEvent(ui::KeyEvent* event) { SelectedWindow()->SelectionWindow())->Activate(); break; default: - // Not a key we are interested in. - return; + // Not a key we are interested in, allow the textfield to handle it. + return false; } - event->StopPropagation(); + return true; } void WindowSelector::OnDisplayAdded(const gfx::Display& display) { @@ -290,8 +343,11 @@ void WindowSelector::OnWindowDestroying(aura::Window* window) { void WindowSelector::OnWindowActivated(aura::Window* gained_active, aura::Window* lost_active) { - if (ignore_activations_ || !gained_active) + if (ignore_activations_ || + !gained_active || + gained_active == text_filter_widget_->GetNativeWindow()) { return; + } ScopedVector<WindowGrid>::iterator grid = std::find_if(grid_list_.begin(), grid_list_.end(), @@ -317,6 +373,43 @@ void WindowSelector::OnAttemptToReactivateWindow(aura::Window* request_active, OnWindowActivated(request_active, actual_active); } +void WindowSelector::ContentsChanged(views::Textfield* sender, + const base::string16& new_contents) { + if (CommandLine::ForCurrentProcess()->HasSwitch( + switches::kAshDisableTextFilteringInOverviewMode)) { + return; + } + + bool should_show_selection_widget = !new_contents.empty(); + if (showing_selection_widget_ != should_show_selection_widget) { + ui::ScopedLayerAnimationSettings animation_settings( + text_filter_widget_->GetNativeWindow()->layer()->GetAnimator()); + animation_settings.SetPreemptionStrategy( + ui::LayerAnimator::IMMEDIATELY_ANIMATE_TO_NEW_TARGET); + animation_settings.SetTweenType(showing_selection_widget_ ? + gfx::Tween::FAST_OUT_LINEAR_IN : gfx::Tween::LINEAR_OUT_SLOW_IN); + + gfx::Transform transform; + if (should_show_selection_widget) + transform.Translate(0, 0); + else + transform.Translate(0, -kTextFilterHeight); + + text_filter_widget_->GetNativeWindow()->SetTransform(transform); + showing_selection_widget_ = should_show_selection_widget; + } + for (ScopedVector<WindowGrid>::iterator iter = grid_list_.begin(); + iter != grid_list_.end(); iter++) { + (*iter)->FilterItems(new_contents); + } + + // If the selection widget is not active, execute a Move() command so that it + // shows up on the first undimmed item. + if (grid_list_[selected_grid_index_]->is_selecting()) + return; + Move(WindowSelector::RIGHT, false); +} + void WindowSelector::PositionWindows(bool animate) { for (ScopedVector<WindowGrid>::iterator iter = grid_list_.begin(); iter != grid_list_.end(); iter++) { @@ -379,16 +472,15 @@ void WindowSelector::ResetFocusRestoreWindow(bool focus) { restore_focus_window_ = NULL; } -void WindowSelector::Move(Direction direction) { - bool overflowed = grid_list_[selected_grid_index_]->Move(direction); - if (overflowed) { - // The grid reported that the movement command corresponds to the next - // root window, identify it and call Move() on it to initialize the - // selection widget. +void WindowSelector::Move(Direction direction, bool animate) { + // Keep calling Move() on the grids until one of them reports no overflow or + // we made a full cycle on all the grids. + for (size_t i = 0; + i <= grid_list_.size() && + grid_list_[selected_grid_index_]->Move(direction, animate); i++) { // TODO(nsatragno): If there are more than two monitors, move between grids // in the requested direction. selected_grid_index_ = (selected_grid_index_ + 1) % grid_list_.size(); - grid_list_[selected_grid_index_]->Move(direction); } } diff --git a/ash/wm/overview/window_selector.h b/ash/wm/overview/window_selector.h index f9ca51a..2b44700 100644 --- a/ash/wm/overview/window_selector.h +++ b/ash/wm/overview/window_selector.h @@ -17,6 +17,7 @@ #include "ui/aura/window_tracker.h" #include "ui/events/event_handler.h" #include "ui/gfx/display_observer.h" +#include "ui/views/controls/textfield/textfield_controller.h" #include "ui/wm/public/activation_change_observer.h" namespace aura { @@ -32,6 +33,11 @@ namespace ui { class LocatedEvent; } +namespace views { +class Textfield; +class Widget; +} + namespace ash { class WindowSelectorDelegate; class WindowSelectorItem; @@ -41,10 +47,10 @@ class WindowGrid; // The WindowSelector shows a grid of all of your windows, allowing to select // one by clicking or tapping on it. class ASH_EXPORT WindowSelector - : public ui::EventHandler, - public gfx::DisplayObserver, + : public gfx::DisplayObserver, public aura::WindowObserver, - public aura::client::ActivationChangeObserver { + public aura::client::ActivationChangeObserver, + public views::TextfieldController { public: enum Direction { LEFT, @@ -66,9 +72,6 @@ class ASH_EXPORT WindowSelector // Called when the last window selector item from a grid is deleted. void OnGridEmpty(WindowGrid* grid); - // ui::EventHandler: - virtual void OnKeyEvent(ui::KeyEvent* event) OVERRIDE; - // gfx::DisplayObserver: virtual void OnDisplayAdded(const gfx::Display& display) OVERRIDE; virtual void OnDisplayRemoved(const gfx::Display& display) OVERRIDE; @@ -86,6 +89,12 @@ class ASH_EXPORT WindowSelector aura::Window* request_active, aura::Window* actual_active) OVERRIDE; + // views::TextfieldController: + virtual void ContentsChanged(views::Textfield* sender, + const base::string16& new_contents) OVERRIDE; + virtual bool HandleKeyEvent(views::Textfield* sender, + const ui::KeyEvent& key_event) OVERRIDE; + private: friend class WindowSelectorTest; @@ -103,7 +112,7 @@ class ASH_EXPORT WindowSelector // Helper function that moves the selection widget to |direction| on the // corresponding window grid. - void Move(Direction direction); + void Move(Direction direction, bool animate); // Tracks observed windows. std::set<aura::Window*> observed_windows_; @@ -142,6 +151,14 @@ class ASH_EXPORT WindowSelector // The number of items in the overview. size_t num_items_; + // Indicates if we are showing the selection widget. + bool showing_selection_widget_; + + // Window text filter widget. As the user writes on it, we filter the items + // in the overview. It is also responsible for handling overview key events, + // such as enter key to select. + scoped_ptr<views::Widget> text_filter_widget_; + DISALLOW_COPY_AND_ASSIGN(WindowSelector); }; diff --git a/ash/wm/overview/window_selector_item.cc b/ash/wm/overview/window_selector_item.cc index e6ce66e..cd3bbaf 100644 --- a/ash/wm/overview/window_selector_item.cc +++ b/ash/wm/overview/window_selector_item.cc @@ -75,8 +75,43 @@ static const int kShadowBlur = 10; const int WindowSelectorItem::kFadeInMilliseconds = 80; +// Opacity for dimmed items. +static const float kDimmedItemOpacity = 0.5f; + +views::Widget* CreateWindowLabel(aura::Window* root_window, + const base::string16 title) { + views::Widget* widget = new views::Widget; + views::Widget::InitParams params; + params.type = views::Widget::InitParams::TYPE_POPUP; + params.ownership = views::Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET; + params.opacity = views::Widget::InitParams::TRANSLUCENT_WINDOW; + params.parent = + Shell::GetContainer(root_window, ash::kShellWindowId_OverlayContainer); + params.accept_events = false; + params.visible_on_all_workspaces = true; + widget->set_focus_on_creation(false); + widget->Init(params); + views::Label* label = new views::Label; + label->SetEnabledColor(kLabelColor); + label->SetBackgroundColor(kLabelBackground); + label->set_shadows(gfx::ShadowValues(1, gfx::ShadowValue( + gfx::Point(0, kVerticalShadowOffset), kShadowBlur, kLabelShadow))); + ui::ResourceBundle& bundle = ui::ResourceBundle::GetSharedInstance(); + label->SetFontList(bundle.GetFontList(ui::ResourceBundle::BoldFont)); + label->SetText(title); + views::BoxLayout* layout = new views::BoxLayout(views::BoxLayout::kVertical, + 0, + kVerticalLabelPadding, + 0); + label->SetLayoutManager(layout); + widget->SetContentsView(label); + widget->Show(); + return widget; +} + WindowSelectorItem::WindowSelectorItem() - : root_window_(NULL), + : dimmed_(false), + root_window_(NULL), in_bounds_update_(false), window_label_view_(NULL) { } @@ -134,12 +169,19 @@ void WindowSelectorItem::SendFocusAlert() const { activate_window_button_->SendFocusAlert(); } +void WindowSelectorItem::SetDimmed(bool dimmed) { + dimmed_ = dimmed; + SetOpacity(dimmed ? kDimmedItemOpacity : 1.0f); +} + void WindowSelectorItem::ButtonPressed(views::Button* sender, const ui::Event& event) { views::Widget::GetWidgetForNativeView(SelectionWindow())->Close(); } void WindowSelectorItem::OnWindowTitleChanged(aura::Window* window) { + // TODO(flackr): Maybe add the new title to a vector of titles so that we can + // filter any of the titles the window had while in the overview session. if (window == SelectionWindow()) window_label_view_->SetText(window->title()); } @@ -207,6 +249,11 @@ void WindowSelectorItem::UpdateCloseButtonBounds(aura::Window* root_window, } } +void WindowSelectorItem::SetOpacity(float opacity) { + window_label_->GetNativeWindow()->layer()->SetOpacity(opacity); + close_button_->GetNativeWindow()->layer()->SetOpacity(opacity); +} + void WindowSelectorItem::UpdateWindowLabels(const gfx::Rect& window_bounds, aura::Window* root_window, bool animate) { diff --git a/ash/wm/overview/window_selector_item.h b/ash/wm/overview/window_selector_item.h index 2cae674..279aed8 100644 --- a/ash/wm/overview/window_selector_item.h +++ b/ash/wm/overview/window_selector_item.h @@ -79,6 +79,11 @@ class WindowSelectorItem : public views::ButtonListener, // label is read. void SendFocusAlert() const; + // Sets if the item is dimmed in the overview. Changing the value will also + // change the visibility of the transform windows. + virtual void SetDimmed(bool dimmed); + bool dimmed() const { return dimmed_; } + const gfx::Rect& bounds() const { return bounds_; } const gfx::Rect& target_bounds() const { return target_bounds_; } @@ -100,6 +105,13 @@ class WindowSelectorItem : public views::ButtonListener, // Sets the bounds used by the selector item's windows. void set_bounds(const gfx::Rect& bounds) { bounds_ = bounds; } + // Changes the opacity of all the windows the item owns. + virtual void SetOpacity(float opacity); + + // True if the item is being shown in the overview, false if it's being + // filtered. + bool dimmed_; + private: friend class WindowSelectorTest; diff --git a/ash/wm/overview/window_selector_panels.cc b/ash/wm/overview/window_selector_panels.cc index e4ab9be..d3c6920 100644 --- a/ash/wm/overview/window_selector_panels.cc +++ b/ash/wm/overview/window_selector_panels.cc @@ -205,4 +205,14 @@ void WindowSelectorPanels::SetItemBounds(aura::Window* root_window, } } +void WindowSelectorPanels::SetOpacity(float opacity) { + // TODO(flackr): find a way to make panels that are hidden behind other panels + // look nice. + for (WindowList::iterator iter = transform_windows_.begin(); + iter != transform_windows_.end(); iter++) { + (*iter)->window()->layer()->SetOpacity(opacity); + } + WindowSelectorItem::SetOpacity(opacity); +} + } // namespace ash diff --git a/ash/wm/overview/window_selector_panels.h b/ash/wm/overview/window_selector_panels.h index b3ea49f..0e22a37 100644 --- a/ash/wm/overview/window_selector_panels.h +++ b/ash/wm/overview/window_selector_panels.h @@ -39,6 +39,7 @@ class WindowSelectorPanels : public WindowSelectorItem { virtual void SetItemBounds(aura::Window* root_window, const gfx::Rect& target_bounds, bool animate) OVERRIDE; + virtual void SetOpacity(float opacity) OVERRIDE; private: typedef ScopedVector<ScopedTransformOverviewWindow> WindowList; diff --git a/ash/wm/overview/window_selector_unittest.cc b/ash/wm/overview/window_selector_unittest.cc index d482f55..5e5969c 100644 --- a/ash/wm/overview/window_selector_unittest.cc +++ b/ash/wm/overview/window_selector_unittest.cc @@ -30,6 +30,7 @@ #include "base/compiler_specific.h" #include "base/memory/scoped_vector.h" #include "base/run_loop.h" +#include "base/strings/string_piece.h" #include "base/strings/utf_string_conversions.h" #include "ui/aura/client/aura_constants.h" #include "ui/aura/client/cursor_client.h" @@ -202,6 +203,19 @@ class WindowSelectorTest : public test::AshTestBase { SelectedWindow()->SelectionWindow(); } + const bool selection_widget_active() { + WindowSelector* ws = ash::Shell::GetInstance()-> + window_selector_controller()->window_selector_.get(); + return ws->grid_list_[ws->selected_grid_index_]->is_selecting(); + } + + bool showing_filter_widget() { + WindowSelector* ws = ash::Shell::GetInstance()-> + window_selector_controller()->window_selector_.get(); + return ws->text_filter_widget_->GetNativeWindow()->layer()-> + GetTargetTransform().IsIdentity(); + } + views::Widget* GetCloseButton(ash::WindowSelectorItem* window) { return window->close_button_.get(); } @@ -224,10 +238,22 @@ class WindowSelectorTest : public test::AshTestBase { GetCloseButton(window_item)->GetNativeView())))); } + void FilterItems(const base::StringPiece& pattern) { + ash::Shell::GetInstance()-> + window_selector_controller()->window_selector_.get()-> + ContentsChanged(NULL, base::UTF8ToUTF16(pattern)); + } + test::ShelfViewTestAPI* shelf_view_test() { return shelf_view_test_.get(); } + views::Widget* text_filter_widget() { + return ash::Shell::GetInstance()-> + window_selector_controller()->window_selector_.get()-> + text_filter_widget_.get(); + } + private: aura::test::TestWindowDelegate delegate_; NonActivatableActivationDelegate non_activatable_activation_delegate_; @@ -266,10 +292,10 @@ TEST_F(WindowSelectorTest, Basic) { // Hide the cursor before entering overview to test that it will be shown. aura::client::GetCursorClient(root_window)->HideCursor(); - // In overview mode the windows should no longer overlap and focus should - // be removed from the window. + // In overview mode the windows should no longer overlap and the text filter + // widget should be focused. ToggleOverview(); - EXPECT_EQ(NULL, GetFocusedWindow()); + EXPECT_EQ(text_filter_widget()->GetNativeWindow(), GetFocusedWindow()); EXPECT_FALSE(WindowsOverlapping(window1.get(), window2.get())); EXPECT_FALSE(WindowsOverlapping(window1.get(), panel1.get())); // Panels 1 and 2 should still be overlapping being in a single selector @@ -294,7 +320,7 @@ TEST_F(WindowSelectorTest, BasicGesture) { wm::ActivateWindow(window1.get()); EXPECT_EQ(window1.get(), GetFocusedWindow()); ToggleOverview(); - EXPECT_EQ(NULL, GetFocusedWindow()); + EXPECT_EQ(text_filter_widget()->GetNativeWindow(), GetFocusedWindow()); aura::test::EventGenerator generator(Shell::GetPrimaryRootWindow(), window2.get()); generator.GestureTapAt(gfx::ToEnclosingRect( @@ -502,9 +528,9 @@ TEST_F(WindowSelectorTest, CancelRestoresFocus) { wm::ActivateWindow(window.get()); EXPECT_EQ(window.get(), GetFocusedWindow()); - // In overview mode, focus should be removed. + // In overview mode, the text filter widget should be focused. ToggleOverview(); - EXPECT_EQ(NULL, GetFocusedWindow()); + EXPECT_EQ(text_filter_widget()->GetNativeWindow(), GetFocusedWindow()); // If canceling overview mode, focus should be restored. ToggleOverview(); @@ -1014,4 +1040,85 @@ TEST_F(WindowSelectorTest, CloseButtonOnPanels) { EXPECT_FALSE(IsSelecting()); } +// Creates three windows and tests filtering them by title. +TEST_F(WindowSelectorTest, BasicTextFiltering) { + gfx::Rect bounds(0, 0, 100, 100); + scoped_ptr<aura::Window> window2(CreateWindow(bounds)); + scoped_ptr<aura::Window> window1(CreateWindow(bounds)); + scoped_ptr<aura::Window> window0(CreateWindow(bounds)); + base::string16 window2_title = base::UTF8ToUTF16("Highway to test"); + base::string16 window1_title = base::UTF8ToUTF16("For those about to test"); + base::string16 window0_title = base::UTF8ToUTF16("We salute you"); + window0->SetTitle(window0_title); + window1->SetTitle(window1_title); + window2->SetTitle(window2_title); + ToggleOverview(); + EXPECT_FALSE(selection_widget_active()); + EXPECT_FALSE(showing_filter_widget()); + FilterItems("Test"); + + // The selection widget should appear when filtering starts, and should be + // selecting the first matching window. + EXPECT_TRUE(selection_widget_active()); + EXPECT_TRUE(showing_filter_widget()); + EXPECT_EQ(GetSelectedWindow(), window1.get()); + + // Window 0 has no "test" on it so it should be the only dimmed item. + std::vector<WindowSelectorItem*> items = GetWindowItemsForRoot(0); + EXPECT_TRUE(items[0]->dimmed()); + EXPECT_FALSE(items[1]->dimmed()); + EXPECT_FALSE(items[2]->dimmed()); + + // No items match the search. + FilterItems("I'm testing 'n testing"); + EXPECT_TRUE(items[0]->dimmed()); + EXPECT_TRUE(items[1]->dimmed()); + EXPECT_TRUE(items[2]->dimmed()); + + // All the items should match the empty string. The filter widget should also + // disappear. + FilterItems(""); + EXPECT_FALSE(showing_filter_widget()); + EXPECT_FALSE(items[0]->dimmed()); + EXPECT_FALSE(items[1]->dimmed()); + EXPECT_FALSE(items[2]->dimmed()); +} + +// Tests selecting in the overview with dimmed and undimmed items. +TEST_F(WindowSelectorTest, TextFilteringSelection) { + gfx::Rect bounds(0, 0, 100, 100); + scoped_ptr<aura::Window> window2(CreateWindow(bounds)); + scoped_ptr<aura::Window> window1(CreateWindow(bounds)); + scoped_ptr<aura::Window> window0(CreateWindow(bounds)); + base::string16 window2_title = base::UTF8ToUTF16("Rock and roll"); + base::string16 window1_title = base::UTF8ToUTF16("Rock and"); + base::string16 window0_title = base::UTF8ToUTF16("Rock"); + window0->SetTitle(window0_title); + window1->SetTitle(window1_title); + window2->SetTitle(window2_title); + ToggleOverview(); + SendKey(ui::VKEY_RIGHT); + EXPECT_TRUE(selection_widget_active()); + EXPECT_EQ(GetSelectedWindow(), window0.get()); + + // Dim the first item, the selection should jump to the next item. + std::vector<WindowSelectorItem*> items = GetWindowItemsForRoot(0); + FilterItems("Rock and"); + EXPECT_EQ(GetSelectedWindow(), window1.get()); + + // Cycle the selection, the dimmed window should not be selected. + SendKey(ui::VKEY_RIGHT); + EXPECT_EQ(GetSelectedWindow(), window2.get()); + SendKey(ui::VKEY_RIGHT); + EXPECT_EQ(GetSelectedWindow(), window1.get()); + + // Dimming all the items should hide the selection widget. + FilterItems("Pop"); + EXPECT_FALSE(selection_widget_active()); + + // Undimming one window should automatically select it. + FilterItems("Rock and roll"); + EXPECT_EQ(GetSelectedWindow(), window2.get()); +} + } // namespace ash diff --git a/ash/wm/overview/window_selector_window.cc b/ash/wm/overview/window_selector_window.cc index ed06e8e..1ece154 100644 --- a/ash/wm/overview/window_selector_window.cc +++ b/ash/wm/overview/window_selector_window.cc @@ -75,4 +75,9 @@ void WindowSelectorWindow::SetItemBounds(aura::Window* root_window, animate); } +void WindowSelectorWindow::SetOpacity(float opacity) { + transform_window_.window()->layer()->SetOpacity(opacity); + WindowSelectorItem::SetOpacity(opacity); +} + } // namespace ash diff --git a/ash/wm/overview/window_selector_window.h b/ash/wm/overview/window_selector_window.h index e508438..5d58085 100644 --- a/ash/wm/overview/window_selector_window.h +++ b/ash/wm/overview/window_selector_window.h @@ -39,6 +39,7 @@ class WindowSelectorWindow : public WindowSelectorItem { virtual void SetItemBounds(aura::Window* root_window, const gfx::Rect& target_bounds, bool animate) OVERRIDE; + virtual void SetOpacity(float opacity) OVERRIDE; private: // The window with a scoped transform represented by this selector item. diff --git a/chrome/app/generated_resources.grd b/chrome/app/generated_resources.grd index 6552411..31fcee0 100644 --- a/chrome/app/generated_resources.grd +++ b/chrome/app/generated_resources.grd @@ -5952,6 +5952,12 @@ Keep your key file in a safe place. You will need it to create new versions of y <message name="IDS_FLAGS_ASH_ENABLE_TOUCH_VIEW_TESTING_DESCRIPTION" desc="Description for the flag to enable the TouchView testing mode."> Enable Ctrl+Alt+Shift+8 to toggle the TouchView maximizing mode. </message> + <message name="IDS_FLAGS_ASH_DISABLE_TEXT_FILTERING_IN_OVERVIEW_MODE_NAME" desc="Title for the flag to disable window filtering in overview mode by inputing text"> + Disable text filtering in Overview Mode. + </message> + <message name="IDS_FLAGS_ASH_DISABLE_TEXT_FILTERING_IN_OVERVIEW_MODE_DESCRIPTION" desc="Description for the flag to disable window filtering in overview mode by inputing text"> + Disables filtering the windows shown in overview mode by entering text. + </message> </if> <message name="IDS_GENERIC_EXPERIMENT_CHOICE_AUTOMATIC" desc="Generic 'Automatic' experiment choice option name."> diff --git a/chrome/browser/about_flags.cc b/chrome/browser/about_flags.cc index a87a228..8693d82 100644 --- a/chrome/browser/about_flags.cc +++ b/chrome/browser/about_flags.cc @@ -1080,6 +1080,12 @@ const Experiment kExperiments[] = { kOsCrOS, SINGLE_VALUE_TYPE(ash::switches::kAshEnableTouchViewTesting), }, + { "ash-disable-text-filtering-in-overview-mode", + IDS_FLAGS_ASH_DISABLE_TEXT_FILTERING_IN_OVERVIEW_MODE_NAME, + IDS_FLAGS_ASH_DISABLE_TEXT_FILTERING_IN_OVERVIEW_MODE_DESCRIPTION, + kOsCrOS, + SINGLE_VALUE_TYPE(ash::switches::kAshDisableTextFilteringInOverviewMode), + }, #endif #if defined(OS_CHROMEOS) { diff --git a/ui/views/controls/textfield/textfield.cc b/ui/views/controls/textfield/textfield.cc index 09791e9..9407f2d 100644 --- a/ui/views/controls/textfield/textfield.cc +++ b/ui/views/controls/textfield/textfield.cc @@ -409,6 +409,11 @@ void Textfield::UseDefaultSelectionTextColor() { SchedulePaint(); } +void Textfield::SetShadows(const gfx::ShadowValues& shadows) { + GetRenderText()->set_shadows(shadows); + SchedulePaint(); +} + SkColor Textfield::GetSelectionBackgroundColor() const { return use_default_selection_background_color_ ? GetNativeTheme()->GetSystemColor( @@ -601,7 +606,7 @@ bool Textfield::OnMousePressed(const ui::MouseEvent& event) { ui::Clipboard::GetForCurrentThread(), ui::CLIPBOARD_TYPE_SELECTION).WriteText(base::string16()); OnAfterUserAction(); - } else if(!read_only()) { + } else if (!read_only()) { PasteSelectionClipboard(event); } } diff --git a/ui/views/controls/textfield/textfield.h b/ui/views/controls/textfield/textfield.h index e836177..f32ec89 100644 --- a/ui/views/controls/textfield/textfield.h +++ b/ui/views/controls/textfield/textfield.h @@ -120,6 +120,9 @@ class VIEWS_EXPORT Textfield : public View, void SetSelectionBackgroundColor(SkColor color); void UseDefaultSelectionBackgroundColor(); + // Set drop shadows underneath the text. + void SetShadows(const gfx::ShadowValues& shadows); + // Gets/Sets whether or not the cursor is enabled. bool GetCursorEnabled() const; void SetCursorEnabled(bool enabled); |