// Copyright 2022 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "ash/shelf/shelf_shutdown_confirmation_bubble.h"
#include "ash/shelf/login_shelf_button.h"
#include "ash/strings/grit/ash_strings.h"
#include "ash/style/ash_color_provider.h"
#include "ash/style/pill_button.h"
#include "ash/style/typography.h"
#include "ash/system/tray/tray_popup_utils.h"
#include "ash/system/tray/tray_utils.h"
#include "base/functional/bind.h"
#include "base/functional/callback_forward.h"
#include "base/functional/callback_helpers.h"
#include "base/metrics/histogram_functions.h"
#include "components/strings/grit/components_strings.h"
#include "components/vector_icons/vector_icons.h"
#include "ui/accessibility/ax_node_data.h"
#include "ui/aura/window.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/base/models/image_model.h"
#include "ui/gfx/geometry/size.h"
#include "ui/gfx/image/image_skia_operations.h"
#include "ui/gfx/paint_vector_icon.h"
#include "ui/gfx/vector_icon_types.h"
#include "ui/views/accessibility/view_accessibility.h"
#include "ui/views/controls/label.h"
#include "ui/views/layout/box_layout.h"
#include "ui/views/layout/flex_layout.h"
#include "ui/views/layout/layout_provider.h"
namespace ash {
namespace {
// The insets of the shutdown confirmation bubble. The value of
// Insets-leading-side is taken from the system.
constexpr int kShutdownConfirmationBubbleInsetsBottom = 12;
constexpr int kShutdownConfirmationBubbleInsetsTop = 8;
gfx::Insets GetShutdownConfirmationBubbleInsets(aura::Window* window) {
gfx::Insets insets = GetTrayBubbleInsets(window);
insets.set_top(kShutdownConfirmationBubbleInsetsTop);
insets.set_bottom(kShutdownConfirmationBubbleInsetsBottom);
return insets;
}
// Histogram for tracking the number of actions on the shelf shutdown
// confirmation bubble.
constexpr char kActionHistogramName[] =
"Ash.Shelf.ShutdownConfirmationBubble.Action";
} // namespace
ShelfShutdownConfirmationBubble::ShelfShutdownConfirmationBubble(
LoginShelfButton* anchor,
ShelfAlignment alignment,
base::OnceClosure on_confirm_callback,
base::OnceClosure on_cancel_callback)
: ShelfBubble(anchor,
alignment,
/*for_tooltip=*/false,
/*arrow_position=*/std::nullopt),
anchor_(anchor) {
DCHECK(on_confirm_callback);
DCHECK(on_cancel_callback);
confirm_callback_ = std::move(on_confirm_callback);
cancel_callback_ = std::move(on_cancel_callback);
auto* layout_provider = views::LayoutProvider::Get();
const gfx::Insets kShutdownConfirmationBubbleInsets =
GetShutdownConfirmationBubbleInsets(
anchor_widget()->GetNativeWindow()->GetRootWindow());
const gfx::Insets dialog_insets =
layout_provider->GetInsetsMetric(views::INSETS_DIALOG);
set_margins(kShutdownConfirmationBubbleInsets + dialog_insets);
set_close_on_deactivate(true);
SetCloseCallback(base::BindOnce(&ShelfShutdownConfirmationBubble::OnClosed,
base::Unretained(this)));
views::FlexLayout* layout =
SetLayoutManager(std::make_unique<views::FlexLayout>());
layout->SetOrientation(views::LayoutOrientation::kVertical);
layout->SetMainAxisAlignment(views::LayoutAlignment::kStart);
layout->SetCrossAxisAlignment(views::LayoutAlignment::kStart);
// Set up the icon.
icon_ = AddChildView(std::make_unique<views::ImageView>());
icon_->SetProperty(
views::kMarginsKey,
gfx::Insets::TLBR(0, 0,
layout_provider->GetDistanceMetric(
views::DISTANCE_UNRELATED_CONTROL_VERTICAL),
0));
// Set up the title view.
title_ = AddChildView(std::make_unique<views::Label>());
title_->SetMultiLine(true);
title_->SetHorizontalAlignment(gfx::HorizontalAlignment::ALIGN_LEFT);
title_->SetAutoColorReadabilityEnabled(false);
TypographyProvider::Get()->StyleLabel(TypographyToken::kCrosHeadline1,
*title_);
title_->SetText(
l10n_util::GetStringUTF16(IDS_ASH_SHUTDOWN_CONFIRMATION_TITLE));
title_->SetProperty(
views::kMarginsKey,
gfx::Insets::TLBR(0, 0,
layout_provider->GetDistanceMetric(
views::DISTANCE_DIALOG_CONTENT_MARGIN_BOTTOM_TEXT),
0));
// Set up layout row for the buttons of cancellation and confirmation.
views::View* button_container = AddChildView(std::make_unique<views::View>());
button_container->SetLayoutManager(std::make_unique<views::BoxLayout>(
views::BoxLayout::Orientation::kHorizontal, gfx::Insets(),
layout_provider->GetDistanceMetric(
views::DISTANCE_RELATED_BUTTON_HORIZONTAL)));
auto cancel_button = std::make_unique<PillButton>(
base::BindRepeating(&ShelfShutdownConfirmationBubble::OnCancelled,
base::Unretained(this)),
l10n_util::GetStringUTF16(IDS_CANCEL),
PillButton::Type::kDefaultWithoutIcon,
/*icon=*/nullptr);
cancel_button->SetID(static_cast<int>(ButtonId::kCancel));
cancel_ = button_container->AddChildView(std::move(cancel_button));
auto confirm_button = std::make_unique<PillButton>(
base::BindRepeating(&ShelfShutdownConfirmationBubble::OnConfirmed,
base::Unretained(this)),
l10n_util::GetStringUTF16(IDS_ASH_SHUTDOWN_CONFIRM_BUTTON),
PillButton::Type::kDefaultWithoutIcon, /*icon=*/nullptr);
confirm_button->SetID(static_cast<int>(ButtonId::kShutdown));
confirm_ = button_container->AddChildView(std::move(confirm_button));
CreateBubble();
auto bubble_border =
std::make_unique<views::BubbleBorder>(arrow(), GetShadow());
bubble_border->set_insets(kShutdownConfirmationBubbleInsets);
bubble_border->SetCornerRadius(
views::LayoutProvider::Get()->GetCornerRadiusMetric(
views::Emphasis::kHigh));
GetBubbleFrameView()->SetBubbleBorder(std::move(bubble_border));
GetBubbleFrameView()->SetBackgroundColor(GetBackgroundColor());
// The bubble content size changes after border setting, therefore resize
// the widget to its content.
// TODO(crbug.com/41493925): widget should autoresize to its content.
SizeToContents();
GetWidget()->Show();
base::UmaHistogramEnumeration(
kActionHistogramName,
ShelfShutdownConfirmationBubble::BubbleAction::kOpened);
GetViewAccessibility().SetRole(ax::mojom::Role::kDialog);
GetViewAccessibility().SetName(title_->GetText());
}
ShelfShutdownConfirmationBubble::~ShelfShutdownConfirmationBubble() {
// In case shutdown confirmation bubble was dismissed, the pointer of the
// ShelfShutdownConfirmationBubble in LoginShelfView shall be cleaned up.
if (cancel_callback_) {
std::move(cancel_callback_).Run();
}
}
void ShelfShutdownConfirmationBubble::OnThemeChanged() {
views::View::OnThemeChanged();
auto* color_provider = AshColorProvider::Get();
SkColor icon_color = color_provider->GetContentLayerColor(
AshColorProvider::ContentLayerType::kButtonIconColor);
icon_->SetImage(
gfx::CreateVectorIcon(vector_icons::kWarningOutlineIcon, icon_color));
SkColor label_color = color_provider->GetContentLayerColor(
AshColorProvider::ContentLayerType::kTextColorPrimary);
title_->SetEnabledColor(label_color);
SkColor button_color = color_provider->GetContentLayerColor(
AshColorProvider::ContentLayerType::kButtonLabelColor);
cancel_->SetEnabledTextColors(button_color);
confirm_->SetEnabledTextColors(button_color);
}
std::u16string ShelfShutdownConfirmationBubble::GetAccessibleWindowTitle()
const {
return title_->GetText();
}
void ShelfShutdownConfirmationBubble::OnCancelled() {
dialog_result_ = DialogResult::kCancelled;
GetWidget()->Close();
}
void ShelfShutdownConfirmationBubble::OnConfirmed() {
dialog_result_ = DialogResult::kConfirmed;
GetWidget()->Close();
}
void ShelfShutdownConfirmationBubble::OnClosed() {
anchor_->SetIsActive(false);
switch (dialog_result_) {
case DialogResult::kCancelled:
ReportBubbleAction(
ShelfShutdownConfirmationBubble::BubbleAction::kCancelled);
std::move(cancel_callback_).Run();
break;
case DialogResult::kConfirmed:
ReportBubbleAction(
ShelfShutdownConfirmationBubble::BubbleAction::kConfirmed);
std::move(confirm_callback_).Run();
break;
case DialogResult::kNone:
ReportBubbleAction(
ShelfShutdownConfirmationBubble::BubbleAction::kDismissed);
break;
}
}
bool ShelfShutdownConfirmationBubble::ShouldCloseOnPressDown() {
return true;
}
bool ShelfShutdownConfirmationBubble::ShouldCloseOnMouseExit() {
return false;
}
void ShelfShutdownConfirmationBubble::ReportBubbleAction(
ShelfShutdownConfirmationBubble::BubbleAction action) {
base::UmaHistogramEnumeration(kActionHistogramName, action);
}
} // namespace ash