diff --git a/Cargo.lock b/Cargo.lock index 82fc13f..d0dfc5a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -12,6 +12,7 @@ checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c" name = "aster" version = "0.1.0" dependencies = [ + "futures-lite", "gtk4", "gtk4-layer-shell", "hyprland", diff --git a/Cargo.toml b/Cargo.toml index 1ddbab0..f36aab2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,7 +7,8 @@ edition = "2021" gtk4 = { version = "0.11", features = ["v4_12"] } gtk4-layer-shell = "0.8" relm4 = { version = "0.11", features = ["macros"] } -hyprland = "0.4.0-beta.3" +hyprland = { version = "0.4.0-beta.3", features = ["tokio"] } +futures-lite = "2" zbus = { version = "5", default-features = false, features = ["tokio"] } tokio = { version = "1", features = ["full"] } serde = { version = "1", features = ["derive"] } diff --git a/src/bar/workspaces.rs b/src/bar/workspaces.rs index 8b13789..1c4f257 100644 --- a/src/bar/workspaces.rs +++ b/src/bar/workspaces.rs @@ -1 +1,50 @@ +use futures_lite::StreamExt; +use gtk4::prelude::*; +use hyprland::{ + data::{Workspace, Workspaces}, + event_listener::{Event, EventStream}, + prelude::*, + shared::WorkspaceId, +}; +use relm4::ComponentSender; +use crate::AppInput; + +pub fn spawn_watcher(sender: ComponentSender) { + relm4::spawn(async move { + if let Ok(ws) = Workspaces::get_async().await { + sender.input(AppInput::WorkspaceList(ws.to_vec())); + } + if let Ok(active) = Workspace::get_active_async().await { + sender.input(AppInput::ActiveWorkspace(active.id)); + } + + let mut stream = EventStream::new(); + while let Some(Ok(event)) = stream.next().await { + match event { + Event::WorkspaceChanged(data) => { + sender.input(AppInput::ActiveWorkspace(data.id)); + } + Event::WorkspaceAdded(_) | Event::WorkspaceDeleted(_) => { + if let Ok(ws) = Workspaces::get_async().await { + sender.input(AppInput::WorkspaceList(ws.to_vec())); + } + } + _ => {} + } + } + }); +} + +pub fn make_button(id: WorkspaceId, name: &str, active: WorkspaceId) -> gtk4::Button { + let btn = gtk4::Button::with_label(name); + btn.add_css_class("workspace-btn"); + if id == active { + btn.add_css_class("active"); + } + btn.connect_clicked(move |_| { + use hyprland::dispatch::{Dispatch, DispatchType, WorkspaceIdentifierWithSpecial}; + let _ = Dispatch::call(DispatchType::Workspace(WorkspaceIdentifierWithSpecial::Id(id))); + }); + btn +} diff --git a/src/main.rs b/src/main.rs index 163ca6b..c8b544c 100644 --- a/src/main.rs +++ b/src/main.rs @@ -4,14 +4,27 @@ mod theme; use gtk4::prelude::*; use gtk4_layer_shell::{Edge, Layer, LayerShell}; +use hyprland::data::Workspace; +use hyprland::shared::WorkspaceId; use relm4::prelude::*; -struct App; +pub struct App { + workspaces: Vec, + active_ws: WorkspaceId, + // Stored handle so update() can manipulate the live widget directly. + workspace_box: gtk4::Box, +} -#[relm4::component] +#[derive(Debug)] +pub enum AppInput { + WorkspaceList(Vec), + ActiveWorkspace(WorkspaceId), +} + +#[relm4::component(pub)] impl SimpleComponent for App { type Init = (); - type Input = (); + type Input = AppInput; type Output = (); view! { @@ -24,11 +37,13 @@ impl SimpleComponent for App { #[wrap(Some)] set_start_widget = >k::Box { set_orientation: gtk::Orientation::Horizontal, - set_spacing: 4, + set_spacing: 0, set_margin_start: 8, - gtk::Label { - set_label: "1 2 3", + #[name = "workspace_box"] + gtk::Box { + set_orientation: gtk::Orientation::Horizontal, + set_spacing: 4, } }, @@ -54,7 +69,7 @@ impl SimpleComponent for App { fn init( _: Self::Init, root: Self::Root, - _sender: ComponentSender, + sender: ComponentSender, ) -> ComponentParts { root.init_layer_shell(); root.set_layer(Layer::Top); @@ -63,15 +78,48 @@ impl SimpleComponent for App { root.set_anchor(Edge::Right, true); root.set_exclusive_zone(32); - let model = App; + // Placeholder until view_output! gives us the real handle. + let mut model = App { + workspaces: vec![], + active_ws: 1, + workspace_box: gtk4::Box::new(gtk4::Orientation::Horizontal, 4), + }; let widgets = view_output!(); + // Swap in the actual widget so update() can reach it. + model.workspace_box = widgets.workspace_box.clone(); + theme::apply(); + bar::workspaces::spawn_watcher(sender); ComponentParts { model, widgets } } - fn update(&mut self, _: Self::Input, _: ComponentSender) {} + fn update(&mut self, msg: Self::Input, _: ComponentSender) { + match msg { + AppInput::WorkspaceList(list) => { + let mut sorted = list; + sorted.sort_by_key(|w| w.id); + self.workspaces = sorted; + } + AppInput::ActiveWorkspace(id) => { + self.active_ws = id; + } + } + self.rebuild_buttons(); + } +} + +impl App { + fn rebuild_buttons(&self) { + while let Some(child) = self.workspace_box.first_child() { + self.workspace_box.remove(&child); + } + for ws in &self.workspaces { + self.workspace_box + .append(&bar::workspaces::make_button(ws.id, &ws.name, self.active_ws)); + } + } } fn main() {