Skip to content

Commit 686aa6e

Browse files
committed
feat(tiling): Add the stacked tiles feature to tiling mode
1 parent 3b948f7 commit 686aa6e

15 files changed

+1551
-266
lines changed

dark.css

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,4 +30,22 @@
3030
.pop-shell-gaps-entry {
3131
/* Seems to get the width just right to fit 3 digits */
3232
width: 75px;
33-
}
33+
}
34+
35+
.pop-shell-tab {
36+
border: 1px solid #333;
37+
color: #000;
38+
padding: 0 1em;
39+
}
40+
41+
.pop-shell-tab-active {
42+
background: #FBB86C;
43+
}
44+
45+
.pop-shell-tab-inactive {
46+
background: #9B8E8A;
47+
}
48+
49+
.pop-shell-tab-urgent {
50+
background: #D00;
51+
}

light.css

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,4 +29,22 @@
2929
.pop-shell-gaps-entry {
3030
/* Seems to get the width just right to fit 3 digits */
3131
width: 75px;
32-
}
32+
}
33+
34+
.pop-shell-tab {
35+
border: 1px solid #333;
36+
color: #000;
37+
padding: 0 1em;
38+
}
39+
40+
.pop-shell-tab-active {
41+
background: #FFAD00;
42+
}
43+
44+
.pop-shell-tab-inactive {
45+
background: #9B8E8A;
46+
}
47+
48+
.pop-shell-tab-urgent {
49+
background: #D00;
50+
}

schemas/org.gnome.shell.extensions.pop-shell.gschema.xml

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,16 @@
7676
</key>
7777

7878
<!-- Window Management Keys -->
79+
<key type="as" name="toggle-stacking">
80+
<default><![CDATA[['s']]]></default>
81+
<summary>Toggle stacking mode inside management mode</summary>
82+
</key>
83+
84+
<key type="as" name="toggle-stacking-global">
85+
<default><![CDATA[['<Super>s']]]></default>
86+
<summary>Toggle stacking mode outside management mode</summary>
87+
</key>
88+
7989
<key type="as" name="management-orientation">
8090
<default><![CDATA[['o']]]></default>
8191
<summary>Toggle tiling orientation</summary>

src/auto_tiler.ts

Lines changed: 159 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -5,16 +5,18 @@ import * as lib from 'lib';
55
import * as log from 'log';
66
import * as node from 'node';
77
import * as result from 'result';
8+
import * as stack from 'stack';
89

910
import type {Entity} from 'ecs';
1011
import type {Ext} from 'extension';
1112
import type {Forest} from 'forest';
1213
import type {Fork} from 'fork';
1314
import type {Rectangle} from 'rectangle';
1415
import type {Result} from 'result';
15-
import type {ShellWindow} from 'window';
16+
import type { ShellWindow } from 'window';
1617

17-
const {Ok, Err, ERR} = result;
18+
const { Stack } = stack;
19+
const { Ok, Err, ERR } = result;
1820
const {NodeKind} = node;
1921
const Tags = Me.imports.tags;
2022

