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
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
ash / wm / splitview / split_view_controller.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_CONTROLLER_H_
#define ASH_WM_SPLITVIEW_SPLIT_VIEW_CONTROLLER_H_
#include <limits>
#include <memory>
#include <optional>
#include <vector>
#include "ash/accessibility/accessibility_observer.h"
#include "ash/ash_export.h"
#include "ash/public/cpp/keyboard/keyboard_controller_observer.h"
#include "ash/shell_observer.h"
#include "ash/wm/overview/overview_metrics.h"
#include "ash/wm/overview/overview_observer.h"
#include "ash/wm/overview/overview_types.h"
#include "ash/wm/splitview/layout_divider_controller.h"
#include "ash/wm/splitview/split_view_divider.h"
#include "ash/wm/splitview/split_view_types.h"
#include "ash/wm/window_state_observer.h"
#include "ash/wm/wm_event.h"
#include "ash/wm/wm_metrics.h"
#include "base/containers/flat_map.h"
#include "base/memory/raw_ptr.h"
#include "base/observer_list.h"
#include "base/time/time.h"
#include "ui/aura/window_observer.h"
#include "ui/display/display.h"
#include "ui/display/display_observer.h"
#include "ui/wm/public/activation_change_observer.h"
namespace display {
enum class TabletState;
} // namespace display
namespace gfx {
class Point;
} // namespace gfx
namespace ui {
class Layer;
class PresentationTimeRecorder;
} // namespace ui
namespace ash {
class AutoSnapController;
class OverviewSession;
class SplitViewOverviewSession;
class SplitViewMetricsController;
class SplitViewObserver;
class SplitViewOverviewSessionTest;
// `SplitViewController` controls what the window snapping behaviors should be
// in different UI modes (clamshell UI mode and tablet UI mode), and how the
// window snapping interacts with the overview mode. There is an instance for
// each display.
// **Clamshell Mode
// **Faster Split Screen:** When a window is snapped to one side of the
// screen from certain snap action sources, this class automatically triggers
// the window overview on the other side, allowing the user to select a second
// window to complete the split screen. Once a second window is chosen, a
// `SnapGroup` is created to manage the window group.
// **Split Screen from Overview:** If the user starts snapping a window while
// already in the window overview, this class will display the overview on the
// other side of the screen to allow selection of the second window for the
// split screen. However, if there are no other windows available to snap,
// the overview will not be shown.
// **Other Cases:** This class does not handle snapping behavior for other
// scenarios in clamshell mode.
//
// **Tablet Mode:**
// The window snapping behaviors in tablet mode will be managed by
// `SplitViewController`. On one window snapped in the tablet mode, the overview
// session will show up on the other half of the screen for user to choose the
// to-be-snapped window. And the user has to explicitly end the split view mode.
// TODO(xdai): Make it work for multi-display non mirror environment.
class ASH_EXPORT SplitViewController : public aura::WindowObserver,
public WindowStateObserver,
public ShellObserver,
public OverviewObserver,
public display::DisplayObserver,
public AccessibilityObserver,
public ash::KeyboardControllerObserver,
public wm::ActivationChangeObserver,
public LayoutDividerController {
public:
// Why splitview was ended.
enum class EndReason {
kNormal = 0,
kHomeLauncherPressed,
kUnsnappableWindowActivated,
kActiveUserChanged,
kWindowDragStarted,
kExitTabletMode,
// Splitview is being ended due to a change in Virtual Desks, such as
// switching desks or removing a desk.
kDesksChange,
// Splitview is being ended due to the `root_window_` is destroyed and the
// SplitViewController is being destroyed.
kRootWindowDestroyed,
// Splitview is being ended due to a Snap Group being added.
kSnapGroups,
};
// The behaviors of split view are very different when in tablet mode and in
// clamshell mode. In tablet mode, split view mode will stay active until the
// user explicitly ends it (e.g., by pressing home launcher, or long pressing
// the overview button, or sliding the divider bar to the edge, etc). However,
// in clamshell mode, there is no divider bar, and split view mode only stays
// active during overview snapping, i.e., it's only possible that split view
// is active when overview is active. Once the user has selected two windows
// to snap to both side of the screen, split view mode is no longer active.
enum class SplitViewType {
kTabletType = 0,
kClamshellType,
};
enum class State {
kNoSnap,
kPrimarySnapped,
kSecondarySnapped,
kBothSnapped,
};
// 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,
};
// Gets the |SplitViewController| for the root window of |window|. |window| is
// important in clamshell mode. In tablet mode, the working assumption for now
// is mirror mode (or just one display), and so |window| can be almost any
// window and it does not matter. For code that only applies to tablet mode,
// you may simply use the primary root (see |Shell::GetPrimaryRootWindow|).
// The user actually can go to the display settings while in tablet mode and
// choose extend; we just are not yet trying to support it really well.
static SplitViewController* Get(const aura::Window* window);
explicit SplitViewController(aura::Window* root_window);
SplitViewController(const SplitViewController&) = delete;
SplitViewController& operator=(const SplitViewController&) = delete;
~SplitViewController() override;
aura::Window* root_window() { return root_window_; }
aura::Window* primary_window() { return primary_window_; }
aura::Window* secondary_window() { return secondary_window_; }
State state() const { return state_; }
SnapPosition default_snap_position() const { return default_snap_position_; }
SplitViewDivider* split_view_divider() { return &split_view_divider_; }
EndReason end_reason() const { return end_reason_; }
SplitViewMetricsController* split_view_metrics_controller() {
return split_view_metrics_controller_.get();
}
aura::Window* to_be_activated_window() { return to_be_activated_window_; }
// Returns the divider position of the split view divider.
int GetDividerPosition() const;
// Returns true if the divider is resizing (not animating) in tablet mode
// split view, or between two windows in Snap Groups.
bool IsResizingWithDivider() const;
// Returns true if split view mode is active. Please see SplitViewType above
// to see the difference between tablet mode and clamshell mode splitview
// mode.
bool InSplitViewMode() const;
bool InClamshellSplitViewMode() const;
bool InTabletSplitViewMode() const;
// Checks the following criteria:
// 1. Split view mode is supported (see |ShouldAllowSplitView|).
// 2. |window| can be activated (see |wm::CanActivateWindow|).
// 3. The |WindowState| of |window| can snap (see |WindowState::CanSnap|).
// 4. |window|'s minimum size, if any, fits into the left or top with the
// default divider position. (If the work area length is odd, then the
// right or bottom will be one pixel larger.)
// See also the `DCHECK`s in `SnapWindow()`.
bool CanSnapWindow(aura::Window* window, float snap_ratio) const;
// Returns true if `window` can keep snapped with the current snap ratio.
bool CanKeepCurrentSnapRatio(aura::Window* window) const;
// Returns true if partial overview should start on the opposite side of the
// screen on the given `window` snapped.
bool WillStartPartialOverview(aura::Window* window) const;
// Returns the snap ratio (if valid) for `window` depending on the default
// window. Returns null if `window` cannot get snapped. If there is no default
// window, it will check if `window` can be half snapped. Otherwise, it checks
// if `window` can be snapped opposite of the default window. If default
// window is 2/3 and `window` cannot be snapped 1/3 but can be snapped 1/2, it
// will be snapped 1/2 unless default window cannot be snapped 1/2.
std::optional<float> ComputeAutoSnapRatio(aura::Window* window);
// Snap `window` in the split view at `snap_position`. It will send snap
// WMEvent to `window` and rely on WindowState to do the actual work to
// change window state and bounds. Note this function does not guarantee
// `window` can be snapped in the split view (e.g., an ARC++ window may
// decide to ignore the state change request), and split view state will only
// be updated after the window state is changed to the desired snap window
// state. If `activate_window` is true, `window` will be activated after being
// snapped in splitview. Please note if `activate_window` is false, it's still
// possible that `window` will be activated after being snapped, see
// `to_be_activated_window_` for details. `snap_ratio` may be provided if the
// window requests a specific snap ratio, i.e. during clamshell <-> tablet
// transition. `snap_action_source` specifies the source for this snap event.
void SnapWindow(aura::Window* window,
SnapPosition snap_position,
WindowSnapActionSource snap_action_source =
WindowSnapActionSource::kNotSpecified,
bool activate_window = false,
float snap_ratio = chromeos::kDefaultSnapRatio);
// This is called by `BaseState` or `TabletModeWindowState` when receiving a
// snap WMEvent i.e. WM_EVENT_SNAP_PRIMARY or WM_EVENT_SNAP_SECONDARY. `this`
// will decide if this window needs to be snapped in split view.
// `snap_action_source` specifies the source for this snap event.
void OnSnapEvent(aura::Window* window,
WMEventType event_type,
WindowSnapActionSource snap_action_source);
// Attaches the to-be-snapped `window` to split view at `snap_position`. It
// will try to remove the `window` from the overview grid first if `window`
// is contained in the overview grid. We'll add a finishing touch to the snap
// animation of `window` if split view mode is not already active, and if
// `window` is not minimized and has a non-identity transform.
// `snap_action_source` specifies the source for this snap event.
void AttachToBeSnappedWindow(aura::Window* window,
SnapPosition snap_position,
WindowSnapActionSource snap_action_source);
// |position| should be |LEFT| or |RIGHT|, and this function returns
// |primary_window_| or |secondary_window_| accordingly.
aura::Window* GetSnappedWindow(SnapPosition position);
// Returns the default snapped window. It's the window that remains open until
// the split mode ends. It's decided by |default_snap_position_|. E.g., If
// |default_snap_position_| equals LEFT, then the default snapped window is
// |primary_window_|. All the other window will open on the right side.
aura::Window* GetDefaultSnappedWindow();
// Gets snapped bounds based on |snap_position|, the side of the screen to
// snap to, and |snap_ratio|, the ratio of the screen that the snapped window
// will occupy, adjusted to accommodate the minimum size of
// |window_for_minimum_size| if |window_for_minimum_size| is not null.
gfx::Rect GetSnappedWindowBoundsInParent(
SnapPosition snap_position,
aura::Window* window_for_minimum_size,
float snap_ratio);
// Returns true if we should consider the width of the split view divider.
bool ShouldConsiderDivider() const;
// Returns true during the divider snap animation.
bool IsDividerAnimating() const;
// Ends the split view mode, from which point `SplitViewController` no longer
// manages the window(s).
void EndSplitView(EndReason end_reason = EndReason::kNormal);
// Returns true if `window` is a snapped window in splitview.
bool IsWindowInSplitView(const aura::Window* window) const;
// Returns true if `window` is in a transitinal state which means that
// `SplitViewController` has already changed its internal snapped state for
// `window` but the snapped state has not been applied to `window`'s window
// state yet. The transional state can be happen in some clients (e.g. ARC
// app) which handle window states asynchronously.
bool IsWindowInTransitionalState(const aura::Window* window) const;
// Called when the overview button tray has been long pressed. Enters
// splitview mode if the active window is snappable. Also enters overview mode
// if device is not currently in overview mode.
void OnOverviewButtonTrayLongPressed(const gfx::Point& event_location);
// Called when a window (either it's browser window or an app window) start/
// end being dragged.
void OnWindowDragStarted(aura::Window* dragged_window);
void OnWindowDragEnded(aura::Window* dragged_window,
SnapPosition desired_snap_position,
const gfx::Point& last_location_in_screen,
WindowSnapActionSource snap_action_source);
void OnWindowDragCanceled();
// Computes the snap position for a dragged window, based on the last
// mouse/gesture event location. Called by |EndWindowDragImpl| when
// desired_snap_position is |NONE| but because split view is already active,
// the dragged window needs to be snapped anyway.
SnapPosition ComputeSnapPosition(const gfx::Point& last_location_in_screen);
// In portrait mode split view, if the virtual keyboard occludes the input
// field in the bottom window. The bottom window will be pushed up above the
// virtual keyboard. In this case, we allow window state to set bounds for
// snapped window.
bool BoundsChangeIsFromVKAndAllowed(aura::Window* window) const;
void AddObserver(SplitViewObserver* observer);
void RemoveObserver(SplitViewObserver* observer);
// aura::WindowObserver:
void OnWindowPropertyChanged(aura::Window* window,
const void* key,
intptr_t old) override;
void OnWindowBoundsChanged(aura::Window* window,
const gfx::Rect& old_bounds,
const gfx::Rect& new_bounds,
ui::PropertyChangeReason reason) override;
void OnWindowDestroyed(aura::Window* window) override;
void OnWindowRemovingFromRootWindow(aura::Window* window,
aura::Window* new_root) override;
// WindowStateObserver:
void OnPostWindowStateTypeChange(WindowState* window_state,
chromeos::WindowStateType old_type) override;
// ShellObserver:
void OnPinnedStateChanged(aura::Window* pinned_window) override;
// OverviewObserver:
void OnOverviewModeStarting() override;
void OnOverviewModeEnding(OverviewSession* overview_session) override;
void OnOverviewModeEnded() override;
// display::DisplayObserver:
void OnDisplaysRemoved(const display::Displays& removed_displays) override;
void OnDisplayMetricsChanged(const display::Display& display,
uint32_t metrics) override;
void OnDisplayTabletStateChanged(display::TabletState state) override;
// AccessibilityObserver:
void OnAccessibilityStatusChanged() override;
void OnAccessibilityControllerShutdown() override;
// KeyboardControllerObserver:
void OnKeyboardOccludedBoundsChanged(const gfx::Rect& screen_bounds) override;
// wm::ActivationChangeObserver:
void OnWindowActivated(ActivationReason reason,
aura::Window* gained_active,
aura::Window* lost_active) override;
// LayoutDividerController:
aura::Window* GetRootWindow() const override;
void StartResizeWithDivider(const gfx::Point& location_in_screen) override;
void UpdateResizeWithDivider(const gfx::Point& location_in_screen) override;
bool EndResizeWithDivider(const gfx::Point& location_in_screen) override;
void OnResizeEnding() override;
void OnResizeEnded() override;
void SwapWindows() override;
gfx::Rect GetSnappedWindowBoundsInScreen(
SnapPosition snap_position,
aura::Window* window_for_minimum_size,
float snap_ratio,
bool account_for_divider_width) const override;
SnapPosition GetPositionOfSnappedWindow(
const aura::Window* window) const override;
static void SetUseFastResizeForTesting(bool val);
private:
friend class SplitViewControllerTest;
friend class SplitViewTestApi;
friend class SplitViewDivider;
friend class SplitViewOverviewSessionTest;
friend class SplitViewOverviewSession;
class DividerSnapAnimation;
class ToBeSnappedWindowsObserver;
// Reason that a snapped window is detached from the splitview.
enum class WindowDetachedReason {
kWindowMinimized,
kWindowDestroyed,
kWindowDragged,
kWindowFloated,
kWindowMovedToAnotherDisplay,
kAddedToSnapGroup,
};
// These functions return the snapped window in the specified snap position
// (left/top or right/bottom) based on the display's orientation.
//
// In primary screen orientation:
// - `GetPhysicallyLeftOrTopWindow()` returns the `primary_window_`;
// - `GetPhysicallyRightOrBottomWindow()` returns the `secondary_window_`.
//
// In non-primary screen orientation:
// - `GetPhysicallyLeftOrTopWindow()` returns the `secondary_window_`;
// - `GetPhysicallyRightOrBottomWindow()` returns the `primary_window_`.
aura::Window* GetPhysicallyLeftOrTopWindow();
aura::Window* GetPhysicallyRightOrBottomWindow();
// Starts observing |window|.
void StartObserving(aura::Window* window);
// Stop observing the window at associated with |snap_position|. Also updates
// shadows and sets |primary_window_| or |secondary_window_| to nullptr.
void StopObserving(SnapPosition snap_position);
// Updates split view state and notify its observer about the change.
void UpdateStateAndNotifyObservers();
// Notifies observers that the split view divider position has been changed.
void NotifyDividerPositionChanged();
// Notifies observers that the windows in split view is resized.
void NotifyWindowResized();
// Notifies observers that the windows are swappped.
void NotifyWindowSwapped();
// Updates the black scrim layer's bounds and opacity while dragging the
// divider. The opacity increases as the split divider gets closer to the edge
// of the screen.
void UpdateBlackScrim(const gfx::Point& location_in_screen);
// Updates the resize mode backdrop. This is drawn behind windows to ensure
// that the allotted space is always filled, even if the window itself hasn't
// resized yet.
void UpdateResizeBackdrop();
// Updates the bounds of the given snapped `window` in splitview.
void UpdateSnappedWindowBounds(aura::Window* window);
// Updates the bounds for the two snapped windows
void UpdateSnappedWindowsBounds();
// Updates the bounds for the snapped windows and divider.
// TODO(http://b/330567348): Consolidate these three functions and make sure
// they work properly behind the scenes.
void UpdateSnappedWindowsAndDividerBounds();
// Gets the position where the black scrim should show.
SnapPosition GetBlackScrimPosition(const gfx::Point& location_in_screen);
// Returns the closest fixed location to `divider_position`.
int GetClosestFixedDividerPosition(int divider_position);
// `StopSnapAnimation()` and notifies the `observers_` about the divider
// position change.
void StopAndShoveAnimatedDivider();
// Stops the divider animation and `SetDividerPosition()`.
void StopSnapAnimation();
// Returns true if we should end split view after resizing, i.e. the
// split view divider is at an edge of the work area.
bool ShouldEndSplitViewAfterResizingAtEdge();
// Ends split view if `ShouldEndSplitViewAfterResizingAtEdge()` returns true.
// Handles extra details associated with dragging the divider off the screen.
void EndSplitViewAfterResizingAtEdgeIfAppropriate();
// After resizing, if we should end split view mode, returns the window that
// needs to be activated. Returns nullptr if there is no such window.
aura::Window* GetActiveWindowAfterResizingUponExit();
// Called after a to-be-snapped window `window` got snapped. It updates the
// split view states and notifies observers about the change. It also restore
// the snapped window's transform if it's not identity and activate it. If
// `previous_state` is given and it is a floated window, attempt to snap the
// next MRU window if possible. `snap_action_source` specifies the source for
// this snap event.
void OnWindowSnapped(aura::Window* window,
std::optional<chromeos::WindowStateType> previous_state,
WindowSnapActionSource snap_action_source);
// If there are two snapped windows, closing/minimizing/tab-dragging one of
// them will open overview window grid on the closed/minimized/tab-dragged
// window side of the screen. If there is only one snapped windows, closing/
// minimizing/tab-dragging the snapped window will end split view mode and
// adjust the overview window grid bounds if the overview mode is active at
// that moment. |reason| specifies the reason that the snapped window is
// detached from splitview.
void OnSnappedWindowDetached(aura::Window* window,
WindowDetachedReason reason);
// Returns the closest ratio to the `current_ratio`. `current_ratio` is the
// the ratio between current divider position and the farthest position
// divider is allowed to end at.
float FindClosestPositionRatio(float current_ratio);
// Gets the divider optional position ratios. The divider can always be
// moved to the positions in `kFixedPositionRatios`. Whether the divider can
// be moved to `chromeos::kOneThirdSnapRatio` or
// `chromeos::kTwoThirdSnapRatio` depends on the minimum size of current
// snapped windows.
void ModifyPositionRatios(std::vector<float>& out_position_ratios);
// Restores |window| transform to identity transform if applicable.
void RestoreTransformIfApplicable(aura::Window* window);
// During resizing, it's possible that the resizing bounds of the snapped
// window is smaller than its minimum bounds, in this case we apply a
// translation to the snapped window to make it visually be placed outside of
// the workspace area.
void SetWindowsTransformDuringResizing();
// Restores the snapped windows transform to identity transform after
// resizing.
void RestoreWindowsTransformAfterResizing();
// Animates to |target_transform| for |window| and its transient descendants.
// |window| will be applied |start_transform| first and then animate to
// |target_transform|. Note |start_transform| and |end_transform| are for
// |window| and need to be adjusted for its transient child windows.
void SetTransformWithAnimation(aura::Window* window,
const gfx::Transform& start_transform,
const gfx::Transform& target_transform);
// Updates the |snapping_window_transformed_bounds_map_| on |window|. It
// should be called before trying to snap the window.
void UpdateSnappingWindowTransformedBounds(aura::Window* window);
// Inserts |window| into overview window grid if overview mode is active. Do
// nothing if overview mode is inactive at the moment.
void InsertWindowToOverview(aura::Window* window, bool animate = true);
// Finalizes and cleans up divider dragging/animating. Called when the divider
// snapping animation completes or is interrupted or totally skipped.
void EndResizeWithDividerImpl();
// Called from a timer during resizing. Facilitates switching between fast and
// normal tablet resizing modes.
void OnResizeTimer();
// Figures out which resize mode we should be using. This is based on the
// speed at which the divider is dragged.
void UpdateTabletResizeMode(base::TimeTicks event_time_ticks,
const gfx::Point& event_location);
// Called when the display tablet state is changed.
void OnTabletModeStarted();
void OnTabletModeEnding();
void OnTabletModeEnded();
// Called by `OnWindowDragEnded()` to do the actual work of finishing the
// window dragging. If `is_being_destroyed` equals true, the dragged window is
// to be destroyed, and SplitViewController should not try to put it in
// splitview. `snap_action_source` specifies the source for this snap event.
void EndWindowDragImpl(aura::Window* window,
bool is_being_destroyed,
SnapPosition desired_snap_position,
const gfx::Point& last_location_in_screen,
WindowSnapActionSource snap_action_source);
// Called by `SwapWindows()` to swap the window(s) if exist that occupy the
// `SnapPosition::kPrimary` and `SnapPosition::kSecondary`. The bounds of the
// window(s) will also be updated.
void SwapWindowsAndUpdateBounds();
// Root window the split view is in.
raw_ptr<aura::Window, DanglingUntriaged> root_window_;
// The current primary/secondary snapped window.
raw_ptr<aura::Window> primary_window_ = nullptr;
raw_ptr<aura::Window> secondary_window_ = nullptr;
// Observes the windows that are to be snapped in split screen.
std::unique_ptr<ToBeSnappedWindowsObserver> to_be_snapped_windows_observer_;
// Split view divider which is 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 horizontally or vertically, the
// windows will be resized either horizontally or vertically accordingly.
SplitViewDivider split_view_divider_;
// A black scrim layer that fades in over a window when its width drops under
// 1/3 of the width of the screen, increasing in opacity as the divider gets
// closer to the edge of the screen.
std::unique_ptr<ui::Layer> black_scrim_layer_;
// Backdrop layers that may be visible below windows when resizing.
std::unique_ptr<ui::Layer> left_resize_backdrop_layer_;
std::unique_ptr<ui::Layer> right_resize_backdrop_layer_;
// The closest position ratio of divider among kFixedPositionRatios,
// kOneThirdSnapRatio and kTwoThirdSnapRatio based on current
// `SplitViewDivider::divider_position_`. Used to update
// `SplitViewDivider::divider_position_` on work area changes.
// TODO(sophiewen): Move this variable to `SplitViewDivider`.
float divider_closest_ratio_ = std::numeric_limits<float>::quiet_NaN();
// The animation that animates the divider to a fixed position after resizing.
std::unique_ptr<DividerSnapAnimation> divider_snap_animation_;
// Current snap state.
State state_ = State::kNoSnap;
// The default snap position. It's decided by the first snapped window. If the
// first window was snapped left, then |default_snap_position_| equals LEFT,
// i.e., all the other windows will open snapped on the right side - and vice
// versa.
SnapPosition default_snap_position_ = SnapPosition::kNone;
// Whether the previous layout is right-side-up (see |IsLayoutPrimary|).
// Consistent with |IsLayoutPrimary|, |is_previous_layout_right_side_up_|
// is always true in clamshell mode. It is not really used in clamshell mode,
// but it is kept up to date in anticipation that future code changes could
// introduce a bug similar to https://crbug.com/1029181 which could be
// overlooked for years while occasionally irritating or confusing real users.
bool is_previous_layout_right_side_up_ = true;
// Stores the reason which cause splitview to end.
EndReason end_reason_ = EndReason::kNormal;
// Stores the overview start and enter/exit type.
std::optional<OverviewStartAction> overview_start_action_;
std::optional<OverviewEnterExitType> enter_exit_overview_type_;
// The time when splitview starts. Used for metric collection purpose.
base::Time splitview_start_time_;
// The map from a to-be-snapped window to its transformed bounds.
base::flat_map<aura::Window*, gfx::Rect>
snapping_window_transformed_bounds_map_;
base::ObserverList<SplitViewObserver>::Unchecked observers_;
// Records the presentation time of resize operation in tablet split view
// mode.
std::unique_ptr<ui::PresentationTimeRecorder> presentation_time_recorder_;
// Observes windows and performs auto snapping if needed.
std::unique_ptr<AutoSnapController> auto_snap_controller_;
// The metrics controller for the same root window.
std::unique_ptr<SplitViewMetricsController> split_view_metrics_controller_;
// Register for DisplayObserver callbacks.
display::ScopedDisplayObserver display_observer_{this};
// A pointer to the to-be-snapped window that will be activated after it's
// snapped in splitview. There can be two cases when this value can be
// non-nullptr, when SnapWindow() explicitly specifies the window needs to be
// activated, or when the to-be-snapped is from overview and was the active
// window before entering overview, so when it's snapped in splitview, it
// should remain to be the active window.
raw_ptr<aura::Window, DanglingUntriaged> to_be_activated_window_ = nullptr;
// The split view resize mode for tablet mode.
TabletResizeMode tablet_resize_mode_ = TabletResizeMode::kNormal;
// Accumulated drag distance, during a time interval.
int accumulated_drag_distance_ = 0;
base::TimeTicks accumulated_drag_time_ticks_;
// Used to potentially invoke `Resize()` during resizes. This is so that
// tablet resize mode can switch to normal mode (letting windows be resized)
// even if the divider isn't moved.
base::OneShotTimer resize_timer_;
// A flag indicates the window bounds is currently changed due to the virtual
// keyboard.
bool changing_bounds_by_vk_ = false;
};
} // namespace ash
#endif // ASH_WM_SPLITVIEW_SPLIT_VIEW_CONTROLLER_H_