recording_overlay_controller.cc 9.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237
  1. // Copyright 2021 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/recording_overlay_controller.h"
  5. #include "ash/capture_mode/capture_mode_controller.h"
  6. #include "ash/capture_mode/stop_recording_button_tray.h"
  7. #include "ash/projector/projector_annotation_tray.h"
  8. #include "ash/public/cpp/capture_mode/recording_overlay_view.h"
  9. #include "ash/public/cpp/shell_window_ids.h"
  10. #include "ash/root_window_controller.h"
  11. #include "ash/shelf/shelf.h"
  12. #include "ash/system/status_area_widget.h"
  13. #include "ui/aura/window.h"
  14. #include "ui/aura/window_targeter.h"
  15. #include "ui/compositor/layer_type.h"
  16. #include "ui/gfx/geometry/rect.h"
  17. #include "ui/views/widget/widget.h"
  18. #include "ui/wm/core/coordinate_conversion.h"
  19. #include "ui/wm/core/window_properties.h"
  20. namespace ash {
  21. namespace {
  22. // When recording a non-root window (i.e. kWindow recording source), the overlay
  23. // is added as a direct child of the window being recorded, and stacked on top
  24. // of all children. This is so that the overlay contents show up in the
  25. // recording above everything else.
  26. //
  27. // + window_being_recorded
  28. // |
  29. // + (Some other child windows hosting contents of the window)
  30. // |
  31. // + Recording overlay widget
  32. //
  33. // (Note that bottom-most child are the top-most child in terms of z-order).
  34. //
  35. // However, when recording the root window (i.e. kFullscreen or kRegion
  36. // recording sources), the overlay is added as a child of the menu container.
  37. // The menu container is high enough in terms of z-order, making the overlay on
  38. // top of most things. However, it's also the same container used by the
  39. // projector bar (which we want to be on top of the overlay, since it has the
  40. // button to toggle the overlay off, and we don't want the overlay to block
  41. // events going to that button). Therefore, the overlay is stacked at the bottom
  42. // of the menu container's children. See UpdateWidgetStacking() below.
  43. //
  44. // + Menu container
  45. // |
  46. // + Recording overlay widget
  47. // |
  48. // + Projector bar widget
  49. //
  50. // TODO(https://crbug.com/1253011): Revise this parenting and z-ordering once
  51. // the deprecated Projector toolbar is removed and replaced by the shelf-pod
  52. // based new tools.
  53. aura::Window* GetWidgetParent(aura::Window* window_being_recorded) {
  54. return window_being_recorded->IsRootWindow()
  55. ? window_being_recorded->GetChildById(kShellWindowId_MenuContainer)
  56. : window_being_recorded;
  57. }
  58. // Given the `bounds_in_parent` of the overlay widget, returns the bounds in the
  59. // correct coordinate system depending on whether the `overlay_window_parent`
  60. // uses screen coordinates or not.
  61. gfx::Rect MaybeAdjustOverlayBounds(const gfx::Rect& bounds_in_parent,
  62. aura::Window* overlay_window_parent) {
  63. DCHECK(overlay_window_parent);
  64. if (!overlay_window_parent->GetProperty(wm::kUsesScreenCoordinatesKey))
  65. return bounds_in_parent;
  66. gfx::Rect bounds_in_screen = bounds_in_parent;
  67. wm::ConvertRectToScreen(overlay_window_parent, &bounds_in_screen);
  68. return bounds_in_screen;
  69. }
  70. // Defines a window targeter that will be installed on the overlay widget's
  71. // window so that we can allow located events over the projector shelf pod or
  72. // its associated bubble widget to go through and not be consumed by the
  73. // overlay. This enables the user to interact with the annotation tools while
  74. // annotating.
  75. class OverlayTargeter : public aura::WindowTargeter {
  76. public:
  77. explicit OverlayTargeter(aura::Window* overlay_window)
  78. : overlay_window_(overlay_window) {}
  79. OverlayTargeter(const OverlayTargeter&) = delete;
  80. OverlayTargeter& operator=(const OverlayTargeter&) = delete;
  81. ~OverlayTargeter() override = default;
  82. // aura::WindowTargeter:
  83. ui::EventTarget* FindTargetForEvent(ui::EventTarget* root,
  84. ui::Event* event) override {
  85. if (event->IsLocatedEvent()) {
  86. auto* root_window = overlay_window_->GetRootWindow();
  87. auto* status_area_widget =
  88. RootWindowController::ForWindow(root_window)->GetStatusAreaWidget();
  89. StopRecordingButtonTray* stop_recording_button =
  90. status_area_widget->stop_recording_button_tray();
  91. auto screen_location = event->AsLocatedEvent()->root_location();
  92. wm::ConvertPointToScreen(root_window, &screen_location);
  93. Shelf* shelf = RootWindowController::ForWindow(root_window)->shelf();
  94. // To be able to bring the auto-hidden shelf back even while annotation is
  95. // active, we expose a slim 1dp region at the edge of the screen in which
  96. // the shelf is aligned. Events in that region will not be consumed so
  97. // that they can be used to show the auto-hidden shelf.
  98. if (!shelf->IsVisible()) {
  99. gfx::Rect root_window_bounds_in_screen =
  100. root_window->GetBoundsInScreen();
  101. const int display_width = root_window_bounds_in_screen.width();
  102. const int display_height = root_window_bounds_in_screen.height();
  103. const gfx::Rect shelf_activation_bounds =
  104. shelf->SelectValueForShelfAlignment(
  105. gfx::Rect(0, display_height - 1, display_width, 1),
  106. gfx::Rect(0, 0, 1, display_height),
  107. gfx::Rect(display_width - 1, 0, 1, display_height));
  108. if (shelf_activation_bounds.Contains(screen_location))
  109. return nullptr;
  110. }
  111. // To be able to end video recording even while annotation is active,
  112. // let events over the stop recording button to go through.
  113. if (stop_recording_button && stop_recording_button->visible_preferred() &&
  114. stop_recording_button->GetBoundsInScreen().Contains(
  115. screen_location)) {
  116. return nullptr;
  117. }
  118. ProjectorAnnotationTray* annotations =
  119. status_area_widget->projector_annotation_tray();
  120. if (annotations && annotations->visible_preferred()) {
  121. // Let events over the projector shelf pod to go through.
  122. if (annotations->GetBoundsInScreen().Contains(screen_location))
  123. return nullptr;
  124. // Let events over the projector bubble widget (if shown) to go through.
  125. views::Widget* bubble_widget = annotations->GetBubbleWidget();
  126. if (bubble_widget && bubble_widget->IsVisible() &&
  127. bubble_widget->GetWindowBoundsInScreen().Contains(
  128. screen_location)) {
  129. return nullptr;
  130. }
  131. // Ensure that the annotator bubble is closed when a press event is
  132. // triggered.
  133. if (event->type() == ui::ET_MOUSE_PRESSED ||
  134. event->type() == ui::ET_TOUCH_PRESSED) {
  135. annotations->ClickedOutsideBubble();
  136. }
  137. }
  138. }
  139. return aura::WindowTargeter::FindTargetForEvent(root, event);
  140. }
  141. private:
  142. aura::Window* const overlay_window_;
  143. };
  144. } // namespace
  145. RecordingOverlayController::RecordingOverlayController(
  146. aura::Window* window_being_recorded,
  147. const gfx::Rect& initial_bounds_in_parent) {
  148. DCHECK(window_being_recorded);
  149. views::Widget::InitParams params(
  150. views::Widget::InitParams::TYPE_WINDOW_FRAMELESS);
  151. params.name = "RecordingOverlayWidget";
  152. params.child = true;
  153. params.parent = GetWidgetParent(window_being_recorded);
  154. // The overlay hosts transparent contents so actual contents of the window
  155. // being recorded shows up underneath.
  156. params.layer_type = ui::LAYER_NOT_DRAWN;
  157. params.opacity = views::Widget::InitParams::WindowOpacity::kTranslucent;
  158. params.bounds =
  159. MaybeAdjustOverlayBounds(initial_bounds_in_parent, params.parent);
  160. // The overlay window does not receive any events until it's shown and
  161. // enabled. See |Start()| below.
  162. params.activatable = views::Widget::InitParams::Activatable::kNo;
  163. params.accept_events = false;
  164. overlay_widget_->Init(std::move(params));
  165. recording_overlay_view_ = overlay_widget_->SetContentsView(
  166. CaptureModeController::Get()->CreateRecordingOverlayView());
  167. auto* overlay_window = overlay_widget_->GetNativeWindow();
  168. overlay_window->SetEventTargeter(
  169. std::make_unique<OverlayTargeter>(overlay_window));
  170. UpdateWidgetStacking();
  171. }
  172. void RecordingOverlayController::Toggle() {
  173. is_enabled_ = !is_enabled_;
  174. if (is_enabled_)
  175. Start();
  176. else
  177. Stop();
  178. }
  179. void RecordingOverlayController::SetBounds(const gfx::Rect& bounds_in_parent) {
  180. overlay_widget_->SetBounds(MaybeAdjustOverlayBounds(
  181. bounds_in_parent, overlay_widget_->GetNativeWindow()));
  182. }
  183. aura::Window* RecordingOverlayController::GetOverlayNativeWindow() {
  184. return overlay_widget_->GetNativeWindow();
  185. }
  186. void RecordingOverlayController::Start() {
  187. DCHECK(is_enabled_);
  188. overlay_widget_->GetNativeWindow()->SetEventTargetingPolicy(
  189. aura::EventTargetingPolicy::kTargetAndDescendants);
  190. overlay_widget_->Show();
  191. }
  192. void RecordingOverlayController::Stop() {
  193. DCHECK(!is_enabled_);
  194. overlay_widget_->GetNativeWindow()->SetEventTargetingPolicy(
  195. aura::EventTargetingPolicy::kNone);
  196. overlay_widget_->Hide();
  197. }
  198. void RecordingOverlayController::UpdateWidgetStacking() {
  199. auto* overlay_window = overlay_widget_->GetNativeWindow();
  200. auto* parent = overlay_window->parent();
  201. DCHECK(parent);
  202. // See more info in the docs of GetWidgetParent() above.
  203. if (parent->GetId() == kShellWindowId_MenuContainer)
  204. parent->StackChildAtBottom(overlay_window);
  205. else
  206. parent->StackChildAtTop(overlay_window);
  207. }
  208. } // namespace ash