export widgets, update the example

This commit is contained in:
Aylur
2023-08-19 03:22:58 +02:00
parent d488dd8865
commit ba23ca40a1
22 changed files with 239 additions and 206 deletions
+147 -123
View File
@@ -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 })
],
};