blob: 5246c4ab5ebfd48bb77ac999cdcc413fd1248aba [file] [log] [blame]
// 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/system/unified/top_shortcuts_view.h"
#include <numeric>
#include "ash/accessibility/accessibility_controller.h"
#include "ash/public/cpp/ash_view_ids.h"
#include "ash/resources/vector_icons/vector_icons.h"
#include "ash/session/session_controller_impl.h"
#include "ash/shell.h"
#include "ash/shutdown_controller.h"
#include "ash/strings/grit/ash_strings.h"
#include "ash/system/tray/tray_constants.h"
#include "ash/system/tray/tray_popup_utils.h"
#include "ash/system/unified/collapse_button.h"
#include "ash/system/unified/sign_out_button.h"
#include "ash/system/unified/top_shortcut_button.h"
#include "ash/system/unified/unified_system_tray_controller.h"
#include "ash/system/unified/user_chooser_detailed_view_controller.h"
#include "ash/system/unified/user_chooser_view.h"
#include "base/numerics/ranges.h"
#include "ui/gfx/paint_vector_icon.h"
#include "ui/views/layout/box_layout.h"
#include "ui/views/layout/fill_layout.h"
#include "ui/views/view_class_properties.h"
namespace ash {
namespace {
class UserAvatarButton : public views::Button {
public:
UserAvatarButton(views::ButtonListener* listener);
~UserAvatarButton() override = default;
private:
DISALLOW_COPY_AND_ASSIGN(UserAvatarButton);
};
UserAvatarButton::UserAvatarButton(views::ButtonListener* listener)
: Button(listener) {
SetLayoutManager(std::make_unique<views::FillLayout>());
SetBorder(views::CreateEmptyBorder(kUnifiedCircularButtonFocusPadding));
AddChildView(CreateUserAvatarView(0 /* user_index */));
SetTooltipText(GetUserItemAccessibleString(0 /* user_index */));
SetInstallFocusRingOnFocus(true);
SetFocusForPlatform();
int focus_ring_radius =
kTrayItemSize + kUnifiedCircularButtonFocusPadding.width();
auto path = std::make_unique<SkPath>();
path->addOval(gfx::RectToSkRect(
gfx::Rect(gfx::Size(focus_ring_radius, focus_ring_radius))));
SetProperty(views::kHighlightPathKey, path.release());
}
} // namespace
TopShortcutButtonContainer::TopShortcutButtonContainer() = default;
TopShortcutButtonContainer::~TopShortcutButtonContainer() = default;
// Buttons are equally spaced by the default value, but the gap will be
// narrowed evenly when the parent view is not large enough.
void TopShortcutButtonContainer::Layout() {
const gfx::Rect child_area = GetContentsBounds();
views::View::Views visible_children;
std::copy_if(children().cbegin(), children().cend(),
std::back_inserter(visible_children), [](const auto* v) {
return v->visible() && (v->GetPreferredSize().width() > 0);
});
if (visible_children.empty())
return;
const int visible_child_width =
std::accumulate(visible_children.cbegin(), visible_children.cend(), 0,
[](int width, const auto* v) {
return width + v->GetPreferredSize().width();
});
int spacing = 0;
if (visible_children.size() > 1) {
spacing = (child_area.width() - visible_child_width) /
(int{visible_children.size()} - 1);
spacing = base::ClampToRange(spacing, kUnifiedTopShortcutButtonMinSpacing,
kUnifiedTopShortcutButtonDefaultSpacing);
}
int x = child_area.x();
int y = child_area.y() + kUnifiedTopShortcutContainerTopPadding +
kUnifiedCircularButtonFocusPadding.bottom();
for (auto* child : visible_children) {
int child_y = y;
int width = child->GetPreferredSize().width();
if (child == user_avatar_button_) {
x -= kUnifiedCircularButtonFocusPadding.left();
child_y -= kUnifiedCircularButtonFocusPadding.bottom();
} else if (child == sign_out_button_) {
// When there's not enough space, shrink the sign-out button.
const int remainder = child_area.width() -
(int{visible_children.size()} - 1) * spacing -
(visible_child_width - width);
width = base::ClampToRange(width, 0, remainder);
}
child->SetBounds(x, child_y, width, child->GetHeightForWidth(width));
x += width + spacing;
if (child == user_avatar_button_)
x -= kUnifiedCircularButtonFocusPadding.right();
}
}
gfx::Size TopShortcutButtonContainer::CalculatePreferredSize() const {
int total_horizontal_size = 0;
int num_visible = 0;
for (const auto* child : children()) {
if (!child->visible())
continue;
int child_horizontal_size = child->GetPreferredSize().width();
if (child_horizontal_size == 0)
continue;
total_horizontal_size += child_horizontal_size;
num_visible++;
}
int width =
(num_visible == 0)
? 0
: total_horizontal_size +
(num_visible - 1) * kUnifiedTopShortcutButtonDefaultSpacing;
int height = kTrayItemSize + kUnifiedCircularButtonFocusPadding.height() +
kUnifiedTopShortcutContainerTopPadding;
return gfx::Size(width, height);
}
void TopShortcutButtonContainer::AddUserAvatarButton(
views::View* user_avatar_button) {
AddChildView(user_avatar_button);
user_avatar_button_ = user_avatar_button;
}
void TopShortcutButtonContainer::AddSignOutButton(
views::View* sign_out_button) {
AddChildView(sign_out_button);
sign_out_button_ = sign_out_button;
}
TopShortcutsView::TopShortcutsView(UnifiedSystemTrayController* controller)
: controller_(controller) {
DCHECK(controller_);
auto* layout = SetLayoutManager(std::make_unique<views::BoxLayout>(
views::BoxLayout::kHorizontal, kUnifiedTopShortcutPadding,
kUnifiedTopShortcutSpacing));
layout->set_cross_axis_alignment(
views::BoxLayout::CrossAxisAlignment::kStart);
container_ = new TopShortcutButtonContainer();
AddChildView(container_);
if (Shell::Get()->session_controller()->login_status() !=
LoginStatus::NOT_LOGGED_IN) {
user_avatar_button_ = new UserAvatarButton(this);
user_avatar_button_->SetEnabled(
UserChooserDetailedViewController::IsUserChooserEnabled());
container_->AddUserAvatarButton(user_avatar_button_);
}
// Show the buttons in this row as disabled if the user is at the login
// screen, lock screen, or in a secondary account flow. The exception is
// |power_button_| which is always shown as enabled.
const bool can_show_web_ui = TrayPopupUtils::CanOpenWebUISettings();
sign_out_button_ = new SignOutButton(this);
container_->AddSignOutButton(sign_out_button_);
bool reboot = Shell::Get()->shutdown_controller()->reboot_on_shutdown();
power_button_ = new TopShortcutButton(
this, kUnifiedMenuPowerIcon,
reboot ? IDS_ASH_STATUS_TRAY_REBOOT : IDS_ASH_STATUS_TRAY_SHUTDOWN);
power_button_->SetID(VIEW_ID_POWER_BUTTON);
container_->AddChildView(power_button_);
lock_button_ = new TopShortcutButton(this, kUnifiedMenuLockIcon,
IDS_ASH_STATUS_TRAY_LOCK);
lock_button_->SetVisible(can_show_web_ui &&
Shell::Get()->session_controller()->CanLockScreen());
container_->AddChildView(lock_button_);
settings_button_ = new TopShortcutButton(this, kUnifiedMenuSettingsIcon,
IDS_ASH_STATUS_TRAY_SETTINGS);
settings_button_->SetVisible(can_show_web_ui);
container_->AddChildView(settings_button_);
// |collapse_button_| should be right-aligned, so we make the buttons
// container flex occupying all remaining space.
layout->SetFlexForView(container_, 1);
collapse_button_ = new CollapseButton(this);
AddChildView(collapse_button_);
OnAccessibilityStatusChanged();
Shell::Get()->accessibility_controller()->AddObserver(this);
}
TopShortcutsView::~TopShortcutsView() {
Shell::Get()->accessibility_controller()->RemoveObserver(this);
}
void TopShortcutsView::SetExpandedAmount(double expanded_amount) {
collapse_button_->SetExpandedAmount(expanded_amount);
}
void TopShortcutsView::ButtonPressed(views::Button* sender,
const ui::Event& event) {
if (sender == user_avatar_button_)
controller_->ShowUserChooserView();
else if (sender == sign_out_button_)
controller_->HandleSignOutAction();
else if (sender == lock_button_)
controller_->HandleLockAction();
else if (sender == settings_button_)
controller_->HandleSettingsAction();
else if (sender == power_button_)
controller_->HandlePowerAction();
else if (sender == collapse_button_)
controller_->ToggleExpanded();
}
void TopShortcutsView::OnAccessibilityStatusChanged() {
collapse_button_->SetEnabled(
!Shell::Get()->accessibility_controller()->spoken_feedback_enabled());
}
} // namespace ash