Commit 4682f12f authored by Sammie Quon's avatar Sammie Quon Committed by Commit Bot

shelf: Allow focus items in the overflow bubble.

Currently if the bubble is open, you cannot focus the items in the bubble
using the keyboard. Also, if there are for example 4 items in the bubble
and the bubble is closed, you have to tab 4 times to get from the last
item on the main shelf to the first item.

This cl makes the overflow bubble activatable, and focuses it when the
index is out of visible bounds on the main shelf. The main shelf regains
focus when the index is out of visible bounds on the overflow shelf.
This cl also fixes when the overflow bubble is closed, there will not
be need to press tab extra to loop back to the beginning.

Test: add tests
Bug: 854313
Change-Id: Ib36939952488ae23300062300bdc43a7b2a1e5fc
Reviewed-on: https://chromium-review.googlesource.com/1125317Reviewed-by: default avatarMitsuru Oshima <oshima@chromium.org>
Reviewed-by: default avatarMichael Wasserman <msw@chromium.org>
Commit-Queue: Sammie Quon <sammiequon@chromium.org>
Cr-Commit-Position: refs/heads/master@{#574964}
parent 955f9260
......@@ -24,7 +24,7 @@ const int32_t kActivatableShellWindowIds[] = {
// containers even though these layers are higher. The user expects their
// windows to be focused before these elements.
kShellWindowId_PanelContainer, kShellWindowId_ShelfContainer,
kShellWindowId_StatusContainer,
kShellWindowId_ShelfBubbleContainer, kShellWindowId_StatusContainer,
};
const size_t kNumActivatableShellWindowIds =
......
......@@ -4,10 +4,12 @@
#include "ash/shelf/overflow_bubble.h"
#include "ash/focus_cycler.h"
#include "ash/shelf/overflow_bubble_view.h"
#include "ash/shelf/overflow_button.h"
#include "ash/shelf/shelf.h"
#include "ash/shelf/shelf_view.h"
#include "ash/shell.h"
#include "ash/shell_port.h"
#include "ash/system/tray/tray_background_view.h"
#include "ui/gfx/geometry/rect.h"
......@@ -16,10 +18,7 @@
namespace ash {
OverflowBubble::OverflowBubble(Shelf* shelf)
: shelf_(shelf),
bubble_(nullptr),
overflow_button_(nullptr),
shelf_view_(nullptr) {
: shelf_(shelf), bubble_(nullptr), overflow_button_(nullptr) {
DCHECK(shelf_);
ShellPort::Get()->AddPointerWatcher(this,
views::PointerWatcherEventTypes::BASIC);
......@@ -39,12 +38,12 @@ void OverflowBubble::Show(OverflowButton* overflow_button,
bubble_ = new OverflowBubbleView(shelf_);
bubble_->InitOverflowBubble(overflow_button, shelf_view);
shelf_view_ = shelf_view;
overflow_button_ = overflow_button;
TrayBackgroundView::InitializeBubbleAnimations(bubble_->GetWidget());
bubble_->GetWidget()->AddObserver(this);
bubble_->GetWidget()->Show();
Shell::Get()->focus_cycler()->AddWidget(bubble_->GetWidget());
overflow_button->OnOverflowBubbleShown();
}
......@@ -55,18 +54,18 @@ void OverflowBubble::Hide() {
OverflowButton* overflow_button = overflow_button_;
Shell::Get()->focus_cycler()->RemoveWidget(bubble_->GetWidget());
bubble_->GetWidget()->RemoveObserver(this);
bubble_->GetWidget()->Close();
bubble_ = nullptr;
overflow_button_ = nullptr;
shelf_view_ = nullptr;
overflow_button->OnOverflowBubbleHidden();
}
void OverflowBubble::ProcessPressedEvent(
const gfx::Point& event_location_in_screen) {
if (!IsShowing() || shelf_view_->IsShowingMenu() ||
if (!IsShowing() || bubble_->shelf_view()->IsShowingMenu() ||
bubble_->GetBoundsInScreen().Contains(event_location_in_screen) ||
overflow_button_->GetBoundsInScreen().Contains(
event_location_in_screen)) {
......@@ -76,8 +75,10 @@ void OverflowBubble::ProcessPressedEvent(
// Do not hide the shelf if one of the buttons on the main shelf was pressed,
// since the user might want to drag an item onto the overflow bubble.
// The button itself will close the overflow bubble on the release event.
if (shelf_view_->main_shelf()->GetVisibleItemsBoundsInScreen().Contains(
event_location_in_screen)) {
if (bubble_->shelf_view()
->main_shelf()
->GetVisibleItemsBoundsInScreen()
.Contains(event_location_in_screen)) {
return;
}
......@@ -98,7 +99,6 @@ void OverflowBubble::OnWidgetDestroying(views::Widget* widget) {
overflow_button_->SchedulePaint();
bubble_ = nullptr;
overflow_button_ = nullptr;
shelf_view_ = nullptr;
}
} // namespace ash
......@@ -5,10 +5,15 @@
#ifndef ASH_SHELF_OVERFLOW_BUBBLE_H_
#define ASH_SHELF_OVERFLOW_BUBBLE_H_
#include "ash/ash_export.h"
#include "base/macros.h"
#include "ui/views/pointer_watcher.h"
#include "ui/views/widget/widget_observer.h"
namespace views {
class Widget;
}
namespace ui {
class PointerEvent;
}
......@@ -21,8 +26,8 @@ class ShelfView;
// OverflowBubble shows shelf items that won't fit on the main shelf in a
// separate bubble.
class OverflowBubble : public views::PointerWatcher,
public views::WidgetObserver {
class ASH_EXPORT OverflowBubble : public views::PointerWatcher,
public views::WidgetObserver {
public:
// |shelf| is the shelf that spawns the bubble.
explicit OverflowBubble(Shelf* shelf);
......@@ -36,7 +41,6 @@ class OverflowBubble : public views::PointerWatcher,
void Hide();
bool IsShowing() const { return !!bubble_; }
ShelfView* shelf_view() { return shelf_view_; }
OverflowBubbleView* bubble_view() { return bubble_; }
private:
......@@ -54,9 +58,6 @@ class OverflowBubble : public views::PointerWatcher,
OverflowBubbleView* bubble_; // Owned by views hierarchy.
OverflowButton* overflow_button_; // Owned by ShelfView.
// ShelfView containing the overflow items. Owned by |bubble_|.
ShelfView* shelf_view_;
DISALLOW_COPY_AND_ASSIGN(OverflowBubble);
};
......
......@@ -9,14 +9,16 @@
#include "ash/public/cpp/shell_window_ids.h"
#include "ash/shelf/shelf.h"
#include "ash/shelf/shelf_constants.h"
#include "ash/shelf/shelf_view.h"
#include "ash/shelf/shelf_widget.h"
#include "ash/shell.h"
#include "ash/wm/window_util.h"
#include "base/i18n/rtl.h"
#include "ui/display/display.h"
#include "ui/display/screen.h"
#include "ui/events/event.h"
#include "ui/gfx/geometry/insets.h"
#include "ui/views/bubble/bubble_frame_view.h"
#include "ui/views/view.h"
#include "ui/views/widget/widget.h"
namespace ash {
......@@ -48,7 +50,7 @@ OverflowBubbleView::~OverflowBubbleView() {
}
void OverflowBubbleView::InitOverflowBubble(views::View* anchor,
views::View* shelf_view) {
ShelfView* shelf_view) {
shelf_view_ = shelf_view;
SetAnchorView(anchor);
......@@ -59,9 +61,8 @@ void OverflowBubbleView::InitOverflowBubble(views::View* anchor,
else
set_margins(gfx::Insets(kEndPadding, 0));
set_shadow(views::BubbleBorder::NO_ASSETS);
// Overflow bubble should not get focus. If it get focus when it is shown,
// active state item is changed to running state.
set_can_activate(false);
set_close_on_deactivate(false);
set_accept_events(true);
// Makes bubble view has a layer and clip its children layers.
SetPaintToLayer();
......@@ -196,6 +197,18 @@ gfx::Rect OverflowBubbleView::GetBubbleBounds() {
return bounds;
}
bool OverflowBubbleView::CanActivate() const {
if (!GetWidget())
return false;
// Do not activate the bubble unless the current active window is the shelf
// window.
aura::Window* active_window = wm::GetActiveWindow();
aura::Window* bubble_window = GetWidget()->GetNativeWindow();
aura::Window* shelf_window = shelf_->shelf_widget()->GetNativeWindow();
return active_window == bubble_window || active_window == shelf_window;
}
void OverflowBubbleView::UpdateShelfBackground(SkColor color) {
set_color(color);
}
......
......@@ -11,12 +11,9 @@
#include "base/macros.h"
#include "ui/views/bubble/bubble_dialog_delegate.h"
namespace views {
class View;
}
namespace ash {
class Shelf;
class ShelfView;
// OverflowBubbleView hosts a ShelfView to display overflown items.
// Exports to access this class from OverflowBubbleViewTestAPI.
......@@ -28,11 +25,14 @@ class ASH_EXPORT OverflowBubbleView : public views::BubbleDialogDelegateView,
// |anchor| is the overflow button on the main shelf. |shelf_view| is the
// ShelfView containing the overflow items.
void InitOverflowBubble(views::View* anchor, views::View* shelf_view);
void InitOverflowBubble(views::View* anchor, ShelfView* shelf_view);
// views::BubbleDialogDelegateView overrides:
// views::BubbleDialogDelegateView:
int GetDialogButtons() const override;
gfx::Rect GetBubbleBounds() override;
bool CanActivate() const override;
ShelfView* shelf_view() { return shelf_view_; }
private:
friend class OverflowBubbleViewTestAPI;
......@@ -53,7 +53,7 @@ class ASH_EXPORT OverflowBubbleView : public views::BubbleDialogDelegateView,
void UpdateShelfBackground(SkColor color) override;
Shelf* shelf_;
views::View* shelf_view_; // Owned by views hierarchy.
ShelfView* shelf_view_; // Owned by views hierarchy.
gfx::Vector2d scroll_offset_;
ShelfBackgroundAnimator background_animator_;
......
......@@ -5,6 +5,7 @@
#include "ash/shelf/overflow_bubble_view_test_api.h"
#include "ash/shelf/overflow_bubble_view.h"
#include "ash/shelf/shelf_view.h"
namespace ash {
......
......@@ -118,8 +118,8 @@ class BoundsAnimatorDisabler {
// the ViewModel.
class ShelfFocusSearch : public views::FocusSearch {
public:
explicit ShelfFocusSearch(views::ViewModel* view_model)
: FocusSearch(nullptr, true, true), view_model_(view_model) {}
explicit ShelfFocusSearch(ShelfView* shelf_view)
: FocusSearch(nullptr, true, true), shelf_view_(shelf_view) {}
~ShelfFocusSearch() override = default;
// views::FocusSearch:
......@@ -131,7 +131,8 @@ class ShelfFocusSearch : public views::FocusSearch {
FocusSearch::AnchoredDialogPolicy can_go_into_anchored_dialog,
views::FocusTraversable** focus_traversable,
View** focus_traversable_view) override {
int index = view_model_->GetIndexOfView(starting_view);
views::ViewModel* view_model = shelf_view_->view_model();
int index = view_model->GetIndexOfView(starting_view);
// The back button (item with index 0 on the model) only exists in tablet
// mode, so punt focus to the app list button (item with index 1 on the
// model).
......@@ -140,23 +141,48 @@ class ShelfFocusSearch : public views::FocusSearch {
++index;
// Increment or decrement index based on the cycle, unless we are at either
// edge, then we loop to the back or front. Skip the back button (item with
// index 0) when not in tablet mode.
// edge, then we loop to the back or front.
const bool is_overflow_shelf = shelf_view_->is_overflow_mode();
const bool overflow_shown =
is_overflow_shelf ? shelf_view_->main_shelf()->IsShowingOverflowBubble()
: shelf_view_->IsShowingOverflowBubble();
if (search_direction == FocusSearch::SearchDirection::kBackwards) {
--index;
if (index < 0 || (index == 0 && !tablet_mode))
index = view_model_->view_size() - 1;
if (index < 0 || (index == 0 && !tablet_mode)) {
index = overflow_shown ? view_model->view_size() - 1
: shelf_view_->last_visible_index();
}
} else {
++index;
if (index >= view_model_->view_size())
index = tablet_mode ? 0 : 1;
if (overflow_shown && index >= view_model->view_size()) {
index = 0;
} else if (!overflow_shown && index > shelf_view_->last_visible_index()) {
index = is_overflow_shelf ? shelf_view_->first_visible_index() : 0;
}
// Skip the back button (item with index 0) when not in tablet mode.
if (!tablet_mode && index == 0)
index = 1;
}
return view_model_->view_at(index);
if (overflow_shown) {
// Switch to the other shelf if |index| is not visible on this shelf.
if (index < shelf_view_->first_visible_index() ||
index > shelf_view_->last_visible_index()) {
ShelfView* new_shelf_view =
is_overflow_shelf
? shelf_view_->main_shelf()
: shelf_view_->overflow_bubble()->bubble_view()->shelf_view();
if (is_overflow_shelf)
shelf_view_->shelf_widget()->set_activated_from_overflow_bubble(true);
return new_shelf_view->view_model()->view_at(index);
}
}
return view_model->view_at(index);
}
private:
views::ViewModel* view_model_;
ShelfView* shelf_view_;
DISALLOW_COPY_AND_ASSIGN(ShelfFocusSearch);
};
......@@ -301,10 +327,10 @@ ShelfView::ShelfView(ShelfModel* model, Shelf* shelf, ShelfWidget* shelf_widget)
DCHECK(model_);
DCHECK(shelf_);
DCHECK(shelf_widget_);
bounds_animator_.reset(new views::BoundsAnimator(this));
bounds_animator_ = std::make_unique<views::BoundsAnimator>(this);
bounds_animator_->AddObserver(this);
set_context_menu_controller(this);
focus_search_.reset(new ShelfFocusSearch(view_model_.get()));
focus_search_ = std::make_unique<ShelfFocusSearch>(this);
}
ShelfView::~ShelfView() {
......@@ -885,7 +911,7 @@ void ShelfView::CalculateIdealBounds(gfx::Rect* overflow_bounds) const {
? kShelfButtonSpacingNewUi
: kShelfButtonSpacing;
const int available_size = shelf_->PrimaryAxisValue(width(), height());
const int available_size = 400;
const int first_panel_index = model_->FirstPanelIndex();
const int last_button_index = first_panel_index - 1;
......@@ -1029,7 +1055,7 @@ void ShelfView::CalculateIdealBounds(gfx::Rect* overflow_bounds) const {
overflow_bounds->set_x(x);
overflow_bounds->set_y(y);
if (overflow_bubble_.get() && overflow_bubble_->IsShowing())
UpdateOverflowRange(overflow_bubble_->shelf_view());
UpdateOverflowRange(overflow_bubble_->bubble_view()->shelf_view());
} else {
if (overflow_bubble_)
overflow_bubble_->Hide();
......@@ -1214,7 +1240,7 @@ void ShelfView::EndDragOnOtherShelf(bool cancel) {
main_shelf_->EndDrag(cancel);
} else {
DCHECK(overflow_bubble_->IsShowing());
overflow_bubble_->shelf_view()->EndDrag(cancel);
overflow_bubble_->bubble_view()->shelf_view()->EndDrag(cancel);
}
}
......@@ -1270,17 +1296,18 @@ bool ShelfView::HandleRipOffDrag(const ui::LocatedEvent& event) {
}
} else if (!is_overflow_mode() && overflow_bubble_ &&
overflow_bubble_->IsShowing() &&
overflow_bubble_->shelf_view()
overflow_bubble_->bubble_view()
->shelf_view()
->GetBoundsForDragInsertInScreen()
.Contains(screen_location)) {
// The item was dragged from the main shelf to the overflow shelf.
if (!dragged_to_another_shelf_) {
dragged_to_another_shelf_ = true;
drag_image_->SetOpacity(1.0f);
overflow_bubble_->shelf_view()->StartDrag(dragged_app_id,
screen_location);
overflow_bubble_->bubble_view()->shelf_view()->StartDrag(
dragged_app_id, screen_location);
} else {
overflow_bubble_->shelf_view()->Drag(screen_location);
overflow_bubble_->bubble_view()->shelf_view()->Drag(screen_location);
}
} else if (dragged_to_another_shelf_) {
// Makes the |drag_image_| partially disappear again.
......@@ -1293,7 +1320,7 @@ bool ShelfView::HandleRipOffDrag(const ui::LocatedEvent& event) {
// back. If the overflow bubble is showing, a copy of the dragged item
// will appear at the end of the overflow shelf. Decrement the last
// visible index of the overflow shelf to hide this copy.
overflow_bubble_->shelf_view()->last_visible_index_--;
overflow_bubble_->bubble_view()->shelf_view()->last_visible_index_--;
}
bounds_animator_->StopAnimatingView(drag_view_);
......@@ -1314,7 +1341,8 @@ bool ShelfView::HandleRipOffDrag(const ui::LocatedEvent& event) {
main_shelf_->GetBoundsForDragInsertInScreen().Contains(screen_location));
dragged_off_shelf |= (!is_overflow_mode() && overflow_bubble_ &&
overflow_bubble_->IsShowing() &&
overflow_bubble_->shelf_view()
overflow_bubble_->bubble_view()
->shelf_view()
->GetBoundsForDragInsertInScreen()
.Contains(screen_location));
......@@ -1338,7 +1366,7 @@ bool ShelfView::HandleRipOffDrag(const ui::LocatedEvent& event) {
// will appear at the end of the overflow shelf. Decrement the last
// visible index of the overflow shelf to hide this copy.
if (overflow_bubble_ && overflow_bubble_->IsShowing())
overflow_bubble_->shelf_view()->last_visible_index_--;
overflow_bubble_->bubble_view()->shelf_view()->last_visible_index_--;
} else if (is_overflow_mode()) {
// Overflow bubble should be shrunk when an item is ripped off.
PreferredSizeChanged();
......@@ -1822,7 +1850,7 @@ void ShelfView::ShelfItemRemoved(int model_index, const ShelfItem& old_item) {
if (overflow_bubble_ && overflow_bubble_->IsShowing()) {
last_hidden_index_ =
std::min(last_hidden_index_, view_model_->view_size() - 1);
UpdateOverflowRange(overflow_bubble_->shelf_view());
UpdateOverflowRange(overflow_bubble_->bubble_view()->shelf_view());
}
if (view->visible()) {
......
......@@ -200,6 +200,20 @@ class ASH_EXPORT ShelfView : public views::View,
const ShelfButton* drag_view() const { return drag_view_; }
// Returns true when this ShelfView is used for Overflow Bubble.
// In this mode, it does not show app list, panel and overflow button.
// Note:
// * When Shelf can contain only one item (overflow button) due to very
// small resolution screen, overflow bubble can show app list and panel
// button.
bool is_overflow_mode() const { return overflow_mode_; }
int first_visible_index() const { return first_visible_index_; }
int last_visible_index() const { return last_visible_index_; }
ShelfWidget* shelf_widget() const { return shelf_widget_; }
OverflowBubble* overflow_bubble() { return overflow_bubble_.get(); }
views::ViewModel* view_model() { return view_model_.get(); }
private:
friend class ShelfViewTestAPI;
......@@ -215,14 +229,6 @@ class ASH_EXPORT ShelfView : public views::View,
// Minimum distance before drag starts.
static const int kMinimumDragDistance;
// Returns true when this ShelfView is used for Overflow Bubble.
// In this mode, it does not show app list, panel and overflow button.
// Note:
// * When Shelf can contain only one item (overflow button) due to very
// small resolution screen, overflow bubble can show app list and panel
// button.
bool is_overflow_mode() const { return overflow_mode_; }
bool dragging() const { return drag_pointer_ != NONE; }
// Sets the bounds of each view to its ideal bounds.
......
This diff is collapsed.
......@@ -16,6 +16,8 @@
#include "ash/session/session_controller.h"
#include "ash/shelf/app_list_button.h"
#include "ash/shelf/login_shelf_view.h"
#include "ash/shelf/overflow_bubble.h"
#include "ash/shelf/overflow_bubble_view.h"
#include "ash/shelf/shelf.h"
#include "ash/shelf/shelf_background_animator_observer.h"
#include "ash/shelf/shelf_constants.h"
......@@ -25,6 +27,7 @@
#include "ash/system/status_area_layout_manager.h"
#include "ash/system/status_area_widget.h"
#include "ash/system/tray/system_tray.h"
#include "ash/wm/window_util.h"
#include "base/command_line.h"
#include "ui/compositor/layer.h"
#include "ui/compositor/scoped_layer_animation_settings.h"
......@@ -148,6 +151,22 @@ void ShelfWidget::DelegateView::SetParentLayer(ui::Layer* layer) {
}
bool ShelfWidget::DelegateView::CanActivate() const {
// Allow activations coming from the overflow bubble if it is currently shown
// and active.
aura::Window* active_window = wm::GetActiveWindow();
aura::Window* bubble_window = nullptr;
aura::Window* shelf_window = shelf_widget_->GetNativeWindow();
if (shelf_widget_->IsShowingOverflowBubble()) {
bubble_window = shelf_widget_->shelf_view_->overflow_bubble()
->bubble_view()
->GetWidget()
->GetNativeWindow();
}
if (active_window &&
(active_window == bubble_window || active_window == shelf_window)) {
return true;
}
// Only allow activation from the focus cycler, not from mouse events, etc.
return focus_cycler_ && focus_cycler_->widget_activating() == GetWidget();
}
......@@ -374,10 +393,19 @@ void ShelfWidget::set_default_last_focusable_child(
void ShelfWidget::OnWidgetActivationChanged(views::Widget* widget,
bool active) {
if (active)
if (active) {
// Do not focus the default element if the widget activation came from the
// overflow bubble focus cycling. The setter of
// |activated_from_overflow_bubble_| should handle focusing the correct
// view.
if (activated_from_overflow_bubble_) {
activated_from_overflow_bubble_ = false;
return;
}
delegate_view_->SetPaneFocusAndFocusDefault();
else
} else {
delegate_view_->GetFocusManager()->ClearFocus();
}
}
void ShelfWidget::UpdateShelfItemBackground(SkColor color) {
......
......@@ -128,6 +128,10 @@ class ASH_EXPORT ShelfWidget : public views::Widget,
return login_shelf_view_;
}
void set_activated_from_overflow_bubble(bool val) {
activated_from_overflow_bubble_ = val;
}
private:
class DelegateView;
friend class DelegateView;
......@@ -157,6 +161,11 @@ class ASH_EXPORT ShelfWidget : public views::Widget,
// Owned by the views hierarchy.
LoginShelfView* const login_shelf_view_;
// Set to true when the widget is activated from the shelf overflow bubble.
// Do not focus the default element in this case. This should be set when
// cycling focus from the overflow bubble to the main shelf.
bool activated_from_overflow_bubble_ = false;
ShelfBackgroundAnimator background_animator_;
ScopedSessionObserver scoped_session_observer_;
......
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