Modules

Modules are used to encapsulate related private data into a central type; along with its own message type and functions. These could be individual pages of your application, sections of a page, or even a reusable widgets composed of smaller widgets.

Below is a hypothetical application which contains two modules: todo and config. Each containing their own respective Page and Message types.

struct App {
    active_page: PageId,
    todo_page: todo::Page,
    config_page: config::Page,
}

Starting with todo page module, which manages todo tasks.

mod todo {
    use cosmic::prelude::*;
    use cosmic::widget;

    pub async fn load() -> Message {
        // ..
    }

    #[derive(Debug, Clone)]
    pub enum Message {
        /// Add a new task
        Add,
        /// Edit an existing task
        EditInput(usize, String),
        /// Move the given task down
        MoveDown(usize),
        /// Move the given task up
        MoveUp(usize),
        /// Update the new task input editor
        NewInput(String),
        /// Remove an existing task
        Remove(usize)
        /// Save to disk
        Save
    }

    pub struct Page {
        new_task_input: String,
        tasks: Vec<String>,
    }

    impl Page {
        pub fn view(&self) -> cosmic::Element<Message> {
            // Where new tasks will be input before being added to the task list.
            let new_task_input = widget::text_input("Write down a new task here", &self.new_task_input)
                .on_input(Message::NewInput)
                .on_submit(Message::Add);

            // Fold each enumerated task into a widget that is pushed to a scrollable column.
            let saved_tasks = self.tasks.iter()
                .enumerate()
                .fold(widget::column(), |column, (id, task)| {
                    column.push(
                        // A hypothetical widget created for this app
                        crate::widget::task(task.as_str())
                            .on_remove(Message::Remove(id))
                            .on_input(|text| Message::EditInput(id, text))
                            .on_move_down(Message::MoveDown(id))
                            .on_move_up(Message::MoveUp(id))
                            .into()
                    )
                })
                .apply(widget::scrollable);

            // Compose the above widgets into the column view.
            widget::column::with_capacity(2)
                .spacing(cosmic::theme::active().cosmic().spacing.space_l)
                .push(new_task_input)
                .push(saved_tasks)
                .into()
        }

        pub fn update(&mut self, message: Message) -> cosmic::Task<cosmic::Action<Message>> {
            match message {
                Message::Add => {
                    self.tasks.insert(std::mem::take(&mut self.new_task_input));
                }

                Message::EditInput(id, task) => {
                    self.tasks[id] = task;
                }

                Message::MoveDown(id) => {
                    if id + 1 < self.tasks.len() {
                        self.tasks.swap(id, id + 1);
                    }
                }

                Message::MoveUp(id) => {
                    if id > 0 {
                        self.tasks.swap(id, id - 1);
                    }
                }

                Message::NewInput(input) => {
                    self.new_task_input = input;
                }

                Message::Remove(id) => {
                    self.tasks.remove(id);
                }

                Message::Save => {
                    // Hypothetical method to save the tasks to disk.
                    let save_future = self.save_to_disk();
                    return cosmic::task::future(save_future);
                }
            }

            cosmic::Task::none()
        }
    }
}

And now the config module:


mod config {
    #[derive(Debug, Clone)]
    pub enum Message {
        OpenUrl(url::Url)
    }

    pub struct Page {
        author_name: String,
        donate_url: url::Url,
        homepage_url: url::Url,
        repository_urlL: url::Url,
    }

    impl Page {
        pub fn view(&self) -> cosmic::Element<Message> {
            // Hypothetical config page
        }

        pub fn update(&mut self, message: Message) -> cosmic::Task<cosmic::Action<Message>> {
            match message {
                OpenUrl(url) => {
                    tokio::spawn(open_url(url));
                }
            }

            cosmic::Task::none()
        }
    }

    pub async fn open_url(url: url::Url) {
        // ...
    }
}

We can then use them in your application's own native view and update functions like so:

use std::
#[derive(Debug, Clone)]
enum Message {
    SetPage(PageId),
    ConfigPage(config::Message),
    TodoPage(todo::Message),
}

impl From<config::Message> for Message {
    fn from(message: config::Message) -> Self {
        Self::ConfigPage(config::Message)
    }
}

impl From<todo::Message> for Message {
    fn from(message: todo::Message) -> Self {
        Self::TodoPage(todo::Message)
    }
}

#[derive(Debug, Clone)]
enum PageId {
    Config
    Todo,
}

// ...

fn view(&self) -> cosmic::Element<Message> {
    match self.active_page {
        PageId::Todo => self.todo_page.view().map(Message::TodoPage),
        PageId::Config => self.config_page.view().map(Message::ConfigPage),
    }
}

fn update(&mut self, message: Message) -> cosmic::Task<cosmic::Action<Message>> {
    match message {
        Message::SetPage(id) => {
            self.active_page = id;

            match self.active_page {
                PageId::Config => (),
                PageId::Todo => return cosmic::task::future(async move {
                    Message::TodoPage(todo::load().await)
                }),
            }
        }

        Message::ConfigPage(message) => {
            self.config_page.update(message).map(Into::into)
        }

        Message::TodoPage(message) => {
            self.todo_page.update(message).map(Into::into)
        }
    }
}

We may even implement the Application::on_close_requested() method in our app to handle that Save message for our todo page.

fn on_close_requested(&mut self) -> Option<Message> {
    Some(Message::TodoPage(todo::Message::Save))
}