Anatomy of a Module
Modules follow a consistent pattern, though they don’t implement a formal trait. Instead, they follow a convention that the App struct and modules/mod.rs rely on.
The Module Pattern
Every module has:
1. A Message Enum
#![allow(unused)]
fn main() {
#[derive(Debug, Clone)]
pub enum Message {
// Module-specific events
}
}
2. A Struct
#![allow(unused)]
fn main() {
pub struct MyModule {
config: MyModuleConfig,
// Module state
}
}
3. Constructor
#![allow(unused)]
fn main() {
impl MyModule {
pub fn new(config: MyModuleConfig) -> Self {
Self { config, /* ... */ }
}
}
}
4. Update Method
#![allow(unused)]
fn main() {
pub fn update(&mut self, message: Message) -> /* Action or Task or () */ {
match message {
// Handle each message variant
}
}
}
5. View Method
#![allow(unused)]
fn main() {
pub fn view(&self) -> Element<'_, Message> {
// Return iced elements (theme values are read with `use_theme`)
}
}
6. Subscription Method
#![allow(unused)]
fn main() {
pub fn subscription(&self) -> Subscription<Message> {
// Return event sources (timers, service events, etc.)
}
}
Optional: Menu View
Modules with popup menus also implement:
#![allow(unused)]
fn main() {
pub fn menu_view(&self) -> Element<'_, Message> {
// Return the menu popup content
}
}
The Action Pattern
Some modules return an Action enum from update() instead of a plain Task. This allows modules to request operations they can’t perform themselves. Each module defines its own Action; for example, the Settings module’s:
#![allow(unused)]
fn main() {
pub enum Action {
None,
Command(Task<Message>),
CloseMenu(SurfaceId),
RequestKeyboardWithCommand(SurfaceId, Task<Message>),
ReleaseKeyboard(SurfaceId),
ReleaseKeyboardWithCommand(SurfaceId, Task<Message>),
OpenTooltipMenu(SurfaceId, MenuType, ButtonUIRef),
CloseTooltipMenu(SurfaceId, MenuType),
}
}
The App::update() method interprets these actions. For example, CloseMenu tells the App to close the menu surface, which the module can’t do directly.
Modules that use the Action pattern: Settings, Tray, Updates, MediaPlayer, Tempo, Notifications.
Service Consumption
Modules consume services through their subscription:
#![allow(unused)]
fn main() {
pub fn subscription(&self) -> Subscription<Message> {
CompositorService::subscribe().map(|event| Message::ServiceEvent(Box::new(event)))
}
}
The module’s Message enum includes variants for service events:
#![allow(unused)]
fn main() {
pub enum Message {
ServiceEvent(Box<ServiceEvent<CompositorService>>),
// ...
}
}
And the update() method handles them:
#![allow(unused)]
fn main() {
Message::ServiceEvent(event) => match *event {
ServiceEvent::Init(service) => {
self.service = Some(service);
}
ServiceEvent::Update(event) => {
if let Some(service) = &mut self.service {
service.update(event);
}
}
// ...
}
}
Integration with App
Each module is integrated into the App through several touchpoints:
- Field in
Appstruct (src/app.rs) - Variant in
Messageenum (src/app/message.rs) - Match arm in
App::update()(src/app.rs) - Entry in
get_module_view()(src/modules/mod.rs) - Entry in
get_module_subscription()(src/modules/mod.rs) ModuleNamevariant (src/config.rs)