Skip to content

Commit b7e1abb

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

15 files changed

+1666
-360
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: 178 additions & 24 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,63 @@ 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) {
36-
const a_ent = this.attached.remove(a);
37-
const b_ent = this.attached.remove(b);
37+
attach_swap(ext: Ext, a: Entity, b: Entity) {
38+
const a_ent = this.attached.get(a),
39+
b_ent = this.attached.get(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+
let a_win = ext.windows.get(a),
42+
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+
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) {
55+
const stack = ext.auto_tiler.forest.stacks.get(a_win.stack);
56+
if (stack) {
57+
a = stack.active;
58+
a_win = ext.windows.get(a);
59+
if (!a_win) return;
60+
61+
stack.deactivate(a_win);
62+
}
63+
}
64+
65+
if (b_win.stack !== null) {
66+
const stack = ext.auto_tiler.forest.stacks.get(b_win.stack);
67+
if (stack) {
68+
b = stack.active;
69+
b_win = ext.windows.get(b);
70+
if (!b_win) return;
71+
72+
stack.deactivate(b_win);
73+
}
74+
}
4775
}
76+
77+
const a_fn = a_fork.replace_window(ext, a_win, b_win);
78+
this.attached.insert(b, a_ent);
79+
80+
const b_fn = b_fork.replace_window(ext, b_win, a_win);
81+
this.attached.insert(a, b_ent);
82+
83+
if (a_fn) a_fn();
84+
if (b_fn) b_fn();
85+
86+
a_win.stack = b_stack;
87+
b_win.stack = a_stack;
88+
89+
a_win.meta.get_compositor_private()?.show();
90+
b_win.meta.get_compositor_private()?.show();
91+
92+
this.tile(ext, a_fork, a_fork.area);
93+
this.tile(ext, b_fork, b_fork.area);
4894
}
4995

