changes
This commit is contained in:
@@ -0,0 +1,110 @@
|
||||
# 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`:
|
||||
|
||||
```rust
|
||||
//! Greeter module messages.
|
||||
|
||||
#[derive(Clone)]
|
||||
pub enum GreeterMsg {
|
||||
Poke,
|
||||
}
|
||||
```
|
||||
|
||||
2. Re-export them from `crates/common/src/messages/modules/mod.rs`:
|
||||
|
||||
```rust
|
||||
mod clock;
|
||||
mod greeter;
|
||||
|
||||
pub use clock::ClockMsg;
|
||||
pub use greeter::GreeterMsg;
|
||||
```
|
||||
|
||||
3. Create `crates/modules/src/greeter.rs`:
|
||||
|
||||
```rust
|
||||
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(())
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
4. Register it in `crates/modules/src/lib.rs`'s `all()` and re-export it:
|
||||
|
||||
```rust
|
||||
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:
|
||||
|
||||
```rust
|
||||
// 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.
|
||||
@@ -2,12 +2,17 @@
|
||||
//! a new file under `src/` plus one entry in `all()` — core never changes.
|
||||
|
||||
mod clock;
|
||||
mod weather;
|
||||
|
||||
pub use clock::{Clock, ClockError};
|
||||
use common::BarModule;
|
||||
use toml::Table;
|
||||
pub use weather::*;
|
||||
|
||||
/// One instance of every enabled module, ready to insert by `id()`.
|
||||
pub fn all(module_config: Table) -> Vec<Box<dyn BarModule>> {
|
||||
vec![Box::new(clock::Clock::new(Some(module_config)))]
|
||||
vec![
|
||||
Box::new(clock::Clock::new(Some(module_config))),
|
||||
Box::new(weather::WeatherModule::default()),
|
||||
]
|
||||
}
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
use std::error::Error;
|
||||
|
||||
use common::{BarModule, Endpoint, ModuleEffect, Service, WeatherKind, Wire};
|
||||
use iced::{
|
||||
widget::{container, mouse_area, text},
|
||||
Element, Task,
|
||||
};
|
||||
|
||||
pub struct WeatherModule {
|
||||
text: String,
|
||||
}
|
||||
|
||||
impl Default for WeatherModule {
|
||||
fn default() -> Self {
|
||||
Self { text: "hi".into() }
|
||||
}
|
||||
}
|
||||
|
||||
impl BarModule for WeatherModule {
|
||||
fn id(&self) -> &'static str {
|
||||
"weather"
|
||||
}
|
||||
|
||||
fn view(&self, _window_id: Option<iced::window::Id>) -> Element<'_, Wire> {
|
||||
let me = Endpoint::module(self.id());
|
||||
container(mouse_area(text(&self.text).size(16)).on_press(Wire::module(
|
||||
me,
|
||||
self.id(),
|
||||
WeatherKind::Poke,
|
||||
)))
|
||||
.into()
|
||||
}
|
||||
|
||||
fn update(&mut self, msg: Wire) -> Task<ModuleEffect> {
|
||||
match msg.downcast::<WeatherKind>() {
|
||||
Some(WeatherKind::Poke) => {
|
||||
self.text = "poked".into();
|
||||
Task::none()
|
||||
}
|
||||
None => Task::none(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Route keys this module subscribes to.
|
||||
fn services(&self) -> Vec<Service> {
|
||||
vec![]
|
||||
}
|
||||
|
||||
/// Optional: read `module.weather` from the config table.
|
||||
fn config(&mut self, _config: toml::Table) -> Result<(), Box<dyn Error>> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user