refactor(ags): typecheck more stuff
This commit is contained in:
parent
e8197651d9
commit
894fdd92f1
17 changed files with 596 additions and 404 deletions
|
@ -1,6 +1,5 @@
|
|||
import App from 'resource:///com/github/Aylur/ags/app.js';
|
||||
import Applications from 'resource:///com/github/Aylur/ags/service/applications.js';
|
||||
import Hyprland from 'resource:///com/github/Aylur/ags/service/hyprland.js';
|
||||
// TODO: find cleaner way to import this
|
||||
import { Fzf } from '../../node_modules/fzf/dist/fzf.es.js';
|
||||
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
// @ts-expect-error
|
||||
import Bluetooth from 'resource:///com/github/Aylur/ags/service/bluetooth.js';
|
||||
|
||||
import { Label, Box, EventBox, Icon, Revealer } from 'resource:///com/github/Aylur/ags/widget.js';
|
||||
|
@ -11,8 +10,8 @@ const SPACING = 5;
|
|||
export default () => {
|
||||
const icon = Icon().hook(Bluetooth, (self) => {
|
||||
if (Bluetooth.enabled) {
|
||||
self.icon = Bluetooth.connectedDevices[0] ?
|
||||
Bluetooth.connectedDevices[0].iconName :
|
||||
self.icon = Bluetooth.connected_devices[0] ?
|
||||
Bluetooth.connected_devices[0].icon_name :
|
||||
'bluetooth-active-symbolic';
|
||||
}
|
||||
else {
|
||||
|
@ -29,8 +28,8 @@ export default () => {
|
|||
Separator(SPACING),
|
||||
|
||||
Label().hook(Bluetooth, (self) => {
|
||||
self.label = Bluetooth.connectedDevices[0] ?
|
||||
`${Bluetooth.connectedDevices[0]}` :
|
||||
self.label = Bluetooth.connected_devices[0] ?
|
||||
`${Bluetooth.connected_devices[0]}` :
|
||||
'Disconnected';
|
||||
}, 'notify::connected-devices'),
|
||||
],
|
||||
|
|
|
@ -70,113 +70,104 @@ export default ({
|
|||
setup(self);
|
||||
|
||||
self
|
||||
.hook(gesture,
|
||||
/**
|
||||
* @param {Overlay} overlay
|
||||
* @param {number} realGesture
|
||||
*/
|
||||
(overlay, realGesture) => {
|
||||
if (realGesture) {
|
||||
Array.from(overlay.attribute.list())
|
||||
.forEach((over) => {
|
||||
over.visible = true;
|
||||
});
|
||||
}
|
||||
else {
|
||||
overlay.attribute.showTopOnly();
|
||||
}
|
||||
.hook(gesture, (_, realGesture) => {
|
||||
if (realGesture) {
|
||||
Array.from(self.attribute.list())
|
||||
.forEach((over) => {
|
||||
over.visible = true;
|
||||
});
|
||||
}
|
||||
else {
|
||||
self.attribute.showTopOnly();
|
||||
}
|
||||
|
||||
// Don't allow gesture when only one player
|
||||
if (overlay.attribute.list().length <= 1) {
|
||||
return;
|
||||
}
|
||||
// Don't allow gesture when only one player
|
||||
if (self.attribute.list().length <= 1) {
|
||||
return;
|
||||
}
|
||||
|
||||
overlay.attribute.dragging = true;
|
||||
let offset = gesture.get_offset()[1];
|
||||
const playerBox = overlay.attribute.list().at(-1);
|
||||
self.attribute.dragging = true;
|
||||
let offset = gesture.get_offset()[1];
|
||||
const playerBox = self.attribute.list().at(-1);
|
||||
|
||||
if (!offset) {
|
||||
return;
|
||||
}
|
||||
if (!offset) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Slide right
|
||||
if (offset >= 0) {
|
||||
playerBox.setCss(`
|
||||
// Slide right
|
||||
if (offset >= 0) {
|
||||
playerBox.setCss(`
|
||||
margin-left: ${offset}px;
|
||||
margin-right: -${offset}px;
|
||||
${playerBox.attribute.bgStyle}
|
||||
`);
|
||||
}
|
||||
}
|
||||
|
||||
// Slide left
|
||||
else {
|
||||
offset = Math.abs(offset);
|
||||
playerBox.setCss(`
|
||||
// Slide left
|
||||
else {
|
||||
offset = Math.abs(offset);
|
||||
playerBox.setCss(`
|
||||
margin-left: -${offset}px;
|
||||
margin-right: ${offset}px;
|
||||
${playerBox.attribute.bgStyle}
|
||||
`);
|
||||
}
|
||||
},
|
||||
'drag-update')
|
||||
}
|
||||
}, 'drag-update')
|
||||
|
||||
|
||||
.hook(gesture,
|
||||
/** @param {Overlay} overlay */
|
||||
(overlay) => {
|
||||
// Don't allow gesture when only one player
|
||||
if (overlay.attribute.list().length <= 1) {
|
||||
return;
|
||||
}
|
||||
.hook(gesture, () => {
|
||||
// Don't allow gesture when only one player
|
||||
if (self.attribute.list().length <= 1) {
|
||||
return;
|
||||
}
|
||||
|
||||
overlay.attribute.dragging = false;
|
||||
const offset = gesture.get_offset()[1];
|
||||
self.attribute.dragging = false;
|
||||
const offset = gesture.get_offset()[1];
|
||||
|
||||
const playerBox = overlay.attribute.list().at(-1);
|
||||
const playerBox = self.attribute.list().at(-1);
|
||||
|
||||
// If crosses threshold after letting go, slide away
|
||||
if (offset && Math.abs(offset) > MAX_OFFSET) {
|
||||
// Disable inputs during animation
|
||||
widget.sensitive = false;
|
||||
// If crosses threshold after letting go, slide away
|
||||
if (offset && Math.abs(offset) > MAX_OFFSET) {
|
||||
// Disable inputs during animation
|
||||
widget.sensitive = false;
|
||||
|
||||
// Slide away right
|
||||
if (offset >= 0) {
|
||||
playerBox.setCss(`
|
||||
// Slide away right
|
||||
if (offset >= 0) {
|
||||
playerBox.setCss(`
|
||||
${TRANSITION}
|
||||
margin-left: ${OFFSCREEN}px;
|
||||
margin-right: -${OFFSCREEN}px;
|
||||
opacity: 0.7; ${playerBox.attribute.bgStyle}
|
||||
`);
|
||||
}
|
||||
}
|
||||
|
||||
// Slide away left
|
||||
else {
|
||||
playerBox.setCss(`
|
||||
// Slide away left
|
||||
else {
|
||||
playerBox.setCss(`
|
||||
${TRANSITION}
|
||||
margin-left: -${OFFSCREEN}px;
|
||||
margin-right: ${OFFSCREEN}px;
|
||||
opacity: 0.7; ${playerBox.attribute.bgStyle}
|
||||
`);
|
||||
}
|
||||
|
||||
timeout(ANIM_DURATION, () => {
|
||||
// Put the player in the back after anim
|
||||
overlay.reorder_overlay(playerBox, 0);
|
||||
// Recenter player
|
||||
playerBox.setCss(playerBox.attribute.bgStyle);
|
||||
|
||||
widget.sensitive = true;
|
||||
|
||||
overlay.attribute.showTopOnly();
|
||||
});
|
||||
}
|
||||
else {
|
||||
// Recenter with transition for animation
|
||||
playerBox.setCss(`${TRANSITION}
|
||||
|
||||
timeout(ANIM_DURATION, () => {
|
||||
// Put the player in the back after anim
|
||||
self.reorder_overlay(playerBox, 0);
|
||||
// Recenter player
|
||||
playerBox.setCss(playerBox.attribute.bgStyle);
|
||||
|
||||
widget.sensitive = true;
|
||||
|
||||
self.attribute.showTopOnly();
|
||||
});
|
||||
}
|
||||
else {
|
||||
// Recenter with transition for animation
|
||||
playerBox.setCss(`${TRANSITION}
|
||||
${playerBox.attribute.bgStyle}`);
|
||||
}
|
||||
},
|
||||
'drag-end');
|
||||
}
|
||||
}, 'drag-end');
|
||||
},
|
||||
});
|
||||
|
||||
|
|
|
@ -26,6 +26,7 @@ const OSDs = () => {
|
|||
},
|
||||
});
|
||||
|
||||
// Send reference of stack to all items
|
||||
stack.items = OSDList.map((osd, i) => [`${i}`, osd(stack)]);
|
||||
|
||||
// Delay popup method so it
|
||||
|
|
|
@ -7,7 +7,16 @@ import { timeout } from 'resource:///com/github/Aylur/ags/utils.js';
|
|||
import { WindowButton } from './dragndrop.js';
|
||||
import * as VARS from './variables.js';
|
||||
|
||||
/**
|
||||
* @typedef {import('types/service/hyprland.js').Client} Client
|
||||
* @typedef {import('types/widgets/revealer').default} Revealer
|
||||
* @typedef {import('types/widgets/box').default} Box
|
||||
*/
|
||||
|
||||
/** @param {number} size */
|
||||
const scale = (size) => (size * VARS.SCALE) - VARS.MARGIN;
|
||||
|
||||
/** @param {Client} client */
|
||||
const getFontSize = (client) => {
|
||||
const valX = scale(client.size[0]) * VARS.ICON_SCALE;
|
||||
const valY = scale(client.size[1]) * VARS.ICON_SCALE;
|
||||
|
@ -17,6 +26,7 @@ const getFontSize = (client) => {
|
|||
return size <= 0 ? 0.1 : size;
|
||||
};
|
||||
|
||||
/** @param {Client} client */
|
||||
const IconStyle = (client) => `
|
||||
min-width: ${scale(client.size[0])}px;
|
||||
min-height: ${scale(client.size[1])}px;
|
||||
|
@ -24,6 +34,12 @@ const IconStyle = (client) => `
|
|||
`;
|
||||
|
||||
|
||||
/**
|
||||
* @param {Client} client
|
||||
* @param {boolean} active
|
||||
* @param {Array<Client>} clients
|
||||
* @param {Box} box
|
||||
*/
|
||||
const Client = (client, active, clients, box) => {
|
||||
const wsName = String(client.workspace.name).replace('special:', '');
|
||||
const wsId = client.workspace.id;
|
||||
|
@ -31,42 +47,40 @@ const Client = (client, active, clients, box) => {
|
|||
|
||||
return Revealer({
|
||||
transition: 'crossfade',
|
||||
reveal_child: true,
|
||||
|
||||
setup: (rev) => {
|
||||
rev.revealChild = true;
|
||||
attribute: {
|
||||
address: client.address,
|
||||
to_destroy: false,
|
||||
},
|
||||
|
||||
properties: [
|
||||
['address', client.address],
|
||||
['toDestroy', false],
|
||||
],
|
||||
|
||||
child: WindowButton({
|
||||
mainBox: box,
|
||||
address: client.address,
|
||||
|
||||
onSecondaryClickRelease: () => {
|
||||
on_secondary_click_release: () => {
|
||||
Hyprland.sendMessage(`dispatch closewindow ${addr}`);
|
||||
},
|
||||
|
||||
onPrimaryClickRelease: () => {
|
||||
on_primary_click_release: () => {
|
||||
if (wsId < 0) {
|
||||
if (client.workspace.name === 'special') {
|
||||
Hyprland.sendMessage('dispatch ' +
|
||||
`movetoworkspacesilent special:${wsId},${addr}`)
|
||||
.then(
|
||||
.then(() => {
|
||||
Hyprland.sendMessage('dispatch ' +
|
||||
`togglespecialworkspace ${wsId}`)
|
||||
.then(
|
||||
() => App.closeWindow('overview'),
|
||||
).catch(print),
|
||||
).catch(print);
|
||||
.then(() => {
|
||||
App.closeWindow('overview');
|
||||
}).catch(print);
|
||||
}).catch(print);
|
||||
}
|
||||
else {
|
||||
Hyprland.sendMessage('dispatch ' +
|
||||
`togglespecialworkspace ${wsName}`).then(
|
||||
() => App.closeWindow('overview'),
|
||||
).catch(print);
|
||||
`togglespecialworkspace ${wsName}`)
|
||||
.then(() => {
|
||||
App.closeWindow('overview');
|
||||
}).catch(print);
|
||||
}
|
||||
}
|
||||
else {
|
||||
|
@ -86,14 +100,15 @@ const Client = (client, active, clients, box) => {
|
|||
.catch(print);
|
||||
}
|
||||
|
||||
Hyprland.sendMessage(`dispatch focuswindow ${addr}`).then(
|
||||
() => App.closeWindow('overview'),
|
||||
).catch(print);
|
||||
Hyprland.sendMessage(`dispatch focuswindow ${addr}`)
|
||||
.then(() => {
|
||||
App.closeWindow('overview');
|
||||
}).catch(print);
|
||||
}
|
||||
},
|
||||
|
||||
child: Icon({
|
||||
className: `window ${active}`,
|
||||
class_name: `window ${active ? 'active' : ''}`,
|
||||
css: `${IconStyle(client)} font-size: 10px;`,
|
||||
icon: client.class,
|
||||
}),
|
||||
|
@ -101,69 +116,79 @@ const Client = (client, active, clients, box) => {
|
|||
});
|
||||
};
|
||||
|
||||
/** @param {Box} box */
|
||||
export const updateClients = (box) => {
|
||||
Hyprland.sendMessage('j/clients').then((out) => {
|
||||
const clients = JSON.parse(out).filter((client) => client.class);
|
||||
/** @type Array<Client> */
|
||||
let clients = JSON.parse(out);
|
||||
|
||||
box._workspaces.forEach((workspace) => {
|
||||
const fixed = workspace.getFixed();
|
||||
const toRemove = fixed.get_children();
|
||||
clients = clients.filter((client) => client.class);
|
||||
|
||||
clients.filter((client) => client.workspace.id === workspace._id)
|
||||
.forEach((client) => {
|
||||
let active = '';
|
||||
box.attribute.workspaces.forEach(
|
||||
/** @param {Revealer} workspace */
|
||||
(workspace) => {
|
||||
const fixed = workspace.attribute.get_fixed();
|
||||
/** @type Array<Revealer> */
|
||||
const toRemove = fixed.get_children();
|
||||
|
||||
if (client.address === Hyprland.active.client.address) {
|
||||
active = 'active';
|
||||
}
|
||||
clients.filter((client) =>
|
||||
client.workspace.id === workspace.attribute.id)
|
||||
.forEach((client) => {
|
||||
const active =
|
||||
client.address === Hyprland.active.client.address;
|
||||
|
||||
// TODO: fix multi monitor issue. this is just a temp fix
|
||||
client.at[1] -= 2920;
|
||||
// TODO: fix multi monitor issue. this is just a temp fix
|
||||
client.at[1] -= 2920;
|
||||
|
||||
// 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 (client.size[0] === 0) {
|
||||
client.size[0] = VARS.DEFAULT_SPECIAL.SIZE_X;
|
||||
client.size[1] = VARS.DEFAULT_SPECIAL.SIZE_Y;
|
||||
client.at[0] = VARS.DEFAULT_SPECIAL.POS_X;
|
||||
client.at[1] = VARS.DEFAULT_SPECIAL.POS_Y;
|
||||
}
|
||||
// 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 (client.size[0] === 0) {
|
||||
client.size[0] = VARS.DEFAULT_SPECIAL.SIZE_X;
|
||||
client.size[1] = VARS.DEFAULT_SPECIAL.SIZE_Y;
|
||||
client.at[0] = VARS.DEFAULT_SPECIAL.POS_X;
|
||||
client.at[1] = VARS.DEFAULT_SPECIAL.POS_Y;
|
||||
}
|
||||
|
||||
const newClient = [
|
||||
fixed.get_children()
|
||||
.find((ch) => ch._address === client.address),
|
||||
client.at[0] * VARS.SCALE,
|
||||
client.at[1] * VARS.SCALE,
|
||||
];
|
||||
const newClient = [
|
||||
fixed.get_children().find(
|
||||
/** @param {typeof WindowButton} ch */
|
||||
// @ts-expect-error
|
||||
(ch) => ch.attribute.address === client.address,
|
||||
),
|
||||
client.at[0] * VARS.SCALE,
|
||||
client.at[1] * VARS.SCALE,
|
||||
];
|
||||
|
||||
// If it exists already
|
||||
if (newClient[0]) {
|
||||
toRemove.splice(toRemove.indexOf(newClient[0]), 1);
|
||||
fixed.move(...newClient);
|
||||
// If it exists already
|
||||
if (newClient[0]) {
|
||||
toRemove.splice(toRemove.indexOf(newClient[0]), 1);
|
||||
fixed.move(...newClient);
|
||||
}
|
||||
else {
|
||||
newClient[0] = Client(client, active, clients, box);
|
||||
fixed.put(...newClient);
|
||||
}
|
||||
|
||||
// Set a timeout here to have an animation when the icon first appears
|
||||
timeout(1, () => {
|
||||
newClient[0].child.child.className =
|
||||
`window ${active}`;
|
||||
newClient[0].child.child.setCss(IconStyle(client));
|
||||
});
|
||||
});
|
||||
|
||||
fixed.show_all();
|
||||
toRemove.forEach((ch) => {
|
||||
if (ch.attribute.to_destroy) {
|
||||
ch.destroy();
|
||||
}
|
||||
else {
|
||||
newClient[0] = Client(client, active, clients, box);
|
||||
fixed.put(...newClient);
|
||||
ch.reveal_child = false;
|
||||
ch.attribute.to_destroy = true;
|
||||
}
|
||||
|
||||
// Set a timeout here to have an animation when the icon first appears
|
||||
timeout(1, () => {
|
||||
newClient[0].child.child.className = `window ${active}`;
|
||||
newClient[0].child.child.setCss(IconStyle(client));
|
||||
});
|
||||
});
|
||||
|
||||
fixed.show_all();
|
||||
toRemove.forEach((ch) => {
|
||||
if (ch._toDestroy) {
|
||||
ch.destroy();
|
||||
}
|
||||
else {
|
||||
ch.revealChild = false;
|
||||
ch._toDestroy = true;
|
||||
}
|
||||
});
|
||||
});
|
||||
},
|
||||
);
|
||||
}).catch(print);
|
||||
};
|
||||
|
|
|
@ -11,23 +11,39 @@ const DEFAULT_STYLE = `
|
|||
border-radius: 10px;
|
||||
`;
|
||||
|
||||
/**
|
||||
* @typedef {import('types/widgets/box').default} Box
|
||||
* @typedef {import('types/widgets/revealer').default} Revealer
|
||||
*/
|
||||
|
||||
|
||||
export const Highlighter = () => Box({
|
||||
vpack: 'start',
|
||||
hpack: 'start',
|
||||
className: 'workspace active',
|
||||
class_name: 'workspace active',
|
||||
css: DEFAULT_STYLE,
|
||||
});
|
||||
|
||||
/**
|
||||
* @param {Box} main
|
||||
* @param {Box} highlighter
|
||||
*/
|
||||
export const updateCurrentWorkspace = (main, highlighter) => {
|
||||
const currentId = Hyprland.active.workspace.id;
|
||||
const row = Math.floor((currentId - 1) / VARS.WORKSPACE_PER_ROW);
|
||||
|
||||
// @ts-expect-error
|
||||
const rowObject = main.children[0].children[row];
|
||||
const workspaces = rowObject.child.centerWidget.child
|
||||
.get_children().filter((w) => w.revealChild);
|
||||
.get_children().filter(
|
||||
/** @param {Revealer} w */
|
||||
(w) => w.reveal_child,
|
||||
);
|
||||
|
||||
const currentIndex = workspaces.findIndex((w) => w._id === currentId);
|
||||
const currentIndex = workspaces.findIndex(
|
||||
/** @param {Revealer} w */
|
||||
(w) => w.attribute.id === currentId,
|
||||
);
|
||||
const left = currentIndex * ((VARS.SCREEN.X * VARS.SCALE) + PADDING);
|
||||
const height = row * ((VARS.SCREEN.Y * VARS.SCALE) + (PADDING / 2));
|
||||
|
||||
|
|
|
@ -9,7 +9,15 @@ import { updateClients } from './clients.js';
|
|||
|
||||
const TARGET = [Gtk.TargetEntry.new('text/plain', Gtk.TargetFlags.SAME_APP, 0)];
|
||||
|
||||
/**
|
||||
* @typedef {import('types/widgets/button').default} Button
|
||||
* @typedef {import('types/widgets/button').ButtonProps} ButtonProps
|
||||
* @typedef {import('types/widgets/eventbox').EventBoxProps=} EventBoxProps
|
||||
* @typedef {import('types/widgets/box').default} Box
|
||||
*/
|
||||
|
||||
|
||||
/** @param {Button} widget */
|
||||
const createSurfaceFromWidget = (widget) => {
|
||||
const alloc = widget.get_allocation();
|
||||
const surface = new Cairo.ImageSurface(
|
||||
|
@ -29,16 +37,19 @@ const createSurfaceFromWidget = (widget) => {
|
|||
|
||||
let hidden = 0;
|
||||
|
||||
export const WorkspaceDrop = (props) => EventBox({
|
||||
/** @params {EventBoxProps} props */
|
||||
export const WorkspaceDrop = ({ ...props }) => EventBox({
|
||||
...props,
|
||||
setup: (self) => {
|
||||
self.drag_dest_set(Gtk.DestDefaults.ALL, TARGET, Gdk.DragAction.COPY);
|
||||
|
||||
self.on('drag-data-received', (_, _c, _x, _y, data) => {
|
||||
let id = self.get_parent()._id;
|
||||
// @ts-expect-error
|
||||
let id = self.get_parent()?.attribute.id;
|
||||
|
||||
if (id < -1) {
|
||||
id = self.get_parent()._name;
|
||||
// @ts-expect-error
|
||||
id = self.get_parent()?.attribute.name;
|
||||
}
|
||||
|
||||
else if (id === -1) {
|
||||
|
@ -56,11 +67,17 @@ export const WorkspaceDrop = (props) => EventBox({
|
|||
},
|
||||
});
|
||||
|
||||
/**
|
||||
* @param {ButtonProps & {
|
||||
* address: string
|
||||
* mainBox: Box
|
||||
* }} o
|
||||
*/
|
||||
export const WindowButton = ({
|
||||
address,
|
||||
mainBox,
|
||||
...props
|
||||
} = {}) => Button({
|
||||
}) => Button({
|
||||
...props,
|
||||
|
||||
cursor: 'pointer',
|
||||
|
@ -78,11 +95,12 @@ export const WindowButton = ({
|
|||
|
||||
self.on('drag-begin', (_, context) => {
|
||||
Gtk.drag_set_icon_surface(context, createSurfaceFromWidget(self));
|
||||
self.get_parent().revealChild = false;
|
||||
// @ts-expect-error
|
||||
self.get_parent()?.set_reveal_child(false);
|
||||
});
|
||||
|
||||
self.on('drag-end', () => {
|
||||
self.get_parent().destroy();
|
||||
self.get_parent()?.destroy();
|
||||
|
||||
updateClients(mainBox);
|
||||
});
|
||||
|
|
|
@ -9,21 +9,30 @@ import { Highlighter, updateCurrentWorkspace } from './current-workspace.js';
|
|||
import { updateClients } from './clients.js';
|
||||
|
||||
|
||||
|
||||
|
||||
// TODO: have a 'page' for each monitor, arrows on both sides to loop through
|
||||
export const Overview = () => {
|
||||
const highlighter = Highlighter();
|
||||
|
||||
const mainBox = Box({
|
||||
// Do this for scss hierarchy
|
||||
className: 'overview',
|
||||
class_name: 'overview',
|
||||
css: 'all: unset',
|
||||
|
||||
vertical: true,
|
||||
vpack: 'center',
|
||||
hpack: 'center',
|
||||
|
||||
attribute: {
|
||||
workspaces: [],
|
||||
|
||||
update: () => {
|
||||
getWorkspaces(mainBox);
|
||||
updateWorkspaces(mainBox);
|
||||
updateClients(mainBox);
|
||||
updateCurrentWorkspace(mainBox, highlighter);
|
||||
},
|
||||
},
|
||||
|
||||
children: [
|
||||
Box({
|
||||
vertical: true,
|
||||
|
@ -42,31 +51,24 @@ export const Overview = () => {
|
|||
|
||||
setup: (self) => {
|
||||
self.hook(Hyprland, () => {
|
||||
if (!App.getWindow('overview').visible) {
|
||||
if (!App.getWindow('overview')?.visible) {
|
||||
return;
|
||||
}
|
||||
|
||||
self.update();
|
||||
self?.attribute.update();
|
||||
});
|
||||
},
|
||||
|
||||
properties: [
|
||||
['workspaces'],
|
||||
],
|
||||
});
|
||||
|
||||
mainBox.update = () => {
|
||||
getWorkspaces(mainBox);
|
||||
updateWorkspaces(mainBox);
|
||||
updateClients(mainBox);
|
||||
updateCurrentWorkspace(mainBox, highlighter);
|
||||
};
|
||||
|
||||
const widget = Overlay({
|
||||
overlays: [highlighter, mainBox],
|
||||
|
||||
attribute: {
|
||||
get_child: () => mainBox,
|
||||
},
|
||||
|
||||
child: Box({
|
||||
className: 'overview',
|
||||
class_name: 'overview',
|
||||
css: `
|
||||
min-height: ${mainBox.get_allocated_height()}px;
|
||||
min-width: ${mainBox.get_allocated_width()}px;
|
||||
|
@ -77,6 +79,7 @@ export const Overview = () => {
|
|||
setup: (self) => {
|
||||
self.on('get-child-position', (_, ch) => {
|
||||
if (ch === mainBox) {
|
||||
// @ts-expect-error
|
||||
self.child.setCss(`
|
||||
transition: min-height 0.2s ease, min-width 0.2s ease;
|
||||
min-height: ${mainBox.get_allocated_height()}px;
|
||||
|
@ -87,8 +90,6 @@ export const Overview = () => {
|
|||
},
|
||||
});
|
||||
|
||||
widget.getChild = () => mainBox;
|
||||
|
||||
return widget;
|
||||
};
|
||||
|
||||
|
@ -99,7 +100,7 @@ export default () => {
|
|||
close_on_unfocus: 'none',
|
||||
onOpen: () => {
|
||||
win.attribute.set_child(Overview());
|
||||
win.attribute.get_child().getChild().update();
|
||||
win.attribute.get_child().attribute.get_child().attribute.update();
|
||||
},
|
||||
});
|
||||
|
||||
|
|
|
@ -10,42 +10,69 @@ const DEFAULT_STYLE = `
|
|||
min-height: ${VARS.SCREEN.Y * VARS.SCALE}px;
|
||||
`;
|
||||
|
||||
/**
|
||||
* @typedef {import('types/widgets/box').default} Box
|
||||
* @typedef {import('types/widgets/revealer').default} Revealer
|
||||
*/
|
||||
|
||||
|
||||
/** @param {Box} box */
|
||||
export const getWorkspaces = (box) => {
|
||||
const children = [];
|
||||
|
||||
box.children.forEach((type) => {
|
||||
type.children.forEach((row) => {
|
||||
row.child.centerWidget.child.children.forEach((ch) => {
|
||||
children.push(ch);
|
||||
});
|
||||
});
|
||||
// @ts-expect-error
|
||||
type.children.forEach(
|
||||
/** @param {Revealer} row */
|
||||
(row) => {
|
||||
// @ts-expect-error
|
||||
row.child.centerWidget.child.children.forEach(
|
||||
/** @param {Revealer} workspace */
|
||||
(workspace) => {
|
||||
children.push(workspace);
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
});
|
||||
box._workspaces = children.sort((a, b) => a._id - b._id);
|
||||
box.attribute.workspaces = children.sort((a, b) =>
|
||||
a.attribute.id - b.attribute.id);
|
||||
};
|
||||
|
||||
/**
|
||||
* @param {number} id
|
||||
* @param {string} name
|
||||
* @param {boolean} normal
|
||||
*/
|
||||
const Workspace = (id, name, normal = true) => {
|
||||
const fixed = Fixed();
|
||||
|
||||
const workspace = Revealer({
|
||||
transition: 'slide_right',
|
||||
transitionDuration: 500,
|
||||
transition_duration: 500,
|
||||
|
||||
attribute: {
|
||||
id,
|
||||
name,
|
||||
get_fixed: () => fixed,
|
||||
},
|
||||
|
||||
setup: (self) => {
|
||||
if (normal) {
|
||||
self.hook(Hyprland, () => {
|
||||
const activeId = Hyprland.active.workspace.id;
|
||||
const active = activeId === self._id;
|
||||
const active = activeId === self.attribute.id;
|
||||
const ws = Hyprland.getWorkspace(self.attribute.id);
|
||||
|
||||
self.revealChild = Hyprland.getWorkspace(self._id)
|
||||
?.windows > 0 || active;
|
||||
self.reveal_child =
|
||||
(ws?.windows && ws.windows > 0) || active;
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
child: WorkspaceDrop({
|
||||
child: Box({
|
||||
className: 'workspace',
|
||||
class_name: 'workspace',
|
||||
css: normal ?
|
||||
|
||||
DEFAULT_STYLE :
|
||||
|
@ -70,23 +97,23 @@ const Workspace = (id, name, normal = true) => {
|
|||
}),
|
||||
});
|
||||
|
||||
workspace._id = id;
|
||||
workspace._name = name;
|
||||
workspace.getFixed = () => fixed;
|
||||
|
||||
return workspace;
|
||||
};
|
||||
|
||||
export const WorkspaceRow = (className, i) => {
|
||||
/**
|
||||
* @param {string} class_name
|
||||
* @param {number} i
|
||||
*/
|
||||
export const WorkspaceRow = (class_name, i) => {
|
||||
const addWorkspace = Workspace(
|
||||
className === 'special' ? -1 : 1000,
|
||||
className === 'special' ? 'special' : '',
|
||||
class_name === 'special' ? -1 : 1000,
|
||||
class_name === 'special' ? 'special' : '',
|
||||
false,
|
||||
);
|
||||
|
||||
return Revealer({
|
||||
transition: 'slide_down',
|
||||
hpack: className === 'special' ? '' : 'start',
|
||||
hpack: class_name === 'special' ? 'fill' : 'start',
|
||||
|
||||
setup: (self) => {
|
||||
self.hook(Hyprland, (rev) => {
|
||||
|
@ -101,18 +128,18 @@ export const WorkspaceRow = (className, i) => {
|
|||
return isInRow && (hasClients || isActive);
|
||||
});
|
||||
|
||||
rev.revealChild = rowExists;
|
||||
rev.reveal_child = rowExists;
|
||||
});
|
||||
},
|
||||
|
||||
child: CenterBox({
|
||||
children: [null, EventBox({
|
||||
center_widget: EventBox({
|
||||
setup: (self) => {
|
||||
self.hook(Hyprland, () => {
|
||||
const maxId = (i + 1) * VARS.WORKSPACE_PER_ROW;
|
||||
const activeId = Hyprland.active.workspace.id;
|
||||
|
||||
const isSpecial = className === 'special';
|
||||
const isSpecial = class_name === 'special';
|
||||
const nextRowExists = Hyprland.workspaces.some((ws) => {
|
||||
const isInNextRow = ws.id > maxId;
|
||||
const hasClients = ws.windows > 0;
|
||||
|
@ -121,22 +148,26 @@ export const WorkspaceRow = (className, i) => {
|
|||
return isInNextRow && (hasClients || isActive);
|
||||
});
|
||||
|
||||
addWorkspace.revealChild = isSpecial || !nextRowExists;
|
||||
addWorkspace.reveal_child = isSpecial || !nextRowExists;
|
||||
});
|
||||
},
|
||||
|
||||
child: Box({
|
||||
className,
|
||||
class_name,
|
||||
children: [addWorkspace],
|
||||
}),
|
||||
}), null],
|
||||
}),
|
||||
}),
|
||||
});
|
||||
};
|
||||
|
||||
/** @param {Box} box */
|
||||
export const updateWorkspaces = (box) => {
|
||||
Hyprland.workspaces.forEach((ws) => {
|
||||
const currentWs = box._workspaces.find((ch) => ch._id === ws.id);
|
||||
const currentWs = box.attribute.workspaces.find(
|
||||
/** @param {Revealer} ch */
|
||||
(ch) => ch.attribute.id === ws.id,
|
||||
);
|
||||
|
||||
if (!currentWs) {
|
||||
let type = 0;
|
||||
|
@ -148,16 +179,19 @@ export const updateWorkspaces = (box) => {
|
|||
}
|
||||
else {
|
||||
rowNo = Math.floor((ws.id - 1) / VARS.WORKSPACE_PER_ROW);
|
||||
// @ts-expect-error
|
||||
const wsQty = box.children[type].children.length;
|
||||
|
||||
if (rowNo >= wsQty) {
|
||||
for (let i = wsQty; i <= rowNo; ++i) {
|
||||
// @ts-expect-error
|
||||
box.children[type].add(WorkspaceRow(
|
||||
type ? 'special' : 'normal', i,
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
// @ts-expect-error
|
||||
const row = box.children[type].children[rowNo]
|
||||
.child.centerWidget.child;
|
||||
|
||||
|
@ -166,8 +200,15 @@ export const updateWorkspaces = (box) => {
|
|||
});
|
||||
|
||||
// Make sure the order is correct
|
||||
box._workspaces.forEach((workspace, i) => {
|
||||
workspace.get_parent().reorder_child(workspace, i);
|
||||
});
|
||||
box.attribute.workspaces.forEach(
|
||||
/**
|
||||
* @param {Revealer} workspace
|
||||
* @param {number} i
|
||||
*/
|
||||
(workspace, i) => {
|
||||
// @ts-expect-error
|
||||
workspace?.get_parent()?.reorder_child(workspace, i);
|
||||
},
|
||||
);
|
||||
box.show_all();
|
||||
};
|
||||
|
|
|
@ -13,7 +13,7 @@ const PowermenuWidget = () => CenterBox({
|
|||
// @ts-expect-error
|
||||
vertical: false,
|
||||
|
||||
startWidget: CursorBox({
|
||||
start_widget: CursorBox({
|
||||
class_name: 'shutdown',
|
||||
on_primary_click_release: () => execAsync(['systemctl', 'poweroff'])
|
||||
.catch(print),
|
||||
|
@ -23,7 +23,7 @@ const PowermenuWidget = () => CenterBox({
|
|||
}),
|
||||
}),
|
||||
|
||||
centerWidget: CursorBox({
|
||||
center_widget: CursorBox({
|
||||
class_name: 'reboot',
|
||||
on_primary_click_release: () => execAsync(['systemctl', 'reboot'])
|
||||
.catch(print),
|
||||
|
@ -33,7 +33,7 @@ const PowermenuWidget = () => CenterBox({
|
|||
}),
|
||||
}),
|
||||
|
||||
endWidget: CursorBox({
|
||||
end_widget: CursorBox({
|
||||
class_name: 'logout',
|
||||
on_primary_click_release: () => Hyprland.sendMessage('dispatch exit')
|
||||
.catch(print),
|
||||
|
|
|
@ -8,56 +8,62 @@ import CursorBox from '../misc/cursorbox.js';
|
|||
const SCROLL_THRESH_H = 200;
|
||||
const SCROLL_THRESH_N = 7;
|
||||
|
||||
/**
|
||||
* @typedef {import('types/widgets/box').default} Box
|
||||
* @typedef {import('types/service/bluetooth').BluetoothDevice} BluetoothDevice
|
||||
*/
|
||||
|
||||
const BluetoothDevice = (dev) => {
|
||||
const widget = Box({
|
||||
className: 'menu-item',
|
||||
});
|
||||
/** @param {BluetoothDevice} dev */
|
||||
const BluetoothDevice = (dev) => Box({
|
||||
class_name: 'menu-item',
|
||||
|
||||
const child = Box({
|
||||
hexpand: true,
|
||||
children: [
|
||||
Icon({
|
||||
binds: [['icon', dev, 'icon-name']],
|
||||
}),
|
||||
attribute: { dev },
|
||||
|
||||
Label({
|
||||
binds: [['label', dev, 'name']],
|
||||
}),
|
||||
|
||||
Icon({
|
||||
icon: 'object-select-symbolic',
|
||||
hexpand: true,
|
||||
hpack: 'end',
|
||||
setup: (self) => {
|
||||
self.hook(dev, () => {
|
||||
self.setCss(`opacity: ${dev.paired ? '1' : '0'};`);
|
||||
});
|
||||
},
|
||||
}),
|
||||
],
|
||||
});
|
||||
|
||||
widget.dev = dev;
|
||||
widget.add(Revealer({
|
||||
revealChild: true,
|
||||
children: [Revealer({
|
||||
reveal_child: true,
|
||||
transition: 'slide_down',
|
||||
|
||||
child: CursorBox({
|
||||
on_primary_click_release: () => dev.setConnection(true),
|
||||
child,
|
||||
}),
|
||||
}));
|
||||
|
||||
return widget;
|
||||
};
|
||||
child: Box({
|
||||
hexpand: true,
|
||||
|
||||
children: [
|
||||
Icon({
|
||||
icon: dev.bind('icon_name'),
|
||||
}),
|
||||
|
||||
Label({
|
||||
label: dev.bind('name'),
|
||||
}),
|
||||
|
||||
Icon({
|
||||
icon: 'object-select-symbolic',
|
||||
hexpand: true,
|
||||
hpack: 'end',
|
||||
|
||||
}).hook(dev, (self) => {
|
||||
self.setCss(`opacity: ${dev.paired ?
|
||||
'1' :
|
||||
'0'};
|
||||
`);
|
||||
}),
|
||||
],
|
||||
}),
|
||||
}),
|
||||
})],
|
||||
});
|
||||
|
||||
export const BluetoothMenu = () => {
|
||||
const DevList = new Map();
|
||||
|
||||
const topArrow = Revealer({
|
||||
transition: 'slide_down',
|
||||
|
||||
child: Icon({
|
||||
icon: `${App.configDir }/icons/down-large.svg`,
|
||||
className: 'scrolled-indicator',
|
||||
class_name: 'scrolled-indicator',
|
||||
size: 16,
|
||||
css: '-gtk-icon-transform: rotate(180deg);',
|
||||
}),
|
||||
|
@ -65,15 +71,17 @@ export const BluetoothMenu = () => {
|
|||
|
||||
const bottomArrow = Revealer({
|
||||
transition: 'slide_up',
|
||||
|
||||
child: Icon({
|
||||
icon: `${App.configDir }/icons/down-large.svg`,
|
||||
className: 'scrolled-indicator',
|
||||
class_name: 'scrolled-indicator',
|
||||
size: 16,
|
||||
}),
|
||||
});
|
||||
|
||||
return Overlay({
|
||||
pass_through: true,
|
||||
|
||||
overlays: [
|
||||
Box({
|
||||
vpack: 'start',
|
||||
|
@ -91,7 +99,7 @@ export const BluetoothMenu = () => {
|
|||
],
|
||||
|
||||
child: Box({
|
||||
className: 'menu',
|
||||
class_name: 'menu',
|
||||
|
||||
child: Scrollable({
|
||||
hscroll: 'never',
|
||||
|
@ -101,28 +109,36 @@ export const BluetoothMenu = () => {
|
|||
self.on('edge-reached', (_, pos) => {
|
||||
// Manage scroll indicators
|
||||
if (pos === 2) {
|
||||
topArrow.revealChild = false;
|
||||
bottomArrow.revealChild = true;
|
||||
topArrow.reveal_child = false;
|
||||
bottomArrow.reveal_child = true;
|
||||
}
|
||||
else if (pos === 3) {
|
||||
topArrow.revealChild = true;
|
||||
bottomArrow.revealChild = false;
|
||||
topArrow.reveal_child = true;
|
||||
bottomArrow.reveal_child = false;
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
child: ListBox({
|
||||
setup: (self) => {
|
||||
self.set_sort_func((a, b) => {
|
||||
return b.get_children()[0].dev.paired -
|
||||
a.get_children()[0].dev.paired;
|
||||
});
|
||||
// @ts-expect-error
|
||||
self.set_sort_func(
|
||||
/**
|
||||
* @param {Box} a
|
||||
* @param {Box} b
|
||||
*/
|
||||
(a, b) => {
|
||||
// @ts-expect-error
|
||||
return b.get_children()[0].attribute.dev.paired - // eslint-disable-line
|
||||
// @ts-expect-error
|
||||
a.get_children()[0].attribute.dev.paired;
|
||||
},
|
||||
);
|
||||
|
||||
self.hook(Bluetooth, () => {
|
||||
// Get all devices
|
||||
const Devices = [].concat(
|
||||
Bluetooth.devices,
|
||||
Bluetooth.connectedDevices,
|
||||
const Devices = Bluetooth.devices.concat(
|
||||
Bluetooth.connected_devices,
|
||||
);
|
||||
|
||||
// Add missing devices
|
||||
|
@ -130,6 +146,7 @@ export const BluetoothMenu = () => {
|
|||
if (!DevList.has(dev) && dev.name) {
|
||||
DevList.set(dev, BluetoothDevice(dev));
|
||||
|
||||
// @ts-expect-error
|
||||
self.add(DevList.get(dev));
|
||||
self.show_all();
|
||||
}
|
||||
|
@ -151,7 +168,7 @@ export const BluetoothMenu = () => {
|
|||
}
|
||||
else {
|
||||
devWidget.children[0]
|
||||
.revealChild = false;
|
||||
.reveal_child = false;
|
||||
devWidget.toDestroy = true;
|
||||
}
|
||||
}
|
||||
|
@ -160,34 +177,48 @@ export const BluetoothMenu = () => {
|
|||
// Start scrolling after a specified height
|
||||
// is reached by the children
|
||||
const height = Math.max(
|
||||
self.get_parent().get_allocated_height(),
|
||||
self.get_parent()?.get_allocated_height() || 0,
|
||||
SCROLL_THRESH_H,
|
||||
);
|
||||
|
||||
const scroll = self.get_parent().get_parent();
|
||||
const scroll = self.get_parent()?.get_parent();
|
||||
|
||||
if (self.get_children().length > SCROLL_THRESH_N) {
|
||||
scroll.vscroll = 'always';
|
||||
scroll.setCss(`min-height: ${height}px;`);
|
||||
if (scroll) {
|
||||
// @ts-expect-error
|
||||
const n_child = self.get_children().length;
|
||||
|
||||
// Make bottom scroll indicator appear only
|
||||
// when first getting overflowing children
|
||||
if (!(bottomArrow.revealChild === true ||
|
||||
topArrow.revealChild === true)) {
|
||||
bottomArrow.revealChild = true;
|
||||
if (n_child > SCROLL_THRESH_N) {
|
||||
// @ts-expect-error
|
||||
scroll.vscroll = 'always';
|
||||
// @ts-expect-error
|
||||
scroll.setCss(`min-height: ${height}px;`);
|
||||
|
||||
// Make bottom scroll indicator appear only
|
||||
// when first getting overflowing children
|
||||
if (!(bottomArrow.reveal_child === true ||
|
||||
topArrow.reveal_child === true)) {
|
||||
bottomArrow.reveal_child = true;
|
||||
}
|
||||
}
|
||||
else {
|
||||
// @ts-expect-error
|
||||
scroll.vscroll = 'never';
|
||||
// @ts-expect-error
|
||||
scroll.setCss('');
|
||||
topArrow.reveal_child = false;
|
||||
bottomArrow.reveal_child = false;
|
||||
}
|
||||
}
|
||||
else {
|
||||
scroll.vscroll = 'never';
|
||||
scroll.setCss('');
|
||||
topArrow.revealChild = false;
|
||||
bottomArrow.revealChild = false;
|
||||
}
|
||||
|
||||
// Trigger sort_func
|
||||
self.get_children().forEach((ch) => {
|
||||
ch.changed();
|
||||
});
|
||||
// @ts-expect-error
|
||||
self.get_children().forEach(
|
||||
/** @param {Box} ListBoxRow */
|
||||
(ListBoxRow) => {
|
||||
// @ts-expect-error
|
||||
ListBoxRow.changed();
|
||||
},
|
||||
);
|
||||
});
|
||||
},
|
||||
}),
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
import App from 'resource:///com/github/Aylur/ags/app.js';
|
||||
// @ts-expect-error
|
||||
import Bluetooth from 'resource:///com/github/Aylur/ags/service/bluetooth.js';
|
||||
import Network from 'resource:///com/github/Aylur/ags/service/network.js';
|
||||
import Variable from 'resource:///com/github/Aylur/ags/variable.js';
|
||||
|
@ -17,16 +16,23 @@ import { BluetoothMenu } from './bluetooth.js';
|
|||
const SPACING = 28;
|
||||
const ButtonStates = [];
|
||||
|
||||
/** @typedef {import('types/widgets/widget').default} Widget */
|
||||
/**
|
||||
* @typedef {import('types/widgets/widget').default} Widget
|
||||
* @typedef {import('types/widgets/box').default} Box
|
||||
* @typedef {import('types/widgets/icon').default} Icon
|
||||
* @typedef {import('types/widgets/label').default} Label
|
||||
* @typedef {import('types/widgets/revealer').default} Revealer
|
||||
* @typedef {[any, function, (string|undefined)?]} BindTuple
|
||||
*/
|
||||
|
||||
|
||||
/**
|
||||
* @param {{
|
||||
* command?: function
|
||||
* secondary_command?: function
|
||||
* onOpen?: function(Widget):void
|
||||
* icon: any
|
||||
* indicator?: any
|
||||
* onOpen?: function(Revealer):void
|
||||
* icon: string|BindTuple
|
||||
* indicator?: BindTuple
|
||||
* menu?: any
|
||||
* }} o
|
||||
*/
|
||||
|
@ -41,10 +47,13 @@ const GridButton = ({
|
|||
const Activated = Variable(false);
|
||||
|
||||
ButtonStates.push(Activated);
|
||||
let iconWidget;
|
||||
/** @type Label */
|
||||
let indicatorWidget = Label();
|
||||
|
||||
// Allow setting icon dynamically or statically
|
||||
if (typeof icon === 'string') {
|
||||
icon = Icon({
|
||||
iconWidget = Icon({
|
||||
class_name: 'grid-label',
|
||||
icon,
|
||||
setup: (self) => {
|
||||
|
@ -56,11 +65,12 @@ const GridButton = ({
|
|||
},
|
||||
});
|
||||
}
|
||||
else {
|
||||
icon = Icon({
|
||||
else if (Array.isArray(icon)) {
|
||||
iconWidget = Icon({
|
||||
class_name: 'grid-label',
|
||||
setup: (self) => {
|
||||
self
|
||||
// @ts-expect-error
|
||||
.hook(...icon)
|
||||
.hook(Activated, () => {
|
||||
self.setCss(`color: ${Activated.value ?
|
||||
|
@ -72,12 +82,13 @@ const GridButton = ({
|
|||
}
|
||||
|
||||
if (indicator) {
|
||||
indicator = Label({
|
||||
indicatorWidget = Label({
|
||||
class_name: 'sub-label',
|
||||
justification: 'left',
|
||||
truncate: 'end',
|
||||
maxWidthChars: 12,
|
||||
max_width_chars: 12,
|
||||
setup: (self) => {
|
||||
// @ts-expect-error
|
||||
self.hook(...indicator);
|
||||
},
|
||||
});
|
||||
|
@ -87,7 +98,7 @@ const GridButton = ({
|
|||
menu = Revealer({
|
||||
transition: 'slide_down',
|
||||
child: menu,
|
||||
binds: [['revealChild', Activated, 'value']],
|
||||
reveal_child: Activated.bind(),
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -110,7 +121,7 @@ const GridButton = ({
|
|||
}
|
||||
},
|
||||
|
||||
child: icon,
|
||||
child: iconWidget,
|
||||
}),
|
||||
|
||||
CursorBox({
|
||||
|
@ -131,10 +142,13 @@ const GridButton = ({
|
|||
self.get_parent()
|
||||
?.get_parent()?.get_parent()
|
||||
?.get_parent()?.get_parent()
|
||||
// @ts-expect-error
|
||||
?.children[1];
|
||||
|
||||
const isSetup = rowMenu.get_children()
|
||||
.find((ch) => ch === menu);
|
||||
const isSetup = rowMenu.get_children().find(
|
||||
/** @param {Box} ch */
|
||||
(ch) => ch === menu,
|
||||
);
|
||||
|
||||
if (!isSetup) {
|
||||
rowMenu.add(menu);
|
||||
|
@ -165,14 +179,14 @@ const GridButton = ({
|
|||
|
||||
],
|
||||
}),
|
||||
indicator,
|
||||
indicatorWidget,
|
||||
],
|
||||
});
|
||||
|
||||
return widget;
|
||||
};
|
||||
|
||||
const Row = ({ buttons } = {}) => {
|
||||
const Row = ({ buttons }) => {
|
||||
const widget = Box({
|
||||
vertical: true,
|
||||
|
||||
|
@ -188,10 +202,13 @@ const Row = ({ buttons } = {}) => {
|
|||
|
||||
for (let i = 0; i < buttons.length; ++i) {
|
||||
if (i === buttons.length - 1) {
|
||||
// @ts-expect-error
|
||||
widget.children[0].add(buttons[i]);
|
||||
}
|
||||
else {
|
||||
// @ts-expect-error
|
||||
widget.children[0].add(buttons[i]);
|
||||
// @ts-expect-error
|
||||
widget.children[0].add(Separator(SPACING));
|
||||
}
|
||||
}
|
||||
|
@ -209,13 +226,17 @@ const FirstRow = () => Row({
|
|||
// TODO: connection editor
|
||||
},
|
||||
|
||||
icon: [Network, (icon) => {
|
||||
icon.icon = Network.wifi?.iconName;
|
||||
}],
|
||||
icon: [Network,
|
||||
/** @param {Icon} self */
|
||||
(self) => {
|
||||
self.icon = Network.wifi?.icon_name;
|
||||
}],
|
||||
|
||||
indicator: [Network, (self) => {
|
||||
self.label = Network.wifi?.ssid || Network.wired?.internet;
|
||||
}],
|
||||
indicator: [Network,
|
||||
/** @param {Label} self */
|
||||
(self) => {
|
||||
self.label = Network.wifi?.ssid || Network.wired?.internet;
|
||||
}],
|
||||
|
||||
menu: NetworkMenu(),
|
||||
onOpen: () => Network.wifi.scan(),
|
||||
|
@ -241,26 +262,30 @@ const FirstRow = () => Row({
|
|||
// TODO: bluetooth connection editor
|
||||
},
|
||||
|
||||
icon: [Bluetooth, (self) => {
|
||||
if (Bluetooth.enabled) {
|
||||
self.icon = Bluetooth.connectedDevices[0] ?
|
||||
Bluetooth.connectedDevices[0].iconName :
|
||||
'bluetooth-active-symbolic';
|
||||
}
|
||||
else {
|
||||
self.icon = 'bluetooth-disabled-symbolic';
|
||||
}
|
||||
}],
|
||||
icon: [Bluetooth,
|
||||
/** @param {Icon} self */
|
||||
(self) => {
|
||||
if (Bluetooth.enabled) {
|
||||
self.icon = Bluetooth.connected_devices[0] ?
|
||||
Bluetooth.connected_devices[0].icon_name :
|
||||
'bluetooth-active-symbolic';
|
||||
}
|
||||
else {
|
||||
self.icon = 'bluetooth-disabled-symbolic';
|
||||
}
|
||||
}],
|
||||
|
||||
indicator: [Bluetooth, (self) => {
|
||||
self.label = Bluetooth.connectedDevices[0] ?
|
||||
`${Bluetooth.connectedDevices[0]}` :
|
||||
'Disconnected';
|
||||
}, 'notify::connected-devices'],
|
||||
indicator: [Bluetooth,
|
||||
/** @param {Label} self */
|
||||
(self) => {
|
||||
self.label = Bluetooth.connected_devices[0] ?
|
||||
`${Bluetooth.connected_devices[0]}` :
|
||||
'Disconnected';
|
||||
}, 'notify::connected-devices'],
|
||||
|
||||
menu: BluetoothMenu(),
|
||||
onOpen: (menu) => {
|
||||
execAsync(`bluetoothctl scan ${menu.revealChild ?
|
||||
execAsync(`bluetoothctl scan ${menu.reveal_child ?
|
||||
'on' :
|
||||
'off'}`).catch(print);
|
||||
},
|
||||
|
@ -282,9 +307,11 @@ const SecondRow = () => Row({
|
|||
.catch(print);
|
||||
},
|
||||
|
||||
icon: [SpeakerIcon, (self) => {
|
||||
self.icon = SpeakerIcon.value;
|
||||
}],
|
||||
icon: [SpeakerIcon,
|
||||
/** @param {Icon} self */
|
||||
(self) => {
|
||||
self.icon = SpeakerIcon.value;
|
||||
}],
|
||||
}),
|
||||
|
||||
GridButton({
|
||||
|
@ -298,9 +325,11 @@ const SecondRow = () => Row({
|
|||
.catch(print);
|
||||
},
|
||||
|
||||
icon: [MicIcon, (self) => {
|
||||
self.icon = MicIcon.value;
|
||||
}],
|
||||
icon: [MicIcon,
|
||||
/** @param {Icon} self */
|
||||
(self) => {
|
||||
self.icon = MicIcon.value;
|
||||
}],
|
||||
}),
|
||||
|
||||
GridButton({
|
||||
|
|
|
@ -14,18 +14,18 @@ const QuickSettingsWidget = () => {
|
|||
});
|
||||
|
||||
return Box({
|
||||
className: 'qs-container',
|
||||
class_name: 'qs-container',
|
||||
vertical: true,
|
||||
children: [
|
||||
|
||||
Box({
|
||||
className: 'quick-settings',
|
||||
class_name: 'quick-settings',
|
||||
vertical: true,
|
||||
children: [
|
||||
|
||||
Label({
|
||||
label: 'Control Center',
|
||||
className: 'title',
|
||||
class_name: 'title',
|
||||
hpack: 'start',
|
||||
css: `
|
||||
margin-left: 20px;
|
||||
|
|
|
@ -10,42 +10,41 @@ import CursorBox from '../misc/cursorbox.js';
|
|||
const SCROLL_THRESH_H = 200;
|
||||
const SCROLL_THRESH_N = 7;
|
||||
|
||||
/** @typedef {import('types/widgets/box').default} Box */
|
||||
|
||||
|
||||
/** @param {any} ap */
|
||||
const AccessPoint = (ap) => {
|
||||
const widget = Box({
|
||||
className: 'menu-item',
|
||||
class_name: 'menu-item',
|
||||
attribute: {
|
||||
ap: Variable(ap),
|
||||
},
|
||||
});
|
||||
|
||||
widget.ap = Variable(ap);
|
||||
|
||||
const child = Box({
|
||||
hexpand: true,
|
||||
children: [
|
||||
Icon({
|
||||
setup: (self) => {
|
||||
self.hook(widget.ap, () => {
|
||||
self.icon = widget.ap.value.iconName;
|
||||
});
|
||||
},
|
||||
Icon().hook(widget.attribute.ap, (self) => {
|
||||
self.icon = widget.attribute.ap.value.iconName;
|
||||
}),
|
||||
|
||||
Label({
|
||||
setup: (self) => {
|
||||
self.hook(widget.ap, () => {
|
||||
self.label = widget.ap.value.ssid || '';
|
||||
});
|
||||
},
|
||||
Label().hook(widget.attribute.ap, (self) => {
|
||||
self.label = widget.attribute.ap.value.ssid || '';
|
||||
}),
|
||||
|
||||
Icon({
|
||||
icon: 'object-select-symbolic',
|
||||
hexpand: true,
|
||||
hpack: 'end',
|
||||
|
||||
setup: (self) => {
|
||||
self.hook(Network, () => {
|
||||
self.setCss(
|
||||
`opacity: ${
|
||||
widget.ap.value.ssid === Network.wifi.ssid ?
|
||||
widget.attribute.ap.value.ssid ===
|
||||
Network.wifi.ssid ?
|
||||
'1' :
|
||||
'0'
|
||||
};
|
||||
|
@ -58,12 +57,13 @@ const AccessPoint = (ap) => {
|
|||
});
|
||||
|
||||
widget.add(Revealer({
|
||||
revealChild: true,
|
||||
reveal_child: true,
|
||||
transition: 'slide_down',
|
||||
|
||||
child: CursorBox({
|
||||
on_primary_click_release: () => {
|
||||
execAsync(`nmcli device wifi connect
|
||||
${widget.ap.value.bssid}`).catch(print);
|
||||
${widget.attribute.ap.value.bssid}`).catch(print);
|
||||
},
|
||||
child,
|
||||
}),
|
||||
|
@ -74,11 +74,13 @@ const AccessPoint = (ap) => {
|
|||
|
||||
export const NetworkMenu = () => {
|
||||
const APList = new Map();
|
||||
|
||||
const topArrow = Revealer({
|
||||
transition: 'slide_down',
|
||||
|
||||
child: Icon({
|
||||
icon: `${App.configDir }/icons/down-large.svg`,
|
||||
className: 'scrolled-indicator',
|
||||
class_name: 'scrolled-indicator',
|
||||
size: 16,
|
||||
css: '-gtk-icon-transform: rotate(180deg);',
|
||||
}),
|
||||
|
@ -86,15 +88,17 @@ export const NetworkMenu = () => {
|
|||
|
||||
const bottomArrow = Revealer({
|
||||
transition: 'slide_up',
|
||||
|
||||
child: Icon({
|
||||
icon: `${App.configDir }/icons/down-large.svg`,
|
||||
className: 'scrolled-indicator',
|
||||
class_name: 'scrolled-indicator',
|
||||
size: 16,
|
||||
}),
|
||||
});
|
||||
|
||||
return Overlay({
|
||||
pass_through: true,
|
||||
|
||||
overlays: [
|
||||
Box({
|
||||
vpack: 'start',
|
||||
|
@ -112,7 +116,7 @@ export const NetworkMenu = () => {
|
|||
],
|
||||
|
||||
child: Box({
|
||||
className: 'menu',
|
||||
class_name: 'menu',
|
||||
|
||||
child: Scrollable({
|
||||
hscroll: 'never',
|
||||
|
@ -122,22 +126,32 @@ export const NetworkMenu = () => {
|
|||
self.on('edge-reached', (_, pos) => {
|
||||
// Manage scroll indicators
|
||||
if (pos === 2) {
|
||||
topArrow.revealChild = false;
|
||||
bottomArrow.revealChild = true;
|
||||
topArrow.reveal_child = false;
|
||||
bottomArrow.reveal_child = true;
|
||||
}
|
||||
else if (pos === 3) {
|
||||
topArrow.revealChild = true;
|
||||
bottomArrow.revealChild = false;
|
||||
topArrow.reveal_child = true;
|
||||
bottomArrow.reveal_child = false;
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
child: ListBox({
|
||||
setup: (self) => {
|
||||
self.set_sort_func((a, b) => {
|
||||
return b.get_children()[0].ap.value.strength -
|
||||
a.get_children()[0].ap.value.strength;
|
||||
});
|
||||
// @ts-expect-error
|
||||
self.set_sort_func(
|
||||
/**
|
||||
* @param {Box} a
|
||||
* @param {Box} b
|
||||
*/
|
||||
(a, b) => {
|
||||
return b.get_children()[0]
|
||||
// @ts-expect-error
|
||||
.attribute.ap.value.strength -
|
||||
// @ts-expect-error
|
||||
a.get_children()[0].attribute.ap.value.strength;
|
||||
},
|
||||
);
|
||||
|
||||
self.hook(Network, () => {
|
||||
// Add missing APs
|
||||
|
@ -145,15 +159,17 @@ export const NetworkMenu = () => {
|
|||
if (ap.ssid !== 'Unknown') {
|
||||
if (APList.has(ap.ssid)) {
|
||||
const accesPoint = APList.get(ap.ssid)
|
||||
.ap.value;
|
||||
.attribute.ap.value;
|
||||
|
||||
if (accesPoint.strength < ap.strength) {
|
||||
APList.get(ap.ssid).ap.value = ap;
|
||||
APList.get(ap.ssid).attribute
|
||||
.ap.value = ap;
|
||||
}
|
||||
}
|
||||
else {
|
||||
APList.set(ap.ssid, AccessPoint(ap));
|
||||
|
||||
// @ts-expect-error
|
||||
self.add(APList.get(ap.ssid));
|
||||
self.show_all();
|
||||
}
|
||||
|
@ -176,7 +192,7 @@ export const NetworkMenu = () => {
|
|||
}
|
||||
else {
|
||||
apWidget.children[0]
|
||||
.revealChild = false;
|
||||
.reveal_child = false;
|
||||
apWidget.toDestroy = true;
|
||||
}
|
||||
}
|
||||
|
@ -185,34 +201,48 @@ export const NetworkMenu = () => {
|
|||
// Start scrolling after a specified height
|
||||
// is reached by the children
|
||||
const height = Math.max(
|
||||
self.get_parent().get_allocated_height(),
|
||||
self.get_parent()?.get_allocated_height() || 0,
|
||||
SCROLL_THRESH_H,
|
||||
);
|
||||
|
||||
const scroll = self.get_parent().get_parent();
|
||||
const scroll = self.get_parent()?.get_parent();
|
||||
|
||||
if (self.get_children().length > SCROLL_THRESH_N) {
|
||||
scroll.vscroll = 'always';
|
||||
scroll.setCss(`min-height: ${height}px;`);
|
||||
if (scroll) {
|
||||
// @ts-expect-error
|
||||
const n_child = self.get_children().length;
|
||||
|
||||
// Make bottom scroll indicator appear only
|
||||
// when first getting overflowing children
|
||||
if (!(bottomArrow.revealChild === true ||
|
||||
topArrow.revealChild === true)) {
|
||||
bottomArrow.revealChild = true;
|
||||
if (n_child > SCROLL_THRESH_N) {
|
||||
// @ts-expect-error
|
||||
scroll.vscroll = 'always';
|
||||
// @ts-expect-error
|
||||
scroll.setCss(`min-height: ${height}px;`);
|
||||
|
||||
// Make bottom scroll indicator appear only
|
||||
// when first getting overflowing children
|
||||
if (!(bottomArrow.reveal_child === true ||
|
||||
topArrow.reveal_child === true)) {
|
||||
bottomArrow.reveal_child = true;
|
||||
}
|
||||
}
|
||||
else {
|
||||
// @ts-expect-error
|
||||
scroll.vscroll = 'never';
|
||||
// @ts-expect-error
|
||||
scroll.setCss('');
|
||||
topArrow.reveal_child = false;
|
||||
bottomArrow.reveal_child = false;
|
||||
}
|
||||
}
|
||||
else {
|
||||
scroll.vscroll = 'never';
|
||||
scroll.setCss('');
|
||||
topArrow.revealChild = false;
|
||||
bottomArrow.revealChild = false;
|
||||
}
|
||||
|
||||
// Trigger sort_func
|
||||
self.get_children().forEach((ch) => {
|
||||
ch.changed();
|
||||
});
|
||||
// @ts-expect-error
|
||||
self.get_children().forEach(
|
||||
/** @param {Box} ListBoxRow */
|
||||
(ListBoxRow) => {
|
||||
// @ts-expect-error
|
||||
ListBoxRow.changed();
|
||||
},
|
||||
);
|
||||
});
|
||||
},
|
||||
}),
|
||||
|
|
|
@ -7,20 +7,20 @@ import { SpeakerIcon } from '../misc/audio-icons.js';
|
|||
|
||||
|
||||
export default () => Box({
|
||||
className: 'slider-box',
|
||||
class_name: 'slider-box',
|
||||
vertical: true,
|
||||
hpack: 'center',
|
||||
children: [
|
||||
|
||||
Box({
|
||||
className: 'slider',
|
||||
class_name: 'slider',
|
||||
vpack: 'start',
|
||||
hpack: 'center',
|
||||
|
||||
children: [
|
||||
Icon({
|
||||
size: 26,
|
||||
className: 'slider-label',
|
||||
class_name: 'slider-label',
|
||||
binds: [['icon', SpeakerIcon, 'value']],
|
||||
}),
|
||||
|
||||
|
@ -30,14 +30,16 @@ export default () => Box({
|
|||
max: 0.999,
|
||||
draw_value: false,
|
||||
|
||||
onChange: ({ value }) => {
|
||||
Audio.speaker.volume = value;
|
||||
on_change: ({ value }) => {
|
||||
if (Audio.speaker) {
|
||||
Audio.speaker.volume = value;
|
||||
}
|
||||
},
|
||||
|
||||
setup: (self) => {
|
||||
self
|
||||
.hook(Audio, () => {
|
||||
self.value = Audio.speaker?.volume;
|
||||
self.value = Audio.speaker?.volume || 0;
|
||||
}, 'speaker-changed')
|
||||
|
||||
.on('button-press-event', () => {
|
||||
|
@ -53,13 +55,13 @@ export default () => Box({
|
|||
}),
|
||||
|
||||
Box({
|
||||
className: 'slider',
|
||||
class_name: 'slider',
|
||||
vpack: 'start',
|
||||
hpack: 'center',
|
||||
|
||||
children: [
|
||||
Icon({
|
||||
className: 'slider-label',
|
||||
class_name: 'slider-label',
|
||||
binds: [['icon', Brightness, 'screen-icon']],
|
||||
}),
|
||||
|
||||
|
@ -68,7 +70,7 @@ export default () => Box({
|
|||
vpack: 'center',
|
||||
draw_value: false,
|
||||
|
||||
onChange: ({ value }) => {
|
||||
on_change: ({ value }) => {
|
||||
Brightness.screen = value;
|
||||
},
|
||||
|
||||
|
|
|
@ -4,6 +4,7 @@ import Mpris from 'resource:///com/github/Aylur/ags/service/mpris.js';
|
|||
import { CenterBox, Icon, ToggleButton } from 'resource:///com/github/Aylur/ags/widget.js';
|
||||
|
||||
|
||||
/** @param {import('types/widgets/revealer').default} rev */
|
||||
export default (rev) => CenterBox({
|
||||
center_widget: ToggleButton({
|
||||
cursor: 'pointer',
|
||||
|
@ -11,30 +12,32 @@ export default (rev) => CenterBox({
|
|||
setup: (self) => {
|
||||
// Open at startup if there are players
|
||||
const id = Mpris.connect('changed', () => {
|
||||
// @ts-expect-error
|
||||
self.set_active(Mpris.players.length > 0);
|
||||
Mpris.disconnect(id);
|
||||
});
|
||||
|
||||
self.on('toggled', () => {
|
||||
// @ts-expect-error
|
||||
if (self.get_active()) {
|
||||
self.get_children()[0]
|
||||
.setCss('-gtk-icon-transform: rotate(0deg);');
|
||||
rev.revealChild = true;
|
||||
self.child
|
||||
// @ts-expect-error
|
||||
?.setCss('-gtk-icon-transform: rotate(0deg);');
|
||||
rev.reveal_child = true;
|
||||
}
|
||||
else {
|
||||
self.get_children()[0]
|
||||
.setCss('-gtk-icon-transform: rotate(180deg);');
|
||||
rev.revealChild = false;
|
||||
self.child
|
||||
// @ts-expect-error
|
||||
?.setCss('-gtk-icon-transform: rotate(180deg);');
|
||||
rev.reveal_child = false;
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
child: Icon({
|
||||
icon: `${App.configDir }/icons/down-large.svg`,
|
||||
className: 'arrow',
|
||||
class_name: 'arrow',
|
||||
css: '-gtk-icon-transform: rotate(180deg);',
|
||||
}),
|
||||
}),
|
||||
start_widget: null,
|
||||
end_widget: null,
|
||||
});
|
||||
|
|
|
@ -114,12 +114,18 @@ class Brightness extends Service {
|
|||
|
||||
#monitorKbdState() {
|
||||
Variable(0, {
|
||||
poll: [INTERVAL, `brightnessctl -d ${KBD} g`, (out) => {
|
||||
if (out !== this.#kbd) {
|
||||
this.#kbd = out;
|
||||
this.emit('kbd', this.#kbd);
|
||||
}
|
||||
}],
|
||||
poll: [
|
||||
INTERVAL,
|
||||
`brightnessctl -d ${KBD} g`,
|
||||
(out) => {
|
||||
if (parseInt(out) !== this.#kbd) {
|
||||
this.#kbd = parseInt(out);
|
||||
this.emit('kbd', this.#kbd);
|
||||
|
||||
return this.#kbd;
|
||||
}
|
||||
},
|
||||
],
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue