Fix window snapping when crossing between displays
Signed-off-by: Hans Kokx <hans.d.kokx@gmail.com>
This commit is contained in:
@@ -0,0 +1,59 @@
|
||||
{
|
||||
"pins" : [
|
||||
{
|
||||
"identity" : "dkcamera",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/zhangao0086/DKCamera",
|
||||
"state" : {
|
||||
"branch" : "master",
|
||||
"revision" : "5c691d11014b910aff69f960475d70e65d9dcc96"
|
||||
}
|
||||
},
|
||||
{
|
||||
"identity" : "dkimagepickercontroller",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/zhangao0086/DKImagePickerController",
|
||||
"state" : {
|
||||
"branch" : "4.3.9",
|
||||
"revision" : "0bdfeacefa308545adde07bef86e349186335915"
|
||||
}
|
||||
},
|
||||
{
|
||||
"identity" : "dkphotogallery",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/zhangao0086/DKPhotoGallery",
|
||||
"state" : {
|
||||
"branch" : "master",
|
||||
"revision" : "311c1bc7a94f1538f82773a79c84374b12a2ef3d"
|
||||
}
|
||||
},
|
||||
{
|
||||
"identity" : "sdwebimage",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/SDWebImage/SDWebImage",
|
||||
"state" : {
|
||||
"revision" : "2de3a496eaf6df9a1312862adcfd54acd73c39c0",
|
||||
"version" : "5.21.7"
|
||||
}
|
||||
},
|
||||
{
|
||||
"identity" : "swiftygif",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/kirualex/SwiftyGif.git",
|
||||
"state" : {
|
||||
"revision" : "4430cbc148baa3907651d40562d96325426f409a",
|
||||
"version" : "5.4.5"
|
||||
}
|
||||
},
|
||||
{
|
||||
"identity" : "tocropviewcontroller",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/TimOliver/TOCropViewController",
|
||||
"state" : {
|
||||
"revision" : "d4a6d8100f4b886fdbc8ae399bf144ff3e9afb7e",
|
||||
"version" : "2.8.0"
|
||||
}
|
||||
}
|
||||
],
|
||||
"version" : 2
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
{
|
||||
"pins" : [
|
||||
{
|
||||
"identity" : "dkcamera",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/zhangao0086/DKCamera",
|
||||
"state" : {
|
||||
"branch" : "master",
|
||||
"revision" : "5c691d11014b910aff69f960475d70e65d9dcc96"
|
||||
}
|
||||
},
|
||||
{
|
||||
"identity" : "dkimagepickercontroller",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/zhangao0086/DKImagePickerController",
|
||||
"state" : {
|
||||
"branch" : "4.3.9",
|
||||
"revision" : "0bdfeacefa308545adde07bef86e349186335915"
|
||||
}
|
||||
},
|
||||
{
|
||||
"identity" : "dkphotogallery",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/zhangao0086/DKPhotoGallery",
|
||||
"state" : {
|
||||
"branch" : "master",
|
||||
"revision" : "311c1bc7a94f1538f82773a79c84374b12a2ef3d"
|
||||
}
|
||||
},
|
||||
{
|
||||
"identity" : "sdwebimage",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/SDWebImage/SDWebImage",
|
||||
"state" : {
|
||||
"revision" : "2de3a496eaf6df9a1312862adcfd54acd73c39c0",
|
||||
"version" : "5.21.7"
|
||||
}
|
||||
},
|
||||
{
|
||||
"identity" : "swiftygif",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/kirualex/SwiftyGif.git",
|
||||
"state" : {
|
||||
"revision" : "4430cbc148baa3907651d40562d96325426f409a",
|
||||
"version" : "5.4.5"
|
||||
}
|
||||
},
|
||||
{
|
||||
"identity" : "tocropviewcontroller",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/TimOliver/TOCropViewController",
|
||||
"state" : {
|
||||
"revision" : "d4a6d8100f4b886fdbc8ae399bf144ff3e9afb7e",
|
||||
"version" : "2.8.0"
|
||||
}
|
||||
}
|
||||
],
|
||||
"version" : 2
|
||||
}
|
||||
+139
-72
@@ -22,6 +22,8 @@ import "src/windows/window_args.dart";
|
||||
|
||||
const Size _mainSize = Size(275, 116);
|
||||
const Size _shadeSize = Size(275, 14);
|
||||
// Toggle to enable extra drag diagnostics (kept off by default).
|
||||
const bool _dragDiagnostics = false;
|
||||
|
||||
Future<void> main(List<String> args) async {
|
||||
WidgetsFlutterBinding.ensureInitialized();
|
||||
@@ -151,6 +153,13 @@ class _WinampRootState extends State<WinampRoot> {
|
||||
/// when it reports `subReady`.
|
||||
final Map<DockWindow, Rect> _pendingSubBounds = {};
|
||||
|
||||
/// The latest desired top-left per window, coalesced by [_flushMoves].
|
||||
final Map<DockWindow, Offset> _pendingMoves = {};
|
||||
Timer? _moveFlushTimer;
|
||||
|
||||
/// How often pending window moves are flushed to the native side.
|
||||
static const Duration _moveFlushInterval = Duration(milliseconds: 16);
|
||||
|
||||
/// Saved top-left positions of the sub-windows, loaded at startup.
|
||||
final Map<SubWindowType, (double, double)> _savedSubPositions = {};
|
||||
|
||||
@@ -200,6 +209,7 @@ class _WinampRootState extends State<WinampRoot> {
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_moveFlushTimer?.cancel();
|
||||
_windowsSubscription?.cancel();
|
||||
_player.removeListener(_onPlayerChanged);
|
||||
_skins.removeListener(_onSkinChanged);
|
||||
@@ -635,19 +645,34 @@ class _WinampRootState extends State<WinampRoot> {
|
||||
|
||||
/// Applies a window rect natively: the main window positions itself;
|
||||
/// sub-windows are moved over their control channel.
|
||||
///
|
||||
/// Moves are coalesced: each call only records the latest position and a
|
||||
/// single flush applies them up to [_moveFlushInterval] later. A fast drag
|
||||
/// emits hundreds of positions per second; sending them all would saturate
|
||||
/// the platform thread (a sub-window move crosses three channel hops) and
|
||||
/// the windows would lag the cursor and catch up in a cascade of jumps.
|
||||
void _moveWindow(DockWindow window, Rect rect) {
|
||||
if (window == DockWindow.main) {
|
||||
windowManager.setPosition(rect.topLeft);
|
||||
return;
|
||||
_pendingMoves[window] = rect.topLeft;
|
||||
_moveFlushTimer ??= Timer(_moveFlushInterval, _flushMoves);
|
||||
}
|
||||
|
||||
void _flushMoves() {
|
||||
_moveFlushTimer = null;
|
||||
final moves = Map.of(_pendingMoves);
|
||||
_pendingMoves.clear();
|
||||
for (final entry in moves.entries) {
|
||||
final window = entry.key;
|
||||
final topLeft = entry.value;
|
||||
if (window == DockWindow.main) {
|
||||
windowManager.setPosition(topLeft);
|
||||
} else {
|
||||
_invokeSub(
|
||||
window,
|
||||
"setBounds",
|
||||
{"x": topLeft.dx, "y": topLeft.dy},
|
||||
);
|
||||
}
|
||||
}
|
||||
_invokeSub(
|
||||
window,
|
||||
"setBounds",
|
||||
{
|
||||
"x": rect.left,
|
||||
"y": rect.top,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
void _persistRects(Map<DockWindow, Rect> rects) {
|
||||
@@ -817,6 +842,7 @@ class _MainWindowDragController implements WindowDragController {
|
||||
bool _begun = false;
|
||||
Offset? _last;
|
||||
Offset? _relGrab;
|
||||
bool? _useNative;
|
||||
|
||||
@override
|
||||
void start(ui.Offset globalPosition) {
|
||||
@@ -825,6 +851,7 @@ class _MainWindowDragController implements WindowDragController {
|
||||
_begun = false;
|
||||
_last = null;
|
||||
_relGrab = globalPosition;
|
||||
_useNative = null; // undecided until first fetch
|
||||
debugPrint(
|
||||
"main drag start (window-local: "
|
||||
"${globalPosition.dx.toStringAsFixed(1)},"
|
||||
@@ -840,61 +867,96 @@ class _MainWindowDragController implements WindowDragController {
|
||||
|
||||
Future<void> _fetch(ui.Offset localPoint) async {
|
||||
final seq = ++_seq;
|
||||
try {
|
||||
final result = await winampChannel.invokeMethod<Map<dynamic, dynamic>>(
|
||||
"getMouseLocation",
|
||||
);
|
||||
if (!_active || seq != _seq || result == null) return;
|
||||
final point = Offset(
|
||||
(result["x"] as num).toDouble(),
|
||||
(result["y"] as num).toDouble(),
|
||||
);
|
||||
if (!_begun) {
|
||||
_begun = true;
|
||||
_state._dock.beginMainDrag(point);
|
||||
debugPrint(
|
||||
"main drag begun at "
|
||||
"${point.dx.toStringAsFixed(1)},${point.dy.toStringAsFixed(1)}",
|
||||
// Decide once whether to use native absolute coordinates or the local
|
||||
// relative fallback for the whole drag. This avoids mid-drag source
|
||||
// switching that causes visible jumps when the native channel is
|
||||
// intermittently available.
|
||||
if (_useNative == null) {
|
||||
try {
|
||||
final result = await winampChannel.invokeMethod<Map<dynamic, dynamic>>(
|
||||
"getMouseLocation",
|
||||
);
|
||||
}
|
||||
if (point == _last) return;
|
||||
_last = point;
|
||||
_state._dock.mainDragTo(point);
|
||||
debugPrint(
|
||||
"main drag to "
|
||||
"${point.dx.toStringAsFixed(1)},${point.dy.toStringAsFixed(1)}",
|
||||
);
|
||||
} catch (_) {
|
||||
// Relative fallback. The pointer's window-local coordinate shifts as the
|
||||
// window follows the cursor, so grabbing the delta since the drag began
|
||||
// and applying it to the current position tracks the cursor exactly.
|
||||
if (!_active || seq != _seq) return;
|
||||
final rect = _state._dock.rectOf(DockWindow.main);
|
||||
final relGrab = _relGrab;
|
||||
if (rect == null || relGrab == null) return;
|
||||
if (!_begun) {
|
||||
// Grab = pointer − window top-left = the window-local grab offset.
|
||||
_begun = true;
|
||||
_state._dock.beginMainDrag(rect.topLeft + relGrab);
|
||||
debugPrint(
|
||||
"main drag begun (relative) at "
|
||||
"${rect.topLeft.dx + relGrab.dx},"
|
||||
"${rect.topLeft.dy + relGrab.dy}",
|
||||
if (!_active || seq != _seq || result == null) return;
|
||||
_useNative = true;
|
||||
final point = Offset(
|
||||
(result["x"] as num).toDouble(),
|
||||
(result["y"] as num).toDouble(),
|
||||
);
|
||||
if (!_begun) {
|
||||
_begun = true;
|
||||
_state._dock.beginMainDrag(point);
|
||||
if (_dragDiagnostics)
|
||||
debugPrint("main drag begun at $point (native)");
|
||||
}
|
||||
if (point != _last) {
|
||||
_last = point;
|
||||
_state._dock.mainDragTo(point);
|
||||
if (_dragDiagnostics) debugPrint("main drag to $point (native)");
|
||||
}
|
||||
return;
|
||||
} catch (_) {
|
||||
_useNative = false;
|
||||
if (_dragDiagnostics)
|
||||
debugPrint("main drag: native unavailable, using relative mode");
|
||||
// fall through to relative path below
|
||||
}
|
||||
_state._dock.moveAttached(
|
||||
DockWindow.main,
|
||||
DockLogic.moveBy(
|
||||
rect,
|
||||
localPoint.dx - relGrab.dx,
|
||||
localPoint.dy - relGrab.dy,
|
||||
),
|
||||
);
|
||||
debugPrint(
|
||||
"main drag to (relative) "
|
||||
"${localPoint.dx - relGrab.dx},${localPoint.dy - relGrab.dy}",
|
||||
);
|
||||
}
|
||||
|
||||
if (_useNative == true) {
|
||||
// Native already chosen; attempt to fetch latest but ignore errors.
|
||||
try {
|
||||
final result = await winampChannel.invokeMethod<Map<dynamic, dynamic>>(
|
||||
"getMouseLocation",
|
||||
);
|
||||
if (!_active || seq != _seq || result == null) return;
|
||||
final point = Offset(
|
||||
(result["x"] as num).toDouble(),
|
||||
(result["y"] as num).toDouble(),
|
||||
);
|
||||
if (!_begun) {
|
||||
_begun = true;
|
||||
_state._dock.beginMainDrag(point);
|
||||
if (_dragDiagnostics)
|
||||
debugPrint("main drag begun at $point (native)");
|
||||
}
|
||||
if (point == _last) return;
|
||||
_last = point;
|
||||
_state._dock.mainDragTo(point);
|
||||
if (_dragDiagnostics) debugPrint("main drag to $point (native)");
|
||||
} catch (_) {
|
||||
// Ignore transient failures once native was chosen; avoid switching
|
||||
// to relative mid-drag to prevent jumps.
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Relative fallback path (chosen because native was unavailable at drag
|
||||
// start). Use the window-local grab offset consistently for the whole
|
||||
// drag so coordinates don't oscillate.
|
||||
if (!_active || seq != _seq) return;
|
||||
final rect = _state._dock.rectOf(DockWindow.main);
|
||||
final relGrab = _relGrab;
|
||||
if (rect == null || relGrab == null) return;
|
||||
if (!_begun) {
|
||||
_begun = true;
|
||||
_state._dock.beginMainDrag(rect.topLeft + relGrab);
|
||||
if (_dragDiagnostics)
|
||||
debugPrint(
|
||||
"main drag begun (relative) at ${rect.topLeft.dx + relGrab.dx},${rect.topLeft.dy + relGrab.dy}",
|
||||
);
|
||||
}
|
||||
_state._dock.moveAttached(
|
||||
DockWindow.main,
|
||||
DockLogic.moveBy(
|
||||
rect,
|
||||
localPoint.dx - relGrab.dx,
|
||||
localPoint.dy - relGrab.dy,
|
||||
),
|
||||
);
|
||||
if (_dragDiagnostics)
|
||||
debugPrint(
|
||||
"main drag to (relative) ${localPoint.dx - relGrab.dx},${localPoint.dy - relGrab.dy}",
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -908,17 +970,22 @@ class _MainWindowDragController implements WindowDragController {
|
||||
/// [_active] is false), then ends the drag.
|
||||
Future<void> _finalize() async {
|
||||
try {
|
||||
final result = await winampChannel.invokeMethod<Map<dynamic, dynamic>>(
|
||||
"getMouseLocation",
|
||||
);
|
||||
if (result != null) {
|
||||
final point = Offset(
|
||||
(result["x"] as num).toDouble(),
|
||||
(result["y"] as num).toDouble(),
|
||||
// Only query native if we chose native mode for this drag. If we
|
||||
// started in relative mode, skip the native fetch to avoid switching
|
||||
// sources at the last moment.
|
||||
if (_useNative != false) {
|
||||
final result = await winampChannel.invokeMethod<Map<dynamic, dynamic>>(
|
||||
"getMouseLocation",
|
||||
);
|
||||
if (point != _last) {
|
||||
_last = point;
|
||||
_state._dock.mainDragTo(point);
|
||||
if (result != null) {
|
||||
final point = Offset(
|
||||
(result["x"] as num).toDouble(),
|
||||
(result["y"] as num).toDouble(),
|
||||
);
|
||||
if (point != _last) {
|
||||
_last = point;
|
||||
_state._dock.mainDragTo(point);
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (_) {
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
import "dart:async";
|
||||
import "dart:ui";
|
||||
|
||||
import "package:flutter/foundation.dart";
|
||||
|
||||
import "dock_logic.dart";
|
||||
|
||||
/// The skin sizes (in unscaled logical pixels) used to compute default window
|
||||
@@ -80,6 +83,7 @@ class DockController {
|
||||
|
||||
Offset? _grab;
|
||||
Set<DockWindow>? _attached;
|
||||
Timer? _settleTimer;
|
||||
|
||||
/// Records the grab offset between the pointer and the main window's
|
||||
/// top-left for a main-window drag (Ui.cpp `do_titlebar` `case 1`).
|
||||
@@ -91,12 +95,21 @@ class DockController {
|
||||
/// [pointer] is the cursor's global screen position in the same coordinate
|
||||
/// space as the window rects, so the grab stays constant while dragging.
|
||||
void beginMainDrag(Offset pointer) {
|
||||
// Cancel any pending post-drag settle when a new drag begins.
|
||||
_settleTimer?.cancel();
|
||||
_settleTimer = null;
|
||||
final rect = _rects[DockWindow.main];
|
||||
if (rect == null) return;
|
||||
_grab = pointer - rect.topLeft;
|
||||
_attached = DockLogic.dockedBelow(
|
||||
rects: _rects,
|
||||
dragged: DockWindow.main,
|
||||
snapLen: snapLen,
|
||||
);
|
||||
debugPrint(
|
||||
"dock beginMainDrag ptr=${pointer.dx.toStringAsFixed(1)},"
|
||||
"${pointer.dy.toStringAsFixed(1)} main=${rect.left.toStringAsFixed(1)},"
|
||||
"${rect.top.toStringAsFixed(1)} attached=${_attached!}",
|
||||
);
|
||||
}
|
||||
|
||||
@@ -115,7 +128,8 @@ class DockController {
|
||||
Set<DockWindow>? attached,
|
||||
}) {
|
||||
attached =
|
||||
attached ?? DockLogic.dockedBelow(rects: _rects, dragged: driven);
|
||||
attached ??
|
||||
DockLogic.dockedBelow(rects: _rects, dragged: driven, snapLen: snapLen);
|
||||
final prevDriven = _rects[driven];
|
||||
if (prevDriven == null) {
|
||||
_rects[driven] = target;
|
||||
@@ -148,10 +162,19 @@ class DockController {
|
||||
return;
|
||||
}
|
||||
|
||||
// Always snap the driven window when snapping is enabled; however, when
|
||||
// actively dragging we avoid the attached-group snap/clamp propagation
|
||||
// which can fight the OS during cross-display transitions and cause
|
||||
// temporary pinning.
|
||||
if (snapEnabled) {
|
||||
mwr = DockLogic.snapToScreen(mwr, screenRect, snapLen: snapLen);
|
||||
}
|
||||
mwr = DockLogic.clampVisible(mwr, screenRect);
|
||||
final dragging = _grab != null;
|
||||
|
||||
// During an active main-drag allow the window to be moved more freely
|
||||
// across display boundaries by reducing the clamp's minVisible amount.
|
||||
final minVisible = dragging ? 1.0 : DockLogic.defaultMinVisible;
|
||||
mwr = DockLogic.clampVisible(mwr, screenRect, minVisible: minVisible);
|
||||
final delta = mwr.topLeft - prevDriven.topLeft;
|
||||
for (final window in attached) {
|
||||
if (window == driven) continue;
|
||||
@@ -162,7 +185,13 @@ class DockController {
|
||||
);
|
||||
}
|
||||
|
||||
if (snapEnabled) {
|
||||
final doAttachedSnap = snapEnabled && !dragging;
|
||||
if (dragging && snapEnabled) {
|
||||
debugPrint(
|
||||
"dock moveAttached: skipping attached-window snap while dragging",
|
||||
);
|
||||
}
|
||||
if (doAttachedSnap) {
|
||||
// Keep the attached windows on screen, propagating any shift to the
|
||||
// whole group (the original `config_keeponscreen` behavior).
|
||||
for (final window in attached) {
|
||||
@@ -201,7 +230,23 @@ class DockController {
|
||||
}
|
||||
}
|
||||
|
||||
final overTop = screenRect.top - mwr.top;
|
||||
if (overTop > 0) {
|
||||
mwr = Rect.fromLTWH(mwr.left, screenRect.top, mwr.width, mwr.height);
|
||||
for (final window in attached) {
|
||||
if (window == driven) continue;
|
||||
moved[window] = DockLogic.moveBy(moved[window]!, 0, overTop);
|
||||
}
|
||||
}
|
||||
|
||||
final updates = <DockWindow, Rect>{driven: mwr, ...moved};
|
||||
debugPrint(
|
||||
"dock moveAttached $driven target=${target.left.toStringAsFixed(1)},"
|
||||
"${target.top.toStringAsFixed(1)} prev=${prevDriven.left.toStringAsFixed(1)},"
|
||||
"${prevDriven.top.toStringAsFixed(1)} mwr=${mwr.left.toStringAsFixed(1)},"
|
||||
"${mwr.top.toStringAsFixed(1)} eq=${updates[DockWindow.eq]?.top.toStringAsFixed(1)} "
|
||||
"pl=${updates[DockWindow.pl]?.top.toStringAsFixed(1)}",
|
||||
);
|
||||
for (final entry in updates.entries) {
|
||||
final old = _rects[entry.key];
|
||||
_rects[entry.key] = entry.value;
|
||||
@@ -233,6 +278,20 @@ class DockController {
|
||||
_grab = null;
|
||||
_attached = null;
|
||||
persistRects(_rects);
|
||||
// Schedule a short delayed settle so attached-window snapping/keep-on-
|
||||
// screen propagation can run after the OS finishes any cross-display
|
||||
// transition. This restores the canonical snapped positions without
|
||||
// interfering with the interactive drag.
|
||||
_settleTimer?.cancel();
|
||||
_settleTimer = Timer(const Duration(milliseconds: 120), () {
|
||||
_settleTimer = null;
|
||||
final mainRect = _rects[DockWindow.main];
|
||||
if (mainRect == null) return;
|
||||
// Re-run the attached move with snapping enabled and no active grab so
|
||||
// propagation and keep-on-screen behavior apply now.
|
||||
moveAttached(DockWindow.main, mainRect, attached: null);
|
||||
persistRects(_rects);
|
||||
});
|
||||
}
|
||||
|
||||
// --- Sub-window drag (reported by the sub-window engine) -------------------
|
||||
@@ -282,7 +341,8 @@ class DockController {
|
||||
if (window == changed) continue;
|
||||
final oldW = oldRects[window];
|
||||
if (oldW == null) continue;
|
||||
if (oldW.top != oldR.bottom || !_horizontalOverlap(oldW, oldR)) {
|
||||
if ((oldW.top - oldR.bottom).abs() > snapLen ||
|
||||
!_horizontalOverlap(oldW, oldR)) {
|
||||
continue;
|
||||
}
|
||||
final newW = Rect.fromLTWH(
|
||||
|
||||
@@ -21,7 +21,8 @@ abstract final class DockLogic {
|
||||
/// horizontal overlap of the two rects.
|
||||
static int windowAttached(Rect a, Rect b) {
|
||||
var r = 0;
|
||||
if (b.right == a.left || b.left == a.right) {
|
||||
if (_closeTo(b.right, a.left, defaultSnapLen) ||
|
||||
_closeTo(b.left, a.right, defaultSnapLen)) {
|
||||
if (_inRange(a.top, b.top, b.bottom) ||
|
||||
_inRange(a.bottom, b.top, b.bottom) ||
|
||||
_inRange(b.top, a.top, a.bottom) ||
|
||||
@@ -29,7 +30,8 @@ abstract final class DockLogic {
|
||||
r |= 1;
|
||||
}
|
||||
}
|
||||
if (b.bottom == a.top || b.top == a.bottom) {
|
||||
if (_closeTo(b.bottom, a.top, defaultSnapLen) ||
|
||||
_closeTo(b.top, a.bottom, defaultSnapLen)) {
|
||||
if (_inRange(b.left, a.left, a.right) ||
|
||||
_inRange(b.right, a.left, a.right) ||
|
||||
_inRange(a.left, b.left, b.right) ||
|
||||
@@ -229,6 +231,7 @@ abstract final class DockLogic {
|
||||
static Set<DockWindow> dockedBelow({
|
||||
required Map<DockWindow, Rect> rects,
|
||||
required DockWindow dragged,
|
||||
double snapLen = defaultSnapLen,
|
||||
}) {
|
||||
final attached = <DockWindow>{dragged};
|
||||
var changed = true;
|
||||
@@ -239,7 +242,7 @@ abstract final class DockLogic {
|
||||
for (final other in rects.entries) {
|
||||
if (other.key == entry.key) continue;
|
||||
if (attached.contains(other.key)) continue;
|
||||
if (other.value.top == entry.value.bottom &&
|
||||
if (_closeTo(other.value.top, entry.value.bottom, snapLen) &&
|
||||
_horizontalOverlap(other.value, entry.value)) {
|
||||
attached.add(other.key);
|
||||
changed = true;
|
||||
@@ -269,7 +272,8 @@ abstract final class DockLogic {
|
||||
if (w == changed) continue;
|
||||
if (!oldRects.containsKey(w)) continue;
|
||||
final oldW = oldRects[w]!;
|
||||
if (oldW.top != oldR.bottom || !_horizontalOverlap(oldW, oldR)) {
|
||||
if (!_closeTo(oldW.top, oldR.bottom, defaultSnapLen) ||
|
||||
!_horizontalOverlap(oldW, oldR)) {
|
||||
continue;
|
||||
}
|
||||
final newW = Rect.fromLTWH(
|
||||
|
||||
@@ -143,16 +143,23 @@ class AppDelegate: FlutterAppDelegate {
|
||||
}
|
||||
result(nil)
|
||||
case "getScreenBounds":
|
||||
// The visible frame of the primary screen (menu bar and dock excluded),
|
||||
// converted to window_manager's top-left origin coordinates.
|
||||
if let screen = NSScreen.screens.first {
|
||||
let f = screen.frame
|
||||
let v = screen.visibleFrame
|
||||
// The union of every screen's visible frame (menu bar and dock
|
||||
// excluded), converted to window_manager's top-left origin coordinates.
|
||||
// The primary screen's full frame is the origin reference, so displays
|
||||
// arranged above it map to negative Y and displays below it to Y beyond
|
||||
// the primary's height — the drag clamp/snap then span the whole desktop.
|
||||
let screens = NSScreen.screens
|
||||
if !screens.isEmpty {
|
||||
let f = screens[0].frame
|
||||
var union = screens[0].visibleFrame
|
||||
for screen in screens.dropFirst() {
|
||||
union = union.union(screen.visibleFrame)
|
||||
}
|
||||
result([
|
||||
"x": v.origin.x,
|
||||
"y": f.height - v.origin.y - v.height,
|
||||
"width": v.width,
|
||||
"height": v.height,
|
||||
"x": union.origin.x,
|
||||
"y": f.height - union.origin.y - union.height,
|
||||
"width": union.width,
|
||||
"height": union.height,
|
||||
])
|
||||
} else {
|
||||
result(nil)
|
||||
|
||||
Reference in New Issue
Block a user