Skip to content

Commit

Permalink
feat(tiling): Add the stacked tiles feature to tiling mode
Browse files Browse the repository at this point in the history
  • Loading branch information
mmstick committed Aug 25, 2020
1 parent 3b948f7 commit a7ea7fc
Show file tree
Hide file tree
Showing 15 changed files with 1,661 additions and 359 deletions.
20 changes: 19 additions & 1 deletion dark.css
Original file line number Diff line number Diff line change
Expand Up @@ -30,4 +30,22 @@
.pop-shell-gaps-entry {
/* Seems to get the width just right to fit 3 digits */
width: 75px;
}
}

.pop-shell-tab {
border: 1px solid #333;
color: #000;
padding: 0 1em;
}

.pop-shell-tab-active {
background: #FBB86C;
}

.pop-shell-tab-inactive {
background: #9B8E8A;
}

.pop-shell-tab-urgent {
background: #D00;
}
20 changes: 19 additions & 1 deletion light.css
Original file line number Diff line number Diff line change
Expand Up @@ -29,4 +29,22 @@
.pop-shell-gaps-entry {
/* Seems to get the width just right to fit 3 digits */
width: 75px;
}
}

.pop-shell-tab {
border: 1px solid #333;
color: #000;
padding: 0 1em;
}

.pop-shell-tab-active {
background: #FFAD00;
}

.pop-shell-tab-inactive {
background: #9B8E8A;
}

.pop-shell-tab-urgent {
background: #D00;
}
10 changes: 10 additions & 0 deletions schemas/org.gnome.shell.extensions.pop-shell.gschema.xml
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,16 @@
</key>

<!-- Window Management Keys -->
<key type="as" name="toggle-stacking">
<default><![CDATA[['s']]]></default>
<summary>Toggle stacking mode inside management mode</summary>
</key>

<key type="as" name="toggle-stacking-global">
<default><![CDATA[['<Super>s']]]></default>
<summary>Toggle stacking mode outside management mode</summary>
</key>

<key type="as" name="management-orientation">
<default><![CDATA[['o']]]></default>
<summary>Toggle tiling orientation</summary>
Expand Down
202 changes: 178 additions & 24 deletions src/auto_tiler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,18 @@ import * as lib from 'lib';
import * as log from 'log';
import * as node from 'node';
import * as result from 'result';
import * as stack from 'stack';

import type {Entity} from 'ecs';
import type {Ext} from 'extension';
import type {Forest} from 'forest';
import type {Fork} from 'fork';
import type {Rectangle} from 'rectangle';
import type {Result} from 'result';
import type {ShellWindow} from 'window';
import type { ShellWindow } from 'window';

const {Ok, Err, ERR} = result;
const { Stack } = stack;
const { Ok, Err, ERR } = result;
const {NodeKind} = node;
const Tags = Me.imports.tags;

