1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
ash / wm / splitview / split_view_divider.h [blame]
// 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_