From ac70a9133aa50f32a8f873eb8ac8333fbd41b96b Mon Sep 17 00:00:00 2001 From: Doloro1978 Date: Tue, 15 Sep 2026 11:46:16 +0100 Subject: [PATCH] popup fixes and clock gets nicer popup --- .cargo/config.toml | 7 ++++ crates/common/src/effect.rs | 12 +++++-- crates/common/src/lib.rs | 2 +- crates/core/src/popup.rs | 69 +++++++++++++++++++++++-------------- crates/core/src/update.rs | 12 +++---- crates/modules/src/clock.rs | 37 +++++++++----------- flake.nix | 11 ++---- 7 files changed, 87 insertions(+), 63 deletions(-) create mode 100644 .cargo/config.toml diff --git a/.cargo/config.toml b/.cargo/config.toml new file mode 100644 index 0000000..e836095 --- /dev/null +++ b/.cargo/config.toml @@ -0,0 +1,7 @@ +# Link with mold from the repo itself, so `cargo build`/`cargo run` do not +# depend on a machine-local ~/.cargo/config.toml. -fuse-ld is understood by +# both the clang and gcc drivers, so this works with whatever `linker` the +# caller has configured (mold is in the devShell PATH; the nix build gets it +# via flake.nix). +[target.x86_64-unknown-linux-gnu] +rustflags = ["-C", "link-arg=-fuse-ld=mold"] diff --git a/crates/common/src/effect.rs b/crates/common/src/effect.rs index 4447e28..4138053 100644 --- a/crates/common/src/effect.rs +++ b/crates/common/src/effect.rs @@ -2,16 +2,24 @@ use iced::{window, Rectangle}; use crate::Wire; +#[derive(Clone, Debug)] +pub struct PopupSettings { + // pub name: String, + pub module_id: String, + pub element_id: String, + pub gap: i32, +} + /// What a module asks the app to do — the single effect vocabulary shared by /// modules and app, so mapping module output into a `Message` is a plain /// `.map(Message::Effect)` with no mirror enum. #[derive(Debug, Clone)] pub enum ModuleEffect { /// Toggle a popup for the given module id (element id anchors it). - RequestPopup(String, String), + RequestPopup(PopupSettings), /// A widget-tree pass reported a module's laid-out bounds; anchor the /// popup there. App-internal, but one effect type keeps routing trivial. - BoundsFound(String, Rectangle), + BoundsFound(Rectangle, PopupSettings), /// Request removal of the popup surface. ClosePopup(window::Id), /// Route a wire to its target (a module, a service inbox, or a topic). diff --git a/crates/common/src/lib.rs b/crates/common/src/lib.rs index 2dd1276..ffcb0ae 100644 --- a/crates/common/src/lib.rs +++ b/crates/common/src/lib.rs @@ -15,7 +15,7 @@ mod wire; pub use config::{BarbarConfig, Modules}; pub use display::Display; -pub use effect::ModuleEffect; +pub use effect::{ModuleEffect, PopupSettings}; pub use messages::*; pub use module::BarModule; pub use reusable_elements::pill; diff --git a/crates/core/src/popup.rs b/crates/core/src/popup.rs index 8e50811..9924cb9 100644 --- a/crates/core/src/popup.rs +++ b/crates/core/src/popup.rs @@ -5,12 +5,12 @@ //! then opens the popup there; the active module renders its content. use iced::advanced::widget as advanced_widget; -use iced::widget::{column, mouse_area, text}; -use iced::{Alignment, Element, Length, Rectangle, Task}; +use iced::widget::{column, container, mouse_area, text}; +use iced::{Alignment, Border, Element, Length, Padding, Rectangle, Task}; use iced_layershell::actions::IcedNewPopupSettings; use iced_layershell::reexport::{PopupAnchor, PopupGravity}; -use common::ModuleEffect; +use common::{pill, ModuleEffect, PopupSettings}; use crate::msg::popup_open; use crate::{Bar, Message, Msg}; @@ -19,17 +19,17 @@ use crate::{Bar, Message, Msg}; const POPUP_W: u32 = 150; const POPUP_H: u32 = 100; -/// Gap (logical px) between the bar's bottom edge and the popup. -const POPUP_GAP: i32 = 32; +// Gap (logical px) between the bar's bottom edge and the popup. +// const POPUP_GAP: i32 = 32; /// Opens a popup for `module_id` below its laid-out `bounds`. Size comes /// from the module's `popup_size()` (default: small menu). -pub fn open_popup(bar: &mut Bar, module_id: String, bounds: Rectangle) -> Task { - bar.active_popup = Some(module_id.clone()); +pub fn open_popup(bar: &mut Bar, bounds: Rectangle, settings: PopupSettings) -> Task { + bar.active_popup = Some(settings.module_id.clone()); let (w, h) = bar .modules - .get(module_id.as_str()) + .get(settings.module_id.as_str()) .and_then(|m| m.popup_size()) .unwrap_or((POPUP_W, POPUP_H)); @@ -41,14 +41,14 @@ pub fn open_popup(bar: &mut Bar, module_id: String, bounds: Rectangle) -> Task Task { /// Runs a widget-tree [`Operation`] that captures the laid-out bounds of the /// given element, then reports them via `BoundsFound` for the module. -pub fn capture_bounds(module_id: String, element_id: String) -> Task { - let target = iced::widget::Id::from(element_id); +pub fn capture_bounds(settings: PopupSettings) -> Task { + let target = iced::widget::Id::from(settings.element_id.clone()); struct FindBounds { target: iced::widget::Id, @@ -98,7 +98,9 @@ pub fn capture_bounds(module_id: String, element_id: String) -> Task { target, found: None, }) - .map(move |bounds| Message::Effect(ModuleEffect::BoundsFound(module_id.clone(), bounds))) + .map(move |bounds| -> Message { + Message::Effect(ModuleEffect::BoundsFound(bounds, settings.clone())) + }) } /// Popup widget tree on its own LayerShell surface; the active module @@ -116,15 +118,32 @@ pub fn view(bar: &Bar) -> Element<'_, Message> { }) .unwrap_or_else(|| text("unknown module").into()); - column![ - text(format!("{} menu", module_id)) - .size(14) - .width(Length::Fill) - .align_x(Alignment::Center), - content, - mouse_area(text("close").size(12)) - .on_press(Message::Effect(ModuleEffect::ClosePopup(popup_id))), - ] - .spacing(1) + container( + column![ + text(format!("{} menu", module_id)) + .size(14) + .width(Length::Fill) + .align_x(Alignment::Center), + content, + mouse_area(container(text("close").size(12)).style(pill)) + .on_press(Message::Effect(ModuleEffect::ClosePopup(popup_id))), + ] + .spacing(1), + ) + .padding(Padding::from(4)) + .style(|theme| container::Style { + text_color: None, + background: Some( + iced::Background::Color(theme.extended_palette().background.base.color) + .scale_alpha(0.4), + ), + border: Border { + width: 3.0, + radius: 10.into(), + color: theme.extended_palette().secondary.strong.color, + }, + snap: true, + ..Default::default() + }) .into() } diff --git a/crates/core/src/update.rs b/crates/core/src/update.rs index 2f635b8..7a4e7ca 100644 --- a/crates/core/src/update.rs +++ b/crates/core/src/update.rs @@ -78,18 +78,18 @@ fn route(bar: &mut Bar, wire: Wire) -> Task { /// Runs an effect; may set up bar state for it (e.g. which popup is open). fn handle_effect(bar: &mut Bar, effect: ModuleEffect) -> Task { match effect { - ModuleEffect::RequestPopup(module_id, element_id) => { + ModuleEffect::RequestPopup(settings) => { // Toggle: close if already open for this module, else open. - if bar.active_popup.as_deref() == Some(module_id.as_str()) { - tracing::debug!(module = %module_id, "closing popup"); + if bar.active_popup.as_deref() == Some(settings.module_id.as_str()) { + tracing::debug!(module = settings.module_id, "closing popup"); popup::close_popup(bar) } else { - tracing::debug!(module = %module_id, "opening popup"); - popup::capture_bounds(module_id, element_id) + tracing::debug!(module = settings.module_id, "opening popup"); + popup::capture_bounds(settings) } } - ModuleEffect::BoundsFound(module_id, bounds) => popup::open_popup(bar, module_id, bounds), + ModuleEffect::BoundsFound(bounds, settings) => popup::open_popup(bar, bounds, settings), ModuleEffect::ClosePopup(id) => { // Request removal only; keep popup state so the popup content diff --git a/crates/modules/src/clock.rs b/crates/modules/src/clock.rs index d3e1bf1..6c0f14f 100644 --- a/crates/modules/src/clock.rs +++ b/crates/modules/src/clock.rs @@ -3,10 +3,12 @@ //! service to switch which kind it publishes; right-click opens a popup. use iced::widget::{container, mouse_area, text}; -use iced::{Element, Padding, Task}; +use iced::Length::Fill; +use iced::{alignment, Element, Padding, Task}; use common::{ - pill, BarModule, ClockKind, ClockMsg, ClockPayload, Endpoint, ModuleEffect, Service, Wire, + pill, BarModule, ClockKind, ClockMsg, ClockPayload, Endpoint, ModuleEffect, PopupSettings, + Service, Wire, }; use serde::Deserialize; use toml::Table; @@ -82,7 +84,12 @@ impl BarModule for Clock { let me = Endpoint::module(self.id()); match window_id { // Popup surface: current time in large text. - Some(_) => container(text(&self.value).size(56)).padding(20).into(), + Some(_) => container(text(&self.value).size(56)) + .align_x(alignment::Horizontal::Center) + .align_y(alignment::Vertical::Center) + .padding(20) + .width(Fill) + .into(), // Bar surface: clickable time. Container carries the module id // so the popup can anchor to these bounds. None => container( @@ -125,10 +132,12 @@ impl BarModule for Clock { want, ))) } - Some(ClockMsg::OpenPopup) => Task::done(ModuleEffect::RequestPopup( - self.id().to_string(), - self.id().to_string(), - )), + Some(ClockMsg::OpenPopup) => Task::done(ModuleEffect::RequestPopup(PopupSettings { + // name: "barbar".into(), + module_id: self.id().into(), + element_id: "clock".into(), + gap: 8, + })), None => Task::none(), } } @@ -141,17 +150,3 @@ impl BarModule for Clock { Some((POPUP_W, POPUP_H)) } } - -#[cfg(test)] -mod tests { - use super::*; - - /// Regression: `new(None)` used to call `default()`, which called - /// `new(None)` — infinite recursion (hard stack overflow) whenever the - /// clock config was absent or failed to parse. - #[test] - fn no_config_terminates() { - assert!(!Clock::new(None).show_seconds); - assert!(!Clock::default().show_seconds); - } -} diff --git a/flake.nix b/flake.nix index 12e0112..3a7afa3 100644 --- a/flake.nix +++ b/flake.nix @@ -79,17 +79,12 @@ # from it, and loads libspa-support from there at runtime. pkgs.pipewire.dev ] + # mold for the final link; the flag itself comes from the + # repo-wide .cargo/config.toml so dev and nix builds share one. ++ pkgs.lib.optionals pkgs.stdenv.hostPlatform.isLinux [ pkgs.mold ]; - # Link with mold (fast linker). Lives in commonArgs so deps and the - # final crate share one flag set; note this rebuilds all dependency - # crates once when first added. - env = pkgs.lib.optionalAttrs pkgs.stdenv.hostPlatform.isLinux { - RUSTFLAGS = "-C link-arg=-fuse-ld=mold"; - }; - buildInputs = guiLibs ++ pkgs.lib.optionals pkgs.stdenv.hostPlatform.isDarwin [ @@ -144,7 +139,7 @@ # Put the GUI shared libraries on the runtime library path so winit # can dlopen libwayland and wgpu can find the vulkan loader. # No RUSTFLAGS here: an env RUSTFLAGS would override (not merge - # with) ~/.cargo/config.toml's rustflags, giving devShell and + # with) .cargo/config.toml's rustflags, giving devShell and # non-devShell builds different fingerprints and force-rebuilding # every dependency on each switch. shellHook = ''