diff options
Diffstat (limited to '')
| -rw-r--r-- | app.ts | 2 | ||||
| -rw-r--r-- | style.scss | 88 | ||||
| -rw-r--r-- | widget/bar/Bar.scss | 80 | ||||
| -rw-r--r-- | widget/notifications/Notifications.scss | 141 | ||||
| -rw-r--r-- | widget/notifications/Notifications.tsx | 108 | 
5 files changed, 333 insertions, 86 deletions
| @@ -3,9 +3,11 @@ import { App } from "astal/gtk4"  import style from "./style.scss"  import Bar from "./widget/bar/Bar" +import Notifications from "./widget/notifications/Notifications"  const windows = [    Bar, +  Notifications,  ];  App.start({ @@ -2,89 +2,5 @@  // $fg-color: #{"@theme_fg_color"};  // $bg-color: #{"@theme_bg_color"}; -window.Bar { -  border: none; -  box-shadow: none; -  background-color: #000; -  color: #fff; - -  >box { -    padding: 0; -  } -} - -box.Workspaces { -  padding-left: 3px; - -  >button { -    min-width: 10px; -    min-height: 10px; -    border-radius: 5px; - -    border: none; -    margin: 5px 2px; -    padding: 0; -    background: #ccc; - -    transition: min-width .1s ease; - -    &.active { -      min-width: 25px; -    } - -    &.focused { -      background: #AD49E1; -    } -  } -} - -box.Tray { -  margin-right: 5px; - -  button { -    margin: 0; -    padding: 1px; -    border: none; -    border-radius: 0; -    min-width: 10px; -    min-height: 10px; - -    background: #000; -  } -} - -box.AudioVolume { -  margin-right: 20px; -  min-width: 140px; - -  image { -    margin-right: 5px; -  } - -  scale { -    margin: 0; -    padding: 0; -  } - -  trough { -    border-radius: 20px; -  } - -  highlight { -    border-radius: 20px; -    min-height: 10px; -  } - -  slider { -    margin: 0; -    min-height: 0; -    min-width: 0; -    opacity: 0; -  } -} - - -window.Notifications { -  background-color: #000; -  color: #fff; -} +@use "./widget/bar/Bar"; +@use "./widget/notifications/Notifications"; diff --git a/widget/bar/Bar.scss b/widget/bar/Bar.scss new file mode 100644 index 0000000..f57d7a7 --- /dev/null +++ b/widget/bar/Bar.scss @@ -0,0 +1,80 @@ +window.Bar { +  border: none; +  box-shadow: none; +  background-color: #000; +  color: #fff; + +  >box { +    padding: 0; +  } +} + +box.Workspaces { +  padding-left: 3px; + +  >button { +    min-width: 10px; +    min-height: 10px; +    border-radius: 5px; + +    border: none; +    margin: 5px 2px; +    padding: 0; +    background: #ccc; + +    transition: min-width .1s ease; + +    &.active { +      min-width: 25px; +    } + +    &.focused { +      background: #AD49E1; +    } +  } +} + +box.Tray { +  margin-right: 5px; + +  button { +    margin: 0; +    padding: 1px; +    border: none; +    border-radius: 0; +    min-width: 10px; +    min-height: 10px; + +    background: #000; +  } +} + +box.AudioVolume { +  margin-right: 20px; +  min-width: 140px; + +  image { +    margin-right: 5px; +  } + +  scale { +    margin: 0; +    padding: 0; +  } + +  trough { +    border-radius: 20px; +  } + +  highlight { +    border-radius: 20px; +    min-height: 10px; +  } + +  slider { +    margin: 0; +    min-height: 0; +    min-width: 0; +    opacity: 0; +  } +} diff --git a/widget/notifications/Notifications.scss b/widget/notifications/Notifications.scss new file mode 100644 index 0000000..5762df1 --- /dev/null +++ b/widget/notifications/Notifications.scss @@ -0,0 +1,141 @@ +$rosewater: #f5e0dc; +$flamingo: #f2cdcd; +$pink: #f5c2e7; +$mauve: #cba6f7; +$red: #f38ba8; +$maroon: #eba0ac; +$peach: #fab387; +$yellow: #f9e2af; +$green: #a6e3a1; +$teal: #94e2d5; +$sky: #89dceb; +$sapphire: #74c7ec; +$blue: #89b4fa; +$lavender: #b4befe; + +$text: #cdd6f4; +$subtext1: #bac2de; +$subtext0: #a6adc8; + +$overlay2: #9399b2; +$overlay1: #7f849c; +$overlay0: #6c7086; + +$surface2: #585b70; +$surface1: #45475a; +$surface0: #313244; + +$base: #1e1e2e; +$mantle: #181825; +$crust: #11111b; + +window.Notifications { +  box.Notification { +    margin: .25rem 1rem; +    border: 1px solid $blue; +    border-radius: 5px; +    min-width: 300px; + +    background-color: $base; + +    separator { +      background: $blue; +    } + +    &.low { +      border-color: $overlay1; + +      separator { +        background: $overlay1; +      } +    } + +    &.critical { +      border-color: $red; + +      separator { +        background: $red; +      } +    } + +    &:first-child { +      margin-top: 1rem; +    } + +    &:last-child { +      margin-bottom: 1rem; +    } + +    box.Header { +      label.Application { +        margin-top: .25rem; +        margin-left: .5rem; + +        color: $text; + +        font-weight: bold; +      } + +      label.Time { +        margin: .25rem .25rem 0 1rem; + +        color: $subtext1; +      } + +      button.Close { +        margin: 0; +        border: 0; +        border-radius: 0; +        padding: 5px; +        min-width: 0; +        min-height: 0; + +        background: none; + +        image { +          background-color: $red; +          border-radius: 50%; +          color: $red; +        } +      } +    } + +    box.Contents { +      margin: .5rem; + +      label.Summary { +        color: $text; + +        font-weight: bold; +      } + +      label.Body { +        color: $subtext1; +      } +    } + +    box.Actions { +      button { +        margin: 0 .25rem; +        border: 0; +        border-radius: 0; +        padding: 5px; +        min-width: 0; +        min-height: 0; + +        background: none; +        color: $text; + +        font-weight: bold; + +        &:first-child { +          margin-left: 0; +        } + +        &:last-child { +          margin-right: 0; +        } +      } +    } +  } +} diff --git a/widget/notifications/Notifications.tsx b/widget/notifications/Notifications.tsx new file mode 100644 index 0000000..6708b96 --- /dev/null +++ b/widget/notifications/Notifications.tsx @@ -0,0 +1,108 @@ +import { GLib, Variable } from "astal"; +import { bind, Subscribable } from "astal/binding"; +import { Astal, Gdk, Gtk } from "astal/gtk4"; +import AstalNotifd from "gi://AstalNotifd?version=0.1"; + +const notificationTimeout: number = 5000; + +class NotificationHandler implements Subscribable<Gtk.Widget[]> { +  private notifications: Variable<Gtk.Widget[]> = Variable([]); +  private notificationsMap: Map<number, Gtk.Widget> = new Map(); + +  constructor() { +    const notifd = AstalNotifd.get_default(); + +    notifd.connect("notified", (_source, id, _replaced) => { +      const n = notifd.get_notification(id); +      this.create(n); +      setTimeout(() => this.remove(id), notificationTimeout); +    }); + +    notifd.connect("resolved", (_source, id, _reason) => { +      this.remove(id); +    }); +  } + +  private rerender() { +    this.notifications.set([...this.notificationsMap.values()].reverse()); +  } + +  private create(n: AstalNotifd.Notification) { +    const notification = Notification(n); +    this.notificationsMap.get(n.id)?.emit("destroy"); +    this.notificationsMap.set(n.id, notification); +    this.rerender(); +  } + +  private remove(id: number) { +    this.notificationsMap.get(id)?.emit("destroy"); +    this.notificationsMap.delete(id); +    this.rerender(); +  } + +  subscribe(callback: (value: Gtk.Widget[]) => void): () => void { +    return this.notifications.subscribe(callback); +  } + +  get(): Gtk.Widget[] { +    return this.notifications.get(); +  } +} + +function getUrgencyClass(n: AstalNotifd.Notification): string { +  switch (n.urgency) { +    case AstalNotifd.Urgency.LOW: +      return "low"; +    case AstalNotifd.Urgency.CRITICAL: +      return "critical"; +    case AstalNotifd.Urgency.NORMAL: +    default: +      return "normal"; +  } +} + +function Notification(n: AstalNotifd.Notification) { +  const appName = n.appName; +  const time = GLib.DateTime.new_from_unix_local(n.time); + +  return <box cssClasses={["Notification", getUrgencyClass(n)]} vertical> +    <box cssClasses={["Header"]}> +      <label cssClasses={["Application"]} halign={Gtk.Align.START} label={appName || "Unknown"} /> +      <label cssClasses={["Time"]} hexpand halign={Gtk.Align.END} label={time.format("%I:%M:%S %p")!} /> +      <button cssClasses={["Close"]} onClicked={() => n.dismiss()}><image iconName="window-close-symbolic" /></button> +    </box> +    <Gtk.Separator visible /> +    <box cssClasses={["Contents"]}> +      <box vertical> +        <label cssClasses={["Summary"]} halign={Gtk.Align.START} label={n.summary} /> +        <label cssClasses={["Body"]} useMarkup wrap maxWidthChars={0} justify={Gtk.Justification.FILL} label={n.body} /> +      </box> +    </box> + +    {n.get_actions().length > 0 && <> +      <Gtk.Separator visible /> +      <box cssClasses={["Actions"]}> +        {n.get_actions().map(({ label, id }) => <button hexpand onClicked={() => n.invoke(id)}><label label={label} /></button>)} +      </box> +    </>} +  </box>; +} + +export default function(gdkmonitor: Gdk.Monitor) { +  const { TOP, RIGHT } = Astal.WindowAnchor; +  const notifications = new NotificationHandler(); + +  return <window +    // NOTE: is required due to last notification being displayed all the time +    visible={bind(notifications).as(v => v.length != 0)} + +    name={"Notifications"} +    cssClasses={["Notifications"]} +    gdkmonitor={gdkmonitor} +    exclusivity={Astal.Exclusivity.EXCLUSIVE} +    anchor={TOP | RIGHT}> +    <box vertical> +      {bind(notifications)} +    </box> +  </window> +} | 
