#include "ash/capture_mode/capture_mode_session.h"
#include <memory>
#include <string>
#include <tuple>
#include <utility>
#include <vector>
#include "ash/accessibility/accessibility_controller.h"
#include "ash/accessibility/magnifier/magnifier_glass.h"
#include "ash/capture_mode/action_button_container_view.h"
#include "ash/capture_mode/action_button_view.h"
#include "ash/capture_mode/capture_label_view.h"
#include "ash/capture_mode/capture_mode_behavior.h"
#include "ash/capture_mode/capture_mode_camera_controller.h"
#include "ash/capture_mode/capture_mode_camera_preview_view.h"
#include "ash/capture_mode/capture_mode_constants.h"
#include "ash/capture_mode/capture_mode_controller.h"
#include "ash/capture_mode/capture_mode_session_focus_cycler.h"
#include "ash/capture_mode/capture_mode_settings_view.h"
#include "ash/capture_mode/capture_mode_source_view.h"
#include "ash/capture_mode/capture_mode_type_view.h"
#include "ash/capture_mode/capture_mode_types.h"
#include "ash/capture_mode/capture_mode_util.h"
#include "ash/capture_mode/capture_region_overlay_controller.h"
#include "ash/capture_mode/capture_window_observer.h"
#include "ash/capture_mode/disclaimer_view.h"
#include "ash/capture_mode/folder_selection_dialog_controller.h"
#include "ash/capture_mode/normal_capture_bar_view.h"
#include "ash/capture_mode/recording_type_menu_view.h"
#include "ash/capture_mode/search_results_panel.h"
#include "ash/capture_mode/user_nudge_controller.h"
#include "ash/constants/ash_pref_names.h"
#include "ash/constants/url_constants.h"
#include "ash/display/mouse_cursor_event_filter.h"
#include "ash/display/window_tree_host_manager.h"
#include "ash/keyboard/ui/keyboard_ui_controller.h"
#include "ash/public/cpp/capture_mode/capture_mode_api.h"
#include "ash/public/cpp/new_window_delegate.h"
#include "ash/public/cpp/resources/grit/ash_public_unscaled_resources.h"
#include "ash/public/cpp/shell_window_ids.h"
#include "ash/resources/vector_icons/vector_icons.h"
#include "ash/root_window_controller.h"
#include "ash/scanner/scanner_controller.h"
#include "ash/scanner/scanner_disclaimer.h"
#include "ash/scanner/scanner_metrics.h"
#include "ash/shell.h"
#include "ash/shell_delegate.h"
#include "ash/strings/grit/ash_strings.h"
#include "ash/style/ash_color_id.h"
#include "ash/style/color_util.h"
#include "ash/style/icon_button.h"
#include "ash/style/pill_button.h"
#include "ash/style/tab_slider_button.h"
#include "ash/utility/cursor_setter.h"
#include "ash/wm/tablet_mode/tablet_mode_controller.h"
#include "ash/wm/window_dimmer.h"
#include "base/check.h"
#include "base/check_op.h"
#include "base/functional/bind.h"
#include "base/functional/callback.h"
#include "base/functional/callback_helpers.h"
#include "base/location.h"
#include "base/memory/raw_ptr.h"
#include "base/memory/weak_ptr.h"
#include "base/strings/utf_string_conversions.h"
#include "base/task/single_thread_task_runner.h"
#include "base/time/time.h"
#include "base/timer/timer.h"
#include "cc/paint/paint_flags.h"
#include "components/prefs/pref_service.h"
#include "third_party/abseil-cpp/absl/cleanup/cleanup.h"
#include "third_party/skia/include/core/SkPath.h"
#include "third_party/skia/include/core/SkPathBuilder.h"
#include "ui/aura/client/aura_constants.h"
#include "ui/aura/client/capture_client.h"
#include "ui/aura/env.h"
#include "ui/aura/window.h"
#include "ui/aura/window_delegate.h"
#include "ui/aura/window_observer.h"
#include "ui/aura/window_tracker.h"
#include "ui/base/cursor/cursor_factory.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/base/metadata/metadata_impl_macros.h"
#include "ui/base/resource/resource_bundle.h"
#include "ui/color/color_id.h"
#include "ui/compositor/layer.h"
#include "ui/compositor/layer_animation_element.h"
#include "ui/compositor/paint_recorder.h"
#include "ui/compositor/scoped_layer_animation_settings.h"
#include "ui/display/screen.h"
#include "ui/events/event_handler.h"
#include "ui/events/types/event_type.h"
#include "ui/gfx/animation/tween.h"
#include "ui/gfx/canvas.h"
#include "ui/gfx/geometry/insets.h"
#include "ui/gfx/geometry/point.h"
#include "ui/gfx/geometry/rect.h"
#include "ui/gfx/geometry/transform_util.h"
#include "ui/gfx/scoped_canvas.h"
#include "ui/gfx/shadow_value.h"
#include "ui/gfx/skia_paint_util.h"
#include "ui/views/animation/animation_builder.h"
#include "ui/views/background.h"
#include "ui/views/controls/label.h"
#include "ui/views/view_utils.h"
#include "ui/views/widget/widget.h"
#include "ui/views/widget/widget_delegate.h"
#include "ui/wm/core/coordinate_conversion.h"
#include "ui/wm/core/cursor_util.h"
namespace ash {
namespace {
constexpr int kAffordanceCircleRadiusDp = 4;
constexpr int kAffordanceCircleTouchHitRadiusDp = 16;
constexpr MagnifierGlass::Params kMagnifierParams{
2.f,
60,
2,
0,
SK_ColorWHITE,
SK_ColorTRANSPARENT,
gfx::ShadowValue(gfx::Vector2d(0, 1),
2,
SkColorSetARGB(0x4C, 0x00, 0x00, 0x00)),
gfx::ShadowValue(gfx::Vector2d(0, 1),
3,
SkColorSetARGB(0x26, 0x00, 0x00, 0x00))};
constexpr int kSizeLabelBorderRadius = 4;
constexpr int kSizeLabelHorizontalPadding = 8;
constexpr int kRegionAffordanceCircleShadow2Blur = 6;
constexpr gfx::ShadowValue kRegionOutlineShadow(gfx::Vector2d(0, 0),
2,
SkColorSetARGB(41, 0, 0, 0));
constexpr gfx::ShadowValue kRegionAffordanceCircleShadow1(
gfx::Vector2d(0, 1),
2,
SkColorSetARGB(76, 0, 0, 0));
constexpr gfx::ShadowValue kRegionAffordanceCircleShadow2(
gfx::Vector2d(0, 2),
kRegionAffordanceCircleShadow2Blur,
SkColorSetARGB(38, 0, 0, 0));
constexpr int kFocusRingStrokeWidthDp = 2;
constexpr int kFocusRingSpacingDp = 2;
constexpr int kDamageOutsetDp = capture_mode::kCaptureRegionBorderStrokePx +
kAffordanceCircleRadiusDp +
kRegionAffordanceCircleShadow2Blur;
constexpr int kDamageWithGlowOutsetDp =
capture_mode::kRegionGlowMaxOutsetDp +
2 * static_cast<int>(capture_mode::kRegionGlowAnimationMaxBlurDp);
constexpr int kCaptureRegionMinimumPaddingDp = 16;
constexpr base::TimeDelta kCaptureLabelCountdownStartDuration =
base::Milliseconds(267);
constexpr base::TimeDelta kCaptureWidgetFadeOutDuration =
base::Milliseconds(167);
constexpr base::TimeDelta kCaptureShieldFadeOutDuration =
base::Milliseconds(333);
constexpr float kLabelScaleUpOnCountdown = 1.2;
constexpr base::TimeDelta kCaptureLabelRegionPhaseChangeDuration =
base::Milliseconds(167);
constexpr base::TimeDelta kCaptureLabelRegionPhaseChangeDelay =
base::Milliseconds(67);
constexpr float kLabelScaleDownOnPhaseChange = 0.8;
constexpr float kRegionDefaultRatio = 0.24f;
constexpr int kSunfishModeCaptureRegionRadiusDp = 16;
constexpr int kSunfishModeCaptureRegionBorderWidthDp = 6;
constexpr int kSunfishRegionFocusRingRadiusDp = 18;
constexpr int kSunfishRegionCornerFocusRingRadiusDp = 16;
constexpr int kSunfishRegionRoundedCornerOffsetDp =
kSunfishModeCaptureRegionRadiusDp / 2;
constexpr int kSunfishRegionDamageOutsetDp =
kSunfishModeCaptureRegionBorderWidthDp +
kSunfishRegionCornerFocusRingRadiusDp;
constexpr base::TimeDelta kScannerActionButtonFadeInDuration =
base::Milliseconds(100);
constexpr base::TimeDelta kImageSearchRequestStartDelay = base::Seconds(1);
enum class CaptureRegionWidgetAlignment {
kCenter,
kRight,
};
bool SetMouseWarpEnabled(bool enable) {
auto* mouse_cursor_filter = Shell::Get()->mouse_cursor_filter();
const bool old_value = mouse_cursor_filter->mouse_warp_enabled();
mouse_cursor_filter->set_mouse_warp_enabled(enable);
return old_value;
}
gfx::Rect GetRectEnclosingPoints(const std::vector<gfx::Point>& points,
aura::Window* root) {
DCHECK_GE(points.size(), 2u);
CHECK(root);
int x = INT_MAX;
int y = INT_MAX;
int right = INT_MIN;
int bottom = INT_MIN;
for (const gfx::Point& point : points) {
x = std::min(point.x(), x);
y = std::min(point.y(), y);
right = std::max(point.x(), right);
bottom = std::max(point.y(), bottom);
}
gfx::Rect new_rect(x, y, right - x, bottom - y);
new_rect.Intersect(root->bounds());
return new_rect;
}
views::Widget::InitParams CreateWidgetParams(aura::Window* parent,
const gfx::Rect& bounds,
const std::string& name) {
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.bounds = bounds;
params.name = name;
return params;
}
ui::Cursor GetCursorForFullscreenOrWindowCapture(bool capture_image) {
const display::Display display =
display::Screen::Get()->GetDisplayNearestWindow(
capture_mode_util::GetPreferredRootWindow());
const float device_scale_factor = display.device_scale_factor();
const gfx::ImageSkia* icon =
ui::ResourceBundle::GetSharedInstance().GetImageSkiaNamed(
capture_image ? IDR_CAPTURE_IMAGE_CURSOR : IDR_CAPTURE_VIDEO_CURSOR);
SkBitmap bitmap = *icon->bitmap();
gfx::Point hotspot(bitmap.width() / 2, bitmap.height() / 2);
wm::ScaleAndRotateCursorBitmapAndHotpoint(
device_scale_factor, display.panel_rotation(), &bitmap, &hotspot);
ui::Cursor cursor = ui::Cursor::NewCustom(
std::move(bitmap), std::move(hotspot), device_scale_factor);
cursor.SetPlatformCursor(ui::CursorFactory::GetInstance()->CreateImageCursor(
cursor.type(), cursor.custom_bitmap(), cursor.custom_hotspot(),
cursor.image_scale_factor()));
return cursor;
}
ui::mojom::CursorType GetCursorTypeForFineTunePosition(
FineTunePosition position) {
switch (position) {
case FineTunePosition::kTopLeftVertex:
return ui::mojom::CursorType::kNorthWestResize;
case FineTunePosition::kBottomRightVertex:
return ui::mojom::CursorType::kSouthEastResize;
case FineTunePosition::kTopEdge:
case FineTunePosition::kBottomEdge:
return ui::mojom::CursorType::kNorthSouthResize;
case FineTunePosition::kTopRightVertex:
return ui::mojom::CursorType::kNorthEastResize;
case FineTunePosition::kBottomLeftVertex:
return ui::mojom::CursorType::kSouthWestResize;
case FineTunePosition::kLeftEdge:
case FineTunePosition::kRightEdge:
return ui::mojom::CursorType::kEastWestResize;
case FineTunePosition::kCenter:
return ui::mojom::CursorType::kMove;
default:
return ui::mojom::CursorType::kCell;
}
}
int GetArrowKeyPressChange(int event_flags) {
if ((event_flags & ui::EF_SHIFT_DOWN) != 0)
return capture_mode::kShiftArrowKeyboardRegionChangeDp;
if ((event_flags & ui::EF_CONTROL_DOWN) != 0)
return capture_mode::kCtrlArrowKeyboardRegionChangeDp;
return capture_mode::kArrowKeyboardRegionChangeDp;
}
void UpdateFloatingPanelBoundsIfNeeded() {
Shell::Get()->accessibility_controller()->UpdateFloatingPanelBoundsIfNeeded();
}
views::Widget* GetCameraPreviewWidget() {
return CaptureModeController::Get()
->camera_controller()
->camera_preview_widget();
}
bool ShouldPassEventToCameraPreview(ui::LocatedEvent* event) {
auto* controller = CaptureModeController::Get();
if (controller->is_recording_in_progress())
return false;
auto* camera_preview_widget = GetCameraPreviewWidget();
if (!camera_preview_widget || !camera_preview_widget->IsVisible())
return false;
if (controller->camera_controller()->is_drag_in_progress())
return true;
auto* target = static_cast<aura::Window*>(event->target());
if (camera_preview_widget->GetNativeWindow()->Contains(target))
return true;
return false;
}
bool IsWidgetOverlappedWithCameraPreview(views::Widget* widget) {
if (CaptureModeController::Get()->is_recording_in_progress())
return false;
auto* camera_preview_widget = GetCameraPreviewWidget();
if (!camera_preview_widget)
return false;
return camera_preview_widget->IsVisible() &&
camera_preview_widget->GetLayer()->GetTargetOpacity() > 0.f &&
camera_preview_widget->GetWindowBoundsInScreen().Intersects(
widget->GetWindowBoundsInScreen());
}
int GetHitTestRadiusForFineTunePosition(bool is_touch,
FineTunePosition position,
CaptureModeBehavior* active_behavior) {
if (is_touch) {
return kAffordanceCircleTouchHitRadiusDp;
}
if (active_behavior->ShouldPaintSunfishCaptureRegion() &&
capture_mode_util::IsCornerFineTunePosition(position)) {
return kSunfishRegionCornerFocusRingRadiusDp;
}
return kAffordanceCircleRadiusDp;
}
gfx::Rect GetHitTestRectAroundPoint(gfx::Point point, int hit_radius) {
return gfx::Rect(point.x() - hit_radius, point.y() - hit_radius,
hit_radius * 2, hit_radius * 2);
}
gfx::Rect GetHitTestRectForFineTunePosition(
const gfx::Rect& capture_region_in_screen,
FineTunePosition position,
bool is_touch,
CaptureModeBehavior* active_behavior) {
const int hit_radius =
GetHitTestRadiusForFineTunePosition(is_touch, position, active_behavior);
gfx::Rect corner_adjusted_capture_region = capture_region_in_screen;
if (active_behavior->ShouldPaintSunfishCaptureRegion() &&
capture_mode_util::IsCornerFineTunePosition(position)) {
corner_adjusted_capture_region.Inset(kSunfishRegionRoundedCornerOffsetDp);
}
switch (position) {
case FineTunePosition::kTopLeftVertex:
return GetHitTestRectAroundPoint(corner_adjusted_capture_region.origin(),
hit_radius);
case FineTunePosition::kTopRightVertex:
return GetHitTestRectAroundPoint(
corner_adjusted_capture_region.top_right(), hit_radius);
case FineTunePosition::kBottomRightVertex:
return GetHitTestRectAroundPoint(
corner_adjusted_capture_region.bottom_right(), hit_radius);
case FineTunePosition::kBottomLeftVertex:
return GetHitTestRectAroundPoint(
corner_adjusted_capture_region.bottom_left(), hit_radius);
case FineTunePosition::kTopEdge:
case FineTunePosition::kBottomEdge: {
const gfx::Size horizontal_size(
capture_region_in_screen.width() - 2 * hit_radius, 2 * hit_radius);
const int horizontal_x = capture_region_in_screen.x() + hit_radius;
const int horizontal_y =
position == FineTunePosition::kTopEdge
? capture_region_in_screen.y() - hit_radius
: capture_region_in_screen.bottom() - hit_radius;
return gfx::Rect(gfx::Point(horizontal_x, horizontal_y), horizontal_size);
}
case FineTunePosition::kLeftEdge:
case FineTunePosition::kRightEdge: {
const gfx::Size vertical_size(
2 * hit_radius, capture_region_in_screen.height() - 2 * hit_radius);
const int vertical_x =
position == FineTunePosition::kLeftEdge
? capture_region_in_screen.x() - hit_radius
: capture_region_in_screen.right() - hit_radius;
const int vertical_y = capture_region_in_screen.y() + hit_radius;
return gfx::Rect(gfx::Point(vertical_x, vertical_y), vertical_size);
}
default:
NOTREACHED();
}
}
gfx::Rect CalculateRegionEdgeBounds(
const gfx::Size& preferred_size,
const gfx::Rect& capture_bar_root_bounds,
const gfx::Rect& capture_region_root_bounds,
const gfx::Rect& other_container_root_bounds,
aura::Window* root,
CaptureRegionWidgetAlignment preferred_alignment) {
enum class Direction { kBottom, kTop, kLeft, kRight };
gfx::Rect initial_bounds(preferred_size);
std::vector<Direction> directions;
switch (preferred_alignment) {
case CaptureRegionWidgetAlignment::kCenter:
initial_bounds.set_x(capture_region_root_bounds.CenterPoint().x() -
preferred_size.width() / 2);
directions = {Direction::kTop, Direction::kBottom, Direction::kLeft,
Direction::kRight};
break;
case CaptureRegionWidgetAlignment::kRight:
initial_bounds.set_x(capture_region_root_bounds.right() -
preferred_size.width());
directions = {Direction::kBottom, Direction::kTop, Direction::kLeft,
Direction::kRight};
break;
}
initial_bounds.set_y(capture_region_root_bounds.CenterPoint().y() -
preferred_size.height() / 2);
const int spacing = CaptureModeSession::kCaptureButtonDistanceFromRegionDp;
gfx::Rect widget_bounds;
for (Direction direction : directions) {
widget_bounds = initial_bounds;
switch (direction) {
case Direction::kBottom:
widget_bounds.set_y(capture_region_root_bounds.bottom() + spacing);
break;
case Direction::kTop:
widget_bounds.set_y(capture_region_root_bounds.y() - spacing -
preferred_size.height());
break;
case Direction::kLeft:
widget_bounds.set_x(capture_region_root_bounds.x() - spacing -
preferred_size.width());
break;
case Direction::kRight:
widget_bounds.set_x(capture_region_root_bounds.right() + spacing);
break;
}
bool intersects_action_buttons =
!other_container_root_bounds.IsEmpty() &&
widget_bounds.Intersects(other_container_root_bounds);
if (!widget_bounds.Intersects(capture_bar_root_bounds) &&
!intersects_action_buttons && root->bounds().Contains(widget_bounds)) {
return widget_bounds;
}
}
widget_bounds.set_size(preferred_size);
widget_bounds.set_x(capture_bar_root_bounds.CenterPoint().x() -
preferred_size.width() / 2);
widget_bounds.set_y(capture_bar_root_bounds.y() -
CaptureModeSession::kCaptureButtonDistanceFromRegionDp -
preferred_size.height());
if (!other_container_root_bounds.IsEmpty() &&
widget_bounds.Intersects(other_container_root_bounds)) {
widget_bounds.set_y(widget_bounds.y() -
CaptureModeSession::kCaptureButtonDistanceFromRegionDp -
preferred_size.height());
}
return widget_bounds;
}
void HideWidgetImmediately(views::Widget* widget) {
widget->GetNativeWindow()->SetProperty(aura::client::kAnimationsDisabledKey,
true);
widget->Hide();
}
}
class CaptureModeSession::ParentContainerObserver
: public aura::WindowObserver {
public:
ParentContainerObserver(aura::Window* parent_container,
CaptureModeSession* capture_mode_session)
: parent_container_(parent_container),
capture_mode_session_(capture_mode_session) {
parent_container_->AddObserver(this);
}
ParentContainerObserver(const ParentContainerObserver&) = delete;
ParentContainerObserver& operator=(const ParentContainerObserver&) = delete;
~ParentContainerObserver() override {
if (parent_container_)
parent_container_->RemoveObserver(this);
}
void OnWindowAdded(aura::Window* window) override {
DeferredRefreshStackingOrder();
}
void OnWindowRemoved(aura::Window* window) override {
DeferredRefreshStackingOrder();
}
void OnWindowDestroying(aura::Window* window) override {
parent_container_->RemoveObserver(this);
parent_container_ = nullptr;
}
private:
void DeferredRefreshStackingOrder() {
base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
FROM_HERE,
base::BindOnce(
[](base::WeakPtr<CaptureModeSession> capture_mode_session) {
if (capture_mode_session) {
capture_mode_session->RefreshStackingOrder();
capture_mode_session->MaybeUpdateCaptureUisOpacity();
}
},
capture_mode_session_->weak_ptr_factory_.GetWeakPtr()));
}
raw_ptr<aura::Window> parent_container_;
const raw_ptr<CaptureModeSession> capture_mode_session_;
};
CaptureModeSession::CaptureModeSession(CaptureModeController* controller,
CaptureModeBehavior* active_behavior)
: BaseCaptureModeSession(controller, active_behavior, SessionType::kReal),
magnifier_glass_(kMagnifierParams),
cursor_setter_(std::make_unique<CursorSetter>()),
focus_cycler_(std::make_unique<CaptureModeSessionFocusCycler>(this)),
capture_toast_controller_(this) {
CHECK(current_root_);
}
CaptureModeSession::~CaptureModeSession() = default;
void CaptureModeSession::A11yAlertCaptureSource(bool trigger_now) {
auto* controller = CaptureModeController::Get();
const bool is_capturing_image = controller->type() == CaptureModeType::kImage;
std::string message;
switch (controller->source()) {
case CaptureModeSource::kFullscreen:
message = l10n_util::GetStringUTF8(
is_capturing_image
? IDS_ASH_SCREEN_CAPTURE_ALERT_FULLSCREEN_SCREENSHOT
: IDS_ASH_SCREEN_CAPTURE_ALERT_FULLSCREEN_RECORD);
break;
case CaptureModeSource::kRegion:
if (!controller->user_capture_region().IsEmpty()) {
message = l10n_util::GetStringUTF8(
is_capturing_image ? IDS_ASH_SCREEN_CAPTURE_ALERT_REGION_SCREENSHOT
: IDS_ASH_SCREEN_CAPTURE_ALERT_REGION_RECORD);
}
break;
case CaptureModeSource::kWindow:
if (auto* window = GetSelectedWindow()) {
message = l10n_util::GetStringFUTF8(
is_capturing_image ? IDS_ASH_SCREEN_CAPTURE_ALERT_WINDOW_SCREENSHOT
: IDS_ASH_SCREEN_CAPTURE_ALERT_WINDOW_RECORD,
window->GetTitle());
}
break;
}
if (!message.empty()) {
if (trigger_now)
capture_mode_util::TriggerAccessibilityAlert(message);
else
capture_mode_util::TriggerAccessibilityAlertSoon(message);
}
}
void CaptureModeSession::SetSettingsMenuShown(bool shown, bool by_key_event) {
capture_mode_bar_view_->SetSettingsMenuShown(shown);
if (!shown) {
capture_mode_settings_widget_.reset();
capture_mode_settings_view_ = nullptr;
if (capture_label_widget_ && !capture_label_widget_->IsVisible())
capture_label_widget_->Show();
return;
}
if (!capture_mode_settings_widget_) {
SetRecordingTypeMenuShown(false);
auto* parent = GetParentContainer(current_root_);
capture_mode_settings_widget_ = std::make_unique<views::Widget>();
capture_toast_controller_.DismissCurrentToastIfAny();
capture_mode_settings_widget_->Init(CreateWidgetParams(
parent, CaptureModeSettingsView::GetBounds(capture_mode_bar_view_),
"CaptureModeSettingsWidget"));
capture_mode_settings_view_ =
capture_mode_settings_widget_->SetContentsView(
std::make_unique<CaptureModeSettingsView>(this, active_behavior_));
OnCaptureFolderMayHaveChanged();
parent->layer()->StackAtTop(capture_mode_settings_widget_->GetLayer());
focus_cycler_->OnMenuOpened(
capture_mode_settings_widget_.get(),
CaptureModeSessionFocusCycler::FocusGroup::kPendingSettings,
by_key_event);
if (capture_label_widget_ && capture_label_widget_->IsVisible()) {
if (capture_mode_settings_widget_->GetWindowBoundsInScreen().Intersects(
capture_label_widget_->GetWindowBoundsInScreen())) {
capture_label_widget_->Hide();
}
}
std::u16string capture_mode_settings_a11y_title =
l10n_util::GetStringUTF16(IDS_ASH_SCREEN_CAPTURE_SETTINGS_A11Y_TITLE);
capture_mode_settings_widget_->GetNativeWindow()->SetTitle(
capture_mode_settings_a11y_title);
capture_mode_settings_widget_->widget_delegate()->SetAccessibleTitle(
capture_mode_settings_a11y_title);
capture_mode_settings_widget_->Show();
}
}
void CaptureModeSession::OpenFolderSelectionDialog() {
DCHECK(!folder_selection_dialog_controller_);
folder_selection_dialog_controller_ =
std::make_unique<FolderSelectionDialogController>(this,
current_root_);
MaybeUpdateCameraPreviewBounds();
}
bool CaptureModeSession::IsInCountDownAnimation() const {
if (is_shutting_down_)
return false;
return capture_label_view_ && capture_label_view_->IsInCountDownAnimation();
}
bool CaptureModeSession::IsBarAnchoredToWindow() const {
return capture_window_observer_ &&
capture_window_observer_->bar_anchored_to_window();
}
void CaptureModeSession::UpdateCursor(const gfx::Point& location_in_screen,
bool is_touch) {
if (is_shutting_down_ || !cursor_setter_) {
return;
}
if (display::Screen::Get()->InTabletMode() &&
!Shell::Get()->tablet_mode_controller()->IsInDevTabletMode()) {
cursor_setter_->HideCursor();
return;
}
auto* root_window = capture_mode_util::GetPreferredRootWindow();
if (IsInCountDownAnimation()) {
cursor_setter_->UpdateCursor(root_window, ui::mojom::CursorType::kPointer);
return;
}
if (should_pass_located_event_to_camera_preview_) {
cursor_setter_->UpdateCursor(root_window, ui::mojom::CursorType::kPointer);
return;
}
if (capture_label_view_) {
const bool is_event_on_capture_button =
capture_label_widget_->GetWindowBoundsInScreen().Contains(
location_in_screen) &&
capture_label_view_->ShouldHandleEvent();
if (is_event_on_capture_button) {
cursor_setter_->UpdateCursor(root_window, ui::mojom::CursorType::kHand);
return;
}
}
const bool is_event_on_action_button =
action_container_widget_ &&
action_container_widget_->GetLayer()->GetTargetOpacity() &&
action_container_widget_->GetWindowBoundsInScreen().Contains(
location_in_screen);
if (is_event_on_action_button) {
cursor_setter_->UpdateCursor(root_window, ui::mojom::CursorType::kHand);
return;
}
const bool is_event_on_capture_bar =
capture_mode_bar_widget_->GetLayer()->GetTargetOpacity() &&
capture_mode_bar_widget_->GetWindowBoundsInScreen().Contains(
location_in_screen);
if (capture_mode_settings_widget_ || is_event_on_capture_bar ||
recording_type_menu_widget_) {
cursor_setter_->UpdateCursor(root_window, ui::mojom::CursorType::kPointer);
return;
}
const CaptureModeSource source = controller_->source();
if (source == CaptureModeSource::kWindow &&
!IsPointOverSelectedWindow(location_in_screen)) {
cursor_setter_->UpdateCursor(root_window, ui::mojom::CursorType::kPointer);
return;
}
if (source == CaptureModeSource::kFullscreen ||
source == CaptureModeSource::kWindow) {
const CaptureModeType capture_mode_type = controller_->type();
cursor_setter_->UpdateCursor(
root_window,
GetCursorForFullscreenOrWindowCapture(capture_mode_type ==
CaptureModeType::kImage),
static_cast<int>(capture_mode_type));
return;
}
DCHECK_EQ(source, CaptureModeSource::kRegion);
if (fine_tune_position_ != FineTunePosition::kNone) {
if (capture_mode_util::IsCornerFineTunePosition(fine_tune_position_)) {
cursor_setter_->HideCursor();
} else {
cursor_setter_->UpdateCursor(
root_window, GetCursorTypeForFineTunePosition(fine_tune_position_));
}
} else {
cursor_setter_->UpdateCursor(
root_window, GetCursorTypeForFineTunePosition(
GetFineTunePosition(location_in_screen, is_touch)));
}
}
void CaptureModeSession::HighlightWindowForTab(aura::Window* window) {
DCHECK(window);
DCHECK_EQ(CaptureModeSource::kWindow, controller_->source());
MaybeChangeRoot(window->GetRootWindow(), false);
capture_window_observer_->SetSelectedWindow(window, true,
false);
}
void CaptureModeSession::MaybeUpdateSettingsBounds() {
if (!capture_mode_settings_widget_) {
return;
}
capture_mode_settings_view_->contents()->SetSize(
capture_mode_settings_view_->contents()->GetPreferredSize());
capture_mode_settings_widget_->SetBounds(CaptureModeSettingsView::GetBounds(
capture_mode_bar_view_, capture_mode_settings_view_));
}
void CaptureModeSession::MaybeUpdateCaptureUisOpacity(
std::optional<gfx::Point> cursor_screen_location) {
if (is_shutting_down_) {
return;
}
if (!cursor_screen_location) {
cursor_screen_location = display::Screen::Get()->GetCursorScreenPoint();
}
base::flat_map<views::Widget*, /*opacity=*/float> widget_opacity_map;
if (capture_mode_bar_widget_) {
widget_opacity_map[capture_mode_bar_widget_.get()] = 1.f;
}
if (capture_label_widget_) {
widget_opacity_map[capture_label_widget_.get()] = 1.f;
}
const bool is_settings_visible = capture_mode_settings_widget_ &&
capture_mode_settings_widget_->IsVisible();
const bool is_recording_type_menu_visible =
recording_type_menu_widget_ && recording_type_menu_widget_->IsVisible();
gfx::Rect capture_region = controller_->user_capture_region();
wm::ConvertRectToScreen(current_root_, &capture_region);
for (auto& pair : widget_opacity_map) {
views::Widget* widget = pair.first;
float& opacity = pair.second;
DCHECK(widget->GetLayer());
const gfx::Rect window_bounds_in_screen = widget->GetWindowBoundsInScreen();
const bool is_cursor_on_top_of_widget =
window_bounds_in_screen.Contains(*cursor_screen_location);
if (widget == capture_mode_bar_widget_.get()) {
if (is_settings_visible) {
continue;
}
if (is_drag_in_progress_) {
opacity = 0.f;
continue;
}
if (is_cursor_on_top_of_widget) {
continue;
}
if (capture_label_widget_ &&
capture_label_widget_->GetWindowBoundsInScreen().Contains(
*cursor_screen_location)) {
continue;
}
const bool capture_bar_intersects_region =
controller_->source() == CaptureModeSource::kRegion &&
window_bounds_in_screen.Intersects(capture_region);
if (capture_bar_intersects_region) {
opacity = capture_mode::kCaptureUiOverlapOpacity;
continue;
}
if (focus_cycler_->CaptureBarFocused()) {
continue;
}
}
if (widget == capture_label_widget_.get() &&
(is_cursor_on_top_of_widget || focus_cycler_->CaptureLabelFocused() ||
is_recording_type_menu_visible)) {
continue;
}
if (IsWidgetOverlappedWithCameraPreview(widget)) {
opacity = capture_mode::kCaptureUiOverlapOpacity;
}
}
for (const auto& pair : widget_opacity_map) {
views::Widget* widget = pair.first;
const float& opacity = pair.second;
capture_mode_util::AnimateToOpacity(widget, opacity);
}
}
void CaptureModeSession::RefreshBarWidgetBounds() {
DCHECK(capture_mode_bar_widget_);
capture_mode_bar_widget_->SetBounds(
active_behavior_->GetCaptureBarBounds(current_root_));
MaybeUpdateSettingsBounds();
if (user_nudge_controller_) {
user_nudge_controller_->Reposition();
}
capture_toast_controller_.MaybeRepositionCaptureToast();
}
views::Widget* CaptureModeSession::GetCaptureModeBarWidget() {
return capture_mode_bar_widget_.get();
}
aura::Window* CaptureModeSession::GetSelectedWindow() const {
return capture_window_observer_ ? capture_window_observer_->window()
: nullptr;
}
void CaptureModeSession::SetPreSelectedWindow(
aura::Window* pre_selected_window) {
CHECK(capture_window_observer_);
capture_window_observer_->SetSelectedWindow(pre_selected_window,
true,
true);
}
void CaptureModeSession::OnCaptureSourceChanged(CaptureModeSource new_source) {
capture_source_changed_ = true;
if (new_source == CaptureModeSource::kWindow) {
capture_window_observer_ = std::make_unique<CaptureWindowObserver>(this);
} else {
capture_window_observer_.reset();
}
if (new_source == CaptureModeSource::kRegion) {
num_capture_region_adjusted_ = 0;
}
capture_mode_bar_view_->OnCaptureSourceChanged(new_source);
UpdateDimensionsLabelWidget(false);
layer()->SchedulePaint(layer()->bounds());
UpdateCaptureLabelWidget(CaptureLabelAnimation::kNone);
UpdateActionContainerWidget();
UpdateCursor(display::Screen::Get()->GetCursorScreenPoint(),
false);
if (focus_cycler_->RegionGroupFocused()) {
focus_cycler_->ClearFocus();
}
A11yAlertCaptureSource(true);
MaybeReparentCameraPreviewWidget();
InvalidateImageSearch();
}
void CaptureModeSession::OnCaptureTypeChanged(CaptureModeType new_type) {
capture_mode_bar_view_->OnCaptureTypeChanged(new_type);
MaybeUpdateSelfieCamInSessionVisibility();
UpdateCaptureLabelWidget(CaptureLabelAnimation::kNone);
UpdateActionContainerWidget();
UpdateCursor(display::Screen::Get()->GetCursorScreenPoint(),
false);
A11yAlertCaptureType();
InvalidateImageSearch();
}
void CaptureModeSession::OnRecordingTypeChanged() {
if (capture_label_view_) {
capture_label_view_->UpdateIconAndText();
UpdateCaptureLabelWidgetBounds(CaptureLabelAnimation::kNone);
}
}
void CaptureModeSession::OnAudioRecordingModeChanged() {
active_behavior_->OnAudioRecordingModeChanged();
}
void CaptureModeSession::OnDemoToolsSettingsChanged() {
active_behavior_->OnDemoToolsSettingsChanged();
}
void CaptureModeSession::OnWaitingForDlpConfirmationStarted() {
is_waiting_for_dlp_confirmation_ = true;
HideAllUis();
}
void CaptureModeSession::OnWaitingForDlpConfirmationEnded(bool reshow_uis) {
is_waiting_for_dlp_confirmation_ = false;
if (reshow_uis) {
ShowAllUis();
}
}
void CaptureModeSession::ReportSessionHistograms() {
const CaptureModeSource source = controller_->source();
const RecordingType recording_type = controller_->recording_type();
if (source == CaptureModeSource::kRegion) {
RecordNumberOfCaptureRegionAdjustments(num_capture_region_adjusted_,
active_behavior_);
const auto region_in_root = controller_->user_capture_region();
if (is_stopping_to_start_video_recording_ &&
recording_type == RecordingType::kGif && !region_in_root.IsEmpty()) {
RecordGifRegionToScreenRatio(100.0f * region_in_root.size().GetArea() /
current_root_->bounds().size().GetArea());
}
}
num_capture_region_adjusted_ = 0;
RecordCaptureModeSwitchesFromInitialMode(capture_source_changed_);
RecordCaptureModeConfiguration(controller_->type(), source, recording_type,
controller_->GetEffectiveAudioRecordingMode(),
active_behavior_);
}
void CaptureModeSession::StartCountDown(
base::OnceClosure countdown_finished_callback) {
DCHECK(capture_label_widget_);
if (!capture_label_widget_->IsVisible()) {
capture_label_widget_->Show();
}
DCHECK(capture_label_view_);
capture_label_view_->StartCountDown(std::move(countdown_finished_callback));
UpdateCaptureLabelWidgetBounds(CaptureLabelAnimation::kCountdownStart);
UpdateCursor(display::Screen::Get()->GetCursorScreenPoint(),
false);
std::vector<ui::Layer*> layers_to_fade_out{
capture_mode_bar_widget_->GetLayer()};
if (capture_mode_settings_widget_) {
layers_to_fade_out.push_back(capture_mode_settings_widget_->GetLayer());
}
if (recording_type_menu_widget_) {
layers_to_fade_out.push_back(recording_type_menu_widget_->GetLayer());
}
if (auto* toast_layer = capture_toast_controller_.MaybeGetToastLayer()) {
layers_to_fade_out.push_back(toast_layer);
}
for (auto* layer : layers_to_fade_out) {
ui::ScopedLayerAnimationSettings layer_settings(layer->GetAnimator());
layer_settings.SetTransitionDuration(kCaptureWidgetFadeOutDuration);
layer_settings.SetTweenType(gfx::Tween::FAST_OUT_SLOW_IN);
layer_settings.SetPreemptionStrategy(
ui::LayerAnimator::IMMEDIATELY_ANIMATE_TO_NEW_TARGET);
layer->SetOpacity(0.f);
}
RepaintRegion();
if (controller_->source() == CaptureModeSource::kFullscreen) {
ui::ScopedLayerAnimationSettings shield_settings(layer()->GetAnimator());
shield_settings.SetTransitionDuration(kCaptureShieldFadeOutDuration);
shield_settings.SetTweenType(gfx::Tween::FAST_OUT_SLOW_IN);
shield_settings.SetPreemptionStrategy(
ui::LayerAnimator::IMMEDIATELY_ANIMATE_TO_NEW_TARGET);
layer()->SetOpacity(0.f);
}
}
void CaptureModeSession::OnCaptureFolderMayHaveChanged() {
if (!capture_mode_settings_widget_) {
return;
}
DCHECK(capture_mode_settings_view_);
capture_mode_settings_view_->OnCaptureFolderMayHaveChanged();
MaybeUpdateSettingsBounds();
}
void CaptureModeSession::OnDefaultCaptureFolderSelectionChanged() {
if (!capture_mode_settings_widget_) {
return;
}
DCHECK(capture_mode_settings_view_);
capture_mode_settings_view_->OnDefaultCaptureFolderSelectionChanged();
}
bool CaptureModeSession::CalculateCameraPreviewTargetVisibility() const {
if (controller_->is_recording_in_progress()) {
return true;
}
if (folder_selection_dialog_controller_) {
return false;
}
if (controller_->source() != CaptureModeSource::kRegion) {
return true;
}
if (controller_->user_capture_region().IsEmpty()) {
return false;
}
return !is_drag_in_progress_ ||
fine_tune_position_ == FineTunePosition::kCenter;
}
void CaptureModeSession::OnCameraPreviewDragStarted() {
DCHECK(!controller_->is_recording_in_progress());
SetSettingsMenuShown(false);
SetRecordingTypeMenuShown(false);
HideAllUis();
}
void CaptureModeSession::OnCameraPreviewDragEnded(
const gfx::Point& screen_location,
bool is_touch) {
auto* camera_preview_widget = GetCameraPreviewWidget();
DCHECK(camera_preview_widget);
if (!camera_preview_widget->GetWindowBoundsInScreen().Contains(
screen_location)) {
should_pass_located_event_to_camera_preview_ = false;
}
MaybeUpdateCaptureUisOpacity(screen_location);
ShowAllUis();
UpdateCursor(screen_location, is_touch);
}
void CaptureModeSession::OnCameraPreviewBoundsOrVisibilityChanged(
bool capture_surface_became_too_small,
bool did_bounds_or_visibility_change) {
auto* camera_preview_widget = GetCameraPreviewWidget();
DCHECK(camera_preview_widget);
const bool is_parented_to_unparented_container =
camera_preview_widget->GetNativeWindow()->parent()->GetId() ==
kShellWindowId_UnparentedContainer;
if (capture_surface_became_too_small && !is_drag_in_progress_ &&
!is_parented_to_unparented_container) {
user_nudge_controller_.reset();
capture_toast_controller_.ShowCaptureToast(
CaptureToastType::kCameraPreview);
} else {
capture_toast_controller_.MaybeDismissCaptureToast(
CaptureToastType::kCameraPreview);
}
if (did_bounds_or_visibility_change) {
MaybeUpdateCaptureUisOpacity();
}
}
void CaptureModeSession::OnCameraPreviewDestroyed() {
capture_toast_controller_.MaybeDismissCaptureToast(
CaptureToastType::kCameraPreview);
}
void CaptureModeSession::MaybeDismissSunfishRegionNudgeForever() {
if (user_nudge_controller_) {
user_nudge_controller_->set_should_dismiss_nudge_forever(true);
}
user_nudge_controller_.reset();
}
void CaptureModeSession::MaybeChangeRoot(aura::Window* new_root,
bool root_window_will_shutdown) {
DCHECK(new_root->IsRootWindow());
if (new_root == current_root_) {
return;
}
auto* new_parent = GetParentContainer(new_root);
parent_container_observer_ =
std::make_unique<ParentContainerObserver>(new_parent, this);
new_parent->layer()->Add(layer());
layer()->SetBounds(new_parent->bounds());
current_root_ = new_root;
Observe(ColorUtil::GetColorProviderSourceForWindow(current_root_));
if (root_window_will_shutdown ||
controller_->source() != CaptureModeSource::kRegion) {
RefreshBarWidgetBounds();
}
UpdateCursor(display::Screen::Get()->GetCursorScreenPoint(),
false);
if (controller_->user_capture_region().IsEmpty()) {
UpdateCaptureLabelWidgetBounds(CaptureLabelAnimation::kNone);
}
is_selecting_region_ = true;
UpdateCaptureRegion(gfx::Rect(), false, false,
root_window_will_shutdown);
UpdateRootWindowDimmers();
MaybeReparentCameraPreviewWidget();
RefreshStackingOrder();
}
std::set<aura::Window*> CaptureModeSession::GetWindowsToIgnoreFromWidgets() {
std::set<aura::Window*> ignore_windows;
CHECK(GetCaptureModeBarWidget());
ignore_windows.insert(GetCaptureModeBarWidget()->GetNativeWindow());
if (capture_mode_settings_widget()) {
ignore_windows.insert(capture_mode_settings_widget()->GetNativeWindow());
}
if (capture_label_widget()) {
ignore_windows.insert(capture_label_widget()->GetNativeWindow());
}
if (auto* capture_toast_widget =
capture_toast_controller()->capture_toast_widget()) {
ignore_windows.insert(capture_toast_widget->GetNativeWindow());
}
return ignore_windows;
}
void CaptureModeSession::OnPerformCaptureForSearchStarting(
PerformCaptureType capture_type) {
gfx::Rect capture_region_in_screen = controller_->user_capture_region();
wm::ConvertRectToScreen(current_root_, &capture_region_in_screen);
for (views::Widget* widget : GetAvailableWidgets()) {
if (capture_type != PerformCaptureType::kScanner ||
widget->GetWindowBoundsInScreen().Intersects(
capture_region_in_screen)) {
HideWidgetImmediately(widget);
}
}
is_capturing_for_search_ = true;
RepaintRegion();
}
void CaptureModeSession::OnPerformCaptureForSearchEnded(
PerformCaptureType capture_type) {
is_capturing_for_search_ = false;
RepaintRegion();
if (!active_behavior_->ShouldReShowUisAtPerformingCapture(capture_type)) {
return;
}
ShowAllWidgets();
if (active_behavior_->ShouldShowGlowWhileProcessingCaptureType(
capture_type)) {
CHECK(capture_region_overlay_controller_);
capture_region_overlay_controller_->StartGlowAnimation(
this);
ScannerController* scanner_controller = Shell::Get()->scanner_controller();
if (!scanner_controller || !scanner_controller->CanStartSession()) {
capture_region_overlay_controller_->PauseGlowAnimation();
}
}
}
base::WeakPtr<BaseCaptureModeSession>
CaptureModeSession::GetImageSearchToken() {
return is_shutting_down_ ? nullptr : weak_token_factory_.GetWeakPtr();
}
ActionButtonView* CaptureModeSession::AddActionButton(
views::Button::PressedCallback callback,
std::u16string text,
const gfx::VectorIcon* icon,
ActionButtonRank rank,
ActionButtonViewID id) {
UpdateActionContainerWidget();
if (!action_container_widget_) {
return nullptr;
}
MaybeDismissSunfishRegionNudgeForever();
CHECK(action_container_view_);
ActionButtonView* action_button = action_container_view_->AddActionButton(
std::move(callback), text, icon, rank, id);
CHECK(action_button);
UpdateActionContainerWidget();
return action_button;
}
void CaptureModeSession::AddSmartActionsButton() {
CHECK_EQ(active_behavior_->behavior_type(), BehaviorType::kDefault);
if (!ScannerController::CanShowUiForShell()) {
RecordScannerFeatureUserState(
ScannerFeatureUserState::
kSmartActionsButtonNotShownDueToCanShowUiFalse);
return;
}
if (controller_->IsNetworkConnectionOffline()) {
RecordScannerFeatureUserState(
ScannerFeatureUserState::kSmartActionsButtonNotShownDueToOffline);
return;
}
RecordScannerFeatureUserState(
ScannerFeatureUserState::kScreenCaptureModeScannerButtonShown);
AddActionButton(
base::BindRepeating(&CaptureModeSession::OnSmartActionsButtonPressed,
weak_ptr_factory_.GetWeakPtr()),
l10n_util::GetStringUTF16(IDS_ASH_SCANNER_SMART_ACTIONS_BUTTON_LABEL),
nullptr,
ActionButtonRank{ActionButtonType::kScanner, 0},
ActionButtonViewID::kSmartActionsButton);
}
void CaptureModeSession::MaybeShowScannerDisclaimer(
ScannerEntryPoint entry_point,
base::RepeatingClosure accept_callback,
base::RepeatingClosure decline_callback) {
bool is_reminder;
switch (GetScannerDisclaimerType(
*capture_mode_util::GetActiveUserPrefService(), entry_point)) {
case ScannerDisclaimerType::kNone:
if (accept_callback) {
std::move(accept_callback).Run();
}
return;
case ScannerDisclaimerType::kReminder:
is_reminder = true;
break;
case ScannerDisclaimerType::kFull:
is_reminder = false;
break;
}
disclaimer_ = DisclaimerView::CreateWidget(
capture_mode_util::GetPreferredRootWindow(), is_reminder,
base::BindRepeating(&CaptureModeSession::OnDisclaimerAccepted,
weak_ptr_factory_.GetWeakPtr(), entry_point,
std::move(accept_callback)),
base::BindRepeating(&CaptureModeSession::OnDisclaimerDeclined,
weak_ptr_factory_.GetWeakPtr(),
std::move(decline_callback)),
base::BindRepeating(&CaptureModeSession::OnDisclaimerLinkPressed,
weak_ptr_factory_.GetWeakPtr(),
chrome::kGooglePrivacyPolicyUrl),
base::BindRepeating(&CaptureModeSession::OnDisclaimerLinkPressed,
weak_ptr_factory_.GetWeakPtr(),
chrome::kScannerLearnMoreUrl));
disclaimer_->Show();
focus_cycler_->OnDisclaimerWidgetOpened(disclaimer_.get());
}
void CaptureModeSession::OnScannerActionsFetched(
ScannerSession::FetchActionsResponse actions_response) {
CHECK(capture_region_overlay_controller_);
capture_region_overlay_controller_->PauseGlowAnimation();
UpdateActionContainerWidget();
if (!action_container_widget_) {
return;
}
if (!actions_response.has_value()) {
if (actions_response.error().can_try_again) {
action_container_view_->ShowErrorView(
actions_response.error().error_message,
base::BindRepeating(&CaptureModeSession::OnScannerTryAgainPressed,
weak_ptr_factory_.GetWeakPtr()));
} else {
action_container_view_->ShowErrorView(
actions_response.error().error_message);
}
UpdateActionContainerWidget();
return;
}
int size = static_cast<int>(actions_response->size());
for (int i = 0; i < size; ++i) {
ScannerActionViewModel& action = actions_response.value()[i];
std::u16string text = action.GetText();
const gfx::VectorIcon& icon = action.GetIcon();
base::RepeatingClosure pressed_callback =
base::BindRepeating(&CaptureModeSession::OnScannerActionButtonPressed,
weak_ptr_factory_.GetWeakPtr(), std::move(action));
if (ActionButtonView* action_button =
AddActionButton(std::move(pressed_callback), std::move(text), &icon,
ActionButtonRank{ActionButtonType::kScanner, i},
ActionButtonViewID::kScannerButton)) {
action_button->PerformFadeInAnimation(kScannerActionButtonFadeInDuration);
}
}
focus_cycler_->OnScannerActionsFetched();
}
void CaptureModeSession::ShowActionContainerError(
const std::u16string& error_message) {
if (!action_container_widget_) {
return;
}
CHECK(action_container_view_);
action_container_view_->ShowErrorView(error_message);
UpdateActionContainerWidget();
}
void CaptureModeSession::OnDisclaimerDeclined(base::RepeatingClosure callback) {
RecordScannerFeatureUserState(
ScannerFeatureUserState::kConsentDisclaimerRejected);
capture_mode_util::GetActiveUserPrefService()->SetBoolean(
prefs::kScannerEnabled, false);
disclaimer_.reset();
if (active_behavior_->ShouldAnnounceCaptureModeUIOnDisclaimerDismissed()) {
capture_mode_util::TriggerAccessibilityAlert(
active_behavior_->GetCaptureModeOpenAnnouncement());
UpdateCaptureLabelWidget(CaptureLabelAnimation::kNone);
}
focus_cycler_->OnDisclaimerWidgetClosed();
if (callback) {
std::move(callback).Run();
}
}
void CaptureModeSession::OnDisclaimerAccepted(ScannerEntryPoint entry_point,
base::RepeatingClosure callback) {
RecordScannerFeatureUserState(
ScannerFeatureUserState::kConsentDisclaimerAccepted);
SetScannerDisclaimerAcked(*capture_mode_util::GetActiveUserPrefService(),
entry_point);
disclaimer_.reset();
if (active_behavior_->ShouldAnnounceCaptureModeUIOnDisclaimerDismissed()) {
capture_mode_util::TriggerAccessibilityAlert(
active_behavior_->GetCaptureModeOpenAnnouncement());
UpdateCaptureLabelWidget(CaptureLabelAnimation::kNone);
}
focus_cycler_->OnDisclaimerWidgetClosed();
if (callback) {
std::move(callback).Run();
}
}
void CaptureModeSession::OnDisclaimerLinkPressed(const char* url) {
NewWindowDelegate::GetInstance()->OpenUrl(
GURL(url), NewWindowDelegate::OpenUrlFrom::kUserInteraction,
NewWindowDelegate::Disposition::kNewForegroundTab);
controller_->Stop();
}
void CaptureModeSession::OnSmartActionsButtonPressed() {
MaybeShowScannerDisclaimer(
ScannerEntryPoint::kSmartActionsButton,
base::BindRepeating(
&CaptureModeSession::OnSmartActionsButtonDisclaimerCheckSuccess,
weak_ptr_factory_.GetWeakPtr()),
base::BindRepeating(
&CaptureModeSession::OnSmartActionsButtonDisclaimerDeclined,
weak_ptr_factory_.GetWeakPtr()));
}
void CaptureModeSession::OnSmartActionsButtonDisclaimerCheckSuccess() {
if (controller_->IsNetworkConnectionOffline()) {
ShowActionContainerError(l10n_util::GetStringUTF16(
IDS_ASH_SCREEN_CAPTURE_ACTION_ATTEMPTED_OFFLINE_ERROR));
return;
}
CHECK(action_container_view_);
action_container_view_->StartSmartActionsButtonTransition();
auto* scanner_controller = Shell::Get()->scanner_controller();
CHECK(scanner_controller);
scanner_controller->StartNewSession();
controller_->PerformCapture(PerformCaptureType::kScanner);
}
void CaptureModeSession::OnSmartActionsButtonDisclaimerDeclined() {
action_container_view_->RemoveSmartActionsButton();
}
void CaptureModeSession::OnScannerActionButtonPressed(
const ScannerActionViewModel& scanner_action) {
Shell::Get()->scanner_controller()->ExecuteAction(scanner_action);
controller_->CloseSearchResultsPanel();
controller_->Stop();
}
void CaptureModeSession::OnScannerTryAgainPressed() {
CHECK(action_container_view_);
action_container_view_->HideErrorView();
UpdateActionContainerWidget();
controller_->PerformCapture(PerformCaptureType::kScanner);
}
void CaptureModeSession::OnPaintLayer(const ui::PaintContext& context) {
if (!is_all_uis_visible_ &&
!controller_->camera_controller()->is_drag_in_progress()) {
return;
}
const auto* color_provider_source = GetColorProviderSource();
CHECK(color_provider_source);
ui::PaintRecorder recorder(context, layer()->size());
recorder.canvas()->DrawColor(
color_provider_source->GetColorProvider()->GetColor(
capture_mode::kDimmingShieldColor));
PaintCaptureRegion(recorder.canvas());
MaybePaintCaptureRegionOverlay(*recorder.canvas());
}
void CaptureModeSession::OnKeyEvent(ui::KeyEvent* event) {
CHECK(focus_cycler_);
if (is_waiting_for_dlp_confirmation_) {
return;
}
if (folder_selection_dialog_controller_) {
if (folder_selection_dialog_controller_->ShouldConsumeEvent(event)) {
event->StopPropagation();
}
return;
}
if (disclaimer_) {
return;
}
if (event->type() != ui::EventType::kKeyPressed) {
return;
}
ui::KeyboardCode key_code = event->key_code();
if (controller_->IsSearchResultsPanelVisible() &&
controller_->GetSearchResultsPanel()->HasFocus() &&
key_code != ui::VKEY_ESCAPE) {
return;
}
auto* camera_preview_view =
controller_->camera_controller()->camera_preview_view();
if (camera_preview_view && camera_preview_view->MaybeHandleKeyEvent(event)) {
event->StopPropagation();
return;
}
bool should_update_opacity = false;
absl::Cleanup deferred_runner = [session = weak_ptr_factory_.GetWeakPtr(),
&should_update_opacity] {
if (session && should_update_opacity) {
session->MaybeUpdateCaptureUisOpacity();
}
};
auto* capture_source_view = capture_mode_bar_view_->GetCaptureSourceView();
const bool is_in_count_down = IsInCountDownAnimation();
switch (key_code) {
case ui::VKEY_ESCAPE: {
event->StopPropagation();
should_update_opacity = true;
if (recording_type_menu_widget_ && !is_in_count_down) {
SetRecordingTypeMenuShown(false);
} else if (capture_mode_settings_widget_ && !is_in_count_down) {
SetSettingsMenuShown(false);
} else if (focus_cycler_->HasFocus() && !is_in_count_down) {
focus_cycler_->ClearFocus();
} else if (can_exit_on_escape_) {
controller_->Stop();
}
return;
}
case ui::VKEY_RETURN: {
event->StopPropagation();
if (!is_in_count_down) {
views::View* ignore_view = nullptr;
if (capture_source_view &&
controller_->source() == CaptureModeSource::kFullscreen) {
ignore_view = capture_source_view->fullscreen_toggle_button();
}
if (focus_cycler_->MaybeActivateFocusedView(ignore_view)) {
should_update_opacity = true;
return;
}
active_behavior_
->OnEnterKeyPressed();
}
return;
}
case ui::VKEY_SPACE: {
event->StopPropagation();
event->SetHandled();
views::View* ignore_view = nullptr;
const bool is_in_region_mode =
controller_->source() == CaptureModeSource::kRegion;
if (capture_source_view && is_in_region_mode) {
ignore_view = capture_source_view->region_toggle_button();
}
if (focus_cycler_->MaybeActivateFocusedView(ignore_view)) {
should_update_opacity = true;
return;
}
if (is_in_region_mode && controller_->user_capture_region().IsEmpty()) {
SelectDefaultRegion();
}
return;
}
case ui::VKEY_TAB: {
constexpr ui::EventFlags kModifiers =
ui::EF_SHIFT_DOWN | ui::EF_CONTROL_DOWN | ui::EF_ALT_DOWN |
ui::EF_COMMAND_DOWN;
const auto shortcut_flags = kModifiers & event->flags();
if (shortcut_flags == ui::EF_NONE ||
shortcut_flags == ui::EF_SHIFT_DOWN) {
event->StopPropagation();
event->SetHandled();
focus_cycler_->AdvanceFocus(event->IsShiftDown());
should_update_opacity = true;
}
return;
}
case ui::VKEY_UP:
case ui::VKEY_DOWN:
case ui::VKEY_LEFT:
case ui::VKEY_RIGHT: {
event->StopPropagation();
event->SetHandled();
UpdateRegionForArrowKeys(key_code, event->flags());
return;
}
default:
return;
}
}
void CaptureModeSession::OnMouseEvent(ui::MouseEvent* event) {
if (is_waiting_for_dlp_confirmation_) {
return;
}
OnLocatedEvent(event, false);
}
void CaptureModeSession::OnTouchEvent(ui::TouchEvent* event) {
if (is_waiting_for_dlp_confirmation_) {
return;
}
OnLocatedEvent(event, true);
}
void CaptureModeSession::OnWindowDestroying(aura::Window* window) {
DCHECK_EQ(window, input_capture_window_);
input_capture_window_->RemoveObserver(this);
input_capture_window_ = nullptr;
}
void CaptureModeSession::OnDisplayTabletStateChanged(
display::TabletState state) {
if (display::IsTabletStateChanging(state)) {
return;
}
UpdateCaptureLabelWidget(CaptureLabelAnimation::kNone);
UpdateCursor(display::Screen::Get()->GetCursorScreenPoint(),
false);
}
void CaptureModeSession::OnDisplayMetricsChanged(
const display::Display& display,
uint32_t metrics) {
if (!(metrics &
(DISPLAY_METRIC_BOUNDS | DISPLAY_METRIC_ROTATION |
DISPLAY_METRIC_DEVICE_SCALE_FACTOR | DISPLAY_METRIC_WORK_AREA))) {
return;
}
EndSelection();
UpdateCursor(display::Screen::Get()->GetCursorScreenPoint(),
false);
ClampCaptureRegionToRootWindowSize();
auto* parent = GetParentContainer(current_root_);
DCHECK_EQ(parent->layer(), layer()->parent());
layer()->SetBounds(parent->bounds());
RefreshBarWidgetBounds();
if (controller_->source() == CaptureModeSource::kFullscreen &&
!controller_->is_recording_in_progress()) {
controller_->camera_controller()->MaybeUpdatePreviewWidget();
}
if (capture_label_widget_) {
UpdateCaptureLabelWidget(CaptureLabelAnimation::kNone);
}
layer()->SchedulePaint(layer()->bounds());
MaybeUpdateCaptureUisOpacity();
}
void CaptureModeSession::OnFolderSelected(const base::FilePath& path) {
CaptureModeController::Get()->SetCustomCaptureFolder(path);
if (controller_->GetCurrentCaptureFolder().is_default_downloads_folder) {
RecordSwitchToDefaultFolderReason(
CaptureModeSwitchToDefaultReason::
kUserSelectedFromFolderSelectionDialog);
}
}
void CaptureModeSession::OnSelectionWindowAdded() {
HideAllUis();
}
void CaptureModeSession::OnSelectionWindowClosed() {
DCHECK(folder_selection_dialog_controller_);
ShowAllUis();
const bool did_user_select_a_folder =
folder_selection_dialog_controller_->did_user_select_a_folder();
folder_selection_dialog_controller_.reset();
MaybeUpdateCameraPreviewBounds();
if (!did_user_select_a_folder) {
OnCaptureFolderMayHaveChanged();
}
keyboard::KeyboardUIController::Get()->HideKeyboardExplicitlyBySystem();
}
void CaptureModeSession::OnColorProviderChanged() {
if (!is_shutting_down_) {
layer()->SchedulePaint(layer()->bounds());
}
}
void CaptureModeSession::AnimationProgressed(const gfx::Animation* animation) {
if (capture_region_overlay_controller_ &&
active_behavior_->CanPaintRegionOverlay()) {
RefreshGlowRegion();
}
}
void CaptureModeSession::A11yAlertCaptureType() {
capture_mode_util::TriggerAccessibilityAlert(
CaptureModeController::Get()->type() == CaptureModeType::kImage
? IDS_ASH_SCREEN_CAPTURE_ALERT_SELECT_TYPE_IMAGE
: IDS_ASH_SCREEN_CAPTURE_ALERT_SELECT_TYPE_VIDEO);
A11yAlertCaptureSource(false);
}
std::vector<views::Widget*> CaptureModeSession::GetAvailableWidgets() {
std::vector<views::Widget*> result;
DCHECK(capture_mode_bar_widget_);
result.push_back(capture_mode_bar_widget_.get());
if (capture_label_widget_)
result.push_back(capture_label_widget_.get());
if (recording_type_menu_widget_)
result.push_back(recording_type_menu_widget_.get());
if (capture_mode_settings_widget_)
result.push_back(capture_mode_settings_widget_.get());
if (dimensions_label_widget_)
result.push_back(dimensions_label_widget_.get());
if (auto* toast = capture_toast_controller_.capture_toast_widget())
result.push_back(toast);
if (action_container_widget_) {
result.push_back(action_container_widget_.get());
}
if (disclaimer_) {
result.push_back(disclaimer_.get());
}
return result;
}
void CaptureModeSession::HideAllUis() {
is_all_uis_visible_ = false;
cursor_setter_.reset();
for (views::Widget* widget : GetAvailableWidgets()) {
HideWidgetImmediately(widget);
}
layer()->SchedulePaint(layer()->bounds());
}
void CaptureModeSession::ShowAllUis() {
is_all_uis_visible_ = true;
cursor_setter_ = std::make_unique<CursorSetter>();
ShowAllWidgets();
layer()->SchedulePaint(layer()->bounds());
}
void CaptureModeSession::ShowAllWidgets() {
for (auto* widget : GetAvailableWidgets()) {
if (CanShowWidget(widget) && !widget->IsVisible()) {
widget->GetLayer()->SetOpacity(1.0f);
widget->Show();
}
widget->GetNativeWindow()->SetProperty(aura::client::kAnimationsDisabledKey,
false);
}
}
bool CaptureModeSession::CanShowWidget(views::Widget* widget) const {
if (widget == capture_toast_controller_.capture_toast_widget())
return !!capture_toast_controller_.current_toast_type();
return !(capture_label_widget_ && capture_mode_settings_widget_ &&
capture_label_widget_.get() == widget &&
capture_mode_settings_widget_->GetWindowBoundsInScreen().Intersects(
capture_label_widget_->GetWindowBoundsInScreen()));
}
void CaptureModeSession::MaybeCreateSunfishRegionNudge() {
user_nudge_controller_.reset();
if (!active_behavior_->ShouldShowUserNudge()) {
return;
}
if (!controller_->CanShowSunfishRegionNudge()) {
return;
}
auto* region_button =
capture_mode_bar_view_->GetCaptureSourceView()->region_toggle_button();
CHECK(region_button);
user_nudge_controller_ =
std::make_unique<UserNudgeController>(this, region_button);
user_nudge_controller_->SetVisible(true);
}
void CaptureModeSession::DoPerformCapture() {
controller_->PerformCapture();
}
void CaptureModeSession::OnSearchButtonPressed() {
RecordSearchButtonPressed();
controller_->PerformCapture(
PerformCaptureType::kSearch);
}
void CaptureModeSession::OnRecordingTypeDropDownButtonPressed(
const ui::Event& event) {
SetRecordingTypeMenuShown(
!recording_type_menu_widget_ || !recording_type_menu_widget_->IsVisible(),
event.IsKeyEvent());
}
void CaptureModeSession::RefreshStackingOrder() {
if (is_shutting_down_)
return;
auto* parent_container = GetParentContainer(current_root_);
DCHECK(parent_container);
auto* session_layer = layer();
auto* parent_container_layer = parent_container->layer();
parent_container_layer->StackAtTop(session_layer);
std::vector<views::Widget*> widget_in_order;
auto* camera_preview_widget = GetCameraPreviewWidget();
if (camera_preview_widget && !controller_->is_recording_in_progress())
widget_in_order.emplace_back(camera_preview_widget);
if (auto* toast = capture_toast_controller_.capture_toast_widget())
widget_in_order.emplace_back(toast);
if (capture_label_widget_)
widget_in_order.emplace_back(capture_label_widget_.get());
if (action_container_widget_) {
widget_in_order.emplace_back(action_container_widget_.get());
}
if (capture_mode_bar_widget_)
widget_in_order.emplace_back(capture_mode_bar_widget_.get());
if (recording_type_menu_widget_)
widget_in_order.emplace_back(recording_type_menu_widget_.get());
if (capture_mode_settings_widget_)
widget_in_order.emplace_back(capture_mode_settings_widget_.get());
for (auto* widget : widget_in_order) {
auto* widget_window = widget->GetNativeWindow();
if (widget_window->parent() == parent_container) {
parent_container->StackChildAtTop(widget_window);
parent_container_layer->StackAtTop(widget_window->layer());
}
}
}
void CaptureModeSession::PaintCaptureRegion(gfx::Canvas* canvas) {
if (active_behavior_->ShouldPaintSunfishCaptureRegion()) {
PaintSunfishCaptureRegion(canvas);
return;
}
gfx::Rect region;
bool adjustable_region = false;
switch (controller_->source()) {
case CaptureModeSource::kFullscreen:
region = current_root_->bounds();
break;
case CaptureModeSource::kWindow:
region = GetSelectedWindowTargetBounds();
break;
case CaptureModeSource::kRegion:
region = controller_->user_capture_region();
adjustable_region = true;
break;
}
if (region.IsEmpty())
return;
gfx::ScopedCanvas scoped_canvas(canvas);
const float dsf = canvas->UndoDeviceScaleFactor();
region = gfx::ScaleToEnclosingRect(region, dsf);
const auto* color_provider = GetColorProviderSource()->GetColorProvider();
if (!adjustable_region) {
canvas->FillRect(region, SK_ColorTRANSPARENT, SkBlendMode::kClear);
canvas->FillRect(region,
color_provider->GetColor(kColorAshCaptureRegionColor));
return;
}
if (capture_region_overlay_controller_ &&
active_behavior_->CanPaintRegionOverlay()) {
capture_region_overlay_controller_->PaintCurrentGlowState(*canvas, region,
color_provider);
}
region.Inset(-capture_mode::kCaptureRegionBorderStrokePx);
canvas->FillRect(region, SK_ColorTRANSPARENT, SkBlendMode::kClear);
if (is_capturing_for_search_) {
return;
}
cc::PaintFlags border_flags;
border_flags.setColor(capture_mode::kRegionBorderColor);
border_flags.setStyle(cc::PaintFlags::kStroke_Style);
border_flags.setStrokeWidth(capture_mode::kCaptureRegionBorderStrokePx);
border_flags.setLooper(gfx::CreateShadowDrawLooper({kRegionOutlineShadow}));
canvas->DrawRect(gfx::RectF(region), border_flags);
auto maybe_draw_focus_ring = [&canvas, ®ion, dsf,
color_provider](FineTunePosition position) {
if (position == FineTunePosition::kNone)
return;
cc::PaintFlags focus_ring_flags;
focus_ring_flags.setAntiAlias(true);
focus_ring_flags.setColor(color_provider->GetColor(ui::kColorAshFocusRing));
focus_ring_flags.setStyle(cc::PaintFlags::kStroke_Style);
focus_ring_flags.setStrokeWidth(kFocusRingStrokeWidthDp);
if (position == FineTunePosition::kCenter) {
gfx::RectF focus_rect(region);
focus_rect.Inset(
gfx::InsetsF(-kFocusRingSpacingDp - kFocusRingStrokeWidthDp / 2));
canvas->DrawRect(focus_rect, focus_ring_flags);
return;
}
const float radius =
dsf * (kAffordanceCircleRadiusDp + kFocusRingSpacingDp +
kFocusRingStrokeWidthDp / 2);
canvas->DrawCircle(
capture_mode_util::GetLocationForFineTunePosition(region, position),
radius, focus_ring_flags);
};
const FineTunePosition focused_fine_tune_position =
focus_cycler_->GetFocusedFineTunePosition();
if (is_selecting_region_ || fine_tune_position_ != FineTunePosition::kNone) {
maybe_draw_focus_ring(focused_fine_tune_position);
return;
}
if (IsInCountDownAnimation())
return;
cc::PaintFlags circle_flags;
circle_flags.setColor(capture_mode::kRegionBorderColor);
circle_flags.setStyle(cc::PaintFlags::kFill_Style);
circle_flags.setAntiAlias(true);
circle_flags.setLooper(gfx::CreateShadowDrawLooper(
{kRegionAffordanceCircleShadow1, kRegionAffordanceCircleShadow2}));
auto draw_circle = [&canvas, &circle_flags,
&dsf](const gfx::Point& location) {
canvas->DrawCircle(location, dsf * kAffordanceCircleRadiusDp, circle_flags);
};
draw_circle(region.origin());
draw_circle(region.top_center());
draw_circle(region.top_right());
draw_circle(region.right_center());
draw_circle(region.bottom_right());
draw_circle(region.bottom_center());
draw_circle(region.bottom_left());
draw_circle(region.left_center());
maybe_draw_focus_ring(focused_fine_tune_position);
}
void CaptureModeSession::PaintSunfishCaptureRegion(gfx::Canvas* canvas) {
gfx::Rect region = controller_->user_capture_region();
if (region.IsEmpty()) {
return;
}
gfx::ScopedCanvas scoped_canvas(canvas);
const float dsf = canvas->UndoDeviceScaleFactor();
region = gfx::ScaleToEnclosingRect(region, dsf);
const auto* color_provider = GetColorProviderSource()->GetColorProvider();
if (capture_region_overlay_controller_ &&
active_behavior_->CanPaintRegionOverlay()) {
capture_region_overlay_controller_->PaintCurrentGlowState(*canvas, region,
color_provider);
}
cc::PaintFlags region_mask_flags;
region_mask_flags.setStyle(cc::PaintFlags::kFill_Style);
region_mask_flags.setAntiAlias(true);
region_mask_flags.setBlendMode(SkBlendMode::kClear);
canvas->DrawRoundRect(region, kSunfishModeCaptureRegionRadiusDp,
region_mask_flags);
if (is_selecting_region_ && !is_capturing_for_search_) {
cc::PaintFlags border_flags;
border_flags.setShader(gfx::CreateGradientShader(
region.origin(), region.top_right(),
color_provider->GetColor(cros_tokens::kCrosSysMuted),
color_provider->GetColor(cros_tokens::kCrosSysComplement)));
border_flags.setStyle(cc::PaintFlags::kStroke_Style);
border_flags.setStrokeWidth(kSunfishModeCaptureRegionBorderWidthDp);
border_flags.setAntiAlias(true);
border_flags.setLooper(gfx::CreateShadowDrawLooper({kRegionOutlineShadow}));
canvas->DrawRoundRect(region, kSunfishModeCaptureRegionRadiusDp,
border_flags);
}
if (is_selecting_region_ || fine_tune_position_ != FineTunePosition::kNone ||
is_capturing_for_search_) {
return;
}
cc::PaintFlags circle_flags;
circle_flags.setColor(capture_mode::kRegionBorderColor);
circle_flags.setStyle(cc::PaintFlags::kFill_Style);
circle_flags.setAntiAlias(true);
circle_flags.setLooper(gfx::CreateShadowDrawLooper(
{kRegionAffordanceCircleShadow1, kRegionAffordanceCircleShadow2}));
auto draw_circle = [&canvas, &circle_flags,
&dsf](const gfx::Point& location) {
canvas->DrawCircle(location, dsf * kAffordanceCircleRadiusDp, circle_flags);
};
draw_circle(region.top_center());
draw_circle(region.right_center());
draw_circle(region.bottom_center());
draw_circle(region.left_center());
cc::PaintFlags corner_arc_flags;
corner_arc_flags.setColor(capture_mode::kRegionBorderColor);
corner_arc_flags.setStyle(cc::PaintFlags::kStroke_Style);
corner_arc_flags.setAntiAlias(true);
corner_arc_flags.setLooper(gfx::CreateShadowDrawLooper(
{kRegionAffordanceCircleShadow1, kRegionAffordanceCircleShadow2}));
corner_arc_flags.setStrokeWidth(kSunfishModeCaptureRegionBorderWidthDp);
corner_arc_flags.setStrokeCap(cc::PaintFlags::Cap::kRound_Cap);
const float arc_diameter = dsf * 2 * kSunfishModeCaptureRegionRadiusDp;
auto draw_corner_arc = [&canvas, &corner_arc_flags, &arc_diameter](
int circle_left, int circle_top,
SkScalar start_angle) {
const SkPath corner_arc =
SkPathBuilder()
.arcTo(SkRect::MakeXYWH(circle_left, circle_top,
arc_diameter, arc_diameter),
start_angle,
90, false)
.detach();
canvas->DrawPath(corner_arc, corner_arc_flags);
};
draw_corner_arc(region.x(), region.y(),
180);
draw_corner_arc(region.right() - arc_diameter,
region.y(),
270);
draw_corner_arc(
region.right() - arc_diameter,
region.bottom() - arc_diameter,
0);
draw_corner_arc(region.x(),
region.bottom() - arc_diameter,
90);
cc::PaintFlags focus_ring_flags;
focus_ring_flags.setAntiAlias(true);
focus_ring_flags.setColor(color_provider->GetColor(ui::kColorAshFocusRing));
focus_ring_flags.setStyle(cc::PaintFlags::kStroke_Style);
focus_ring_flags.setStrokeWidth(kFocusRingStrokeWidthDp);
const FineTunePosition focused_fine_tune_position =
focus_cycler_->GetFocusedFineTunePosition();
switch (focused_fine_tune_position) {
case FineTunePosition::kNone:
break;
case FineTunePosition::kCenter: {
gfx::RectF focus_rect(region);
focus_rect.Outset(kFocusRingSpacingDp + kFocusRingStrokeWidthDp / 2);
canvas->DrawRoundRect(focus_rect, kSunfishRegionFocusRingRadiusDp,
focus_ring_flags);
break;
}
case FineTunePosition::kTopLeftVertex:
case FineTunePosition::kTopRightVertex:
case FineTunePosition::kBottomRightVertex:
case FineTunePosition::kBottomLeftVertex: {
const float radius = dsf * (kSunfishRegionCornerFocusRingRadiusDp +
kFocusRingStrokeWidthDp / 2);
region.Inset(kSunfishRegionRoundedCornerOffsetDp);
canvas->DrawCircle(capture_mode_util::GetLocationForFineTunePosition(
region, focused_fine_tune_position),
radius, focus_ring_flags);
break;
}
case FineTunePosition::kTopEdge:
case FineTunePosition::kRightEdge:
case FineTunePosition::kBottomEdge:
case FineTunePosition::kLeftEdge: {
const float radius =
dsf * (kAffordanceCircleRadiusDp + kFocusRingSpacingDp +
kFocusRingStrokeWidthDp / 2);
canvas->DrawCircle(capture_mode_util::GetLocationForFineTunePosition(
region, focused_fine_tune_position),
radius, focus_ring_flags);
break;
}
}
}
void CaptureModeSession::MaybePaintCaptureRegionOverlay(
gfx::Canvas& canvas) const {
if (capture_region_overlay_controller_ &&
active_behavior_->CanPaintRegionOverlay()) {
capture_region_overlay_controller_->PaintCaptureRegionOverlay(
canvas, controller_->user_capture_region());
}
}
void CaptureModeSession::OnLocatedEvent(ui::LocatedEvent* event,
bool is_touch) {
if (folder_selection_dialog_controller_) {
if (folder_selection_dialog_controller_->ShouldConsumeEvent(event))
event->StopPropagation();
return;
}
if (IsInCountDownAnimation()) {
event->StopPropagation();
return;
}
if (disclaimer_) {
if (cursor_setter_) {
cursor_setter_->ResetCursor();
}
return;
}
if (event->type() == ui::EventType::kMouseCaptureChanged ||
event->type() == ui::EventType::kMouseExited ||
event->type() == ui::EventType::kMouseEntered) {
return;
}
if (event->flags() & ui::EF_IS_SYNTHESIZED)
return;
gfx::Point screen_location = event->location();
aura::Window* event_target = static_cast<aura::Window*>(event->target());
wm::ConvertPointToScreen(event_target, &screen_location);
const CaptureModeSource capture_source = controller_->source();
const bool is_capture_region = capture_source == CaptureModeSource::kRegion;
const bool is_press_event = event->type() == ui::EventType::kMousePressed ||
event->type() == ui::EventType::kTouchPressed;
if (is_press_event && focus_cycler_->HasFocus())
focus_cycler_->ClearFocus();
const bool is_bar_anchored_to_window =
controller_->source() == CaptureModeSource::kWindow &&
capture_window_observer_->bar_anchored_to_window();
const bool can_change_root =
!is_bar_anchored_to_window && (!is_capture_region || is_press_event);
if (can_change_root) {
MaybeChangeRoot(capture_mode_util::GetPreferredRootWindow(screen_location),
false);
}
const bool is_release_event =
event->type() == ui::EventType::kMouseReleased ||
event->type() == ui::EventType::kTouchReleased;
if (is_release_event && is_capture_region &&
current_root_ !=
capture_mode_bar_widget_->GetNativeWindow()->GetRootWindow()) {
RefreshBarWidgetBounds();
}
MaybeUpdateCaptureUisOpacity(screen_location);
if (controller_->IsEventOnSearchResultsPanel(*event, screen_location)) {
if (cursor_setter_) {
cursor_setter_->ResetCursor();
}
if (is_press_event && (event->flags() & ui::EF_RIGHT_MOUSE_BUTTON)) {
controller_->Stop();
}
return;
}
should_pass_located_event_to_camera_preview_ =
ShouldPassEventToCameraPreview(event);
absl::Cleanup deferred_cursor_updater =
[session = weak_ptr_factory_.GetWeakPtr(), screen_location, is_touch] {
if (session) {
session->UpdateCursor(screen_location, is_touch);
}
};
if (should_pass_located_event_to_camera_preview_) {
DCHECK(!controller_->is_recording_in_progress());
return;
}
if (ShouldCaptureLabelHandleEvent(event_target))
return;
if (capture_mode_util::IsEventTargetedOnWidget(
*event, action_container_widget_.get())) {
return;
}
if (capture_mode_util::IsEventTargetedOnWidget(
*event, recording_type_menu_widget_.get())) {
return;
}
if (capture_mode_util::IsEventTargetedOnWidget(
*event, capture_mode_settings_widget_.get())) {
return;
}
const bool should_close_settings =
is_press_event &&
!capture_mode_bar_view_->IsEventOnSettingsButton(screen_location) &&
capture_mode_settings_widget_;
if (should_close_settings) {
ignore_located_events_ = true;
SetSettingsMenuShown(false);
}
const bool should_close_recording_type_menu =
is_press_event &&
!IsPointOnRecordingTypeDropDownButton(screen_location) &&
recording_type_menu_widget_;
if (should_close_recording_type_menu) {
ignore_located_events_ = true;
SetRecordingTypeMenuShown(false);
}
const bool old_ignore_located_events = ignore_located_events_;
if (ignore_located_events_) {
if (is_release_event)
ignore_located_events_ = false;
}
if (capture_mode_util::IsEventTargetedOnWidget(
*event, capture_mode_bar_widget_.get())) {
return;
}
event->SetHandled();
event->StopPropagation();
if (should_close_settings || old_ignore_located_events ||
should_close_recording_type_menu) {
return;
}
const bool is_capture_fullscreen =
capture_source == CaptureModeSource::kFullscreen;
const bool is_capture_window = capture_source == CaptureModeSource::kWindow;
if (is_capture_fullscreen || is_capture_window) {
switch (event->type()) {
case ui::EventType::kMouseMoved:
case ui::EventType::kTouchPressed:
case ui::EventType::kTouchMoved: {
if (is_capture_window) {
capture_window_observer_->UpdateSelectedWindowAtPosition(
screen_location);
}
break;
}
case ui::EventType::kMouseReleased:
case ui::EventType::kTouchReleased:
if (is_capture_fullscreen ||
IsPointOverSelectedWindow(screen_location)) {
DoPerformCapture();
}
break;
default:
break;
}
return;
}
DCHECK(is_capture_region);
const gfx::Point& location_in_root = event->root_location();
switch (event->type()) {
case ui::EventType::kMousePressed:
case ui::EventType::kTouchPressed:
old_mouse_warp_status_ = SetMouseWarpEnabled(false);
OnLocatedEventPressed(location_in_root, is_touch);
break;
case ui::EventType::kMouseDragged:
case ui::EventType::kTouchMoved:
OnLocatedEventDragged(location_in_root);
break;
case ui::EventType::kMouseReleased:
case ui::EventType::kTouchReleased:
if (old_mouse_warp_status_)
SetMouseWarpEnabled(*old_mouse_warp_status_);
old_mouse_warp_status_.reset();
OnLocatedEventReleased(location_in_root);
break;
default:
break;
}
}
FineTunePosition CaptureModeSession::GetFineTunePosition(
const gfx::Point& location_in_screen,
bool is_touch) const {
if (controller_->user_capture_region().IsEmpty())
return FineTunePosition::kNone;
gfx::Rect capture_region_in_screen = controller_->user_capture_region();
wm::ConvertRectToScreen(current_root_, &capture_region_in_screen);
static const std::vector<FineTunePosition> drag_positions = {
FineTunePosition::kBottomRightVertex, FineTunePosition::kBottomLeftVertex,
FineTunePosition::kTopLeftVertex, FineTunePosition::kTopRightVertex,
FineTunePosition::kBottomEdge, FineTunePosition::kLeftEdge,
FineTunePosition::kTopEdge, FineTunePosition::kRightEdge};
for (FineTunePosition position : drag_positions) {
if (GetHitTestRectForFineTunePosition(capture_region_in_screen, position,
is_touch, active_behavior_)
.Contains(location_in_screen)) {
return position;
}
}
if (capture_region_in_screen.Contains(location_in_screen))
return FineTunePosition::kCenter;
return FineTunePosition::kNone;
}
void CaptureModeSession::OnLocatedEventPressed(
const gfx::Point& location_in_root,
bool is_touch) {
initial_location_in_root_ = location_in_root;
previous_location_in_root_ = location_in_root;
is_drag_in_progress_ = true;
Shell::Get()->UpdateCursorCompositingEnabled();
if (user_nudge_controller_)
user_nudge_controller_->SetVisible(false);
gfx::Point screen_location = location_in_root;
wm::ConvertPointToScreen(current_root_, &screen_location);
MaybeUpdateCaptureUisOpacity(screen_location);
InvalidateImageSearch();
absl::Cleanup deferred_runner = [session = weak_ptr_factory_.GetWeakPtr()] {
if (session) {
session->MaybeUpdateCameraPreviewBounds();
}
};
if (is_selecting_region_)
return;
fine_tune_position_ = GetFineTunePosition(screen_location, is_touch);
ClearActionContainer();
if (fine_tune_position_ == FineTunePosition::kNone) {
is_selecting_region_ = true;
UpdateCaptureRegion(gfx::Rect(), true, true,
false);
num_capture_region_adjusted_ = 0;
return;
}
if (fine_tune_position_ != FineTunePosition::kNone) {
++num_capture_region_adjusted_;
RepaintRegion();
}
if (fine_tune_position_ != FineTunePosition::kCenter &&
fine_tune_position_ != FineTunePosition::kNone) {
anchor_points_ = GetAnchorPointsForPosition(fine_tune_position_);
const gfx::Point position_location =
capture_mode_util::GetLocationForFineTunePosition(
controller_->user_capture_region(), fine_tune_position_);
MaybeShowMagnifierGlassAtPoint(position_location);
}
}
void CaptureModeSession::OnLocatedEventDragged(
const gfx::Point& location_in_root) {
const gfx::Point previous_location_in_root = previous_location_in_root_;
previous_location_in_root_ = location_in_root;
controller_->OnLocatedEventDragged();
if (is_selecting_region_) {
UpdateCaptureRegion(
GetRectEnclosingPoints({initial_location_in_root_, location_in_root},
current_root_),
true, true,
false);
return;
}
if (fine_tune_position_ == FineTunePosition::kNone)
return;
if (fine_tune_position_ == FineTunePosition::kCenter) {
gfx::Rect new_capture_region = controller_->user_capture_region();
new_capture_region.Offset(location_in_root - previous_location_in_root);
new_capture_region.AdjustToFit(current_root_->bounds());
UpdateCaptureRegion(new_capture_region, false,
true, false);
return;
}
std::vector<gfx::Point> points = anchor_points_;
DCHECK(!points.empty());
gfx::Point resizing_point = location_in_root;
if (fine_tune_position_ == FineTunePosition::kLeftEdge ||
fine_tune_position_ == FineTunePosition::kRightEdge) {
resizing_point.set_y(points.front().y());
} else if (fine_tune_position_ == FineTunePosition::kTopEdge ||
fine_tune_position_ == FineTunePosition::kBottomEdge) {
resizing_point.set_x(points.front().x());
}
points.push_back(resizing_point);
UpdateCaptureRegion(GetRectEnclosingPoints(points, current_root_),
true, true,
false);
MaybeShowMagnifierGlassAtPoint(location_in_root);
}
void CaptureModeSession::OnLocatedEventReleased(
const gfx::Point& location_in_root) {
if (user_nudge_controller_)
user_nudge_controller_->SetVisible(true);
gfx::Point screen_location = location_in_root;
wm::ConvertPointToScreen(current_root_, &screen_location);
EndSelection(screen_location);
RepaintRegion();
absl::Cleanup deferred_runner = [session = weak_ptr_factory_.GetWeakPtr()] {
if (session) {
session->MaybeUpdateCameraPreviewBounds();
}
};
if (ShowDefaultActionButtonsOrPerformSearch()) {
return;
}
if (!is_selecting_region_) {
return;
}
is_selecting_region_ = false;
UpdateCaptureLabelWidget(CaptureLabelAnimation::kRegionPhaseChange);
UpdateActionContainerWidget();
A11yAlertCaptureSource(true);
}
void CaptureModeSession::UpdateCaptureRegion(
const gfx::Rect& new_capture_region,
bool is_resizing,
bool by_user,
bool root_window_will_shutdown) {
const gfx::Rect old_capture_region = controller_->user_capture_region();
if (old_capture_region == new_capture_region)
return;
gfx::Rect damage_region = old_capture_region;
damage_region.Union(new_capture_region);
if (capture_region_overlay_controller_ &&
capture_region_overlay_controller_->HasGlowAnimation()) {
capture_region_overlay_controller_->RemoveGlowAnimation();
damage_region.Outset(kDamageWithGlowOutsetDp);
} else {
damage_region.Outset(active_behavior_->ShouldPaintSunfishCaptureRegion()
? kSunfishRegionDamageOutsetDp
: kDamageOutsetDp);
}
layer()->SchedulePaint(damage_region);
controller_->SetUserCaptureRegion(new_capture_region, by_user);
InvalidateImageSearch();
ClearActionContainer();
UpdateDimensionsLabelWidget(is_resizing);
UpdateCaptureLabelWidget(CaptureLabelAnimation::kNone);
if (!root_window_will_shutdown) {
focus_cycler_->OnFineTunePositionUpdated(false);
}
image_search_request_timer_.Start(
FROM_HERE, kImageSearchRequestStartDelay,
base::BindOnce(
[](base::WeakPtr<CaptureModeSession> capture_mode_session) {
if (capture_mode_session) {
std::ignore = capture_mode_session
->ShowDefaultActionButtonsOrPerformSearch();
}
},
weak_ptr_factory_.GetWeakPtr()));
}
void CaptureModeSession::UpdateDimensionsLabelWidget(bool is_resizing) {
const bool should_not_show =
!is_resizing || controller_->source() != CaptureModeSource::kRegion ||
controller_->user_capture_region().IsEmpty();
if (should_not_show) {
dimensions_label_widget_.reset();
return;
}
if (!dimensions_label_widget_) {
auto* parent = GetParentContainer(current_root_);
dimensions_label_widget_ = std::make_unique<views::Widget>();
dimensions_label_widget_->Init(
CreateWidgetParams(parent, gfx::Rect(), "CaptureModeDimensionsLabel"));
dimensions_label_widget_->SetVisibilityAnimationTransition(
views::Widget::VisibilityTransition::ANIMATE_SHOW);
auto size_label = std::make_unique<views::Label>();
size_label->SetEnabledColor(kColorAshTextColorPrimary);
size_label->SetBackground(views::CreateRoundedRectBackground(
kColorAshShieldAndBase80, kSizeLabelBorderRadius));
size_label->SetAutoColorReadabilityEnabled(false);
dimensions_label_widget_->SetContentsView(std::move(size_label));
dimensions_label_widget_->Show();
if (parent == capture_mode_bar_widget_->GetNativeWindow()->parent()) {
parent->StackChildBelow(dimensions_label_widget_->GetNativeWindow(),
capture_mode_bar_widget_->GetNativeWindow());
}
}
views::Label* size_label =
static_cast<views::Label*>(dimensions_label_widget_->GetContentsView());
const gfx::Rect capture_region = controller_->user_capture_region();
size_label->SetText(base::UTF8ToUTF16(base::StringPrintf(
"%d x %d", capture_region.width(), capture_region.height())));
UpdateDimensionsLabelBounds();
}
void CaptureModeSession::UpdateDimensionsLabelBounds() {
DCHECK(dimensions_label_widget_ &&
dimensions_label_widget_->GetContentsView());
gfx::Rect bounds(
dimensions_label_widget_->GetContentsView()->GetPreferredSize());
const gfx::Rect capture_region = controller_->user_capture_region();
gfx::Rect screen_region = current_root_->bounds();
bounds.set_width(bounds.width() + 2 * kSizeLabelHorizontalPadding);
bounds.set_x(capture_region.CenterPoint().x() - bounds.width() / 2);
bounds.set_y(capture_region.bottom() + kSizeLabelYDistanceFromRegionDp);
screen_region.Inset(
gfx::Insets::TLBR(0, 0, kSizeLabelYDistanceFromRegionDp, 0));
bounds.AdjustToFit(screen_region);
wm::ConvertRectToScreen(current_root_, &bounds);
dimensions_label_widget_->SetBounds(bounds);
}
void CaptureModeSession::MaybeShowMagnifierGlassAtPoint(
const gfx::Point& location_in_root) {
if (!capture_mode_util::IsCornerFineTunePosition(fine_tune_position_))
return;
magnifier_glass_.ShowFor(current_root_, location_in_root);
}
void CaptureModeSession::CloseMagnifierGlass() {
magnifier_glass_.Close();
}
std::vector<gfx::Point> CaptureModeSession::GetAnchorPointsForPosition(
FineTunePosition position) {
std::vector<gfx::Point> anchor_points;
const gfx::Rect rect = controller_->user_capture_region();
switch (position) {
case FineTunePosition::kNone:
case FineTunePosition::kCenter:
break;
case FineTunePosition::kTopLeftVertex:
anchor_points.push_back(rect.bottom_right());
break;
case FineTunePosition::kTopEdge:
anchor_points.push_back(rect.bottom_left());
anchor_points.push_back(rect.bottom_right());
break;
case FineTunePosition::kTopRightVertex:
anchor_points.push_back(rect.bottom_left());
break;
case FineTunePosition::kLeftEdge:
anchor_points.push_back(rect.top_right());
anchor_points.push_back(rect.bottom_right());
break;
case FineTunePosition::kRightEdge:
anchor_points.push_back(rect.origin());
anchor_points.push_back(rect.bottom_left());
break;
case FineTunePosition::kBottomLeftVertex:
anchor_points.push_back(rect.top_right());
break;
case FineTunePosition::kBottomEdge:
anchor_points.push_back(rect.origin());
anchor_points.push_back(rect.top_right());
break;
case FineTunePosition::kBottomRightVertex:
anchor_points.push_back(rect.origin());
break;
}
DCHECK(!anchor_points.empty());
DCHECK_LE(anchor_points.size(), 2u);
return anchor_points;
}
void CaptureModeSession::UpdateCaptureLabelWidget(
CaptureLabelAnimation animation_type) {
if (!capture_label_widget_) {
capture_label_widget_ = std::make_unique<views::Widget>();
auto* parent = GetParentContainer(current_root_);
capture_label_widget_->Init(
CreateWidgetParams(parent, gfx::Rect(), "CaptureLabel"));
capture_label_view_ = capture_label_widget_->SetContentsView(
std::make_unique<CaptureLabelView>(
this,
base::BindRepeating(&CaptureModeSession::DoPerformCapture,
base::Unretained(this)),
base::BindRepeating(
&CaptureModeSession::OnRecordingTypeDropDownButtonPressed,
base::Unretained(this))));
capture_label_widget_->GetNativeWindow()->SetTitle(
l10n_util::GetStringUTF16(IDS_ASH_SCREEN_CAPTURE_A11Y_TITLE));
capture_label_widget_->Show();
}
capture_label_view_->UpdateIconAndText();
UpdateCaptureLabelWidgetBounds(animation_type);
MaybeUpdateRecordingTypeMenu();
focus_cycler_->OnCaptureLabelWidgetUpdated();
}
void CaptureModeSession::UpdateCaptureLabelWidgetBounds(
CaptureLabelAnimation animation_type) {
DCHECK(capture_label_widget_);
const gfx::Rect bounds = CalculateCaptureLabelWidgetBounds();
const gfx::Rect old_bounds =
capture_label_widget_->GetNativeWindow()->GetBoundsInScreen();
if (old_bounds == bounds)
return;
if (animation_type == CaptureLabelAnimation::kNone) {
capture_label_widget_->SetBounds(bounds);
return;
}
ui::Layer* layer = capture_label_widget_->GetLayer();
ui::LayerAnimator* animator = layer->GetAnimator();
if (animation_type == CaptureLabelAnimation::kRegionPhaseChange) {
capture_label_widget_->SetBounds(bounds);
const gfx::Point center_point = bounds.CenterPoint();
layer->SetTransform(
gfx::GetScaleTransform(gfx::Point(center_point.x() - bounds.x(),
center_point.y() - bounds.y()),
kLabelScaleDownOnPhaseChange));
layer->SetOpacity(0.f);
ui::ScopedLayerAnimationSettings settings(animator);
settings.SetTransitionDuration(kCaptureLabelRegionPhaseChangeDuration);
settings.SetTweenType(gfx::Tween::ACCEL_LIN_DECEL_100);
settings.SetPreemptionStrategy(ui::LayerAnimator::ENQUEUE_NEW_ANIMATION);
animator->SchedulePauseForProperties(
kCaptureLabelRegionPhaseChangeDelay,
ui::LayerAnimationElement::TRANSFORM |
ui::LayerAnimationElement::OPACITY);
layer->SetTransform(gfx::Transform());
layer->SetOpacity(1.f);
return;
}
DCHECK_EQ(CaptureLabelAnimation::kCountdownStart, animation_type);
if (!old_bounds.IsEmpty()) {
ui::ScopedLayerAnimationSettings settings(animator);
settings.SetTweenType(gfx::Tween::LINEAR_OUT_SLOW_IN);
settings.SetPreemptionStrategy(
ui::LayerAnimator::IMMEDIATELY_ANIMATE_TO_NEW_TARGET);
settings.SetTransitionDuration(kCaptureLabelCountdownStartDuration);
capture_label_widget_->SetBounds(bounds);
} else {
capture_label_widget_->SetBounds(bounds);
const gfx::Point center_point = bounds.CenterPoint();
layer->SetTransform(
gfx::GetScaleTransform(gfx::Point(center_point.x() - bounds.x(),
center_point.y() - bounds.y()),
kLabelScaleUpOnCountdown));
layer->SetOpacity(0.f);
ui::ScopedLayerAnimationSettings settings(animator);
settings.SetTransitionDuration(kCaptureLabelCountdownStartDuration);
settings.SetTweenType(gfx::Tween::LINEAR);
settings.SetPreemptionStrategy(
ui::LayerAnimator::IMMEDIATELY_ANIMATE_TO_NEW_TARGET);
layer->SetOpacity(1.f);
settings.SetTweenType(gfx::Tween::LINEAR_OUT_SLOW_IN);
layer->SetTransform(gfx::Transform());
}
}
gfx::Rect CaptureModeSession::CalculateCaptureLabelWidgetBounds() {
DCHECK(capture_label_widget_);
DCHECK(capture_label_view_);
const gfx::Size preferred_size = capture_label_view_->GetPreferredSize();
const gfx::Rect capture_bar_bounds =
capture_mode_bar_widget_->GetNativeWindow()->bounds();
const gfx::Rect action_container_widget_bounds =
action_container_widget_ && action_container_widget_->IsVisible()
? action_container_widget_->GetNativeWindow()->bounds()
: gfx::Rect();
auto calculate_bounds = [&preferred_size, &capture_bar_bounds,
&action_container_widget_bounds](
const gfx::Rect& capture_bounds,
aura::Window* root) {
gfx::Rect label_bounds(capture_bounds);
gfx::Size capture_bounds_min_size = preferred_size;
capture_bounds_min_size.Enlarge(kCaptureRegionMinimumPaddingDp,
kCaptureRegionMinimumPaddingDp);
if (label_bounds.width() > capture_bounds_min_size.width() &&
label_bounds.height() > capture_bounds_min_size.height()) {
label_bounds.ClampToCenteredSize(preferred_size);
if (!label_bounds.Intersects(capture_bar_bounds))
return label_bounds;
}
return CalculateRegionEdgeBounds(preferred_size, capture_bar_bounds,
capture_bounds,
action_container_widget_bounds, root,
CaptureRegionWidgetAlignment::kCenter);
};
gfx::Rect bounds(current_root_->bounds());
const gfx::Rect capture_region = controller_->user_capture_region();
const gfx::Rect window_bounds = GetSelectedWindowTargetBounds();
const CaptureModeSource source = controller_->source();
if (source == CaptureModeSource::kRegion && !is_selecting_region_ &&
!capture_region.IsEmpty()) {
if (capture_label_view_->IsInCountDownAnimation()) {
bounds = capture_label_widget_->GetNativeWindow()->bounds();
bounds.ClampToCenteredSize(preferred_size);
} else {
bounds = calculate_bounds(capture_region, current_root_);
}
} else if (source == CaptureModeSource::kWindow && !window_bounds.IsEmpty()) {
bounds = calculate_bounds(window_bounds, current_root_);
} else {
bounds.ClampToCenteredSize(preferred_size);
}
wm::ConvertRectToScreen(current_root_, &bounds);
return bounds;
}
bool CaptureModeSession::ShouldCaptureLabelHandleEvent(
aura::Window* event_target) {
if (!capture_label_widget_ ||
capture_label_widget_->GetNativeWindow() != event_target) {
return false;
}
DCHECK(capture_label_view_);
return capture_label_view_->ShouldHandleEvent();
}
void CaptureModeSession::UpdateRootWindowDimmers() {
root_window_dimmers_.clear();
for (aura::Window* root_window : Shell::GetAllRootWindows()) {
if (root_window == current_root_) {
continue;
}
auto dimmer = std::make_unique<WindowDimmer>(root_window);
dimmer->SetDimColor(capture_mode::kDimmingShieldColor);
dimmer->window()->Show();
root_window_dimmers_.emplace(std::move(dimmer));
}
}
bool CaptureModeSession::IsUsingCustomCursor(CaptureModeType type) const {
return cursor_setter_->IsUsingCustomCursor(static_cast<int>(type));
}
void CaptureModeSession::ClampCaptureRegionToRootWindowSize() {
InvalidateImageSearch();
gfx::Rect new_capture_region = controller_->user_capture_region();
new_capture_region.AdjustToFit(current_root_->bounds());
controller_->SetUserCaptureRegion(new_capture_region, false);
}
void CaptureModeSession::EndSelection(
std::optional<gfx::Point> cursor_screen_location) {
fine_tune_position_ = FineTunePosition::kNone;
anchor_points_.clear();
is_drag_in_progress_ = false;
Shell::Get()->UpdateCursorCompositingEnabled();
MaybeUpdateCaptureUisOpacity(cursor_screen_location);
UpdateActionContainerWidget();
UpdateDimensionsLabelWidget(false);
CloseMagnifierGlass();
InvalidateImageSearch();
}
void CaptureModeSession::RepaintRegion() {
gfx::Rect damage_region = controller_->user_capture_region();
damage_region.Outset(active_behavior_->ShouldPaintSunfishCaptureRegion()
? kSunfishRegionDamageOutsetDp
: kDamageOutsetDp);
layer()->SchedulePaint(damage_region);
}
void CaptureModeSession::SelectDefaultRegion() {
is_selecting_region_ = false;
gfx::Rect default_capture_region = current_root_->bounds();
default_capture_region.ClampToCenteredSize(gfx::ScaleToCeiledSize(
default_capture_region.size(), kRegionDefaultRatio));
UpdateCaptureRegion(default_capture_region, false,
true, false);
capture_mode_util::TriggerAccessibilityAlert(
IDS_ASH_SCREEN_CAPTURE_ALERT_DEFAULT_REGION_SELECTED);
}
void CaptureModeSession::UpdateRegionForArrowKeys(ui::KeyboardCode key_code,
int event_flags) {
CHECK(focus_cycler_);
const FineTunePosition focused_fine_tune_position =
focus_cycler_->GetFocusedFineTunePosition();
if (focused_fine_tune_position == FineTunePosition::kNone) {
return;
}
switch (key_code) {
case ui::VKEY_LEFT:
case ui::VKEY_RIGHT:
if (focused_fine_tune_position == FineTunePosition::kTopEdge ||
focused_fine_tune_position == FineTunePosition::kBottomEdge) {
return;
}
break;
case ui::VKEY_UP:
case ui::VKEY_DOWN:
if (focused_fine_tune_position == FineTunePosition::kLeftEdge ||
focused_fine_tune_position == FineTunePosition::kRightEdge) {
return;
}
break;
default:
NOTREACHED();
}
const bool horizontal =
key_code == ui::VKEY_LEFT || key_code == ui::VKEY_RIGHT;
const int change = GetArrowKeyPressChange(event_flags);
gfx::Rect new_capture_region = controller_->user_capture_region();
if (focused_fine_tune_position == FineTunePosition::kCenter) {
if (horizontal) {
new_capture_region.Offset(key_code == ui::VKEY_LEFT ? -change : change,
0);
} else {
new_capture_region.Offset(0, key_code == ui::VKEY_UP ? -change : change);
}
new_capture_region.AdjustToFit(current_root_->bounds());
} else {
const gfx::Point location =
capture_mode_util::GetLocationForFineTunePosition(
new_capture_region, focused_fine_tune_position);
gfx::Insets insets;
if (horizontal) {
const bool affordance_on_left = location.x() == new_capture_region.x();
const bool shrink = affordance_on_left ^ (key_code == ui::VKEY_LEFT);
if (shrink && new_capture_region.width() < change) {
return;
}
const int inset = shrink ? change : -change;
insets = gfx::Insets::TLBR(0, affordance_on_left ? inset : 0, 0,
affordance_on_left ? 0 : inset);
} else {
const bool affordance_on_top = location.y() == new_capture_region.y();
const bool shrink = affordance_on_top ^ (key_code == ui::VKEY_UP);
if (shrink && new_capture_region.height() < change) {
return;
}
const int inset = shrink ? change : -change;
insets = gfx::Insets::TLBR(affordance_on_top ? inset : 0, 0,
affordance_on_top ? 0 : inset, 0);
}
new_capture_region.Inset(insets);
new_capture_region.Intersect(current_root_->bounds());
}
UpdateCaptureRegion(new_capture_region, false,
true, false);
}
void CaptureModeSession::MaybeReparentCameraPreviewWidget() {
if (!controller_->is_recording_in_progress())
controller_->camera_controller()->MaybeReparentPreviewWidget();
}
void CaptureModeSession::MaybeUpdateCameraPreviewBounds() {
if (!controller_->is_recording_in_progress()) {
controller_->camera_controller()->MaybeUpdatePreviewWidget(
false);
}
}
void CaptureModeSession::SetRecordingTypeMenuShown(bool shown,
bool by_key_event) {
if (!shown) {
recording_type_menu_widget_.reset();
recording_type_menu_view_ = nullptr;
return;
}
if (!recording_type_menu_widget_) {
DCHECK(capture_label_widget_);
DCHECK(capture_label_widget_->IsVisible());
SetSettingsMenuShown(false);
auto* parent = GetParentContainer(current_root_);
recording_type_menu_widget_ = std::make_unique<views::Widget>();
capture_toast_controller_.DismissCurrentToastIfAny();
recording_type_menu_widget_->Init(
CreateWidgetParams(parent,
RecordingTypeMenuView::GetIdealScreenBounds(
capture_label_widget_->GetWindowBoundsInScreen(),
current_root_->GetBoundsInScreen()),
"RecordingTypeMenuWidget"));
recording_type_menu_view_ = recording_type_menu_widget_->SetContentsView(
std::make_unique<RecordingTypeMenuView>(
base::BindRepeating(&CaptureModeSession::SetRecordingTypeMenuShown,
weak_ptr_factory_.GetWeakPtr(), false,
false)));
auto* menu_window = recording_type_menu_widget_->GetNativeWindow();
parent->StackChildAtTop(menu_window);
menu_window->SetTitle(l10n_util::GetStringUTF16(
IDS_ASH_SCREEN_CAPTURE_RECORDING_TYPE_MENU_A11Y_TITLE));
focus_cycler_->OnMenuOpened(
recording_type_menu_widget_.get(),
CaptureModeSessionFocusCycler::FocusGroup::kPendingRecordingType,
by_key_event);
}
recording_type_menu_widget_->Show();
}
bool CaptureModeSession::IsPointOnRecordingTypeDropDownButton(
const gfx::Point& screen_location) const {
if (!capture_label_widget_ || !capture_label_widget_->IsVisible())
return false;
DCHECK(capture_label_view_);
return capture_label_view_->IsPointOnRecordingTypeDropDownButton(
screen_location);
}
void CaptureModeSession::MaybeUpdateRecordingTypeMenu() {
if (!recording_type_menu_widget_)
return;
if (!capture_label_widget_ ||
!capture_label_view_->IsRecordingTypeDropDownButtonVisible()) {
SetRecordingTypeMenuShown(false);
return;
}
recording_type_menu_widget_->SetBounds(
RecordingTypeMenuView::GetIdealScreenBounds(
capture_label_widget_->GetWindowBoundsInScreen(),
current_root_->GetBoundsInScreen(),
recording_type_menu_widget_->GetContentsView()));
}
bool CaptureModeSession::IsPointOverSelectedWindow(
const gfx::Point& screen_point) const {
auto* selected_window = GetSelectedWindow();
return selected_window &&
(capture_mode_util::GetTopMostCapturableWindowAtPoint(screen_point) ==
selected_window);
}
void CaptureModeSession::UpdateActionContainerWidget() {
if (!ShouldShowActionContainerWidget()) {
if (action_container_widget_ && action_container_widget_->IsVisible()) {
action_container_view_->ClearContainer();
action_container_widget_->Hide();
}
return;
}
if (!action_container_widget_) {
action_container_widget_ = std::make_unique<views::Widget>();
auto* parent = GetParentContainer(current_root_);
action_container_widget_->Init(
CreateWidgetParams(parent, gfx::Rect(), "ActionButtonsContainer"));
action_container_widget_->widget_delegate()->SetTitle(
active_behavior_->GetActionButtonContainerTitle());
action_container_view_ = action_container_widget_->SetContentsView(
std::make_unique<ActionButtonContainerView>());
}
if (action_container_view_->GetPreferredSize().IsEmpty()) {
action_container_widget_->Hide();
return;
}
action_container_widget_->Show();
const gfx::Rect bounds = CalculateActionContainerWidgetBounds();
if (bounds != action_container_widget_->GetWindowBoundsInScreen()) {
action_container_widget_->SetBounds(bounds);
}
}
gfx::Rect CaptureModeSession::CalculateActionContainerWidgetBounds() const {
DCHECK(action_container_widget_);
const gfx::Size preferred_size = action_container_view_->GetPreferredSize();
const gfx::Rect capture_bar_bounds =
capture_mode_bar_widget_->GetNativeWindow()->bounds();
const gfx::Rect capture_label_widget_bounds =
capture_label_widget_ ? capture_label_widget_->GetNativeWindow()->bounds()
: gfx::Rect();
const gfx::Rect capture_region = controller_->user_capture_region();
gfx::Rect bounds = CalculateRegionEdgeBounds(
preferred_size, capture_bar_bounds, capture_region,
capture_label_widget_bounds, current_root_,
CaptureRegionWidgetAlignment::kRight);
wm::ConvertRectToScreen(current_root_, &bounds);
return bounds;
}
void CaptureModeSession::ClearActionContainer() {
if (action_container_widget_) {
CHECK(action_container_view_);
action_container_view_->ClearContainer();
UpdateActionContainerWidget();
}
}
[[nodiscard]] bool
CaptureModeSession::ShowDefaultActionButtonsOrPerformSearch() {
InvalidateImageSearch();
if (!ShouldShowActionContainerWidgetWithoutFeatureChecks()) {
return false;
}
if (!CanShowSunfishOrScannerUi()) {
if (active_behavior_->behavior_type() == BehaviorType::kDefault) {
RecordScannerFeatureUserState(
ScannerFeatureUserState::
kSmartActionsButtonNotShownDueToFeatureChecks);
}
return false;
}
if (active_behavior_->ShouldShowDefaultActionButtonsInActionContainer() &&
CanShowSunfishUi()) {
if (controller_->IsNetworkConnectionOffline()) {
ShowActionContainerError(l10n_util::GetStringUTF16(
IDS_ASH_SCREEN_CAPTURE_MORE_ACTIONS_UNAVAILABLE_OFFLINE_ERROR));
} else {
RecordSearchButtonShown();
capture_mode_util::AddActionButton(
base::BindRepeating(&CaptureModeSession::OnSearchButtonPressed,
weak_ptr_factory_.GetWeakPtr()),
l10n_util::GetStringUTF16(
IDS_ASH_SCREEN_CAPTURE_SUNFISH_SEARCH_BUTTON_TITLE),
&kLensIcon,
ActionButtonRank(ActionButtonType::kSunfish, 1),
ActionButtonViewID::kSearchButton);
}
}
auto weak_ptr = weak_ptr_factory_.GetWeakPtr();
active_behavior_->OnRegionSelectedOrAdjustedWhenActionContainerShowing();
return !weak_ptr;
}
bool CaptureModeSession::ShouldShowActionContainerWidgetWithoutFeatureChecks()
const {
if (is_drag_in_progress_) {
return false;
}
if (controller_->type() != CaptureModeType::kImage ||
controller_->source() != CaptureModeSource::kRegion ||
controller_->user_capture_region().IsEmpty()) {
return false;
}
if (!active_behavior_->CanShowActionButtons()) {
return false;
}
return true;
}
bool CaptureModeSession::ShouldShowActionContainerWidget() const {
return ShouldShowActionContainerWidgetWithoutFeatureChecks() &&
CanShowSunfishOrScannerUi();
}
void CaptureModeSession::MaybeRemoveGlowAnimation() {
if (capture_region_overlay_controller_ &&
capture_region_overlay_controller_->HasGlowAnimation()) {
capture_region_overlay_controller_->RemoveGlowAnimation();
RefreshGlowRegion();
}
}
void CaptureModeSession::RefreshGlowRegion() {
gfx::Rect glow_bounds(controller_->user_capture_region());
glow_bounds.Outset(kDamageWithGlowOutsetDp);
layer()->SchedulePaint(glow_bounds);
}
void CaptureModeSession::InvalidateImageSearch() {
weak_token_factory_.InvalidateWeakPtrs();
image_search_request_timer_.Stop();
MaybeRemoveGlowAnimation();
}
void CaptureModeSession::InitInternal() {
layer()->set_delegate(this);
auto* parent = GetParentContainer(current_root_);
parent_container_observer_ =
std::make_unique<ParentContainerObserver>(parent, this);
parent->layer()->Add(layer());
layer()->SetBounds(parent->bounds());
layer()->SetName("CaptureModeSession");
if (!active_behavior_->NeedsDisclaimerOnInit()) {
capture_mode_util::TriggerAccessibilityAlert(
active_behavior_->GetCaptureModeOpenAnnouncement());
}
if (auto* capture_client = aura::client::GetCaptureClient(current_root_)) {
input_capture_window_ = capture_client->GetCaptureWindow();
if (input_capture_window_) {
aura::WindowTracker tracker({input_capture_window_.get()});
capture_client->ReleaseCapture(input_capture_window_);
if (tracker.windows().empty()) {
input_capture_window_ = nullptr;
} else {
input_capture_window_->AddObserver(this);
}
}
}
ClampCaptureRegionToRootWindowSize();
capture_mode_bar_widget_->Init(
CreateWidgetParams(GetParentContainer(current_root_),
active_behavior_->GetCaptureBarBounds(current_root_),
"CaptureModeBarWidget"));
capture_mode_bar_view_ = capture_mode_bar_widget_->SetContentsView(
active_behavior_->CreateCaptureModeBarView());
std::u16string capture_mode_bar_a11y_title =
active_behavior_->GetCaptureModeBarTitle();
capture_mode_bar_widget_->GetNativeWindow()->SetTitle(
capture_mode_bar_a11y_title);
capture_mode_bar_widget_->widget_delegate()->SetAccessibleTitle(
capture_mode_bar_a11y_title);
capture_mode_bar_widget_->Show();
if (Shell::Get()->accessibility_controller()->spoken_feedback().enabled()) {
focus_cycler_->AdvanceFocus(false);
}
UpdateActionContainerWidget();
if (ShowDefaultActionButtonsOrPerformSearch()) {
return;
}
if (!active_behavior_->NeedsDisclaimerOnInit()) {
UpdateCaptureLabelWidget(CaptureLabelAnimation::kNone);
}
UpdateCursor(display::Screen::Get()->GetCursorScreenPoint(),
false);
if (controller_->source() == CaptureModeSource::kWindow) {
capture_window_observer_ = std::make_unique<CaptureWindowObserver>(this);
}
UpdateRootWindowDimmers();
Observe(ColorUtil::GetColorProviderSourceForWindow(current_root_));
display_observer_.emplace(this);
aura::Env::GetInstance()->AddPreTargetHandler(
this, ui::EventTarget::Priority::kSystem);
UpdateFloatingPanelBoundsIfNeeded();
capture_mode_bar_view_->OnCaptureTypeChanged(controller_->type());
MaybeCreateSunfishRegionNudge();
if (active_behavior_->ShouldRegionOverlayBeAllowed()) {
capture_region_overlay_controller_ =
std::make_unique<CaptureRegionOverlayController>();
}
}
void CaptureModeSession::ShutdownInternal() {
aura::Env::GetInstance()->RemovePreTargetHandler(this);
capture_region_overlay_controller_.reset();
InvalidateImageSearch();
display_observer_.reset();
user_nudge_controller_.reset();
capture_window_observer_.reset();
Observe(nullptr);
if (input_capture_window_) {
input_capture_window_->RemoveObserver(this);
if (auto* client = aura::client::GetCaptureClient(
input_capture_window_->GetRootWindow())) {
client->SetCapture(input_capture_window_);
}
}
if (old_mouse_warp_status_) {
SetMouseWarpEnabled(*old_mouse_warp_status_);
}
capture_mode_bar_view_ = nullptr;
capture_mode_settings_view_ = nullptr;
capture_label_view_ = nullptr;
recording_type_menu_view_ = nullptr;
action_container_view_ = nullptr;
for (auto* widget : GetAvailableWidgets()) {
widget->CloseNow();
}
if (a11y_alert_on_session_exit_) {
capture_mode_util::TriggerAccessibilityAlert(
IDS_ASH_SCREEN_CAPTURE_ALERT_CLOSE);
}
UpdateFloatingPanelBoundsIfNeeded();
}
void CaptureModeSession::OnSearchResultsPanelCreated(
views::Widget* panel_widget) {
focus_cycler_->OnSearchResultsPanelCreated(panel_widget);
}
bool CaptureModeSession::TakeFocusForSearchResultsPanel(bool reverse) {
focus_cycler_->AdvanceFocusAfterSearchResultsPanel(reverse);
return true;
}
void CaptureModeSession::ClearPseudoFocus() {
focus_cycler_->ClearFocus();
}
void CaptureModeSession::SetA11yOverrideWindowToSearchResultsPanel() {
focus_cycler_->SetA11yOverrideWindowToSearchResultsPanel();
}
}