nixos-configs/config/ags/js/overview/main.js

274 lines
8.8 KiB
JavaScript
Raw Normal View History

const { Window, Box, CenterBox, Icon, Revealer, EventBox } = ags.Widget;
const { closeWindow } = ags.App;
const { execAsync } = ags.Utils;
2023-09-23 14:53:25 -04:00
const { Hyprland } = ags.Service;
const { Gtk, Gdk } = imports.gi;
import Cairo from 'cairo';
import { Button } from '../misc/cursorbox.js';
import { PopUp } from '../misc/popup.js';
const WORKSPACE_PER_ROW = 6;
const SCALE = 0.11;
const ICON_SCALE = 0.8;
const MARGIN = 8;
2023-09-20 21:52:04 -04:00
const SCREEN = {
X: 1920,
Y: 1200,
}
const DEFAULT_SPECIAL = {
SIZE_X: 1524,
SIZE_Y: 908,
POS_X: 197,
POS_Y: 170,
};
const IconStyle = app => `min-width: ${app.size[0] * SCALE - MARGIN}px;
min-height: ${app.size[1] * SCALE - MARGIN}px;
font-size: ${Math.min(app.size[0] * SCALE - MARGIN,
app.size[1] * SCALE - MARGIN) * ICON_SCALE}px;`;
Array.prototype.remove = function (el) { this.splice(this.indexOf(el), 1) };
2023-09-23 14:53:25 -04:00
const TARGET = [Gtk.TargetEntry.new('text/plain', Gtk.TargetFlags.SAME_APP, 0)];
export function createSurfaceFromWidget(widget) {
const alloc = widget.get_allocation();
const surface = new Cairo.ImageSurface(
Cairo.Format.ARGB32,
alloc.width,
alloc.height,
);
const cr = new Cairo.Context(surface);
cr.setSourceRGBA(255, 255, 255, 0);
cr.rectangle(0, 0, alloc.width, alloc.height);
cr.fill();
widget.draw(cr);
return surface;
}
const WorkspaceRow = (className, i) => Revealer({
transition: 'slide_down',
connections: [[Hyprland, rev => {
rev.revealChild = Hyprland.workspaces.some(ws => ws.id > i * WORKSPACE_PER_ROW &&
(ws.windows > 0 ||
ws.id === Hyprland.active.workspace.id));
}]],
child: CenterBox({
children: [null, Box({
className: className,
}), null],
}),
});
const OverviewWidget = Box({
className: 'overview',
vertical: true,
children: [
Box({
vertical: true,
children: [
WorkspaceRow('normal', 0),
],
}),
Box({
vertical: true,
children: [
WorkspaceRow('special', 0),
],
}),
],
connections: [
[Hyprland, box => {
box._getWorkspaces(box);
box._updateWs(box);
box._updateApps(box);
}],
],
properties: [
['workspaces'],
['getWorkspaces', box => {
let children = [];
box.children.forEach(type => {
type.children.forEach(row => {
row.child.centerWidget.children.forEach(ch => {
children.push(ch);
});
});
});
box._workspaces = children.sort((a, b) => a._id - b._id);
}],
['updateWs', box => {
Hyprland.workspaces.forEach(ws => {
let currentWs = box._workspaces.find(ch => ch._id == ws.id);
if (!currentWs) {
var type = 0;
var rowNo = 0;
if (ws.id < 0) {
// This means it's a special workspace
type = 1;
}
else {
rowNo = Math.floor((ws.id - 1) / WORKSPACE_PER_ROW);
if (rowNo >= box.children[type].children.length) {
for (let i = box.children[type].children.length; i <= rowNo; ++i) {
box.children[type].add(WorkspaceRow('normal', i));
}
}
}
var row = box.children[type].children[rowNo].child.centerWidget;
currentWs = Revealer({
transition: 'slide_right',
properties: [
['id', ws.id],
],
connections: [[Hyprland, box => {
let active = Hyprland.active.workspace.id === box._id;
box.child.child.toggleClassName('active', active);
box.revealChild = Hyprland.getWorkspace(box._id)?.windows > 0 || active;
}]],
child: EventBox({
tooltipText: `Workspace: ${ws.id}`,
child: Box({
className: 'workspace',
style: `min-width: ${SCREEN.X * SCALE}px;
min-height: ${SCREEN.Y * SCALE}px;`,
2023-09-23 14:53:25 -04:00
setup: eventbox => {
eventbox.drag_dest_set(Gtk.DestDefaults.ALL, TARGET, Gdk.DragAction.COPY);
eventbox.connect('drag-data-received', (_w, _c, _x, _y, data) => {
execAsync(`hyprctl dispatch movetoworkspacesilent ${ws.id},address:${data.get_text()}`)
.catch(print);
});
},
child: ags.Widget({
type: Gtk.Fixed,
}),
}),
}),
});
row.add(currentWs);
}
});
box.show_all();
// Make sure the order is correct
box._workspaces.forEach((workspace, i) => {
workspace.get_parent().reorder_child(workspace, i)
});
}],
['updateApps', box => {
ags.Utils.execAsync('hyprctl clients -j')
.then(result => {
let clients = JSON.parse(result).filter(client => client.class)
box._workspaces.forEach(workspace => {
let fixed = workspace.child.child.children[0];
let toRemove = fixed.get_children();
clients.filter(app => app.workspace.id == workspace._id).forEach(app => {
let active = '';
if (app.address == Hyprland.active.client.address) {
active = 'active';
}
// Special workspaces that haven't been opened yet
// return a size of 0. We need to set them to default
// values to show the workspace properly
if (app.size[0] === 0) {
app.size[0] = DEFAULT_SPECIAL.SIZE_X;
app.size[1] = DEFAULT_SPECIAL.SIZE_Y;
app.at[0] = DEFAULT_SPECIAL.POS_X;
app.at[1] = DEFAULT_SPECIAL.POS_Y;
}
let existingApp = fixed.get_children().find(ch => ch._address == app.address);
toRemove.remove(existingApp);
if (existingApp) {
fixed.move(
existingApp,
app.at[0] * SCALE,
app.at[1] * SCALE,
);
existingApp.child.child.className = `window ${active}`;
existingApp.child.child.style = IconStyle(app);
}
else {
fixed.put(
Revealer({
2023-09-23 14:53:25 -04:00
transition: 'crossfade',
setup: rev => {
rev.revealChild = true;
2023-09-23 14:53:25 -04:00
},
properties: [
['address', app.address],
['toDestroy', false]
],
child: Button({
2023-09-23 14:53:25 -04:00
onSecondaryClickRelease: () => {
execAsync(`hyprctl dispatch closewindow address:${address}`)
.catch(print)
},
onPrimaryClickRelease: () => {
if (app.class === 'thunderbird' || app.class === 'Spotify')
execAsync(['bash', '-c', `$AGS_PATH/launch-app.sh ${app.class}`])
.then(() => closeWindow('overview'))
.catch(print);
else
execAsync(`hyprctl dispatch focuswindow address:${app.address}`)
.then(() => closeWindow('overview'))
.catch(print);
},
2023-09-23 14:53:25 -04:00
setup: button => {
button.drag_source_set(Gdk.ModifierType.BUTTON1_MASK, TARGET, Gdk.DragAction.COPY);
button.connect('drag-data-get', (_w, _c, data) => data.set_text(app.address, app.address.length));
button.connect('drag-begin', (_, context) => {
Gtk.drag_set_icon_surface(context, createSurfaceFromWidget(button));
button.get_parent().revealChild = false;
});
button.connect('drag-end', () => button.get_parent().revealChild = true);
},
child: Icon({
className: `window ${active}`,
style: IconStyle(app),
icon: app.class,
}),
}),
}),
app.at[0] * SCALE,
app.at[1] * SCALE,
);
}
});
fixed.show_all();
toRemove.forEach(ch => {
if (ch._toDestroy) {
ch.destroy();
}
else {
ch.revealChild = false;
ch._toDestroy = true;
}
});
});
}).catch(print);
}],
],
});
export const Overview = Window({
name: 'overview',
layer: 'overlay',
child: PopUp({
name: 'overview',
transition: 'crossfade',
child: OverviewWidget,
}),
});