diff --git a/example/applauncher/applauncher.js b/example/applauncher/applauncher.js index fb324be9..4d049115 100644 --- a/example/applauncher/applauncher.js +++ b/example/applauncher/applauncher.js @@ -1,45 +1,45 @@ -const { query } = await Service.import('applications'); -const WINDOW_NAME = 'applauncher'; +const { query } = await Service.import("applications") +const WINDOW_NAME = "applauncher" /** @param {import('resource:///com/github/Aylur/ags/service/applications.js').Application} app */ const AppItem = app => Widget.Button({ on_clicked: () => { - App.closeWindow(WINDOW_NAME); - app.launch(); + App.closeWindow(WINDOW_NAME) + app.launch() }, attribute: { app }, child: Widget.Box({ children: [ Widget.Icon({ - icon: app.icon_name || '', + icon: app.icon_name || "", size: 42, }), Widget.Label({ - class_name: 'title', + class_name: "title", label: app.name, xalign: 0, - vpack: 'center', - truncate: 'end', + vpack: "center", + truncate: "end", }), ], }), -}); +}) const Applauncher = ({ width = 500, height = 500, spacing = 12 }) => { // list of application buttons - let applications = query('').map(AppItem); + let applications = query("").map(AppItem) // container holding the buttons const list = Widget.Box({ vertical: true, children: applications, spacing, - }); + }) // repopulate the box, so the most frequent apps are on top of the list function repopulate() { - applications = query('').map(AppItem); - list.children = applications; + applications = query("").map(AppItem) + list.children = applications } // search entry @@ -50,16 +50,16 @@ const Applauncher = ({ width = 500, height = 500, spacing = 12 }) => { // to launch the first item on Enter on_accept: () => { if (applications[0]) { - App.toggleWindow(WINDOW_NAME); - applications[0].attribute.app.launch(); + App.toggleWindow(WINDOW_NAME) + applications[0].attribute.app.launch() } }, // filter out the list on_change: ({ text }) => applications.forEach(item => { - item.visible = item.attribute.app.match(text ?? ''); + item.visible = item.attribute.app.match(text ?? "") }), - }); + }) return Widget.Box({ vertical: true, @@ -69,39 +69,37 @@ const Applauncher = ({ width = 500, height = 500, spacing = 12 }) => { // wrap the list in a scrollable Widget.Scrollable({ - hscroll: 'never', - css: ` - min-width: ${width}px; - min-height: ${height}px; - `, + hscroll: "never", + css: `min-width: ${width}px;` + + `min-height: ${height}px;`, child: list, }), ], setup: self => self.hook(App, (_, windowName, visible) => { if (windowName !== WINDOW_NAME) - return; + return // when the applauncher shows up if (visible) { - repopulate(); - entry.text = ''; - entry.grab_focus(); + repopulate() + entry.text = "" + entry.grab_focus() } }), - }); -}; + }) +} // there needs to be only one instance export const applauncher = Widget.Window({ name: WINDOW_NAME, - setup: self => self.keybind('Escape', () => { + setup: self => self.keybind("Escape", () => { App.closeWindow(WINDOW_NAME) }), visible: false, - keymode: 'exclusive', + keymode: "exclusive", child: Applauncher({ width: 500, height: 500, spacing: 12, }), -}); +}) diff --git a/example/applauncher/config.js b/example/applauncher/config.js index d8ce590f..61842eed 100644 --- a/example/applauncher/config.js +++ b/example/applauncher/config.js @@ -1,5 +1,5 @@ -import { applauncher } from './applauncher.js'; +import { applauncher } from "./applauncher.js" App.config({ windows: [applauncher], -}); +}) diff --git a/example/icon-browser/icon-browser.js b/example/icon-browser/icon-browser.js index 75e860a7..f4187afb 100755 --- a/example/icon-browser/icon-browser.js +++ b/example/icon-browser/icon-browser.js @@ -1,74 +1,73 @@ #!/usr/bin/env -S ags -b icon-browser -c -import Gtk from 'gi://Gtk?version=3.0'; +import Gtk from "gi://Gtk?version=3.0" const IconPicker = () => { const selected = Widget.Label({ - css: 'font-size: 1.2em;', - label: 'Click on an icon te get its name', + css: "font-size: 1.2em;", + label: "Click on an icon te get its name", selectable: true, - }); + }) const flowbox = Widget.FlowBox({ min_children_per_line: 7, row_spacing: 12, column_spacing: 12, - vpack: 'start', - hpack: 'start', + vpack: "start", + hpack: "start", setup: self => { - self.connect('child-activated', (_, child) => { - selected.label = child.get_child()?.iconName || ''; - }); + self.connect("child-activated", (_, child) => { + selected.label = child.get_child()?.iconName || "" + }) Gtk.IconTheme.get_default().list_icons(null).sort().map(icon => { - !icon.endsWith('.symbolic') && self.insert(Widget.Icon({ + !icon.endsWith(".symbolic") && self.insert(Widget.Icon({ icon, size: 38, - }), -1); - }); + }), -1) + }) - self.show_all(); + self.show_all() }, - }); + }) const entry = Widget.Entry({ - placeholder_text: 'Type to seach...', - primary_icon_name: 'system-search-symbolic', + placeholder_text: "Type to seach...", + primary_icon_name: "system-search-symbolic", on_change: ({ text }) => flowbox.get_children().forEach(child => { - child.visible = child.get_child().iconName.includes(text); + child.visible = child.get_child().iconName.includes(text) }), - }); + }) return Widget.Box({ - css: 'padding: 30px;', + css: "padding: 30px;", spacing: 20, vertical: true, children: [ entry, Widget.Scrollable({ - hscroll: 'never', - vscroll: 'always', + hscroll: "never", + vscroll: "always", hexpand: true, vexpand: true, - css: - 'min-width: 400px;' + - 'min-height: 500px;', + css: "min-width: 400px;" + + "min-height: 500px;", child: flowbox, }), selected, ], - }); -}; + }) +} const win = new Gtk.Window({ - name: 'icon-browser', + name: "icon-browser", child: IconPicker(), -}); +}) -win.show_all(); -win.connect('delete-event', () => { - App.quit(); - return true; -}); +win.show_all() +win.connect("delete-event", () => { + App.quit() + return true +}) -export default { windows: [win] }; +export default { windows: [win] } diff --git a/example/media-widget/Media.js b/example/media-widget/Media.js index b4bb7d75..556b042f 100644 --- a/example/media-widget/Media.js +++ b/example/media-widget/Media.js @@ -1,121 +1,121 @@ -const mpris = await Service.import('mpris'); -const players = mpris.bind('players'); +const mpris = await Service.import("mpris") +const players = mpris.bind("players") -const FALLBACK_ICON = 'audio-x-generic-symbolic'; -const PLAY_ICON = 'media-playback-start-symbolic'; -const PAUSE_ICON = 'media-playback-pause-symbolic'; -const PREV_ICON = 'media-skip-backward-symbolic'; -const NEXT_ICON = 'media-skip-forward-symbolic'; +const FALLBACK_ICON = "audio-x-generic-symbolic" +const PLAY_ICON = "media-playback-start-symbolic" +const PAUSE_ICON = "media-playback-pause-symbolic" +const PREV_ICON = "media-skip-backward-symbolic" +const NEXT_ICON = "media-skip-forward-symbolic" /** @param {number} length */ function lengthStr(length) { - const min = Math.floor(length / 60); - const sec = Math.floor(length % 60); - const sec0 = sec < 10 ? '0' : ''; - return `${min}:${sec0}${sec}`; + const min = Math.floor(length / 60) + const sec = Math.floor(length % 60) + const sec0 = sec < 10 ? "0" : "" + return `${min}:${sec0}${sec}` } /** @param {import('types/service/mpris').MprisPlayer} player */ -const Player = player => { +function Player(player) { const img = Widget.Box({ - class_name: 'img', - vpack: 'start', - css: player.bind('cover_path').transform(p => ` + class_name: "img", + vpack: "start", + css: player.bind("cover_path").transform(p => ` background-image: url('${p}'); `), - }); + }) const title = Widget.Label({ - class_name: 'title', + class_name: "title", wrap: true, - hpack: 'start', - label: player.bind('track_title'), - }); + hpack: "start", + label: player.bind("track_title"), + }) const artist = Widget.Label({ - class_name: 'artist', + class_name: "artist", wrap: true, - hpack: 'start', - label: player.bind('track_artists').transform(a => a.join(', ')), - }); + hpack: "start", + label: player.bind("track_artists").transform(a => a.join(", ")), + }) const positionSlider = Widget.Slider({ - class_name: 'position', + class_name: "position", draw_value: false, on_change: ({ value }) => player.position = value * player.length, setup: self => { const update = () => { - self.visible = player.length > 0; - self.value = player.position / player.length; - }; - self.hook(player, update); - self.hook(player, update, 'position'); - self.poll(1000, update); + self.visible = player.length > 0 + self.value = player.position / player.length + } + self.hook(player, update) + self.hook(player, update, "position") + self.poll(1000, update) }, - }); + }) const positionLabel = Widget.Label({ - class_name: 'position', - hpack: 'start', + class_name: "position", + hpack: "start", setup: self => { const update = (_, time) => { - self.label = lengthStr(time || player.position); - self.visible = player.length > 0; - }; + self.label = lengthStr(time || player.position) + self.visible = player.length > 0 + } - self.hook(player, update, 'position'); - self.poll(1000, update); + self.hook(player, update, "position") + self.poll(1000, update) }, - }); + }) const lengthLabel = Widget.Label({ - class_name: 'length', - hpack: 'end', - visible: player.bind('length').transform(l => l > 0), - label: player.bind('length').transform(lengthStr), - }); + class_name: "length", + hpack: "end", + visible: player.bind("length").transform(l => l > 0), + label: player.bind("length").transform(lengthStr), + }) const icon = Widget.Icon({ - class_name: 'icon', + class_name: "icon", hexpand: true, - hpack: 'end', - vpack: 'start', - tooltip_text: player.identity || '', - icon: player.bind('entry').transform(entry => { - const name = `${entry}-symbolic`; - return Utils.lookUpIcon(name) ? name : FALLBACK_ICON; + hpack: "end", + vpack: "start", + tooltip_text: player.identity || "", + icon: player.bind("entry").transform(entry => { + const name = `${entry}-symbolic` + return Utils.lookUpIcon(name) ? name : FALLBACK_ICON }), - }); + }) const playPause = Widget.Button({ - class_name: 'play-pause', + class_name: "play-pause", on_clicked: () => player.playPause(), - visible: player.bind('can_play'), + visible: player.bind("can_play"), child: Widget.Icon({ - icon: player.bind('play_back_status').transform(s => { + icon: player.bind("play_back_status").transform(s => { switch (s) { - case 'Playing': return PAUSE_ICON; - case 'Paused': - case 'Stopped': return PLAY_ICON; + case "Playing": return PAUSE_ICON + case "Paused": + case "Stopped": return PLAY_ICON } }), }), - }); + }) const prev = Widget.Button({ on_clicked: () => player.previous(), - visible: player.bind('can_go_prev'), + visible: player.bind("can_go_prev"), child: Widget.Icon(PREV_ICON), - }); + }) const next = Widget.Button({ on_clicked: () => player.next(), - visible: player.bind('can_go_next'), + visible: player.bind("can_go_next"), child: Widget.Icon(NEXT_ICON), - }); + }) return Widget.Box( - { class_name: 'player' }, + { class_name: "player" }, img, Widget.Box( { @@ -139,12 +139,14 @@ const Player = player => { end_widget: lengthLabel, }), ), - ); -}; + ) +} -export default () => Widget.Box({ - vertical: true, - css: 'padding: 1px', // small hack to make sure it is visible - visible: players.as(p => p.length > 0), - children: players.as(p => p.map(Player)), -}); +export function Media() { + return Widget.Box({ + vertical: true, + css: "min-height: 2px; min-width: 2px;", // small hack to make it visible + visible: players.as(p => p.length > 0), + children: players.as(p => p.map(Player)), + }) +} diff --git a/example/media-widget/config.js b/example/media-widget/config.js index 661a17fc..d8f5c86f 100644 --- a/example/media-widget/config.js +++ b/example/media-widget/config.js @@ -1,12 +1,12 @@ -import Media from './Media.js'; +import { Media } from "./Media.js" const win = Widget.Window({ - name: 'mpris', - anchor: ['top', 'right'], + name: "mpris", + anchor: ["top", "right"], child: Media(), -}); +}) App.config({ - style: './style.css', + style: "./style.css", windows: [win], -}); +}) diff --git a/example/notification-popups/config.js b/example/notification-popups/config.js index 665bb617..bcb678b0 100644 --- a/example/notification-popups/config.js +++ b/example/notification-popups/config.js @@ -1,18 +1,18 @@ -import { notificationPopup } from './notificationPopups.js'; +import { NotificationPopups } from "./notificationPopups.js" Utils.timeout(100, () => Utils.notify({ - summary: 'Notification Popup Example', + summary: "Notification Popup Example", iconName: "info-symbolic", - body: 'Lorem ipsum dolor sit amet, qui minim labore adipisicing ' - + 'minim sint cillum sint consectetur cupidatat.', + body: "Lorem ipsum dolor sit amet, qui minim labore adipisicing " + + "minim sint cillum sint consectetur cupidatat.", actions: { "Cool": () => print("pressed Cool"), }, })) App.config({ - style: App.configDir + '/style.css', + style: App.configDir + "/style.css", windows: [ - notificationPopup, + NotificationPopups(), ], -}); +}) diff --git a/example/notification-popups/notificationPopups.js b/example/notification-popups/notificationPopups.js index ae60f6d0..f360d3cd 100644 --- a/example/notification-popups/notificationPopups.js +++ b/example/notification-popups/notificationPopups.js @@ -1,99 +1,130 @@ -const notifications = await Service.import('notifications'); -const popups = notifications.bind('popups'); +const notifications = await Service.import("notifications") /** @param {import('resource:///com/github/Aylur/ags/service/notifications.js').Notification} n */ -const NotificationIcon = ({ app_entry, app_icon, image }) => { +function NotificationIcon({ app_entry, app_icon, image }) { if (image) { return Widget.Box({ - css: ` - background-image: url("${image}"); - background-size: contain; - background-repeat: no-repeat; - background-position: center; - `, - }); + css: `background-image: url("${image}");` + + "background-size: contain;" + + "background-repeat: no-repeat;" + + "background-position: center;", + }) } - let icon = 'dialog-information-symbolic'; + let icon = "dialog-information-symbolic" if (Utils.lookUpIcon(app_icon)) - icon = app_icon; + icon = app_icon if (app_entry && Utils.lookUpIcon(app_entry)) - icon = app_entry; + icon = app_entry - return Widget.Icon(icon); -}; + return Widget.Box({ + child: Widget.Icon(icon), + }) +} /** @param {import('resource:///com/github/Aylur/ags/service/notifications.js').Notification} n */ -export const Notification = n => { +function Notification(n) { const icon = Widget.Box({ - vpack: 'start', - class_name: 'icon', + vpack: "start", + class_name: "icon", child: NotificationIcon(n), - }); + }) const title = Widget.Label({ - class_name: 'title', + class_name: "title", xalign: 0, - justification: 'left', + justification: "left", hexpand: true, max_width_chars: 24, - truncate: 'end', + truncate: "end", wrap: true, label: n.summary, use_markup: true, - }); + }) const body = Widget.Label({ - class_name: 'body', + class_name: "body", hexpand: true, use_markup: true, xalign: 0, - justification: 'left', + justification: "left", label: n.body, wrap: true, - }); + }) const actions = Widget.Box({ - class_name: 'actions', + class_name: "actions", children: n.actions.map(({ id, label }) => Widget.Button({ - class_name: 'action-button', - on_clicked: () => n.invoke(id), + class_name: "action-button", + on_clicked: () => { + n.invoke(id) + n.dismiss() + }, hexpand: true, child: Widget.Label(label), })), - }); + }) - return Widget.EventBox({ - on_primary_click: () => n.dismiss(), + return Widget.EventBox( + { + attribute: { id: n.id }, + on_primary_click: n.dismiss, + }, + Widget.Box( + { + class_name: `notification ${n.urgency}`, + vertical: true, + }, + Widget.Box([ + icon, + Widget.Box( + { vertical: true }, + title, + body, + ), + ]), + actions, + ), + ) +} + +export function NotificationPopups(monitor = 0) { + const list = Widget.Box({ + vertical: true, + children: notifications.popups.map(Notification), + }) + + function onNotified(_, /** @type {number} */ id) { + const n = notifications.getNotification(id) + if (n) + list.children = [Notification(n), ...list.children] + } + + function onDismissed(_, /** @type {number} */ id) { + list.children.find(n => n.attribute.id === id)?.destroy() + } + + list.hook(notifications, onNotified, "notified") + .hook(notifications, onDismissed, "dismissed") + + return Widget.Window({ + monitor, + name: `notifications${monitor}`, + class_name: "notification-popups", + anchor: ["top", "right"], child: Widget.Box({ - class_name: `notification ${n.urgency}`, + css: "min-width: 2px; min-height: 2px;", + class_name: "notifications", vertical: true, - children: [ - Widget.Box({ - children: [ - icon, - Widget.Box({ - vertical: true, - children: [ - title, - body, - ], - }), - ], - }), - actions, - ], - }), - }); -}; + child: list, -export const notificationPopup = Widget.Window({ - name: 'notifications', - anchor: ['top', 'right'], - child: Widget.Box({ - class_name: 'notifications', - vertical: true, - children: popups.as(popups => popups.map(Notification)), - }), -}); + /** this is a simple one liner that could be used instead of + hooking into the 'notified' and 'dismissed' signals. + but its not very optimized becuase it will recreate + the whole list everytime a notification is added or dismissed */ + // children: notifications.bind('popups') + // .as(popups => popups.map(Notification)) + }), + }) +} diff --git a/example/notification-popups/style.css b/example/notification-popups/style.css index 1e819fcd..2d2883b5 100644 --- a/example/notification-popups/style.css +++ b/example/notification-popups/style.css @@ -1,8 +1,4 @@ -window#notifications { - all: unset; -} - -window#notifications box.notifications { +window.notification-popups box.notifications { padding: .5em; } diff --git a/example/simple-bar/config.js b/example/simple-bar/config.js index 81f8eb9c..4c0a30bb 100644 --- a/example/simple-bar/config.js +++ b/example/simple-bar/config.js @@ -1,11 +1,11 @@ -const hyprland = await Service.import('hyprland') -const notifications = await Service.import('notifications') -const mpris = await Service.import('mpris') -const audio = await Service.import('audio') -const battery = await Service.import('battery') -const systemtray = await Service.import('systemtray') - -const date = Variable('', { +const hyprland = await Service.import("hyprland") +const notifications = await Service.import("notifications") +const mpris = await Service.import("mpris") +const audio = await Service.import("audio") +const battery = await Service.import("battery") +const systemtray = await Service.import("systemtray") + +const date = Variable("", { poll: [1000, 'date "+%H:%M:%S %b %e."'], }) @@ -14,16 +14,16 @@ const date = Variable('', { // then you can simply instantiate one by calling it function Workspaces() { - const activeId = hyprland.active.workspace.bind('id') - const workspaces = hyprland.bind('workspaces') + const activeId = hyprland.active.workspace.bind("id") + const workspaces = hyprland.bind("workspaces") .as(ws => ws.map(({ id }) => Widget.Button({ on_clicked: () => hyprland.messageAsync(`dispatch workspace ${id}`), child: Widget.Label(`${id}`), - class_name: activeId.as(i => `${i === id ? 'focused' : ''}`), + class_name: activeId.as(i => `${i === id ? "focused" : ""}`), }))) return Widget.Box({ - class_name: 'workspaces', + class_name: "workspaces", children: workspaces, }) } @@ -31,15 +31,15 @@ function Workspaces() { function ClientTitle() { return Widget.Label({ - class_name: 'client-title', - label: hyprland.active.client.bind('title'), + class_name: "client-title", + label: hyprland.active.client.bind("title"), }) } function Clock() { return Widget.Label({ - class_name: 'clock', + class_name: "clock", label: date.bind(), }) } @@ -48,16 +48,16 @@ function Clock() { // we don't need dunst or any other notification daemon // because the Notifications module is a notification daemon itself function Notification() { - const popups = notifications.bind('popups') + const popups = notifications.bind("popups") return Widget.Box({ - class_name: 'notification', + class_name: "notification", visible: popups.as(p => p.length > 0), children: [ Widget.Icon({ - icon: 'preferences-system-notifications-symbolic', + icon: "preferences-system-notifications-symbolic", }), Widget.Label({ - label: popups.as(p => p[0]?.summary || ''), + label: popups.as(p => p[0]?.summary || ""), }), ], }) @@ -68,17 +68,17 @@ function Media() { const label = Utils.watch("", mpris, "player-changed", () => { if (mpris.players[0]) { const { track_artists, track_title } = mpris.players[0] - return `${track_artists.join(', ')} - ${track_title}` + return `${track_artists.join(", ")} - ${track_title}` } else { - return 'Nothing is playing' + return "Nothing is playing" } }) return Widget.Button({ - class_name: 'media', - on_primary_click: () => mpris.getPlayer('')?.playPause(), - on_scroll_up: () => mpris.getPlayer('')?.next(), - on_scroll_down: () => mpris.getPlayer('')?.previous(), + class_name: "media", + on_primary_click: () => mpris.getPlayer("")?.playPause(), + on_scroll_up: () => mpris.getPlayer("")?.next(), + on_scroll_down: () => mpris.getPlayer("")?.previous(), child: Widget.Label({ label }), }) } @@ -86,11 +86,11 @@ function Media() { function Volume() { const icons = { - 101: 'overamplified', - 67: 'high', - 34: 'medium', - 1: 'low', - 0: 'muted', + 101: "overamplified", + 67: "high", + 34: "medium", + 1: "low", + 0: "muted", } function getIcon() { @@ -101,7 +101,7 @@ function Volume() { } const icon = Widget.Icon({ - icon: Utils.watch(getIcon(), audio.speaker, getIcon) + icon: Utils.watch(getIcon(), audio.speaker, getIcon), }) const slider = Widget.Slider({ @@ -114,26 +114,26 @@ function Volume() { }) return Widget.Box({ - class_name: 'volume', - css: 'min-width: 180px', + class_name: "volume", + css: "min-width: 180px", children: [icon, slider], }) } function BatteryLabel() { - const value = battery.bind('percent').as(p => p > 0 ? p / 100 : 0) - const icon = battery.bind('percent').as(p => + const value = battery.bind("percent").as(p => p > 0 ? p / 100 : 0) + const icon = battery.bind("percent").as(p => `battery-level-${Math.floor(p / 10) * 10}-symbolic`) return Widget.Box({ - class_name: 'battery', - visible: battery.bind('available'), + class_name: "battery", + visible: battery.bind("available"), children: [ Widget.Icon({ icon }), Widget.LevelBar({ widthRequest: 140, - vpack: 'center', + vpack: "center", value, }), ], @@ -142,12 +142,12 @@ function BatteryLabel() { function SysTray() { - const items = systemtray.bind('items') + const items = systemtray.bind("items") .as(items => items.map(item => Widget.Button({ - child: Widget.Icon({ icon: item.bind('icon') }), + child: Widget.Icon({ icon: item.bind("icon") }), on_primary_click: (_, event) => item.activate(event), on_secondary_click: (_, event) => item.openMenu(event), - tooltip_markup: item.bind('tooltip_markup'), + tooltip_markup: item.bind("tooltip_markup"), }))) return Widget.Box({ @@ -179,7 +179,7 @@ function Center() { function Right() { return Widget.Box({ - hpack: 'end', + hpack: "end", spacing: 8, children: [ Volume(), @@ -193,10 +193,10 @@ function Right() { function Bar(monitor = 0) { return Widget.Window({ name: `bar-${monitor}`, // name has to be unique - class_name: 'bar', + class_name: "bar", monitor, - anchor: ['top', 'left', 'right'], - exclusivity: 'exclusive', + anchor: ["top", "left", "right"], + exclusivity: "exclusive", child: Widget.CenterBox({ start_widget: Left(), center_widget: Center(), @@ -206,7 +206,7 @@ function Bar(monitor = 0) { } App.config({ - style: './style.css', + style: "./style.css", windows: [ Bar(),