iced_multi_window/
lib.rs

1//! # `iced-multi-window`
2//!
3//! Utilities for managing many windows in an `iced` application.
4//!
5//! ## Goals
6//!
7//! 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.
8//!
9//! ## Usage
10//!
11//! 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.
12//!
13//! 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`.
14//!
15//! 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()`.
16
17use dyn_clone::DynClone;
18use iced::{
19    window::{self, Id},
20    Element, Task,
21};
22use std::{any::type_name, collections::HashMap};
23
24#[allow(private_bounds)]
25pub trait Window<App, Theme, Message, Renderer = iced::Renderer>:
26    Send + std::fmt::Debug + DynClone
27{
28    fn view<'a>(&'a self, app: &'a App) -> iced::Element<'a, Message, Theme, Renderer>;
29    fn title(&self, app: &App) -> String;
30    fn theme(&self, app: &App) -> Theme;
31    fn settings(&self) -> window::Settings;
32    /// The unique identifier for this window. This includes any internal data.
33    fn id(&self) -> String {
34        let data = format!("{:?}", self);
35        let data = if let Some(i) = data.find(" {") {
36            data[i..].to_string()
37        } else {
38            format!("::{}", data)
39        };
40
41        format!("{}{}", type_name::<Self>(), data)
42    }
43    /// An identifier for this window's "class". Whereas `id` is used to identify individual windows, `class` is used to identify a window's type.
44    fn class(&self) -> &'static str {
45        type_name::<Self>()
46    }
47}
48
49dyn_clone::clone_trait_object!(<App, Theme, Message, Renderer> Window<App, Theme, Message, Renderer>);
50
51impl<App, Theme, Message, Renderer, T: Window<App, Theme, Message, Renderer>> PartialEq<T>
52    for Box<dyn Window<App, Theme, Message, Renderer>>
53{
54    fn eq(&self, other: &T) -> bool {
55        self.id() == other.id()
56    }
57}
58
59impl<App, Theme, Message, Renderer> Window<App, Theme, Message, Renderer>
60    for Box<dyn Window<App, Theme, Message, Renderer>>
61{
62    fn view<'a>(&'a self, app: &'a App) -> iced::Element<'a, Message, Theme, Renderer> {
63        self.as_ref().view(app)
64    }
65
66    fn title(&self, app: &App) -> String {
67        self.as_ref().title(app)
68    }
69
70    fn theme(&self, app: &App) -> Theme {
71        self.as_ref().theme(app)
72    }
73
74    fn settings(&self) -> window::Settings {
75        self.as_ref().settings()
76    }
77
78    fn id(&self) -> String {
79        self.as_ref().id()
80    }
81
82    fn class(&self) -> &'static str {
83        self.as_ref().class()
84    }
85}
86
87pub struct WindowManager<App, Theme, Message, Renderer = iced::Renderer> {
88    windows: HashMap<Id, Box<dyn Window<App, Theme, Message, Renderer>>>,
89}
90
91impl<App, Theme, Message, Renderer> WindowManager<App, Theme, Message, Renderer> {
92    /// Returns the window associated with the given Id, panicking if it doesn't exist.
93    fn get(&self, id: Id) -> &dyn Window<App, Theme, Message, Renderer> {
94        self.windows
95            .get(&id)
96            .expect("No window found with given Id")
97            .as_ref()
98    }
99
100    pub fn view<'a>(&'a self, app: &'a App, id: Id) -> Element<'a, Message, Theme, Renderer> {
101        self.get(id).view(app)
102    }
103
104    pub fn title(&self, app: &App, id: Id) -> String {
105        self.get(id).title(app)
106    }
107
108    pub fn theme(&self, app: &App, id: Id) -> Theme {
109        self.get(id).theme(app)
110    }
111
112    pub fn open(
113        &mut self,
114        window: Box<dyn Window<App, Theme, Message, Renderer>>,
115    ) -> (Id, Task<Id>) {
116        let (id, task) = window::open(window.settings());
117        self.windows.insert(id, window);
118        (id, task)
119    }
120
121    pub fn close_all(&mut self) -> Task<Id> {
122        let mut tasks = Vec::new();
123        for id in self.windows.keys() {
124            tasks.push(window::close(*id));
125        }
126        Task::batch(tasks)
127    }
128
129    pub fn close_all_of(
130        &mut self,
131        window: Box<dyn Window<App, Theme, Message, Renderer>>,
132    ) -> Task<Id> {
133        let mut tasks = Vec::new();
134        for (id, w) in self.windows.iter() {
135            if *w == window {
136                tasks.push(window::close(*id));
137            }
138        }
139
140        Task::batch(tasks)
141    }
142
143    /// Checks for any open instances of the given window.
144    pub fn any_of(&self, window: &impl Window<App, Theme, Message, Renderer>) -> bool {
145        self.windows.values().any(|w| w == window)
146    }
147
148    /// Updates internal state to reflect that the given window Id  was closed.
149    pub fn was_closed(&mut self, id: Id) {
150        self.windows.remove(&id);
151    }
152
153    /// Returns all instances of the given window and their associated Ids.
154    #[allow(clippy::type_complexity)]
155    pub fn instances_of(
156        &self,
157        window: &impl Window<App, Theme, Message, Renderer>,
158    ) -> Vec<(&Id, &Box<dyn Window<App, Theme, Message, Renderer>>)> {
159        self.windows.iter().filter(|(_, w)| *w == window).collect()
160    }
161
162    pub fn empty(&self) -> bool {
163        self.windows.is_empty()
164    }
165}
166
167impl<App, Theme, Message, Renderer> Default for WindowManager<App, Theme, Message, Renderer> {
168    fn default() -> Self {
169        Self {
170            windows: HashMap::new(),
171        }
172    }
173}