| // Copyright 2020 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #ifndef ASH_CAPTURE_MODE_CAPTURE_MODE_SESSION_H_ |
| #define ASH_CAPTURE_MODE_CAPTURE_MODE_SESSION_H_ |
| |
| #include <memory> |
| #include <optional> |
| #include <vector> |
| |
| #include "ash/accessibility/magnifier/magnifier_glass.h" |
| #include "ash/capture_mode/base_capture_mode_session.h" |
| #include "ash/capture_mode/capture_label_view.h" |
| #include "ash/capture_mode/capture_mode_behavior.h" |
| #include "ash/capture_mode/capture_mode_toast_controller.h" |
| #include "ash/capture_mode/capture_mode_types.h" |
| #include "ash/capture_mode/folder_selection_dialog_controller.h" |
| #include "ash/shell_observer.h" |
| #include "base/containers/flat_set.h" |
| #include "base/memory/raw_ptr.h" |
| #include "base/memory/weak_ptr.h" |
| #include "ui/aura/window_observer.h" |
| #include "ui/color/color_provider_source_observer.h" |
| #include "ui/compositor/layer_delegate.h" |
| #include "ui/display/display_observer.h" |
| #include "ui/events/event.h" |
| #include "ui/events/event_handler.h" |
| #include "ui/gfx/geometry/point.h" |
| #include "ui/views/widget/unique_widget_ptr.h" |
| #include "ui/views/widget/widget.h" |
| |
| namespace display { |
| enum class TabletState; |
| } // namespace display |
| |
| namespace gfx { |
| class Canvas; |
| } // namespace gfx |
| |
| namespace ash { |
| |
| class CaptureModeBarView; |
| class CaptureModeController; |
| class CaptureModeSessionFocusCycler; |
| class CaptureModeSettingsView; |
| class CaptureWindowObserver; |
| class CursorSetter; |
| class RecordingTypeMenuView; |
| class UserNudgeController; |
| class WindowDimmer; |
| |
| // Encapsulates an active capture mode session (i.e. an instance of this class |
| // lives as long as capture mode is active). It creates and owns the capture |
| // mode bar widget. |
| // The CaptureModeSession is a LayerOwner that owns a texture layer placed right |
| // beneath the layer of the bar widget. This layer is used to paint a dimming |
| // shield of the areas that won't be captured, and another bright region showing |
| // the one that will be. |
| class ASH_EXPORT CaptureModeSession |
| : public BaseCaptureModeSession, |
| public ui::LayerDelegate, |
| public ui::EventHandler, |
| public aura::WindowObserver, |
| public display::DisplayObserver, |
| public FolderSelectionDialogController::Delegate, |
| public ui::ColorProviderSourceObserver { |
| public: |
| // Centralized place to control the events, observe windows and create the |
| // capture mode needed widgets including `capture_mode_bar_widget_`, |
| // `capture_label_widget_`, `recording_type_menu_widget_`, etc, on a |
| // calculated root window. `active_behavior` will customize the widgets or |
| // restrict certain operations. |
| CaptureModeSession(CaptureModeController* controller, |
| CaptureModeBehavior* active_behavior); |
| CaptureModeSession(const CaptureModeSession&) = delete; |
| CaptureModeSession& operator=(const CaptureModeSession&) = delete; |
| ~CaptureModeSession() override; |
| |
| // The vertical distance from the size label to the custom capture region. |
| static constexpr int kSizeLabelYDistanceFromRegionDp = 8; |
| |
| // The vertical distance of the capture button from the capture region, if the |
| // capture button does not fit inside the capture region. |
| static constexpr int kCaptureButtonDistanceFromRegionDp = 24; |
| |
| views::Widget* capture_label_widget() { return capture_label_widget_.get(); } |
| views::Widget* capture_mode_settings_widget() { |
| return capture_mode_settings_widget_.get(); |
| } |
| bool is_selecting_region() const { return is_selecting_region_; } |
| CaptureModeToastController* capture_toast_controller() { |
| return &capture_toast_controller_; |
| } |
| |
| // Called when a user toggles the capture source or capture type to announce |
| // an accessibility alert. If `trigger_now` is true, it will announce |
| // immediately; otherwise, it will trigger another alert asynchronously with |
| // the alert. |
| void A11yAlertCaptureSource(bool trigger_now); |
| |
| // Called when the settings menu is toggled. If `by_key_event` is true, it |
| // means that the settings menu is being opened or closed as a result of a key |
| // event (e.g. pressing the space bar) on the settings button. |
| void SetSettingsMenuShown(bool shown, bool by_key_event = false); |
| |
| // Opens the dialog that lets users pick the folder to which they want the |
| // captured files to be saved. |
| void OpenFolderSelectionDialog(); |
| |
| // Returns true if we are currently in video recording countdown animation. |
| bool IsInCountDownAnimation() const; |
| |
| // Returns true if `capture_window_observer_` exists and the capture bar is |
| // anchored to a pre-selected window. |
| bool IsBarAnchoredToWindow() const; |
| |
| // Updates the current cursor depending on current |location_in_screen| and |
| // current capture type and source. |is_touch| is used when calculating fine |
| // tune position in region capture mode. We'll have a larger hit test region |
| // for the touch events than the mouse events. |
| void UpdateCursor(const gfx::Point& location_in_screen, bool is_touch); |
| |
| // Highlights the give |window| for keyboard navigation |
| // events (tabbing through windows in capture window mode). |
| void HighlightWindowForTab(aura::Window* window); |
| |
| // Called when the settings view has been updated, its bounds may need to be |
| // updated correspondingly. |
| void MaybeUpdateSettingsBounds(); |
| |
| // Called when opacity of capture UIs (capture bar, capture label) may need to |
| // be updated. For example, when camera preview is created, destroyed, |
| // reparented, display metrics change or located events enter / exit / move |
| // on capture UI. |
| void MaybeUpdateCaptureUisOpacity( |
| std::optional<gfx::Point> cursor_screen_location = std::nullopt); |
| |
| // Sets the correct screen bounds on the `capture_mode_bar_widget_` based on |
| // the `current_root_`, potentially moving the bar to a new display if |
| // `current_root_` is different`. |
| void RefreshBarWidgetBounds(); |
| |
| // BaseCaptureModeSession: |
| views::Widget* GetCaptureModeBarWidget() override; |
| aura::Window* GetSelectedWindow() const override; |
| void SetPreSelectedWindow(aura::Window* pre_selected_window) override; |
| void OnCaptureSourceChanged(CaptureModeSource new_source) override; |
| void OnCaptureTypeChanged(CaptureModeType new_type) override; |
| void OnRecordingTypeChanged() override; |
| void OnAudioRecordingModeChanged() override; |
| void OnDemoToolsSettingsChanged() override; |
| void OnWaitingForDlpConfirmationStarted() override; |
| void OnWaitingForDlpConfirmationEnded(bool reshow_uis) override; |
| void ReportSessionHistograms() override; |
| void StartCountDown(base::OnceClosure countdown_finished_callback) override; |
| void OnCaptureFolderMayHaveChanged() override; |
| void OnDefaultCaptureFolderSelectionChanged() override; |
| bool CalculateCameraPreviewTargetVisibility() const override; |
| void OnCameraPreviewDragStarted() override; |
| void OnCameraPreviewDragEnded(const gfx::Point& screen_location, |
| bool is_touch) override; |
| void OnCameraPreviewBoundsOrVisibilityChanged( |
| bool capture_surface_became_too_small, |
| bool did_bounds_or_visibility_change) override; |
| void OnCameraPreviewDestroyed() override; |
| void MaybeDismissUserNudgeForever() override; |
| void MaybeChangeRoot(aura::Window* new_root) override; |
| std::set<aura::Window*> GetWindowsToIgnoreFromWidgets() override; |
| |
| // ui::LayerDelegate: |
| void OnPaintLayer(const ui::PaintContext& context) override; |
| void OnDeviceScaleFactorChanged(float old_device_scale_factor, |
| float new_device_scale_factor) override {} |
| |
| // ui::EventHandler: |
| void OnKeyEvent(ui::KeyEvent* event) override; |
| void OnMouseEvent(ui::MouseEvent* event) override; |
| void OnTouchEvent(ui::TouchEvent* event) override; |
| |
| // aura::WindowObserver: |
| void OnWindowDestroying(aura::Window* window) override; |
| |
| // display::DisplayObserver: |
| void OnDisplayTabletStateChanged(display::TabletState state) override; |
| void OnDisplayMetricsChanged(const display::Display& display, |
| uint32_t metrics) override; |
| |
| // FolderSelectionDialogController::Delegate: |
| void OnFolderSelected(const base::FilePath& path) override; |
| void OnSelectionWindowAdded() override; |
| void OnSelectionWindowClosed() override; |
| |
| // ui::ColorProviderSourceObserver: |
| void OnColorProviderChanged() override; |
| |
| private: |
| friend class CaptureModeSettingsTestApi; |
| friend class CaptureModeSessionFocusCycler; |
| friend class CaptureModeSessionTestApi; |
| friend class CaptureModeTestApi; |
| class ParentContainerObserver; |
| |
| enum class CaptureLabelAnimation { |
| // No animation on the capture label. |
| kNone, |
| // The animation on the capture label when the user has finished selecting a |
| // region and is moving to the fine tune phase. |
| kRegionPhaseChange, |
| // The animation on the capture label when the user has clicked record and |
| // the capture label animates into a countdown label. |
| kCountdownStart, |
| }; |
| |
| // Called when switching a capture type from another capture type. |
| void A11yAlertCaptureType(); |
| |
| // Returns a list of all the currently available widgets that are owned by |
| // this session. |
| std::vector<views::Widget*> GetAvailableWidgets(); |
| |
| // All UI elements, cursors, widgets and paintings on the session's layer can |
| // be either shown or hidden with the below functions. |
| void HideAllUis(); |
| void ShowAllUis(); |
| |
| // Called by `ShowAllUis` for each widget. Returns true if the given `widget` |
| // could be shown, otherwise, returns false. |
| bool CanShowWidget(views::Widget* widget) const; |
| |
| // If possible, this recreates and shows the nudge that alerts the user about |
| // the new folder selection settings. The nudge will be created on top of the |
| // the settings button on the capture mode bar. |
| void MaybeCreateUserNudge(); |
| |
| // Called to accept and trigger a capture operation. This happens e.g. when |
| // the user hits enter, selects a window/display to capture, or presses on the |
| // record button in the capture label view. |
| void DoPerformCapture(); |
| |
| // Called when the drop-down button in the `capture_label_widget_` is pressed |
| // which toggles the recording type menu on and off. |
| void OnRecordingTypeDropDownButtonPressed(const ui::Event& event); |
| |
| // Ensures that the bar widget is on top of everything, and the overlay (which |
| // is the |layer()| of this class that paints the capture region) is stacked |
| // below the bar. |
| void RefreshStackingOrder(); |
| |
| // Paints the current capture region depending on the current capture source. |
| void PaintCaptureRegion(gfx::Canvas* canvas); |
| |
| // Helper to unify mouse/touch events. Forwards events to the three below |
| // functions and they are located on |capture_button_widget_|. Blocks events |
| // from reaching other handlers, unless the event is located on |
| // |capture_mode_bar_widget_|. |is_touch| signifies this is a touch event, and |
| // we will use larger hit targets for the drag affordances. |
| void OnLocatedEvent(ui::LocatedEvent* event, bool is_touch); |
| |
| // Returns the fine tune position that corresponds to the given |
| // `location_in_screen`. |
| FineTunePosition GetFineTunePosition(const gfx::Point& location_in_screen, |
| bool is_touch) const; |
| |
| // Handles updating the select region UI. |
| void OnLocatedEventPressed(const gfx::Point& location_in_root, bool is_touch); |
| void OnLocatedEventDragged(const gfx::Point& location_in_root); |
| void OnLocatedEventReleased(const gfx::Point& location_in_root); |
| |
| // Updates the capture region and the capture region widgets depending on the |
| // value of |is_resizing|. |by_user| is true if the capture region is changed |
| // by user. |
| void UpdateCaptureRegion(const gfx::Rect& new_capture_region, |
| bool is_resizing, |
| bool by_user); |
| |
| // Updates the dimensions label widget shown during a region capture session. |
| // If not |is_resizing|, not a region capture session or the capture region is |
| // empty, clear existing |dimensions_label_widget_|. Otherwise, create and |
| // update the dimensions label. |
| void UpdateDimensionsLabelWidget(bool is_resizing); |
| |
| // Updates the bounds of |dimensions_label_widget_| relative to the current |
| // capture region. Both |dimensions_label_widget_| and its content view must |
| // exist. |
| void UpdateDimensionsLabelBounds(); |
| |
| // If |fine_tune_position_| is not a corner, do nothing. Otherwise show |
| // |magnifier_glass_| at |location_in_root| in the current root window and |
| // hide the cursor. |
| void MaybeShowMagnifierGlassAtPoint(const gfx::Point& location_in_root); |
| |
| // Closes |magnifier_glass_|. |
| void CloseMagnifierGlass(); |
| |
| // Retrieves the anchor points on the current selected region associated with |
| // |position|. The anchor points are described as the points that do not |
| // change when resizing the capture region while dragging one of the drag |
| // affordances. There is one anchor point if |position| is a vertex, and two |
| // anchor points if |position| is an edge. |
| std::vector<gfx::Point> GetAnchorPointsForPosition(FineTunePosition position); |
| |
| // Updates the capture label widget's icon/text and bounds. The capture label |
| // widget may be animated depending on |animation_type|. |
| void UpdateCaptureLabelWidget(CaptureLabelAnimation animation_type); |
| |
| // Updates the capture label widget's bounds. The capture label |
| // widget may be animated depending on |animation_type|. |
| void UpdateCaptureLabelWidgetBounds(CaptureLabelAnimation animation_type); |
| |
| // Calculates the targeted capture label widget bounds in screen coordinates. |
| gfx::Rect CalculateCaptureLabelWidgetBounds(); |
| |
| // Returns true if the capture label should handle the event. |event_target| |
| // is the window which is receiving the event. The capture label should handle |
| // the event if its associated window is |event_target| and its capture button |
| // child is visible. |
| bool ShouldCaptureLabelHandleEvent(aura::Window* event_target); |
| |
| // Updates |root_window_dimmers_| to dim the correct root windows. |
| void UpdateRootWindowDimmers(); |
| |
| // Returns true if we're using custom image capture icon when |type| is |
| // kImage or using custom video capture icon when |type| is kVideo. |
| bool IsUsingCustomCursor(CaptureModeType type) const; |
| |
| // Ensure the user region in |controller_| is within the bounds of the root |
| // window. This is called when creating |this| or when the display bounds have |
| // changed. |
| void ClampCaptureRegionToRootWindowSize(); |
| |
| // Ends a region selection. Cleans up internal state and updates the cursor, |
| // capture UIs' opacity and magnifier glass. The `cursor_screen_location` |
| // could not be provided in some use cases, for example the capture region is |
| // updated because of the display metrics are changed. When |
| // `cursor_screen_location` is not provived, we will try to get the screen |
| // location of the mouse. |
| void EndSelection( |
| std::optional<gfx::Point> cursor_screen_location = std::nullopt); |
| |
| // Schedules a paint on the region and enough inset around it so that the |
| // shadow, affordance circles, etc. are all repainted. |
| void RepaintRegion(); |
| |
| // Selects a default region that is centered and whose size is a ratio of the |
| // root window bounds. Called when the space key is pressed. |
| void SelectDefaultRegion(); |
| |
| // Updates the region either horizontally or vertically. Called when the arrow |
| // keys are pressed. `event_flags` are the flags from the event that triggers |
| // these calls. Different modifiers will move the region more or less. |
| void UpdateRegionForArrowKeys(ui::KeyboardCode key_code, int event_flags); |
| |
| // Called when the parent container of camera preview may need to be updated. |
| void MaybeReparentCameraPreviewWidget(); |
| |
| // Called at the beginning or end of the drag of capture region to update the |
| // camera preview's bounds and visibility. |
| void MaybeUpdateCameraPreviewBounds(); |
| |
| // Creates or distroys the recording type menu widget based on the given |
| // `shown` value. If `by_key_event` is true, it means that the recording type |
| // menu is being opened or closed as a result of a key event (e.g. pressing |
| // the space bar) on the recording type drop down button. |
| void SetRecordingTypeMenuShown(bool shown, bool by_key_event = false); |
| |
| // Returns true if the given `screen_location` is on the drop down button in |
| // the `capture_label_widget_` which when clicked opens the recording type |
| // menu. |
| bool IsPointOnRecordingTypeDropDownButton( |
| const gfx::Point& screen_location) const; |
| |
| // Updates the availability or bounds of the recording type menu widget |
| // according to the current state. |
| void MaybeUpdateRecordingTypeMenu(); |
| |
| // Returns true if there is a selected window and it is the topmost |
| // capturable window at `screen_point`. Returns false otherwise. |
| bool IsPointOverSelectedWindow(const gfx::Point& screen_point) const; |
| |
| // BaseCaptureModeSession: |
| void InitInternal() override; |
| void ShutdownInternal() override; |
| |
| views::UniqueWidgetPtr capture_mode_bar_widget_ = |
| std::make_unique<views::Widget>(); |
| |
| // The content view of the above widget and owned by its views hierarchy. |
| raw_ptr<CaptureModeBarView, DanglingUntriaged> capture_mode_bar_view_ = |
| nullptr; |
| |
| views::UniqueWidgetPtr capture_mode_settings_widget_; |
| |
| // The content view of the above widget and owned by its views hierarchy. |
| raw_ptr<CaptureModeSettingsView, DanglingUntriaged> |
| capture_mode_settings_view_ = nullptr; |
| |
| // Widget which displays capture region size during a region capture session. |
| views::UniqueWidgetPtr dimensions_label_widget_; |
| |
| // Widget that shows an optional icon and a message in the middle of the |
| // screen or in the middle of the capture region and prompts the user what to |
| // do next. The icon and message can be different in different capture type |
| // and source and can be empty in some cases. And in video capture mode, when |
| // starting capturing, the widget will transform into a 3-second countdown |
| // timer. |
| views::UniqueWidgetPtr capture_label_widget_; |
| raw_ptr<CaptureLabelView, DanglingUntriaged> capture_label_view_ = nullptr; |
| |
| // Widget that hosts the recording type menu, from which the user can pick the |
| // desired recording format type. |
| views::UniqueWidgetPtr recording_type_menu_widget_; |
| raw_ptr<RecordingTypeMenuView, DanglingUntriaged> recording_type_menu_view_ = |
| nullptr; |
| |
| // Magnifier glass used during a region capture session. |
| MagnifierGlass magnifier_glass_; |
| |
| // True if all UIs (cursors, widgets, and paintings on the layer) of the |
| // capture mode session is visible. |
| bool is_all_uis_visible_ = true; |
| |
| // Stores the data needed to select a region during a region capture session. |
| // This variable indicates if the user is currently selecting a region to |
| // capture, it will be true when the first mouse/touch presses down and will |
| // remain true until the mouse/touch releases up. After that, if the capture |
| // region is non empty, the capture session will enter the fine tune phase, |
| // where the user can reposition and resize the region with a lot of accuracy. |
| bool is_selecting_region_ = false; |
| |
| // True when a located pressed event is received outside the bounds of a |
| // present settings menu widget. This event will be used to dismiss the |
| // settings menu and all future located events up to and including the |
| // released event will be ignored (i.e. will not be used to update the capture |
| // region, perform capture ... etc.). |
| bool ignore_located_events_ = false; |
| |
| // The location of the last press and drag events. |
| gfx::Point initial_location_in_root_; |
| gfx::Point previous_location_in_root_; |
| // The position of the last press event during the fine tune phase drag. |
| FineTunePosition fine_tune_position_ = FineTunePosition::kNone; |
| // The points that do not change during a fine tune resize. This is empty |
| // when |fine_tune_position_| is kNone or kCenter, or if there is no drag |
| // underway. |
| std::vector<gfx::Point> anchor_points_; |
| |
| // Caches the old status of mouse warping while dragging or resizing a |
| // captured region. |
| std::optional<bool> old_mouse_warp_status_; |
| |
| // Observer to observe the current selected to-be-captured window. |
| std::unique_ptr<CaptureWindowObserver> capture_window_observer_; |
| |
| // Observer to observe the parent container `kShellWindowId_MenuContainer`. |
| std::unique_ptr<ParentContainerObserver> parent_container_observer_; |
| |
| // Contains the window dimmers which dim all the root windows except |
| // |current_root_|. |
| base::flat_set<std::unique_ptr<WindowDimmer>> root_window_dimmers_; |
| |
| // The object to specify the cursor type. |
| std::unique_ptr<CursorSetter> cursor_setter_; |
| |
| // Counter used to track the number of times a user adjusts a capture region. |
| // This should be reset when a user creates a new capture region, changes |
| // capture sources or when a user performs a capture. |
| int num_capture_region_adjusted_ = 0; |
| |
| // True if at any point during the lifetime of |this|, the capture source |
| // changed. Used for metrics collection. |
| bool capture_source_changed_ = false; |
| |
| // The window which had input capture prior to entering the session. It may be |
| // null if no such window existed. |
| raw_ptr<aura::Window, DanglingUntriaged> input_capture_window_ = nullptr; |
| |
| // The display observer between init/shutdown. |
| std::optional<display::ScopedDisplayObserver> display_observer_; |
| |
| // True when we ask the DLP manager to check the screen content before we |
| // perform the capture. |
| bool is_waiting_for_dlp_confirmation_ = false; |
| |
| // The object which handles tab focus while in a capture session. |
| std::unique_ptr<CaptureModeSessionFocusCycler> focus_cycler_; |
| |
| // 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> |
| folder_selection_dialog_controller_; |
| |
| // Controls the user nudge animations. |
| std::unique_ptr<UserNudgeController> user_nudge_controller_; |
| |
| // Controls creating, destroying or updating the visibility of the capture |
| // toast. |
| CaptureModeToastController capture_toast_controller_; |
| |
| base::WeakPtrFactory<CaptureModeSession> weak_ptr_factory_{this}; |
| }; |
| |
| } // namespace ash |
| |
| #endif // ASH_CAPTURE_MODE_CAPTURE_MODE_SESSION_H_ |