mirror of
https://github.com/zoriya/ags.git
synced 2026-06-03 02:51:39 +00:00
export widgets, update the example
This commit is contained in:
+147
-123
@@ -1,188 +1,212 @@
|
||||
// importing
|
||||
const { Hyprland, Notifications, Mpris, Audio, Battery } = ags.Service;
|
||||
const { exec, CONFIG_DIR } = ags.Utils;
|
||||
// importing
|
||||
import Hyprland from 'resource:///com/github/Aylur/ags/service/hyprland.js';
|
||||
import Notifications from 'resource:///com/github/Aylur/ags/service/notifications.js';
|
||||
import Mpris from 'resource:///com/github/Aylur/ags/service/mpris.js';
|
||||
import Audio from 'resource:///com/github/Aylur/ags/service/audio.js';
|
||||
import Battery from 'resource:///com/github/Aylur/ags/service/battery.js';
|
||||
import App from 'resource:///com/github/Aylur/ags/app.js';
|
||||
import {
|
||||
Box, Button, Stack, Label, Icon, CenterBox, Window, Slider, ProgressBar
|
||||
} from 'resource:///com/github/Aylur/ags/widget.js';
|
||||
import { exec, execAsync } from 'resource:///com/github/Aylur/ags/utils.js';
|
||||
|
||||
const workspaces = {
|
||||
type: 'box',
|
||||
// import statements are long, so there is also the global ags object you can import from
|
||||
// const { Hyprland, Notifications, Mpris, Audio, Battery } = ags.Service;
|
||||
// const { App } = ags;
|
||||
// const { exec } = ags.Utils;
|
||||
// const {
|
||||
// Box, Button, Stack, Label, Icon, CenterBox, Window, Slider, ProgressBar
|
||||
// } = ags.Widget;
|
||||
|
||||
// every widget is a subclass of Gtk.<widget>
|
||||
// with a few extra available properties
|
||||
// for example Box is a subclass of Gtk.Box
|
||||
|
||||
// widgets can be only assigned as a child in one container
|
||||
// so to make a reuseable widget, just make it a function
|
||||
// then you can use it by calling simply calling it
|
||||
const Workspaces = () => Box({
|
||||
className: 'workspaces',
|
||||
// box is an instance of Gtk.Box
|
||||
connections: [[Hyprland, box => {
|
||||
// remove every children
|
||||
box.get_children().forEach(ch => ch.destroy());
|
||||
|
||||
// add a button for each workspace
|
||||
const workspaces = 10;
|
||||
for (let i = 1; i <= workspaces; ++i) {
|
||||
box.add(ags.Widget({
|
||||
type: 'button',
|
||||
onClick: () => execAsync(`hyprctl dispatch workspace ${i}`),
|
||||
child: i.toString(),
|
||||
className: Hyprland.active.workspace.id == i ? 'focused' : '',
|
||||
}));
|
||||
}
|
||||
|
||||
// make the box render it
|
||||
box.show_all();
|
||||
// generate an array [1..10] then make buttons from the index
|
||||
const arr = Array.from({ length: 10 }, (_, i) => i + 1);
|
||||
box.children = arr.map(i => Button({
|
||||
onClicked: () => execAsync(`hyprctl dispatch workspace ${i}`),
|
||||
child: Label({ label: `${i}` }),
|
||||
className: Hyprland.active.workspace.id == i ? 'focused' : '',
|
||||
}));
|
||||
}]],
|
||||
};
|
||||
});
|
||||
|
||||
const clientTitle = {
|
||||
type: 'label',
|
||||
const ClientTitle = () => Label({
|
||||
className: 'client-title',
|
||||
// label is an instance of Gtk.Label
|
||||
// an initial label value can be given but its pointless
|
||||
// because callbacks from connections are run on construction
|
||||
// so in this case this is redundant
|
||||
label: Hyprland.active.client.title || '',
|
||||
connections: [[Hyprland, label => {
|
||||
label.label = Hyprland.active.client.title || '';
|
||||
}]],
|
||||
};
|
||||
});
|
||||
|
||||
const clock = {
|
||||
type: 'label',
|
||||
const Clock = () => Label({
|
||||
className: 'clock',
|
||||
// trim is for the whitespace at the end of the date output
|
||||
// but doing this is actually bad practice
|
||||
// because exec() will block the main thread, but in case of runnig date
|
||||
// I don't think it matters
|
||||
connections: [[1000, label => label.label = exec('date "+%H:%M:%S %b %e."').trim()]],
|
||||
};
|
||||
connections: [
|
||||
// this is bad practice, since exec() will block the main event loop
|
||||
// in the case of a simple date its not really a problem
|
||||
[1000, label => label.label = exec('date "+%H:%M:%S %b %e."')],
|
||||
|
||||
// this is what you should do
|
||||
[1000, label => execAsync(['date', '+%H:%M:%S %b %e.'])
|
||||
.then(date => label.label = date).catch(console.error)],
|
||||
],
|
||||
});
|
||||
|
||||
// we don't need dunst or any other notification daemon
|
||||
// because ags has a notification daemon built in
|
||||
const notification = {
|
||||
type: 'box',
|
||||
const Notification = () => Box({
|
||||
className: 'notification',
|
||||
children: [
|
||||
{
|
||||
type: 'icon',
|
||||
Icon({
|
||||
icon: 'preferences-system-notifications-symbolic',
|
||||
// icon is an instance of Gtk.Image
|
||||
connections: [[Notifications, icon => icon.visible = Notifications.popups.size > 0]]
|
||||
},
|
||||
{
|
||||
type: 'label',
|
||||
connections: [
|
||||
[Notifications, icon => icon.visible = Notifications.popups.size > 0],
|
||||
],
|
||||
}),
|
||||
Label({
|
||||
connections: [[Notifications, label => {
|
||||
// notifications is a map, to get the last elememnt lets make an array
|
||||
label.label = Array.from(Notifications.popups)?.pop()?.[1].summary || '';
|
||||
label.label = Array.from(Notifications.popups.values())?.pop()?.summary || '';
|
||||
}]],
|
||||
},
|
||||
}),
|
||||
],
|
||||
};
|
||||
});
|
||||
|
||||
const media = {
|
||||
type: 'label',
|
||||
const Media = () => Button({
|
||||
onPrimaryClick: () => Mpris.getPlayer('')?.playPause(),
|
||||
onScrollUp: () => Mpris.getPlayer('')?.next(),
|
||||
onScrollDown: () => Mpris.getPlayer('')?.previous(),
|
||||
className: 'media',
|
||||
connections: [[Mpris, label => {
|
||||
const mpris = Mpris.getPlayer('');
|
||||
if (mpris)
|
||||
label.label = `${mpris.trackArtists.join(', ')} - ${mpris.trackTitle}`;
|
||||
else
|
||||
label.label = 'Nothing is playing';
|
||||
}]],
|
||||
};
|
||||
child: Label({
|
||||
connections: [[Mpris, label => {
|
||||
const mpris = Mpris.getPlayer('');
|
||||
// mpris player can be undefined
|
||||
if (mpris)
|
||||
label.label = `${mpris.trackArtists.join(', ')} - ${mpris.trackTitle}`;
|
||||
else
|
||||
label.label = 'Nothing is playing';
|
||||
}]],
|
||||
}),
|
||||
});
|
||||
|
||||
const volume = {
|
||||
type: 'box',
|
||||
const Volume = () => Box({
|
||||
className: 'volume',
|
||||
style: 'min-width: 180px',
|
||||
children: [
|
||||
{
|
||||
type: 'dynamic',
|
||||
Stack({
|
||||
items: [
|
||||
{ value: 101, widget: { type: 'icon', icon: 'audio-volume-overamplified-symbolic' } },
|
||||
{ value: 67, widget: { type: 'icon', icon: 'audio-volume-high-symbolic' } },
|
||||
{ value: 34, widget: { type: 'icon', icon: 'audio-volume-medium-symbolic' } },
|
||||
{ value: 1, widget: { type: 'icon', icon: 'audio-volume-low-symbolic' } },
|
||||
{ value: 0, widget: { type: 'icon', icon: 'audio-volume-muted-symbolic' } },
|
||||
// tuples of [string, Widget]
|
||||
['101', Icon('audio-volume-overamplified-symbolic')],
|
||||
['67', Icon('audio-volume-high-symbolic')],
|
||||
['34', Icon('audio-volume-medium-symbolic')],
|
||||
['1', Icon('audio-volume-low-symbolic')],
|
||||
['0', Icon('audio-volume-muted-symbolic')],
|
||||
],
|
||||
// dynamic is a Gtk.Box with an extra update method
|
||||
connections: [[Audio, dynamic => dynamic.update(value => {
|
||||
connections: [[Audio, stack => {
|
||||
if (!Audio.speaker)
|
||||
return;
|
||||
|
||||
if (Audio.speaker.isMuted)
|
||||
return value === 0;
|
||||
if (Audio.speaker.isMuted) {
|
||||
stack.shown = '0';
|
||||
return;
|
||||
}
|
||||
|
||||
return value <= (Audio.speaker.volume*100);
|
||||
}), 'speaker-changed']],
|
||||
},
|
||||
{
|
||||
type: 'slider',
|
||||
const show = [100, 66, 33, 1, 0].find(
|
||||
threshold => threshold <= Audio.speaker.volume * 100);
|
||||
|
||||
stack.shown = `${show + 1}`;
|
||||
}, 'speaker-changed']],
|
||||
}),
|
||||
Slider({
|
||||
hexpand: true,
|
||||
drawValue: false,
|
||||
onChange: value => Audio.speaker.volume = value,
|
||||
connections: [[Audio, slider => {
|
||||
if (!Audio.speaker)
|
||||
return;
|
||||
|
||||
slider.adjustment.value = Audio.speaker.volume;
|
||||
slider.value = Audio.speaker.volume;
|
||||
}, 'speaker-changed']],
|
||||
}
|
||||
}),
|
||||
],
|
||||
};
|
||||
});
|
||||
|
||||
const battery = {
|
||||
type: 'box',
|
||||
const BatteryLabel = () => Box({
|
||||
className: 'battery',
|
||||
children: [
|
||||
{
|
||||
type: 'icon',
|
||||
Icon({
|
||||
connections: [[Battery, icon => {
|
||||
// icon is an instance of Gtk.Image
|
||||
icon.icon_name = `battery-level-${Math.floor(Battery.percent/10)*10}-symbolic`;
|
||||
}]]
|
||||
},
|
||||
{
|
||||
type: 'progressbar',
|
||||
icon.icon = `battery-level-${Math.floor(Battery.percent / 10) * 10}-symbolic`;
|
||||
}]],
|
||||
}),
|
||||
ProgressBar({
|
||||
valign: 'center',
|
||||
// progressbar is a Gtk.ProgressBar, setValue() just calls set_fraction()
|
||||
connections: [[Battery, progress => progress.setValue(Battery.percent/100)]],
|
||||
},
|
||||
connections: [[Battery, progress => {
|
||||
if (Battery.percent < 0)
|
||||
return;
|
||||
|
||||
progress.fraction = Battery.percent / 100;
|
||||
}]],
|
||||
}),
|
||||
],
|
||||
};
|
||||
});
|
||||
|
||||
// layout of the bar
|
||||
const left = {
|
||||
type: 'box',
|
||||
const Left = () => Box({
|
||||
className: 'left',
|
||||
children: [
|
||||
workspaces,
|
||||
clientTitle,
|
||||
Workspaces(),
|
||||
ClientTitle(),
|
||||
],
|
||||
};
|
||||
});
|
||||
|
||||
const center = {
|
||||
type: 'box',
|
||||
const Center = () => Box({
|
||||
className: 'center',
|
||||
children: [
|
||||
media,
|
||||
notification,
|
||||
Media(),
|
||||
Notification(),
|
||||
],
|
||||
};
|
||||
});
|
||||
|
||||
const right = {
|
||||
type: 'box',
|
||||
const Right = () => Box({
|
||||
className: 'right',
|
||||
halign: 'end',
|
||||
children: [
|
||||
volume,
|
||||
battery,
|
||||
clock,
|
||||
Volume(),
|
||||
BatteryLabel(),
|
||||
Clock(),
|
||||
],
|
||||
};
|
||||
});
|
||||
|
||||
const bar = {
|
||||
name: 'bar',
|
||||
const Bar = ({ monitor }) => Window({
|
||||
name: `bar${monitor}`, // name has to be unique
|
||||
monitor,
|
||||
anchor: ['top', 'left', 'right'],
|
||||
exclusive: true,
|
||||
child: {
|
||||
type: 'centerbox',
|
||||
children: [
|
||||
left,
|
||||
center,
|
||||
right,
|
||||
],
|
||||
},
|
||||
}
|
||||
child: CenterBox({
|
||||
startWidget: Left(),
|
||||
centerWidget: Center(),
|
||||
endWidget: Right(),
|
||||
}),
|
||||
})
|
||||
|
||||
// exporting the config
|
||||
var config = {
|
||||
style: CONFIG_DIR+'/style.css',
|
||||
windows: [bar],
|
||||
// exporting the config so ags can manage the windows
|
||||
export default {
|
||||
style: App.configDir + '/style.css',
|
||||
windows: [
|
||||
Bar({ monitor: 0 }),
|
||||
|
||||
// you can call it as many times
|
||||
Bar({ monitor: 1 })
|
||||
],
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user