capture_mode_camera_preview_view.cc 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456
  1. // Copyright 2022 The Chromium Authors. All rights reserved.
  2. // Use of this source code is governed by a BSD-style license that can be
  3. // found in the LICENSE file.
  4. #include "ash/capture_mode/capture_mode_camera_preview_view.h"
  5. #include "ash/accessibility/scoped_a11y_override_window_setter.h"
  6. #include "ash/capture_mode/capture_mode_constants.h"
  7. #include "ash/capture_mode/capture_mode_controller.h"
  8. #include "ash/capture_mode/capture_mode_session.h"
  9. #include "ash/capture_mode/capture_mode_util.h"
  10. #include "ash/resources/vector_icons/vector_icons.h"
  11. #include "ash/shell.h"
  12. #include "ash/strings/grit/ash_strings.h"
  13. #include "ash/style/ash_color_provider.h"
  14. #include "base/bind.h"
  15. #include "base/check.h"
  16. #include "ui/aura/window_tree_host.h"
  17. #include "ui/base/l10n/l10n_util.h"
  18. #include "ui/base/metadata/metadata_impl_macros.h"
  19. #include "ui/compositor/layer.h"
  20. #include "ui/events/event.h"
  21. #include "ui/gfx/geometry/point_f.h"
  22. #include "ui/gfx/paint_vector_icon.h"
  23. #include "ui/views/accessibility/view_accessibility.h"
  24. #include "ui/views/animation/animation_builder.h"
  25. #include "ui/views/background.h"
  26. #include "ui/views/controls/highlight_path_generator.h"
  27. #include "ui/views/controls/native/native_view_host.h"
  28. #include "ui/views/widget/widget.h"
  29. namespace ash {
  30. namespace {
  31. // The duration for the resize button fading in process.
  32. constexpr base::TimeDelta kResizeButtonFadeInDuration = base::Milliseconds(150);
  33. // The duration for the reize button fading out process.
  34. constexpr base::TimeDelta kResizeButtonFadeOutDuration = base::Milliseconds(50);
  35. gfx::PointF GetEventScreenLocation(const ui::LocatedEvent& event) {
  36. return event.target()->GetScreenLocationF(event);
  37. }
  38. const gfx::VectorIcon& GetIconOfResizeButton(
  39. const bool is_camera_preview_collapsed) {
  40. return is_camera_preview_collapsed ? kCaptureModeCameraPreviewExpandIcon
  41. : kCaptureModeCameraPreviewCollapseIcon;
  42. }
  43. bool IsArrowKeyEvent(const ui::KeyEvent* event) {
  44. const ui::KeyboardCode key_code = event->key_code();
  45. return key_code == ui::VKEY_DOWN || key_code == ui::VKEY_RIGHT ||
  46. key_code == ui::VKEY_LEFT || key_code == ui::VKEY_UP;
  47. }
  48. } // namespace
  49. // -----------------------------------------------------------------------------
  50. // CameraPreviewResizeButton:
  51. CameraPreviewResizeButton::CameraPreviewResizeButton(
  52. CameraPreviewView* camera_preview_view,
  53. views::Button::PressedCallback callback,
  54. const gfx::VectorIcon& icon)
  55. : CaptureModeButton(std::move(callback), icon),
  56. camera_preview_view_(camera_preview_view) {}
  57. CameraPreviewResizeButton::~CameraPreviewResizeButton() = default;
  58. void CameraPreviewResizeButton::PseudoFocus() {
  59. DCHECK(camera_preview_view_->is_collapsible());
  60. CaptureModeSessionFocusCycler::HighlightableView::PseudoFocus();
  61. camera_preview_view_->RefreshResizeButtonVisibility();
  62. }
  63. void CameraPreviewResizeButton::PseudoBlur() {
  64. CaptureModeSessionFocusCycler::HighlightableView::PseudoBlur();
  65. camera_preview_view_->ScheduleRefreshResizeButtonVisibility();
  66. }
  67. BEGIN_METADATA(CameraPreviewResizeButton, CaptureModeButton)
  68. END_METADATA
  69. // -----------------------------------------------------------------------------
  70. // CameraPreviewView:
  71. CameraPreviewView::CameraPreviewView(
  72. CaptureModeCameraController* camera_controller,
  73. const CameraId& camera_id,
  74. mojo::Remote<video_capture::mojom::VideoSource> camera_video_source,
  75. const media::VideoCaptureFormat& capture_format,
  76. bool should_flip_frames_horizontally)
  77. : camera_controller_(camera_controller),
  78. camera_id_(camera_id),
  79. camera_video_renderer_(std::move(camera_video_source),
  80. capture_format,
  81. should_flip_frames_horizontally),
  82. camera_video_host_view_(
  83. AddChildView(std::make_unique<views::NativeViewHost>())),
  84. resize_button_(AddChildView(std::make_unique<CameraPreviewResizeButton>(
  85. this,
  86. base::BindRepeating(&CameraPreviewView::OnResizeButtonPressed,
  87. base::Unretained(this)),
  88. GetIconOfResizeButton(
  89. camera_controller_->is_camera_preview_collapsed())))),
  90. scoped_a11y_overrider_(
  91. std::make_unique<ScopedA11yOverrideWindowSetter>()) {
  92. // Add an empty border to the camera preview. This is done to keep some gap
  93. // between the focus ring and the contents of the camera preview, as focus
  94. // ring will extend a little beyond the border.
  95. SetBorder(views::CreateEmptyBorder(capture_mode::kCameraPreviewBorderSize));
  96. resize_button_->SetPaintToLayer();
  97. resize_button_->layer()->SetFillsBoundsOpaquely(false);
  98. resize_button_->SetBackground(views::CreateRoundedRectBackground(
  99. AshColorProvider::Get()->GetBaseLayerColor(
  100. AshColorProvider::BaseLayerType::kTransparent80),
  101. resize_button_->GetPreferredSize().height() / 2.f));
  102. accessibility_observation_.Observe(Shell::Get()->accessibility_controller());
  103. RefreshResizeButtonVisibility();
  104. UpdateResizeButtonTooltip();
  105. }
  106. CameraPreviewView::~CameraPreviewView() {
  107. auto* controller = CaptureModeController::Get();
  108. if (controller->IsActive() && !controller->is_recording_in_progress())
  109. controller->capture_mode_session()->OnCameraPreviewDestroyed();
  110. }
  111. void CameraPreviewView::SetIsCollapsible(bool value) {
  112. if (value != is_collapsible_) {
  113. is_collapsible_ = value;
  114. RefreshResizeButtonVisibility();
  115. }
  116. }
  117. bool CameraPreviewView::MaybeHandleKeyEvent(const ui::KeyEvent* event) {
  118. // Handle control+arrow keys to snap the camera preview to different corners
  119. // when it has focus.
  120. if (has_focus() && event->IsControlDown() && IsArrowKeyEvent(event)) {
  121. const CameraPreviewSnapPosition current_snap_position =
  122. camera_controller_->camera_preview_snap_position();
  123. CameraPreviewSnapPosition new_snap_position = current_snap_position;
  124. const ui::KeyboardCode key_code = event->key_code();
  125. if (key_code == ui::VKEY_LEFT || key_code == ui::VKEY_RIGHT) {
  126. new_snap_position =
  127. capture_mode_util::GetCameraNextHorizontalSnapPosition(
  128. current_snap_position, /*going_left=*/key_code == ui::VKEY_LEFT);
  129. } else {
  130. DCHECK(key_code == ui::VKEY_UP || key_code == ui::VKEY_DOWN);
  131. new_snap_position = capture_mode_util::GetCameraNextVerticalSnapPosition(
  132. current_snap_position, /*going_up=*/key_code == ui::VKEY_UP);
  133. }
  134. // Still call `SetCameraPreviewSnapPosition` even if the snap position does
  135. // not change. As we still want to trigger a11y alert in this case.
  136. camera_controller_->SetCameraPreviewSnapPosition(new_snap_position,
  137. /*animate=*/true);
  138. return new_snap_position != current_snap_position;
  139. }
  140. auto* controller = CaptureModeController::Get();
  141. if (controller->IsActive() || !controller->is_recording_in_progress())
  142. return false;
  143. // Handle the key events on camera preview when it has focus (either the
  144. // preview view or the resize button) in video recording.
  145. const bool camera_preview_has_focus =
  146. has_focus() || resize_button_->has_focus();
  147. bool should_handle_event = false;
  148. switch (event->key_code()) {
  149. case ui::VKEY_TAB:
  150. if (camera_preview_has_focus) {
  151. if (resize_button_->has_focus()) {
  152. DCHECK(is_collapsible_);
  153. resize_button_->PseudoBlur();
  154. PseudoFocus();
  155. } else if (is_collapsible_) {
  156. PseudoBlur();
  157. resize_button_->PseudoFocus();
  158. }
  159. should_handle_event = true;
  160. }
  161. break;
  162. case ui::VKEY_SPACE:
  163. if (resize_button_->has_focus()) {
  164. resize_button_->ClickView();
  165. should_handle_event = true;
  166. }
  167. break;
  168. case ui::VKEY_ESCAPE:
  169. if (camera_preview_has_focus) {
  170. BlurA11yFocus();
  171. should_handle_event = true;
  172. }
  173. break;
  174. default:
  175. break;
  176. }
  177. return should_handle_event;
  178. }
  179. void CameraPreviewView::RefreshResizeButtonVisibility() {
  180. const float target_opacity = CalculateResizeButtonTargetOpacity();
  181. if (target_opacity == resize_button_->layer()->GetTargetOpacity())
  182. return;
  183. resize_button_hide_timer_.Stop();
  184. if (target_opacity == 1.f) {
  185. FadeInResizeButton();
  186. ScheduleRefreshResizeButtonVisibility();
  187. } else {
  188. FadeOutResizeButton();
  189. }
  190. }
  191. void CameraPreviewView::UpdateA11yOverrideWindow() {
  192. if (has_focus() || resize_button_->has_focus()) {
  193. scoped_a11y_overrider_->MaybeUpdateA11yOverrideWindow(
  194. GetWidget()->GetNativeWindow());
  195. } else {
  196. scoped_a11y_overrider_->MaybeUpdateA11yOverrideWindow(nullptr);
  197. }
  198. }
  199. void CameraPreviewView::MaybeBlurFocus(const ui::MouseEvent& event) {
  200. auto* target = static_cast<aura::Window*>(event.target());
  201. if (!GetWidget()->GetNativeWindow()->Contains(target))
  202. BlurA11yFocus();
  203. }
  204. void CameraPreviewView::AddedToWidget() {
  205. camera_video_host_view_->Attach(camera_video_renderer_.host_window());
  206. // This must be called after the renderer's `host_window()` has been attached
  207. // to the `NativeViewHost`, and before we call `Initialize()`, since
  208. // `Initialize()` will create a layer tree frame sink for the `host_window()`
  209. // and we're not allowed to change the event targeting policy after that.
  210. DisableEventHandlingInCameraVideoHostHierarchy();
  211. camera_video_renderer_.Initialize();
  212. }
  213. bool CameraPreviewView::OnMousePressed(const ui::MouseEvent& event) {
  214. camera_controller_->StartDraggingPreview(GetEventScreenLocation(event));
  215. return true;
  216. }
  217. bool CameraPreviewView::OnMouseDragged(const ui::MouseEvent& event) {
  218. camera_controller_->ContinueDraggingPreview(GetEventScreenLocation(event));
  219. return true;
  220. }
  221. void CameraPreviewView::OnMouseReleased(const ui::MouseEvent& event) {
  222. camera_controller_->EndDraggingPreview(GetEventScreenLocation(event),
  223. /*is_touch=*/false);
  224. }
  225. void CameraPreviewView::OnGestureEvent(ui::GestureEvent* event) {
  226. const gfx::PointF screen_location = GetEventScreenLocation(*event);
  227. switch (event->type()) {
  228. case ui::ET_GESTURE_SCROLL_BEGIN:
  229. camera_controller_->StartDraggingPreview(screen_location);
  230. break;
  231. case ui::ET_GESTURE_SCROLL_UPDATE:
  232. DCHECK(camera_controller_->is_drag_in_progress());
  233. camera_controller_->ContinueDraggingPreview(screen_location);
  234. break;
  235. case ui::ET_SCROLL_FLING_START:
  236. // TODO(conniekxu): Handle fling event.
  237. break;
  238. case ui::ET_GESTURE_SCROLL_END:
  239. DCHECK(camera_controller_->is_drag_in_progress());
  240. camera_controller_->EndDraggingPreview(screen_location,
  241. /*is_touch=*/true);
  242. break;
  243. case ui::ET_GESTURE_END:
  244. if (camera_controller_->is_drag_in_progress()) {
  245. camera_controller_->EndDraggingPreview(screen_location,
  246. /*is_touch=*/true);
  247. }
  248. break;
  249. case ui::ET_GESTURE_TAP:
  250. has_been_tapped_ = true;
  251. RefreshResizeButtonVisibility();
  252. has_been_tapped_ = false;
  253. break;
  254. default:
  255. break;
  256. }
  257. event->StopPropagation();
  258. event->SetHandled();
  259. }
  260. void CameraPreviewView::OnMouseEntered(const ui::MouseEvent& event) {
  261. RefreshResizeButtonVisibility();
  262. }
  263. void CameraPreviewView::OnMouseExited(const ui::MouseEvent& event) {
  264. ScheduleRefreshResizeButtonVisibility();
  265. }
  266. void CameraPreviewView::Layout() {
  267. const gfx::Size resize_button_size = resize_button_->GetPreferredSize();
  268. const gfx::Rect bounds(
  269. (width() - resize_button_size.width()) / 2.f,
  270. height() - resize_button_size.height() -
  271. capture_mode::kSpaceBetweenResizeButtonAndCameraPreview -
  272. capture_mode::kCameraPreviewBorderSize,
  273. resize_button_size.width(), resize_button_size.height());
  274. resize_button_->SetBoundsRect(bounds);
  275. camera_video_host_view_->SetBoundsRect(GetContentsBounds());
  276. // The size must have changed, and we need to update the rounded corners. Note
  277. // that the assumption is that this view must have a square size, so when
  278. // rounded corners are applied, it looks like a perfect circle.
  279. // The rounded corners is applied on the window hosting the camera frames.
  280. // That window's layer is a solid-color layer, so applying rounded corners on
  281. // it won't make it produce a render surface. This is better than applying it
  282. // on the preview widget's layer (which is a texture layer) and would cause a
  283. // a render surface. This also avoids the rendering artifacts seen in
  284. // https://crbug.com/1312059.
  285. DCHECK_EQ(width(), height());
  286. auto* layer = camera_video_renderer_.host_window()->layer();
  287. layer->SetRoundedCornerRadius(gfx::RoundedCornersF(height() / 2.f));
  288. layer->SetIsFastRoundedCorner(true);
  289. // Refocus the camera preview to relayout the focus ring on it.
  290. if (has_focus())
  291. PseudoFocus();
  292. }
  293. void CameraPreviewView::GetAccessibleNodeData(ui::AXNodeData* node_data) {
  294. views::View::GetAccessibleNodeData(node_data);
  295. node_data->role = ax::mojom::Role::kVideo;
  296. node_data->SetName(
  297. l10n_util::GetStringUTF16(IDS_ASH_SCREEN_CAPTURE_CAMERA_PREVIEW_FOCUSED));
  298. }
  299. views::View* CameraPreviewView::GetView() {
  300. return this;
  301. }
  302. std::unique_ptr<views::HighlightPathGenerator>
  303. CameraPreviewView::CreatePathGenerator() {
  304. return std::make_unique<views::CircleHighlightPathGenerator>(
  305. gfx::Insets(views::FocusRing::kDefaultHaloThickness / 2));
  306. }
  307. void CameraPreviewView::OnAccessibilityStatusChanged() {
  308. RefreshResizeButtonVisibility();
  309. }
  310. void CameraPreviewView::OnAccessibilityControllerShutdown() {
  311. accessibility_observation_.Reset();
  312. }
  313. void CameraPreviewView::OnResizeButtonPressed() {
  314. camera_controller_->ToggleCameraPreviewSize();
  315. UpdateResizeButton();
  316. }
  317. void CameraPreviewView::UpdateResizeButton() {
  318. resize_button_->SetImage(
  319. views::Button::STATE_NORMAL,
  320. gfx::CreateVectorIcon(
  321. GetIconOfResizeButton(
  322. camera_controller_->is_camera_preview_collapsed()),
  323. AshColorProvider::Get()->GetContentLayerColor(
  324. AshColorProvider::ContentLayerType::kIconColorPrimary)));
  325. UpdateResizeButtonTooltip();
  326. }
  327. void CameraPreviewView::UpdateResizeButtonTooltip() {
  328. resize_button_->SetTooltipText(l10n_util::GetStringUTF16(
  329. camera_controller_->is_camera_preview_collapsed()
  330. ? IDS_ASH_SCREEN_CAPTURE_TOOLTIP_EXPAND_SELFIE_CAMERA
  331. : IDS_ASH_SCREEN_CAPTURE_TOOLTIP_COLLAPSE_SELFIE_CAMERA));
  332. }
  333. void CameraPreviewView::DisableEventHandlingInCameraVideoHostHierarchy() {
  334. camera_video_host_view_->SetCanProcessEventsWithinSubtree(false);
  335. camera_video_host_view_->SetFocusBehavior(FocusBehavior::NEVER);
  336. auto* const host_window = camera_video_renderer_.host_window();
  337. auto* const widget_window = GetWidget()->GetNativeWindow();
  338. DCHECK(host_window);
  339. DCHECK(host_window->parent());
  340. DCHECK(widget_window);
  341. for (auto* window = host_window; window && window != widget_window;
  342. window = window->parent()) {
  343. window->SetEventTargetingPolicy(aura::EventTargetingPolicy::kNone);
  344. }
  345. }
  346. void CameraPreviewView::FadeInResizeButton() {
  347. resize_button_->SetVisible(true);
  348. views::AnimationBuilder()
  349. .SetPreemptionStrategy(
  350. ui::LayerAnimator::IMMEDIATELY_ANIMATE_TO_NEW_TARGET)
  351. .Once()
  352. .SetDuration(kResizeButtonFadeInDuration)
  353. .SetOpacity(resize_button_->layer(), 1.0f);
  354. }
  355. void CameraPreviewView::FadeOutResizeButton() {
  356. views::AnimationBuilder()
  357. .OnEnded(base::BindOnce(
  358. [](base::WeakPtr<CameraPreviewView> camera_preview_view) {
  359. if (camera_preview_view)
  360. camera_preview_view->resize_button()->SetVisible(false);
  361. },
  362. weak_ptr_factory_.GetWeakPtr()))
  363. .SetPreemptionStrategy(
  364. ui::LayerAnimator::IMMEDIATELY_ANIMATE_TO_NEW_TARGET)
  365. .Once()
  366. .SetDuration(kResizeButtonFadeOutDuration)
  367. .SetOpacity(resize_button_->layer(), 0.0f);
  368. }
  369. void CameraPreviewView::ScheduleRefreshResizeButtonVisibility() {
  370. resize_button_hide_timer_.Start(
  371. FROM_HERE, capture_mode::kResizeButtonShowDuration, this,
  372. &CameraPreviewView::RefreshResizeButtonVisibility);
  373. }
  374. float CameraPreviewView::CalculateResizeButtonTargetOpacity() {
  375. if (!is_collapsible_ || camera_controller_->is_drag_in_progress())
  376. return 0.f;
  377. if (Shell::Get()->accessibility_controller()->IsSwitchAccessRunning() ||
  378. IsMouseHovered() || resize_button_->IsMouseHovered() ||
  379. resize_button_->has_focus() || has_been_tapped_) {
  380. return 1.f;
  381. }
  382. return 0.f;
  383. }
  384. void CameraPreviewView::BlurA11yFocus() {
  385. PseudoBlur();
  386. resize_button_->PseudoBlur();
  387. UpdateA11yOverrideWindow();
  388. }
  389. BEGIN_METADATA(CameraPreviewView, views::View)
  390. END_METADATA
  391. } // namespace ash