// Copyright 2023 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/system/focus_mode/focus_mode_tray.h"
#include "ash/constants/notifier_catalogs.h"
#include "ash/constants/tray_background_view_catalog.h"
#include "ash/glanceables/common/glanceables_util.h"
#include "ash/resources/vector_icons/vector_icons.h"
#include "ash/strings/grit/ash_strings.h"
#include "ash/style/ash_color_id.h"
#include "ash/style/typography.h"
#include "ash/system/focus_mode/focus_mode_controller.h"
#include "ash/system/focus_mode/focus_mode_countdown_view.h"
#include "ash/system/focus_mode/focus_mode_ending_moment_view.h"
#include "ash/system/focus_mode/focus_mode_session.h"
#include "ash/system/focus_mode/focus_mode_util.h"
#include "ash/system/progress_indicator/progress_indicator.h"
#include "ash/system/toast/anchored_nudge_manager_impl.h"
#include "ash/system/tray/tray_bubble_wrapper.h"
#include "ash/system/tray/tray_container.h"
#include "ash/system/tray/tray_utils.h"
#include "ash/wm/splitview/split_view_utils.h"
#include "base/check.h"
#include "chromeos/constants/chromeos_features.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/base/metadata/metadata_header_macros.h"
#include "ui/base/metadata/metadata_impl_macros.h"
#include "ui/base/models/image_model.h"
#include "ui/chromeos/styles/cros_tokens_color_mappings.h"
#include "ui/color/color_id.h"
#include "ui/compositor/layer.h"
#include "ui/views/accessibility/view_accessibility.h"
#include "ui/views/animation/animation_builder.h"
#include "ui/views/controls/button/image_button.h"
#include "ui/views/controls/label.h"
#include "ui/views/layout/box_layout_view.h"
namespace ash {
namespace {
constexpr int kIconSize = 20;
constexpr int kBubbleInset = 16;
constexpr int kTaskItemViewInsets = 6;
constexpr int kTaskItemViewCornerRadius = 16;
constexpr int kProgressIndicatorThickness = 2;
constexpr auto kTaskTitleLabelInsets = gfx::Insets::TLBR(0, 12, 0, 18);
constexpr auto kProgressIndicatorInsets = gfx::Insets(-6);
constexpr base::TimeDelta kStartAnimationDelay = base::Milliseconds(300);
constexpr base::TimeDelta kTaskItemViewFadeOutDuration =
base::Milliseconds(200);
std::u16string GetAccessibleTrayName(
const FocusModeSession::Snapshot& session_snapshot,
const size_t congratulatory_index) {
if (session_snapshot.state == FocusModeSession::State::kEnding) {
return focus_mode_util::GetCongratulatoryTextAndEmoji(congratulatory_index);
}
const std::u16string duration_string =
session_snapshot.remaining_time < base::Minutes(1)
? l10n_util::GetStringUTF16(
IDS_ASH_STATUS_TRAY_FOCUS_MODE_SESSION_LESS_THAN_ONE_MINUTE)
: focus_mode_util::GetDurationString(session_snapshot.remaining_time,
/*digital_format=*/false);
return l10n_util::GetStringFUTF16(
IDS_ASH_STATUS_TRAY_FOCUS_MODE_TRAY_BUBBLE_ACCESSIBLE_NAME,
duration_string);
}
std::u16string GetAccessibleBubbleName(
const FocusModeSession::Snapshot& session_snapshot,
const std::u16string& task_title,
const size_t congratulatory_index) {
if (session_snapshot.state == FocusModeSession::State::kEnding) {
std::u16string title =
focus_mode_util::GetCongratulatoryTextAndEmoji(congratulatory_index);
std::u16string body = l10n_util::GetStringUTF16(
IDS_ASH_STATUS_TRAY_FOCUS_MODE_ENDING_MOMENT_BODY);
return l10n_util::GetStringFUTF16(
IDS_ASH_STATUS_TRAY_FOCUS_MODE_ENDING_MOMENT_DIALOG, title, body);
}
const std::u16string time_remaining =
focus_mode_util::GetDurationString(session_snapshot.remaining_time,
/*digital_format=*/false);
return task_title.empty()
? l10n_util::GetStringFUTF16(
IDS_ASH_STATUS_TRAY_FOCUS_MODE_TRAY_BUBBLE_ACCESSIBLE_NAME,
time_remaining)
: l10n_util::GetStringFUTF16(
IDS_ASH_STATUS_TRAY_FOCUS_MODE_TRAY_BUBBLE_TASK_ACCESSIBLE_NAME,
time_remaining, task_title);
}
} // namespace
class FocusModeTray::TaskItemView : public views::BoxLayoutView {
METADATA_HEADER(TaskItemView, views::BoxLayoutView)
public:
TaskItemView(const std::u16string& title, PressedCallback callback) {
SetBorder(views::CreateEmptyBorder(kTaskItemViewInsets));
// Set the background color is not opaque.
SetPaintToLayer();
layer()->SetFillsBoundsOpaquely(false);
SetBackground(views::CreateThemedRoundedRectBackground(
cros_tokens::kCrosSysSystemOnBase, kTaskItemViewCornerRadius));
const bool is_network_connected = glanceables_util::IsNetworkConnected();
radio_button_ =
AddChildView(std::make_unique<views::ImageButton>(std::move(callback)));
radio_button_->SetImageModel(
views::Button::STATE_NORMAL,
ui::ImageModel::FromVectorIcon(kRadioButtonUncheckedIcon,
is_network_connected
? cros_tokens::kCrosSysPrimary
: cros_tokens::kCrosSysDisabled,
kIconSize));
const std::u16string radio_text = l10n_util::GetStringUTF16(
IDS_ASH_STATUS_TRAY_FOCUS_MODE_TASK_VIEW_RADIO_BUTTON);
views::ViewAccessibility& radio_button_view_a11y =
radio_button_->GetViewAccessibility();
radio_button_view_a11y.SetName(radio_text);
radio_button_view_a11y.SetDescription(title);
radio_button_->SetTooltipText(radio_text);
radio_button_->SetEnabled(is_network_connected);
task_title_ = AddChildView(std::make_unique<views::Label>());
TypographyProvider::Get()->StyleLabel(TypographyToken::kCrosButton2,
*task_title_);
task_title_->SetEnabledColorId(is_network_connected
? cros_tokens::kCrosSysOnSurface
: cros_tokens::kCrosSysDisabled);
task_title_->SetText(title);
task_title_->SetTooltipText(title);
task_title_->SetBorder(views::CreateEmptyBorder(kTaskTitleLabelInsets));
task_title_->SetEnabled(is_network_connected);
}
TaskItemView(const TaskItemView&) = delete;
TaskItemView& operator=(const TaskItemView&) = delete;
~TaskItemView() override {
radio_button_ = nullptr;
task_title_ = nullptr;
}
const views::ImageButton* GetRadioButton() const { return radio_button_; }
const views::Label* GetTaskTitle() const { return task_title_; }
bool GetWasCompleted() const { return was_completed_; }
void UpdateTitle(const std::u16string& title) {
radio_button_->GetViewAccessibility().SetDescription(title);
task_title_->SetText(title);
task_title_->SetTooltipText(title);
}
// Sets `radio_button_` as toggled which will update the button with a check
// icon, and adds a strike through on `task_title_`.
void UpdateStyleToCompleted() {
if (was_completed_) {
return;
}
was_completed_ = true;
radio_button_->SetImageModel(
views::Button::STATE_NORMAL,
ui::ImageModel::FromVectorIcon(kDoneIcon, cros_tokens::kCrosSysPrimary,
kIconSize));
task_title_->SetFontList(
TypographyProvider::Get()
->ResolveTypographyToken(TypographyToken::kCrosButton2)
.DeriveWithStyle(gfx::Font::FontStyle::STRIKE_THROUGH));
task_title_->SetEnabledColorId(cros_tokens::kCrosSysSecondary);
}
private:
bool was_completed_ = false;
raw_ptr<views::ImageButton> radio_button_ = nullptr;
raw_ptr<views::Label> task_title_ = nullptr;
};
BEGIN_METADATA(FocusModeTray, TaskItemView)
END_METADATA
FocusModeTray::FocusModeTray(Shelf* shelf)
: TrayBackgroundView(shelf,
TrayBackgroundViewCatalogName::kFocusMode,
RoundedCornerBehavior::kAllRounded),
image_view_(tray_container()->AddChildView(
std::make_unique<views::ImageView>())) {
SetCallback(base::BindRepeating(&FocusModeTray::FocusModeIconActivated,
weak_ptr_factory_.GetWeakPtr()));
image_view_->SetHorizontalAlignment(views::ImageView::Alignment::kCenter);
image_view_->SetVerticalAlignment(views::ImageView::Alignment::kCenter);
image_view_->SetPreferredSize(gfx::Size(kTrayItemSize, kTrayItemSize));
tray_container()->SetPaintToLayer();
tray_container()->layer()->SetFillsBoundsOpaquely(false);
progress_indicator_ =
ProgressIndicator::CreateDefaultInstance(base::BindRepeating(
[](FocusModeTray* view) -> std::optional<float> {
if (!view->visible_preferred() || view->is_active()) {
return 0.0f;
}
if (view->show_progress_ring_after_animation_) {
return ProgressIndicator::kForcedShow;
}
auto* controller = FocusModeController::Get();
// `kProgressComplete` is only returned by an ending moment, so that
// we can know when the pulse animation is done.
if (controller->in_ending_moment()) {
if (!view->bounce_in_animation_finished_) {
return ProgressIndicator::kForcedShow;
}
bool is_animating = false;
if (auto* progress_ring_animation =
view->progress_indicator_->animation_registry()
->GetProgressRingAnimationForKey(
view->progress_indicator_->animation_key())) {
is_animating = progress_ring_animation->IsAnimating();
}
// After the pulse animation, the ring isn't shown when the value
// is left at `kProgressComplete`, so we need to set it manually
// to `kForcedShow` to show the ring.
if (!is_animating && (view->progress_indicator_->progress() ==
ProgressIndicator::kProgressComplete)) {
view->show_progress_ring_after_animation_ = true;
return ProgressIndicator::kForcedShow;
}
return ProgressIndicator::kProgressComplete;
}
return controller->current_session()
->GetSnapshot(base::Time::Now())
.progress;
},
base::Unretained(this)));
progress_indicator_->SetInnerIconVisible(false);
progress_indicator_->SetInnerRingVisible(false);
progress_indicator_->SetOuterRingStrokeWidth(kProgressIndicatorThickness);
progress_indicator_->SetColorId(cros_tokens::kCrosRefPrimary70);
tray_container()->layer()->Add(
progress_indicator_->CreateLayer(base::BindRepeating(
[](TrayContainer* view, ui::ColorId color_id) {
return view->GetColorProvider()->GetColor(color_id);
},
base::Unretained(tray_container()))));
UpdateProgressRing();
auto* controller = FocusModeController::Get();
SetVisiblePreferred(controller->in_focus_session() ||
controller->in_ending_moment());
tasks_observation_.Observe(&controller->tasks_model());
controller->AddObserver(this);
}
FocusModeTray::~FocusModeTray() {
if (bubble_) {
bubble_->bubble_view()->ResetDelegate();
}
tasks_observation_.Reset();
FocusModeController::Get()->RemoveObserver(this);
}
void FocusModeTray::ClickedOutsideBubble(const ui::LocatedEvent& event) {
Shelf* target_shelf =
Shelf::ForWindow(static_cast<aura::Window*>(event.target()));
auto* target_tray = target_shelf->status_area_widget()->focus_mode_tray();
// Do not reset the focus session if the located event is on a different
// `FocusModeTray` view.
if (shelf() != target_shelf && target_tray->EventTargetsTray(event)) {
CloseBubbleAndMaybeReset(/*should_reset=*/false);
return;
}
CloseBubble();
}
std::u16string FocusModeTray::GetAccessibleNameForTray() {
if (!session_snapshot_) {
return std::u16string();
}
return GetAccessibleTrayName(
session_snapshot_.value(),
FocusModeController::Get()->congratulatory_index());
}
std::u16string FocusModeTray::GetAccessibleNameForBubble() {
if (!session_snapshot_) {
return std::u16string();
}
auto* focus_mode_controller = FocusModeController::Get();
const FocusModeTask* selected_task =
focus_mode_controller->tasks_model().selected_task();
const std::u16string task_title =
selected_task ? base::UTF8ToUTF16(selected_task->title)
: std::u16string();
return GetAccessibleBubbleName(session_snapshot_.value(), task_title,
focus_mode_controller->congratulatory_index());
}
void FocusModeTray::HideBubbleWithView(const TrayBubbleView* bubble_view) {
if (bubble_->bubble_view() == bubble_view) {
CloseBubble();
}
}
void FocusModeTray::HideBubble(const TrayBubbleView* bubble_view) {
if (bubble_->bubble_view() == bubble_view) {
CloseBubble();
}
}
TrayBubbleView* FocusModeTray::GetBubbleView() {
return bubble_ ? bubble_->bubble_view() : nullptr;
}
void FocusModeTray::CloseBubbleInternal() {
CloseBubbleAndMaybeReset(/*should_reset=*/true);
}
void FocusModeTray::ShowBubble() {
if (bubble_) {
return;
}
auto* controller = FocusModeController::Get();
CHECK(controller->current_session());
if (controller->in_ending_moment()) {
controller->EnablePersistentEnding();
AnchoredNudgeManager::Get()->MaybeRecordNudgeAction(
NudgeCatalogName::kFocusModeEndingMomentNudge);
}
auto bubble_view =
std::make_unique<TrayBubbleView>(CreateInitParamsForTrayBubble(
/*tray=*/this, /*anchor_to_shelf_corner=*/false));
bubble_view_container_ =
bubble_view->AddChildView(std::make_unique<views::BoxLayoutView>());
bubble_view_container_->SetOrientation(
views::BoxLayout::Orientation::kVertical);
bubble_view_container_->SetBorder(
views::CreateEmptyBorder(gfx::Insets(kBubbleInset)));
bubble_view_container_->SetBetweenChildSpacing(kBubbleInset);
countdown_view_ = bubble_view_container_->AddChildView(
std::make_unique<FocusModeCountdownView>(/*include_end_button=*/true));
ending_moment_view_ = bubble_view_container_->AddChildView(
std::make_unique<FocusModeEndingMomentView>());
session_snapshot_ =
controller->current_session()->GetSnapshot(base::Time::Now());
UpdateBubbleViews(session_snapshot_.value());
bubble_ = std::make_unique<TrayBubbleWrapper>(this);
bubble_->ShowBubble(std::move(bubble_view));
SetIsActive(true);
progress_indicator_->layer()->SetOpacity(0);
UpdateProgressRing();
controller->tasks_model().RequestUpdate();
}
void FocusModeTray::UpdateTrayItemColor(bool is_active) {
UpdateTrayIcon();
}
void FocusModeTray::OnThemeChanged() {
TrayBackgroundView::OnThemeChanged();
UpdateTrayIcon();
}
void FocusModeTray::OnAnimationEnded() {
TrayBackgroundView::OnAnimationEnded();
// The bounce-in animation can happen on a session start or on an ending
// moment start. Only for the bounce-in animation during the ending moment, we
// will set `bounce_in_animation_finished_` to tell the progress callback the
// animation was ended.
auto* controller = FocusModeController::Get();
if (!visible_preferred() || !controller->in_ending_moment()) {
return;
}
bounce_in_animation_finished_ = true;
controller->MaybeShowEndingMomentNudge();
}
void FocusModeTray::OnFocusModeChanged(bool in_focus_session) {
UpdateProgressRing();
show_progress_ring_after_animation_ = false;
progress_ring_update_threshold_ = 0.0;
auto* focus_mode_controller = FocusModeController::Get();
auto current_session = focus_mode_controller->current_session();
if (!current_session) {
session_snapshot_.reset();
return;
}
session_snapshot_ = current_session->GetSnapshot(base::Time::Now());
image_view_->SetTooltipText(
GetAccessibleTrayName(session_snapshot_.value(),
focus_mode_controller->congratulatory_index()));
if (bubble_) {
UpdateBubbleViews(session_snapshot_.value());
} else if (session_snapshot_->state == FocusModeSession::State::kEnding) {
bounce_in_animation_finished_ = false;
BounceInAnimation();
}
}
void FocusModeTray::OnTimerTick(
const FocusModeSession::Snapshot& session_snapshot) {
session_snapshot_ = session_snapshot;
image_view_->SetTooltipText(GetAccessibleTrayName(
session_snapshot_.value(),
FocusModeController::Get()->congratulatory_index()));
// We only paint the progress ring if it has reached the next threshold of
// progress. This is to try and decrease power usage of Focus mode when the
// user is idling and there are no required paints in the display.
if (session_snapshot_->progress >= progress_ring_update_threshold_) {
UpdateProgressRing();
// Change the next progress step into a percentage threshold.
progress_ring_update_threshold_ =
(double)focus_mode_util::GetNextProgressStep(
session_snapshot_->progress) /
focus_mode_util::kProgressIndicatorSteps;
}
MaybeUpdateCountdownViewUI(session_snapshot);
}
void FocusModeTray::OnActiveSessionDurationChanged(
const FocusModeSession::Snapshot& session_snapshot) {
session_snapshot_ = session_snapshot;
image_view_->SetTooltipText(GetAccessibleTrayName(
session_snapshot_.value(),
FocusModeController::Get()->congratulatory_index()));
UpdateProgressRing();
progress_ring_update_threshold_ = 0.0;
MaybeUpdateCountdownViewUI(session_snapshot);
}
void FocusModeTray::OnSelectedTaskChanged(
const std::optional<FocusModeTask>& task) {
if (!bubble_) {
return;
}
// Task was either completed or cleared.
if (!task) {
selected_task_.reset();
if (!task_item_view_) {
// Task view is already gone. Nothing to do.
return;
}
if (task_item_view_->GetWasCompleted()) {
// Task was already completed and is in the process of being deleted.
return;
}
// Task was deleted.
OnClearTask();
return;
}
// A new task was picked or updated. Update the UI.
const std::string& task_title = task->title;
if (task_title.empty()) {
// Can't create a task view for an empty title.
return;
}
selected_task_ = task->task_id;
if (task_item_view_) {
// Assume that the title changed and try to update it.
task_item_view_->UpdateTitle(base::UTF8ToUTF16(task_title));
return;
}
CreateTaskItemView(task_title);
// We need to update the bubble after creating the `task_item_view_` so the
// widget bounds are updated and shows the view.
bubble_->bubble_view()->UpdateBubble();
}
void FocusModeTray::OnTasksUpdated(const std::vector<FocusModeTask>& tasks) {}
void FocusModeTray::OnTaskCompleted(const FocusModeTask& completed_task) {
// Initiate UI update to indicate that the task was completed.
if (!task_item_view_ || task_item_view_->GetWasCompleted()) {
return;
}
task_item_view_->UpdateStyleToCompleted();
OnClearTask();
}
void FocusModeTray::Layout(PassKey) {
LayoutSuperclass<views::View>(this);
// Position the progress indicator based on the position of the image view.
// The centered position inside of the tray container changes based on shelf
// orientation and tablet mode, but there is already logic to keep the image
// view centered that we can use.
gfx::Rect progress_bounds = gfx::Rect(views::View::ConvertRectToTarget(
/*source=*/image_view_,
/*target=*/tray_container(), image_view_->GetImageBounds()));
progress_bounds.Inset(kProgressIndicatorInsets);
progress_indicator_->layer()->SetBounds(progress_bounds);
}
const views::ImageButton* FocusModeTray::GetRadioButtonForTesting() const {
return task_item_view_->GetRadioButton();
}
const views::Label* FocusModeTray::GetTaskTitleForTesting() const {
return task_item_view_->GetTaskTitle();
}
void FocusModeTray::CreateTaskItemView(const std::string& task_title) {
if (task_title.empty()) {
return;
}
task_item_view_ =
bubble_view_container_->AddChildView(std::make_unique<TaskItemView>(
base::UTF8ToUTF16(task_title),
base::BindRepeating(&FocusModeTray::HandleCompleteTaskButton,
weak_ptr_factory_.GetWeakPtr())));
task_item_view_->SetProperty(views::kBoxLayoutFlexKey,
views::BoxLayoutFlexSpecification());
}
void FocusModeTray::UpdateTrayIcon() {
SkColor color = GetColorProvider()->GetColor(
is_active() ? cros_tokens::kCrosSysSystemOnPrimaryContainer
: cros_tokens::kCrosSysOnSurface);
image_view_->SetImage(CreateVectorIcon(kFocusModeLampIcon, color));
}
void FocusModeTray::FocusModeIconActivated(const ui::Event& event) {
if (bubble_ && bubble_->bubble_view()->GetVisible()) {
CloseBubble();
return;
}
ShowBubble();
}
void FocusModeTray::UpdateBubbleViews(
const FocusModeSession::Snapshot& session_snapshot) {
const bool is_ending_moment =
session_snapshot.state == FocusModeSession::State::kEnding;
countdown_view_->SetVisible(!is_ending_moment);
ending_moment_view_->SetVisible(is_ending_moment);
if (is_ending_moment) {
MaybeUpdateEndingMomentViewUI(session_snapshot);
} else {
MaybeUpdateCountdownViewUI(session_snapshot);
}
}
void FocusModeTray::MaybeUpdateCountdownViewUI(
const FocusModeSession::Snapshot& session_snapshot) {
if (countdown_view_ && countdown_view_->GetVisible() &&
session_snapshot.state == FocusModeSession::State::kOn) {
countdown_view_->UpdateUI(session_snapshot);
}
}
void FocusModeTray::MaybeUpdateEndingMomentViewUI(
const FocusModeSession::Snapshot& session_snapshot) {
if (ending_moment_view_ && ending_moment_view_->GetVisible()) {
ending_moment_view_->ShowEndingMomentContents(
FocusModeController::CanExtendSessionDuration(session_snapshot));
}
}
void FocusModeTray::HandleCompleteTaskButton() {
// The user clicked on the task complete button. Notify the model. UI updates
// happen in the model events.
if (!selected_task_.has_value()) {
// If there is no selected id, `OnClearTask()` should have been triggered
// already either by `OnTaskCompleted()` or `OnSelectedTaskChanged()`, so
// we can just return.
return;
}
FocusModeController::Get()->tasks_model().UpdateTask(
FocusModeTasksModel::TaskUpdate::CompletedUpdate(*selected_task_));
}
void FocusModeTray::OnClearTask() {
if (!selected_task_.has_value()) {
return;
}
selected_task_.reset();
if (!task_item_view_) {
return;
}
// We want to show the check icon and a strikethrough on the label for
// `kStartAnimationDelay` before removing `task_item_view_` from the
// bubble.
base::SequencedTaskRunner::GetCurrentDefault()->PostDelayedTask(
FROM_HERE,
base::BindOnce(&FocusModeTray::AnimateBubbleResize,
weak_ptr_factory_.GetWeakPtr()),
kStartAnimationDelay);
}
void FocusModeTray::OnBubbleResizeAnimationStarted() {
if (bubble_ && task_item_view_) {
auto* ptr = task_item_view_.get();
task_item_view_ = nullptr;
bubble_view_container_->RemoveChildViewT(ptr);
}
}
void FocusModeTray::OnBubbleResizeAnimationEnded() {
if (bubble_) {
bubble_->bubble_view()->UpdateBubble();
}
}
void FocusModeTray::AnimateBubbleResize() {
// If there is no `task_item_view_` or it has already been cleared, we should
// skip the animation.
if (!bubble_ || !task_item_view_) {
return;
}
// `remove_height` is the height of the `task_item_view_` and the spacing
// above it.
const int remove_height = task_item_view_->bounds().height() + kBubbleInset;
auto target_bounds = bubble_->bubble_view()->layer()->bounds();
target_bounds.Inset(gfx::Insets::TLBR(remove_height, 0, 0, 0));
views::AnimationBuilder()
.SetPreemptionStrategy(ui::LayerAnimator::IMMEDIATELY_SET_NEW_TARGET)
.OnStarted(base::BindOnce(&FocusModeTray::OnBubbleResizeAnimationStarted,
weak_ptr_factory_.GetWeakPtr()))
.OnEnded(base::BindOnce(&FocusModeTray::OnBubbleResizeAnimationEnded,
weak_ptr_factory_.GetWeakPtr()))
.Once()
.SetDuration(kTaskItemViewFadeOutDuration)
.SetBounds(bubble_->bubble_view()->layer(), target_bounds,
gfx::Tween::EASE_OUT);
}
void FocusModeTray::UpdateProgressRing() {
// Schedule a repaint of the indicator.
progress_indicator_->InvalidateLayer();
}
bool FocusModeTray::EventTargetsTray(const ui::LocatedEvent& event) const {
if (event.target() != GetWidget()->GetNativeWindow()) {
return false;
}
gfx::Point location_in_status_area = event.location();
views::View::ConvertPointFromWidget(this, &location_in_status_area);
return bounds().Contains(location_in_status_area);
}
void FocusModeTray::CloseBubbleAndMaybeReset(bool should_reset) {
if (!bubble_) {
return;
}
if (auto* bubble_view = bubble_->GetBubbleView()) {
bubble_view->ResetDelegate();
}
bubble_.reset();
countdown_view_ = nullptr;
ending_moment_view_ = nullptr;
task_item_view_ = nullptr;
bubble_view_container_ = nullptr;
SetIsActive(false);
progress_indicator_->layer()->SetOpacity(1);
UpdateProgressRing();
if (auto* controller = FocusModeController::Get();
!controller->in_focus_session() && should_reset) {
controller->ResetFocusSession();
}
}
BEGIN_METADATA(FocusModeTray)
END_METADATA
} // namespace ash