diff options
author | Aylur <[email protected]> | 2025-01-16 17:37:00 +0100 |
---|---|---|
committer | Aylur <[email protected]> | 2025-01-16 17:37:04 +0100 |
commit | 9e8634d892c559c5b44565a68bf35b13cbcb5572 (patch) | |
tree | 36a8b911e919959cdf64d3c64646f5066c6a2523 /examples/gtk3/py/simple-bar | |
parent | bc796ac226800c43e724e27f53f410c157acaffe (diff) |
add: gtk3 ts popover example
closes #224
closes #157
Diffstat (limited to 'examples/gtk3/py/simple-bar')
-rw-r--r-- | examples/gtk3/py/simple-bar/README.md | 13 | ||||
-rw-r--r-- | examples/gtk3/py/simple-bar/__init__.py | 0 | ||||
-rwxr-xr-x | examples/gtk3/py/simple-bar/app.py | 36 | ||||
-rw-r--r-- | examples/gtk3/py/simple-bar/style.scss | 106 | ||||
-rw-r--r-- | examples/gtk3/py/simple-bar/versions.py | 15 | ||||
-rw-r--r-- | examples/gtk3/py/simple-bar/widget/Bar.py | 251 | ||||
-rw-r--r-- | examples/gtk3/py/simple-bar/widget/__init__.py | 0 |
7 files changed, 421 insertions, 0 deletions
diff --git a/examples/gtk3/py/simple-bar/README.md b/examples/gtk3/py/simple-bar/README.md new file mode 100644 index 0000000..48cc27c --- /dev/null +++ b/examples/gtk3/py/simple-bar/README.md @@ -0,0 +1,13 @@ +# Simple Bar Example + + + +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). +- [dart-sass](https://sass-lang.com/dart-sass/) as the css precompiler diff --git a/examples/gtk3/py/simple-bar/__init__.py b/examples/gtk3/py/simple-bar/__init__.py new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/examples/gtk3/py/simple-bar/__init__.py diff --git a/examples/gtk3/py/simple-bar/app.py b/examples/gtk3/py/simple-bar/app.py new file mode 100755 index 0000000..d95dc0e --- /dev/null +++ b/examples/gtk3/py/simple-bar/app.py @@ -0,0 +1,36 @@ +#!/usr/bin/env python3 +import sys +import versions +import subprocess +from gi.repository import AstalIO, Astal, Gio +from widget.Bar import Bar +from pathlib import Path + +scss = str(Path(__file__).parent.resolve() / "style.scss") +css = "/tmp/style.css" + + +class App(Astal.Application): + def do_astal_application_request( + self, msg: str, conn: Gio.SocketConnection + ) -> None: + print(msg) + AstalIO.write_sock(conn, "hello") + + def do_activate(self) -> None: + self.hold() + subprocess.run(["sass", scss, css]) + self.apply_css(css, True) + for mon in self.get_monitors(): + self.add_window(Bar(mon)) + + +instance_name = "python" +app = App(instance_name=instance_name) + +if __name__ == "__main__": + try: + app.acquire_socket() + app.run(None) + except Exception as e: + print(AstalIO.send_message(instance_name, "".join(sys.argv[1:]))) diff --git a/examples/gtk3/py/simple-bar/style.scss b/examples/gtk3/py/simple-bar/style.scss new file mode 100644 index 0000000..f5f771a --- /dev/null +++ b/examples/gtk3/py/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/py/simple-bar/versions.py b/examples/gtk3/py/simple-bar/versions.py new file mode 100644 index 0000000..0e57708 --- /dev/null +++ b/examples/gtk3/py/simple-bar/versions.py @@ -0,0 +1,15 @@ +import gi + +gi.require_version("AstalIO", "0.1") +gi.require_version("Astal", "3.0") +gi.require_version("Gtk", "3.0") +gi.require_version("Gdk", "3.0") +gi.require_version("Gio", "2.0") +gi.require_version("GObject", "2.0") + +gi.require_version("AstalBattery", "0.1") +gi.require_version("AstalWp", "0.1") +gi.require_version("AstalNetwork", "0.1") +gi.require_version("AstalTray", "0.1") +gi.require_version("AstalMpris", "0.1") +gi.require_version("AstalHyprland", "0.1") diff --git a/examples/gtk3/py/simple-bar/widget/Bar.py b/examples/gtk3/py/simple-bar/widget/Bar.py new file mode 100644 index 0000000..555ab85 --- /dev/null +++ b/examples/gtk3/py/simple-bar/widget/Bar.py @@ -0,0 +1,251 @@ +import math +from gi.repository import ( + AstalIO, + Astal, + Gtk, + Gdk, + GLib, + GObject, + AstalBattery as Battery, + AstalWp as Wp, + AstalNetwork as Network, + AstalTray as Tray, + AstalMpris as Mpris, + AstalHyprland as Hyprland, +) + +SYNC = GObject.BindingFlags.SYNC_CREATE + + +class Workspaces(Gtk.Box): + def __init__(self) -> None: + super().__init__() + Astal.widget_set_class_names(self, ["Workspaces"]) + hypr = Hyprland.get_default() + hypr.connect("notify::workspaces", self.sync) + hypr.connect("notify::focused-workspace", self.sync) + self.sync() + + def sync(self, *_): + hypr = Hyprland.get_default() + for child in self.get_children(): + child.destroy() + + for ws in hypr.get_workspaces(): + if not (ws.get_id() >= -99 and ws.get_id() <= -2): # filter out special workspaces + self.add(self.button(ws)) + + def button(self, ws): + hypr = Hyprland.get_default() + btn = Gtk.Button(visible=True) + btn.add(Gtk.Label(visible=True, label=ws.get_id())) + + if hypr.get_focused_workspace() == ws: + Astal.widget_set_class_names(btn, ["focused"]) + + btn.connect("clicked", lambda *_: ws.focus()) + return btn + + +class FocusedClient(Gtk.Label): + def __init__(self) -> None: + super().__init__() + Astal.widget_set_class_names(self, ["Focused"]) + Hyprland.get_default().connect("notify::focused-client", self.sync) + self.sync() + + def sync(self, *_): + client = Hyprland.get_default().get_focused_client() + if client is None: + return self.set_label("") + + client.bind_property("title", self, "label", SYNC) + + +class Media(Gtk.Box): + def __init__(self) -> None: + super().__init__() + self.players = {} + mpris = Mpris.get_default() + Astal.widget_set_class_names(self, ["Media"]) + mpris.connect("notify::players", self.sync) + self.sync() + + def sync(self): + mpris = Mpris.get_default() + for child in self.get_children(): + child.destroy() + + if len(mpris.get_players()) == 0: + self.add(Gtk.Label(visible=True, label="Nothing Playing")) + return + + player = mpris.get_players()[0] + label = Gtk.Label(visible=True) + cover = Gtk.Box(valign=Gtk.Align.CENTER) + Astal.widget_set_class_names(cover, ["Cover"]) + + self.add(cover) + self.add(label) + + player.bind_property( + "title", + label, + "label", + SYNC, + lambda *_: f"{player.get_artist()} - {player.get_title()}", + ) + + def on_cover_art(*_): + Astal.widget_set_css( + cover, f"background-image: url('{player.get_cover_art()}')" + ) + + id = player.connect("notify::cover-art", on_cover_art) + cover.connect("destroy", lambda _: player.disconnect(id)) + on_cover_art() + + +class SysTray(Gtk.Box): + def __init__(self) -> None: + super().__init__() + Astal.widget_set_class_names(self, ["SysTray"]) + self.items = {} + tray = Tray.get_default() + tray.connect("item_added", self.add_item) + tray.connect("item_removed", self.remove_item) + + def add_item(self, _: Tray.Tray, id: str): + if id in self.items: + return + + item = Tray.get_default().get_item(id) + btn = Gtk.MenuButton(use_popover=False, visible=True) + icon = Astal.Icon(visible=True) + + item.bind_property("tooltip-markup", btn, "tooltip-markup", SYNC) + item.bind_property("gicon", icon, "gicon", SYNC) + item.bind_property("menu-model", btn, "menu-model", SYNC) + btn.insert_action_group("dbusmenu", item.get_action_group()) + + def on_action_group(*args): + btn.insert_action_group("dbusmenu", item.get_action_group()) + + item.connect("notify::action-group", on_action_group) + + btn.add(icon) + self.add(btn) + self.items[id] = btn + + def remove_item(self, _: Tray.Tray, id: str): + if id in self.items: + del self.items[id] + + +class Wifi(Astal.Icon): + def __init__(self) -> None: + super().__init__() + Astal.widget_set_class_names(self, ["Wifi"]) + wifi = Network.get_default().get_wifi() + if wifi: + wifi.bind_property("ssid", self, "tooltip-text", SYNC) + wifi.bind_property("icon-name", self, "icon", SYNC) + + +class AudioSlider(Gtk.Box): + def __init__(self) -> None: + super().__init__() + Astal.widget_set_class_names(self, ["AudioSlider"]) + Astal.widget_set_css(self, "min-width: 140px") + + icon = Astal.Icon() + slider = Astal.Slider(hexpand=True) + + self.add(icon) + self.add(slider) + + speaker = Wp.get_default().get_audio().get_default_speaker() + speaker.bind_property("volume-icon", icon, "icon", SYNC) + speaker.bind_property("volume", slider, "value", SYNC) + slider.connect("dragged", lambda *_: speaker.set_volume(slider.get_value())) + + +class BatteryLevel(Gtk.Box): + def __init__(self) -> None: + super().__init__() + Astal.widget_set_class_names(self, ["Battery"]) + + icon = Astal.Icon() + label = Astal.Label() + + self.add(icon) + self.add(label) + + bat = Battery.get_default() + bat.bind_property("is-present", self, "visible", SYNC) + bat.bind_property("battery-icon-name", icon, "icon", SYNC) + bat.bind_property( + "percentage", + label, + "label", + SYNC, + lambda _, value: f"{math.floor(value * 100)}%", + ) + + +class Time(Astal.Label): + def __init__(self, format="%H:%M - %A %e."): + super().__init__() + self.format = format + self.interval = AstalIO.Time.interval(1000, self.sync) + self.connect("destroy", self.interval.cancel) + Astal.widget_set_class_names(self, ["Time"]) + + def sync(self): + self.set_label(GLib.DateTime.new_now_local().format(self.format)) + + +class Left(Gtk.Box): + def __init__(self) -> None: + super().__init__(hexpand=True, halign=Gtk.Align.START) + self.add(Workspaces()) + self.add(FocusedClient()) + + +class Center(Gtk.Box): + def __init__(self) -> None: + super().__init__() + self.add(Media()) + + +class Right(Gtk.Box): + def __init__(self) -> None: + super().__init__(hexpand=True, halign=Gtk.Align.END) + self.add(SysTray()) + self.add(Wifi()) + self.add(AudioSlider()) + self.add(BatteryLevel()) + self.add(Time()) + + +class Bar(Astal.Window): + def __init__(self, monitor: Gdk.Monitor): + super().__init__( + anchor=Astal.WindowAnchor.LEFT + | Astal.WindowAnchor.RIGHT + | Astal.WindowAnchor.TOP, + gdkmonitor=monitor, + exclusivity=Astal.Exclusivity.EXCLUSIVE, + ) + + Astal.widget_set_class_names(self, ["Bar"]) + + self.add( + Astal.CenterBox( + start_widget=Left(), + center_widget=Center(), + end_widget=Right(), + ) + ) + + self.show_all() diff --git a/examples/gtk3/py/simple-bar/widget/__init__.py b/examples/gtk3/py/simple-bar/widget/__init__.py new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/examples/gtk3/py/simple-bar/widget/__init__.py |