123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469 |
- // Copyright 2020 The Chromium Authors. All rights reserved.
- // Use of this source code is governed by a BSD-style license that can be
- // found in the LICENSE file.
- #include "ash/capture_mode/capture_mode_util.h"
- #include "ash/accessibility/accessibility_controller_impl.h"
- #include "ash/capture_mode/capture_mode_camera_controller.h"
- #include "ash/capture_mode/capture_mode_constants.h"
- #include "ash/capture_mode/capture_mode_controller.h"
- #include "ash/capture_mode/capture_mode_session.h"
- #include "ash/capture_mode/stop_recording_button_tray.h"
- #include "ash/constants/ash_features.h"
- #include "ash/public/cpp/clipboard_history_controller.h"
- #include "ash/public/cpp/style/scoped_light_mode_as_default.h"
- #include "ash/public/cpp/window_finder.h"
- #include "ash/resources/vector_icons/vector_icons.h"
- #include "ash/root_window_controller.h"
- #include "ash/shell.h"
- #include "ash/strings/grit/ash_strings.h"
- #include "ash/style/ash_color_provider.h"
- #include "ash/wm/tablet_mode/tablet_mode_controller.h"
- #include "base/check.h"
- #include "base/notreached.h"
- #include "ui/base/l10n/l10n_util.h"
- #include "ui/chromeos/events/keyboard_layout_util.h"
- #include "ui/compositor/layer.h"
- #include "ui/gfx/geometry/point.h"
- #include "ui/gfx/geometry/rect.h"
- #include "ui/gfx/geometry/transform_util.h"
- #include "ui/gfx/paint_vector_icon.h"
- #include "ui/message_center/views/notification_background_painter.h"
- #include "ui/views/animation/animation_builder.h"
- #include "ui/views/controls/image_view.h"
- #include "ui/views/layout/box_layout.h"
- #include "ui/views/widget/widget.h"
- namespace ash::capture_mode_util {
- namespace {
- constexpr int kBannerViewTopRadius = 0;
- constexpr int kBannerViewBottomRadius = 8;
- constexpr float kScaleUpFactor = 0.8f;
- // Returns the target visibility of the camera preview, given the
- // `confine_bounds_short_side_length`. The out parameter
- // `out_is_surface_too_small` will be set to true if the preview should be
- // hidden due to the surface within which it's confined is too small. Otherwise,
- // it's unchanged.
- bool CalculateCameraPreviewTargetVisibility(
- int confine_bounds_short_side_length,
- bool* out_is_surface_too_small) {
- DCHECK(out_is_surface_too_small);
- // If the short side of the bounds within which the camera preview should be
- // confined is too small, the camera should be hidden.
- if (confine_bounds_short_side_length <
- capture_mode::kMinCaptureSurfaceShortSideLengthForVisibleCamera) {
- *out_is_surface_too_small = true;
- return false;
- }
- // Now that we determined that its size doesn't affect its visibility, we need
- // to check if we're in a capture mode session that is in a state that affects
- // the camera preview's visibility.
- auto* controller = CaptureModeController::Get();
- return !controller->IsActive() ||
- controller->capture_mode_session()
- ->CalculateCameraPreviewTargetVisibility();
- }
- // Returns the local center point of the given `layer`.
- gfx::Point GetLocalCenterPoint(ui::Layer* layer) {
- return gfx::Rect(layer->GetTargetBounds().size()).CenterPoint();
- }
- void FadeInWidget(views::Widget* widget,
- const AnimationParams& animation_params) {
- DCHECK(widget);
- auto* layer = widget->GetLayer();
- DCHECK(!widget->GetNativeWindow()->TargetVisibility() ||
- layer->GetTargetOpacity() < 1.f);
- // Please notice the order matters here. When the opacity is set to 0.f, if
- // there's any on-going fade out animation, the `OnEnded` in `FadeOutWidget`
- // will be triggered, which will hide the widget and set its opacity to 1.f.
- // So `Show` should be triggered after setting the opacity to 0 to undo the
- // work done by the FadeOutWidget's OnEnded .
- if (layer->opacity() == 1.f)
- layer->SetOpacity(0.f);
- if (!widget->GetNativeWindow()->TargetVisibility())
- widget->Show();
- if (animation_params.apply_scale_up_animation) {
- layer->SetTransform(
- capture_mode_util::GetScaleTransformAboutCenter(layer, kScaleUpFactor));
- }
- views::AnimationBuilder builder;
- auto& animation_sequence_block =
- builder
- .SetPreemptionStrategy(
- ui::LayerAnimator::IMMEDIATELY_ANIMATE_TO_NEW_TARGET)
- .Once()
- .SetDuration(animation_params.animation_duration)
- .SetOpacity(layer, 1.f, animation_params.tween_type);
- // We should only set transform here if `apply_scale_up_animation` is true,
- // otherwise, it may mess up with the snap animation in
- // `SetCameraPreviewBounds`.
- if (animation_params.apply_scale_up_animation) {
- animation_sequence_block.SetTransform(layer, gfx::Transform(),
- gfx::Tween::ACCEL_20_DECEL_100);
- }
- }
- void FadeOutWidget(views::Widget* widget,
- const AnimationParams& animation_params) {
- DCHECK(widget);
- DCHECK(widget->GetNativeWindow()->TargetVisibility());
- auto* layer = widget->GetLayer();
- DCHECK_EQ(layer->GetTargetOpacity(), 1.f);
- views::AnimationBuilder()
- .OnEnded(base::BindOnce(
- [](base::WeakPtr<views::Widget> the_widget) {
- if (!the_widget)
- return;
- // Please notice, the order matters here. If we set the layer's
- // opacity back to 1.f before calling `Hide`, flickering can be
- // seen.
- the_widget->Hide();
- the_widget->GetLayer()->SetOpacity(1.f);
- },
- widget->GetWeakPtr()))
- .SetPreemptionStrategy(
- ui::LayerAnimator::IMMEDIATELY_ANIMATE_TO_NEW_TARGET)
- .Once()
- .SetDuration(animation_params.animation_duration)
- .SetOpacity(layer, 0.f, animation_params.tween_type);
- }
- } // namespace
- bool IsCaptureModeActive() {
- return CaptureModeController::Get()->IsActive();
- }
- gfx::Point GetLocationForFineTunePosition(const gfx::Rect& rect,
- FineTunePosition position) {
- switch (position) {
- case FineTunePosition::kTopLeft:
- return rect.origin();
- case FineTunePosition::kTopCenter:
- return rect.top_center();
- case FineTunePosition::kTopRight:
- return rect.top_right();
- case FineTunePosition::kRightCenter:
- return rect.right_center();
- case FineTunePosition::kBottomRight:
- return rect.bottom_right();
- case FineTunePosition::kBottomCenter:
- return rect.bottom_center();
- case FineTunePosition::kBottomLeft:
- return rect.bottom_left();
- case FineTunePosition::kLeftCenter:
- return rect.left_center();
- default:
- break;
- }
- NOTREACHED();
- return gfx::Point();
- }
- bool IsCornerFineTunePosition(FineTunePosition position) {
- switch (position) {
- case FineTunePosition::kTopLeft:
- case FineTunePosition::kTopRight:
- case FineTunePosition::kBottomRight:
- case FineTunePosition::kBottomLeft:
- return true;
- default:
- break;
- }
- return false;
- }
- StopRecordingButtonTray* GetStopRecordingButtonForRoot(aura::Window* root) {
- DCHECK(root);
- DCHECK(root->IsRootWindow());
- // Recording can end when a display being fullscreen-captured gets removed, in
- // this case, we don't need to hide the button.
- if (root->is_destroying())
- return nullptr;
- // Can be null while shutting down.
- auto* root_window_controller = RootWindowController::ForWindow(root);
- if (!root_window_controller)
- return nullptr;
- auto* stop_recording_button = root_window_controller->GetStatusAreaWidget()
- ->stop_recording_button_tray();
- DCHECK(stop_recording_button);
- return stop_recording_button;
- }
- void SetStopRecordingButtonVisibility(aura::Window* root, bool visible) {
- if (auto* stop_recording_button = GetStopRecordingButtonForRoot(root))
- stop_recording_button->SetVisiblePreferred(visible);
- }
- void TriggerAccessibilityAlert(const std::string& message) {
- Shell::Get()
- ->accessibility_controller()
- ->TriggerAccessibilityAlertWithMessage(message);
- }
- void TriggerAccessibilityAlert(int message_id) {
- TriggerAccessibilityAlert(l10n_util::GetStringUTF8(message_id));
- }
- void TriggerAccessibilityAlertSoon(const std::string& message) {
- base::ThreadTaskRunnerHandle::Get()->PostTask(
- FROM_HERE,
- base::BindOnce(
- &AccessibilityControllerImpl::TriggerAccessibilityAlertWithMessage,
- Shell::Get()->accessibility_controller()->GetWeakPtr(), message));
- }
- void TriggerAccessibilityAlertSoon(int message_id) {
- TriggerAccessibilityAlertSoon(l10n_util::GetStringUTF8(message_id));
- }
- CameraPreviewSnapPosition GetCameraNextHorizontalSnapPosition(
- CameraPreviewSnapPosition current,
- bool going_left) {
- switch (current) {
- case CameraPreviewSnapPosition::kTopLeft:
- return going_left ? current : CameraPreviewSnapPosition::kTopRight;
- case CameraPreviewSnapPosition::kTopRight:
- return going_left ? CameraPreviewSnapPosition::kTopLeft : current;
- case CameraPreviewSnapPosition::kBottomLeft:
- return going_left ? current : CameraPreviewSnapPosition::kBottomRight;
- case CameraPreviewSnapPosition::kBottomRight:
- return going_left ? CameraPreviewSnapPosition::kBottomLeft : current;
- }
- }
- CameraPreviewSnapPosition GetCameraNextVerticalSnapPosition(
- CameraPreviewSnapPosition current,
- bool going_up) {
- switch (current) {
- case CameraPreviewSnapPosition::kTopLeft:
- return going_up ? current : CameraPreviewSnapPosition::kBottomLeft;
- case CameraPreviewSnapPosition::kTopRight:
- return going_up ? current : CameraPreviewSnapPosition::kBottomRight;
- case CameraPreviewSnapPosition::kBottomLeft:
- return going_up ? CameraPreviewSnapPosition::kTopLeft : current;
- case CameraPreviewSnapPosition::kBottomRight:
- return going_up ? CameraPreviewSnapPosition::kTopRight : current;
- }
- }
- std::unique_ptr<views::View> CreateClipboardShortcutView() {
- std::unique_ptr<views::View> clipboard_shortcut_view =
- std::make_unique<views::View>();
- auto* color_provider = AshColorProvider::Get();
- const SkColor background_color = color_provider->GetControlsLayerColor(
- AshColorProvider::ControlsLayerType::kControlBackgroundColorActive);
- // The text and icon are showing on the background with |background_color|
- // so its color is same with kButtonLabelColorPrimary although they're
- // not theoretically showing on a button.
- const SkColor text_icon_color = color_provider->GetContentLayerColor(
- AshColorProvider::ContentLayerType::kButtonLabelColorPrimary);
- clipboard_shortcut_view->SetLayoutManager(std::make_unique<views::BoxLayout>(
- views::BoxLayout::Orientation::kHorizontal));
- const std::u16string shortcut_key = l10n_util::GetStringUTF16(
- ui::DeviceUsesKeyboardLayout2() ? IDS_ASH_SHORTCUT_MODIFIER_LAUNCHER
- : IDS_ASH_SHORTCUT_MODIFIER_SEARCH);
- const std::u16string label_text = l10n_util::GetStringFUTF16(
- IDS_ASH_MULTIPASTE_SCREENSHOT_NOTIFICATION_NUDGE, shortcut_key);
- views::Label* shortcut_label =
- clipboard_shortcut_view->AddChildView(std::make_unique<views::Label>());
- shortcut_label->SetText(label_text);
- shortcut_label->SetBackgroundColor(background_color);
- shortcut_label->SetEnabledColor(text_icon_color);
- return clipboard_shortcut_view;
- }
- // Creates the banner view that will show on top of the notification image.
- std::unique_ptr<views::View> CreateBannerView() {
- std::unique_ptr<views::View> banner_view = std::make_unique<views::View>();
- // Use the light mode as default as notification is still using light
- // theme as the default theme.
- ScopedLightModeAsDefault scoped_light_mode_as_default;
- auto* color_provider = AshColorProvider::Get();
- const SkColor background_color = color_provider->GetControlsLayerColor(
- AshColorProvider::ControlsLayerType::kControlBackgroundColorActive);
- // The text and icon are showing on the background with |background_color|
- // so its color is same with kButtonLabelColorPrimary although they're
- // not theoretically showing on a button.
- const SkColor text_icon_color = color_provider->GetContentLayerColor(
- AshColorProvider::ContentLayerType::kButtonLabelColorPrimary);
- auto* layout =
- banner_view->SetLayoutManager(std::make_unique<views::BoxLayout>(
- views::BoxLayout::Orientation::kHorizontal,
- gfx::Insets::VH(kBannerVerticalInsetDip, kBannerHorizontalInsetDip),
- kBannerIconTextSpacingDip));
- if (features::IsNotificationsRefreshEnabled()) {
- banner_view->SetBackground(views::CreateBackgroundFromPainter(
- std::make_unique<message_center::NotificationBackgroundPainter>(
- kBannerViewTopRadius, kBannerViewBottomRadius, background_color)));
- } else {
- banner_view->SetBackground(views::CreateSolidBackground(background_color));
- }
- views::ImageView* icon =
- banner_view->AddChildView(std::make_unique<views::ImageView>());
- icon->SetImage(gfx::CreateVectorIcon(kCaptureModeCopiedToClipboardIcon,
- kBannerIconSizeDip, text_icon_color));
- views::Label* label = banner_view->AddChildView(
- std::make_unique<views::Label>(l10n_util::GetStringUTF16(
- IDS_ASH_SCREEN_CAPTURE_SCREENSHOT_COPIED_TO_CLIPBOARD)));
- label->SetBackgroundColor(background_color);
- label->SetHorizontalAlignment(gfx::ALIGN_LEFT);
- label->SetEnabledColor(text_icon_color);
- if (!Shell::Get()->tablet_mode_controller()->InTabletMode()) {
- banner_view->AddChildView(CreateClipboardShortcutView());
- layout->SetFlexForView(label, 1);
- // Notify the clipboard history of the created notification.
- ClipboardHistoryController::Get()->OnScreenshotNotificationCreated();
- }
- return banner_view;
- }
- // Creates the play icon view which shows on top of the video thumbnail in the
- // notification.
- std::unique_ptr<views::View> CreatePlayIconView() {
- auto play_view = std::make_unique<views::ImageView>();
- auto* color_provider = AshColorProvider::Get();
- const SkColor icon_color = color_provider->GetContentLayerColor(
- AshColorProvider::ContentLayerType::kIconColorPrimary);
- play_view->SetImage(gfx::CreateVectorIcon(kCaptureModePlayIcon,
- kPlayIconSizeDip, icon_color));
- play_view->SetHorizontalAlignment(views::ImageView::Alignment::kCenter);
- play_view->SetVerticalAlignment(views::ImageView::Alignment::kCenter);
- const SkColor background_color = color_provider->GetBaseLayerColor(
- AshColorProvider::BaseLayerType::kTransparent80);
- play_view->SetBackground(views::CreateRoundedRectBackground(
- background_color, kPlayIconBackgroundCornerRadiusDip));
- return play_view;
- }
- gfx::Transform GetScaleTransformAboutCenter(ui::Layer* layer, float scale) {
- return gfx::GetScaleTransform(GetLocalCenterPoint(layer), scale);
- }
- CameraPreviewSizeSpecs CalculateCameraPreviewSizeSpecs(
- const gfx::Size& confine_bounds_size,
- bool is_collapsed) {
- // We divide the shorter side of the confine bounds by a divider to calculate
- // the expanded diameter. Note that both expanded and collapsed diameters are
- // clamped at a minimum value of `kMinCameraPreviewDiameter`.
- const int short_side =
- std::min(confine_bounds_size.width(), confine_bounds_size.height());
- const int expanded_diameter =
- std::max(short_side / capture_mode::kCaptureSurfaceShortSideDivider,
- capture_mode::kMinCameraPreviewDiameter);
- // If the expanded diameter is below a certain threshold, we consider it too
- // small to allow it to collapse, and in that case the resize button will be
- // hidden.
- const bool is_collapsible =
- expanded_diameter >= capture_mode::kMinCollapsibleCameraPreviewDiameter;
- // Pick the actual diameter based on whether the preview is currently expanded
- // or collapsed.
- const int diameter =
- !is_collapsed
- ? expanded_diameter
- : std::max(expanded_diameter / capture_mode::kCollapsedPreviewDivider,
- capture_mode::kMinCameraPreviewDiameter);
- bool is_surface_too_small = false;
- const bool should_be_visible =
- CalculateCameraPreviewTargetVisibility(short_side, &is_surface_too_small);
- // If the surface was determined to be too small, the preview should be
- // hidden.
- DCHECK(!is_surface_too_small || !should_be_visible);
- return CameraPreviewSizeSpecs{gfx::Size(diameter, diameter), is_collapsible,
- should_be_visible, is_surface_too_small};
- }
- aura::Window* GetTopMostCapturableWindowAtPoint(
- const gfx::Point& screen_point) {
- auto* controller = CaptureModeController::Get();
- std::set<aura::Window*> ignore_windows;
- auto* camera_controller = controller->camera_controller();
- if (auto* camera_preview_widget = camera_controller->camera_preview_widget())
- ignore_windows.insert(camera_preview_widget->GetNativeWindow());
- if (controller->IsActive()) {
- auto* capture_session = controller->capture_mode_session();
- if (auto* capture_label_widget = capture_session->capture_label_widget())
- ignore_windows.insert(capture_label_widget->GetNativeWindow());
- if (auto* capture_toast_widget = capture_session->capture_toast_controller()
- ->capture_toast_widget()) {
- ignore_windows.insert(capture_toast_widget->GetNativeWindow());
- }
- }
- return GetTopmostWindowAtPoint(screen_point, ignore_windows);
- }
- bool GetWidgetCurrentVisibility(views::Widget* widget) {
- // Note that we use `aura::Window::TargetVisibility()` rather than
- // `views::Widget::IsVisible()` (which in turn uses
- // `aura::Window::IsVisible()`). The reason is because the latter takes into
- // account whether window's layer is drawn or not. We want to calculate the
- // current visibility only based on the actual visibility of the window
- // itself, so that we can correctly compare it against `target_visibility`.
- // Note that the widget may be a child of the unparented container (which is
- // always hidden), yet the native window is shown.
- return widget->GetNativeWindow()->TargetVisibility() &&
- widget->GetLayer()->GetTargetOpacity() > 0.f;
- }
- bool SetWidgetVisibility(views::Widget* widget,
- bool target_visibility,
- absl::optional<AnimationParams> animation_params) {
- DCHECK(widget);
- if (target_visibility == GetWidgetCurrentVisibility(widget))
- return false;
- if (animation_params) {
- if (target_visibility)
- FadeInWidget(widget, *animation_params);
- else
- FadeOutWidget(widget, *animation_params);
- } else {
- if (target_visibility)
- widget->Show();
- else
- widget->Hide();
- }
- return true;
- }
- } // namespace ash::capture_mode_util
|