diff --git a/src/10-Core/Wtq/Configuration/OffScreenLocation.cs b/src/10-Core/Wtq/Configuration/OffScreenLocation.cs
index 822554c..1b68b24 100644
--- a/src/10-Core/Wtq/Configuration/OffScreenLocation.cs
+++ b/src/10-Core/Wtq/Configuration/OffScreenLocation.cs
@@ -5,6 +5,11 @@ namespace Wtq.Configuration;
///
public enum OffScreenLocation
{
+ ///
+ /// Used for detecting serialization issues.
+ ///
+ None = 0,
+
///
/// Above the screen.
///
@@ -14,4 +19,14 @@ public enum OffScreenLocation
/// Below the screen.
///
Below,
+
+ ///
+ /// Left of the screen.
+ ///
+ Left,
+
+ ///
+ /// Right of the screen.
+ ///
+ Right,
}
\ No newline at end of file
diff --git a/src/10-Core/Wtq/Configuration/WtqAppOptions.cs b/src/10-Core/Wtq/Configuration/WtqAppOptions.cs
index f055227..4d80a42 100644
--- a/src/10-Core/Wtq/Configuration/WtqAppOptions.cs
+++ b/src/10-Core/Wtq/Configuration/WtqAppOptions.cs
@@ -60,6 +60,9 @@ public sealed class WtqAppOptions
///
public TaskBarIconVisibility? TaskbarIconVisibility { get; set; }
+ ///
+ public IEnumerable? OffScreenLocations { get; init; }
+
///
public int? VerticalOffset { get; set; }
diff --git a/src/10-Core/Wtq/Configuration/WtqOptions.cs b/src/10-Core/Wtq/Configuration/WtqOptions.cs
index e77765a..959fc9e 100644
--- a/src/10-Core/Wtq/Configuration/WtqOptions.cs
+++ b/src/10-Core/Wtq/Configuration/WtqOptions.cs
@@ -1,3 +1,5 @@
+using static Wtq.Configuration.OffScreenLocation;
+
namespace Wtq.Configuration;
///
@@ -123,6 +125,14 @@ public int AnimationDurationMsWhenSwitchingApps
public TaskBarIconVisibility TaskBarIconVisibility { get; init; }
= TaskBarIconVisibility.AlwaysHidden;
+ ///
+ /// When moving an app off the screen, WTQ looks for an empty space to move the window to.
+ /// Depending on your monitor setup, this may be above the screen, but switches to below if another monitor exists there.
+ /// By default, WTQ looks for empty space in this order: Above, Below, Left, Right.
+ ///
+ public IEnumerable OffScreenLocations { get; init; }
+ = [Above, Below, Left, Right];
+
///
/// How much room to leave between the top of the terminal and the top of the screen, in pixels.
/// Defaults to "0".
@@ -185,6 +195,13 @@ public TaskBarIconVisibility GetTaskbarIconVisibilityForApp(WtqAppOptions opts)
return opts.TaskbarIconVisibility ?? TaskBarIconVisibility;
}
+ public IEnumerable GetOffScreenLocationsForApp(WtqAppOptions opts)
+ {
+ Guard.Against.Null(opts);
+
+ return opts.OffScreenLocations ?? OffScreenLocations;
+ }
+
public float GetVerticalOffsetForApp(WtqAppOptions opts)
{
Guard.Against.Null(opts);
diff --git a/src/10-Core/Wtq/Services/WtqAppToggleService.cs b/src/10-Core/Wtq/Services/WtqAppToggleService.cs
index 614dbe4..1fdd5e5 100644
--- a/src/10-Core/Wtq/Services/WtqAppToggleService.cs
+++ b/src/10-Core/Wtq/Services/WtqAppToggleService.cs
@@ -1,3 +1,5 @@
+using static Wtq.Configuration.OffScreenLocation;
+
namespace Wtq.Services;
///
@@ -20,18 +22,22 @@ public async Task ToggleOnAsync(WtqApp app, ToggleModifiers mods)
// Animation duration.
var durationMs = GetDurationMs(mods);
- // Screen bounds.
+ // All available screen rects.
+ var screenRects = await _screenInfoProvider.GetScreenRectsAsync().NoCtx();
+
+ // Get rect of the screen where the app currently is.
var screenRect = await GetTargetScreenRectAsync(app).NoCtx();
- // Source & target bounds.
- var windowRectSrc = GetOffScreenWindowRect(app, screenRect);
+ // Source & target rects.
+ var windowRectSrc = GetOffScreenWindowRect(app, screenRect, screenRects);
var windowRectDst = GetOnScreenWindowRect(app, screenRect);
- // Move window.
_log.LogDebug("ToggleOn app '{App}' from '{From}' to '{To}'", app, windowRectSrc, windowRectDst);
+ // Resize window.
await app.ResizeWindowAsync(windowRectDst.Size).NoCtx();
+ // Move window.
await _tween
.AnimateAsync(
src: windowRectSrc.Location,
@@ -50,24 +56,29 @@ public async Task ToggleOffAsync(WtqApp app, ToggleModifiers mods)
// Animation duration.
var durationMs = GetDurationMs(mods);
- // Screen bounds.
+ // All available screen rects.
+ var screenRects = await _screenInfoProvider.GetScreenRectsAsync().NoCtx();
+
+ // Get rect of the screen where the app currently is.
var screenRect = await app.GetScreenRectAsync().NoCtx();
- // Source & target bounds.
+ // Source & target rects.
var windowRectSrc = await app.GetWindowRectAsync().NoCtx();
- var windowRectDst = GetOffScreenWindowRect(app, screenRect);
+ var windowRectDst = GetOffScreenWindowRect(app, screenRect, screenRects);
_log.LogDebug("ToggleOff app '{App}' from '{From}' to '{To}'", app, windowRectSrc, windowRectDst);
+ // Resize window.
await app.ResizeWindowAsync(windowRectDst.Size).NoCtx();
+ // Move window.
await _tween
.AnimateAsync(
- windowRectSrc.Location,
- windowRectDst.Location,
- durationMs,
- _opts.CurrentValue.AnimationTypeToggleOff,
- app.MoveWindowAsync)
+ src: windowRectSrc.Location,
+ dst: windowRectDst.Location,
+ durationMs: durationMs,
+ animType: _opts.CurrentValue.AnimationTypeToggleOff,
+ move: app.MoveWindowAsync)
.NoCtx();
}
@@ -133,18 +144,73 @@ private Rectangle GetOnScreenWindowRect(WtqApp app, Rectangle screenRect)
///
/// Get the position rect a window should be when off-screen.
///
- private Rectangle GetOffScreenWindowRect(WtqApp app, Rectangle screenRect)
+ private Rectangle GetOffScreenWindowRect(
+ WtqApp app,
+ Rectangle currScreenRect,
+ Rectangle[] screenRects)
+ {
+ Guard.Against.Null(app);
+ Guard.Against.Null(screenRects);
+
+ // Get the app's current window rectangle.
+ var windowRect = GetOnScreenWindowRect(app, currScreenRect);
+
+ // Get possible rectangles to move the app to.
+ var targetRects = GetOffScreenWindowRects(app, windowRect, currScreenRect);
+
+ // Return first target rectangle that does not overlap with a screen.
+ var targetRect = targetRects
+ .FirstOrDefault(r => !screenRects.Any(scr => scr.IntersectsWith(r)));
+
+ return !targetRect.IsEmpty ? targetRect : targetRects[0];
+ }
+
+ ///
+ /// Returns a set of s, each a possible off-screen position for the to move to.
+ /// The list is ordered by , as specified in the settings.
+ ///
+ private Rectangle[] GetOffScreenWindowRects(
+ WtqApp app,
+ Rectangle windowRect,
+ Rectangle screenRect)
{
Guard.Against.Null(app);
+ Guard.Against.Null(windowRect);
+ Guard.Against.Null(screenRect);
+
+ var margin = 100;
- var windowRect = GetOnScreenWindowRect(app, screenRect);
+ return _opts.CurrentValue
+ .GetOffScreenLocationsForApp(app.Options)
+ .Select(dir => dir switch
+ {
+ Above or None => windowRect with
+ {
+ // Top of the screen, minus height of the app window.
+ Y = screenRect.Y - windowRect.Height - margin,
+ },
+
+ Below => windowRect with
+ {
+ // Bottom of the screen.
+ Y = screenRect.Y + screenRect.Height + margin,
+ },
- windowRect.Y
- = screenRect.Y // Top of the screen (which can be negative, when on the non-primary screen).
- - windowRect.Height // Minus height of the app window.
- - 100; // Minus a little margin.
+ Left => windowRect with
+ {
+ // Left of the screen, minus width of the app window.
+ X = screenRect.X - windowRect.Width - margin,
+ },
+
+ Right => windowRect with
+ {
+ // Right of the screen, plus width of the app window.
+ X = screenRect.X + screenRect.Width + windowRect.Width + margin,
+ },
- return windowRect;
+ _ => throw new WtqException("Unknown toggle direction."),
+ })
+ .ToArray();
}
///
diff --git a/src/10-Core/Wtq/Utils/WtqTween.cs b/src/10-Core/Wtq/Utils/WtqTween.cs
index 6990486..c34b034 100644
--- a/src/10-Core/Wtq/Utils/WtqTween.cs
+++ b/src/10-Core/Wtq/Utils/WtqTween.cs
@@ -49,11 +49,9 @@ public async Task AnimateAsync(
var linearProgress = sinceStartMs / durationMs;
var progress = (float)animFunc(linearProgress);
- var current = MathUtils.Lerp(src, dst, progress);
-
// TODO: Currently, we don't need to move on the X-axis, and there's a little bit of jitter, where X seems to lerp around a bit.
- // Find out why and fix that, then remove this.
- current.X = dst.X;
+ // Find out why and fix that.
+ var current = MathUtils.Lerp(src, dst, progress);
await move(current).NoCtx();