Files
barbar/crates/modules
..
2026-09-13 16:25:47 +01:00
2026-09-12 21:50:09 +01:00

modules

Bar modules. Each module renders a piece of the bar (or its popup) and is object-safe behind common::BarModule, so core holds them as Box<dyn BarModule> and never knows their message types — everything crosses the common::Wire boundary.

Message types are shared, so they live in the common crate, under common/src/messages/modules/. That is what lets a module and core name the same payload without either depending on the other.

Getting started: add a module

  1. Declare the module's messages in crates/common/src/messages/modules/greeter.rs:
//! Greeter module messages.

#[derive(Clone)]
pub enum GreeterMsg {
    Poke,
}
  1. Re-export them from crates/common/src/messages/modules/mod.rs:
mod clock;
mod greeter;

pub use clock::ClockMsg;
pub use greeter::GreeterMsg;
  1. Create crates/modules/src/greeter.rs:
use std::error::Error;

use iced::widget::text;
use iced::{Element, Task};

use common::{BarModule, Endpoint, GreeterMsg, ModuleEffect, Service, Wire};

pub struct Greeter {
    text: String,
}

impl Default for Greeter {
    fn default() -> Self {
        Self { text: "hi".into() }
    }
}

impl BarModule for Greeter {
    fn id(&self) -> &'static str {
        "greeter"
    }

    fn view(&self, _window_id: Option<iced::window::Id>) -> Element<'_, Wire> {
        text(&self.text).into()
    }

    fn update(&mut self, msg: Wire) -> Task<ModuleEffect> {
        match msg.downcast::<GreeterMsg>() {
            Some(GreeterMsg::Poke) => {
                self.text = "poked".into();
                Task::none()
            }
            None => Task::none(),
        }
    }

    /// Route keys this module subscribes to.
    fn services(&self) -> Vec<Service> {
        Vec::new(
    }

    /// Optional: read `module.greeter` from the config table.
    fn config(&mut self, _config: toml::Table) -> Result<(), Box<dyn Error>> {
        Ok(())
    }
}
  1. Register it in crates/modules/src/lib.rs's all() and re-export it:
mod greeter;
pub use greeter::Greeter;

pub fn all(module_config: toml::Table) -> Vec<Box<dyn BarModule>> {
    vec![
        Box::new(clock::Clock::new(Some(module_config.clone()))),
        Box::new(greeter::Greeter::default()),
    ]
}

That's it — core picks it up by id(). Use wire targeting:

// Module-local message back to yourself:
Wire::module(Endpoint::module(self.id()), self.id(), GreeterMsg::Poke)

To talk to a service, add it to services() and send via Wire::service(...); service ticks arrive in update() and you downcast to the payload type (e.g. common::ClockPayload). See src/clock.rs for a full example.