refec
This commit is contained in:
Generated
+39
-10
@@ -98,16 +98,6 @@ version = "1.5.1"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53"
|
checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "barbar-core"
|
|
||||||
version = "0.1.0"
|
|
||||||
dependencies = [
|
|
||||||
"chrono",
|
|
||||||
"iced",
|
|
||||||
"iced_layershell",
|
|
||||||
"tokio",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "bit-set"
|
name = "bit-set"
|
||||||
version = "0.8.0"
|
version = "0.8.0"
|
||||||
@@ -340,6 +330,13 @@ dependencies = [
|
|||||||
"memchr",
|
"memchr",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "common"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"iced",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "concurrent-queue"
|
name = "concurrent-queue"
|
||||||
version = "2.5.0"
|
version = "2.5.0"
|
||||||
@@ -349,6 +346,19 @@ dependencies = [
|
|||||||
"crossbeam-utils",
|
"crossbeam-utils",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "core"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"chrono",
|
||||||
|
"common",
|
||||||
|
"iced",
|
||||||
|
"iced_layershell",
|
||||||
|
"module-clock",
|
||||||
|
"service-datatime",
|
||||||
|
"tokio",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "core-foundation"
|
name = "core-foundation"
|
||||||
version = "0.9.4"
|
version = "0.9.4"
|
||||||
@@ -1702,6 +1712,15 @@ dependencies = [
|
|||||||
"paste",
|
"paste",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "module-clock"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"common",
|
||||||
|
"iced",
|
||||||
|
"service-datatime",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "naga"
|
name = "naga"
|
||||||
version = "27.0.3"
|
version = "27.0.3"
|
||||||
@@ -2499,6 +2518,16 @@ dependencies = [
|
|||||||
"syn 3.0.5",
|
"syn 3.0.5",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "service-datatime"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"chrono",
|
||||||
|
"common",
|
||||||
|
"iced",
|
||||||
|
"tokio",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "shlex"
|
name = "shlex"
|
||||||
version = "2.0.1"
|
version = "2.0.1"
|
||||||
|
|||||||
+2
-1
@@ -1,5 +1,6 @@
|
|||||||
[workspace]
|
[workspace]
|
||||||
members = ["crates/*"]
|
members = ["crates/core", "crates/modules/clock", "crates/services/datatime", "crates/common"]
|
||||||
|
resolver = "2"
|
||||||
|
|
||||||
[workspace.package]
|
[workspace.package]
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
|
|||||||
@@ -0,0 +1,11 @@
|
|||||||
|
[package]
|
||||||
|
name = "common"
|
||||||
|
version.workspace = true
|
||||||
|
edition.workspace = true
|
||||||
|
|
||||||
|
[lib]
|
||||||
|
name = "common"
|
||||||
|
path = "src/lib.rs"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
iced = { workspace = true }
|
||||||
@@ -0,0 +1,127 @@
|
|||||||
|
//! Barbar's protocol crate: the types that cross crate boundaries.
|
||||||
|
//!
|
||||||
|
//! Nothing here may reference app-level types (`Message`, `Cmd`, `Msg`,
|
||||||
|
//! layer-shell effects). `common` is the hub every other crate depends on,
|
||||||
|
//! so it must stay acyclic and pure.
|
||||||
|
|
||||||
|
use std::any::Any;
|
||||||
|
use std::fmt;
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
use iced::window;
|
||||||
|
use iced::{Element, Task};
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// Service model
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
/// What a clock subscription wants: the tick interval and payload selector.
|
||||||
|
#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
|
||||||
|
pub enum ClockKind {
|
||||||
|
Mins,
|
||||||
|
Seconds,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Clock tick payload: the value plus the kind that produced it.
|
||||||
|
#[derive(Clone, PartialEq, Eq, Debug)]
|
||||||
|
pub struct ClockPayload {
|
||||||
|
pub kind: ClockKind,
|
||||||
|
pub value: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Route key a module subscribes with; `Clock(kind)` selects the ticker.
|
||||||
|
#[derive(Clone, Copy, PartialEq, Eq)]
|
||||||
|
pub enum Service {
|
||||||
|
Clock(ClockKind),
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A service event with a typed payload. The app only reads
|
||||||
|
/// `key()` for fan-out; the consuming module downcasts the payload.
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub enum ServicePayloadKind {
|
||||||
|
Clock(ClockPayload),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ServicePayloadKind {
|
||||||
|
/// Route key for this event. One arm per kind — adding a service
|
||||||
|
/// touches only this match, never the routing code.
|
||||||
|
pub fn key(&self) -> Service {
|
||||||
|
match self {
|
||||||
|
Self::Clock(payload) => Service::Clock(payload.kind),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Erases the typed payload for the module to downcast.
|
||||||
|
pub fn into_payload(self) -> Arc<dyn Any + Send + Sync> {
|
||||||
|
match self {
|
||||||
|
Self::Clock(payload) => Arc::new(payload),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// Module protocol
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
/// A module-local message, boxed with its owner's `id`. The app routes by
|
||||||
|
/// `id` and never names a module's message type; the module downcasts.
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct ModuleMsg {
|
||||||
|
pub id: &'static str,
|
||||||
|
pub payload: Arc<dyn Any + Send + Sync>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ModuleMsg {
|
||||||
|
/// Wraps a module's own message; `id` must match its `BarModule::id`.
|
||||||
|
pub fn new<T: Any + Send + Sync>(id: &'static str, msg: T) -> Self {
|
||||||
|
Self {
|
||||||
|
id,
|
||||||
|
payload: Arc::new(msg),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Downcasts to the module's own message type.
|
||||||
|
pub fn downcast<T: Any + Send + Sync>(&self) -> Option<&T> {
|
||||||
|
self.payload.downcast_ref()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Debug for ModuleMsg {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
f.debug_struct("ModuleMsg")
|
||||||
|
.field("id", &self.id)
|
||||||
|
.field("payload", &self.payload.type_id())
|
||||||
|
.finish()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// What a module asks the app to do. Modules never name app types; the app
|
||||||
|
/// maps these into its own effects (`Cmd`).
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub enum ModuleEffect {
|
||||||
|
/// Toggle a popup for the given module id (element id anchors it).
|
||||||
|
RequestPopup(String, String),
|
||||||
|
/// Request removal of the popup surface.
|
||||||
|
ClosePopup(window::Id),
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A bar module. Object-safe; modules live in a `BTreeMap` keyed by id. The
|
||||||
|
/// `ModuleMsg` boundary keeps the app ignorant of each module's message enum.
|
||||||
|
pub trait BarModule: Send {
|
||||||
|
fn id(&self) -> &'static str;
|
||||||
|
|
||||||
|
/// Bar contents. When `window_id` is `Some`, this is the module's popup
|
||||||
|
/// surface and the module returns its popup contents instead.
|
||||||
|
fn view(&self, window_id: Option<window::Id>) -> Element<'_, ModuleMsg>;
|
||||||
|
|
||||||
|
/// Handles a routed message; may return app-level tasks (e.g. popups).
|
||||||
|
fn update(&mut self, msg: ModuleMsg) -> Task<ModuleEffect>;
|
||||||
|
|
||||||
|
/// What services this module wants.
|
||||||
|
fn services(&self) -> Vec<Service>;
|
||||||
|
|
||||||
|
/// Requested popup size when it's not the generic small menu.
|
||||||
|
fn popup_size(&self) -> Option<(u32, u32)> {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,13 +1,18 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "barbar-core"
|
name = "core"
|
||||||
version.workspace = true
|
version.workspace = true
|
||||||
edition.workspace = true
|
edition.workspace = true
|
||||||
|
|
||||||
[[bin]]
|
[[bin]]
|
||||||
name = "barbar-core"
|
name = "core"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
common = { path = "../common" }
|
||||||
iced = { workspace = true }
|
iced = { workspace = true }
|
||||||
iced_layershell = { workspace = true }
|
iced_layershell = { workspace = true }
|
||||||
tokio = { workspace = true }
|
tokio = { workspace = true }
|
||||||
chrono = { workspace = true }
|
chrono = { workspace = true }
|
||||||
|
# Modules
|
||||||
|
module_clock = { package = "module-clock", path = "../modules/clock" }
|
||||||
|
# Services
|
||||||
|
service_datatime = { package = "service-datatime", path = "../services/datatime" }
|
||||||
|
|||||||
+28
-10
@@ -13,13 +13,9 @@ use iced_layershell::reexport::Anchor;
|
|||||||
use iced_layershell::settings::{LayerShellSettings, Settings, StartMode};
|
use iced_layershell::settings::{LayerShellSettings, Settings, StartMode};
|
||||||
use iced_layershell::to_layer_message;
|
use iced_layershell::to_layer_message;
|
||||||
|
|
||||||
use crate::module::{BarModule, ModuleMsg};
|
use common::{BarModule, ModuleEffect, ModuleMsg, Service as ModuleService, ServicePayloadKind};
|
||||||
use crate::services::Service as ModuleService;
|
|
||||||
use crate::services::ServicePayloadKind;
|
|
||||||
|
|
||||||
mod module;
|
|
||||||
mod popup;
|
mod popup;
|
||||||
mod services;
|
|
||||||
|
|
||||||
/// Height of the bar in logical pixels.
|
/// Height of the bar in logical pixels.
|
||||||
const BAR_HEIGHT: u32 = 36;
|
const BAR_HEIGHT: u32 = 36;
|
||||||
@@ -43,7 +39,7 @@ struct Bar {
|
|||||||
impl Bar {
|
impl Bar {
|
||||||
fn new() -> Self {
|
fn new() -> Self {
|
||||||
let mut modules: BTreeMap<&'static str, Box<dyn BarModule>> = BTreeMap::new();
|
let mut modules: BTreeMap<&'static str, Box<dyn BarModule>> = BTreeMap::new();
|
||||||
modules.insert("clock", Box::new(crate::module::clock::Clock::new()));
|
modules.insert("clock", Box::new(module_clock::Clock::new()));
|
||||||
let routes = modules
|
let routes = modules
|
||||||
.iter()
|
.iter()
|
||||||
.map(|(id, module)| (*id, module.services()))
|
.map(|(id, module)| (*id, module.services()))
|
||||||
@@ -100,6 +96,26 @@ fn namespace() -> String {
|
|||||||
String::from("barbar")
|
String::from("barbar")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Maps a module-requested effect into an app message. Modules never name
|
||||||
|
/// app types; this is the only place the two meet.
|
||||||
|
fn module_effect_to_message(effect: ModuleEffect) -> Message {
|
||||||
|
match effect {
|
||||||
|
ModuleEffect::RequestPopup(module_id, element_id) => {
|
||||||
|
Message::Effect(Cmd::RequestPopup(module_id, element_id))
|
||||||
|
}
|
||||||
|
ModuleEffect::ClosePopup(id) => Message::Effect(Cmd::ClosePopup(id)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// One arm per service kind: turns a route key into its subscription.
|
||||||
|
/// Adding a service touches only this match (plus the leaf crate).
|
||||||
|
fn service_subscription(service: &ModuleService) -> Subscription<Message> {
|
||||||
|
match service {
|
||||||
|
ModuleService::Clock(_) => service_datatime::ClockTicker::run()
|
||||||
|
.map(|event| Message::Event(Msg::Subscription(event))),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn main() -> Result<(), iced_layershell::Error> {
|
fn main() -> Result<(), iced_layershell::Error> {
|
||||||
let start_mode = match std::env::args().nth(1) {
|
let start_mode = match std::env::args().nth(1) {
|
||||||
Some(output) => StartMode::TargetScreen(output),
|
Some(output) => StartMode::TargetScreen(output),
|
||||||
@@ -127,7 +143,7 @@ fn gather_subscriptions(bar: &Bar) -> Subscription<Message> {
|
|||||||
let route_sub = Subscription::batch(
|
let route_sub = Subscription::batch(
|
||||||
bar.routes
|
bar.routes
|
||||||
.values()
|
.values()
|
||||||
.flat_map(|x| x.iter().map(ModuleService::subscription))
|
.flat_map(|x| x.iter().map(service_subscription))
|
||||||
.collect::<Vec<_>>(),
|
.collect::<Vec<_>>(),
|
||||||
);
|
);
|
||||||
let close_events = window::close_events().map(|id| Message::Event(Msg::WindowClosed(id)));
|
let close_events = window::close_events().map(|id| Message::Event(Msg::WindowClosed(id)));
|
||||||
@@ -151,8 +167,8 @@ fn update(bar: &mut Bar, msg: Message) -> Task<Message> {
|
|||||||
fn handle_event(bar: &mut Bar, event: Msg) -> Task<Message> {
|
fn handle_event(bar: &mut Bar, event: Msg) -> Task<Message> {
|
||||||
match event {
|
match event {
|
||||||
Msg::Module(m) => match bar.modules.get_mut(m.id) {
|
Msg::Module(m) => match bar.modules.get_mut(m.id) {
|
||||||
// Modules return their own app-level tasks (e.g. popups).
|
// Modules return protocol tasks; map effects into `Cmd`s.
|
||||||
Some(module) => module.update(m),
|
Some(module) => module.update(m).map(module_effect_to_message),
|
||||||
None => Task::none(),
|
None => Task::none(),
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -169,10 +185,12 @@ fn handle_event(bar: &mut Bar, event: Msg) -> Task<Message> {
|
|||||||
.is_some_and(|kinds| kinds.contains(&key))
|
.is_some_and(|kinds| kinds.contains(&key))
|
||||||
})
|
})
|
||||||
.map(|(_, module)| {
|
.map(|(_, module)| {
|
||||||
module.update(ModuleMsg {
|
module
|
||||||
|
.update(ModuleMsg {
|
||||||
id: module.id(),
|
id: module.id(),
|
||||||
payload: payload.clone(),
|
payload: payload.clone(),
|
||||||
})
|
})
|
||||||
|
.map(module_effect_to_message)
|
||||||
})
|
})
|
||||||
.fold(Task::none(), |acc, t| acc.chain(t))
|
.fold(Task::none(), |acc, t| acc.chain(t))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,63 +0,0 @@
|
|||||||
use std::any::Any;
|
|
||||||
use std::fmt;
|
|
||||||
use std::sync::Arc;
|
|
||||||
|
|
||||||
use iced::{Element, Task};
|
|
||||||
|
|
||||||
use crate::services::Service;
|
|
||||||
use crate::Message;
|
|
||||||
|
|
||||||
pub mod clock;
|
|
||||||
|
|
||||||
/// A module-local message, boxed with its owner's `id`. The app routes by
|
|
||||||
/// `id` and never names a module's message type; the module downcasts.
|
|
||||||
#[derive(Clone)]
|
|
||||||
pub struct ModuleMsg {
|
|
||||||
pub id: &'static str,
|
|
||||||
pub payload: Arc<dyn Any + Send + Sync>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ModuleMsg {
|
|
||||||
/// Wraps a module's own message; `id` must match its `BarModule::id`.
|
|
||||||
pub fn new<T: Any + Send + Sync>(id: &'static str, msg: T) -> Self {
|
|
||||||
Self {
|
|
||||||
id,
|
|
||||||
payload: Arc::new(msg),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Downcasts to the module's own message type.
|
|
||||||
pub fn downcast<T: Any + Send + Sync>(&self) -> Option<&T> {
|
|
||||||
self.payload.downcast_ref()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl fmt::Debug for ModuleMsg {
|
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
||||||
f.debug_struct("ModuleMsg")
|
|
||||||
.field("id", &self.id)
|
|
||||||
.field("payload", &self.payload.type_id())
|
|
||||||
.finish()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A bar module. Object-safe; modules live in a `BTreeMap` keyed by id. The
|
|
||||||
/// `ModuleMsg` boundary keeps the app ignorant of each module's message enum.
|
|
||||||
pub trait BarModule: Send {
|
|
||||||
fn id(&self) -> &'static str;
|
|
||||||
|
|
||||||
/// Bar contents. When `window_id` is `Some`, this is the module's popup
|
|
||||||
/// surface and the module returns its popup contents instead.
|
|
||||||
fn view(&self, window_id: Option<iced::window::Id>) -> Element<'_, ModuleMsg>;
|
|
||||||
|
|
||||||
/// Handles a routed message; may return app-level tasks (e.g. popups).
|
|
||||||
fn update(&mut self, msg: ModuleMsg) -> Task<Message>;
|
|
||||||
|
|
||||||
/// What services this module wants.
|
|
||||||
fn services(&self) -> Vec<Service>;
|
|
||||||
|
|
||||||
/// Requested popup size when it's not the generic small menu.
|
|
||||||
fn popup_size(&self) -> Option<(u32, u32)> {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,47 +0,0 @@
|
|||||||
use std::any::Any;
|
|
||||||
use std::sync::Arc;
|
|
||||||
|
|
||||||
use crate::{
|
|
||||||
services::clock::{ClockKind, ClockPayload, ClockTicker},
|
|
||||||
Message,
|
|
||||||
};
|
|
||||||
|
|
||||||
pub mod clock;
|
|
||||||
|
|
||||||
/// Route key a module subscribes with; `Clock(kind)` selects the ticker.
|
|
||||||
#[derive(Clone, Copy, PartialEq, Eq)]
|
|
||||||
pub enum Service {
|
|
||||||
Clock(ClockKind),
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Service {
|
|
||||||
pub fn subscription(&self) -> iced::Subscription<Message> {
|
|
||||||
match self {
|
|
||||||
Self::Clock(_) => ClockTicker::run(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A service event with a typed payload. The app only reads
|
|
||||||
/// `key()` for fan-out; the consuming module downcasts the payload.
|
|
||||||
#[derive(Clone, Debug)]
|
|
||||||
pub enum ServicePayloadKind {
|
|
||||||
Clock(ClockPayload),
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ServicePayloadKind {
|
|
||||||
/// Route key for this event. One arm per kind — adding a service
|
|
||||||
/// touches only this match, never the routing code.
|
|
||||||
pub fn key(&self) -> Service {
|
|
||||||
match self {
|
|
||||||
Self::Clock(payload) => Service::Clock(payload.kind),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Erases the typed payload for the module to downcast.
|
|
||||||
pub fn into_payload(self) -> Arc<dyn Any + Send + Sync> {
|
|
||||||
match self {
|
|
||||||
Self::Clock(payload) => Arc::new(payload),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
[package]
|
||||||
|
name = "module-clock"
|
||||||
|
version.workspace = true
|
||||||
|
edition.workspace = true
|
||||||
|
|
||||||
|
[lib]
|
||||||
|
name = "module_clock"
|
||||||
|
path = "src/lib.rs"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
common = { path = "../../common" }
|
||||||
|
service_datatime = { package = "service-datatime", path = "../../services/datatime" }
|
||||||
|
iced = { workspace = true }
|
||||||
@@ -1,14 +1,17 @@
|
|||||||
|
//! 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.
|
||||||
|
|
||||||
use iced::widget::{container, mouse_area, text};
|
use iced::widget::{container, mouse_area, text};
|
||||||
use iced::{Element, Task};
|
use iced::{Element, Task};
|
||||||
|
|
||||||
use crate::module::{BarModule, ModuleMsg};
|
use common::{BarModule, ClockKind, ClockPayload, ModuleEffect, ModuleMsg, Service};
|
||||||
use crate::services::clock::{ClockKind, ClockPayload};
|
|
||||||
use crate::services::Service;
|
|
||||||
use crate::{Cmd, Message};
|
|
||||||
|
|
||||||
/// Clock module: subscribes to second ticks, renders the time in the bar.
|
/// Big-text popup size (logical px).
|
||||||
/// Left-click toggles the seconds suffix; right-click opens a popup with
|
const POPUP_W: u32 = 360;
|
||||||
/// the current time in large text.
|
const POPUP_H: u32 = 160;
|
||||||
|
|
||||||
|
/// A module-local message for the clock.
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub enum ClockMsg {
|
pub enum ClockMsg {
|
||||||
/// Toggle the seconds suffix.
|
/// Toggle the seconds suffix.
|
||||||
@@ -17,10 +20,6 @@ pub enum ClockMsg {
|
|||||||
OpenPopup,
|
OpenPopup,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Big-text popup size (logical px).
|
|
||||||
const POPUP_W: u32 = 360;
|
|
||||||
const POPUP_H: u32 = 160;
|
|
||||||
|
|
||||||
pub struct Clock {
|
pub struct Clock {
|
||||||
value: String,
|
value: String,
|
||||||
show_seconds: bool,
|
show_seconds: bool,
|
||||||
@@ -48,6 +47,12 @@ impl Clock {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Default for Clock {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self::new()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl BarModule for Clock {
|
impl BarModule for Clock {
|
||||||
fn id(&self) -> &'static str {
|
fn id(&self) -> &'static str {
|
||||||
"clock"
|
"clock"
|
||||||
@@ -69,7 +74,7 @@ impl BarModule for Clock {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn update(&mut self, msg: ModuleMsg) -> Task<Message> {
|
fn update(&mut self, msg: ModuleMsg) -> Task<ModuleEffect> {
|
||||||
// Subscription fan-out delivers a raw `ClockPayload`, not `ClockMsg`.
|
// Subscription fan-out delivers a raw `ClockPayload`, not `ClockMsg`.
|
||||||
if let Some(payload) = msg.downcast::<ClockPayload>() {
|
if let Some(payload) = msg.downcast::<ClockPayload>() {
|
||||||
self.value = payload.value.clone();
|
self.value = payload.value.clone();
|
||||||
@@ -80,10 +85,10 @@ impl BarModule for Clock {
|
|||||||
self.show_seconds = !self.show_seconds;
|
self.show_seconds = !self.show_seconds;
|
||||||
Task::none()
|
Task::none()
|
||||||
}
|
}
|
||||||
Some(ClockMsg::OpenPopup) => Task::done(Message::Effect(Cmd::RequestPopup(
|
Some(ClockMsg::OpenPopup) => Task::done(ModuleEffect::RequestPopup(
|
||||||
self.id().to_string(),
|
self.id().to_string(),
|
||||||
self.id().to_string(),
|
self.id().to_string(),
|
||||||
))),
|
)),
|
||||||
None => Task::none(),
|
None => Task::none(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
[package]
|
||||||
|
name = "service-datatime"
|
||||||
|
version.workspace = true
|
||||||
|
edition.workspace = true
|
||||||
|
|
||||||
|
[lib]
|
||||||
|
name = "service_datatime"
|
||||||
|
path = "src/lib.rs"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
common = { path = "../../common" }
|
||||||
|
iced = { workspace = true }
|
||||||
|
tokio = { workspace = true }
|
||||||
|
chrono = { workspace = true }
|
||||||
@@ -1,22 +1,14 @@
|
|||||||
|
//! Clock service: emits a `ServicePayloadKind` event whenever a clock kind's
|
||||||
|
//! displayed value changes (minute or second rollover).
|
||||||
|
//!
|
||||||
|
//! The ticker knows nothing about the app's `Message` type; it produces
|
||||||
|
//! protocol events from `common` that core routes to subscribed modules.
|
||||||
|
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
|
||||||
use iced::{futures::SinkExt, Subscription};
|
use iced::{futures::SinkExt, Subscription};
|
||||||
|
|
||||||
use crate::{services::ServicePayloadKind, Message, Msg};
|
use common::{ClockKind, ClockPayload, ServicePayloadKind};
|
||||||
|
|
||||||
/// What a clock subscription wants: the tick interval and payload selector.
|
|
||||||
#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
|
|
||||||
pub enum ClockKind {
|
|
||||||
Mins,
|
|
||||||
Seconds,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Clock tick payload: the value plus the kind that produced it.
|
|
||||||
#[derive(Clone, PartialEq, Eq, Debug)]
|
|
||||||
pub struct ClockPayload {
|
|
||||||
pub kind: ClockKind,
|
|
||||||
pub value: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Emits a payload only when a kind's displayed value changes (minute or
|
/// Emits a payload only when a kind's displayed value changes (minute or
|
||||||
/// second rollover). Drive `tick` once per second.
|
/// second rollover). Drive `tick` once per second.
|
||||||
@@ -30,20 +22,19 @@ impl ClockTicker {
|
|||||||
Self::default()
|
Self::default()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn run() -> Subscription<Message> {
|
/// Subscription that emits `ServicePayloadKind::Clock` on every change.
|
||||||
|
pub fn run() -> Subscription<ServicePayloadKind> {
|
||||||
Subscription::run_with((), |_| {
|
Subscription::run_with((), |_| {
|
||||||
iced::stream::channel(
|
iced::stream::channel(
|
||||||
0,
|
0,
|
||||||
|mut sender: iced::futures::channel::mpsc::Sender<Message>| async move {
|
|mut sender: iced::futures::channel::mpsc::Sender<ServicePayloadKind>| async move {
|
||||||
let mut clock = ClockTicker::new();
|
let mut clock = ClockTicker::new();
|
||||||
let mut interval = tokio::time::interval(std::time::Duration::from_secs(1));
|
let mut interval = tokio::time::interval(std::time::Duration::from_secs(1));
|
||||||
loop {
|
loop {
|
||||||
interval.tick().await;
|
interval.tick().await;
|
||||||
for payload in clock.tick() {
|
for payload in clock.tick() {
|
||||||
if sender
|
if sender
|
||||||
.send(Message::Event(Msg::Subscription(
|
.send(ServicePayloadKind::Clock(payload))
|
||||||
ServicePayloadKind::Clock(payload),
|
|
||||||
)))
|
|
||||||
.await
|
.await
|
||||||
.is_err()
|
.is_err()
|
||||||
{
|
{
|
||||||
Reference in New Issue
Block a user