1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218
|
// 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/capture_mode/capture_mode_toast_controller.h"
#include <memory>
#include "ash/capture_mode/capture_mode_session.h"
#include "ash/capture_mode/capture_mode_util.h"
#include "ash/constants/ash_features.h"
#include "ash/public/cpp/shell_window_ids.h"
#include "ash/strings/grit/ash_strings.h"
#include "ash/system/toast/system_toast_view.h"
#include "base/strings/utf_string_conversions.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/compositor/layer.h"
#include "ui/gfx/geometry/rect.h"
namespace ash {
namespace {
constexpr int kToastSpacingFromBar = 8;
// Animation duration for updating the visibility of `capture_toast_widget_`.
constexpr base::TimeDelta kCaptureToastVisibilityChangeDuration =
base::Milliseconds(200);
// The duration that `capture_toast_widget_` remains visible after been created
// and there are no actions taken, after which the toast widget will be
// dismissed.
constexpr base::TimeDelta kDelayToDismissToast = base::Seconds(6);
std::u16string GetCaptureToastTextOnToastType(
CaptureToastType capture_toast_type) {
return capture_toast_type == CaptureToastType::kCameraPreview
? l10n_util::GetStringUTF16(
IDS_ASH_SCREEN_CAPTURE_SURFACE_TOO_SMALL_USER_NUDGE)
: l10n_util::GetStringUTF16(IDS_ASH_SUNFISH_EDUCATE_TOAST_MESSAGE);
}
// Returns the init params that will be used for the toast widget.
views::Widget::InitParams CreateWidgetParams(aura::Window* parent) {
views::Widget::InitParams params(
views::Widget::InitParams::NATIVE_WIDGET_OWNS_WIDGET,
views::Widget::InitParams::TYPE_POPUP);
params.opacity = views::Widget::InitParams::WindowOpacity::kTranslucent;
params.parent = parent;
params.name = "CaptureModeToastWidget";
params.accept_events = false;
return params;
}
} // namespace
CaptureModeToastController::CaptureModeToastController(
CaptureModeSession* session)
: capture_session_(session) {}
CaptureModeToastController::~CaptureModeToastController() {
// Widget needs to be closed immediately so it does not show in the
// screenshot.
if (capture_toast_widget_) {
capture_toast_widget_->CloseNow();
}
}
void CaptureModeToastController::ShowCaptureToast(
CaptureToastType capture_toast_type) {
current_toast_type_ = capture_toast_type;
const std::u16string capture_toast_text =
GetCaptureToastTextOnToastType(capture_toast_type);
if (!capture_toast_widget_) {
BuildCaptureToastWidget(capture_toast_text);
} else {
toast_contents_view_->SetText(capture_toast_text);
}
capture_mode_util::TriggerAccessibilityAlertSoon(
base::UTF16ToUTF8(capture_toast_text));
MaybeRepositionCaptureToast();
const bool did_visibility_change = capture_mode_util::SetWidgetVisibility(
capture_toast_widget_.get(), /*target_visibility=*/true,
capture_mode_util::AnimationParams{kCaptureToastVisibilityChangeDuration,
gfx::Tween::FAST_OUT_SLOW_IN,
/*apply_scale_up_animation=*/false});
// Only if the capture toast type is the `kCameraPreview`, the capture toast
// should be auto dismissed after `kDelayToDismissToast`.
if (did_visibility_change &&
capture_toast_type == CaptureToastType::kCameraPreview) {
capture_toast_dismiss_timer_.Start(
FROM_HERE, kDelayToDismissToast,
base::BindOnce(&CaptureModeToastController::MaybeDismissCaptureToast,
base::Unretained(this), capture_toast_type,
/*animate=*/true));
}
}
void CaptureModeToastController::MaybeDismissCaptureToast(
CaptureToastType capture_toast_type,
bool animate) {
if (!current_toast_type_) {
DCHECK(!capture_toast_widget_ ||
!capture_mode_util::GetWidgetCurrentVisibility(
capture_toast_widget_.get()));
return;
}
if (!capture_toast_widget_) {
DCHECK(!current_toast_type_);
return;
}
if (capture_toast_type != current_toast_type_)
return;
capture_toast_dismiss_timer_.Stop();
current_toast_type_.reset();
if (animate) {
capture_mode_util::SetWidgetVisibility(
capture_toast_widget_.get(), /*target_visibility=*/false,
capture_mode_util::AnimationParams{
kCaptureToastVisibilityChangeDuration, gfx::Tween::FAST_OUT_SLOW_IN,
/*apply_scale_up_animation=*/false});
return;
}
capture_toast_widget_->Hide();
}
void CaptureModeToastController::DismissCurrentToastIfAny() {
if (current_toast_type_)
MaybeDismissCaptureToast(*current_toast_type_, /*animate=*/false);
}
void CaptureModeToastController::MaybeRepositionCaptureToast() {
if (!capture_toast_widget_)
return;
auto* parent_window = capture_session_->current_root()->GetChildById(
kShellWindowId_MenuContainer);
if (capture_toast_widget_->GetNativeWindow()->parent() != parent_window) {
parent_window->AddChild(capture_toast_widget_->GetNativeWindow());
auto* layer = capture_toast_widget_->GetLayer();
// Any ongoing opacity animation should be committed when we reparent the
// toast in case that the residual animation continues in the old position
// after been repositioned to a new bounds.
layer->SetOpacity(layer->GetTargetOpacity());
}
capture_toast_widget_->SetBounds(CalculateToastWidgetBoundsInScreen());
}
ui::Layer* CaptureModeToastController::MaybeGetToastLayer() {
return capture_toast_widget_ ? capture_toast_widget_->GetLayer() : nullptr;
}
void CaptureModeToastController::OnWidgetDestroying(views::Widget* widget) {
toast_contents_view_ = nullptr;
if (capture_toast_widget_) {
capture_toast_widget_->RemoveObserver(this);
}
}
void CaptureModeToastController::BuildCaptureToastWidget(
const std::u16string& text) {
// Create the widget before init it to ensure that the `capture_toast_widget_`
// is available when the window gets added to the parent container.
capture_toast_widget_ = std::make_unique<views::Widget>();
capture_toast_widget_->Init(
CreateWidgetParams(capture_session_->current_root()->GetChildById(
kShellWindowId_MenuContainer)));
capture_toast_widget_->AddObserver(this);
toast_contents_view_ = capture_toast_widget_->SetContentsView(
std::make_unique<SystemToastView>(text));
// We animate the `capture_toast_widget_` explicitly in `ShowCaptureToast()`
// and `MaybeDismissCaptureToast()`. Any default visibility animations added
// by the widget's window should be disabled.
capture_toast_widget_->SetVisibilityAnimationTransition(
views::Widget::ANIMATE_NONE);
const auto toast_bounds_in_screen = CalculateToastWidgetBoundsInScreen();
capture_toast_widget_->SetBounds(toast_bounds_in_screen);
toast_contents_view_->SetPaintToLayer();
toast_contents_view_->layer()->SetRoundedCornerRadius(
gfx::RoundedCornersF(toast_bounds_in_screen.height() / 2.f));
capture_toast_widget_->Show();
// The widget is created initially with 0 opacity, and will animate to be
// fully visible when `ShowCaptureToast` is called.
capture_toast_widget_->GetLayer()->SetOpacity(0);
}
gfx::Rect CaptureModeToastController::CalculateToastWidgetBoundsInScreen()
const {
DCHECK(toast_contents_view_);
gfx::Rect bounds;
const auto preferred_size = toast_contents_view_->GetPreferredSize();
bounds = gfx::Rect(preferred_size);
// Align the centers of the capture mode bar and the toast horizontally.
const auto bar_widget_bounds_in_screen =
capture_session_->GetCaptureModeBarWidget()->GetWindowBoundsInScreen();
bounds.set_x(bar_widget_bounds_in_screen.CenterPoint().x() -
preferred_size.width() / 2);
bounds.set_y(bar_widget_bounds_in_screen.y() - bounds.height() -
kToastSpacingFromBar);
return bounds;
}
} // namespace ash
|