iced_multi_window/
lib.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
//! # `iced-multi-window`
//!
//! Utilities for managing many windows in an `iced` application.
//!
//! ## Goals
//!
//! Working with multiple windows in iced can become quite painful quite quickly. If you want to introduce a window type with unique behavior, you may have to make additions in more than five places accross your codebase. Oversights are easy, and most of the mistakes you can make aren't caught by the compiler. This library seeks to ease this experince by making defining and working with multiple windows simpler, more intuitive, and harder to screw up.
//!
//! ## Usage
//!
//! The first step is to define the windows that will appear in your app. This is done by creating a corresponding struct and implementing the `Window` trait for it. This trait will describe the logic behind that window's content, title, and theme, as well as defining its spawn-time settings.
//!
//! Next, add a `WindowManager` to your app's state. It keeps track of all of the `Id`s and corresponding `Window`s that are currently open. It also provides `view`, `theme`, and `title` methods that return the proper output for the specified `Id`.
//!
//! You have to manually inform the `WindowManager` when a window is closed. This can be done by subscribing to `iced::window::close_events()` and passing the `Id` of each closed window to `WindowManager::was_closed()`.

use dyn_clone::DynClone;
use iced::{
    window::{self, Id},
    Element, Task,
};
use std::{any::type_name, collections::HashMap};

#[allow(private_bounds)]
pub trait Window<App, Theme, Message, Renderer = iced::Renderer>:
    Send + std::fmt::Debug + DynClone
{
    fn view<'a>(&'a self, app: &'a App) -> iced::Element<'a, Message, Theme, Renderer>;
    fn title(&self, app: &App) -> String;
    fn theme(&self, app: &App) -> Theme;
    fn settings(&self) -> window::Settings;
    /// The unique identifier for this window. This includes any internal data.
    fn id(&self) -> String {
        let data = format!("{:?}", self);
        let data = if let Some(i) = data.find(" {") {
            data[i..].to_string()
        } else {
            format!("::{}", data)
        };

        format!("{}{}", type_name::<Self>(), data)
    }
    /// An identifier for this window's "class". Whereas `id` is used to identify individual windows, `class` is used to identify a window's type.
    fn class(&self) -> &'static str {
        type_name::<Self>()
    }
}

dyn_clone::clone_trait_object!(<App, Theme, Message, Renderer> Window<App, Theme, Message, Renderer>);

impl<App, Theme, Message, Renderer, T: Window<App, Theme, Message, Renderer>> PartialEq<T>
    for Box<dyn Window<App, Theme, Message, Renderer>>
{
    fn eq(&self, other: &T) -> bool {
        self.id() == other.id()
    }
}

impl<App, Theme, Message, Renderer> Window<App, Theme, Message, Renderer>
    for Box<dyn Window<App, Theme, Message, Renderer>>
{
    fn view<'a>(&'a self, app: &'a App) -> iced::Element<'a, Message, Theme, Renderer> {
        self.as_ref().view(app)
    }

    fn title(&self, app: &App) -> String {
        self.as_ref().title(app)
    }

    fn theme(&self, app: &App) -> Theme {
        self.as_ref().theme(app)
    }

    fn settings(&self) -> window::Settings {
        self.as_ref().settings()
    }

    fn id(&self) -> String {
        self.as_ref().id()
    }

    fn class(&self) -> &'static str {
        self.as_ref().class()
    }
}

pub struct WindowManager<App, Theme, Message, Renderer = iced::Renderer> {
    windows: HashMap<Id, Box<dyn Window<App, Theme, Message, Renderer>>>,
}

impl<App, Theme, Message, Renderer> WindowManager<App, Theme, Message, Renderer> {
    /// Returns the window associated with the given Id, panicking if it doesn't exist.
    fn get(&self, id: Id) -> &dyn Window<App, Theme, Message, Renderer> {
        self.windows
            .get(&id)
            .expect("No window found with given Id")
            .as_ref()
    }

    pub fn view<'a>(&'a self, app: &'a App, id: Id) -> Element<'a, Message, Theme, Renderer> {
        self.get(id).view(app)
    }

    pub fn title(&self, app: &App, id: Id) -> String {
        self.get(id).title(app)
    }

    pub fn theme(&self, app: &App, id: Id) -> Theme {
        self.get(id).theme(app)
    }

    pub fn open(
        &mut self,
        window: Box<dyn Window<App, Theme, Message, Renderer>>,
    ) -> (Id, Task<Id>) {
        let (id, task) = window::open(window.settings());
        self.windows.insert(id, window);
        (id, task)
    }

    pub fn close_all(&mut self) -> Task<Id> {
        let mut tasks = Vec::new();
        for id in self.windows.keys() {
            tasks.push(window::close(*id));
        }
        Task::batch(tasks)
    }

    pub fn close_all_of(
        &mut self,
        window: Box<dyn Window<App, Theme, Message, Renderer>>,
    ) -> Task<Id> {
        let mut tasks = Vec::new();
        for (id, w) in self.windows.iter() {
            if *w == window {
                tasks.push(window::close(*id));
            }
        }

        Task::batch(tasks)
    }

    /// Checks for any open instances of the given window.
    pub fn any_of(&self, window: &impl Window<App, Theme, Message, Renderer>) -> bool {
        self.windows.values().any(|w| w == window)
    }

    /// Updates internal state to reflect that the given window Id  was closed.
    pub fn was_closed(&mut self, id: Id) {
        self.windows.remove(&id);
    }

    /// Returns all instances of the given window and their associated Ids.
    #[allow(clippy::type_complexity)]
    pub fn instances_of(
        &self,
        window: &impl Window<App, Theme, Message, Renderer>,
    ) -> Vec<(&Id, &Box<dyn Window<App, Theme, Message, Renderer>>)> {
        self.windows.iter().filter(|(_, w)| *w == window).collect()
    }

    pub fn empty(&self) -> bool {
        self.windows.is_empty()
    }
}

impl<App, Theme, Message, Renderer> Default for WindowManager<App, Theme, Message, Renderer> {
    fn default() -> Self {
        Self {
            windows: HashMap::new(),
        }
    }
}