aboutsummaryrefslogtreecommitdiffstats
path: root/window-switcher/Expose.js
diff options
context:
space:
mode:
Diffstat (limited to 'window-switcher/Expose.js')
-rw-r--r--window-switcher/Expose.js59
1 files changed, 38 insertions, 21 deletions
diff --git a/window-switcher/Expose.js b/window-switcher/Expose.js
index 2b14e4e..4120907 100644
--- a/window-switcher/Expose.js
+++ b/window-switcher/Expose.js
@@ -16,35 +16,24 @@
// [{x, y, w, h}] inside a W by H screen.
//
// Windows are seeded on a golden-angle spiral, the most recent at the centre,
-// then pushed apart until nothing overlaps, then fitted with one scale, so
-// their sizes stay relative to each other. Seeding from the windows' real
-// positions was tried first and degenerates here: every window fills its
-// monitor, so they all start in one pile, and pushing a pile apart makes a
-// line that still overlapped after 300 passes and filled under 15% of the
-// screen.
+// then pulled toward the centre and pushed apart again for a number of
+// rounds, so the scatter is a cluster rather than a loose spiral, then
+// fitted with one scale, so their sizes stay relative to each other.
+// Seeding from the windows' real positions was tried first and degenerates
+// here: every window fills its monitor, so they all start in one pile, and
+// pushing a pile apart makes a line that still overlapped after 300 passes
+// and filled under 15% of the screen.
const GAP = 60; // real pixels kept between two windows
const PASSES = 300;
const FILL = 0.92; // of the screen, each way
const MAX_SCALE = 0.5; // a lone window is not a full-screen mirror
const SPREAD = 0.45; // spiral step, as a fraction of the tallest window
+const ROUNDS = 30;
+const PULL = 0.85; // centre distance kept per gravity round
const GOLDEN = 137.5 * Math.PI / 180;
-function layout(rects, W, H) {
- if (rects.length === 0) return [];
- const aspect = W / H;
- const step = SPREAD * Math.max(...rects.map(o => o.h));
-
- // The spiral is stretched by the screen's aspect, so the cluster comes
- // out the shape of the screen rather than round.
- const r = rects.map((o, k) => {
- const rad = step * Math.sqrt(k), t = k * GOLDEN;
- return { x: rad * Math.cos(t) * aspect - o.w / 2,
- y: rad * Math.sin(t) - o.h / 2, w: o.w, h: o.h };
- });
-
- // ponytail: all pairs every pass, O(n^2); fine for this desk's ~20
- // windows, a spatial grid if it ever is not.
+function separate(r, W, H) {
for (let pass = 0; pass < PASSES; pass++) {
let moved = false;
for (let i = 0; i < r.length; i++) for (let j = i + 1; j < r.length; j++) {
@@ -67,6 +56,34 @@ function layout(rects, W, H) {
if (!moved) break;
}
+}
+
+function layout(rects, W, H) {
+ if (rects.length === 0) return [];
+ const aspect = W / H;
+ const step = SPREAD * Math.max(...rects.map(o => o.h));
+
+ // The spiral is stretched by the screen's aspect, so the cluster comes
+ // out the shape of the screen rather than round.
+ const r = rects.map((o, k) => {
+ const rad = step * Math.sqrt(k), t = k * GOLDEN;
+ return { x: rad * Math.cos(t) * aspect - o.w / 2,
+ y: rad * Math.sin(t) - o.h / 2, w: o.w, h: o.h };
+ });
+
+ // Gravity: everything is pulled toward the centre, then pushed apart
+ // again, until the cluster stops shrinking. Separation alone leaves
+ // the spiral's gaps in place and the scatter reads as loose.
+ // ponytail: all pairs every pass, O(n^2); fine for this desk's ~20
+ // windows, a spatial grid if it ever is not.
+ for (let round = 0; round < ROUNDS; round++) {
+ for (const o of r) {
+ o.x = (o.x + o.w / 2) * PULL - o.w / 2;
+ o.y = (o.y + o.h / 2) * PULL - o.h / 2;
+ }
+ separate(r, W, H);
+ }
+
let x0 = Infinity, y0 = Infinity, x1 = -Infinity, y1 = -Infinity;
for (const o of r) {
x0 = Math.min(x0, o.x); y0 = Math.min(y0, o.y);