Expand All @@ -32,19 +34,63 @@ export class AutoTiler {
* Call this when a window has swapped positions with another, so that we
* may update the associations in the auto-tiler world.
*/
attach_swap(a: Entity, b: Entity) {
const a_ent = this.attached.remove(a);
const b_ent = this.attached.remove(b);
attach_swap(ext: Ext, a: Entity, b: Entity) {
const a_ent = this.attached.get(a),
b_ent = this.attached.get(b);

if (a_ent) {
this.forest.forks.with(a_ent, (fork) => fork.replace_window(a, b));
this.attached.insert(b, a_ent);
}
let a_win = ext.windows.get(a),
b_win = ext.windows.get(b);

if (!a_ent || !b_ent || !a_win || !b_win) return;

const a_fork = this.forest.forks.get(a_ent),
b_fork = this.forest.forks.get(b_ent);

if (!a_fork || !b_fork) return;

if (b_ent) {
this.forest.forks.with(b_ent, (fork) => fork.replace_window(b, a));
this.attached.insert(a, b_ent);
const a_stack = a_win.stack, b_stack = b_win.stack;

if (ext.auto_tiler) {
if (a_win.stack !== null) {
const stack = ext.auto_tiler.forest.stacks.get(a_win.stack);
if (stack) {
a = stack.active;
a_win = ext.windows.get(a);
if (!a_win) return;

stack.deactivate(a_win);
}
}

if (b_win.stack !== null) {
const stack = ext.auto_tiler.forest.stacks.get(b_win.stack);
if (stack) {
b = stack.active;
b_win = ext.windows.get(b);
if (!b_win) return;

stack.deactivate(b_win);
}
}
}

const a_fn = a_fork.replace_window(ext, a_win, b_win);
this.attached.insert(b, a_ent);

const b_fn = b_fork.replace_window(ext, b_win, a_win);
this.attached.insert(a, b_ent);

if (a_fn) a_fn();
if (b_fn) b_fn();

a_win.stack = b_stack;
b_win.stack = a_stack;

a_win.meta.get_compositor_private()?.show();
b_win.meta.get_compositor_private()?.show();

this.tile(ext, a_fork, a_fork.area);
this.tile(ext, b_fork, b_fork.area);
}

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

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

if (attached) {
const [, fork] = attached;
Expand Down Expand Up @@ -155,10 +201,17 @@ export class AutoTiler {
}
}

/** Destroy all widgets owned by this object. Call before dropping. */
destroy() {
for (const stack of this.forest.stacks.values()) stack.destroy();

this.forest.stacks.truncate(0);
}

/** Detaches the window from a tiling branch, if it is attached to one. */
detach_window(ext: Ext, win: Entity) {
this.attached.take_with(win, (prev_fork: Entity) => {
const reflow_fork = this.forest.detach(prev_fork, win);
const reflow_fork = this.forest.detach(ext, prev_fork, win);

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

if (fork) {
if (fork.left.kind == NodeKind.WINDOW && fork.right && fork.right.kind == NodeKind.WINDOW) {
if (fork.left.inner.kind === 2 && fork.right && fork.right.inner.kind === 2) {
if (fork.left.is_window(win)) {
const sibling = ext.windows.get(fork.right.entity);
const sibling = ext.windows.get(fork.right.inner.entity);
if (sibling && sibling.rect().contains(cursor)) {
fork.left.entity = fork.right.entity;
fork.right.entity = win;
fork.left.inner.entity = fork.right.inner.entity;
fork.right.inner.entity = win;

this.tile(ext, fork, fork.area);
return true;
}
} else if (fork.right.is_window(win)) {
const sibling = ext.windows.get(fork.left.entity);
const sibling = ext.windows.get(fork.left.inner.entity);
if (sibling && sibling.rect().contains(cursor)) {
fork.right.entity = fork.left.entity;
fork.left.entity = win;
fork.right.inner.entity = fork.left.inner.entity;
fork.left.inner.entity = win;

this.tile(ext, fork, fork.area);
return true;
Expand All @@ -214,6 +267,30 @@ export class AutoTiler {
return false;
}

find_stack(entity: Entity): null | [Fork, node.Node, boolean] {
const att = this.attached.get(entity);
if (att) {
const fork = this.forest.forks.get(att);
if (fork) {
if (fork.left.is_in_stack(entity)) {
return [fork, fork.left, true];
} else if (fork.right?.is_in_stack(entity)) {
return [fork, fork.right, false];
}
}
}

return null;
}

/** Find the fork that belongs to a window */
get_parent_fork(window: Entity): null | Fork {
const entity = this.attached.get(window);
if (!entity) return null;

return this.forest.forks.get(entity);
}

/** Performed when a window that has been dropped is destined to be tiled
*
* ## Implementation Notes
Expand Down Expand Up @@ -288,8 +365,85 @@ export class AutoTiler {
const result = this.toggle_orientation_(ext, window);
if (result.kind == ERR) {
log.warn(`toggle_orientation: ${result.value}`);
}
}

toggle_stacking(ext: Ext) {
const focused = ext.focus_window();
if (!focused) return;

// Disable floating if floating is enabled
if (ext.contains_tag(focused.entity, Tags.Floating)) {
ext.delete_tag(focused.entity, Tags.Floating);
this.auto_tile(ext, focused, false);
}

let stack = null, fork = null;
const fork_entity = this.attached.get(focused.entity);

if (fork_entity) {
fork = this.forest.forks.get(fork_entity);
if (fork) {
const stack_toggle = (fork: Fork, branch: node.Node) => {
// If the stack contains 1 item, unstack it
const stack = branch.inner as node.NodeStack;
if (stack.entities.length === 1) {
focused.stack = null;
this.forest.stacks.remove(stack.idx)?.destroy();
fork.measure(this.forest, ext, fork.area, this.forest.on_record());
return node.Node.window(focused.entity);
}

return null;
};

if (fork.left.is_window(focused.entity)) {
// Assign left window as stack.
focused.stack = this.forest.stacks.insert(new Stack(ext, focused.entity, fork.workspace));
fork.left = node.Node.stacked(focused.entity, focused.stack);
stack = fork.left.inner as node.NodeStack;
fork.measure(this.forest, ext, fork.area, this.forest.on_record());
} else if (fork.left.is_in_stack(focused.entity)) {
const node = stack_toggle(fork, fork.left);
if (node) fork.left = node;
} else if (fork.right?.is_window(focused.entity)) {
// Assign right window as stack
focused.stack = this.forest.stacks.insert(new Stack(ext, focused.entity, fork.workspace));
fork.right = node.Node.stacked(focused.entity, focused.stack);
stack = fork.right.inner as node.NodeStack;
fork.measure(this.forest, ext, fork.area, this.forest.on_record());
} else if (fork.right?.is_in_stack(focused.entity)) {
const node = stack_toggle(fork, fork.right);
if (node) fork.right = node;
}
}
}

if (stack) this.update_stack(ext, stack);

if (fork) this.tile(ext, fork, fork.area);
}

update_stack(ext: Ext, stack: node.NodeStack) {
if (stack.rect) {
const container = this.forest.stacks.get(stack.idx);
if (container) {
container.clear();

// Collect names of each entity in the stack
for (const entity of stack.entities) {
const window = ext.windows.get(entity);
if (window) {
window.stack = stack.idx;
container.add(window);
}
}

container.update_positions(stack.rect);
container.auto_activate();
}
} else {
log.info('toggled orientation');
log.warn('stack rect was null');
}
}

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

for (const child of this.forest.iter(fork_entity, NodeKind.FORK)) {
const child_fork = this.forest.forks.get(child.entity);
const child_fork = this.forest.forks.get((child.inner as node.NodeFork).entity);
if (child_fork) {
child_fork.rebalance_orientation();
this.forest.measure(ext, child_fork, child_fork.area);
Expand Down
Loading

0 comments on commit a7ea7fc

Please sign in to comment.