summaryrefslogtreecommitdiff
path: root/examples/gtk3/lua/notifications
diff options
context:
space:
mode:
authorKevin <[email protected]>2025-01-23 09:34:46 -0300
committerGitHub <[email protected]>2025-01-23 13:34:46 +0100
commit907230e479ba6c9489463797f81c7348ed754302 (patch)
tree4f12e013cebcff9097ae6a0e23885f1236b8b3d7 /examples/gtk3/lua/notifications
parent4d6d85562be7fe25cf659f1f1898244e1bdb44ca (diff)
add: lua gtk3 examples (#156)
Diffstat (limited to 'examples/gtk3/lua/notifications')
-rw-r--r--examples/gtk3/lua/notifications/README.md5
-rw-r--r--examples/gtk3/lua/notifications/init.lua20
-rw-r--r--examples/gtk3/lua/notifications/lib.lua74
-rw-r--r--examples/gtk3/lua/notifications/notifications/Notification.lua105
-rw-r--r--examples/gtk3/lua/notifications/notifications/Notification.scss126
-rw-r--r--examples/gtk3/lua/notifications/notifications/NotificationPopups.lua57
-rw-r--r--examples/gtk3/lua/notifications/style.scss1
7 files changed, 388 insertions, 0 deletions
diff --git a/examples/gtk3/lua/notifications/README.md b/examples/gtk3/lua/notifications/README.md
new file mode 100644
index 0000000..60dad60
--- /dev/null
+++ b/examples/gtk3/lua/notifications/README.md
@@ -0,0 +1,5 @@
+# Notifications Popups
+
+![notifs](https://github.com/user-attachments/assets/0df0eddc-5c74-4af0-a694-48dc8ec6bb44)
+
+A replacement for dunst and other daemons using [Notifd](https://aylur.github.io/astal/guide/libraries/notifd).
diff --git a/examples/gtk3/lua/notifications/init.lua b/examples/gtk3/lua/notifications/init.lua
new file mode 100644
index 0000000..886e9ab
--- /dev/null
+++ b/examples/gtk3/lua/notifications/init.lua
@@ -0,0 +1,20 @@
+local astal = require("astal")
+local App = require("astal.gtk3.app")
+
+local NotificationPopups = require("notifications.NotificationPopups")
+local src = require("lib").src
+
+local scss = src("style.scss")
+local css = "/tmp/style.css"
+
+astal.exec("sass " .. scss .. " " .. css)
+
+App:start({
+ instance_name = "notifications",
+ css = css,
+ main = function()
+ for _, mon in pairs(App.monitors) do
+ NotificationPopups(mon)
+ end
+ end,
+})
diff --git a/examples/gtk3/lua/notifications/lib.lua b/examples/gtk3/lua/notifications/lib.lua
new file mode 100644
index 0000000..289fc7e
--- /dev/null
+++ b/examples/gtk3/lua/notifications/lib.lua
@@ -0,0 +1,74 @@
+local astal = require("astal")
+local Variable = require("astal").Variable
+local Gtk = require("astal.gtk3").Gtk
+local GLib = astal.require("GLib")
+
+local M = {}
+
+function M.src(path)
+ local str = debug.getinfo(2, "S").source:sub(2)
+ local src = str:match("(.*/)") or str:match("(.*\\)") or "./"
+ return src .. path
+end
+
+---@generic T, R
+---@param array T[]
+---@param func fun(T, i: integer): R
+---@return R[]
+function M.map(array, func)
+ local new_arr = {}
+ for i, v in ipairs(array) do
+ new_arr[i] = func(v, i)
+ end
+ return new_arr
+end
+
+---@param path string
+---@return boolean
+function M.file_exists(path) return GLib.file_test(path, "EXISTS") end
+
+function M.varmap(initial)
+ local map = initial
+ local var = Variable()
+
+ local function notify()
+ local arr = {}
+ for _, value in pairs(map) do
+ table.insert(arr, value)
+ end
+ var:set(arr)
+ end
+
+ local function delete(key)
+ if Gtk.Widget:is_type_of(map[key]) then map[key]:destroy() end
+
+ map[key] = nil
+ end
+
+ notify()
+
+ return setmetatable({
+ set = function(key, value)
+ delete(key)
+ map[key] = value
+ notify()
+ end,
+ delete = function(key)
+ delete(key)
+ notify()
+ end,
+ get = function() return var:get() end,
+ subscribe = function(callback) return var:subscribe(callback) end,
+ }, {
+ __call = function() return var() end,
+ })
+end
+
+---@param time number
+---@param format? string
+function M.time(time, format)
+ format = format or "%H:%M"
+ return GLib.DateTime.new_from_unix_local(time):format(format)
+end
+
+return M
diff --git a/examples/gtk3/lua/notifications/notifications/Notification.lua b/examples/gtk3/lua/notifications/notifications/Notification.lua
new file mode 100644
index 0000000..39d36f5
--- /dev/null
+++ b/examples/gtk3/lua/notifications/notifications/Notification.lua
@@ -0,0 +1,105 @@
+local Widget = require("astal.gtk3").Widget
+local Gtk = require("astal.gtk3").Gtk
+local Astal = require("astal.gtk3").Astal
+
+local map = require("lib").map
+local time = require("lib").time
+local file_exists = require("lib").file_exists
+
+local function is_icon(icon) return Astal.Icon.lookup_icon(icon) ~= nil end
+
+---@param props { setup?: function, on_hover_lost?: function, notification: any }
+return function(props)
+ local n = props.notification
+
+ local header = Widget.Box({
+ class_name = "header",
+ (n.app_icon or n.desktop_entry) and Widget.Icon({
+ class_name = "app-icon",
+ icon = n.app_icon or n.desktop_entry,
+ }),
+ Widget.Label({
+ class_name = "app-name",
+ halign = "START",
+ ellipsize = "END",
+ label = n.app_name or "Unknown",
+ }),
+ Widget.Label({
+ class_name = "time",
+ hexpand = true,
+ halign = "END",
+ label = time(n.time),
+ }),
+ Widget.Button({
+ on_clicked = function() n:dismiss() end,
+ Widget.Icon({ icon = "window-close-symbolic" }),
+ }),
+ })
+
+ local content = Widget.Box({
+ class_name = "content",
+ (n.image and file_exists(n.image)) and Widget.Box({
+ valign = "START",
+ class_name = "image",
+ css = string.format("background-image: url('%s')", n.image),
+ }),
+ n.image and is_icon(n.image) and Widget.Box({
+ valign = "START",
+ class_name = "icon-image",
+ Widget.Icon({
+ icon = n.image,
+ hexpand = true,
+ vexpand = true,
+ halign = "CENTER",
+ valign = "CENTER",
+ }),
+ }),
+ Widget.Box({
+ vertical = true,
+ Widget.Label({
+ class_name = "summary",
+ halign = "START",
+ xalign = 0,
+ ellipsize = "END",
+ label = n.summary,
+ }),
+ Widget.Label({
+ class_name = "body",
+ wrap = true,
+ use_markup = true,
+ halign = "START",
+ xalign = 0,
+ justify = "FILL",
+ label = n.body,
+ }),
+ }),
+ })
+
+ return Widget.EventBox({
+ class_name = string.format("Notification %s", string.lower(n.urgency)),
+ setup = props.setup,
+ on_hover_lost = props.on_hover_lost,
+ Widget.Box({
+ vertical = true,
+ header,
+ Gtk.Separator({ visible = true }),
+ content,
+ #n.actions > 0 and Widget.Box({
+ class_name = "actions",
+ map(n.actions, function(action)
+ local label, id = action.label, action.id
+
+ return Widget.Button({
+ hexpand = true,
+ on_clicked = function() return n:invoke(id) end,
+ Widget.Label({
+ label = label,
+ halign = "CENTER",
+ hexpand = true,
+ }),
+ })
+ end),
+ }),
+ }),
+ })
+end
diff --git a/examples/gtk3/lua/notifications/notifications/Notification.scss b/examples/gtk3/lua/notifications/notifications/Notification.scss
new file mode 100644
index 0000000..089d587
--- /dev/null
+++ b/examples/gtk3/lua/notifications/notifications/Notification.scss
@@ -0,0 +1,126 @@
+@use "sass:string";
+
+@function gtkalpha($c, $a) {
+ @return string.unquote("alpha(#{$c},#{$a})");
+}
+
+// https://gitlab.gnome.org/GNOME/gtk/-/blob/gtk-3-24/gtk/theme/Adwaita/_colors-public.scss
+$fg-color: #{"@theme_fg_color"};
+$bg-color: #{"@theme_bg_color"};
+$error: red;
+
+window.NotificationPopups {
+ all: unset;
+}
+
+eventbox.Notification {
+
+ &:first-child>box {
+ margin-top: 1rem;
+ }
+
+ &:last-child>box {
+ margin-bottom: 1rem;
+ }
+
+ // eventboxes can not take margins so we style its inner box instead
+ >box {
+ min-width: 400px;
+ border-radius: 13px;
+ background-color: $bg-color;
+ margin: .5rem 1rem .5rem 1rem;
+ box-shadow: 2px 3px 8px 0 gtkalpha(black, .4);
+ border: 1pt solid gtkalpha($fg-color, .03);
+ }
+
+ &.critical>box {
+ border: 1pt solid gtkalpha($error, .4);
+
+ .header {
+
+ .app-name {
+ color: gtkalpha($error, .8);
+
+ }
+
+ .app-icon {
+ color: gtkalpha($error, .6);
+ }
+ }
+ }
+
+ .header {
+ padding: .5rem;
+ color: gtkalpha($fg-color, 0.5);
+
+ .app-icon {
+ margin: 0 .4rem;
+ }
+
+ .app-name {
+ margin-right: .3rem;
+ font-weight: bold;
+
+ &:first-child {
+ margin-left: .4rem;
+ }
+ }
+
+ .time {
+ margin: 0 .4rem;
+ }
+
+ button {
+ padding: .2rem;
+ min-width: 0;
+ min-height: 0;
+ }
+ }
+
+ separator {
+ margin: 0 .4rem;
+ background-color: gtkalpha($fg-color, .1);
+ }
+
+ .content {
+ margin: 1rem;
+ margin-top: .5rem;
+
+ .summary {
+ font-size: 1.2em;
+ color: $fg-color;
+ }
+
+ .body {
+ color: gtkalpha($fg-color, 0.8);
+ }
+
+ .image {
+ border: 1px solid gtkalpha($fg-color, .02);
+ margin-right: .5rem;
+ border-radius: 9px;
+ min-width: 100px;
+ min-height: 100px;
+ background-size: cover;
+ background-position: center;
+ }
+ }
+
+ .actions {
+ margin: 1rem;
+ margin-top: 0;
+ padding: .2rem;
+
+ button {
+ margin: 0 .3rem;
+
+ &:first-child {
+ margin-left: 0;
+ }
+
+ &:last-child {
+ margin-right: 0;
+ }
+ }
+ }
+}
diff --git a/examples/gtk3/lua/notifications/notifications/NotificationPopups.lua b/examples/gtk3/lua/notifications/notifications/NotificationPopups.lua
new file mode 100644
index 0000000..c5f9e1b
--- /dev/null
+++ b/examples/gtk3/lua/notifications/notifications/NotificationPopups.lua
@@ -0,0 +1,57 @@
+local astal = require("astal")
+local Widget = require("astal.gtk3").Widget
+
+local Notifd = astal.require("AstalNotifd")
+local Notification = require("notifications.Notification")
+local timeout = astal.timeout
+
+local TIMEOUT_DELAY = 5000
+
+local varmap = require("lib").varmap
+local notifd = Notifd.get_default()
+
+local function NotificationMap()
+ local notif_map = varmap({})
+
+ notifd.on_notified = function(_, id)
+ notif_map.set(
+ id,
+ Notification({
+ notification = notifd:get_notification(id),
+ -- once hovering over the notification is done
+ -- destroy the widget without calling notification.dismiss()
+ -- so that it acts as a "popup" and we can still display it
+ -- in a notification center like widget
+ -- but clicking on the close button will close it
+ on_hover_lost = function() notif_map.delete(id) end,
+ setup = function()
+ timeout(TIMEOUT_DELAY, function()
+ -- uncomment this if you want to "hide" the notifications
+ -- after TIMEOUT_DELAY
+
+ -- NotificationMap.delete(id)
+ end)
+ end,
+ })
+ )
+ end
+
+ notifd.on_resolved = function(_, id) notif_map.delete(id) end
+
+ return notif_map
+end
+
+return function(gdkmonitor)
+ local Anchor = astal.require("Astal").WindowAnchor
+ local notifs = NotificationMap()
+
+ return Widget.Window({
+ class_name = "NotificationPopups",
+ gdkmonitor = gdkmonitor,
+ anchor = Anchor.TOP + Anchor.RIGHT,
+ Widget.Box({
+ vertical = true,
+ notifs(),
+ }),
+ })
+end
diff --git a/examples/gtk3/lua/notifications/style.scss b/examples/gtk3/lua/notifications/style.scss
new file mode 100644
index 0000000..7ef0168
--- /dev/null
+++ b/examples/gtk3/lua/notifications/style.scss
@@ -0,0 +1 @@
+@use "./notifications/Notification.scss";