Skip to content

Commit

Permalink
Toggle direction (#148)
Browse files Browse the repository at this point in the history
  • Loading branch information
flyingpie authored Nov 6, 2024
1 parent 5d986c0 commit cf08311
Show file tree
Hide file tree
Showing 5 changed files with 122 additions and 23 deletions.
15 changes: 15 additions & 0 deletions src/10-Core/Wtq/Configuration/OffScreenLocation.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,11 @@ namespace Wtq.Configuration;
/// </summary>
public enum OffScreenLocation
{
/// <summary>
/// Used for detecting serialization issues.
/// </summary>
None = 0,

/// <summary>
/// Above the screen.
/// </summary>
Expand All @@ -14,4 +19,14 @@ public enum OffScreenLocation
/// Below the screen.
/// </summary>
Below,

/// <summary>
/// Left of the screen.
/// </summary>
Left,

/// <summary>
/// Right of the screen.
/// </summary>
Right,
}
3 changes: 3 additions & 0 deletions src/10-Core/Wtq/Configuration/WtqAppOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,9 @@ public sealed class WtqAppOptions
/// <inheritdoc cref="WtqOptions.TaskBarIconVisibility"/>
public TaskBarIconVisibility? TaskbarIconVisibility { get; set; }

/// <inheritdoc cref="WtqOptions.OffScreenLocations"/>
public IEnumerable<OffScreenLocation>? OffScreenLocations { get; init; }

/// <inheritdoc cref="WtqOptions.VerticalOffset"/>
public int? VerticalOffset { get; set; }

Expand Down
17 changes: 17 additions & 0 deletions src/10-Core/Wtq/Configuration/WtqOptions.cs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
using static Wtq.Configuration.OffScreenLocation;

namespace Wtq.Configuration;

/// <summary>
Expand Down Expand Up @@ -123,6 +125,14 @@ public int AnimationDurationMsWhenSwitchingApps
public TaskBarIconVisibility TaskBarIconVisibility { get; init; }
= TaskBarIconVisibility.AlwaysHidden;

/// <summary>
/// When moving an app off the screen, WTQ looks for an empty space to move the window to.<br/>
/// Depending on your monitor setup, this may be above the screen, but switches to below if another monitor exists there.<br/>
/// By default, WTQ looks for empty space in this order: Above, Below, Left, Right.
/// </summary>
public IEnumerable<OffScreenLocation> OffScreenLocations { get; init; }
= [Above, Below, Left, Right];

/// <summary>
/// How much room to leave between the top of the terminal and the top of the screen, in pixels.<br/>
/// Defaults to "0".
Expand Down Expand Up @@ -185,6 +195,13 @@ public TaskBarIconVisibility GetTaskbarIconVisibilityForApp(WtqAppOptions opts)
return opts.TaskbarIconVisibility ?? TaskBarIconVisibility;
}

public IEnumerable<OffScreenLocation> GetOffScreenLocationsForApp(WtqAppOptions opts)
{
Guard.Against.Null(opts);

return opts.OffScreenLocations ?? OffScreenLocations;
}

public float GetVerticalOffsetForApp(WtqAppOptions opts)
{
Guard.Against.Null(opts);
Expand Down
104 changes: 85 additions & 19 deletions src/10-Core/Wtq/Services/WtqAppToggleService.cs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
using static Wtq.Configuration.OffScreenLocation;

namespace Wtq.Services;

/// <inheritdoc cref="IWtqAppToggleService"/>
Expand All @@ -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,
Expand All @@ -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();
}

Expand Down Expand Up @@ -133,18 +144,73 @@ private Rectangle GetOnScreenWindowRect(WtqApp app, Rectangle screenRect)
/// <summary>
/// Get the position rect a window should be when off-screen.
/// </summary>
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];
}

/// <summary>
/// Returns a set of <see cref="Rectangle"/>s, each a possible off-screen position for the <paramref name="windowRect"/> to move to.<br/>
/// The list is ordered by <see cref="OffScreenLocation"/>, as specified in the settings.
/// </summary>
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();
}

/// <summary>
Expand Down
6 changes: 2 additions & 4 deletions src/10-Core/Wtq/Utils/WtqTween.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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();

Expand Down

0 comments on commit cf08311

Please sign in to comment.