// 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 if `kSnapGroup` is enabled.
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_