5096
update_toplevel(ext: Ext, fork: Fork, monitor: number, smart_gaps: boolean) {
@@ -93,8 +139,8 @@ export class AutoTiler {
93139
}
94140

95141
/** 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);
142+
attach_to_window(ext: Ext, attachee: ShellWindow, attacher: ShellWindow, cursor: Rectangle, stack_from_left: boolean = true) {
143+
let attached = this.forest.attach_window(ext, attachee.entity, attacher.entity, cursor, stack_from_left);
98144

99145
if (attached) {
100146
const [, fork] = attached;
@@ -155,10 +201,17 @@ export class AutoTiler {
155201
}
156202
}
157203

204+
/** Destroy all widgets owned by this object. Call before dropping. */
205+
destroy() {
206+
for (const stack of this.forest.stacks.values()) stack.destroy();
207+
208+
this.forest.stacks.truncate(0);
209+
}
210+
158211
/** Detaches the window from a tiling branch, if it is attached to one. */
159212
detach_window(ext: Ext, win: Entity) {
160213
this.attached.take_with(win, (prev_fork: Entity) => {
161-
const reflow_fork = this.forest.detach(prev_fork, win);
214+
const reflow_fork = this.forest.detach(ext, prev_fork, win);
162215

163216
if (reflow_fork) {
164217
const fork = reflow_fork[1];
@@ -187,21 +240,21 @@ export class AutoTiler {
187240
const fork = this.forest.forks.get(fork_entity);
188241

189242
if (fork) {
190-
if (fork.left.kind == NodeKind.WINDOW && fork.right && fork.right.kind == NodeKind.WINDOW) {
243+
if (fork.left.inner.kind === 2 && fork.right && fork.right.inner.kind === 2) {
191244
if (fork.left.is_window(win)) {
192-
const sibling = ext.windows.get(fork.right.entity);
245+
const sibling = ext.windows.get(fork.right.inner.entity);
193246
if (sibling && sibling.rect().contains(cursor)) {
194-
fork.left.entity = fork.right.entity;
195-
fork.right.entity = win;
247+
fork.left.inner.entity = fork.right.inner.entity;
248+
fork.right.inner.entity = win;
196249

197250
this.tile(ext, fork, fork.area);
198251
return true;
199252
}
200253
} else if (fork.right.is_window(win)) {
201-
const sibling = ext.windows.get(fork.left.entity);
254+
const sibling = ext.windows.get(fork.left.inner.entity);
202255
if (sibling && sibling.rect().contains(cursor)) {
203-
fork.right.entity = fork.left.entity;
204-
fork.left.entity = win;
256+
fork.right.inner.entity = fork.left.inner.entity;
257+
fork.left.inner.entity = win;
205258

206259
this.tile(ext, fork, fork.area);
207260
return true;
@@ -214,6 +267,30 @@ export class AutoTiler {
214267
return false;
215268
}
216269

270+
find_stack(entity: Entity): null | [Fork, node.Node, boolean] {
271+
const att = this.attached.get(entity);
272+
if (att) {
273+
const fork = this.forest.forks.get(att);
274+
if (fork) {
275+
if (fork.left.is_in_stack(entity)) {
276+
return [fork, fork.left, true];
277+
} else if (fork.right?.is_in_stack(entity)) {
278+
return [fork, fork.right, false];
279+
}
280+
}
281+
}
282+
283+
return null;
284+
}
285+
286+
/** Find the fork that belongs to a window */
287+
get_parent_fork(window: Entity): null | Fork {
288+
const entity = this.attached.get(window);
289+
if (!entity) return null;
290+
291+
return this.forest.forks.get(entity);
292+
}
293+
217294
/** Performed when a window that has been dropped is destined to be tiled
218295
*
219296
* ## Implementation Notes
@@ -288,8 +365,85 @@ export class AutoTiler {
288365
const result = this.toggle_orientation_(ext, window);
289366
if (result.kind == ERR) {
290367
log.warn(`toggle_orientation: ${result.value}`);
368+
}
369+
}
370+
371+
toggle_stacking(ext: Ext) {
372+
const focused = ext.focus_window();
373+
if (!focused) return;
374+
375+
// Disable floating if floating is enabled
376+
if (ext.contains_tag(focused.entity, Tags.Floating)) {
377+
ext.delete_tag(focused.entity, Tags.Floating);
378+
this.auto_tile(ext, focused, false);
379+
}
380+
381+
let stack = null, fork = null;
382+
const fork_entity = this.attached.get(focused.entity);
383+
384+
if (fork_entity) {
385+
fork = this.forest.forks.get(fork_entity);
386+
if (fork) {
387+
const stack_toggle = (fork: Fork, branch: node.Node) => {
388+
// If the stack contains 1 item, unstack it
389+
const stack = branch.inner as node.NodeStack;
390+
if (stack.entities.length === 1) {
391+
focused.stack = null;
392+
this.forest.stacks.remove(stack.idx)?.destroy();
393+
fork.measure(this.forest, ext, fork.area, this.forest.on_record());
394+
return node.Node.window(focused.entity);
395+
}
396+
397+
return null;
398+
};
399+
400+
if (fork.left.is_window(focused.entity)) {
401+
// Assign left window as stack.
402+
focused.stack = this.forest.stacks.insert(new Stack(ext, focused.entity, fork.workspace));
403+
fork.left = node.Node.stacked(focused.entity, focused.stack);
404+
stack = fork.left.inner as node.NodeStack;
405+
fork.measure(this.forest, ext, fork.area, this.forest.on_record());
406+
} else if (fork.left.is_in_stack(focused.entity)) {
407+
const node = stack_toggle(fork, fork.left);
408+
if (node) fork.left = node;
409+
} else if (fork.right?.is_window(focused.entity)) {
410+
// Assign right window as stack
411+
focused.stack = this.forest.stacks.insert(new Stack(ext, focused.entity, fork.workspace));
412+
fork.right = node.Node.stacked(focused.entity, focused.stack);
413+
stack = fork.right.inner as node.NodeStack;
414+
fork.measure(this.forest, ext, fork.area, this.forest.on_record());
415+
} else if (fork.right?.is_in_stack(focused.entity)) {
416+
const node = stack_toggle(fork, fork.right);
417+
if (node) fork.right = node;
418+
}
419+
}
420+
}
421+
422+
if (stack) this.update_stack(ext, stack);
423+
424+
if (fork) this.tile(ext, fork, fork.area);
425+
}
426+
427+
update_stack(ext: Ext, stack: node.NodeStack) {
428+
if (stack.rect) {
429+
const container = this.forest.stacks.get(stack.idx);
430+
if (container) {
431+
container.clear();
432+
433+
// Collect names of each entity in the stack
434+
for (const entity of stack.entities) {
435+
const window = ext.windows.get(entity);
436+
if (window) {
437+
window.stack = stack.idx;
438+
container.add(window);
439+
}
440+
}
441+
442+
container.update_positions(stack.rect);
443+
container.auto_activate();
444+
}
291445
} else {
292-
log.info('toggled orientation');
446+
log.warn('stack rect was null');
293447
}
294448
}
295449

@@ -366,7 +520,7 @@ export class AutoTiler {
366520
this.forest.measure(ext, fork, fork.area);
367521

368522
for (const child of this.forest.iter(fork_entity, NodeKind.FORK)) {
369-
const child_fork = this.forest.forks.get(child.entity);
523+
const child_fork = this.forest.forks.get((child.inner as node.NodeFork).entity);
370524
if (child_fork) {
371525
child_fork.rebalance_orientation();
372526
this.forest.measure(ext, child_fork, child_fork.area);

0 commit comments

Comments
 (0)