DeskButton: Add focus ring and chromevox support
Adds highlight support and focus traversability to the desk button. This also accounts for an overview activation issue that happens when the desk button is focused when overview starts by deactivating any active shelf window before overview starts. Bug: b/280814722 Change-Id: I7ae18840b36f3299a3873911e2a37d9cf6bf8980 Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/4607543 Reviewed-by: Toni Barzic <tbarzic@chromium.org> Reviewed-by: Yongshun Liu <yongshun@chromium.org> Reviewed-by: Daniel Andersson <dandersson@chromium.org> Reviewed-by: Ben Becker <benbecker@chromium.org> Commit-Queue: Andrew Pantera <andp@chromium.org> Cr-Commit-Position: refs/heads/main@{#1172020}
This commit is contained in:

committed by
Chromium LUCI CQ

parent
1e18e7fb17
commit
7de823da2c
ash
ash_strings.grd
ash_strings_grd
IDS_SHELF_DESK_BUTTON_TITLE.png.sha1IDS_SHELF_NEXT_DESK_BUTTON_TITLE.png.sha1IDS_SHELF_NEXT_DESK_BUTTON_TITLE_16_DESKS.png.sha1IDS_SHELF_PREVIOUS_DESK_BUTTON_TITLE.png.sha1IDS_SHELF_PREVIOUS_DESK_BUTTON_TITLE_16_DESKS.png.sha1
shelf
desk_button_widget.ccdesk_button_widget.hshelf_focus_cycler.ccshelf_focus_cycler.hshelf_layout_manager.ccshelf_widget.cc
wm
desks
@ -277,6 +277,22 @@ Style notes:
|
||||
<message name="IDS_SHELF_LAUNCHER_NUDGE_TEXT" desc="The text shown to users beside the home button that shows the launcher UI when the device is in clamshell mode. This nudge text guides them to click on the home button to see all the apps in the launcher UI.">
|
||||
See all apps
|
||||
</message>
|
||||
<message name="IDS_SHELF_PREVIOUS_DESK_BUTTON_TITLE" desc="The title used for the previous desk button within the desk button on the shelf.">
|
||||
Previous Desk: <ph name="PREVIOUS_DESK_NAME">$1<ex>Desk 1</ex></ph>, use Search+Space or keyboard shortcut: Shift+Search+<ph name="PREVIOUS_DESK_INDEX">$2<ex>1</ex></ph> to activate.
|
||||
</message>
|
||||
<message name="IDS_SHELF_PREVIOUS_DESK_BUTTON_TITLE_16_DESKS" desc="The title used for the previous desk button within the desk button on the shelf when that desk is beyond the 8th desk.">
|
||||
Previous Desk: <ph name="PREVIOUS_DESK_NAME">$1<ex>Desk 12</ex></ph>, use Search+Space to activate.
|
||||
</message>
|
||||
<message name="IDS_SHELF_DESK_BUTTON_TITLE" desc="The title used for the desk button on the shelf.">
|
||||
Current Desk: <ph name="CURRENT_DESK_NAME">$1<ex>Desk 2</ex></ph>, use Search+Space to open all desks menu.
|
||||
</message>
|
||||
<message name="IDS_SHELF_NEXT_DESK_BUTTON_TITLE" desc="The title used for the next desk button within the desk button on the shelf.">
|
||||
Next Desk: <ph name="NEXT_DESK_NAME">$1<ex>Desk 3</ex></ph>, use Search+Space or keyboard shortcut: Shift+Search+<ph name="NEXT_DESK_INDEX">$2<ex>3</ex></ph> to activate.
|
||||
</message>
|
||||
<message name="IDS_SHELF_NEXT_DESK_BUTTON_TITLE_16_DESKS" desc="The title used for the next desk button within the desk button on the shelf when that desk is beyond the 8th desk.">
|
||||
Next Desk: <ph name="NEXT_DESK_NAME">$1<ex>Desk 14</ex></ph>, use Search+Space to activate.
|
||||
</message>
|
||||
|
||||
|
||||
<!-- Status tray items -->
|
||||
<message name="IDS_ASH_STATUS_TRAY_ACCESSIBLE_DESCRIPTION" is_accessibility_with_no_ui="true" desc="The accessible description of the status tray and the information on it.">
|
||||
|
1
ash/ash_strings_grd/IDS_SHELF_DESK_BUTTON_TITLE.png.sha1
Normal file
1
ash/ash_strings_grd/IDS_SHELF_DESK_BUTTON_TITLE.png.sha1
Normal file
@ -0,0 +1 @@
|
||||
7cd27ea32853e41b4398004fe2dfa2d9939b438b
|
@ -0,0 +1 @@
|
||||
9779b0564eaba7361dd7f0f33e7300437224bc3f
|
@ -0,0 +1 @@
|
||||
fccfc6a0870cc6a6621843b07bd187d30836816f
|
@ -0,0 +1 @@
|
||||
e98b80cc9d6aa1c9da35e3d6535f88625b81b071
|
@ -0,0 +1 @@
|
||||
400267ebd23a9cd1c926baa225b8ce6406f968e2
|
@ -9,6 +9,7 @@
|
||||
#include "ash/public/cpp/shelf_types.h"
|
||||
#include "ash/screen_util.h"
|
||||
#include "ash/shelf/scrollable_shelf_view.h"
|
||||
#include "ash/shelf/shelf_focus_cycler.h"
|
||||
#include "ash/shelf/shelf_layout_manager.h"
|
||||
#include "ash/shelf/shelf_navigation_widget.h"
|
||||
#include "ash/shell.h"
|
||||
@ -54,6 +55,9 @@ class DeskButtonWidget::DelegateView : public views::WidgetDelegateView {
|
||||
// views::WidgetDelegateView:
|
||||
bool CanActivate() const override;
|
||||
|
||||
// views::View:
|
||||
bool AcceleratorPressed(const ui::Accelerator& accelerator) override;
|
||||
|
||||
// Notifies the `desk_button_` to update layout and values based on the new
|
||||
// expanded state.
|
||||
void OnExpandedStateUpdate(bool expanded);
|
||||
@ -75,6 +79,7 @@ void DeskButtonWidget::DelegateView::Init(
|
||||
desk_button_ = GetContentsView()->AddChildView(
|
||||
std::make_unique<DeskButton>(desk_button_widget_));
|
||||
OnExpandedStateUpdate(desk_button_widget->is_expanded());
|
||||
AddAccelerator(ui::Accelerator(ui::VKEY_ESCAPE, ui::EF_NONE));
|
||||
}
|
||||
|
||||
bool DeskButtonWidget::DelegateView::CanActivate() const {
|
||||
@ -83,6 +88,13 @@ bool DeskButtonWidget::DelegateView::CanActivate() const {
|
||||
return Shell::Get()->focus_cycler()->widget_activating() == GetWidget();
|
||||
}
|
||||
|
||||
bool DeskButtonWidget::DelegateView::AcceleratorPressed(
|
||||
const ui::Accelerator& accelerator) {
|
||||
CHECK_EQ(accelerator.key_code(), ui::VKEY_ESCAPE);
|
||||
GetWidget()->Deactivate();
|
||||
return true;
|
||||
}
|
||||
|
||||
void DeskButtonWidget::DelegateView::OnExpandedStateUpdate(bool expanded) {
|
||||
desk_button_->OnExpandedStateUpdate(expanded);
|
||||
}
|
||||
@ -139,6 +151,29 @@ gfx::Rect DeskButtonWidget::GetTargetExpandedBounds() const {
|
||||
return current_bounds;
|
||||
}
|
||||
|
||||
void DeskButtonWidget::MaybeFocusOut(bool reverse) {
|
||||
// The focus order is the previous desk button, the desk button, then the next
|
||||
// desk button.
|
||||
views::View* views[] = {GetDeskButton()->prev_desk_button(), GetDeskButton(),
|
||||
GetDeskButton()->next_desk_button()};
|
||||
views::View* focused_view = GetFocusManager()->GetFocusedView();
|
||||
|
||||
const int count = std::size(views);
|
||||
int focused = base::ranges::find(views, focused_view) - std::begin(views);
|
||||
// This method is only called if the desk button widget already has focus.
|
||||
CHECK(focused != count);
|
||||
|
||||
int next = focused + (reverse ? -1 : 1);
|
||||
// Only the previous and next desk buttons can be disabled. If they are next,
|
||||
// the current focus is on the desk button and focus should leave the desk
|
||||
// button widget.
|
||||
if (next < 0 || next >= count || !views[next]->GetEnabled()) {
|
||||
FocusOut(reverse);
|
||||
return;
|
||||
}
|
||||
views[next]->RequestFocus();
|
||||
}
|
||||
|
||||
bool DeskButtonWidget::ShouldBeVisible() const {
|
||||
const ShelfLayoutManager* layout_manager = shelf_->shelf_layout_manager();
|
||||
const OverviewController* overview_controller =
|
||||
@ -262,4 +297,27 @@ gfx::Point DeskButtonWidget::GetCenteredOrigin() const {
|
||||
navigation_bounds.y() + navigation_bounds.height() + shelf_padding.top());
|
||||
}
|
||||
|
||||
void DeskButtonWidget::FocusOut(bool reverse) {
|
||||
GetDeskButton()->MaybeContract();
|
||||
shelf_->shelf_focus_cycler()->FocusOut(reverse, SourceView::kDeskButton);
|
||||
}
|
||||
|
||||
bool DeskButtonWidget::OnNativeWidgetActivationChanged(bool active) {
|
||||
if (!Widget::OnNativeWidgetActivationChanged(active)) {
|
||||
return false;
|
||||
}
|
||||
if (active) {
|
||||
if (default_last_focusable_child_ &&
|
||||
GetDeskButton()->next_desk_button()->GetEnabled()) {
|
||||
GetDeskButton()->next_desk_button()->RequestFocus();
|
||||
} else if (!default_last_focusable_child_ &&
|
||||
GetDeskButton()->prev_desk_button()->GetEnabled()) {
|
||||
GetDeskButton()->prev_desk_button()->RequestFocus();
|
||||
} else {
|
||||
GetDeskButton()->RequestFocus();
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace ash
|
||||
|
@ -49,6 +49,11 @@ class ASH_EXPORT DeskButtonWidget : public ShelfComponent,
|
||||
// the current positioning.
|
||||
gfx::Rect GetTargetExpandedBounds() const;
|
||||
|
||||
// Depending on what child view has focus, either focus out of the desk
|
||||
// button, or pass the focus to the next view. `reverse` indicates backward
|
||||
// focusing, otherwise forward focusing.
|
||||
void MaybeFocusOut(bool reverse);
|
||||
|
||||
// Whether the desk button should currently be visible.
|
||||
bool ShouldBeVisible() const;
|
||||
|
||||
@ -73,6 +78,10 @@ class ASH_EXPORT DeskButtonWidget : public ShelfComponent,
|
||||
|
||||
DeskButton* GetDeskButton() const;
|
||||
|
||||
void set_default_last_focusable_child(bool default_last_focusable_child) {
|
||||
default_last_focusable_child_ = default_last_focusable_child;
|
||||
}
|
||||
|
||||
private:
|
||||
class DelegateView;
|
||||
|
||||
@ -80,6 +89,13 @@ class ASH_EXPORT DeskButtonWidget : public ShelfComponent,
|
||||
// centered in the shelf.
|
||||
gfx::Point GetCenteredOrigin() const;
|
||||
|
||||
// Sets the desk button to not be hovered and set un-expanded if necessary
|
||||
// before focusing out.
|
||||
void FocusOut(bool reverse);
|
||||
|
||||
// views::Widget:
|
||||
bool OnNativeWidgetActivationChanged(bool active) override;
|
||||
|
||||
raw_ptr<DelegateView, DanglingUntriaged> delegate_view_ = nullptr;
|
||||
|
||||
gfx::Rect target_bounds_;
|
||||
@ -87,6 +103,10 @@ class ASH_EXPORT DeskButtonWidget : public ShelfComponent,
|
||||
raw_ptr<Shelf> const shelf_;
|
||||
bool is_horizontal_shelf_;
|
||||
bool is_expanded_;
|
||||
|
||||
// When true, the default focus of the desk button widget is the last
|
||||
// focusable child.
|
||||
bool default_last_focusable_child_ = false;
|
||||
};
|
||||
|
||||
} // namespace ash
|
||||
|
@ -5,6 +5,7 @@
|
||||
#include "ash/shelf/shelf_focus_cycler.h"
|
||||
|
||||
#include "ash/focus_cycler.h"
|
||||
#include "ash/shelf/desk_button_widget.h"
|
||||
#include "ash/shelf/login_shelf_view.h"
|
||||
#include "ash/shelf/login_shelf_widget.h"
|
||||
#include "ash/shelf/scrollable_shelf_view.h"
|
||||
@ -16,6 +17,7 @@
|
||||
#include "ash/system/status_area_widget.h"
|
||||
#include "ash/system/status_area_widget_delegate.h"
|
||||
#include "ash/system/tray/system_tray_notifier.h"
|
||||
#include "ash/wm/desks/desk_button/desk_button.h"
|
||||
|
||||
namespace ash {
|
||||
|
||||
@ -26,14 +28,22 @@ void ShelfFocusCycler::FocusOut(bool reverse, SourceView source_view) {
|
||||
// simple cycling logic instead of a long switch.
|
||||
switch (source_view) {
|
||||
case SourceView::kShelfNavigationView:
|
||||
if (reverse)
|
||||
if (reverse) {
|
||||
FocusStatusArea(reverse);
|
||||
else
|
||||
} else {
|
||||
FocusDeskButton(reverse);
|
||||
}
|
||||
break;
|
||||
case SourceView::kDeskButton:
|
||||
if (reverse) {
|
||||
FocusNavigation(reverse);
|
||||
} else {
|
||||
FocusShelf(reverse);
|
||||
}
|
||||
break;
|
||||
case SourceView::kShelfView:
|
||||
if (reverse)
|
||||
FocusNavigation(reverse);
|
||||
FocusDeskButton(reverse);
|
||||
else
|
||||
FocusStatusArea(reverse);
|
||||
break;
|
||||
@ -71,6 +81,23 @@ void ShelfFocusCycler::FocusNavigation(bool last_element) {
|
||||
Shell::Get()->focus_cycler()->FocusWidget(navigation_widget);
|
||||
}
|
||||
|
||||
void ShelfFocusCycler::FocusDeskButton(bool last_element) {
|
||||
DeskButtonWidget* desk_button_widget = shelf_->desk_button_widget();
|
||||
if (features::IsDeskButtonEnabled() &&
|
||||
desk_button_widget->ShouldBeVisible()) {
|
||||
desk_button_widget->set_default_last_focusable_child(last_element);
|
||||
if (!shelf_->IsHorizontalAlignment()) {
|
||||
desk_button_widget->SetExpanded(true);
|
||||
}
|
||||
desk_button_widget->GetDeskButton()->SetFocused(true);
|
||||
Shell::Get()->focus_cycler()->FocusWidget(desk_button_widget);
|
||||
} else if (last_element) {
|
||||
FocusNavigation(last_element);
|
||||
} else {
|
||||
FocusShelf(last_element);
|
||||
}
|
||||
}
|
||||
|
||||
void ShelfFocusCycler::FocusShelf(bool last_element) {
|
||||
if (shelf_->shelf_widget()->GetLoginShelfView()->GetVisible()) {
|
||||
// TODO(https://crbug.com/1343114): refactor the code below after the login
|
||||
|
@ -17,6 +17,7 @@ enum class SourceView {
|
||||
kShelfNavigationView = 0,
|
||||
kShelfView,
|
||||
kStatusAreaView,
|
||||
kDeskButton,
|
||||
};
|
||||
|
||||
// ShelfFocusCycler handles the special focus transitions from the Login UI,
|
||||
@ -38,6 +39,9 @@ class ASH_EXPORT ShelfFocusCycler {
|
||||
// Focuses the navigation widget (back and home buttons).
|
||||
void FocusNavigation(bool last_element);
|
||||
|
||||
// Focuses the desk button widget.
|
||||
void FocusDeskButton(bool last_element);
|
||||
|
||||
// Focuses the shelf widget (app shortcuts).
|
||||
void FocusShelf(bool last_element);
|
||||
|
||||
|
@ -1307,6 +1307,13 @@ void ShelfLayoutManager::OnSplitViewStateChanged(
|
||||
}
|
||||
|
||||
void ShelfLayoutManager::OnOverviewModeWillStart() {
|
||||
// If a shelf window is active before overview starts, deactivate it to avoid
|
||||
// overview window activation issues.
|
||||
// TODO(b/289287310): Consolidate behavior: shelf and overview.
|
||||
auto* active_window = window_util::GetActiveWindow();
|
||||
if (active_window && IsShelfWindow(active_window)) {
|
||||
wm::DeactivateWindow(active_window);
|
||||
}
|
||||
overview_mode_will_start_ = true;
|
||||
}
|
||||
|
||||
@ -2416,13 +2423,22 @@ bool ShelfLayoutManager::IsShelfWindow(aura::Window* window) {
|
||||
(login_shelf_window && login_shelf_window->Contains(window));
|
||||
}
|
||||
|
||||
// Calculate whether `window` is contained by the desk button widget.
|
||||
bool window_in_desk_button_widget = false;
|
||||
if (features::IsDeskButtonEnabled()) {
|
||||
const aura::Window* desk_button_window =
|
||||
shelf_->desk_button_widget()->GetNativeWindow();
|
||||
window_in_desk_button_widget =
|
||||
(desk_button_window && desk_button_window->Contains(window));
|
||||
}
|
||||
|
||||
return (shelf_window && shelf_window->Contains(window)) ||
|
||||
(navigation_window && navigation_window->Contains(window)) ||
|
||||
(hotseat_window && hotseat_window->Contains(window)) ||
|
||||
(status_area_window && status_area_window->Contains(window)) ||
|
||||
(drag_handle_nudge_window &&
|
||||
drag_handle_nudge_window->Contains(window)) ||
|
||||
window_in_login_shelf_widget;
|
||||
window_in_login_shelf_widget || window_in_desk_button_widget;
|
||||
}
|
||||
|
||||
bool ShelfLayoutManager::IsStatusAreaWindow(aura::Window* window) {
|
||||
|
@ -808,6 +808,9 @@ void ShelfWidget::Shutdown() {
|
||||
Shell::Get()->focus_cycler()->RemoveWidget(shelf_->status_area_widget());
|
||||
Shell::Get()->focus_cycler()->RemoveWidget(navigation_widget());
|
||||
Shell::Get()->focus_cycler()->RemoveWidget(hotseat_widget());
|
||||
if (features::IsDeskButtonEnabled()) {
|
||||
Shell::Get()->focus_cycler()->RemoveWidget(desk_button_widget());
|
||||
}
|
||||
|
||||
// Don't need to update the shelf background during shutdown.
|
||||
background_animator_.RemoveObserver(delegate_view_);
|
||||
@ -838,6 +841,9 @@ void ShelfWidget::PostCreateShelf() {
|
||||
|
||||
// Add widgets to |focus_cycler| in the desired focus order in LTR.
|
||||
focus_cycler->AddWidget(navigation_widget());
|
||||
if (features::IsDeskButtonEnabled()) {
|
||||
focus_cycler->AddWidget(desk_button_widget());
|
||||
}
|
||||
hotseat_widget()->SetFocusCycler(focus_cycler);
|
||||
focus_cycler->AddWidget(status_area_widget());
|
||||
|
||||
|
@ -10,7 +10,10 @@
|
||||
#include "ash/screen_util.h"
|
||||
#include "ash/shelf/desk_button_widget.h"
|
||||
#include "ash/shelf/shelf.h"
|
||||
#include "ash/shelf/shelf_navigation_widget.h"
|
||||
#include "ash/shelf/shelf_widget.h"
|
||||
#include "ash/shell.h"
|
||||
#include "ash/strings/grit/ash_strings.h"
|
||||
#include "ash/style/typography.h"
|
||||
#include "ash/wm/desks/desk.h"
|
||||
#include "ash/wm/desks/desk_bar_controller.h"
|
||||
@ -19,6 +22,7 @@
|
||||
#include "base/i18n/case_conversion.h"
|
||||
#include "base/metrics/histogram_functions.h"
|
||||
#include "base/strings/string_number_conversions.h"
|
||||
#include "ui/base/l10n/l10n_util.h"
|
||||
#include "ui/base/metadata/metadata_impl_macros.h"
|
||||
#include "ui/chromeos/styles/cros_tokens_color_mappings.h"
|
||||
#include "ui/compositor/layer.h"
|
||||
@ -26,7 +30,9 @@
|
||||
#include "ui/gfx/canvas.h"
|
||||
#include "ui/gfx/geometry/rounded_corners_f.h"
|
||||
#include "ui/gfx/geometry/size.h"
|
||||
#include "ui/views/accessibility/view_accessibility.h"
|
||||
#include "ui/views/background.h"
|
||||
#include "ui/views/controls/highlight_path_generator.h"
|
||||
#include "ui/views/controls/label.h"
|
||||
#include "ui/views/layout/flex_layout.h"
|
||||
|
||||
@ -37,6 +43,8 @@ namespace {
|
||||
constexpr int kDeskSwitchButtonWidth = 20;
|
||||
constexpr int kDeskSwitchButtonHeight = 36;
|
||||
constexpr int kButtonCornerRadius = 12;
|
||||
constexpr int kFocusRingHaloInset = -3;
|
||||
constexpr int kMaxDeskShortcut = 8;
|
||||
|
||||
} // namespace
|
||||
|
||||
@ -88,6 +96,19 @@ void DeskSwitchButton::OnPaintBackground(gfx::Canvas* canvas) {
|
||||
}
|
||||
}
|
||||
|
||||
void DeskSwitchButton::AboutToRequestFocusFromTabTraversal(bool reverse) {
|
||||
Shelf::ForWindow(GetWidget()->GetNativeWindow())
|
||||
->desk_button_widget()
|
||||
->MaybeFocusOut(reverse);
|
||||
}
|
||||
|
||||
void DeskSwitchButton::OnViewBlurred(views::View* observed_view) {
|
||||
Shelf::ForWindow(GetWidget()->GetNativeWindow())
|
||||
->desk_button_widget()
|
||||
->GetDeskButton()
|
||||
->MaybeContract();
|
||||
}
|
||||
|
||||
BEGIN_METADATA(DeskSwitchButton, views::ImageButton)
|
||||
END_METADATA
|
||||
|
||||
@ -108,28 +129,37 @@ DeskButton::DeskButton(DeskButtonWidget* desk_button_widget)
|
||||
SetPaintToLayer();
|
||||
SetNotifyEnterExitOnChild(true);
|
||||
layer()->SetFillsBoundsOpaquely(false);
|
||||
SetFocusBehavior(views::View::FocusBehavior::ALWAYS);
|
||||
SetBackground(views::CreateThemedRoundedRectBackground(
|
||||
cros_tokens::kCrosSysSystemOnBaseOpaque, kButtonCornerRadius));
|
||||
SetLayoutManager(std::make_unique<views::FlexLayout>());
|
||||
SetLayoutManager(std::make_unique<views::FlexLayout>())
|
||||
->SetOrientation(views::LayoutOrientation::kHorizontal)
|
||||
.SetMainAxisAlignment(views::LayoutAlignment::kCenter)
|
||||
.SetDefault(
|
||||
views::kFlexBehaviorKey,
|
||||
views::FlexSpecification(views::MinimumFlexSizeRule::kPreferred,
|
||||
views::MaximumFlexSizeRule::kPreferred));
|
||||
|
||||
SetupFocus(this);
|
||||
|
||||
prev_desk_button_->SetImageModel(
|
||||
views::Button::STATE_NORMAL,
|
||||
ui::ImageModel::FromVectorIcon(kChevronSmallLeftIcon));
|
||||
prev_desk_button_->SetAccessibleName(u"Previous desk button");
|
||||
prev_desk_button_->SetAccessibleName(u"Previous desk:");
|
||||
prev_desk_button_->SetBackground(views::CreateThemedRoundedRectBackground(
|
||||
cros_tokens::kCrosSysHoverOnSubtle,
|
||||
gfx::RoundedCornersF{kButtonCornerRadius, 0, 0, kButtonCornerRadius},
|
||||
/*for_border_thickness=*/0));
|
||||
SetupFocus(prev_desk_button_);
|
||||
|
||||
next_desk_button_->SetImageModel(
|
||||
views::Button::STATE_NORMAL,
|
||||
ui::ImageModel::FromVectorIcon(kChevronSmallRightIcon));
|
||||
next_desk_button_->SetAccessibleName(u"Next desk button");
|
||||
next_desk_button_->SetAccessibleName(u"Next desk:");
|
||||
next_desk_button_->SetBackground(views::CreateThemedRoundedRectBackground(
|
||||
cros_tokens::kCrosSysHoverOnSubtle,
|
||||
gfx::RoundedCornersF{0, kButtonCornerRadius, kButtonCornerRadius, 0},
|
||||
/*for_border_thickness=*/0));
|
||||
SetupFocus(next_desk_button_);
|
||||
|
||||
CalculateDisplayNames(DesksController::Get()->active_desk());
|
||||
CHECK(!is_expanded_);
|
||||
@ -186,7 +216,7 @@ void DeskButton::SetActivation(bool is_activated) {
|
||||
!is_activated_);
|
||||
|
||||
if (!force_expanded_state_) {
|
||||
if (!is_activated_ && is_hovered_) {
|
||||
if (!is_activated_ && (is_hovered_ || is_focused_)) {
|
||||
desk_button_widget_->SetExpanded(true);
|
||||
} else {
|
||||
desk_button_widget_->SetExpanded(false);
|
||||
@ -204,6 +234,23 @@ void DeskButton::SetActivation(bool is_activated) {
|
||||
MaybeUpdateDeskSwitchButtonVisibility();
|
||||
}
|
||||
|
||||
void DeskButton::SetFocused(bool is_focused) {
|
||||
if (is_focused_ == is_focused) {
|
||||
return;
|
||||
}
|
||||
|
||||
is_focused_ = is_focused;
|
||||
MaybeUpdateDeskSwitchButtonVisibility();
|
||||
}
|
||||
|
||||
void DeskButton::MaybeContract() {
|
||||
SetFocused(HasFocus() || next_desk_button_->HasFocus() ||
|
||||
prev_desk_button_->HasFocus());
|
||||
if (!is_focused_ && !force_expanded_state_) {
|
||||
desk_button_widget_->SetExpanded(false);
|
||||
}
|
||||
}
|
||||
|
||||
std::u16string DeskButton::GetTitleForView(const views::View* view) {
|
||||
if (view == this) {
|
||||
return desk_name_;
|
||||
@ -229,6 +276,12 @@ void DeskButton::GetAccessibleNodeData(ui::AXNodeData* node_data) {
|
||||
if (GetAccessibleName().empty()) {
|
||||
node_data->SetNameExplicitlyEmpty();
|
||||
}
|
||||
|
||||
ShelfWidget* shelf_widget =
|
||||
Shelf::ForWindow(GetWidget()->GetNativeWindow())->shelf_widget();
|
||||
GetViewAccessibility().OverridePreviousFocus(
|
||||
shelf_widget->navigation_widget());
|
||||
GetViewAccessibility().OverrideNextFocus(shelf_widget);
|
||||
}
|
||||
|
||||
void DeskButton::OnMouseEntered(const ui::MouseEvent& event) {
|
||||
@ -266,7 +319,7 @@ void DeskButton::OnMouseExited(const ui::MouseEvent& event) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (is_expanded_ && !force_expanded_state_) {
|
||||
if (is_expanded_ && !force_expanded_state_ && !is_focused_) {
|
||||
// TODO(b/272383056): Would be better to have the widget register a
|
||||
// callback like "preferred_expanded_state_changed".
|
||||
desk_button_widget_->SetExpanded(false);
|
||||
@ -311,6 +364,16 @@ void DeskButton::OnDeskNameChanged(const Desk* desk,
|
||||
desk_name_label_->SetText(is_expanded_ ? desk_name_ : abbreviated_desk_name_);
|
||||
}
|
||||
|
||||
void DeskButton::AboutToRequestFocusFromTabTraversal(bool reverse) {
|
||||
Shelf::ForWindow(GetWidget()->GetNativeWindow())
|
||||
->desk_button_widget()
|
||||
->MaybeFocusOut(reverse);
|
||||
}
|
||||
|
||||
void DeskButton::OnViewBlurred(views::View* observed_view) {
|
||||
MaybeContract();
|
||||
}
|
||||
|
||||
void DeskButton::OnButtonPressed() {
|
||||
base::UmaHistogramBoolean(kDeskButtonPressesHistogramName, true);
|
||||
|
||||
@ -362,6 +425,9 @@ void DeskButton::CalculateDisplayNames(const Desk* desk) {
|
||||
abbreviated_desk_name_ += base::NumberToString16(
|
||||
DesksController::Get()->GetActiveDeskIndex() + 1);
|
||||
}
|
||||
|
||||
SetAccessibleName(
|
||||
l10n_util::GetStringFUTF16(IDS_SHELF_DESK_BUTTON_TITLE, desk_name_));
|
||||
}
|
||||
|
||||
void DeskButton::MaybeUpdateDeskSwitchButtonVisibility() {
|
||||
@ -374,11 +440,37 @@ void DeskButton::MaybeUpdateDeskSwitchButtonVisibility() {
|
||||
// There are certain conditions that indicate that we cannot show either of
|
||||
// the buttons.
|
||||
const bool can_show_desk_switch_buttons =
|
||||
is_hovered_ && !is_activated_ && is_expanded_;
|
||||
(is_hovered_ || is_focused_) && !is_activated_ && is_expanded_;
|
||||
prev_desk_button_->SetShown(can_show_desk_switch_buttons &&
|
||||
can_show_prev_desk_button);
|
||||
next_desk_button_->SetShown(can_show_desk_switch_buttons &&
|
||||
can_show_next_desk_button);
|
||||
|
||||
if (prev_desk_button_->GetEnabled()) {
|
||||
if ((active_desk_index - 1) <= kMaxDeskShortcut - 1) {
|
||||
prev_desk_button_->SetAccessibleName(l10n_util::GetStringFUTF16(
|
||||
IDS_SHELF_PREVIOUS_DESK_BUTTON_TITLE,
|
||||
desks_controller->GetDeskName(active_desk_index - 1),
|
||||
base::NumberToString16(active_desk_index)));
|
||||
} else {
|
||||
prev_desk_button_->SetAccessibleName(l10n_util::GetStringFUTF16(
|
||||
IDS_SHELF_PREVIOUS_DESK_BUTTON_TITLE_16_DESKS,
|
||||
desks_controller->GetDeskName(active_desk_index - 1)));
|
||||
}
|
||||
}
|
||||
|
||||
if (next_desk_button_->GetEnabled()) {
|
||||
if ((active_desk_index + 1) <= kMaxDeskShortcut - 1) {
|
||||
next_desk_button_->SetAccessibleName(l10n_util::GetStringFUTF16(
|
||||
IDS_SHELF_NEXT_DESK_BUTTON_TITLE,
|
||||
desks_controller->GetDeskName(active_desk_index + 1),
|
||||
base::NumberToString16(active_desk_index + 2)));
|
||||
} else {
|
||||
next_desk_button_->SetAccessibleName(l10n_util::GetStringFUTF16(
|
||||
IDS_SHELF_NEXT_DESK_BUTTON_TITLE_16_DESKS,
|
||||
desks_controller->GetDeskName(active_desk_index + 1)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void DeskButton::UpdateShelfAutoHideDisabler(
|
||||
@ -397,6 +489,14 @@ void DeskButton::UpdateShelfAutoHideDisabler(
|
||||
}
|
||||
}
|
||||
|
||||
void DeskButton::SetupFocus(views::Button* view) {
|
||||
view->SetInstallFocusRingOnFocus(true);
|
||||
view->SetFocusBehavior(views::View::FocusBehavior::ALWAYS);
|
||||
views::FocusRing::Get(view)->SetColorId(cros_tokens::kCrosSysFocusRing);
|
||||
views::InstallRoundRectHighlightPathGenerator(
|
||||
view, gfx::Insets(kFocusRingHaloInset), kButtonCornerRadius);
|
||||
}
|
||||
|
||||
BEGIN_METADATA(DeskButton, Button)
|
||||
END_METADATA
|
||||
|
||||
|
@ -41,6 +41,12 @@ class DeskSwitchButton : public views::ImageButton {
|
||||
void OnMouseExited(const ui::MouseEvent& event) override;
|
||||
void OnPaintBackground(gfx::Canvas* canvas) override;
|
||||
|
||||
// views::View:
|
||||
void AboutToRequestFocusFromTabTraversal(bool reverse) override;
|
||||
|
||||
// views::ViewObserver:
|
||||
void OnViewBlurred(views::View* observed_view) override;
|
||||
|
||||
bool hovered_ = false;
|
||||
};
|
||||
|
||||
@ -72,6 +78,12 @@ class ASH_EXPORT DeskButton : public views::Button,
|
||||
|
||||
void SetActivation(bool is_activated);
|
||||
|
||||
void SetFocused(bool is_focused);
|
||||
|
||||
// Changes whether the button is expanded and if the switch buttons are shown
|
||||
// depending on if the button is focused.
|
||||
void MaybeContract();
|
||||
|
||||
std::u16string GetTitleForView(const views::View* view);
|
||||
|
||||
views::Label* desk_name_label_for_test() const { return desk_name_label_; }
|
||||
@ -95,6 +107,12 @@ class ASH_EXPORT DeskButton : public views::Button,
|
||||
void OnDeskNameChanged(const Desk* desk,
|
||||
const std::u16string& new_name) override;
|
||||
|
||||
// views::View:
|
||||
void AboutToRequestFocusFromTabTraversal(bool reverse) override;
|
||||
|
||||
// views::ViewObserver:
|
||||
void OnViewBlurred(views::View* observed_view) override;
|
||||
|
||||
// Toggles the button's `is_activated_` state and adjusts the button's style
|
||||
// to reflect the new activation state.
|
||||
void OnButtonPressed();
|
||||
@ -117,6 +135,9 @@ class ASH_EXPORT DeskButton : public views::Button,
|
||||
absl::optional<Shelf::ScopedDisableAutoHide>& disabler,
|
||||
bool should_enable_shelf_auto_hide);
|
||||
|
||||
// Set up the focus ring, focus behavior, and highlight path for the buttons.
|
||||
void SetupFocus(views::Button* view);
|
||||
|
||||
// Widget that maintains this object.
|
||||
// TODO(b/272383056): Remove this and this class's dependence on accessing it.
|
||||
raw_ptr<DeskButtonWidget> desk_button_widget_;
|
||||
@ -144,6 +165,9 @@ class ASH_EXPORT DeskButton : public views::Button,
|
||||
// button has been pressed).
|
||||
bool is_activated_ = false;
|
||||
|
||||
// Tracks whether the button currently has focus.
|
||||
bool is_focused_ = false;
|
||||
|
||||
// Indicates that the shelf is horizontal and therefore the button should
|
||||
// always be expanded.
|
||||
bool force_expanded_state_ = false;
|
||||
|
@ -10991,6 +10991,48 @@ TEST_P(DeskButtonTest, BarBoundsWithWorkAreaChangeDockedMagnifier) {
|
||||
EXPECT_EQ(bounds, expected_bounds);
|
||||
}
|
||||
|
||||
// Tests that desk button tab order is correct in the shelf.
|
||||
TEST_P(DeskButtonTest, TabOrder) {
|
||||
NewDesk();
|
||||
NewDesk();
|
||||
auto* desks_controller = DesksController::Get();
|
||||
ASSERT_THAT(desks_controller->desks().size(), 3);
|
||||
ASSERT_THAT(desks_controller->GetActiveDeskIndex(), 0);
|
||||
|
||||
// Tabbing once should focus the shelf navigation widget.
|
||||
SendKey(ui::VKEY_TAB);
|
||||
|
||||
// One more tab should focus the desk button, then the next desk button. There
|
||||
// should be no previous desk button since we're on the first desk
|
||||
SendKey(ui::VKEY_TAB);
|
||||
ASSERT_TRUE(GetDeskButton()->HasFocus());
|
||||
SendKey(ui::VKEY_TAB);
|
||||
ASSERT_TRUE(GetDeskButton()->next_desk_button()->HasFocus());
|
||||
|
||||
// Tabbing in the other direction should work too.
|
||||
SendKey(ui::VKEY_TAB, ui::EF_SHIFT_DOWN);
|
||||
ASSERT_TRUE(GetDeskButton()->HasFocus());
|
||||
SendKey(ui::VKEY_TAB);
|
||||
ASSERT_TRUE(GetDeskButton()->next_desk_button()->HasFocus());
|
||||
|
||||
// Pressing the next desk button should take us to the next desk, and
|
||||
// immediately pressing enter again should take us to the last desk.
|
||||
DeskSwitchAnimationWaiter waiter;
|
||||
SendKey(ui::VKEY_RETURN);
|
||||
waiter.Wait();
|
||||
ASSERT_THAT(desks_controller->GetActiveDeskIndex(), 1);
|
||||
ASSERT_TRUE(GetDeskButton()->prev_desk_button()->GetEnabled());
|
||||
ASSERT_TRUE(GetDeskButton()->next_desk_button()->GetEnabled());
|
||||
DeskSwitchAnimationWaiter waiter2;
|
||||
SendKey(ui::VKEY_RETURN);
|
||||
waiter2.Wait();
|
||||
ASSERT_THAT(desks_controller->GetActiveDeskIndex(), 2);
|
||||
|
||||
// Focus should have been passed to the hotseat widget now that the next desk
|
||||
// button isn't visible.
|
||||
ASSERT_FALSE(GetDeskButton()->next_desk_button()->GetEnabled());
|
||||
}
|
||||
|
||||
// TODO(afakhry): Add more tests:
|
||||
// - Always on top windows are not tracked by any desk.
|
||||
// - Reusing containers when desks are removed and created.
|
||||
|
Reference in New Issue
Block a user