How to send messages to iced application

1.2k views Asked by At

I have an iced application that I want to control through a web UI. For this I use axum and spawn it in a different tokio task. I would like to send a message from the axum endpoint to the iced application, but I can neither invoke the update method of the iced application nor can I listen to the crossbeam channel inside the iced application.

My code looks like this right now:

mod messages;
mod ui;
mod web;

use crossbeam_channel::unbounded;
use iced::Application;
use iced::Settings;
use tokio;

#[tokio::main]
async fn main() -> iced::Result {
    let (s, r) = unbounded::<messages::WebToUIMessage>();
    let server_handle = tokio::task::spawn(async { web::run_server(s).await });

    let result = ui::MediaHub::run(Settings::default());
    // We abort the server when the UI finishes
    server_handle.abort();
    result
}
1

There are 1 answers

0
Ben On BEST ANSWER

You can pass a channel receiver through a Flags struct when starting an iced app. Then in the subscription function use iced::subscription::unfold to convert the received channel messages into iced messages that the UI will respond to. Runnable code is as follows:

use iced::{widget::text, Application, Command, Element, Settings, Subscription};
use tokio::sync::mpsc;
use std::cell::RefCell;

fn main() -> iced::Result {
    let (sender, receiver) = mpsc::unbounded_channel::<i32>();

    std::thread::spawn(move || {
        for i in 0.. {
            sender.send(i).unwrap();
            std::thread::sleep(std::time::Duration::from_millis(200));
        }
    });

    Ui::run(Settings::with_flags(UiFlags { receiver }))
}

struct UiFlags {
    receiver: mpsc::UnboundedReceiver<i32>,
}

struct Ui {
    receiver: RefCell<Option<mpsc::UnboundedReceiver<i32>>>,
    num: i32,
}

#[derive(Debug, Clone)]
enum Message {
    ExternalMessageReceived(i32),
}

impl Application for Ui {
    type Executor = iced::executor::Default;
    type Message = Message;
    type Theme = iced::Theme;
    type Flags = UiFlags;

    fn new(flags: UiFlags) -> (Self, Command<Message>) {
        let app = Ui {
            receiver: RefCell::new(Some(flags.receiver)),
            num: 0,
        };
        (app, Command::none())
    }

    fn title(&self) -> String {
        String::from("External Message Example")
    }

    fn update(&mut self, message: Message) -> Command<Message> {
        match message {
            Message::ExternalMessageReceived(num) => {
                self.num = num;
            }
        }
        Command::none()
    }

    fn subscription(&self) -> Subscription<Message> {
        iced::subscription::unfold(
            "led changes",
            self.receiver.take(),
            move |mut receiver| async move {
                let num = receiver.as_mut().unwrap().recv().await.unwrap();
                (Some(Message::ExternalMessageReceived(num)), receiver)
            },
        )
    }

    fn view(&self) -> Element<Message> {
        text(self.num).into()
    }
}