Creating an application with multiple windows

Hello all, I’m trying to get my application to create a second “popup” window for entering configurations into my program. I do not want a modal style, but a separate, new window to be created that can be dragged around on the desktop environment like any other. Are there any resources I could look at on getting this to work in stable iced? I’m only looking for the bare minimum, pressing a button that launches a new window that I can draw to like a standard single-windowed iced program. If I can get to the point of creating the second window, I’m confident I can figure out the rest.

Using iced 13.1, the multi_window example does not compile for a number of reasons. Stripping down the program, I’m not sure how to fix the errors surrounding the creation of the daemon:

iced::daemon(Example::new, Example::update, Example::view)
        .subscription(Example::subscription)
        .run()

which gives:

error[E0593]: function is expected to take 2 arguments, but it takes 0 arguments
  --> src/main.rs:14:5
   |
14 |     iced::daemon(Example::new, Example::update, Example::view)
   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ expected function that takes 2 arguments
...
42 |     fn new() -> (Self, Task<Message>) {
   |     --------------------------------- takes 0 arguments
   |
   = note: required for `fn() -> (Example, Task<Message>) {Example::new}` to implement `iced::daemon::Title<Example>`
note: required by a bound in `daemon`
  --> /home/vlm/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/iced-0.13.1/src/daemon.rs:22:17
   |
21 | pub fn daemon<State, Message, Theme, Renderer>(
   |        ------ required by a bound in this function
22 |     title: impl Title<State>,
   |                 ^^^^^^^^^^^^ required by this bound in `daemon`

I’m not sure what the two arguments are supposed to be, and I’m not exactly sure what is going on in the logic of the rest of the example that presumably creates the windows.

I think I need to something like:

let (id, task) = window::open(window::Settings::default());

in the update logic of the Message tied to the window creation button, but I’m not sure what to do with the id, and the task is of type Task<Id>, and my update() returns a Task<Message>.

Again, I’m only looking to create a singular second window as a result of a button press. Any resources or help would be appreciated, thanks!

Alright, after a bit more digging, I got it figured out. Based on this post and the example given, here is a bare minimum example as I described as wanting in the above post:

use iced::widget::{button, column, text};
use iced::{Element, Size, Subscription, Task, window};
use std::collections::BTreeMap;

#[derive(Debug, Clone, PartialEq)]
pub enum WindowType {
    MainWindow,
    SecondWindow,
}

impl WindowType {
    pub fn settings(&self) -> window::Settings {
        match self {
            WindowType::MainWindow => window::Settings {
                size: Size::new(400.0, 400.0),
                ..window::Settings::default()
            },
            WindowType::SecondWindow => window::Settings {
                size: Size::new(200.0, 200.0),
                ..window::Settings::default()
            },
        }
    }
}

#[derive(Debug, Clone)]
pub enum MainWindowMessage {
    CreateSecondWindow,
}

#[derive(Debug, Clone)]
pub enum SecondWindowMessage {
    Hello,
}

#[derive(Debug, Clone)]
pub enum Message {
    WindowOpened(window::Id, WindowType),
    WindowClosed(window::Id),
    MainWindow(MainWindowMessage),
    SecondWindow(SecondWindowMessage),
}

struct App {
    windows: BTreeMap<window::Id, WindowType>,
}

impl App {
    fn new() -> (Self, Task<Message>) {
        let window_type = WindowType::MainWindow;

        let (_new_id, task) = window::open(window_type.settings());

        (
            Self {
                windows: BTreeMap::new(),
            },
            task.map(move |id| Message::WindowOpened(id, window_type.clone())),
        )
    }

    fn title(&self, id: window::Id) -> String {
        if let Some(window_type) = self.windows.get(&id) {
            match window_type {
                WindowType::MainWindow => "main window".to_string(),
                WindowType::SecondWindow => "second window".to_string(),
            }
        } else {
            "invalid window".to_string()
        }
    }

    fn update(&mut self, message: Message) -> Task<Message> {
        match message {
            Message::WindowOpened(new_window_id, new_window_type) => {
                self.windows.insert(new_window_id, new_window_type);
            }
            Message::WindowClosed(id) => {
                self.windows.remove(&id);

                if self.windows.is_empty() {
                    return iced::exit();
                }
            }
            Message::MainWindow(main_window_message) => match main_window_message {
                MainWindowMessage::CreateSecondWindow => {
                    let new_window_type = WindowType::SecondWindow;

                    // if the window type already exists, do not create a new window and focus the existing one
                    for (window_id, window_type) in &self.windows {
                        if new_window_type == *window_type {
                            return window::gain_focus(*window_id);
                        }
                    }

                    let (_new_id, task) = window::open(new_window_type.settings());

                    return task.map(move |id| Message::WindowOpened(id, new_window_type.clone()));
                }
            },
            Message::SecondWindow(second_window_message) => match second_window_message {
                SecondWindowMessage::Hello => {
                    println!("hello!");
                }
            },
        }

        Task::none()
    }

    fn view(&'_ self, id: window::Id) -> Element<'_, Message> {
        if let Some(window_type) = self.windows.get(&id) {
            match window_type {
                WindowType::MainWindow => column![
                    button("launch second window")
                        .on_press(Message::MainWindow(MainWindowMessage::CreateSecondWindow))
                ]
                .into(),
                WindowType::SecondWindow => column![
                    text("the second window"),
                    button("hello").on_press(Message::SecondWindow(SecondWindowMessage::Hello))
                ]
                .into(),
            }
        } else {
            column![].into()
        }
    }

    fn subscription(&self) -> Subscription<Message> {
        window::close_events().map(Message::WindowClosed)
    }
}

pub fn main() -> iced::Result {
    iced::daemon(App::title, App::update, App::view)
        .subscription(App::subscription)
        .run_with(App::new)
}

1 Like