Skip to content

Commit

Permalink
capture_selfie_cam: Let events outside the preview circle go through
Browse files Browse the repository at this point in the history
This CL defines a window targeter which installed on the camera preview
window so that we can allow located events outside of the camera preview
circle to go through and not be consumed by the camera preview. This
enables the user to interact with other UI components below camera
preview.

Fixed: 1312063
Test: manual, added unit-tests
Change-Id: I891168a3d4c4f99d12084a7e190decac7c997802
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/3566276
Reviewed-by: Ahmed Fakhry <afakhry@chromium.org>
Reviewed-by: Min Chen <minch@chromium.org>
Commit-Queue: Connie Xu <conniekxu@chromium.org>
Cr-Commit-Position: refs/heads/main@{#988785}
  • Loading branch information
conniekxu authored and Chromium LUCI CQ committed Apr 5, 2022
1 parent 906a9ff commit 68195b0
Show file tree
Hide file tree
Showing 4 changed files with 166 additions and 48 deletions.
48 changes: 48 additions & 0 deletions ash/capture_mode/capture_mode_camera_controller.cc
Original file line number Diff line number Diff line change
Expand Up @@ -22,13 +22,15 @@
#include "base/strings/stringprintf.h"
#include "base/time/time.h"
#include "media/capture/video/video_capture_device_descriptor.h"
#include "ui/aura/window_targeter.h"
#include "ui/compositor/layer.h"
#include "ui/gfx/geometry/point.h"
#include "ui/gfx/geometry/point_conversions.h"
#include "ui/gfx/geometry/size.h"
#include "ui/gfx/geometry/transform.h"
#include "ui/views/animation/animation_builder.h"
#include "ui/views/widget/widget.h"
#include "ui/wm/core/coordinate_conversion.h"
#include "ui/wm/core/window_properties.h"

namespace ash {
Expand Down Expand Up @@ -182,6 +184,49 @@ gfx::Size GetInitialPreviewSize(bool is_camera_preview_collapsed) {

} // namespace

// Defines a window targeter that will be installed on the camera preview
// widget's window so that we can allow located events outside of the camera
// preview circle to go through and not be consumed by the camera preview. This
// enables the user to interact with other UI components below camera preview.
class CameraPreviewTargeter : public aura::WindowTargeter {
public:
explicit CameraPreviewTargeter(aura::Window* camera_preview_window)
: camera_preview_window_(camera_preview_window) {}
CameraPreviewTargeter(const CameraPreviewTargeter&) = delete;
CameraPreviewTargeter& operator=(const CameraPreviewTargeter&) = delete;
~CameraPreviewTargeter() override = default;

// aura::WindowTargeter:
ui::EventTarget* FindTargetForEvent(ui::EventTarget* root,
ui::Event* event) override {
if (event->IsLocatedEvent()) {
auto screen_location = event->AsLocatedEvent()->root_location();
wm::ConvertPointToScreen(camera_preview_window_->GetRootWindow(),
&screen_location);
const gfx::Rect camera_preview_bounds =
camera_preview_window_->GetBoundsInScreen();
const gfx::Point camera_preview_center_point =
camera_preview_bounds.CenterPoint();
const int camera_preview_radius = camera_preview_bounds.width() / 2;

// Check if events are outside of the camera preview circle by comparing
// if the distance between screen location and center of camera preview is
// larger than camera preview circle's radius. If it's larger, allow the
// events to go through so that they can be used by other UI components
// below camera preview.
if ((screen_location - camera_preview_center_point).LengthSquared() >
camera_preview_radius * camera_preview_radius) {
return nullptr;
}
}

return aura::WindowTargeter::FindTargetForEvent(root, event);
}

private:
aura::Window* const camera_preview_window_;
};

// -----------------------------------------------------------------------------
// CameraId:

Expand Down Expand Up @@ -543,6 +588,9 @@ void CaptureModeCameraController::RefreshCameraPreview() {
const auto preview_bounds = GetPreviewWidgetBounds();
camera_preview_widget_ = std::make_unique<views::Widget>();
camera_preview_widget_->Init(CreateWidgetParams(preview_bounds));
auto* camera_preview_window = camera_preview_widget_->GetNativeWindow();
camera_preview_window->SetEventTargeter(
std::make_unique<CameraPreviewTargeter>(camera_preview_window));
mojo::Remote<video_capture::mojom::VideoSource> camera_video_source;
video_source_provider_remote_->GetVideoSource(
camera_info->device_id,
Expand Down
70 changes: 66 additions & 4 deletions ash/capture_mode/capture_mode_camera_unittests.cc
Original file line number Diff line number Diff line change
Expand Up @@ -1956,8 +1956,65 @@ TEST_P(CaptureModeCameraPreviewTest,
snap_position_before_drag);
}

// Tests that when mouse event is on top of camera preview, cursor type should
// be updated accordingly.
// Tests that dragging camera preview outside of the preview circle shouldn't
// work even if the drag events are contained in the preview bounds.
TEST_P(CaptureModeCameraPreviewTest, DragPreviewOutsidePreviewCircle) {
StartCaptureSessionWithParam();
auto* camera_controller = GetCameraController();
AddDefaultCamera();
camera_controller->SetSelectedCamera(CameraId(kDefaultCameraModelId, 1));
auto* preview_widget = camera_controller->camera_preview_widget();
const gfx::Point capture_bounds_center_point =
GetCaptureBoundsInScreen().CenterPoint();
const gfx::Rect preview_bounds_in_screen_before_drag =
preview_widget->GetWindowBoundsInScreen();

// Try to drag camera preview at its origin point, verify camera
// preview is not draggable and its position is not changed.
auto* event_generator = GetEventGenerator();
event_generator->MoveMouseTo(preview_bounds_in_screen_before_drag.origin());
event_generator->PressLeftButton();
event_generator->MoveMouseTo(capture_bounds_center_point);
EXPECT_EQ(preview_widget->GetWindowBoundsInScreen(),
preview_bounds_in_screen_before_drag);
}

// Tests that dragging camera preview outside of the preview circle doesn't
// work when video recording is in progress.
TEST_P(CaptureModeCameraPreviewTest,
DragPreviewOutsidePreviewCircleWhileVideoRecordingInProgress) {
StartCaptureSessionWithParam();
auto* camera_controller = GetCameraController();
AddDefaultCamera();
camera_controller->SetSelectedCamera(CameraId(kDefaultCameraModelId, 1));
auto* preview_widget = camera_controller->camera_preview_widget();
const gfx::Point capture_bounds_center_point =
GetCaptureBoundsInScreen().CenterPoint();

const gfx::Rect preview_bounds_in_screen_before_drag =
preview_widget->GetWindowBoundsInScreen();
const auto snap_position_before_drag =
camera_controller->camera_preview_snap_position();
// Verify by default snap position is `kBottomRight`.
EXPECT_EQ(snap_position_before_drag, CameraPreviewSnapPosition::kBottomRight);

// Try to drag camera preview at its origin point to the top left of current
// capture bounds' center point, verity it's not moved.
auto* event_generator = GetEventGenerator();
event_generator->MoveMouseTo(preview_bounds_in_screen_before_drag.origin());
event_generator->PressLeftButton();
event_generator->MoveMouseTo(capture_bounds_center_point);
EXPECT_EQ(preview_widget->GetWindowBoundsInScreen(),
preview_bounds_in_screen_before_drag);

// Release drag, verify snap position is not changed.
event_generator->ReleaseLeftButton();
EXPECT_EQ(camera_controller->camera_preview_snap_position(),
snap_position_before_drag);
}

// Tests that when mouse event is on top of camera preview circle, cursor type
// should be updated accordingly.
TEST_P(CaptureModeCameraPreviewTest, CursorTypeUpdates) {
StartCaptureSessionWithParam();
auto* camera_controller = GetCameraController();
Expand All @@ -1973,9 +2030,14 @@ TEST_P(CaptureModeCameraPreviewTest, CursorTypeUpdates) {
preview_bounds_in_screen.origin();
auto* event_generator = GetEventGenerator();

// Verify that moving mouse on camera preview will update the cursor type to
// `kPointer`.
auto* cursor_manager = Shell::Get()->cursor_manager();
// Verify that moving mouse to the origin point on camera preview won't
// update the cursor type to `kPointer`.
event_generator->MoveMouseTo(preview_bounds_in_screen.origin());
EXPECT_NE(cursor_manager->GetCursor(), ui::mojom::CursorType::kPointer);

// Verify that moving mouse on camera preview will update the cursor type
// to `kPointer`.
event_generator->MoveMouseTo(camera_preview_center_point);
EXPECT_EQ(cursor_manager->GetCursor(), ui::mojom::CursorType::kPointer);

Expand Down
93 changes: 49 additions & 44 deletions ash/capture_mode/capture_mode_session.cc
Original file line number Diff line number Diff line change
Expand Up @@ -371,36 +371,40 @@ bool CameraPreviewWillBeShown(CaptureModeController* controller) {
}
}

bool IsDragAllowedOnCameraPreview(const gfx::Point& screen_location) {
auto* controller = CaptureModeController::Get();
auto* camera_controller = controller->camera_controller();
if (camera_controller && !controller->is_recording_in_progress()) {
auto* camera_preview_widget = camera_controller->camera_preview_widget();
if ((camera_preview_widget && camera_preview_widget->IsVisible() &&
camera_preview_widget->GetWindowBoundsInScreen().Contains(
screen_location)) ||
camera_controller->is_drag_in_progress()) {
return true;
}
}
return false;
}

views::Widget* GetCameraPreviewWidget() {
auto* camera_controller = CaptureModeController::Get()->camera_controller();
return camera_controller ? camera_controller->camera_preview_widget()
: nullptr;
}

// Returns true if the given `event` is targeted on the camera preview.
// Otherwise, returns false.
bool IsEventTargetedOnCameraPreview(ui::LocatedEvent* event) {
bool ShouldPassEventToCameraPreview(ui::LocatedEvent* event) {
auto* controller = CaptureModeController::Get();

// If there's a video recording in progress, return false immediately, since
// even camera preview exists, it doesn't belong to the current capture
// session.
if (controller->is_recording_in_progress())
return false;

auto* camera_preview_widget = GetCameraPreviewWidget();
if (camera_preview_widget && camera_preview_widget->IsVisible()) {
auto* target = static_cast<aura::Window*>(event->target());
if (camera_preview_widget->GetNativeWindow()->Contains(target))
return true;
}
if (!camera_preview_widget || !camera_preview_widget->IsVisible())
return false;

auto* camera_controller = controller->camera_controller();
if (camera_controller && camera_controller->is_drag_in_progress())
return true;

// If the event is targeted on the camera preview, even it's not located
// on the camera preview, we should still pass the event to camera preview
// to handle it. For example, when pressing on the resize button inside camera
// preview, but release the press outside of camera preview, even the release
// event is not on the camera preview, we should still pass the event to it,
// otherwise camera preview will wait for the release event forever which will
// make the regular drag for camera preview not work.
auto* target = static_cast<aura::Window*>(event->target());
if (camera_preview_widget->GetNativeWindow()->Contains(target))
return true;

return false;
}

Expand Down Expand Up @@ -1248,9 +1252,9 @@ void CaptureModeSession::UpdateCursor(const gfx::Point& location_in_screen,
return;
}

// If the current mouse is on camera preview or camera preview drag is in
// progress, use the pointer cursor.
if (IsDragAllowedOnCameraPreview(location_in_screen)) {
// If the current located event should be handled by camera preview, use the
// pointer cursor.
if (should_pass_located_event_to_camera_preview_) {
cursor_setter_->UpdateCursor(ui::mojom::CursorType::kPointer);
return;
}
Expand Down Expand Up @@ -1400,6 +1404,20 @@ void CaptureModeSession::OnCameraPreviewDragStarted() {
void CaptureModeSession::OnCameraPreviewDragEnded(
const gfx::Point& screen_location,
bool is_touch) {
// When drag for camera preview is ended, camera preview will be snapped to
// one of the snap position, but cursor will leave at where the drag is
// released. In order to update cursor type correctly after camera preview is
// snapped, we should update `should_pass_located_event_to_camera_preview_` to
// false if cursor is not on top of camera preview, since `UpdateCursor` will
// rely on its value to decide whether cursor should be updated for camera
// preview.
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;
}

// If CaptureUIs (capture bar, capture label) are overlapped with camera
// preview and cursor is not on top of it, its opacity should be updated to
// `kCaptureUiOverlapOpacity` instead of fully opaque.
Expand Down Expand Up @@ -1736,25 +1754,12 @@ void CaptureModeSession::OnLocatedEvent(ui::LocatedEvent* event,

MaybeUpdateCaptureUisOpacity(screen_location);

if (IsDragAllowedOnCameraPreview(screen_location)) {
// Update the value of `should_pass_located_event_to_camera_preview_` here
// before calling `UpdateCursor` which uses it.
should_pass_located_event_to_camera_preview_ =
ShouldPassEventToCameraPreview(event);
if (should_pass_located_event_to_camera_preview_) {
DCHECK(!controller_->is_recording_in_progress());
// Update cursor type when the event is on top of camera preview.
UpdateCursor(screen_location, is_touch);

// Pass the event to camera preview to handle it if the event is on top of
// camera preview and there's no video recording is in progress.
return;
}

// If the event is targeted on the camera preview, even it's not located
// on the camera preview, we should still pass the event to camera preview
// to handle it. For example, when pressing on the resize button inside camera
// preview, but release the press outside of camera preview, even the release
// event is not on the camera preview, we should still pass the event to it,
// otherwise camera preview will wait for the release event forever which will
// make the regular drag for camera preview not work.
if (!controller_->is_recording_in_progress() &&
IsEventTargetedOnCameraPreview(event)) {
UpdateCursor(screen_location, is_touch);
return;
}
Expand Down
3 changes: 3 additions & 0 deletions ash/capture_mode/capture_mode_session.h
Original file line number Diff line number Diff line change
Expand Up @@ -512,6 +512,9 @@ class ASH_EXPORT CaptureModeSession
// true.
bool located_press_event_on_settings_menu_ = false;

// True if a located event should be passed to camera preview to be handled.
bool should_pass_located_event_to_camera_preview_ = false;

// Controls the folder selection dialog. Not null only while the dialog is
// shown.
std::unique_ptr<FolderSelectionDialogController>
Expand Down

0 comments on commit 68195b0

Please sign in to comment.