123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456 |
- // Copyright 2022 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_camera_preview_view.h"
- #include "ash/accessibility/scoped_a11y_override_window_setter.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/capture_mode_util.h"
- #include "ash/resources/vector_icons/vector_icons.h"
- #include "ash/shell.h"
- #include "ash/strings/grit/ash_strings.h"
- #include "ash/style/ash_color_provider.h"
- #include "base/bind.h"
- #include "base/check.h"
- #include "ui/aura/window_tree_host.h"
- #include "ui/base/l10n/l10n_util.h"
- #include "ui/base/metadata/metadata_impl_macros.h"
- #include "ui/compositor/layer.h"
- #include "ui/events/event.h"
- #include "ui/gfx/geometry/point_f.h"
- #include "ui/gfx/paint_vector_icon.h"
- #include "ui/views/accessibility/view_accessibility.h"
- #include "ui/views/animation/animation_builder.h"
- #include "ui/views/background.h"
- #include "ui/views/controls/highlight_path_generator.h"
- #include "ui/views/controls/native/native_view_host.h"
- #include "ui/views/widget/widget.h"
- namespace ash {
- namespace {
- // The duration for the resize button fading in process.
- constexpr base::TimeDelta kResizeButtonFadeInDuration = base::Milliseconds(150);
- // The duration for the reize button fading out process.
- constexpr base::TimeDelta kResizeButtonFadeOutDuration = base::Milliseconds(50);
- gfx::PointF GetEventScreenLocation(const ui::LocatedEvent& event) {
- return event.target()->GetScreenLocationF(event);
- }
- const gfx::VectorIcon& GetIconOfResizeButton(
- const bool is_camera_preview_collapsed) {
- return is_camera_preview_collapsed ? kCaptureModeCameraPreviewExpandIcon
- : kCaptureModeCameraPreviewCollapseIcon;
- }
- bool IsArrowKeyEvent(const ui::KeyEvent* event) {
- const ui::KeyboardCode key_code = event->key_code();
- return key_code == ui::VKEY_DOWN || key_code == ui::VKEY_RIGHT ||
- key_code == ui::VKEY_LEFT || key_code == ui::VKEY_UP;
- }
- } // namespace
- // -----------------------------------------------------------------------------
- // CameraPreviewResizeButton:
- CameraPreviewResizeButton::CameraPreviewResizeButton(
- CameraPreviewView* camera_preview_view,
- views::Button::PressedCallback callback,
- const gfx::VectorIcon& icon)
- : CaptureModeButton(std::move(callback), icon),
- camera_preview_view_(camera_preview_view) {}
- CameraPreviewResizeButton::~CameraPreviewResizeButton() = default;
- void CameraPreviewResizeButton::PseudoFocus() {
- DCHECK(camera_preview_view_->is_collapsible());
- CaptureModeSessionFocusCycler::HighlightableView::PseudoFocus();
- camera_preview_view_->RefreshResizeButtonVisibility();
- }
- void CameraPreviewResizeButton::PseudoBlur() {
- CaptureModeSessionFocusCycler::HighlightableView::PseudoBlur();
- camera_preview_view_->ScheduleRefreshResizeButtonVisibility();
- }
- BEGIN_METADATA(CameraPreviewResizeButton, CaptureModeButton)
- END_METADATA
- // -----------------------------------------------------------------------------
- // CameraPreviewView:
- CameraPreviewView::CameraPreviewView(
- CaptureModeCameraController* camera_controller,
- const CameraId& camera_id,
- mojo::Remote<video_capture::mojom::VideoSource> camera_video_source,
- const media::VideoCaptureFormat& capture_format,
- bool should_flip_frames_horizontally)
- : camera_controller_(camera_controller),
- camera_id_(camera_id),
- camera_video_renderer_(std::move(camera_video_source),
- capture_format,
- should_flip_frames_horizontally),
- camera_video_host_view_(
- AddChildView(std::make_unique<views::NativeViewHost>())),
- resize_button_(AddChildView(std::make_unique<CameraPreviewResizeButton>(
- this,
- base::BindRepeating(&CameraPreviewView::OnResizeButtonPressed,
- base::Unretained(this)),
- GetIconOfResizeButton(
- camera_controller_->is_camera_preview_collapsed())))),
- scoped_a11y_overrider_(
- std::make_unique<ScopedA11yOverrideWindowSetter>()) {
- // Add an empty border to the camera preview. This is done to keep some gap
- // between the focus ring and the contents of the camera preview, as focus
- // ring will extend a little beyond the border.
- SetBorder(views::CreateEmptyBorder(capture_mode::kCameraPreviewBorderSize));
- resize_button_->SetPaintToLayer();
- resize_button_->layer()->SetFillsBoundsOpaquely(false);
- resize_button_->SetBackground(views::CreateRoundedRectBackground(
- AshColorProvider::Get()->GetBaseLayerColor(
- AshColorProvider::BaseLayerType::kTransparent80),
- resize_button_->GetPreferredSize().height() / 2.f));
- accessibility_observation_.Observe(Shell::Get()->accessibility_controller());
- RefreshResizeButtonVisibility();
- UpdateResizeButtonTooltip();
- }
- CameraPreviewView::~CameraPreviewView() {
- auto* controller = CaptureModeController::Get();
- if (controller->IsActive() && !controller->is_recording_in_progress())
- controller->capture_mode_session()->OnCameraPreviewDestroyed();
- }
- void CameraPreviewView::SetIsCollapsible(bool value) {
- if (value != is_collapsible_) {
- is_collapsible_ = value;
- RefreshResizeButtonVisibility();
- }
- }
- bool CameraPreviewView::MaybeHandleKeyEvent(const ui::KeyEvent* event) {
- // Handle control+arrow keys to snap the camera preview to different corners
- // when it has focus.
- if (has_focus() && event->IsControlDown() && IsArrowKeyEvent(event)) {
- const CameraPreviewSnapPosition current_snap_position =
- camera_controller_->camera_preview_snap_position();
- CameraPreviewSnapPosition new_snap_position = current_snap_position;
- const ui::KeyboardCode key_code = event->key_code();
- if (key_code == ui::VKEY_LEFT || key_code == ui::VKEY_RIGHT) {
- new_snap_position =
- capture_mode_util::GetCameraNextHorizontalSnapPosition(
- current_snap_position, /*going_left=*/key_code == ui::VKEY_LEFT);
- } else {
- DCHECK(key_code == ui::VKEY_UP || key_code == ui::VKEY_DOWN);
- new_snap_position = capture_mode_util::GetCameraNextVerticalSnapPosition(
- current_snap_position, /*going_up=*/key_code == ui::VKEY_UP);
- }
- // Still call `SetCameraPreviewSnapPosition` even if the snap position does
- // not change. As we still want to trigger a11y alert in this case.
- camera_controller_->SetCameraPreviewSnapPosition(new_snap_position,
- /*animate=*/true);
- return new_snap_position != current_snap_position;
- }
- auto* controller = CaptureModeController::Get();
- if (controller->IsActive() || !controller->is_recording_in_progress())
- return false;
- // Handle the key events on camera preview when it has focus (either the
- // preview view or the resize button) in video recording.
- const bool camera_preview_has_focus =
- has_focus() || resize_button_->has_focus();
- bool should_handle_event = false;
- switch (event->key_code()) {
- case ui::VKEY_TAB:
- if (camera_preview_has_focus) {
- if (resize_button_->has_focus()) {
- DCHECK(is_collapsible_);
- resize_button_->PseudoBlur();
- PseudoFocus();
- } else if (is_collapsible_) {
- PseudoBlur();
- resize_button_->PseudoFocus();
- }
- should_handle_event = true;
- }
- break;
- case ui::VKEY_SPACE:
- if (resize_button_->has_focus()) {
- resize_button_->ClickView();
- should_handle_event = true;
- }
- break;
- case ui::VKEY_ESCAPE:
- if (camera_preview_has_focus) {
- BlurA11yFocus();
- should_handle_event = true;
- }
- break;
- default:
- break;
- }
- return should_handle_event;
- }
- void CameraPreviewView::RefreshResizeButtonVisibility() {
- const float target_opacity = CalculateResizeButtonTargetOpacity();
- if (target_opacity == resize_button_->layer()->GetTargetOpacity())
- return;
- resize_button_hide_timer_.Stop();
- if (target_opacity == 1.f) {
- FadeInResizeButton();
- ScheduleRefreshResizeButtonVisibility();
- } else {
- FadeOutResizeButton();
- }
- }
- void CameraPreviewView::UpdateA11yOverrideWindow() {
- if (has_focus() || resize_button_->has_focus()) {
- scoped_a11y_overrider_->MaybeUpdateA11yOverrideWindow(
- GetWidget()->GetNativeWindow());
- } else {
- scoped_a11y_overrider_->MaybeUpdateA11yOverrideWindow(nullptr);
- }
- }
- void CameraPreviewView::MaybeBlurFocus(const ui::MouseEvent& event) {
- auto* target = static_cast<aura::Window*>(event.target());
- if (!GetWidget()->GetNativeWindow()->Contains(target))
- BlurA11yFocus();
- }
- void CameraPreviewView::AddedToWidget() {
- camera_video_host_view_->Attach(camera_video_renderer_.host_window());
- // This must be called after the renderer's `host_window()` has been attached
- // to the `NativeViewHost`, and before we call `Initialize()`, since
- // `Initialize()` will create a layer tree frame sink for the `host_window()`
- // and we're not allowed to change the event targeting policy after that.
- DisableEventHandlingInCameraVideoHostHierarchy();
- camera_video_renderer_.Initialize();
- }
- bool CameraPreviewView::OnMousePressed(const ui::MouseEvent& event) {
- camera_controller_->StartDraggingPreview(GetEventScreenLocation(event));
- return true;
- }
- bool CameraPreviewView::OnMouseDragged(const ui::MouseEvent& event) {
- camera_controller_->ContinueDraggingPreview(GetEventScreenLocation(event));
- return true;
- }
- void CameraPreviewView::OnMouseReleased(const ui::MouseEvent& event) {
- camera_controller_->EndDraggingPreview(GetEventScreenLocation(event),
- /*is_touch=*/false);
- }
- void CameraPreviewView::OnGestureEvent(ui::GestureEvent* event) {
- const gfx::PointF screen_location = GetEventScreenLocation(*event);
- switch (event->type()) {
- case ui::ET_GESTURE_SCROLL_BEGIN:
- camera_controller_->StartDraggingPreview(screen_location);
- break;
- case ui::ET_GESTURE_SCROLL_UPDATE:
- DCHECK(camera_controller_->is_drag_in_progress());
- camera_controller_->ContinueDraggingPreview(screen_location);
- break;
- case ui::ET_SCROLL_FLING_START:
- // TODO(conniekxu): Handle fling event.
- break;
- case ui::ET_GESTURE_SCROLL_END:
- DCHECK(camera_controller_->is_drag_in_progress());
- camera_controller_->EndDraggingPreview(screen_location,
- /*is_touch=*/true);
- break;
- case ui::ET_GESTURE_END:
- if (camera_controller_->is_drag_in_progress()) {
- camera_controller_->EndDraggingPreview(screen_location,
- /*is_touch=*/true);
- }
- break;
- case ui::ET_GESTURE_TAP:
- has_been_tapped_ = true;
- RefreshResizeButtonVisibility();
- has_been_tapped_ = false;
- break;
- default:
- break;
- }
- event->StopPropagation();
- event->SetHandled();
- }
- void CameraPreviewView::OnMouseEntered(const ui::MouseEvent& event) {
- RefreshResizeButtonVisibility();
- }
- void CameraPreviewView::OnMouseExited(const ui::MouseEvent& event) {
- ScheduleRefreshResizeButtonVisibility();
- }
- void CameraPreviewView::Layout() {
- const gfx::Size resize_button_size = resize_button_->GetPreferredSize();
- const gfx::Rect bounds(
- (width() - resize_button_size.width()) / 2.f,
- height() - resize_button_size.height() -
- capture_mode::kSpaceBetweenResizeButtonAndCameraPreview -
- capture_mode::kCameraPreviewBorderSize,
- resize_button_size.width(), resize_button_size.height());
- resize_button_->SetBoundsRect(bounds);
- camera_video_host_view_->SetBoundsRect(GetContentsBounds());
- // The size must have changed, and we need to update the rounded corners. Note
- // that the assumption is that this view must have a square size, so when
- // rounded corners are applied, it looks like a perfect circle.
- // The rounded corners is applied on the window hosting the camera frames.
- // That window's layer is a solid-color layer, so applying rounded corners on
- // it won't make it produce a render surface. This is better than applying it
- // on the preview widget's layer (which is a texture layer) and would cause a
- // a render surface. This also avoids the rendering artifacts seen in
- // https://crbug.com/1312059.
- DCHECK_EQ(width(), height());
- auto* layer = camera_video_renderer_.host_window()->layer();
- layer->SetRoundedCornerRadius(gfx::RoundedCornersF(height() / 2.f));
- layer->SetIsFastRoundedCorner(true);
- // Refocus the camera preview to relayout the focus ring on it.
- if (has_focus())
- PseudoFocus();
- }
- void CameraPreviewView::GetAccessibleNodeData(ui::AXNodeData* node_data) {
- views::View::GetAccessibleNodeData(node_data);
- node_data->role = ax::mojom::Role::kVideo;
- node_data->SetName(
- l10n_util::GetStringUTF16(IDS_ASH_SCREEN_CAPTURE_CAMERA_PREVIEW_FOCUSED));
- }
- views::View* CameraPreviewView::GetView() {
- return this;
- }
- std::unique_ptr<views::HighlightPathGenerator>
- CameraPreviewView::CreatePathGenerator() {
- return std::make_unique<views::CircleHighlightPathGenerator>(
- gfx::Insets(views::FocusRing::kDefaultHaloThickness / 2));
- }
- void CameraPreviewView::OnAccessibilityStatusChanged() {
- RefreshResizeButtonVisibility();
- }
- void CameraPreviewView::OnAccessibilityControllerShutdown() {
- accessibility_observation_.Reset();
- }
- void CameraPreviewView::OnResizeButtonPressed() {
- camera_controller_->ToggleCameraPreviewSize();
- UpdateResizeButton();
- }
- void CameraPreviewView::UpdateResizeButton() {
- resize_button_->SetImage(
- views::Button::STATE_NORMAL,
- gfx::CreateVectorIcon(
- GetIconOfResizeButton(
- camera_controller_->is_camera_preview_collapsed()),
- AshColorProvider::Get()->GetContentLayerColor(
- AshColorProvider::ContentLayerType::kIconColorPrimary)));
- UpdateResizeButtonTooltip();
- }
- void CameraPreviewView::UpdateResizeButtonTooltip() {
- resize_button_->SetTooltipText(l10n_util::GetStringUTF16(
- camera_controller_->is_camera_preview_collapsed()
- ? IDS_ASH_SCREEN_CAPTURE_TOOLTIP_EXPAND_SELFIE_CAMERA
- : IDS_ASH_SCREEN_CAPTURE_TOOLTIP_COLLAPSE_SELFIE_CAMERA));
- }
- void CameraPreviewView::DisableEventHandlingInCameraVideoHostHierarchy() {
- camera_video_host_view_->SetCanProcessEventsWithinSubtree(false);
- camera_video_host_view_->SetFocusBehavior(FocusBehavior::NEVER);
- auto* const host_window = camera_video_renderer_.host_window();
- auto* const widget_window = GetWidget()->GetNativeWindow();
- DCHECK(host_window);
- DCHECK(host_window->parent());
- DCHECK(widget_window);
- for (auto* window = host_window; window && window != widget_window;
- window = window->parent()) {
- window->SetEventTargetingPolicy(aura::EventTargetingPolicy::kNone);
- }
- }
- void CameraPreviewView::FadeInResizeButton() {
- resize_button_->SetVisible(true);
- views::AnimationBuilder()
- .SetPreemptionStrategy(
- ui::LayerAnimator::IMMEDIATELY_ANIMATE_TO_NEW_TARGET)
- .Once()
- .SetDuration(kResizeButtonFadeInDuration)
- .SetOpacity(resize_button_->layer(), 1.0f);
- }
- void CameraPreviewView::FadeOutResizeButton() {
- views::AnimationBuilder()
- .OnEnded(base::BindOnce(
- [](base::WeakPtr<CameraPreviewView> camera_preview_view) {
- if (camera_preview_view)
- camera_preview_view->resize_button()->SetVisible(false);
- },
- weak_ptr_factory_.GetWeakPtr()))
- .SetPreemptionStrategy(
- ui::LayerAnimator::IMMEDIATELY_ANIMATE_TO_NEW_TARGET)
- .Once()
- .SetDuration(kResizeButtonFadeOutDuration)
- .SetOpacity(resize_button_->layer(), 0.0f);
- }
- void CameraPreviewView::ScheduleRefreshResizeButtonVisibility() {
- resize_button_hide_timer_.Start(
- FROM_HERE, capture_mode::kResizeButtonShowDuration, this,
- &CameraPreviewView::RefreshResizeButtonVisibility);
- }
- float CameraPreviewView::CalculateResizeButtonTargetOpacity() {
- if (!is_collapsible_ || camera_controller_->is_drag_in_progress())
- return 0.f;
- if (Shell::Get()->accessibility_controller()->IsSwitchAccessRunning() ||
- IsMouseHovered() || resize_button_->IsMouseHovered() ||
- resize_button_->has_focus() || has_been_tapped_) {
- return 1.f;
- }
- return 0.f;
- }
- void CameraPreviewView::BlurA11yFocus() {
- PseudoBlur();
- resize_button_->PseudoBlur();
- UpdateA11yOverrideWindow();
- }
- BEGIN_METADATA(CameraPreviewView, views::View)
- END_METADATA
- } // namespace ash
|