Commit b12b2d3b authored by Sarah Hu's avatar Sarah Hu Committed by Commit Bot

Create menu view for public account user.

Bug: 809635
Change-Id: Ifd6e73c24c0994f551442ce2d21355829e5454dd
Reviewed-on: https://chromium-review.googlesource.com/1022257
Commit-Queue: Xiaoyin Hu <xiaoyinh@chromium.org>
Reviewed-by: default avatarXiyuan Xia <xiyuan@chromium.org>
Reviewed-by: default avatarAchuith Bhandarkar <achuith@chromium.org>
Cr-Commit-Position: refs/heads/master@{#555866}
parent 441931f2
...@@ -334,6 +334,8 @@ component("ash") { ...@@ -334,6 +334,8 @@ component("ash") {
"login/ui/login_detachable_base_model.h", "login/ui/login_detachable_base_model.h",
"login/ui/login_expanded_public_account_view.cc", "login/ui/login_expanded_public_account_view.cc",
"login/ui/login_expanded_public_account_view.h", "login/ui/login_expanded_public_account_view.h",
"login/ui/login_menu_view.cc",
"login/ui/login_menu_view.h",
"login/ui/login_password_view.cc", "login/ui/login_password_view.cc",
"login/ui/login_password_view.h", "login/ui/login_password_view.h",
"login/ui/login_pin_view.cc", "login/ui/login_pin_view.cc",
......
...@@ -572,7 +572,37 @@ void LockContentsView::OnPublicSessionLocalesChanged( ...@@ -572,7 +572,37 @@ void LockContentsView::OnPublicSessionLocalesChanged(
void LockContentsView::OnPublicSessionKeyboardLayoutsChanged( void LockContentsView::OnPublicSessionKeyboardLayoutsChanged(
const AccountId& account_id, const AccountId& account_id,
const std::string& locale, const std::string& locale,
const std::vector<mojom::InputMethodItemPtr>& keyboard_layouts) {} const std::vector<mojom::InputMethodItemPtr>& keyboard_layouts) {
// Update expanded view because keyboard layouts is user interactive content.
// I.e. user selects a language locale and the corresponding keyboard layouts
// will be changed.
if (expanded_view_->visible() &&
expanded_view_->current_user()->basic_user_info->account_id ==
account_id) {
mojom::LoginUserInfoPtr user_info = expanded_view_->current_user()->Clone();
user_info->public_account_info->default_locale = locale;
user_info->public_account_info->keyboard_layouts =
mojo::Clone(keyboard_layouts);
expanded_view_->UpdateForUser(user_info);
}
LoginUserView* user_view = TryToFindUserView(account_id);
if (!user_view || !IsPublicAccountUser(user_view->current_user())) {
LOG(ERROR) << "Unable to find public account user.";
return;
}
mojom::LoginUserInfoPtr user_info = user_view->current_user()->Clone();
// Skip updating keyboard layouts if |locale| is not the default locale
// of the user. I.e. user changed the default locale in the expanded view,
// and it should be handled by expanded view.
if (user_info->public_account_info->default_locale != locale)
return;
user_info->public_account_info->keyboard_layouts =
mojo::Clone(keyboard_layouts);
user_view->UpdateForUser(user_info, false /*animate*/);
}
void LockContentsView::OnDetachableBasePairingStatusChanged( void LockContentsView::OnDetachableBasePairingStatusChanged(
DetachableBasePairingStatus pairing_status) { DetachableBasePairingStatus pairing_status) {
......
...@@ -416,8 +416,7 @@ TEST_F(LockScreenSanityTest, RemoveUser) { ...@@ -416,8 +416,7 @@ TEST_F(LockScreenSanityTest, RemoveUser) {
// dropdown does not result in an interactive/focusable view. // dropdown does not result in an interactive/focusable view.
focus_and_submit(secondary().dropdown()); focus_and_submit(secondary().dropdown());
EXPECT_TRUE(secondary().menu()); EXPECT_TRUE(secondary().menu());
EXPECT_FALSE( EXPECT_FALSE(HasFocusInAnyChildView(secondary().menu()->bubble_view()));
HasFocusInAnyChildView(secondary().menu()->bubble_view_for_test()));
// TODO(jdufault): Run submit() and then EXPECT_FALSE(secondary().menu()); to // TODO(jdufault): Run submit() and then EXPECT_FALSE(secondary().menu()); to
// verify that double-enter closes the bubble. // verify that double-enter closes the bubble.
...@@ -427,7 +426,7 @@ TEST_F(LockScreenSanityTest, RemoveUser) { ...@@ -427,7 +426,7 @@ TEST_F(LockScreenSanityTest, RemoveUser) {
// well as removes the user from the UI. // well as removes the user from the UI.
focus_and_submit(primary().dropdown()); focus_and_submit(primary().dropdown());
EXPECT_TRUE(primary().menu()); EXPECT_TRUE(primary().menu());
EXPECT_TRUE(HasFocusInAnyChildView(primary().menu()->bubble_view_for_test())); EXPECT_TRUE(HasFocusInAnyChildView(primary().menu()->bubble_view()));
EXPECT_CALL(*client, OnRemoveUserWarningShown()).Times(1); EXPECT_CALL(*client, OnRemoveUserWarningShown()).Times(1);
submit(); submit();
EXPECT_CALL(*client, RemoveUser(users()[0]->basic_user_info->account_id)) EXPECT_CALL(*client, RemoveUser(users()[0]->basic_user_info->account_id))
......
...@@ -13,6 +13,7 @@ ...@@ -13,6 +13,7 @@
#include "ash/login/ui/lock_screen.h" #include "ash/login/ui/lock_screen.h"
#include "ash/login/ui/lock_window.h" #include "ash/login/ui/lock_window.h"
#include "ash/login/ui/login_button.h" #include "ash/login/ui/login_button.h"
#include "ash/login/ui/login_menu_view.h"
#include "ash/login/ui/non_accessible_view.h" #include "ash/login/ui/non_accessible_view.h"
#include "ash/resources/vector_icons/vector_icons.h" #include "ash/resources/vector_icons/vector_icons.h"
#include "ash/shell.h" #include "ash/shell.h"
...@@ -404,7 +405,7 @@ void LoginBubble::ShowUserMenu(const base::string16& username, ...@@ -404,7 +405,7 @@ void LoginBubble::ShowUserMenu(const base::string16& username,
bool had_focus = bubble_opener_->HasFocus(); bool had_focus = bubble_opener_->HasFocus();
Show(); Show();
if (had_focus) { if (had_focus) {
// Try to focus the bubble view only if the tooltip was focused. // Try to focus the bubble view only if the bubble opener was focused.
bubble_view_->RequestFocus(); bubble_view_->RequestFocus();
} }
} }
...@@ -419,10 +420,36 @@ void LoginBubble::ShowTooltip(const base::string16& message, ...@@ -419,10 +420,36 @@ void LoginBubble::ShowTooltip(const base::string16& message,
Show(); Show();
} }
void LoginBubble::ShowSelectionMenu(LoginMenuView* menu,
LoginButton* bubble_opener) {
if (bubble_view_)
CloseImmediately();
flags_ = kFlagsNone;
bubble_opener_ = bubble_opener;
const bool had_focus = bubble_opener_->HasFocus();
// Transfer the ownership of |menu| to bubble widget.
bubble_view_ = menu;
Show();
if (had_focus) {
// Try to focus the bubble view only if the bubble opener was focused.
bubble_view_->RequestFocus();
}
}
void LoginBubble::Close() { void LoginBubble::Close() {
ScheduleAnimation(false /*visible*/); ScheduleAnimation(false /*visible*/);
} }
void LoginBubble::CloseImmediately() {
DCHECK(bubble_view_);
bubble_view_->layer()->GetAnimator()->RemoveObserver(this);
bubble_view_->GetWidget()->Close();
is_visible_ = false;
}
bool LoginBubble::IsVisible() { bool LoginBubble::IsVisible() {
return bubble_view_ && bubble_view_->GetWidget()->IsVisible(); return bubble_view_ && bubble_view_->GetWidget()->IsVisible();
} }
...@@ -490,13 +517,6 @@ void LoginBubble::Show() { ...@@ -490,13 +517,6 @@ void LoginBubble::Show() {
bubble_view_->NotifyAccessibilityEvent(ax::mojom::Event::kAlert, true); bubble_view_->NotifyAccessibilityEvent(ax::mojom::Event::kAlert, true);
} }
void LoginBubble::CloseImmediately() {
DCHECK(bubble_view_);
bubble_view_->layer()->GetAnimator()->RemoveObserver(this);
bubble_view_->GetWidget()->Close();
is_visible_ = false;
}
void LoginBubble::ProcessPressedEvent(const ui::LocatedEvent* event) { void LoginBubble::ProcessPressedEvent(const ui::LocatedEvent* event) {
if (!bubble_view_) if (!bubble_view_)
return; return;
......
...@@ -15,6 +15,7 @@ ...@@ -15,6 +15,7 @@
namespace ash { namespace ash {
class LoginButton; class LoginButton;
class LoginMenuView;
// A wrapper for the bubble view in the login screen. // A wrapper for the bubble view in the login screen.
// This class observes keyboard events, mouse clicks and touch down events // This class observes keyboard events, mouse clicks and touch down events
...@@ -58,10 +59,18 @@ class ASH_EXPORT LoginBubble : public views::WidgetObserver, ...@@ -58,10 +59,18 @@ class ASH_EXPORT LoginBubble : public views::WidgetObserver,
// Shows a tooltip. // Shows a tooltip.
void ShowTooltip(const base::string16& message, views::View* anchor_view); void ShowTooltip(const base::string16& message, views::View* anchor_view);
// Shows a selection menu.
void ShowSelectionMenu(LoginMenuView* menu, LoginButton* bubble_opener);
// Schedule animation for closing the bubble. // Schedule animation for closing the bubble.
// The bubble widget will be closed when the animation is ended. // The bubble widget will be closed when the animation is ended.
void Close(); void Close();
// Close the bubble immediately, without scheduling animation.
// Used to clean up old bubble widget when a new bubble is going to be
// created or it will be called before anchor view is hidden.
void CloseImmediately();
// True if the bubble is visible. // True if the bubble is visible.
bool IsVisible(); bool IsVisible();
...@@ -80,17 +89,12 @@ class ASH_EXPORT LoginBubble : public views::WidgetObserver, ...@@ -80,17 +89,12 @@ class ASH_EXPORT LoginBubble : public views::WidgetObserver,
void OnLayerAnimationScheduled( void OnLayerAnimationScheduled(
ui::LayerAnimationSequence* sequence) override{}; ui::LayerAnimationSequence* sequence) override{};
LoginBaseBubbleView* bubble_view_for_test() { return bubble_view_; } LoginBaseBubbleView* bubble_view() { return bubble_view_; }
private: private:
// Show the bubble widget and schedule animation for bubble showing. // Show the bubble widget and schedule animation for bubble showing.
void Show(); void Show();
// Close the bubble immediately, without scheduling animation.
// Used to clean up old bubble widget when a new bubble is
// going to be created.
void CloseImmediately();
void ProcessPressedEvent(const ui::LocatedEvent* event); void ProcessPressedEvent(const ui::LocatedEvent* event);
// Starts show/hide animation. // Starts show/hide animation.
......
...@@ -7,8 +7,10 @@ ...@@ -7,8 +7,10 @@
#include "ash/login/ui/login_bubble.h" #include "ash/login/ui/login_bubble.h"
#include "ash/login/ui/login_button.h" #include "ash/login/ui/login_button.h"
#include "ash/login/ui/login_menu_view.h"
#include "ash/login/ui/login_test_base.h" #include "ash/login/ui/login_test_base.h"
#include "base/strings/utf_string_conversions.h" #include "base/strings/utf_string_conversions.h"
#include "base/test/bind_test_util.h"
#include "testing/gtest/include/gtest/gtest.h" #include "testing/gtest/include/gtest/gtest.h"
#include "ui/events/test/event_generator.h" #include "ui/events/test/event_generator.h"
#include "ui/views/animation/test/ink_drop_host_view_test_api.h" #include "ui/views/animation/test/ink_drop_host_view_test_api.h"
...@@ -35,6 +37,29 @@ constexpr int kBubbleBottomMarginDp = 18; ...@@ -35,6 +37,29 @@ constexpr int kBubbleBottomMarginDp = 18;
// Non zero size for the bubble anchor view. // Non zero size for the bubble anchor view.
constexpr int kBubbleAnchorViewSizeDp = 100; constexpr int kBubbleAnchorViewSizeDp = 100;
std::vector<LoginMenuView::Item> PopulateMenuItems() {
std::vector<LoginMenuView::Item> items;
// Add one regular item.
LoginMenuView::Item item1;
item1.title = "Regular Item 1";
item1.is_group = false;
item1.selected = true;
items.push_back(item1);
// Add one group item.
LoginMenuView::Item item2;
item2.title = "Group Item 2";
item2.is_group = true;
items.push_back(item2);
// Add another regular item.
LoginMenuView::Item item3;
item3.title = "Regular Item 2";
item3.is_group = false;
items.push_back(item3);
return items;
}
class LoginBubbleTest : public LoginTestBase { class LoginBubbleTest : public LoginTestBase {
protected: protected:
LoginBubbleTest() = default; LoginBubbleTest() = default;
...@@ -79,6 +104,12 @@ class LoginBubbleTest : public LoginTestBase { ...@@ -79,6 +104,12 @@ class LoginBubbleTest : public LoginTestBase {
std::move(on_remove_show_warning), std::move(on_remove)); std::move(on_remove_show_warning), std::move(on_remove));
} }
void ShowSelectionMenu(const LoginMenuView::OnSelect& on_select) {
LoginMenuView* view =
new LoginMenuView(PopulateMenuItems(), container_, on_select);
bubble_->ShowSelectionMenu(view, bubble_opener_);
}
// Owned by test widget view hierarchy. // Owned by test widget view hierarchy.
views::View* container_ = nullptr; views::View* container_ = nullptr;
// Owned by test widget view hierarchy. // Owned by test widget view hierarchy.
...@@ -99,7 +130,7 @@ TEST_F(LoginBubbleTest, BaseBubbleSettings) { ...@@ -99,7 +130,7 @@ TEST_F(LoginBubbleTest, BaseBubbleSettings) {
bubble_->ShowTooltip(base::string16(), bubble_opener_); bubble_->ShowTooltip(base::string16(), bubble_opener_);
EXPECT_TRUE(bubble_->IsVisible()); EXPECT_TRUE(bubble_->IsVisible());
LoginBaseBubbleView* bubble_view = bubble_->bubble_view_for_test(); LoginBaseBubbleView* bubble_view = bubble_->bubble_view();
EXPECT_EQ(bubble_view->GetDialogButtons(), ui::DIALOG_BUTTON_NONE); EXPECT_EQ(bubble_view->GetDialogButtons(), ui::DIALOG_BUTTON_NONE);
EXPECT_EQ(bubble_view->width(), kBubbleTotalWidthDp); EXPECT_EQ(bubble_view->width(), kBubbleTotalWidthDp);
EXPECT_EQ(bubble_view->color(), SK_ColorBLACK); EXPECT_EQ(bubble_view->color(), SK_ColorBLACK);
...@@ -151,7 +182,7 @@ TEST_F(LoginBubbleTest, BubbleMouseEventHandling) { ...@@ -151,7 +182,7 @@ TEST_F(LoginBubbleTest, BubbleMouseEventHandling) {
// Verifies that mouse event on the bubble itself won't close the bubble. // Verifies that mouse event on the bubble itself won't close the bubble.
generator.MoveMouseTo( generator.MoveMouseTo(
bubble_->bubble_view_for_test()->GetBoundsInScreen().CenterPoint()); bubble_->bubble_view()->GetBoundsInScreen().CenterPoint());
generator.ClickLeftButton(); generator.ClickLeftButton();
EXPECT_TRUE(bubble_->IsVisible()); EXPECT_TRUE(bubble_->IsVisible());
...@@ -179,7 +210,7 @@ TEST_F(LoginBubbleTest, BubbleGestureEventHandling) { ...@@ -179,7 +210,7 @@ TEST_F(LoginBubbleTest, BubbleGestureEventHandling) {
// Verifies that gesture event on the bubble itself won't close the bubble. // Verifies that gesture event on the bubble itself won't close the bubble.
generator.GestureTapAt( generator.GestureTapAt(
bubble_->bubble_view_for_test()->GetBoundsInScreen().CenterPoint()); bubble_->bubble_view()->GetBoundsInScreen().CenterPoint());
EXPECT_TRUE(bubble_->IsVisible()); EXPECT_TRUE(bubble_->IsVisible());
// Verifies that gesture event on the other view will close the bubble. // Verifies that gesture event on the other view will close the bubble.
...@@ -229,9 +260,8 @@ TEST_F(LoginBubbleTest, RemoveUserRequiresTwoActivations) { ...@@ -229,9 +260,8 @@ TEST_F(LoginBubbleTest, RemoveUserRequiresTwoActivations) {
EXPECT_TRUE(bubble_->IsVisible()); EXPECT_TRUE(bubble_->IsVisible());
// Focus the remove user button. // Focus the remove user button.
views::View* remove_user_button = views::View* remove_user_button = bubble_->bubble_view()->GetViewByID(
bubble_->bubble_view_for_test()->GetViewByID( LoginBubble::kUserMenuRemoveUserButtonIdForTest);
LoginBubble::kUserMenuRemoveUserButtonIdForTest);
remove_user_button->RequestFocus(); remove_user_button->RequestFocus();
EXPECT_TRUE(remove_user_button->HasFocus()); EXPECT_TRUE(remove_user_button->HasFocus());
...@@ -276,7 +306,7 @@ TEST_F(LoginBubbleTest, ErrorBubbleMouseEventHandling) { ...@@ -276,7 +306,7 @@ TEST_F(LoginBubbleTest, ErrorBubbleMouseEventHandling) {
// Verifies that mouse event on the bubble itself won't close the bubble. // Verifies that mouse event on the bubble itself won't close the bubble.
generator.MoveMouseTo( generator.MoveMouseTo(
bubble_->bubble_view_for_test()->GetBoundsInScreen().CenterPoint()); bubble_->bubble_view()->GetBoundsInScreen().CenterPoint());
generator.ClickLeftButton(); generator.ClickLeftButton();
EXPECT_TRUE(bubble_->IsVisible()); EXPECT_TRUE(bubble_->IsVisible());
...@@ -296,7 +326,7 @@ TEST_F(LoginBubbleTest, ErrorBubbleGestureEventHandling) { ...@@ -296,7 +326,7 @@ TEST_F(LoginBubbleTest, ErrorBubbleGestureEventHandling) {
// Verifies that gesture event on the bubble itself won't close the bubble. // Verifies that gesture event on the bubble itself won't close the bubble.
generator.GestureTapAt( generator.GestureTapAt(
bubble_->bubble_view_for_test()->GetBoundsInScreen().CenterPoint()); bubble_->bubble_view()->GetBoundsInScreen().CenterPoint());
EXPECT_TRUE(bubble_->IsVisible()); EXPECT_TRUE(bubble_->IsVisible());
// Verifies that gesture event on the other view will close the bubble. // Verifies that gesture event on the other view will close the bubble.
...@@ -315,7 +345,7 @@ TEST_F(LoginBubbleTest, PersistentErrorBubbleEventHandling) { ...@@ -315,7 +345,7 @@ TEST_F(LoginBubbleTest, PersistentErrorBubbleEventHandling) {
// Verifies that mouse event on the bubble itself won't close the bubble. // Verifies that mouse event on the bubble itself won't close the bubble.
generator.MoveMouseTo( generator.MoveMouseTo(
bubble_->bubble_view_for_test()->GetBoundsInScreen().CenterPoint()); bubble_->bubble_view()->GetBoundsInScreen().CenterPoint());
generator.ClickLeftButton(); generator.ClickLeftButton();
EXPECT_TRUE(bubble_->IsVisible()); EXPECT_TRUE(bubble_->IsVisible());
...@@ -326,7 +356,7 @@ TEST_F(LoginBubbleTest, PersistentErrorBubbleEventHandling) { ...@@ -326,7 +356,7 @@ TEST_F(LoginBubbleTest, PersistentErrorBubbleEventHandling) {
// Verifies that gesture event on the bubble itself won't close the bubble. // Verifies that gesture event on the bubble itself won't close the bubble.
generator.GestureTapAt( generator.GestureTapAt(
bubble_->bubble_view_for_test()->GetBoundsInScreen().CenterPoint()); bubble_->bubble_view()->GetBoundsInScreen().CenterPoint());
EXPECT_TRUE(bubble_->IsVisible()); EXPECT_TRUE(bubble_->IsVisible());
// Verifies that gesture event on the other view won't close the bubble. // Verifies that gesture event on the other view won't close the bubble.
...@@ -343,4 +373,57 @@ TEST_F(LoginBubbleTest, PersistentErrorBubbleEventHandling) { ...@@ -343,4 +373,57 @@ TEST_F(LoginBubbleTest, PersistentErrorBubbleEventHandling) {
EXPECT_FALSE(bubble_->IsVisible()); EXPECT_FALSE(bubble_->IsVisible());
} }
TEST_F(LoginBubbleTest, TestShowSelectionMenu) {
ui::test::EventGenerator& generator = GetEventGenerator();
EXPECT_FALSE(bubble_->IsVisible());
LoginMenuView::Item selected_item;
bool selected = false;
ShowSelectionMenu(base::BindLambdaForTesting([&](LoginMenuView::Item item) {
selected_item = item;
selected = true;
}));
EXPECT_TRUE(bubble_->IsVisible());
// Verifies that regular item 1 is selectable.
LoginMenuView* menu_view =
static_cast<LoginMenuView*>(bubble_->bubble_view());
LoginMenuView::TestApi test_api1(menu_view);
EXPECT_TRUE(test_api1.contents()->child_at(0)->HasFocus());
generator.PressKey(ui::KeyboardCode::VKEY_RETURN, 0 /*flag*/);
EXPECT_FALSE(bubble_->IsVisible());
EXPECT_EQ(selected_item.title, "Regular Item 1");
EXPECT_TRUE(selected);
// Verfies that group item 2 is not selectable.
selected = false;
ShowSelectionMenu(base::BindLambdaForTesting([&](LoginMenuView::Item item) {
selected_item = item;
selected = true;
}));
EXPECT_TRUE(bubble_->IsVisible());
menu_view = static_cast<LoginMenuView*>(bubble_->bubble_view());
LoginMenuView::TestApi test_api2(menu_view);
test_api2.contents()->child_at(1)->RequestFocus();
generator.PressKey(ui::KeyboardCode::VKEY_RETURN, 0 /*flag*/);
EXPECT_TRUE(bubble_->IsVisible());
EXPECT_FALSE(selected);
// Verifies up/down arrow key can navigate menu entries.
generator.PressKey(ui::KeyboardCode::VKEY_UP, 0 /*flag*/);
EXPECT_TRUE(test_api2.contents()->child_at(0)->HasFocus());
generator.PressKey(ui::KeyboardCode::VKEY_UP, 0 /*flag*/);
EXPECT_TRUE(test_api2.contents()->child_at(0)->HasFocus());
generator.PressKey(ui::KeyboardCode::VKEY_DOWN, 0 /*flag*/);
// Group item is skipped in up/down key navigation.
EXPECT_TRUE(test_api2.contents()->child_at(2)->HasFocus());
generator.PressKey(ui::KeyboardCode::VKEY_DOWN, 0 /*flag*/);
EXPECT_TRUE(test_api2.contents()->child_at(2)->HasFocus());
EXPECT_TRUE(bubble_->IsVisible());
bubble_->Close();
EXPECT_FALSE(bubble_->IsVisible());
}
} // namespace ash } // namespace ash
...@@ -43,6 +43,7 @@ class ASH_EXPORT LoginExpandedPublicAccountView : public NonAccessibleView { ...@@ -43,6 +43,7 @@ class ASH_EXPORT LoginExpandedPublicAccountView : public NonAccessibleView {
void ProcessPressedEvent(const ui::LocatedEvent* event); void ProcessPressedEvent(const ui::LocatedEvent* event);
void UpdateForUser(const mojom::LoginUserInfoPtr& user); void UpdateForUser(const mojom::LoginUserInfoPtr& user);
const mojom::LoginUserInfoPtr& current_user() const; const mojom::LoginUserInfoPtr& current_user() const;
void Hide();
// views::View: // views::View:
void OnPaint(gfx::Canvas* canvas) override; void OnPaint(gfx::Canvas* canvas) override;
......
// Copyright 2018 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "ash/login/ui/login_menu_view.h"
#include "ash/login/ui/hover_notifier.h"
#include "ash/login/ui/non_accessible_view.h"
#include "base/strings/utf_string_conversions.h"
#include "ui/views/controls/button/button.h"
#include "ui/views/controls/label.h"
#include "ui/views/controls/scroll_view.h"
#include "ui/views/controls/scrollbar/overlay_scroll_bar.h"
#include "ui/views/layout/box_layout.h"
namespace ash {
constexpr int kMenuItemWidthDp = 178;
constexpr int kMenuItemHeightDp = 28;
constexpr int kRegularMenuItemLeftPaddingDp = 2;
constexpr int kGroupMenuItemLeftPaddingDp = 10;
constexpr int kNonEmptyHeight = 1;
namespace {
constexpr SkColor kMenuBackgroundColor = SkColorSetRGB(0x3C, 0x40, 0x43);
class MenuItemView : public views::Button, public views::ButtonListener {
public:
MenuItemView(const LoginMenuView::Item& item,
const LoginMenuView::OnHighLight& on_highlight)
: views::Button(this), item_(item), on_highlight_(on_highlight) {
SetFocusBehavior(FocusBehavior::ALWAYS);
SetLayoutManager(
std::make_unique<views::BoxLayout>(views::BoxLayout::kHorizontal));
SetPreferredSize(gfx::Size(kMenuItemWidthDp, kMenuItemHeightDp));
auto* spacing = new NonAccessibleView();
spacing->SetPreferredSize(gfx::Size(item.is_group
? kRegularMenuItemLeftPaddingDp
: kGroupMenuItemLeftPaddingDp,
kNonEmptyHeight));
AddChildView(spacing);
views::Label* label = new views::Label(base::UTF8ToUTF16(item.title));
label->SetEnabledColor(SK_ColorWHITE);
label->SetSubpixelRenderingEnabled(false);
label->SetAutoColorReadabilityEnabled(false);
label->SetHorizontalAlignment(gfx::ALIGN_LEFT);
AddChildView(label);
if (item.selected)
SetBackground(views::CreateSolidBackground(SK_ColorGRAY));
hover_notifier_ = std::make_unique<HoverNotifier>(
this,
base::BindRepeating(&MenuItemView::OnHover, base::Unretained(this)));
}
~MenuItemView() override = default;
// views::View:
int GetHeightForWidth(int w) const override {
// Make row height fixed avoiding layout manager adjustments.
return GetPreferredSize().height();
}
// views::ButtonListener:
void ButtonPressed(views::Button* sender, const ui::Event& event) override {
DCHECK(sender == this);
if (item_.is_group)
return;
on_highlight_.Run(true /*by_selection*/);
GetWidget()->Close();
}
void OnHover(bool has_hover) {
if (has_hover && !item_.is_group)
RequestFocus();
}
void OnFocus() override {
ScrollViewToVisible();
on_highlight_.Run(false /*by_selection*/);
}
const LoginMenuView::Item& item() const { return item_; }
private:
const LoginMenuView::Item item_;
const LoginMenuView::OnHighLight on_highlight_;
std::unique_ptr<HoverNotifier> hover_notifier_;
DISALLOW_COPY_AND_ASSIGN(MenuItemView);
};
class LoginScrollBar : public views::OverlayScrollBar {
public:
LoginScrollBar() : OverlayScrollBar(false) {}
// OverlayScrollBar:
bool OnKeyPressed(const ui::KeyEvent& event) override {
// Let LoginMenuView to handle up/down keypress.
return false;
}
private:
DISALLOW_COPY_AND_ASSIGN(LoginScrollBar);
};
} // namespace
LoginMenuView::TestApi::TestApi(LoginMenuView* view) : view_(view) {}
LoginMenuView::TestApi::~TestApi() = default;
views::View* LoginMenuView::TestApi::contents() const {
return view_->contents_;
}
LoginMenuView::Item::Item() = default;
LoginMenuView::LoginMenuView(const std::vector<Item>& items,
views::View* anchor_view,
const OnSelect& on_select)
: LoginBaseBubbleView(anchor_view), on_select_(on_select) {
set_can_activate(true);
set_margins(gfx::Insets());
set_color(kMenuBackgroundColor);
SetFocusBehavior(views::View::FocusBehavior::ALWAYS);
scroller_ = new views::ScrollView();
scroller_->SetBackgroundColor(SK_ColorTRANSPARENT);
scroller_->set_draw_overflow_indicator(false);
scroller_->ClipHeightTo(kMenuItemHeightDp, kMenuItemHeightDp * 5);
AddChildView(scroller_);
views::BoxLayout* box_layout = SetLayoutManager(
std::make_unique<views::BoxLayout>(views::BoxLayout::kVertical));
box_layout->SetFlexForView(scroller_, 1);
contents_ = new NonAccessibleView();
views::BoxLayout* layout = contents_->SetLayoutManager(
std::make_unique<views::BoxLayout>(views::BoxLayout::kVertical));
layout->SetDefaultFlex(1);
layout->set_minimum_cross_axis_size(kMenuItemWidthDp);
layout->set_main_axis_alignment(
views::BoxLayout::MainAxisAlignment::MAIN_AXIS_ALIGNMENT_CENTER);
for (size_t i = 0; i < items.size(); i++) {
const Item& item = items[i];
contents_->AddChildView(new MenuItemView(
item, base::BindRepeating(&LoginMenuView::OnHighLightChange,
base::Unretained(this), i)));
if (item.selected)
selected_index_ = i;
}
scroller_->SetContents(contents_);
scroller_->SetVerticalScrollBar(new LoginScrollBar());
}
LoginMenuView::~LoginMenuView() = default;
void LoginMenuView::OnHighLightChange(int item_index, bool by_selection) {
selected_index_ = item_index;
views::View* highlight_item = contents_->child_at(item_index);
for (views::View* child : contents_->GetChildrenInZOrder()) {
child->SetBackground(views::CreateSolidBackground(
child == highlight_item ? SK_ColorGRAY : SK_ColorTRANSPARENT));
}
if (by_selection) {
MenuItemView* menu_view = static_cast<MenuItemView*>(highlight_item);
on_select_.Run(menu_view->item());
}
contents_->SchedulePaint();
}
int LoginMenuView::FindNextItem(bool reverse) {
int delta = reverse ? -1 : 1;
int current_index = selected_index_ + delta;
while (current_index >= 0 && current_index < contents_->child_count()) {
MenuItemView* menu_view =
static_cast<MenuItemView*>(contents_->child_at(current_index));
if (!menu_view->item().is_group)
break;
current_index += delta;
}
if (current_index < 0 || current_index == contents_->child_count())
return selected_index_;
return current_index;
}
void LoginMenuView::OnFocus() {
// Forward the focus to the selected child view.
contents_->child_at(selected_index_)->RequestFocus();
}
bool LoginMenuView::OnKeyPressed(const ui::KeyEvent& event) {
const ui::KeyboardCode key = event.key_code();
if (key == ui::VKEY_UP || key == ui::VKEY_DOWN) {
contents_->child_at(FindNextItem(key == ui::VKEY_UP))->RequestFocus();
return true;
}
return false;
}
void LoginMenuView::VisibilityChanged(View* starting_from, bool is_visible) {
if (is_visible)
contents_->child_at(selected_index_)->RequestFocus();
}
} // namespace ash
// Copyright 2018 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef ASH_LOGIN_UI_LOGIN_MENU_VIEW_H_
#define ASH_LOGIN_UI_LOGIN_MENU_VIEW_H_
#include "ash/ash_export.h"
#include "ash/login/ui/login_base_bubble_view.h"
#include "base/callback.h"
#include "ui/views/view.h"
namespace ash {
// Implements a menu view for login screen.
// This is used instead of views::Combobox because we want a more customisable
// view for the menu items to support some sepcific styles like nested menu
// entries which have different left margin (less than regular items) and are
// not selectable. views::Combobox uses views::MenuItemView which is not very
// straightforward to customize.
class ASH_EXPORT LoginMenuView : public LoginBaseBubbleView {
public:
// TestApi is used for tests to get internal implementation details.
class ASH_EXPORT TestApi {
public:
explicit TestApi(LoginMenuView* view);
~TestApi();
views::View* contents() const;
private:
LoginMenuView* const view_;
};
struct Item {
Item();
std::string title;
std::string value;
bool is_group = false;
bool selected = false;
};
using OnSelect = base::RepeatingCallback<void(Item item)>;
using OnHighLight = base::RepeatingCallback<void(bool by_selection)>;
LoginMenuView(const std::vector<Item>& items,
views::View* anchor_view,
const OnSelect& on_select);
~LoginMenuView() override;
void OnHighLightChange(int item_index, bool by_selection);
int FindNextItem(bool reverse);
// views::View:
void OnFocus() override;
bool OnKeyPressed(const ui::KeyEvent& event) override;
void VisibilityChanged(View* starting_from, bool is_visible) override;
private:
// Owned by this class.
views::ScrollView* scroller_ = nullptr;
// Owned by ScrollView.
views::View* contents_ = nullptr;
const OnSelect on_select_;
int selected_index_ = 0;
DISALLOW_COPY_AND_ASSIGN(LoginMenuView);
};
} // namespace ash
#endif // ASH_LOGIN_UI_LOGIN_MENU_VIEW_H_
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