| // Copyright 2017 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_WM_SPLITVIEW_SPLIT_VIEW_DIVIDER_H_ |
| #define ASH_WM_SPLITVIEW_SPLIT_VIEW_DIVIDER_H_ |
| |
| #include "ash/ash_export.h" |
| #include "base/memory/raw_ptr.h" |
| #include "base/scoped_multi_source_observation.h" |
| #include "ui/aura/window.h" |
| #include "ui/aura/window_observer.h" |
| #include "ui/display/display_observer.h" |
| #include "ui/views/widget/widget_observer.h" |
| #include "ui/wm/core/transient_window_observer.h" |
| |
| namespace gfx { |
| class Rect; |
| } // namespace gfx |
| |
| namespace views { |
| class Widget; |
| } // namespace views |
| |
| namespace ash { |
| |
| class LayoutDividerController; |
| class SnapGroupController; |
| class SplitViewDividerView; |
| |
| // Observes the windows in the split view and controls the stacking orders among |
| // the split view divider and its observed windows. The divider widget should |
| // always be placed above its observed windows to be able to receive events |
| // unless it's being dragged. |
| class ASH_EXPORT SplitViewDivider : public aura::WindowObserver, |
| public wm::TransientWindowObserver, |
| public display::DisplayObserver { |
| public: |
| // The split view resize behavior in tablet mode. The normal mode resizes |
| // windows on drag events. In the fast mode, windows are instead moved. A |
| // single drag "session" may involve both modes. |
| enum class TabletResizeMode { |
| kNormal, |
| kFast, |
| }; |
| |
| explicit SplitViewDivider(LayoutDividerController* controller); |
| SplitViewDivider(const SplitViewDivider&) = delete; |
| SplitViewDivider& operator=(const SplitViewDivider&) = delete; |
| ~SplitViewDivider() override; |
| |
| // static |
| // Returns the divider bounds in screen where `divider_position` is in the |
| // divider's root window's bounds. |
| static gfx::Rect GetDividerBoundsInScreen( |
| const gfx::Rect& work_area_bounds_in_screen, |
| bool landscape, |
| int divider_position, |
| bool is_dragging); |
| |
| views::Widget* divider_widget() { return divider_widget_; } |
| |
| int divider_position() const { return divider_position_; } |
| |
| bool target_visibility() const { return target_visibility_; } |
| |
| bool is_resizing_with_divider() const { return is_resizing_with_divider_; } |
| |
| // Does not consider any order of `observed_windows_`. Clients of the divider |
| // are responsible for maintaining the order themselves. |
| const aura::Window::Windows& observed_windows() const { |
| return observed_windows_; |
| } |
| const gfx::Point previous_event_location() const { |
| return previous_event_location_; |
| } |
| |
| // Returns the divider widget's native window, or nullptr if none exists. |
| aura::Window* GetDividerWindow(); |
| |
| // Returns true if the divider widget is created. |
| bool HasDividerWidget() const; |
| |
| bool IsDividerWidgetVisible() const; |
| |
| // Updates the divider's target visibility. |
| void SetVisible(bool visible); |
| |
| // Sets the divider's position in root window bounds, ensuring it meets the |
| // minimum window size requirement. |
| void SetDividerPosition(int divider_position); |
| |
| // Updates divider position while resizing, keeping it within allowed range. |
| void UpdateDividerPosition(const gfx::Point& location_in_screen); |
| |
| // Returns the root window of this. |
| aura::Window* GetRootWindow() const; |
| |
| // Resizing functions used when resizing with `split_view_divider_` in the |
| // tablet split view mode or clamshell mode when two windows are in a Snap |
| // Group. |
| void StartResizeWithDivider(const gfx::Point& location_in_screen); |
| void ResizeWithDivider(const gfx::Point& location_in_screen); |
| void EndResizeWithDivider(const gfx::Point& location_in_screen); |
| |
| // Finalizes and cleans up divider dragging/animating. Called when the divider |
| // snapping animation completes or is interrupted or totally skipped, or by |
| // external events (split view ending, tablet mode ending, etc.). |
| void CleanUpWindowResizing(); |
| |
| // Updates `divider_widget_`'s bounds. |
| void UpdateDividerBounds(); |
| |
| // Calculates the divider's expected bounds according to the divider's |
| // position. |
| gfx::Rect GetDividerBoundsInScreen(bool is_dragging); |
| |
| // Provides visual feedback by adjusting `divider_widget_` bounds in response |
| // to user hover or drag interactions (enlarged on interaction, thin default). |
| void EnlargeOrShrinkDivider(bool should_enlarge); |
| |
| // Sets the adjustability of the divider bar. Unadjustable divider does not |
| // receive event and the divider bar view is not visible. When the divider is |
| // moved for the virtual keyboard, the divider will be set unadjustable. |
| void SetAdjustable(bool adjustable); |
| |
| // Returns true if the divider bar is adjustable. |
| bool IsAdjustable() const; |
| |
| void MaybeAddObservedWindow(aura::Window* window); |
| void MaybeRemoveObservedWindow(aura::Window* window); |
| |
| // Called by the LayoutDividerController on a keyboard bounds change, where |
| // `work_area` is the total work area and `y` is the vertical position of the |
| // bottom window. |
| void OnKeyboardOccludedBoundsChangedInPortrait(const gfx::Rect& work_area, |
| int y); |
| |
| // Called when a window tab(s) are being dragged around the workspace. The |
| // divider should be placed beneath the dragged window during dragging and be |
| // placed above the dragged window when drag is completed. |
| void OnWindowDragStarted(aura::Window* dragged_window); |
| void OnWindowDragEnded(); |
| |
| // Calls the delegate to swap the windows. |
| void SwapWindows(); |
| |
| // aura::WindowObserver: |
| void OnWindowDestroying(aura::Window* window) override; |
| void OnWindowBoundsChanged(aura::Window* window, |
| const gfx::Rect& old_bounds, |
| const gfx::Rect& new_bounds, |
| ui::PropertyChangeReason reason) override; |
| void OnWindowStackingChanged(aura::Window* window) override; |
| void OnWindowVisibilityChanged(aura::Window* window, bool visible) override; |
| |
| // wm::TransientWindowObserver: |
| void OnTransientChildAdded(aura::Window* window, |
| aura::Window* transient) override; |
| void OnTransientChildRemoved(aura::Window* window, |
| aura::Window* transient) override; |
| |
| // display::DisplayObserver: |
| void OnDisplayMetricsChanged(const display::Display& display, |
| uint32_t metrics) override; |
| |
| SplitViewDividerView* divider_view_for_testing() { return divider_view_; } |
| |
| private: |
| class SplitViewDividerWidget; |
| |
| // Refreshes the divider's state by creating or closing the divider widget if |
| // needed, and updating its visibility, bounds, and stacking order as needed. |
| // If `observed_windows_changed` is true, this will refresh the divider |
| // position and stacking order. |
| void RefreshDividerState(bool observed_windows_changed); |
| |
| void CreateDividerWidget(int divider_position); |
| void CloseDividerWidget(); |
| |
| // Returns the `TargetVisibility()` of the `divider_widget_`, which directly |
| // assesses the window's target visibility, regardless of the visibility of |
| // its parent's layer. |
| bool GetActualTargetVisibility() const; |
| |
| // Refreshes the stacking order of the `divider_widget_` to be right on top of |
| // the `observed_windows_` and reparents the split view divider to be on the |
| // same parent container of the above window of the `observed_windows_` while |
| // not dragging. The `divider_widget` will be temporarily stacked below the |
| // window being dragged and reparented if the window being dragged has |
| // different parent with the divider widget native window. |
| void RefreshStackingOrder(); |
| |
| void StartObservingTransientChild(aura::Window* transient); |
| void StopObservingTransientChild(aura::Window* transient); |
| |
| // Gets the expected end drag position for `window` depending on current |
| // screen orientation and split divider position. |
| gfx::Point GetEndDragLocationInScreen(aura::Window* window) const; |
| |
| // Finalizes and cleans up after stopping dragging the divider bar to resize |
| // snapped windows. |
| void FinishWindowResizing(); |
| |
| const raw_ptr<LayoutDividerController> controller_; |
| |
| // The distance between the origin of `divider_widget_` and the origin |
| // of the current display's work area in screen coordinates, which essentially |
| // makes it relative to the divider widget's root window's work area. |
| // |<--- divider_position_ --->| |
| // --------------------------------------------------------------- |
| // | | | | |
| // | primary window | | secondary window | |
| // | | | | |
| // --------------------------------------------------------------- |
| // Initialized as -1 before `divider_widget_` is created and shown. |
| int divider_position_ = -1; |
| |
| // True if the divider widget should be shown, false otherwise. |
| bool target_visibility_ = false; |
| |
| // Split view divider widget. It's a black bar stretching from one edge of the |
| // screen to the other, containing a small white drag bar in the middle. As |
| // the user presses on it and drag it to left or right, the left and right |
| // window will be resized accordingly. |
| raw_ptr<views::Widget> divider_widget_ = nullptr; |
| |
| // The contents view of the `divider_widget_`. |
| raw_ptr<SplitViewDividerView> divider_view_ = nullptr; |
| |
| // This variable indicates the dragging state and records the window being |
| // dragged which will be used to refresh the stacking order of the |
| // `divider_widget_` to be stacked below the `dragged_window_`. |
| raw_ptr<aura::Window> dragged_window_ = nullptr; |
| |
| // The window(s) observed by the divider which will be updated upon adding or |
| // removing window. Note this does not guarantee any order about which of the |
| // `observed_windows_` is primary or secondary snapped. |
| aura::Window::Windows observed_windows_; |
| |
| // If true, skip refreshing the divider state. This is used to avoid recursive |
| // updates when updating the divider state. |
| bool is_refreshing_state_ = false; |
| |
| // If true, skip the stacking order update. This is used to avoid recursive |
| // update when updating the stacking order. |
| bool is_refreshing_stacking_order_ = false; |
| |
| // Tracks observed transient windows. |
| base::ScopedMultiSourceObservation<aura::Window, aura::WindowObserver> |
| transient_windows_observations_{this}; |
| |
| // True when the divider is being dragged (not during its snap animation). |
| bool is_resizing_with_divider_ = false; |
| |
| // The location of the previous mouse/gesture event in screen coordinates. |
| gfx::Point previous_event_location_; |
| |
| // True *while* a resize event is being processed. |
| bool processing_resize_event_ = false; |
| |
| display::ScopedDisplayObserver display_observer_{this}; |
| }; |
| |
| } // namespace ash |
| |
| #endif // ASH_WM_SPLITVIEW_SPLIT_VIEW_DIVIDER_H_ |