summaryrefslogtreecommitdiff
path: root/examples/gtk3/js/simple-bar
diff options
context:
space:
mode:
authorAylur <[email protected]>2025-01-16 17:37:00 +0100
committerAylur <[email protected]>2025-01-16 17:37:04 +0100
commit9e8634d892c559c5b44565a68bf35b13cbcb5572 (patch)
tree36a8b911e919959cdf64d3c64646f5066c6a2523 /examples/gtk3/js/simple-bar
parentbc796ac226800c43e724e27f53f410c157acaffe (diff)
add: gtk3 ts popover example
closes #224 closes #157
Diffstat (limited to 'examples/gtk3/js/simple-bar')
-rw-r--r--examples/gtk3/js/simple-bar/README.md12
-rw-r--r--examples/gtk3/js/simple-bar/app.ts13
-rw-r--r--examples/gtk3/js/simple-bar/style.scss106
-rw-r--r--examples/gtk3/js/simple-bar/widget/Bar.tsx161
4 files changed, 292 insertions, 0 deletions
diff --git a/examples/gtk3/js/simple-bar/README.md b/examples/gtk3/js/simple-bar/README.md
new file mode 100644
index 0000000..f92b20e
--- /dev/null
+++ b/examples/gtk3/js/simple-bar/README.md
@@ -0,0 +1,12 @@
+# Simple Bar Example
+
+![simple-bar](https://github.com/user-attachments/assets/a306c864-56b7-44c4-8820-81f424f32b9b)
+
+A simple bar for Hyprland using
+
+- [Battery library](https://aylur.github.io/astal/guide/libraries/battery).
+- [Hyprland library](https://aylur.github.io/astal/guide/libraries/hyprland).
+- [Mpris library](https://aylur.github.io/astal/guide/libraries/mpris).
+- [Network library](https://aylur.github.io/astal/guide/libraries/network).
+- [Tray library](https://aylur.github.io/astal/guide/libraries/tray).
+- [WirePlumber library](https://aylur.github.io/astal/guide/libraries/wireplumber).
diff --git a/examples/gtk3/js/simple-bar/app.ts b/examples/gtk3/js/simple-bar/app.ts
new file mode 100644
index 0000000..4b7ea48
--- /dev/null
+++ b/examples/gtk3/js/simple-bar/app.ts
@@ -0,0 +1,13 @@
+import { App } from "astal/gtk3"
+import style from "./style.scss"
+import Bar from "./widget/Bar"
+
+App.start({
+ css: style,
+ instanceName: "js",
+ requestHandler(request, res) {
+ print(request)
+ res("ok")
+ },
+ main: () => App.get_monitors().map(Bar),
+})
diff --git a/examples/gtk3/js/simple-bar/style.scss b/examples/gtk3/js/simple-bar/style.scss
new file mode 100644
index 0000000..f5f771a
--- /dev/null
+++ b/examples/gtk3/js/simple-bar/style.scss
@@ -0,0 +1,106 @@
+@use "sass:color";
+
+$bg: #212223;
+$fg: #f1f1f1;
+$accent: #378DF7;
+$radius: 7px;
+
+window.Bar {
+ border: none;
+ box-shadow: none;
+ background-color: $bg;
+ color: $fg;
+ font-size: 1.1em;
+ font-weight: bold;
+
+ label {
+ margin: 0 8px;
+ }
+
+ .Workspaces {
+ button {
+ all: unset;
+ background-color: transparent;
+
+ &:hover label {
+ background-color: color.adjust($fg, $alpha: -0.84);
+ border-color: color.adjust($accent, $alpha: -0.8);
+ }
+
+ &:active label {
+ background-color: color.adjust($fg, $alpha: -0.8)
+ }
+ }
+
+ label {
+ transition: 200ms;
+ padding: 0 8px;
+ margin: 2px;
+ border-radius: $radius;
+ border: 1pt solid transparent;
+ }
+
+ .focused label {
+ color: $accent;
+ border-color: $accent;
+ }
+ }
+
+ .SysTray {
+ margin-right: 8px;
+
+ button {
+ padding: 0 4px;
+ }
+ }
+
+ .FocusedClient {
+ color: $accent;
+ }
+
+ .Media .Cover {
+ min-height: 1.2em;
+ min-width: 1.2em;
+ border-radius: $radius;
+ background-position: center;
+ background-size: contain;
+ }
+
+ .Battery label {
+ padding-left: 0;
+ margin-left: 0;
+ }
+
+ .AudioSlider {
+ * {
+ all: unset;
+ }
+
+ icon {
+ margin-right: .6em;
+ }
+
+ & {
+ margin: 0 1em;
+ }
+
+ trough {
+ background-color: color.adjust($fg, $alpha: -0.8);
+ border-radius: $radius;
+ }
+
+ highlight {
+ background-color: $accent;
+ min-height: .8em;
+ border-radius: $radius;
+ }
+
+ slider {
+ background-color: $fg;
+ border-radius: $radius;
+ min-height: 1em;
+ min-width: 1em;
+ margin: -.2em;
+ }
+ }
+}
diff --git a/examples/gtk3/js/simple-bar/widget/Bar.tsx b/examples/gtk3/js/simple-bar/widget/Bar.tsx
new file mode 100644
index 0000000..6592f6a
--- /dev/null
+++ b/examples/gtk3/js/simple-bar/widget/Bar.tsx
@@ -0,0 +1,161 @@
+import { App } from "astal/gtk3"
+import { Variable, GLib, bind } from "astal"
+import { Astal, Gtk, Gdk } from "astal/gtk3"
+import Hyprland from "gi://AstalHyprland"
+import Mpris from "gi://AstalMpris"
+import Battery from "gi://AstalBattery"
+import Wp from "gi://AstalWp"
+import Network from "gi://AstalNetwork"
+import Tray from "gi://AstalTray"
+
+function SysTray() {
+ const tray = Tray.get_default()
+
+ return <box className="SysTray">
+ {bind(tray, "items").as(items => items.map(item => (
+ <menubutton
+ tooltipMarkup={bind(item, "tooltipMarkup")}
+ usePopover={false}
+ actionGroup={bind(item, "actionGroup").as(ag => ["dbusmenu", ag])}
+ menuModel={bind(item, "menuModel")}>
+ <icon gicon={bind(item, "gicon")} />
+ </menubutton>
+ )))}
+ </box>
+}
+
+function Wifi() {
+ const network = Network.get_default()
+ const wifi = bind(network, "wifi")
+
+ return <box visible={wifi.as(Boolean)}>
+ {wifi.as(wifi => wifi && (
+ <icon
+ tooltipText={bind(wifi, "ssid").as(String)}
+ className="Wifi"
+ icon={bind(wifi, "iconName")}
+ />
+ ))}
+ </box>
+
+}
+
+function AudioSlider() {
+ const speaker = Wp.get_default()?.audio.defaultSpeaker!
+
+ return <box className="AudioSlider" css="min-width: 140px">
+ <icon icon={bind(speaker, "volumeIcon")} />
+ <slider
+ hexpand
+ onDragged={({ value }) => speaker.volume = value}
+ value={bind(speaker, "volume")}
+ />
+ </box>
+}
+
+function BatteryLevel() {
+ const bat = Battery.get_default()
+
+ return <box className="Battery"
+ visible={bind(bat, "isPresent")}>
+ <icon icon={bind(bat, "batteryIconName")} />
+ <label label={bind(bat, "percentage").as(p =>
+ `${Math.floor(p * 100)} %`
+ )} />
+ </box>
+}
+
+function Media() {
+ const mpris = Mpris.get_default()
+
+ return <box className="Media">
+ {bind(mpris, "players").as(ps => ps[0] ? (
+ <box>
+ <box
+ className="Cover"
+ valign={Gtk.Align.CENTER}
+ css={bind(ps[0], "coverArt").as(cover =>
+ `background-image: url('${cover}');`
+ )}
+ />
+ <label
+ label={bind(ps[0], "title").as(() =>
+ `${ps[0].title} - ${ps[0].artist}`
+ )}
+ />
+ </box>
+ ) : (
+ "Nothing Playing"
+ ))}
+ </box>
+}
+
+function Workspaces() {
+ const hypr = Hyprland.get_default()
+
+ return <box className="Workspaces">
+ {bind(hypr, "workspaces").as(wss => wss
+ .filter(ws => !(ws.id >= -99 && ws.id <= -2)) // filter out special workspaces
+ .sort((a, b) => a.id - b.id)
+ .map(ws => (
+ <button
+ className={bind(hypr, "focusedWorkspace").as(fw =>
+ ws === fw ? "focused" : "")}
+ onClicked={() => ws.focus()}>
+ {ws.id}
+ </button>
+ ))
+ )}
+ </box>
+}
+
+function FocusedClient() {
+ const hypr = Hyprland.get_default()
+ const focused = bind(hypr, "focusedClient")
+
+ return <box
+ className="Focused"
+ visible={focused.as(Boolean)}>
+ {focused.as(client => (
+ client && <label label={bind(client, "title").as(String)} />
+ ))}
+ </box>
+}
+
+function Time({ format = "%H:%M - %A %e." }) {
+ const time = Variable<string>("").poll(1000, () =>
+ GLib.DateTime.new_now_local().format(format)!)
+
+ return <label
+ className="Time"
+ onDestroy={() => time.drop()}
+ label={time()}
+ />
+}
+
+export default function Bar(monitor: Gdk.Monitor) {
+ const { TOP, LEFT, RIGHT } = Astal.WindowAnchor
+
+ return <window
+ className="Bar"
+ gdkmonitor={monitor}
+ exclusivity={Astal.Exclusivity.EXCLUSIVE}
+ anchor={TOP | LEFT | RIGHT}>
+ <centerbox>
+ <box hexpand halign={Gtk.Align.START}>
+ <Workspaces />
+ <FocusedClient />
+ </box>
+ <box>
+ <Media />
+ </box>
+ <box hexpand halign={Gtk.Align.END} >
+ <SysTray />
+ <Wifi />
+ <AudioSlider />
+ <BatteryLevel />
+ <Time />
+ </box>
+ </centerbox>
+ </window>
+}