Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 17 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,13 +35,23 @@ For configuration, please use the built-in preferences dialog (Gnome Tweak Tool
* You can set the margins between the windows as well

## Edit Layouts:
To edit a layout follow the instructions below
* Click "Edit Layout" (will edit the currently applied layout).
* Left-Click to split the region
* Middle-Click to split with opposite orientation
* Right-Click to remove

When done click "Stop Editing"
To edit a layout, click **"Edit Layout"** (this will edit the currently applied layout).

### Grid Layouts
* **Left-Click** a zone to split it vertically.
* **Middle-Click** a zone to split it horizontally.
* **Right-Click** a zone to remove it.
* **Shift + Click** or **Ctrl + Click** a zone to toggle it between an active snapping zone and a blank spacer zone (invisible during normal snapping).

### Canvas / Overlapping Layouts
* **Drag and drop** any zone to move it.
* **Drag zone edges or corners** to resize it.
* **Double-Click in empty space** to create a new zone.
* **Right-Click** a zone to remove it.
* **Left-Click** a zone to split it vertically.
* **Middle-Click** a zone to split it horizontally.

When done, click **"Save Layout"** or **"Cancel Editing"**.

Layouts are stored in the default config dir (usually `~/.config/gSnap`). This directory
should be user-writable, to save the layouts changes.
Expand Down
48 changes: 34 additions & 14 deletions src/extension/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -701,6 +701,7 @@ export default class App extends Extension {
let dialog = new LayoutNameDialog(
`Rename Layout ${currentMonitorLayout.name}`,
currentMonitorLayout.name,
false,
(text: string) => {
currentMonitorLayout.name = text;
this.saveLayouts();
Expand All @@ -713,19 +714,39 @@ export default class App extends Extension {
let dialog = new LayoutNameDialog(
`Create new layout`,
'New Layout',
(text: string) => {
this.layouts.definitions.push({
name: text,
type: 0,
length: 100,
items: [
{
type: 0,
length: 100,
items: []
}
]
});
true,
(text: string, isCanvas: boolean) => {
if (isCanvas) {
this.layouts.definitions.push({
name: text,
type: 2,
length: 100,
items: [
{
type: 0,
length: 100,
x: 20,
y: 20,
w: 60,
h: 60,
items: []
}
]
});
} else {
this.layouts.definitions.push({
name: text,
type: 0,
length: 100,
items: [
{
type: 0,
length: 100,
items: []
}
]
});
}
this.setLayout(this.layouts.definitions.length - 1);
this.saveLayouts();
this.reloadMenu();
Expand All @@ -741,7 +762,6 @@ export default class App extends Extension {

this.editor[m.index]?.destroy();
this.editor[m.index] = new ZoneEditor(activeMonitors()[m.index], editLayout, gridSettings[SETTINGS.WINDOW_MARGIN]);
this.editor[m.index]?.init();
this.editor[m.index]?.show();
});

Expand Down
160 changes: 117 additions & 43 deletions src/extension/dialogs.ts
Original file line number Diff line number Diff line change
@@ -1,44 +1,118 @@
// @ts-ignore
import * as Dialog from 'resource:///org/gnome/shell/ui/dialog.js';
// @ts-ignore
import * as ModalDialog from 'resource:///org/gnome/shell/ui/modalDialog.js';
// @ts-ignore
import St from 'gi://St';
// @ts-ignore
import Clutter from 'gi://Clutter';


export class LayoutNameDialog {
private _modalDialog: ModalDialog.ModalDialog;
private _okayCallback: (text: string) => void;

constructor(title: string, layoutName: string, okayCallback: (text: string) => void) {
this._modalDialog = new ModalDialog.ModalDialog({});
this._okayCallback = okayCallback;

const listLayout = new Dialog.ListSection({ title });
this._modalDialog.contentLayout.add_child(listLayout);

const entry = new St.Entry({ text: layoutName });
listLayout.add_child(entry);

this._modalDialog.setButtons([
{
label: "Ok",
key: Clutter.Return,
action: () => {
this._okayCallback(entry.text);
this._modalDialog.close();
},
},
{
label: "Cancel",
key: Clutter.Escape,
action: () => this._modalDialog.close(),
}]);
}

open() {
this._modalDialog.open();
}
// @ts-ignore
import * as Dialog from 'resource:///org/gnome/shell/ui/dialog.js';
// @ts-ignore
import * as ModalDialog from 'resource:///org/gnome/shell/ui/modalDialog.js';
// @ts-ignore
import St from 'gi://St';
// @ts-ignore
import Clutter from 'gi://Clutter';
// @ts-ignore
import * as PopupMenu from 'resource:///org/gnome/shell/ui/popupMenu.js';
// @ts-ignore
import * as Main from 'resource:///org/gnome/shell/ui/main.js';


export class LayoutNameDialog {
private _modalDialog: ModalDialog.ModalDialog;
private _okayCallback: (text: string, isCanvas: boolean) => void;
private _menu: any = null;
private _menuManager: any = null;

constructor(title: string, layoutName: string, showTypeSelection: boolean, okayCallback: (text: string, isCanvas: boolean) => void) {
this._modalDialog = new ModalDialog.ModalDialog({});
this._okayCallback = okayCallback;

const listLayout = new Dialog.ListSection({ title });
this._modalDialog.contentLayout.add_child(listLayout);

const entry = new St.Entry({ text: layoutName });
listLayout.add_child(entry);

let isCanvas = false;
if (showTypeSelection) {
const row = new St.BoxLayout({
vertical: false,
x_expand: true,
y_align: Clutter.ActorAlign.CENTER,
style: 'margin-top: 10px;'
});

const label = new St.Label({
text: "Layout Type: ",
y_align: Clutter.ActorAlign.CENTER,
x_expand: true
});
row.add_child(label);

const dropdownButton = new St.Button({
label: "Grid Layout ▾",
style_class: "button",
can_focus: true,
x_align: Clutter.ActorAlign.END,
y_align: Clutter.ActorAlign.CENTER
});
dropdownButton.set_style('padding: 6px 12px; border: 1px solid rgba(255,255,255,0.15); border-radius: 4px; min-width: 150px; text-align: center;');
row.add_child(dropdownButton);

this._menu = new PopupMenu.PopupMenu(dropdownButton, 0.5, St.Side.BOTTOM);
Main.uiGroup.add_child(this._menu.actor);
this._menu.actor.hide();

this._menuManager = new PopupMenu.PopupMenuManager(this);
this._menuManager.addMenu(this._menu);

const gridItem = new PopupMenu.PopupMenuItem("Grid Layout");
gridItem.connect('activate', () => {
isCanvas = false;
dropdownButton.label = "Grid Layout ▾";
this._menu.close();
});
this._menu.addMenuItem(gridItem);

const canvasItem = new PopupMenu.PopupMenuItem("Canvas Layout");
canvasItem.connect('activate', () => {
isCanvas = true;
dropdownButton.label = "Canvas Layout ▾";
this._menu.close();
});
this._menu.addMenuItem(canvasItem);

dropdownButton.connect('clicked', () => {
this._menu.toggle();
});

listLayout.add_child(row);
}

this._modalDialog.setButtons([
{
label: "Ok",
key: Clutter.Return,
action: () => {
this._okayCallback(entry.text, isCanvas);
this.destroyMenu();
this._modalDialog.close();
},
},
{
label: "Cancel",
key: Clutter.Escape,
action: () => {
this.destroyMenu();
this._modalDialog.close();
},
}]);
}

destroyMenu() {
if (this._menu) {
this._menu.destroy();
this._menu = null;
}
this._menuManager = null;
}

open() {
this._modalDialog.open();
}
}
Loading