capture_mode_util.cc 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469
  1. // Copyright 2020 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_util.h"
  5. #include "ash/accessibility/accessibility_controller_impl.h"
  6. #include "ash/capture_mode/capture_mode_camera_controller.h"
  7. #include "ash/capture_mode/capture_mode_constants.h"
  8. #include "ash/capture_mode/capture_mode_controller.h"
  9. #include "ash/capture_mode/capture_mode_session.h"
  10. #include "ash/capture_mode/stop_recording_button_tray.h"
  11. #include "ash/constants/ash_features.h"
  12. #include "ash/public/cpp/clipboard_history_controller.h"
  13. #include "ash/public/cpp/style/scoped_light_mode_as_default.h"
  14. #include "ash/public/cpp/window_finder.h"
  15. #include "ash/resources/vector_icons/vector_icons.h"
  16. #include "ash/root_window_controller.h"
  17. #include "ash/shell.h"
  18. #include "ash/strings/grit/ash_strings.h"
  19. #include "ash/style/ash_color_provider.h"
  20. #include "ash/wm/tablet_mode/tablet_mode_controller.h"
  21. #include "base/check.h"
  22. #include "base/notreached.h"
  23. #include "ui/base/l10n/l10n_util.h"
  24. #include "ui/chromeos/events/keyboard_layout_util.h"
  25. #include "ui/compositor/layer.h"
  26. #include "ui/gfx/geometry/point.h"
  27. #include "ui/gfx/geometry/rect.h"
  28. #include "ui/gfx/geometry/transform_util.h"
  29. #include "ui/gfx/paint_vector_icon.h"
  30. #include "ui/message_center/views/notification_background_painter.h"
  31. #include "ui/views/animation/animation_builder.h"
  32. #include "ui/views/controls/image_view.h"
  33. #include "ui/views/layout/box_layout.h"
  34. #include "ui/views/widget/widget.h"
  35. namespace ash::capture_mode_util {
  36. namespace {
  37. constexpr int kBannerViewTopRadius = 0;
  38. constexpr int kBannerViewBottomRadius = 8;
  39. constexpr float kScaleUpFactor = 0.8f;
  40. // Returns the target visibility of the camera preview, given the
  41. // `confine_bounds_short_side_length`. The out parameter
  42. // `out_is_surface_too_small` will be set to true if the preview should be
  43. // hidden due to the surface within which it's confined is too small. Otherwise,
  44. // it's unchanged.
  45. bool CalculateCameraPreviewTargetVisibility(
  46. int confine_bounds_short_side_length,
  47. bool* out_is_surface_too_small) {
  48. DCHECK(out_is_surface_too_small);
  49. // If the short side of the bounds within which the camera preview should be
  50. // confined is too small, the camera should be hidden.
  51. if (confine_bounds_short_side_length <
  52. capture_mode::kMinCaptureSurfaceShortSideLengthForVisibleCamera) {
  53. *out_is_surface_too_small = true;
  54. return false;
  55. }
  56. // Now that we determined that its size doesn't affect its visibility, we need
  57. // to check if we're in a capture mode session that is in a state that affects
  58. // the camera preview's visibility.
  59. auto* controller = CaptureModeController::Get();
  60. return !controller->IsActive() ||
  61. controller->capture_mode_session()
  62. ->CalculateCameraPreviewTargetVisibility();
  63. }
  64. // Returns the local center point of the given `layer`.
  65. gfx::Point GetLocalCenterPoint(ui::Layer* layer) {
  66. return gfx::Rect(layer->GetTargetBounds().size()).CenterPoint();
  67. }
  68. void FadeInWidget(views::Widget* widget,
  69. const AnimationParams& animation_params) {
  70. DCHECK(widget);
  71. auto* layer = widget->GetLayer();
  72. DCHECK(!widget->GetNativeWindow()->TargetVisibility() ||
  73. layer->GetTargetOpacity() < 1.f);
  74. // Please notice the order matters here. When the opacity is set to 0.f, if
  75. // there's any on-going fade out animation, the `OnEnded` in `FadeOutWidget`
  76. // will be triggered, which will hide the widget and set its opacity to 1.f.
  77. // So `Show` should be triggered after setting the opacity to 0 to undo the
  78. // work done by the FadeOutWidget's OnEnded .
  79. if (layer->opacity() == 1.f)
  80. layer->SetOpacity(0.f);
  81. if (!widget->GetNativeWindow()->TargetVisibility())
  82. widget->Show();
  83. if (animation_params.apply_scale_up_animation) {
  84. layer->SetTransform(
  85. capture_mode_util::GetScaleTransformAboutCenter(layer, kScaleUpFactor));
  86. }
  87. views::AnimationBuilder builder;
  88. auto& animation_sequence_block =
  89. builder
  90. .SetPreemptionStrategy(
  91. ui::LayerAnimator::IMMEDIATELY_ANIMATE_TO_NEW_TARGET)
  92. .Once()
  93. .SetDuration(animation_params.animation_duration)
  94. .SetOpacity(layer, 1.f, animation_params.tween_type);
  95. // We should only set transform here if `apply_scale_up_animation` is true,
  96. // otherwise, it may mess up with the snap animation in
  97. // `SetCameraPreviewBounds`.
  98. if (animation_params.apply_scale_up_animation) {
  99. animation_sequence_block.SetTransform(layer, gfx::Transform(),
  100. gfx::Tween::ACCEL_20_DECEL_100);
  101. }
  102. }
  103. void FadeOutWidget(views::Widget* widget,
  104. const AnimationParams& animation_params) {
  105. DCHECK(widget);
  106. DCHECK(widget->GetNativeWindow()->TargetVisibility());
  107. auto* layer = widget->GetLayer();
  108. DCHECK_EQ(layer->GetTargetOpacity(), 1.f);
  109. views::AnimationBuilder()
  110. .OnEnded(base::BindOnce(
  111. [](base::WeakPtr<views::Widget> the_widget) {
  112. if (!the_widget)
  113. return;
  114. // Please notice, the order matters here. If we set the layer's
  115. // opacity back to 1.f before calling `Hide`, flickering can be
  116. // seen.
  117. the_widget->Hide();
  118. the_widget->GetLayer()->SetOpacity(1.f);
  119. },
  120. widget->GetWeakPtr()))
  121. .SetPreemptionStrategy(
  122. ui::LayerAnimator::IMMEDIATELY_ANIMATE_TO_NEW_TARGET)
  123. .Once()
  124. .SetDuration(animation_params.animation_duration)
  125. .SetOpacity(layer, 0.f, animation_params.tween_type);
  126. }
  127. } // namespace
  128. bool IsCaptureModeActive() {
  129. return CaptureModeController::Get()->IsActive();
  130. }
  131. gfx::Point GetLocationForFineTunePosition(const gfx::Rect& rect,
  132. FineTunePosition position) {
  133. switch (position) {
  134. case FineTunePosition::kTopLeft:
  135. return rect.origin();
  136. case FineTunePosition::kTopCenter:
  137. return rect.top_center();
  138. case FineTunePosition::kTopRight:
  139. return rect.top_right();
  140. case FineTunePosition::kRightCenter:
  141. return rect.right_center();
  142. case FineTunePosition::kBottomRight:
  143. return rect.bottom_right();
  144. case FineTunePosition::kBottomCenter:
  145. return rect.bottom_center();
  146. case FineTunePosition::kBottomLeft:
  147. return rect.bottom_left();
  148. case FineTunePosition::kLeftCenter:
  149. return rect.left_center();
  150. default:
  151. break;
  152. }
  153. NOTREACHED();
  154. return gfx::Point();
  155. }
  156. bool IsCornerFineTunePosition(FineTunePosition position) {
  157. switch (position) {
  158. case FineTunePosition::kTopLeft:
  159. case FineTunePosition::kTopRight:
  160. case FineTunePosition::kBottomRight:
  161. case FineTunePosition::kBottomLeft:
  162. return true;
  163. default:
  164. break;
  165. }
  166. return false;
  167. }
  168. StopRecordingButtonTray* GetStopRecordingButtonForRoot(aura::Window* root) {
  169. DCHECK(root);
  170. DCHECK(root->IsRootWindow());
  171. // Recording can end when a display being fullscreen-captured gets removed, in
  172. // this case, we don't need to hide the button.
  173. if (root->is_destroying())
  174. return nullptr;
  175. // Can be null while shutting down.
  176. auto* root_window_controller = RootWindowController::ForWindow(root);
  177. if (!root_window_controller)
  178. return nullptr;
  179. auto* stop_recording_button = root_window_controller->GetStatusAreaWidget()
  180. ->stop_recording_button_tray();
  181. DCHECK(stop_recording_button);
  182. return stop_recording_button;
  183. }
  184. void SetStopRecordingButtonVisibility(aura::Window* root, bool visible) {
  185. if (auto* stop_recording_button = GetStopRecordingButtonForRoot(root))
  186. stop_recording_button->SetVisiblePreferred(visible);
  187. }
  188. void TriggerAccessibilityAlert(const std::string& message) {
  189. Shell::Get()
  190. ->accessibility_controller()
  191. ->TriggerAccessibilityAlertWithMessage(message);
  192. }
  193. void TriggerAccessibilityAlert(int message_id) {
  194. TriggerAccessibilityAlert(l10n_util::GetStringUTF8(message_id));
  195. }
  196. void TriggerAccessibilityAlertSoon(const std::string& message) {
  197. base::ThreadTaskRunnerHandle::Get()->PostTask(
  198. FROM_HERE,
  199. base::BindOnce(
  200. &AccessibilityControllerImpl::TriggerAccessibilityAlertWithMessage,
  201. Shell::Get()->accessibility_controller()->GetWeakPtr(), message));
  202. }
  203. void TriggerAccessibilityAlertSoon(int message_id) {
  204. TriggerAccessibilityAlertSoon(l10n_util::GetStringUTF8(message_id));
  205. }
  206. CameraPreviewSnapPosition GetCameraNextHorizontalSnapPosition(
  207. CameraPreviewSnapPosition current,
  208. bool going_left) {
  209. switch (current) {
  210. case CameraPreviewSnapPosition::kTopLeft:
  211. return going_left ? current : CameraPreviewSnapPosition::kTopRight;
  212. case CameraPreviewSnapPosition::kTopRight:
  213. return going_left ? CameraPreviewSnapPosition::kTopLeft : current;
  214. case CameraPreviewSnapPosition::kBottomLeft:
  215. return going_left ? current : CameraPreviewSnapPosition::kBottomRight;
  216. case CameraPreviewSnapPosition::kBottomRight:
  217. return going_left ? CameraPreviewSnapPosition::kBottomLeft : current;
  218. }
  219. }
  220. CameraPreviewSnapPosition GetCameraNextVerticalSnapPosition(
  221. CameraPreviewSnapPosition current,
  222. bool going_up) {
  223. switch (current) {
  224. case CameraPreviewSnapPosition::kTopLeft:
  225. return going_up ? current : CameraPreviewSnapPosition::kBottomLeft;
  226. case CameraPreviewSnapPosition::kTopRight:
  227. return going_up ? current : CameraPreviewSnapPosition::kBottomRight;
  228. case CameraPreviewSnapPosition::kBottomLeft:
  229. return going_up ? CameraPreviewSnapPosition::kTopLeft : current;
  230. case CameraPreviewSnapPosition::kBottomRight:
  231. return going_up ? CameraPreviewSnapPosition::kTopRight : current;
  232. }
  233. }
  234. std::unique_ptr<views::View> CreateClipboardShortcutView() {
  235. std::unique_ptr<views::View> clipboard_shortcut_view =
  236. std::make_unique<views::View>();
  237. auto* color_provider = AshColorProvider::Get();
  238. const SkColor background_color = color_provider->GetControlsLayerColor(
  239. AshColorProvider::ControlsLayerType::kControlBackgroundColorActive);
  240. // The text and icon are showing on the background with |background_color|
  241. // so its color is same with kButtonLabelColorPrimary although they're
  242. // not theoretically showing on a button.
  243. const SkColor text_icon_color = color_provider->GetContentLayerColor(
  244. AshColorProvider::ContentLayerType::kButtonLabelColorPrimary);
  245. clipboard_shortcut_view->SetLayoutManager(std::make_unique<views::BoxLayout>(
  246. views::BoxLayout::Orientation::kHorizontal));
  247. const std::u16string shortcut_key = l10n_util::GetStringUTF16(
  248. ui::DeviceUsesKeyboardLayout2() ? IDS_ASH_SHORTCUT_MODIFIER_LAUNCHER
  249. : IDS_ASH_SHORTCUT_MODIFIER_SEARCH);
  250. const std::u16string label_text = l10n_util::GetStringFUTF16(
  251. IDS_ASH_MULTIPASTE_SCREENSHOT_NOTIFICATION_NUDGE, shortcut_key);
  252. views::Label* shortcut_label =
  253. clipboard_shortcut_view->AddChildView(std::make_unique<views::Label>());
  254. shortcut_label->SetText(label_text);
  255. shortcut_label->SetBackgroundColor(background_color);
  256. shortcut_label->SetEnabledColor(text_icon_color);
  257. return clipboard_shortcut_view;
  258. }
  259. // Creates the banner view that will show on top of the notification image.
  260. std::unique_ptr<views::View> CreateBannerView() {
  261. std::unique_ptr<views::View> banner_view = std::make_unique<views::View>();
  262. // Use the light mode as default as notification is still using light
  263. // theme as the default theme.
  264. ScopedLightModeAsDefault scoped_light_mode_as_default;
  265. auto* color_provider = AshColorProvider::Get();
  266. const SkColor background_color = color_provider->GetControlsLayerColor(
  267. AshColorProvider::ControlsLayerType::kControlBackgroundColorActive);
  268. // The text and icon are showing on the background with |background_color|
  269. // so its color is same with kButtonLabelColorPrimary although they're
  270. // not theoretically showing on a button.
  271. const SkColor text_icon_color = color_provider->GetContentLayerColor(
  272. AshColorProvider::ContentLayerType::kButtonLabelColorPrimary);
  273. auto* layout =
  274. banner_view->SetLayoutManager(std::make_unique<views::BoxLayout>(
  275. views::BoxLayout::Orientation::kHorizontal,
  276. gfx::Insets::VH(kBannerVerticalInsetDip, kBannerHorizontalInsetDip),
  277. kBannerIconTextSpacingDip));
  278. if (features::IsNotificationsRefreshEnabled()) {
  279. banner_view->SetBackground(views::CreateBackgroundFromPainter(
  280. std::make_unique<message_center::NotificationBackgroundPainter>(
  281. kBannerViewTopRadius, kBannerViewBottomRadius, background_color)));
  282. } else {
  283. banner_view->SetBackground(views::CreateSolidBackground(background_color));
  284. }
  285. views::ImageView* icon =
  286. banner_view->AddChildView(std::make_unique<views::ImageView>());
  287. icon->SetImage(gfx::CreateVectorIcon(kCaptureModeCopiedToClipboardIcon,
  288. kBannerIconSizeDip, text_icon_color));
  289. views::Label* label = banner_view->AddChildView(
  290. std::make_unique<views::Label>(l10n_util::GetStringUTF16(
  291. IDS_ASH_SCREEN_CAPTURE_SCREENSHOT_COPIED_TO_CLIPBOARD)));
  292. label->SetBackgroundColor(background_color);
  293. label->SetHorizontalAlignment(gfx::ALIGN_LEFT);
  294. label->SetEnabledColor(text_icon_color);
  295. if (!Shell::Get()->tablet_mode_controller()->InTabletMode()) {
  296. banner_view->AddChildView(CreateClipboardShortcutView());
  297. layout->SetFlexForView(label, 1);
  298. // Notify the clipboard history of the created notification.
  299. ClipboardHistoryController::Get()->OnScreenshotNotificationCreated();
  300. }
  301. return banner_view;
  302. }
  303. // Creates the play icon view which shows on top of the video thumbnail in the
  304. // notification.
  305. std::unique_ptr<views::View> CreatePlayIconView() {
  306. auto play_view = std::make_unique<views::ImageView>();
  307. auto* color_provider = AshColorProvider::Get();
  308. const SkColor icon_color = color_provider->GetContentLayerColor(
  309. AshColorProvider::ContentLayerType::kIconColorPrimary);
  310. play_view->SetImage(gfx::CreateVectorIcon(kCaptureModePlayIcon,
  311. kPlayIconSizeDip, icon_color));
  312. play_view->SetHorizontalAlignment(views::ImageView::Alignment::kCenter);
  313. play_view->SetVerticalAlignment(views::ImageView::Alignment::kCenter);
  314. const SkColor background_color = color_provider->GetBaseLayerColor(
  315. AshColorProvider::BaseLayerType::kTransparent80);
  316. play_view->SetBackground(views::CreateRoundedRectBackground(
  317. background_color, kPlayIconBackgroundCornerRadiusDip));
  318. return play_view;
  319. }
  320. gfx::Transform GetScaleTransformAboutCenter(ui::Layer* layer, float scale) {
  321. return gfx::GetScaleTransform(GetLocalCenterPoint(layer), scale);
  322. }
  323. CameraPreviewSizeSpecs CalculateCameraPreviewSizeSpecs(
  324. const gfx::Size& confine_bounds_size,
  325. bool is_collapsed) {
  326. // We divide the shorter side of the confine bounds by a divider to calculate
  327. // the expanded diameter. Note that both expanded and collapsed diameters are
  328. // clamped at a minimum value of `kMinCameraPreviewDiameter`.
  329. const int short_side =
  330. std::min(confine_bounds_size.width(), confine_bounds_size.height());
  331. const int expanded_diameter =
  332. std::max(short_side / capture_mode::kCaptureSurfaceShortSideDivider,
  333. capture_mode::kMinCameraPreviewDiameter);
  334. // If the expanded diameter is below a certain threshold, we consider it too
  335. // small to allow it to collapse, and in that case the resize button will be
  336. // hidden.
  337. const bool is_collapsible =
  338. expanded_diameter >= capture_mode::kMinCollapsibleCameraPreviewDiameter;
  339. // Pick the actual diameter based on whether the preview is currently expanded
  340. // or collapsed.
  341. const int diameter =
  342. !is_collapsed
  343. ? expanded_diameter
  344. : std::max(expanded_diameter / capture_mode::kCollapsedPreviewDivider,
  345. capture_mode::kMinCameraPreviewDiameter);
  346. bool is_surface_too_small = false;
  347. const bool should_be_visible =
  348. CalculateCameraPreviewTargetVisibility(short_side, &is_surface_too_small);
  349. // If the surface was determined to be too small, the preview should be
  350. // hidden.
  351. DCHECK(!is_surface_too_small || !should_be_visible);
  352. return CameraPreviewSizeSpecs{gfx::Size(diameter, diameter), is_collapsible,
  353. should_be_visible, is_surface_too_small};
  354. }
  355. aura::Window* GetTopMostCapturableWindowAtPoint(
  356. const gfx::Point& screen_point) {
  357. auto* controller = CaptureModeController::Get();
  358. std::set<aura::Window*> ignore_windows;
  359. auto* camera_controller = controller->camera_controller();
  360. if (auto* camera_preview_widget = camera_controller->camera_preview_widget())
  361. ignore_windows.insert(camera_preview_widget->GetNativeWindow());
  362. if (controller->IsActive()) {
  363. auto* capture_session = controller->capture_mode_session();
  364. if (auto* capture_label_widget = capture_session->capture_label_widget())
  365. ignore_windows.insert(capture_label_widget->GetNativeWindow());
  366. if (auto* capture_toast_widget = capture_session->capture_toast_controller()
  367. ->capture_toast_widget()) {
  368. ignore_windows.insert(capture_toast_widget->GetNativeWindow());
  369. }
  370. }
  371. return GetTopmostWindowAtPoint(screen_point, ignore_windows);
  372. }
  373. bool GetWidgetCurrentVisibility(views::Widget* widget) {
  374. // Note that we use `aura::Window::TargetVisibility()` rather than
  375. // `views::Widget::IsVisible()` (which in turn uses
  376. // `aura::Window::IsVisible()`). The reason is because the latter takes into
  377. // account whether window's layer is drawn or not. We want to calculate the
  378. // current visibility only based on the actual visibility of the window
  379. // itself, so that we can correctly compare it against `target_visibility`.
  380. // Note that the widget may be a child of the unparented container (which is
  381. // always hidden), yet the native window is shown.
  382. return widget->GetNativeWindow()->TargetVisibility() &&
  383. widget->GetLayer()->GetTargetOpacity() > 0.f;
  384. }
  385. bool SetWidgetVisibility(views::Widget* widget,
  386. bool target_visibility,
  387. absl::optional<AnimationParams> animation_params) {
  388. DCHECK(widget);
  389. if (target_visibility == GetWidgetCurrentVisibility(widget))
  390. return false;
  391. if (animation_params) {
  392. if (target_visibility)
  393. FadeInWidget(widget, *animation_params);
  394. else
  395. FadeOutWidget(widget, *animation_params);
  396. } else {
  397. if (target_visibility)
  398. widget->Show();
  399. else
  400. widget->Hide();
  401. }
  402. return true;
  403. }
  404. } // namespace ash::capture_mode_util