| // Copyright 2021 The Chromium Authors. All rights reserved. |
| // 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_FOCUS_CYCLER_H_ |
| #define ASH_CAPTURE_MODE_CAPTURE_MODE_SESSION_FOCUS_CYCLER_H_ |
| |
| #include <cstddef> |
| #include <vector> |
| |
| #include "ash/ash_export.h" |
| #include "ash/capture_mode/capture_mode_types.h" |
| #include "ui/aura/window_observer.h" |
| #include "ui/views/widget/widget.h" |
| #include "ui/views/widget/widget_observer.h" |
| |
| namespace views { |
| class FocusRing; |
| class HighlightPathGenerator; |
| class View; |
| class Widget; |
| } // namespace views |
| |
| namespace ash { |
| |
| class CaptureModeSession; |
| class ScopedA11yOverrideWindowSetter; |
| |
| // CaptureModeSessionFocusCycler handles the special focus transitions which |
| // happen between the capture session UI items. These include the capture bar |
| // buttons, the selection region UI and the capture button. |
| // TODO(crbug.com/1182456): The selection region UI are drawn directly on a |
| // layer. We simulate focus by drawing focus rings on the same layer, but this |
| // is not compatible with accessibility. Investigate using AxVirtualView or |
| // making the dots actual Views. |
| class ASH_EXPORT CaptureModeSessionFocusCycler : public views::WidgetObserver { |
| public: |
| // The different groups which can receive focus during a capture mode session. |
| // A group may have multiple items which can receive focus. |
| // TODO(crbug.com/1182456): Investigate removing the groups concept and having |
| // one flat list. |
| enum class FocusGroup { |
| kNone = 0, |
| // The buttons to select the capture type and source on the capture bar. |
| kTypeSource, |
| // In region mode, the UI to adjust a partial region. |
| kSelection, |
| // The button in the middle of a selection region to capture or record. |
| kCaptureButton, |
| // In window mode, the group to tab through the available MRU windows. |
| kCaptureWindow, |
| // The buttons to open the settings menu and exit capture mode on the |
| // capture bar. |
| kSettingsClose, |
| // A state where nothing is visibly focused. The next focus will advance to |
| // the settings menu. This is to match tab focusing on other menus such as |
| // quick settings. |
| kPendingSettings, |
| // The buttons inside the settings menu. |
| kSettingsMenu, |
| // The camera preview shown inside the current capture surface. |
| kCameraPreview, |
| }; |
| |
| // If a focusable capture session item is part of a views hierarchy, it needs |
| // to override this class, which manages a custom focus ring. |
| class HighlightableView { |
| public: |
| bool has_focus() const { return has_focus_; } |
| |
| // Get the view class associated with |this|. |
| virtual views::View* GetView() = 0; |
| |
| // Subclasses can override this for a custom focus ring shape. Defaults to |
| // return nullptr, which means the focus ring will use the |
| // HighlightPathGenerator used for clipping ink drops. |
| virtual std::unique_ptr<views::HighlightPathGenerator> |
| CreatePathGenerator(); |
| |
| // Shows the focus ring and triggers setting accessibility focus on the |
| // associated view. |
| virtual void PseudoFocus(); |
| |
| // Hides the focus ring. |
| virtual void PseudoBlur(); |
| |
| // Attempt to mimic a click on the associated view. Called by |
| // CaptureModeSession when it receives a space key event, as the button is |
| // not actuallly focused and will do nothing otherwise. Does nothing if the |
| // view is not a subclass of Button. |
| virtual void ClickView(); |
| |
| protected: |
| // TODO(crbug.com/1182456): This can result in multiple of these objects |
| // thinking they have focus if CaptureModeSessionFocusCycler does not call |
| // PseudoFocus or PseudoBlur properly. Investigate if there is a better |
| // approach. |
| bool has_focus_ = false; |
| |
| private: |
| // A convenience pointer to the focus ring, which is owned by the views |
| // hierarchy. |
| views::FocusRing* focus_ring_ = nullptr; |
| }; |
| |
| // An aura window that can be focused in capture session. |
| class HighlightableWindow : public HighlightableView, |
| public aura::WindowObserver { |
| public: |
| HighlightableWindow(aura::Window* window, CaptureModeSession* session); |
| HighlightableWindow(const HighlightableWindow&) = delete; |
| HighlightableWindow& operator=(const HighlightableWindow&) = delete; |
| ~HighlightableWindow() override; |
| |
| // HighlightableView: |
| views::View* GetView() override; |
| void PseudoFocus() override; |
| void PseudoBlur() override; |
| void ClickView() override; |
| |
| // aura::WindowObserver: |
| void OnWindowDestroying(aura::Window* window) override; |
| |
| private: |
| aura::Window* const window_; |
| CaptureModeSession* const session_; |
| }; |
| |
| explicit CaptureModeSessionFocusCycler(CaptureModeSession* session); |
| CaptureModeSessionFocusCycler(const CaptureModeSessionFocusCycler&) = delete; |
| CaptureModeSessionFocusCycler& operator=( |
| const CaptureModeSessionFocusCycler&) = delete; |
| ~CaptureModeSessionFocusCycler() override; |
| |
| // Advances the focus by simulating focus on a view, or updating the |
| // CaptureModeSession to draw focus on elements which are not associated with |
| // a view. The order is as follows: |
| // 1) Capture mode type and source: (Screenshot/recording) and |
| // (fullscreen/region/window) on the capture bar. |
| // 2) Region selection area: If visible. |
| // 3) Capture/record button: If visible. |
| // 4) Settings menu: If visible. |
| // 5) Settings and close button: On the capture bar. |
| // This should be called by CaptureModeSession when it receives a VKEY_TAB. |
| void AdvanceFocus(bool reverse); |
| |
| // Removes focus. Called by CaptureModeSession when it receives a VKEY_ESC, or |
| // when the state has changed such that a currently focus item is invalid |
| // (i.e. switching from region mode to windowed mode makes a focused selection |
| // or capture button invalid). |
| void ClearFocus(); |
| |
| // Returns true if anything has focus. |
| bool HasFocus() const; |
| |
| // Called when the CaptureModeSession receives a VKEY_SPACE event. Returns |
| // true if the focused view should take the event; when this happens the |
| // CaptureModeSession should not handle the event. |
| bool OnSpacePressed(); |
| |
| // Returns true if the current focus group is associated with the UI used for |
| // displaying a region. |
| bool RegionGroupFocused() const; |
| |
| // Returns true if the current focus group is associated with capture bar, |
| // otherwise returns false. |
| bool CaptureBarFocused() const; |
| |
| // Returns true if the current focus is on capture label, otherwise returns |
| // false. |
| bool CaptureLabelFocused() const; |
| |
| // Gets the current fine tune position for drawing the focus rects/rings on |
| // the session's layer. Returns FineTunePosition::kNone if focus is on another |
| // group. |
| FineTunePosition GetFocusedFineTunePosition() const; |
| |
| // Called when the capture label widget is made visible or hidden, or changes |
| // states. If the label button is visible, it should be on the a11y annotation |
| // cycle, otherwise it should be removed from the a11y annotation cycle. |
| void OnCaptureLabelWidgetUpdated(); |
| |
| // Called when the settings menu is created. Starts observing the settings |
| // menu. |
| void OnSettingsMenuWidgetCreated(); |
| |
| // views::WidgetObserver: |
| void OnWidgetClosing(views::Widget* widget) override; |
| |
| private: |
| friend class CaptureModeSessionTestApi; |
| |
| // Removes the focus ring from the current focused item if possible. Does not |
| // alter |current_focus_group_| or |focus_index_|. |
| void ClearCurrentVisibleFocus(); |
| |
| // Get the next group in the focus order. |
| FocusGroup GetNextGroup(bool reverse) const; |
| |
| // Returns the current focus group list. It will be one of |
| // `groups_for_fullscreen_`, `groups_for_region_` and `groups_for_window_`, |
| // depending on the current capture source. |
| const std::vector<FocusGroup>& GetCurrentGroupList() const; |
| |
| // Returns true if the given `group` is available inside the focus group list |
| // returned from GetCurrentGroupList(). |
| bool IsGroupAvailable(FocusGroup group) const; |
| |
| // Returns the number of elements in a particular group. |
| size_t GetGroupSize(FocusGroup group) const; |
| |
| // Returns the items of a particular |group|. Returns an empty array for the |
| // kSelection group as the items in that group do not have associated views. |
| std::vector<HighlightableView*> GetGroupItems(FocusGroup group) const; |
| |
| // Updates the capture mode widgets so that accessibility features can |
| // traverse between our widgets. |
| void UpdateA11yAnnotation(); |
| |
| views::Widget* GetSettingsMenuWidget() const; |
| |
| // Returns the window which is supposed to be set as the a11y override window |
| // for accessibility controller according to the `current_focus_group_`. |
| aura::Window* GetA11yOverrideWindow() const; |
| |
| // Returns true if there's a focused view in the given `views`, otherwise |
| // returns false. In the meanwhile, updates `focus_index_` according to the |
| // index of the current focused view to ensure it is up to date. |
| bool FindFocusedViewAndUpdateFocusIndex( |
| std::vector<HighlightableView*> views); |
| |
| // Highlights the corresponding HighlightableWindow first before moving the |
| // focus on the items inside. This happens when current focus group is |
| // `kCaptureWindow`, we need to focus the window first to update it as the |
| // current selected window. Thus the camera preview can be shown inside the |
| // updated selected window. |
| void MaybeFocusHighlightableWindow( |
| const std::vector<HighlightableView*>& current_views); |
| |
| // The current focus group and focus index. |
| FocusGroup current_focus_group_ = FocusGroup::kNone; |
| size_t focus_index_ = 0u; |
| |
| // Focusable groups for each capture source. |
| const std::vector<FocusGroup> groups_for_fullscreen_; |
| const std::vector<FocusGroup> groups_for_region_; |
| const std::vector<FocusGroup> groups_for_window_; |
| |
| // Highlightable windows of the focus group `kCaptureWindow`. Windows opened |
| // after the session starts will not be included. |
| std::map<aura::Window*, std::unique_ptr<HighlightableWindow>> |
| highlightable_windows_; |
| |
| // The session that owns |this|. Guaranteed to be non null for the lifetime of |
| // |this|. |
| CaptureModeSession* session_; |
| |
| // Accessibility features will focus on whatever window is returned by |
| // GetA11yOverrideWindow(). Once `this` goes out of scope, the a11y override |
| // window is set to null. |
| std::unique_ptr<ScopedA11yOverrideWindowSetter> scoped_a11y_overrider_; |
| |
| base::ScopedObservation<views::Widget, views::WidgetObserver> |
| settings_menu_widget_observeration_{this}; |
| |
| // True if the settings menu was opened via clicking space when the settings |
| // button had focus. In this case, advancing focus will focus into the |
| // settings menu. Otherwise, advancing focus with the settings menu open will |
| // close the settings menu. |
| bool settings_menu_opened_with_keyboard_nav_ = false; |
| }; |
| |
| } // namespace ash |
| |
| #endif // ASH_CAPTURE_MODE_CAPTURE_MODE_SESSION_FOCUS_CYCLER_H_ |