msg type -> wire::push() -> update handle wire into module/service/ect

This commit is contained in:
2026-09-12 15:04:06 +01:00
parent 539c80cda9
commit 27ec2b90a6
24 changed files with 411 additions and 204 deletions
+43 -24
View File
@@ -1,13 +1,14 @@
//! Clock module: subscribes to second ticks, renders the time in the bar.
//! Left-click toggles the seconds suffix; right-click opens a popup with
//! the current time in large text.
//! Clock module: subscribes to the clock service, renders the time in the
//! bar. Left-click toggles the seconds suffix — and pokes the running clock
//! service to switch which kind it publishes; right-click opens a popup.
use iced::widget::{container, mouse_area, text};
use iced::{Element, Task};
use common::{BarModule, ModuleEffect, ModuleMsg, Service};
use common::{
BarModule, ClockKind, ClockMsg, ClockPayload, Endpoint, ModuleEffect, Service, Wire,
};
use serde::Deserialize;
use services::datetime::{ClockKind, ClockPayload};
use toml::Table;
/// Big-text popup size (logical px).
@@ -22,15 +23,6 @@ pub enum ClockError {
NotBool(&'static str),
}
/// A module-local message for the clock.
#[derive(Clone)]
pub enum ClockMsg {
/// Toggle the seconds suffix.
ToggleSeconds,
/// Open the big-time popup.
OpenPopup,
}
pub struct Clock {
value: String,
show_seconds: bool,
@@ -44,11 +36,10 @@ struct ClockConfig {
impl Clock {
pub fn new(config: Option<Table>) -> Self {
let module_config = config_clock(config);
match module_config {
match config_clock(config) {
Some(x) => Self {
value: "--:--:--".to_string(),
show_seconds: { x.format },
show_seconds: x.format,
},
None => Self::default(),
}
@@ -70,7 +61,10 @@ impl Clock {
impl Default for Clock {
fn default() -> Self {
Self::new(None)
Self {
value: "--:--:--".to_string(),
show_seconds: false,
}
}
}
@@ -84,7 +78,8 @@ impl BarModule for Clock {
"clock"
}
fn view(&self, window_id: Option<iced::window::Id>) -> Element<'_, ModuleMsg> {
fn view(&self, window_id: Option<iced::window::Id>) -> Element<'_, Wire> {
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(),
@@ -92,16 +87,16 @@ impl BarModule for Clock {
// so the popup can anchor to these bounds.
None => container(
mouse_area(text(self.shown()).size(16))
.on_press(ModuleMsg::new(self.id(), ClockMsg::ToggleSeconds))
.on_right_press(ModuleMsg::new(self.id(), ClockMsg::OpenPopup)),
.on_press(Wire::module(me, self.id(), ClockMsg::ToggleSeconds))
.on_right_press(Wire::module(me, self.id(), ClockMsg::OpenPopup)),
)
.id(iced::widget::Id::from(self.id()))
.into(),
}
}
fn update(&mut self, msg: ModuleMsg) -> Task<ModuleEffect> {
// Subscription fan-out delivers a raw `ClockPayload`, not `ClockMsg`.
fn update(&mut self, msg: Wire) -> Task<ModuleEffect> {
// The service publishes a raw `ClockPayload`; UI sends `ClockMsg`.
if let Some(payload) = msg.downcast::<ClockPayload>() {
self.value = payload.value.clone();
return Task::none();
@@ -109,7 +104,17 @@ impl BarModule for Clock {
match msg.downcast::<ClockMsg>() {
Some(ClockMsg::ToggleSeconds) => {
self.show_seconds = !self.show_seconds;
Task::none()
let want = if self.show_seconds {
ClockKind::Seconds
} else {
ClockKind::Mins
};
// Poke the running clock service: tell it which kind to publish.
Task::done(ModuleEffect::Send(Wire::service(
Endpoint::module(self.id()),
ClockKind::Seconds.key().0,
want,
)))
}
Some(ClockMsg::OpenPopup) => Task::done(ModuleEffect::RequestPopup(
self.id().to_string(),
@@ -127,3 +132,17 @@ 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);
}
}