diff --git a/schemas/org.gnome.shell.extensions.pop-shell.gschema.xml b/schemas/org.gnome.shell.extensions.pop-shell.gschema.xml
index fc766da7..91a29e4f 100644
--- a/schemas/org.gnome.shell.extensions.pop-shell.gschema.xml
+++ b/schemas/org.gnome.shell.extensions.pop-shell.gschema.xml
@@ -64,6 +64,11 @@
Hide the outer gap when a tree contains only one window
+
+ false
+ Destroy stacks when separated by the mouse
+
+
false
Snaps windows to the tiling grid on drop
diff --git a/src/auto_tiler.ts b/src/auto_tiler.ts
index d193b9c6..109848ac 100644
--- a/src/auto_tiler.ts
+++ b/src/auto_tiler.ts
@@ -232,9 +232,9 @@ export class AutoTiler {
}
/** Detaches the window from a tiling branch, if it is attached to one. */
- detach_window(ext: Ext, win: Entity) {
+ detach_window(ext: Ext, win: Entity, destroy_stack: boolean = true) {
this.attached.take_with(win, (prev_fork: Entity) => {
- const reflow_fork = this.forest.detach(ext, prev_fork, win);
+ const reflow_fork = this.forest.detach(ext, prev_fork, win, destroy_stack);
if (reflow_fork) {
const fork = reflow_fork[1];
diff --git a/src/forest.ts b/src/forest.ts
index cf88d526..bbb797b7 100644
--- a/src/forest.ts
+++ b/src/forest.ts
@@ -350,7 +350,7 @@ export class Forest extends Ecs.World {
}
/** Detaches an entity from the a fork, re-arranging the fork's tree as necessary */
- detach(ext: Ext, fork_entity: Entity, window: Entity): [Entity, Fork.Fork] | null {
+ detach(ext: Ext, fork_entity: Entity, window: Entity, destroy_stack: boolean = false): [Entity, Fork.Fork] | null {
const fork = this.forks.get(fork_entity);
if (!fork) return null;
@@ -387,8 +387,11 @@ export class Forest extends Ecs.World {
ext,
fork.left.inner as Node.NodeStack,
window,
- () => {
- if (fork.right) {
+ destroy_stack,
+ (window: undefined | Entity) => {
+ if (window)
+ fork.left = Node.Node.window(window);
+ else if (fork.right) {
fork.left = fork.right
fork.right = null
if (parent) {
@@ -428,9 +431,14 @@ export class Forest extends Ecs.World {
ext,
fork.right.inner as Node.NodeStack,
window,
- () => {
- fork.right = null
+ destroy_stack,
+ (window) => {
+ if (window)
+ fork.right = Node.Node.window(window);
+ else {
+ fork.right = null;
this.reassign_to_parent(fork, fork.left)
+ }
},
);
}
@@ -714,7 +722,7 @@ export class Forest extends Ecs.World {
}
/** Removes window from stack, destroying the stack if it was the last window. */
- private remove_from_stack(ext: Ext, stack: Node.NodeStack, window: Entity, on_last: () => void) {
+ private remove_from_stack(ext: Ext, stack: Node.NodeStack, window: Entity, destroy_stack: boolean, on_last: (win?: Entity) => void) {
if (stack.entities.length === 1) {
this.stacks.remove(stack.idx)?.destroy();
on_last();
@@ -723,6 +731,10 @@ export class Forest extends Ecs.World {
if (s) {
Node.stack_remove(this, stack, window)
}
+ if (destroy_stack && stack.entities.length === 1) {
+ on_last(stack.entities[0])
+ this.stacks.remove(stack.idx)?.destroy()
+ }
}
const win = ext.windows.get(window);
diff --git a/src/prefs.ts b/src/prefs.ts
index 7d326283..4db828be 100644
--- a/src/prefs.ts
+++ b/src/prefs.ts
@@ -1,4 +1,4 @@
-export { }
+export { };
const ExtensionUtils = imports.misc.extensionUtils;
// @ts-ignore
@@ -8,9 +8,9 @@ const { Gtk } = imports.gi;
const { Settings } = imports.gi.Gio;
-import * as settings from 'settings';
-import * as log from 'log';
import * as focus from 'focus';
+import * as log from 'log';
+import * as settings from 'settings';
interface AppWidgets {
fullscreen_launcher: any,
@@ -20,6 +20,7 @@ interface AppWidgets {
outer_gap: any,
show_skip_taskbar: any,
smart_gaps: any,
+ auto_unstack: any,
snap_to_grid: any,
window_titles: any,
mouse_cursor_focus_position: any,
@@ -52,6 +53,12 @@ function settings_dialog_new(): Gtk.Container {
Settings.sync();
})
+ app.auto_unstack.set_active(ext.auto_unstack());
+ app.auto_unstack.connect('state-set', (_widget: any, state: boolean) => {
+ ext.set_auto_unstack(state);
+ Settings.sync();
+ });
+
app.outer_gap.set_text(String(ext.gap_outer()));
app.outer_gap.connect('activate', (widget: any) => {
let parsed = parseInt((widget.get_text() as string).trim());
@@ -71,7 +78,7 @@ function settings_dialog_new(): Gtk.Container {
});
app.log_level.set_active(ext.log_level());
- app.log_level.connect("changed", () => {
+ app.log_level.connect('changed', () => {
let active_id = app.log_level.get_active_id();
ext.set_log_level(active_id);
});
@@ -89,20 +96,20 @@ function settings_dialog_new(): Gtk.Container {
});
app.mouse_cursor_focus_position.set_active(ext.mouse_cursor_focus_location());
- app.mouse_cursor_focus_position.connect("changed", () => {
+ app.mouse_cursor_focus_position.connect('changed', () => {
let active_id = app.mouse_cursor_focus_position.get_active_id();
ext.set_mouse_cursor_focus_location(active_id);
});
- app.fullscreen_launcher.set_active(ext.fullscreen_launcher())
- app.fullscreen_launcher.connect('state-set', (_widget: any, state: boolean) => {
- ext.set_fullscreen_launcher(state)
+ app.fullscreen_launcher.set_active(ext.fullscreen_launcher());
+ app.fullscreen_launcher.connect('state-set',(_widget: any, state: boolean) => {
+ ext.set_fullscreen_launcher(state);
Settings.sync()
});
- app.stacking_with_mouse.set_active(ext.stacking_with_mouse())
+ app.stacking_with_mouse.set_active(ext.stacking_with_mouse());
app.stacking_with_mouse.connect('state-set', (_widget: any, state: boolean) => {
- ext.set_stacking_with_mouse(state)
+ ext.set_stacking_with_mouse(state);
Settings.sync()
});
@@ -135,6 +142,11 @@ function settings_dialog_view(): [AppWidgets, Gtk.Container] {
xalign: 0.0
})
+ const unstack_label = new Gtk.Label({
+ label: "Destroy stacks when separated by the mouse",
+ xalign: 0.0
+ })
+
const show_skip_taskbar_label = new Gtk.Label({
label: "Show Minimize to Tray Windows",
xalign: 0.0
@@ -155,7 +167,7 @@ function settings_dialog_view(): [AppWidgets, Gtk.Container] {
xalign: 0.0
})
- const [inner_gap, outer_gap] = gaps_section(grid, 9);
+ const [inner_gap, outer_gap] = gaps_section(grid, 10);
const settings = {
inner_gap,
@@ -163,23 +175,24 @@ function settings_dialog_view(): [AppWidgets, Gtk.Container] {
fullscreen_launcher: new Gtk.Switch({ halign: Gtk.Align.END }),
stacking_with_mouse: new Gtk.Switch({ halign: Gtk.Align.END }),
smart_gaps: new Gtk.Switch({ halign: Gtk.Align.END }),
+ auto_unstack: new Gtk.Switch({ halign: Gtk.Align.END }),
snap_to_grid: new Gtk.Switch({ halign: Gtk.Align.END }),
window_titles: new Gtk.Switch({ halign: Gtk.Align.END }),
show_skip_taskbar: new Gtk.Switch({ halign: Gtk.Align.END }),
mouse_cursor_follows_active_window: new Gtk.Switch({ halign: Gtk.Align.END }),
mouse_cursor_focus_position: build_combo(
grid,
- 7,
+ 8,
focus.FocusPosition,
- 'Mouse Cursor Focus Position',
+ "Mouse Cursor Focus Position"
),
log_level: build_combo(
- grid,
- 8,
- log.LOG_LEVELS,
- 'Log Level',
- )
- }
+ grid,
+ 9,
+ log.LOG_LEVELS,
+ "Log Level"
+ ),
+ };
grid.attach(win_label, 0, 0, 1, 1)
grid.attach(settings.window_titles, 1, 0, 1, 1)
@@ -190,19 +203,22 @@ function settings_dialog_view(): [AppWidgets, Gtk.Container] {
grid.attach(smart_label, 0, 2, 1, 1)
grid.attach(settings.smart_gaps, 1, 2, 1, 1)
- grid.attach(fullscreen_launcher_label, 0, 3, 1, 1)
- grid.attach(settings.fullscreen_launcher, 1, 3, 1, 1)
+ grid.attach(unstack_label, 0, 3, 1, 1)
+ grid.attach(settings.auto_unstack, 1, 3, 1, 1)
+
+ grid.attach(fullscreen_launcher_label, 0, 4, 1, 1)
+ grid.attach(settings.fullscreen_launcher, 1, 4, 1, 1)
- grid.attach(stacking_with_mouse, 0, 4, 1, 1)
- grid.attach(settings.stacking_with_mouse, 1, 4, 1, 1)
+ grid.attach(stacking_with_mouse, 0, 5, 1, 1)
+ grid.attach(settings.stacking_with_mouse, 1, 5, 1, 1)
- grid.attach(show_skip_taskbar_label, 0, 5, 1, 1)
- grid.attach(settings.show_skip_taskbar, 1, 5, 1, 1)
+ grid.attach(show_skip_taskbar_label, 0, 6, 1, 1)
+ grid.attach(settings.show_skip_taskbar, 1, 6, 1, 1)
- grid.attach(mouse_cursor_follows_active_window_label, 0, 6, 1, 1)
- grid.attach(settings.mouse_cursor_follows_active_window, 1, 6, 1, 1)
+ grid.attach(mouse_cursor_follows_active_window_label, 0, 7, 1, 1)
+ grid.attach(settings.mouse_cursor_follows_active_window, 1, 7, 1, 1)
- return [settings, grid]
+ return [settings, grid];
}
function gaps_section(grid: any, top: number): [any, any] {
diff --git a/src/settings.ts b/src/settings.ts
index 39a6a784..28767a14 100644
--- a/src/settings.ts
+++ b/src/settings.ts
@@ -2,7 +2,7 @@ const Me = imports.misc.extensionUtils.getCurrentExtension();
const { Gio, Gdk } = imports.gi;
-const DARK = ["dark", "adapta", "plata", "dracula"]
+const DARK = ["dark", "adapta", "plata", "dracula"];
interface Settings extends GObject.Object {
get_boolean(key: string): boolean;
@@ -14,7 +14,7 @@ interface Settings extends GObject.Object {
get_string(key: string): string;
set_string(key: string, value: string): void;
- bind(key: string, object: GObject.Object, property: string, flags: any): void
+ bind(key: string, object: GObject.Object, property: string, flags: any): void;
}
function settings_new_id(schema_id: string): Settings | null {
@@ -22,10 +22,10 @@ function settings_new_id(schema_id: string): Settings | null {
return new Gio.Settings({ schema_id });
} catch (why) {
if (schema_id !== "org.gnome.shell.extensions.user-theme") {
- global.log(`failed to get settings for ${schema_id}: ${why}`)
+ global.log(`failed to get settings for ${schema_id}: ${why}`);
}
- return null
+ return null;
}
}
@@ -33,15 +33,15 @@ function settings_new_schema(schema: string): Settings {
const GioSSS = Gio.SettingsSchemaSource;
const schemaDir = Me.dir.get_child("schemas");
- let schemaSource = schemaDir.query_exists(null) ?
- GioSSS.new_from_directory(schemaDir.get_path(), GioSSS.get_default(), false) :
+ let schemaSource = schemaDir.query_exists(null)
+ ? GioSSS.new_from_directory(schemaDir.get_path(), GioSSS.get_default(), false) :
GioSSS.get_default();
const schemaObj = schemaSource.lookup(schema, true);
if (!schemaObj) {
throw new Error("Schema " + schema + " could not be found for extension "
- + Me.metadata.uuid + ". Please check your installation.")
+ + Me.metadata.uuid + ". Please check your installation.");
}
return new Gio.Settings({ settings_schema: schemaObj });
@@ -52,19 +52,20 @@ const ACTIVE_HINT_BORDER_RADIUS = "active-hint-border-radius";
const STACKING_WITH_MOUSE = "stacking-with-mouse";
const COLUMN_SIZE = "column-size";
const EDGE_TILING = "edge-tiling";
-const FULLSCREEN_LAUNCHER = "fullscreen-launcher"
+const FULLSCREEN_LAUNCHER = "fullscreen-launcher";
const GAP_INNER = "gap-inner";
const GAP_OUTER = "gap-outer";
const ROW_SIZE = "row-size";
const SHOW_TITLE = "show-title";
const SMART_GAPS = "smart-gaps";
+const AUTO_UNSTACK = "auto-unstack";
const SNAP_TO_GRID = "snap-to-grid";
const TILE_BY_DEFAULT = "tile-by-default";
const HINT_COLOR_RGBA = "hint-color-rgba";
const DEFAULT_RGBA_COLOR = "rgba(251, 184, 108, 1)"; //pop-orange
const LOG_LEVEL = "log-level";
const SHOW_SKIPTASKBAR = "show-skip-taskbar";
-const MOUSE_CURSOR_FOLLOWS_ACTIVE_WINDOW = "mouse-cursor-follows-active-window"
+const MOUSE_CURSOR_FOLLOWS_ACTIVE_WINDOW = "mouse-cursor-follows-active-window";
const MOUSE_CURSOR_FOCUS_LOCATION = "mouse-cursor-focus-location";
export class ExtensionSettings {
@@ -96,7 +97,7 @@ export class ExtensionSettings {
}
fullscreen_launcher(): boolean {
- return this.ext.get_boolean(FULLSCREEN_LAUNCHER)
+ return this.ext.get_boolean(FULLSCREEN_LAUNCHER);
}
gap_inner(): number {
@@ -120,19 +121,19 @@ export class ExtensionSettings {
theme(): string {
return this.shell
- ? this.shell.get_string("name")
- : this.int
- ? this.int.get_string("gtk-theme")
- : "Adwaita"
+ ? this.shell.get_string("name")
+ : this.int
+ ? this.int.get_string("gtk-theme")
+ : "Adwaita"
}
is_dark(): boolean {
- const theme = this.theme().toLowerCase()
- return DARK.some(dark => theme.includes(dark))
+ const theme = this.theme().toLowerCase();
+ return DARK.some(dark => theme.includes(dark));
}
is_high_contrast(): boolean {
- return this.theme().toLowerCase() === "highcontrast"
+ return this.theme().toLowerCase() === "highcontrast";
}
row_size(): number {
@@ -147,6 +148,10 @@ export class ExtensionSettings {
return this.ext.get_boolean(SMART_GAPS);
}
+ auto_unstack(): boolean {
+ return this.ext.get_boolean(AUTO_UNSTACK);
+ }
+
snap_to_grid(): boolean {
return this.ext.get_boolean(SNAP_TO_GRID);
}
@@ -196,11 +201,11 @@ export class ExtensionSettings {
}
set_edge_tiling(enable: boolean) {
- this.mutter?.set_boolean(EDGE_TILING, enable)
+ this.mutter?.set_boolean(EDGE_TILING, enable);
}
set_fullscreen_launcher(enable: boolean) {
- this.ext.set_boolean(FULLSCREEN_LAUNCHER, enable)
+ this.ext.set_boolean(FULLSCREEN_LAUNCHER, enable);
}
set_gap_inner(gap: number) {
@@ -233,6 +238,10 @@ export class ExtensionSettings {
this.ext.set_boolean(SMART_GAPS, set);
}
+ set_auto_unstack(set: boolean) {
+ this.ext.set_boolean(AUTO_UNSTACK, set);
+ }
+
set_snap_to_grid(set: boolean) {
this.ext.set_boolean(SNAP_TO_GRID, set);
}
diff --git a/src/tiling.ts b/src/tiling.ts
index c5c16dce..6717f9b2 100644
--- a/src/tiling.ts
+++ b/src/tiling.ts
@@ -392,6 +392,15 @@ export class Tiler {
detach(Lib.Orientation.VERTICAL, true)
break
}
+ if (ext.moved_by_mouse && inner.entities.length === 1 && ext.settings.auto_unstack()) {
+ const ent = inner.entities[0]
+ const win = ext.windows.get(ent)
+ const fork = ext.auto_tiler.get_parent_fork(ent)
+ if (fork && win) {
+ ext.auto_tiler.unstack(ext, fork, win)
+ ext.auto_tiler.tile(ext, fork, fork.area)
+ }
+ }
}
move_auto_(ext: Ext, mov1: Rectangle, mov2: Rectangle, callback: (m: Rectangle, a: Rectangle, mov: Rectangle) => boolean) {