@@ -32,19 +34,46 @@ export class AutoTiler {
3234
* Call this when a window has swapped positions with another, so that we
3335
* may update the associations in the auto-tiler world.
3436
*/
35-
attach_swap(a: Entity, b: Entity) {
37+
attach_swap(ext: Ext, a: Entity, b: Entity) {
3638
const a_ent = this.attached.remove(a);
3739
const b_ent = this.attached.remove(b);
3840

39-
if (a_ent) {
40-
this.forest.forks.with(a_ent, (fork) => fork.replace_window(a, b));
41-
this.attached.insert(b, a_ent);
42-
}
41+
const a_win = ext.windows.get(a);
42+
const b_win = ext.windows.get(b);
43+
44+
if (!a_ent || !b_ent || !a_win || !b_win) return;
45+
46+
const a_fork = this.forest.forks.get(a_ent);
47+
const b_fork = this.forest.forks.get(b_ent);
48+
49+
if (!a_fork || !b_fork) return;
4350

44-
if (b_ent) {
45-
this.forest.forks.with(b_ent, (fork) => fork.replace_window(b, a));
46-
this.attached.insert(a, b_ent);
51+
const a_stack = a_win.stack, b_stack = b_win.stack;
52+
53+
if (ext.auto_tiler) {
54+
if (a_win.stack !== null) ext.auto_tiler.forest.stacks.get(a_win.stack)?.deactivate(a_win);
55+
if (b_win.stack !== null) ext.auto_tiler.forest.stacks.get(b_win.stack)?.deactivate(b_win);
4756
}
57+
58+
const a_fn = a_fork.replace_window(ext, a_win, b_win);
59+
this.attached.insert(b, a_ent);
60+
61+
const b_fn = b_fork.replace_window(ext, b_win, a_win);
62+
this.attached.insert(a, b_ent);
63+
64+
if (a_fn) a_fn();
65+
if (b_fn) b_fn();
66+
67+
a_win.stack = b_stack;
68+
b_win.stack = a_stack;
69+
70+
a_win.meta.get_compositor_private()?.show();
71+
b_win.meta.get_compositor_private()?.show();
72+
73+
log.debug(`swapped: ${this.forest.fmt(ext)}`);
74+
75+
this.tile(ext, a_fork, a_fork.area);
76+
this.tile(ext, b_fork, b_fork.area);
4877
}
4978

5079
update_toplevel(ext: Ext, fork: Fork, monitor: number, smart_gaps: boolean) {
@@ -93,8 +122,8 @@ export class AutoTiler {
93122
}
94123

95124
/** Tiles a window into another */
96-
attach_to_window(ext: Ext, attachee: ShellWindow, attacher: ShellWindow, cursor: Rectangle) {
97-
let attached = this.forest.attach_window(ext, attachee.entity, attacher.entity, cursor);
125+
attach_to_window(ext: Ext, attachee: ShellWindow, attacher: ShellWindow, cursor: Rectangle, stack_from_left: boolean = true) {
126+
let attached = this.forest.attach_window(ext, attachee.entity, attacher.entity, cursor, stack_from_left);
98127

99128
if (attached) {
100129
const [, fork] = attached;
@@ -155,10 +184,17 @@ export class AutoTiler {
155184
}
156185
}
157186

187+
/** Destroy all widgets owned by this object. Call before dropping. */
188+
destroy() {
189+
for (const stack of this.forest.stacks.values()) stack.destroy();
190+
191+
this.forest.stacks.truncate(0);
192+
}
193+
158194
/** Detaches the window from a tiling branch, if it is attached to one. */
159195
detach_window(ext: Ext, win: Entity) {
160196
this.attached.take_with(win, (prev_fork: Entity) => {
161-
const reflow_fork = this.forest.detach(prev_fork, win);
197+
const reflow_fork = this.forest.detach(ext, prev_fork, win);
162198

163199
if (reflow_fork) {
164200
const fork = reflow_fork[1];
@@ -187,21 +223,21 @@ export class AutoTiler {
187223
const fork = this.forest.forks.get(fork_entity);
188224

189225
if (fork) {
190-
if (fork.left.kind == NodeKind.WINDOW && fork.right && fork.right.kind == NodeKind.WINDOW) {
226+
if (fork.left.inner.kind === 2 && fork.right && fork.right.inner.kind === 2) {
191227
if (fork.left.is_window(win)) {
192-
const sibling = ext.windows.get(fork.right.entity);
228+
const sibling = ext.windows.get(fork.right.inner.entity);
193229
if (sibling && sibling.rect().contains(cursor)) {
194-
fork.left.entity = fork.right.entity;
195-
fork.right.entity = win;
230+
fork.left.inner.entity = fork.right.inner.entity;
231+
fork.right.inner.entity = win;
196232

197233
this.tile(ext, fork, fork.area);
198234
return true;
199235
}
200236
} else if (fork.right.is_window(win)) {
201-
const sibling = ext.windows.get(fork.left.entity);
237+
const sibling = ext.windows.get(fork.left.inner.entity);
202238
if (sibling && sibling.rect().contains(cursor)) {
203-
fork.right.entity = fork.left.entity;
204-
fork.left.entity = win;
239+
fork.right.inner.entity = fork.left.inner.entity;
240+
fork.left.inner.entity = win;
205241

206242
this.tile(ext, fork, fork.area);
207243
return true;
@@ -214,6 +250,30 @@ export class AutoTiler {
214250
return false;
215251
}
216252

253+
find_stack(entity: Entity): null | [Fork, node.Node, boolean] {
254+
const att = this.attached.get(entity);
255+
if (att) {
256+
const fork = this.forest.forks.get(att);
257+
if (fork) {
258+
if (fork.left.is_in_stack(entity)) {
259+
return [fork, fork.left, true];
260+
} else if (fork.right?.is_in_stack(entity)) {
261+
return [fork, fork.right, false];
262+
}
263+
}
264+
}
265+
266+
return null;
267+
}
268+
269+
/** Find the fork that belongs to a window */
270+
get_parent_fork(window: Entity): null | Fork {
271+
const entity = this.attached.get(window);
272+
if (!entity) return null;
273+
274+
return this.forest.forks.get(entity);
275+
}
276+
217277
/** Performed when a window that has been dropped is destined to be tiled
218278
*
219279
* ## Implementation Notes
@@ -288,8 +348,85 @@ export class AutoTiler {
288348
const result = this.toggle_orientation_(ext, window);
289349
if (result.kind == ERR) {
290350
log.warn(`toggle_orientation: ${result.value}`);
351+
}
352+
}
353+
354+
toggle_stacking(ext: Ext) {
355+
const focused = ext.focus_window();
356+
if (!focused) return;
357+
358+
// Disable floating if floating is enabled
359+
if (ext.contains_tag(focused.entity, Tags.Floating)) {
360+
ext.delete_tag(focused.entity, Tags.Floating);
361+
this.auto_tile(ext, focused, false);
362+
}
363+
364+
let stack = null, fork = null;
365+
const fork_entity = this.attached.get(focused.entity);
366+
367+
if (fork_entity) {
368+
fork = this.forest.forks.get(fork_entity);
369+
if (fork) {
370+
const stack_toggle = (fork: Fork, branch: node.Node) => {
371+
// If the stack contains 1 item, unstack it
372+
const stack = branch.inner as node.NodeStack;
373+
if (stack.entities.length === 1) {
374+
focused.stack = null;
375+
this.forest.stacks.remove(stack.idx)?.destroy();
376+
fork.measure(this.forest, ext, fork.area, this.forest.on_record());
377+
return node.Node.window(focused.entity);
378+
}
379+
380+
return null;
381+
};
382+
383+
if (fork.left.is_window(focused.entity)) {
384+
// Assign left window as stack.
385+
focused.stack = this.forest.stacks.insert(new Stack(ext, focused.entity, fork.workspace));
386+
fork.left = node.Node.stacked(focused.entity, focused.stack);
387+
stack = fork.left.inner as node.NodeStack;
388+
fork.measure(this.forest, ext, fork.area, this.forest.on_record());
389+
} else if (fork.left.is_in_stack(focused.entity)) {
390+
const node = stack_toggle(fork, fork.left);
391+
if (node) fork.left = node;
392+
} else if (fork.right?.is_window(focused.entity)) {
393+
// Assign right window as stack
394+
focused.stack = this.forest.stacks.insert(new Stack(ext, focused.entity, fork.workspace));
395+
fork.right = node.Node.stacked(focused.entity, focused.stack);
396+
stack = fork.right.inner as node.NodeStack;
397+
fork.measure(this.forest, ext, fork.area, this.forest.on_record());
398+
} else if (fork.right?.is_in_stack(focused.entity)) {
399+
const node = stack_toggle(fork, fork.right);
400+
if (node) fork.right = node;
401+
}
402+
}
403+
}
404+
405+
if (stack) this.update_stack(ext, stack);
406+
407+
if (fork) this.tile(ext, fork, fork.area);
408+
}
409+
410+
update_stack(ext: Ext, stack: node.NodeStack) {
411+
if (stack.rect) {
412+
const container = this.forest.stacks.get(stack.idx);
413+
if (container) {
414+
container.clear();
415+
416+
// Collect names of each entity in the stack
417+
for (const entity of stack.entities) {
418+
const window = ext.windows.get(entity);
419+
if (window) {
420+
window.stack = stack.idx;
421+
container.add(window);
422+
}
423+
}
424+
425+
container.update_positions(stack.rect);
426+
container.auto_activate();
427+
}
291428
} else {
292-
log.info('toggled orientation');
429+
log.warn('stack rect was null');
293430
}
294431
}
295432

@@ -366,7 +503,7 @@ export class AutoTiler {
366503
this.forest.measure(ext, fork, fork.area);
367504

368505
for (const child of this.forest.iter(fork_entity, NodeKind.FORK)) {
369-
const child_fork = this.forest.forks.get(child.entity);
506+
const child_fork = this.forest.forks.get((child.inner as node.NodeFork).entity);
370507
if (child_fork) {
371508
child_fork.rebalance_orientation();
372509
this.forest.measure(ext, child_fork, child_fork.area);

0 commit comments

Comments
 (0)