summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorLibravatar Héctor Ramón <hector@hecrj.dev>2023-12-05 01:03:09 +0100
committerLibravatar GitHub <noreply@github.com>2023-12-05 01:03:09 +0100
commitfc285d3e461626408c56bbc1605fcf0c974b2f69 (patch)
tree8aca292516d9aa43b78a14f51dd90caf60c691d7
parent8727b3fc50ec251d9c117c51ca1289be5ba9b117 (diff)
parent5c5e7653bed248ba63faa6563e4d673e4441415e (diff)
downloadiced-fc285d3e461626408c56bbc1605fcf0c974b2f69.tar.gz
iced-fc285d3e461626408c56bbc1605fcf0c974b2f69.tar.bz2
iced-fc285d3e461626408c56bbc1605fcf0c974b2f69.zip
Merge pull request #1964 from bungoboingo/feat/multi-window-support
[Feature] 🪟 Multi Window 🪟 .. redux!
-rw-r--r--.github/ISSUE_TEMPLATE/BUG-REPORT.yml1
-rw-r--r--Cargo.toml2
-rw-r--r--ECOSYSTEM.md8
-rw-r--r--core/Cargo.toml3
-rw-r--r--core/src/event.rs2
-rw-r--r--core/src/point.rs58
-rw-r--r--core/src/widget/tree.rs2
-rw-r--r--core/src/window.rs6
-rw-r--r--core/src/window/event.rs22
-rw-r--r--core/src/window/id.rs21
-rw-r--r--core/src/window/position.rs (renamed from winit/src/position.rs)6
-rw-r--r--core/src/window/settings.rs (renamed from src/window/settings.rs)73
-rw-r--r--core/src/window/settings/linux.rs (renamed from winit/src/settings/linux.rs)0
-rw-r--r--core/src/window/settings/macos.rs (renamed from winit/src/settings/macos.rs)0
-rw-r--r--core/src/window/settings/other.rs (renamed from winit/src/settings/other.rs)0
-rw-r--r--core/src/window/settings/wasm.rs (renamed from winit/src/settings/wasm.rs)0
-rw-r--r--core/src/window/settings/windows.rs (renamed from winit/src/settings/windows.rs)0
-rw-r--r--examples/events/src/main.rs12
-rw-r--r--examples/exit/src/main.rs2
-rw-r--r--examples/integration/src/main.rs7
-rw-r--r--examples/loading_spinners/src/circular.rs2
-rw-r--r--examples/loading_spinners/src/linear.rs2
-rw-r--r--examples/multi_window/Cargo.toml9
-rw-r--r--examples/multi_window/src/main.rs215
-rw-r--r--examples/screenshot/src/main.rs7
-rw-r--r--examples/solar_system/src/main.rs14
-rw-r--r--examples/toast/src/main.rs4
-rw-r--r--examples/todos/src/main.rs14
-rw-r--r--examples/visible_bounds/src/main.rs2
-rw-r--r--futures/src/event.rs2
-rw-r--r--graphics/src/compositor.rs5
-rw-r--r--renderer/src/compositor.rs48
-rw-r--r--runtime/Cargo.toml1
-rw-r--r--runtime/src/command/action.rs4
-rw-r--r--runtime/src/lib.rs3
-rw-r--r--runtime/src/multi_window.rs6
-rw-r--r--runtime/src/multi_window/program.rs32
-rw-r--r--runtime/src/multi_window/state.rs280
-rw-r--r--runtime/src/window.rs102
-rw-r--r--runtime/src/window/action.rs160
-rw-r--r--src/lib.rs3
-rw-r--r--src/multi_window.rs4
-rw-r--r--src/multi_window/application.rs245
-rw-r--r--src/settings.rs13
-rw-r--r--src/window.rs4
-rw-r--r--src/window/position.rs32
-rw-r--r--tiny_skia/src/window/compositor.rs32
-rw-r--r--wgpu/src/window/compositor.rs25
-rw-r--r--widget/src/shader.rs2
-rw-r--r--widget/src/text_input.rs6
-rw-r--r--winit/Cargo.toml2
-rw-r--r--winit/src/application.rs140
-rw-r--r--winit/src/application/profiler.rs101
-rw-r--r--winit/src/conversion.rs186
-rw-r--r--winit/src/lib.rs9
-rw-r--r--winit/src/multi_window.rs1212
-rw-r--r--winit/src/multi_window/state.rs240
-rw-r--r--winit/src/multi_window/window_manager.rs156
-rw-r--r--winit/src/settings.rs232
59 files changed, 3020 insertions, 761 deletions
diff --git a/.github/ISSUE_TEMPLATE/BUG-REPORT.yml b/.github/ISSUE_TEMPLATE/BUG-REPORT.yml
index d4c94fcd..09b31697 100644
--- a/.github/ISSUE_TEMPLATE/BUG-REPORT.yml
+++ b/.github/ISSUE_TEMPLATE/BUG-REPORT.yml
@@ -25,7 +25,6 @@ body:
Before filing an issue...
- If you are using `wgpu`, you need an environment that supports Vulkan, Metal, or DirectX 12. Please, make sure you can run [the `wgpu` examples].
- - If you are using `glow`, you need support for OpenGL 2.1+. Please, make sure you can run [the `glow` examples].
If you have any issues running any of the examples, make sure your graphics drivers are up-to-date. If the issues persist, please report them to the authors of the libraries directly!
diff --git a/Cargo.toml b/Cargo.toml
index ad4cd1bb..0afbcd51 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -49,6 +49,8 @@ web-colors = ["iced_renderer/web-colors"]
webgl = ["iced_renderer/webgl"]
# Enables the syntax `highlighter` module
highlighter = ["iced_highlighter"]
+# Enables experimental multi-window support.
+multi-window = ["iced_winit/multi-window"]
# Enables the advanced module
advanced = []
diff --git a/ECOSYSTEM.md b/ECOSYSTEM.md
index 86581e4a..da3066d8 100644
--- a/ECOSYSTEM.md
+++ b/ECOSYSTEM.md
@@ -45,7 +45,7 @@ The widgets of a _graphical_ user interface produce some primitives that eventua
Currently, there are two different official renderers:
- [`iced_wgpu`] is powered by [`wgpu`] and supports Vulkan, DirectX 12, and Metal.
-- [`iced_glow`] is powered by [`glow`] and supports OpenGL 2.1+ and OpenGL ES 2.0+.
+- [`tiny-skia`] is used as a fallback software renderer when `wgpu` is not supported.
Additionally, the [`iced_graphics`] subcrate contains a bunch of backend-agnostic types that can be leveraged to build renderers. Both of the renderers rely on the graphical foundations provided by this crate.
@@ -54,10 +54,7 @@ The widgets of a graphical user _interface_ are interactive. __Shells__ gather a
Normally, a shell will be responsible of creating a window and managing the lifecycle of a user interface, implementing a runtime of [The Elm Architecture].
-As of now, there are two official shells:
-
-- [`iced_winit`] implements a shell runtime on top of [`winit`].
-- [`iced_glutin`] is similar to [`iced_winit`], but it also deals with [OpenGL context creation].
+As of now, there is one official shell: [`iced_winit`] implements a shell runtime on top of [`winit`].
## The web target
The Web platform provides all the abstractions necessary to draw widgets and gather users interactions.
@@ -91,5 +88,4 @@ Finally, [`iced`] unifies everything into a simple abstraction to create cross-p
[`winit`]: https://github.com/rust-windowing/winit
[`glutin`]: https://github.com/rust-windowing/glutin
[`dodrio`]: https://github.com/fitzgen/dodrio
-[OpenGL context creation]: https://www.khronos.org/opengl/wiki/Creating_an_OpenGL_Context
[The Elm Architecture]: https://guide.elm-lang.org/architecture/
diff --git a/core/Cargo.toml b/core/Cargo.toml
index 82946847..4672c754 100644
--- a/core/Cargo.toml
+++ b/core/Cargo.toml
@@ -23,5 +23,8 @@ palette.optional = true
[target.'cfg(target_arch = "wasm32")'.dependencies]
instant.workspace = true
+[target.'cfg(windows)'.dependencies]
+raw-window-handle.workspace = true
+
[dev-dependencies]
approx = "0.5"
diff --git a/core/src/event.rs b/core/src/event.rs
index 953cd73f..870b3074 100644
--- a/core/src/event.rs
+++ b/core/src/event.rs
@@ -19,7 +19,7 @@ pub enum Event {
Mouse(mouse::Event),
/// A window event
- Window(window::Event),
+ Window(window::Id, window::Event),
/// A touch event
Touch(touch::Event),
diff --git a/core/src/point.rs b/core/src/point.rs
index 9bf7726b..ef42852f 100644
--- a/core/src/point.rs
+++ b/core/src/point.rs
@@ -1,26 +1,34 @@
use crate::Vector;
+use num_traits::{Float, Num};
+use std::fmt;
+
/// A 2D point.
-#[derive(Debug, Clone, Copy, PartialEq, Default)]
-pub struct Point {
+#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
+pub struct Point<T = f32> {
/// The X coordinate.
- pub x: f32,
+ pub x: T,
/// The Y coordinate.
- pub y: f32,
+ pub y: T,
}
impl Point {
/// The origin (i.e. a [`Point`] at (0, 0)).
- pub const ORIGIN: Point = Point::new(0.0, 0.0);
+ pub const ORIGIN: Self = Self::new(0.0, 0.0);
+}
+impl<T: Num> Point<T> {
/// Creates a new [`Point`] with the given coordinates.
- pub const fn new(x: f32, y: f32) -> Self {
+ pub const fn new(x: T, y: T) -> Self {
Self { x, y }
}
/// Computes the distance to another [`Point`].
- pub fn distance(&self, to: Point) -> f32 {
+ pub fn distance(&self, to: Self) -> T
+ where
+ T: Float,
+ {
let a = self.x - to.x;
let b = self.y - to.y;
@@ -34,9 +42,9 @@ impl From<[f32; 2]> for Point {
}
}
-impl From<[u16; 2]> for Point {
+impl From<[u16; 2]> for Point<u16> {
fn from([x, y]: [u16; 2]) -> Self {
- Point::new(x.into(), y.into())
+ Point::new(x, y)
}
}
@@ -46,10 +54,13 @@ impl From<Point> for [f32; 2] {
}
}
-impl std::ops::Add<Vector> for Point {
+impl<T> std::ops::Add<Vector<T>> for Point<T>
+where
+ T: std::ops::Add<Output = T>,
+{
type Output = Self;
- fn add(self, vector: Vector) -> Self {
+ fn add(self, vector: Vector<T>) -> Self {
Self {
x: self.x + vector.x,
y: self.y + vector.y,
@@ -57,10 +68,13 @@ impl std::ops::Add<Vector> for Point {
}
}
-impl std::ops::Sub<Vector> for Point {
+impl<T> std::ops::Sub<Vector<T>> for Point<T>
+where
+ T: std::ops::Sub<Output = T>,
+{
type Output = Self;
- fn sub(self, vector: Vector) -> Self {
+ fn sub(self, vector: Vector<T>) -> Self {
Self {
x: self.x - vector.x,
y: self.y - vector.y,
@@ -68,10 +82,22 @@ impl std::ops::Sub<Vector> for Point {
}
}
-impl std::ops::Sub<Point> for Point {
- type Output = Vector;
+impl<T> std::ops::Sub<Point<T>> for Point<T>
+where
+ T: std::ops::Sub<Output = T>,
+{
+ type Output = Vector<T>;
- fn sub(self, point: Point) -> Vector {
+ fn sub(self, point: Self) -> Vector<T> {
Vector::new(self.x - point.x, self.y - point.y)
}
}
+
+impl<T> fmt::Display for Point<T>
+where
+ T: fmt::Display,
+{
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ write!(f, "Point {{ x: {}, y: {} }}", self.x, self.y)
+ }
+}
diff --git a/core/src/widget/tree.rs b/core/src/widget/tree.rs
index d4b8828a..ff52b1ce 100644
--- a/core/src/widget/tree.rs
+++ b/core/src/widget/tree.rs
@@ -67,7 +67,7 @@ impl Tree {
}
}
- /// Reconciliates the children of the tree with the provided list of widgets.
+ /// Reconciles the children of the tree with the provided list of widgets.
pub fn diff_children<'a, Message, Renderer>(
&mut self,
new_children: &[impl Borrow<dyn Widget<Message, Renderer> + 'a>],
diff --git a/core/src/window.rs b/core/src/window.rs
index a6dbdfb4..448ffc45 100644
--- a/core/src/window.rs
+++ b/core/src/window.rs
@@ -1,15 +1,21 @@
//! Build window-based GUI applications.
pub mod icon;
+pub mod settings;
mod event;
+mod id;
mod level;
mod mode;
+mod position;
mod redraw_request;
mod user_attention;
pub use event::Event;
pub use icon::Icon;
+pub use id::Id;
pub use level::Level;
pub use mode::Mode;
+pub use position::Position;
pub use redraw_request::RedrawRequest;
+pub use settings::Settings;
pub use user_attention::UserAttention;
diff --git a/core/src/window/event.rs b/core/src/window/event.rs
index e2fb5e66..b9ee7aca 100644
--- a/core/src/window/event.rs
+++ b/core/src/window/event.rs
@@ -1,10 +1,27 @@
use crate::time::Instant;
+use crate::{Point, Size};
use std::path::PathBuf;
/// A window-related event.
-#[derive(PartialEq, Eq, Clone, Debug)]
+#[derive(PartialEq, Clone, Debug)]
pub enum Event {
+ /// A window was opened.
+ Opened {
+ /// The position of the opened window. This is relative to the top-left corner of the desktop
+ /// the window is on, including virtual desktops. Refers to window's "inner" position,
+ /// or the client area, in logical pixels.
+ ///
+ /// **Note**: Not available in Wayland.
+ position: Option<Point>,
+ /// The size of the created window. This is its "inner" size, or the size of the
+ /// client area, in logical pixels.
+ size: Size,
+ },
+
+ /// A window was closed.
+ Closed,
+
/// A window was moved.
Moved {
/// The new logical x location of the window
@@ -27,9 +44,6 @@ pub enum Event {
RedrawRequested(Instant),
/// The user has requested for the window to close.
- ///
- /// Usually, you will want to terminate the execution whenever this event
- /// occurs.
CloseRequested,
/// A window was focused.
diff --git a/core/src/window/id.rs b/core/src/window/id.rs
new file mode 100644
index 00000000..20474c8f
--- /dev/null
+++ b/core/src/window/id.rs
@@ -0,0 +1,21 @@
+use std::hash::Hash;
+
+use std::sync::atomic::{self, AtomicU64};
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
+/// The id of the window.
+///
+/// Internally Iced reserves `window::Id::MAIN` for the first window spawned.
+pub struct Id(u64);
+
+static COUNT: AtomicU64 = AtomicU64::new(1);
+
+impl Id {
+ /// The reserved window [`Id`] for the first window in an Iced application.
+ pub const MAIN: Self = Id(0);
+
+ /// Creates a new unique window [`Id`].
+ pub fn unique() -> Id {
+ Id(COUNT.fetch_add(1, atomic::Ordering::Relaxed))
+ }
+}
diff --git a/winit/src/position.rs b/core/src/window/position.rs
index c260c29e..73391e75 100644
--- a/winit/src/position.rs
+++ b/core/src/window/position.rs
@@ -1,5 +1,7 @@
+use crate::Point;
+
/// The position of a window in a given screen.
-#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+#[derive(Debug, Clone, Copy, PartialEq)]
pub enum Position {
/// The platform-specific default position for a new window.
Default,
@@ -12,7 +14,7 @@ pub enum Position {
/// position. So if you have decorations enabled and want the window to be
/// at (0, 0) you would have to set the position to
/// `(PADDING_X, PADDING_Y)`.
- Specific(i32, i32),
+ Specific(Point),
}
impl Default for Position {
diff --git a/src/window/settings.rs b/core/src/window/settings.rs
index 0ee573e5..fbbf86ab 100644
--- a/src/window/settings.rs
+++ b/core/src/window/settings.rs
@@ -1,21 +1,47 @@
-use crate::window::{Icon, Level, Position};
+//! Configure your windows.
+#[cfg(target_os = "windows")]
+#[path = "settings/windows.rs"]
+mod platform;
+
+#[cfg(target_os = "macos")]
+#[path = "settings/macos.rs"]
+mod platform;
+
+#[cfg(target_os = "linux")]
+#[path = "settings/linux.rs"]
+mod platform;
+
+#[cfg(target_arch = "wasm32")]
+#[path = "settings/wasm.rs"]
+mod platform;
-pub use iced_winit::settings::PlatformSpecific;
+#[cfg(not(any(
+ target_os = "windows",
+ target_os = "macos",
+ target_os = "linux",
+ target_arch = "wasm32"
+)))]
+#[path = "settings/other.rs"]
+mod platform;
+use crate::window::{Icon, Level, Position};
+use crate::Size;
+
+pub use platform::PlatformSpecific;
/// The window settings of an application.
#[derive(Debug, Clone)]
pub struct Settings {
- /// The initial size of the window.
- pub size: (u32, u32),
+ /// The initial logical dimensions of the window.
+ pub size: Size,
/// The initial position of the window.
pub position: Position,
/// The minimum size of the window.
- pub min_size: Option<(u32, u32)>,
+ pub min_size: Option<Size>,
/// The maximum size of the window.
- pub max_size: Option<(u32, u32)>,
+ pub max_size: Option<Size>,
/// Whether the window should be visible or not.
pub visible: bool,
@@ -37,12 +63,22 @@ pub struct Settings {
/// Platform specific settings.
pub platform_specific: PlatformSpecific,
+
+ /// Whether the window will close when the user requests it, e.g. when a user presses the
+ /// close button.
+ ///
+ /// This can be useful if you want to have some behavior that executes before the window is
+ /// actually destroyed. If you disable this, you must manually close the window with the
+ /// `window::close` command.
+ ///
+ /// By default this is enabled.
+ pub exit_on_close_request: bool,
}
impl Default for Settings {
- fn default() -> Settings {
- Settings {
- size: (1024, 768),
+ fn default() -> Self {
+ Self {
+ size: Size::new(1024.0, 768.0),
position: Position::default(),
min_size: None,
max_size: None,
@@ -52,25 +88,8 @@ impl Default for Settings {
transparent: false,
level: Level::default(),
icon: None,
+ exit_on_close_request: true,
platform_specific: PlatformSpecific::default(),
}
}
}
-
-impl From<Settings> for iced_winit::settings::Window {
- fn from(settings: Settings) -> Self {
- Self {
- size: settings.size,
- position: iced_winit::Position::from(settings.position),
- min_size: settings.min_size,
- max_size: settings.max_size,
- visible: settings.visible,
- resizable: settings.resizable,
- decorations: settings.decorations,
- transparent: settings.transparent,
- level: settings.level,
- icon: settings.icon.map(Icon::into),
- platform_specific: settings.platform_specific,
- }
- }
-}
diff --git a/winit/src/settings/linux.rs b/core/src/window/settings/linux.rs
index 009b9d9e..009b9d9e 100644
--- a/winit/src/settings/linux.rs
+++ b/core/src/window/settings/linux.rs
diff --git a/winit/src/settings/macos.rs b/core/src/window/settings/macos.rs
index f86e63ad..f86e63ad 100644
--- a/winit/src/settings/macos.rs
+++ b/core/src/window/settings/macos.rs
diff --git a/winit/src/settings/other.rs b/core/src/window/settings/other.rs
index b1103f62..b1103f62 100644
--- a/winit/src/settings/other.rs
+++ b/core/src/window/settings/other.rs
diff --git a/winit/src/settings/wasm.rs b/core/src/window/settings/wasm.rs
index 8e0f1bbc..8e0f1bbc 100644
--- a/winit/src/settings/wasm.rs
+++ b/core/src/window/settings/wasm.rs
diff --git a/winit/src/settings/windows.rs b/core/src/window/settings/windows.rs
index 45d753bd..45d753bd 100644
--- a/winit/src/settings/windows.rs
+++ b/core/src/window/settings/windows.rs
diff --git a/examples/events/src/main.rs b/examples/events/src/main.rs
index 32d0da2c..334b012d 100644
--- a/examples/events/src/main.rs
+++ b/examples/events/src/main.rs
@@ -10,7 +10,10 @@ use iced::{
pub fn main() -> iced::Result {
Events::run(Settings {
- exit_on_close_request: false,
+ window: window::Settings {
+ exit_on_close_request: false,
+ ..window::Settings::default()
+ },
..Settings::default()
})
}
@@ -54,8 +57,9 @@ impl Application for Events {
Command::none()
}
Message::EventOccurred(event) => {
- if let Event::Window(window::Event::CloseRequested) = event {
- window::close()
+ if let Event::Window(id, window::Event::CloseRequested) = event
+ {
+ window::close(id)
} else {
Command::none()
}
@@ -65,7 +69,7 @@ impl Application for Events {
Command::none()
}
- Message::Exit => window::close(),
+ Message::Exit => window::close(window::Id::MAIN),
}
}
diff --git a/examples/exit/src/main.rs b/examples/exit/src/main.rs
index 6152f627..ec618dc1 100644
--- a/examples/exit/src/main.rs
+++ b/examples/exit/src/main.rs
@@ -34,7 +34,7 @@ impl Application for Exit {
fn update(&mut self, message: Message) -> Command<Message> {
match message {
- Message::Confirm => window::close(),
+ Message::Confirm => window::close(window::Id::MAIN),
Message::Exit => {
self.show_confirm = true;
diff --git a/examples/integration/src/main.rs b/examples/integration/src/main.rs
index 0f32fca0..276794c8 100644
--- a/examples/integration/src/main.rs
+++ b/examples/integration/src/main.rs
@@ -6,13 +6,17 @@ use scene::Scene;
use iced_wgpu::graphics::Viewport;
use iced_wgpu::{wgpu, Backend, Renderer, Settings};
+use iced_winit::conversion;
use iced_winit::core::mouse;
use iced_winit::core::renderer;
+use iced_winit::core::window;
use iced_winit::core::{Color, Font, Pixels, Size};
+use iced_winit::futures;
use iced_winit::runtime::program;
use iced_winit::runtime::Debug;
use iced_winit::style::Theme;
-use iced_winit::{conversion, futures, winit, Clipboard};
+use iced_winit::winit;
+use iced_winit::Clipboard;
use winit::{
event::{Event, ModifiersState, WindowEvent},
@@ -180,6 +184,7 @@ pub fn main() -> Result<(), Box<dyn std::error::Error>> {
// Map window event to iced event
if let Some(event) = iced_winit::conversion::window_event(
+ window::Id::MAIN,
&event,
window.scale_factor(),
modifiers,
diff --git a/examples/loading_spinners/src/circular.rs b/examples/loading_spinners/src/circular.rs
index bf01c3b4..dca8046a 100644
--- a/examples/loading_spinners/src/circular.rs
+++ b/examples/loading_spinners/src/circular.rs
@@ -279,7 +279,7 @@ where
let state = tree.state.downcast_mut::<State>();
- if let Event::Window(window::Event::RedrawRequested(now)) = event {
+ if let Event::Window(_, window::Event::RedrawRequested(now)) = event {
state.animation = state.animation.timed_transition(
self.cycle_duration,
self.rotation_duration,
diff --git a/examples/loading_spinners/src/linear.rs b/examples/loading_spinners/src/linear.rs
index c5bb4791..db10bfba 100644
--- a/examples/loading_spinners/src/linear.rs
+++ b/examples/loading_spinners/src/linear.rs
@@ -200,7 +200,7 @@ where
let state = tree.state.downcast_mut::<State>();
- if let Event::Window(window::Event::RedrawRequested(now)) = event {
+ if let Event::Window(_, window::Event::RedrawRequested(now)) = event {
*state = state.timed_transition(self.cycle_duration, now);
shell.request_redraw(RedrawRequest::At(
diff --git a/examples/multi_window/Cargo.toml b/examples/multi_window/Cargo.toml
new file mode 100644
index 00000000..2e222dfb
--- /dev/null
+++ b/examples/multi_window/Cargo.toml
@@ -0,0 +1,9 @@
+[package]
+name = "multi_window"
+version = "0.1.0"
+authors = ["Bingus <shankern@protonmail.com>"]
+edition = "2021"
+publish = false
+
+[dependencies]
+iced = { path = "../..", features = ["debug", "multi-window"] }
diff --git a/examples/multi_window/src/main.rs b/examples/multi_window/src/main.rs
new file mode 100644
index 00000000..5a5e70c1
--- /dev/null
+++ b/examples/multi_window/src/main.rs
@@ -0,0 +1,215 @@
+use iced::event;
+use iced::executor;
+use iced::multi_window::{self, Application};
+use iced::widget::{button, column, container, scrollable, text, text_input};
+use iced::window;
+use iced::{
+ Alignment, Command, Element, Length, Point, Settings, Subscription, Theme,
+ Vector,
+};
+
+use std::collections::HashMap;
+
+fn main() -> iced::Result {
+ Example::run(Settings::default())
+}
+
+#[derive(Default)]
+struct Example {
+ windows: HashMap<window::Id, Window>,
+ next_window_pos: window::Position,
+}
+
+#[derive(Debug)]
+struct Window {
+ title: String,
+ scale_input: String,
+ current_scale: f64,
+ theme: Theme,
+ input_id: iced::widget::text_input::Id,
+}
+
+#[derive(Debug, Clone)]
+enum Message {
+ ScaleInputChanged(window::Id, String),
+ ScaleChanged(window::Id, String),
+ TitleChanged(window::Id, String),
+ CloseWindow(window::Id),
+ WindowOpened(window::Id, Option<Point>),
+ WindowClosed(window::Id),
+ NewWindow,
+}
+
+impl multi_window::Application for Example {
+ type Executor = executor::Default;
+ type Message = Message;
+ type Theme = Theme;
+ type Flags = ();
+
+ fn new(_flags: ()) -> (Self, Command<Message>) {
+ (
+ Example {
+ windows: HashMap::from([(window::Id::MAIN, Window::new(1))]),
+ next_window_pos: window::Position::Default,
+ },
+ Command::none(),
+ )
+ }
+
+ fn title(&self, window: window::Id) -> String {
+ self.windows
+ .get(&window)
+ .map(|window| window.title.clone())
+ .unwrap_or("Example".to_string())
+ }
+
+ fn update(&mut self, message: Message) -> Command<Message> {
+ match message {
+ Message::ScaleInputChanged(id, scale) => {
+ let window =
+ self.windows.get_mut(&id).expect("Window not found!");
+ window.scale_input = scale;
+
+ Command::none()
+ }
+ Message::ScaleChanged(id, scale) => {
+ let window =
+ self.windows.get_mut(&id).expect("Window not found!");
+
+ window.current_scale = scale
+ .parse::<f64>()
+ .unwrap_or(window.current_scale)
+ .clamp(0.5, 5.0);
+
+ Command::none()
+ }
+ Message::TitleChanged(id, title) => {
+ let window =
+ self.windows.get_mut(&id).expect("Window not found.");
+
+ window.title = title;
+
+ Command::none()
+ }
+ Message::CloseWindow(id) => window::close(id),
+ Message::WindowClosed(id) => {
+ self.windows.remove(&id);
+ Command::none()
+ }
+ Message::WindowOpened(id, position) => {
+ if let Some(position) = position {
+ self.next_window_pos = window::Position::Specific(
+ position + Vector::new(20.0, 20.0),
+ );
+ }
+
+ if let Some(window) = self.windows.get(&id) {
+ text_input::focus(window.input_id.clone())
+ } else {
+ Command::none()
+ }
+ }
+ Message::NewWindow => {
+ let count = self.windows.len() + 1;
+
+ let (id, spawn_window) = window::spawn(window::Settings {
+ position: self.next_window_pos,
+ exit_on_close_request: count % 2 == 0,
+ ..Default::default()
+ });
+
+ self.windows.insert(id, Window::new(count));
+
+ spawn_window
+ }
+ }
+ }
+
+ fn view(&self, window: window::Id) -> Element<Message> {
+ let content = self.windows.get(&window).unwrap().view(window);
+
+ container(content)
+ .width(Length::Fill)
+ .height(Length::Fill)
+ .center_x()
+ .center_y()
+ .into()
+ }
+
+ fn theme(&self, window: window::Id) -> Self::Theme {
+ self.windows.get(&window).unwrap().theme.clone()
+ }
+
+ fn scale_factor(&self, window: window::Id) -> f64 {
+ self.windows
+ .get(&window)
+ .map(|window| window.current_scale)
+ .unwrap_or(1.0)
+ }
+
+ fn subscription(&self) -> Subscription<Self::Message> {
+ event::listen_with(|event, _| {
+ if let iced::Event::Window(id, window_event) = event {
+ match window_event {
+ window::Event::CloseRequested => {
+ Some(Message::CloseWindow(id))
+ }
+ window::Event::Opened { position, .. } => {
+ Some(Message::WindowOpened(id, position))
+ }
+ window::Event::Closed => Some(Message::WindowClosed(id)),
+ _ => None,
+ }
+ } else {
+ None
+ }
+ })
+ }
+}
+
+impl Window {
+ fn new(count: usize) -> Self {
+ Self {
+ title: format!("Window_{}", count),
+ scale_input: "1.0".to_string(),
+ current_scale: 1.0,
+ theme: if count % 2 == 0 {
+ Theme::Light
+ } else {
+ Theme::Dark
+ },
+ input_id: text_input::Id::unique(),
+ }
+ }
+
+ fn view(&self, id: window::Id) -> Element<Message> {
+ let scale_input = column![
+ text("Window scale factor:"),
+ text_input("Window Scale", &self.scale_input)
+ .on_input(move |msg| { Message::ScaleInputChanged(id, msg) })
+ .on_submit(Message::ScaleChanged(
+ id,
+ self.scale_input.to_string()
+ ))
+ ];
+
+ let title_input = column![
+ text("Window title:"),
+ text_input("Window Title", &self.title)
+ .on_input(move |msg| { Message::TitleChanged(id, msg) })
+ .id(self.input_id.clone())
+ ];
+
+ let new_window_button =
+ button(text("New Window")).on_press(Message::NewWindow);
+
+ let content = scrollable(
+ column![scale_input, title_input, new_window_button]
+ .spacing(50)
+ .width(Length::Fill)
+ .align_items(Alignment::Center),
+ );
+
+ container(content).width(200).center_x().into()
+ }
+}
diff --git a/examples/screenshot/src/main.rs b/examples/screenshot/src/main.rs
index f781a401..20d34be6 100644
--- a/examples/screenshot/src/main.rs
+++ b/examples/screenshot/src/main.rs
@@ -1,8 +1,8 @@
-use iced::alignment;
use iced::keyboard::KeyCode;
use iced::theme::{Button, Container};
use iced::widget::{button, column, container, image, row, text, text_input};
use iced::window::screenshot::{self, Screenshot};
+use iced::{alignment, window};
use iced::{
event, executor, keyboard, Alignment, Application, Command, ContentFit,
Element, Event, Length, Rectangle, Renderer, Subscription, Theme,
@@ -70,7 +70,10 @@ impl Application for Example {
fn update(&mut self, message: Self::Message) -> Command<Self::Message> {
match message {
Message::Screenshot => {
- return iced::window::screenshot(Message::ScreenshotData);
+ return iced::window::screenshot(
+ window::Id::MAIN,
+ Message::ScreenshotData,
+ );
}
Message::ScreenshotData(screenshot) => {
self.screenshot = Some(screenshot);
diff --git a/examples/solar_system/src/main.rs b/examples/solar_system/src/main.rs
index 8295dded..82421a86 100644
--- a/examples/solar_system/src/main.rs
+++ b/examples/solar_system/src/main.rs
@@ -114,14 +114,14 @@ impl State {
pub fn new() -> State {
let now = Instant::now();
- let (width, height) = window::Settings::default().size;
+ let size = window::Settings::default().size;
State {
space_cache: canvas::Cache::default(),
system_cache: canvas::Cache::default(),
start: now,
now,
- stars: Self::generate_stars(width, height),
+ stars: Self::generate_stars(size.width, size.height),
}
}
@@ -130,7 +130,7 @@ impl State {
self.system_cache.clear();
}
- fn generate_stars(width: u32, height: u32) -> Vec<(Point, f32)> {
+ fn generate_stars(width: f32, height: f32) -> Vec<(Point, f32)> {
use rand::Rng;
let mut rng = rand::thread_rng();
@@ -139,12 +139,8 @@ impl State {
.map(|_| {
(
Point::new(
- rng.gen_range(
- (-(width as f32) / 2.0)..(width as f32 / 2.0),
- ),
- rng.gen_range(
- (-(height as f32) / 2.0)..(height as f32 / 2.0),
- ),
+ rng.gen_range((-width / 2.0)..(width / 2.0)),
+ rng.gen_range((-height / 2.0)..(height / 2.0)),
),
rng.gen_range(0.5..1.0),
)
diff --git a/examples/toast/src/main.rs b/examples/toast/src/main.rs
index 934049d5..31b6f191 100644
--- a/examples/toast/src/main.rs
+++ b/examples/toast/src/main.rs
@@ -539,7 +539,9 @@ mod toast {
clipboard: &mut dyn Clipboard,
shell: &mut Shell<'_, Message>,
) -> event::Status {
- if let Event::Window(window::Event::RedrawRequested(now)) = &event {
+ if let Event::Window(_, window::Event::RedrawRequested(now)) =
+ &event
+ {
let mut next_redraw: Option<window::RedrawRequest> = None;
self.instants.iter_mut().enumerate().for_each(
diff --git a/examples/todos/src/main.rs b/examples/todos/src/main.rs
index 1ad3aba7..4dac032c 100644
--- a/examples/todos/src/main.rs
+++ b/examples/todos/src/main.rs
@@ -8,7 +8,7 @@ use iced::widget::{
};
use iced::window;
use iced::{Application, Element};
-use iced::{Color, Command, Length, Settings, Subscription};
+use iced::{Color, Command, Length, Settings, Size, Subscription};
use once_cell::sync::Lazy;
use serde::{Deserialize, Serialize};
@@ -22,7 +22,7 @@ pub fn main() -> iced::Result {
Todos::run(Settings {
window: window::Settings {
- size: (500, 800),
+ size: Size::new(500.0, 800.0),
..window::Settings::default()
},
..Settings::default()
@@ -54,7 +54,7 @@ enum Message {
FilterChanged(Filter),
TaskMessage(usize, TaskMessage),
TabPressed { shift: bool },
- ChangeWindowMode(window::Mode),
+ ToggleFullscreen(window::Mode),
}
impl Application for Todos {
@@ -165,8 +165,8 @@ impl Application for Todos {
widget::focus_next()
}
}
- Message::ChangeWindowMode(mode) => {
- window::change_mode(mode)
+ Message::ToggleFullscreen(mode) => {
+ window::change_mode(window::Id::MAIN, mode)
}
_ => Command::none(),
};
@@ -272,10 +272,10 @@ impl Application for Todos {
shift: modifiers.shift(),
}),
(keyboard::KeyCode::Up, keyboard::Modifiers::SHIFT) => {
- Some(Message::ChangeWindowMode(window::Mode::Fullscreen))
+ Some(Message::ToggleFullscreen(window::Mode::Fullscreen))
}
(keyboard::KeyCode::Down, keyboard::Modifiers::SHIFT) => {
- Some(Message::ChangeWindowMode(window::Mode::Windowed))
+ Some(Message::ToggleFullscreen(window::Mode::Windowed))
}
_ => None,
}
diff --git a/examples/visible_bounds/src/main.rs b/examples/visible_bounds/src/main.rs
index 697badb4..fdf1e0f9 100644
--- a/examples/visible_bounds/src/main.rs
+++ b/examples/visible_bounds/src/main.rs
@@ -167,7 +167,7 @@ impl Application for Example {
Event::Mouse(mouse::Event::CursorMoved { position }) => {
Some(Message::MouseMoved(position))
}
- Event::Window(window::Event::Resized { .. }) => {
+ Event::Window(_, window::Event::Resized { .. }) => {
Some(Message::WindowResized)
}
_ => None,
diff --git a/futures/src/event.rs b/futures/src/event.rs
index 214d2d40..97224506 100644
--- a/futures/src/event.rs
+++ b/futures/src/event.rs
@@ -35,7 +35,7 @@ where
subscription::filter_map(
(EventsWith, f),
move |event, status| match event {
- Event::Window(window::Event::RedrawRequested(_)) => None,
+ Event::Window(_, window::Event::RedrawRequested(_)) => None,
_ => f(event, status),
},
)
diff --git a/graphics/src/compositor.rs b/graphics/src/compositor.rs
index 0222a80f..b8b575b4 100644
--- a/graphics/src/compositor.rs
+++ b/graphics/src/compositor.rs
@@ -22,7 +22,10 @@ pub trait Compositor: Sized {
fn new<W: HasRawWindowHandle + HasRawDisplayHandle>(
settings: Self::Settings,
compatible_window: Option<&W>,
- ) -> Result<(Self, Self::Renderer), Error>;
+ ) -> Result<Self, Error>;
+
+ /// Creates a [`Self::Renderer`] for the [`Compositor`].
+ fn create_renderer(&self) -> Self::Renderer;
/// Crates a new [`Surface`] for the given window.
///
diff --git a/renderer/src/compositor.rs b/renderer/src/compositor.rs
index d1500089..9d0ff9ab 100644
--- a/renderer/src/compositor.rs
+++ b/renderer/src/compositor.rs
@@ -26,7 +26,7 @@ impl<Theme> crate::graphics::Compositor for Compositor<Theme> {
fn new<W: HasRawWindowHandle + HasRawDisplayHandle>(
settings: Self::Settings,
compatible_window: Option<&W>,
- ) -> Result<(Self, Self::Renderer), Error> {
+ ) -> Result<Self, Error> {
let candidates =
Candidate::list_from_env().unwrap_or(Candidate::default_list());
@@ -34,9 +34,7 @@ impl<Theme> crate::graphics::Compositor for Compositor<Theme> {
for candidate in candidates {
match candidate.build(settings, compatible_window) {
- Ok((compositor, renderer)) => {
- return Ok((compositor, renderer))
- }
+ Ok(compositor) => return Ok(compositor),
Err(new_error) => {
error = new_error;
}
@@ -46,6 +44,18 @@ impl<Theme> crate::graphics::Compositor for Compositor<Theme> {
Err(error)
}
+ fn create_renderer(&self) -> Self::Renderer {
+ match self {
+ Compositor::TinySkia(compositor) => {
+ Renderer::TinySkia(compositor.create_renderer())
+ }
+ #[cfg(feature = "wgpu")]
+ Compositor::Wgpu(compositor) => {
+ Renderer::Wgpu(compositor.create_renderer())
+ }
+ }
+ }
+
fn create_surface<W: HasRawWindowHandle + HasRawDisplayHandle>(
&mut self,
window: &W,
@@ -220,24 +230,21 @@ impl Candidate {
self,
settings: Settings,
_compatible_window: Option<&W>,
- ) -> Result<(Compositor<Theme>, Renderer<Theme>), Error> {
+ ) -> Result<Compositor<Theme>, Error> {
match self {
Self::TinySkia => {
- let (compositor, backend) =
- iced_tiny_skia::window::compositor::new();
+ let compositor = iced_tiny_skia::window::compositor::new(
+ iced_tiny_skia::Settings {
+ default_font: settings.default_font,
+ default_text_size: settings.default_text_size,
+ },
+ );
- Ok((
- Compositor::TinySkia(compositor),
- Renderer::TinySkia(iced_tiny_skia::Renderer::new(
- backend,
- settings.default_font,
- settings.default_text_size,
- )),
- ))
+ Ok(Compositor::TinySkia(compositor))
}
#[cfg(feature = "wgpu")]
Self::Wgpu => {
- let (compositor, backend) = iced_wgpu::window::compositor::new(
+ let compositor = iced_wgpu::window::compositor::new(
iced_wgpu::Settings {
default_font: settings.default_font,
default_text_size: settings.default_text_size,
@@ -247,14 +254,7 @@ impl Candidate {
_compatible_window,
)?;
- Ok((
- Compositor::Wgpu(compositor),
- Renderer::Wgpu(iced_wgpu::Renderer::new(
- backend,
- settings.default_font,
- settings.default_text_size,
- )),
- ))
+ Ok(Compositor::Wgpu(compositor))
}
#[cfg(not(feature = "wgpu"))]
Self::Wgpu => {
diff --git a/runtime/Cargo.toml b/runtime/Cargo.toml
index d19aedd3..8089d545 100644
--- a/runtime/Cargo.toml
+++ b/runtime/Cargo.toml
@@ -12,6 +12,7 @@ keywords.workspace = true
[features]
debug = []
+multi-window = []
[dependencies]
iced_core.workspace = true
diff --git a/runtime/src/command/action.rs b/runtime/src/command/action.rs
index 6551e233..cb0936df 100644
--- a/runtime/src/command/action.rs
+++ b/runtime/src/command/action.rs
@@ -84,7 +84,9 @@ impl<T> fmt::Debug for Action<T> {
Self::Clipboard(action) => {
write!(f, "Action::Clipboard({action:?})")
}
- Self::Window(action) => write!(f, "Action::Window({action:?})"),
+ Self::Window(action) => {
+ write!(f, "Action::Window({action:?})")
+ }
Self::System(action) => write!(f, "Action::System({action:?})"),
Self::Widget(_action) => write!(f, "Action::Widget"),
Self::LoadFont { .. } => write!(f, "Action::LoadFont"),
diff --git a/runtime/src/lib.rs b/runtime/src/lib.rs
index 29e94d65..03906f45 100644
--- a/runtime/src/lib.rs
+++ b/runtime/src/lib.rs
@@ -26,6 +26,9 @@ pub mod system;
pub mod user_interface;
pub mod window;
+#[cfg(feature = "multi-window")]
+pub mod multi_window;
+
// We disable debug capabilities on release builds unless the `debug` feature
// is explicitly enabled.
#[cfg(feature = "debug")]
diff --git a/runtime/src/multi_window.rs b/runtime/src/multi_window.rs
new file mode 100644
index 00000000..cf778a20
--- /dev/null
+++ b/runtime/src/multi_window.rs
@@ -0,0 +1,6 @@
+//! A multi-window application.
+pub mod program;
+pub mod state;
+
+pub use program::Program;
+pub use state::State;
diff --git a/runtime/src/multi_window/program.rs b/runtime/src/multi_window/program.rs
new file mode 100644
index 00000000..591b3e9a
--- /dev/null
+++ b/runtime/src/multi_window/program.rs
@@ -0,0 +1,32 @@
+//! Build interactive programs using The Elm Architecture.
+use crate::core::text;
+use crate::core::window;
+use crate::core::{Element, Renderer};
+use crate::Command;
+
+/// The core of a user interface for a multi-window application following The Elm Architecture.
+pub trait Program: Sized {
+ /// The graphics backend to use to draw the [`Program`].
+ type Renderer: Renderer + text::Renderer;
+
+ /// The type of __messages__ your [`Program`] will produce.
+ type Message: std::fmt::Debug + Send;
+
+ /// Handles a __message__ and updates the state of the [`Program`].
+ ///
+ /// This is where you define your __update logic__. All the __messages__,
+ /// produced by either user interactions or commands, will be handled by
+ /// this method.
+ ///
+ /// Any [`Command`] returned will be executed immediately in the
+ /// background by shells.
+ fn update(&mut self, message: Self::Message) -> Command<Self::Message>;
+
+ /// Returns the widgets to display in the [`Program`] for the `window`.
+ ///
+ /// These widgets can produce __messages__ based on user interaction.
+ fn view(
+ &self,
+ window: window::Id,
+ ) -> Element<'_, Self::Message, Self::Renderer>;
+}
diff --git a/runtime/src/multi_window/state.rs b/runtime/src/multi_window/state.rs
new file mode 100644
index 00000000..49f72c39
--- /dev/null
+++ b/runtime/src/multi_window/state.rs
@@ -0,0 +1,280 @@
+//! The internal state of a multi-window [`Program`].
+use crate::core::event::{self, Event};
+use crate::core::mouse;
+use crate::core::renderer;
+use crate::core::widget::operation::{self, Operation};
+use crate::core::{Clipboard, Size};
+use crate::user_interface::{self, UserInterface};
+use crate::{Command, Debug, Program};
+
+/// The execution state of a multi-window [`Program`]. It leverages caching, event
+/// processing, and rendering primitive storage.
+#[allow(missing_debug_implementations)]
+pub struct State<P>
+where
+ P: Program + 'static,
+{
+ program: P,
+ caches: Option<Vec<user_interface::Cache>>,
+ queued_events: Vec<Event>,
+ queued_messages: Vec<P::Message>,
+ mouse_interaction: mouse::Interaction,
+}
+
+impl<P> State<P>
+where
+ P: Program + 'static,
+{
+ /// Creates a new [`State`] with the provided [`Program`], initializing its
+ /// primitive with the given logical bounds and renderer.
+ pub fn new(
+ program: P,
+ bounds: Size,
+ renderer: &mut P::Renderer,
+ debug: &mut Debug,
+ ) -> Self {
+ let user_interface = build_user_interface(
+ &program,
+ user_interface::Cache::default(),
+ renderer,
+ bounds,
+ debug,
+ );
+
+ let caches = Some(vec![user_interface.into_cache()]);
+
+ State {
+ program,
+ caches,
+ queued_events: Vec::new(),
+ queued_messages: Vec::new(),
+ mouse_interaction: mouse::Interaction::Idle,
+ }
+ }
+
+ /// Returns a reference to the [`Program`] of the [`State`].
+ pub fn program(&self) -> &P {
+ &self.program
+ }
+
+ /// Queues an event in the [`State`] for processing during an [`update`].
+ ///
+ /// [`update`]: Self::update
+ pub fn queue_event(&mut self, event: Event) {
+ self.queued_events.push(event);
+ }
+
+ /// Queues a message in the [`State`] for processing during an [`update`].
+ ///
+ /// [`update`]: Self::update
+ pub fn queue_message(&mut self, message: P::Message) {
+ self.queued_messages.push(message);
+ }
+
+ /// Returns whether the event queue of the [`State`] is empty or not.
+ pub fn is_queue_empty(&self) -> bool {
+ self.queued_events.is_empty() && self.queued_messages.is_empty()
+ }
+
+ /// Returns the current [`mouse::Interaction`] of the [`State`].
+ pub fn mouse_interaction(&self) -> mouse::Interaction {
+ self.mouse_interaction
+ }
+
+ /// Processes all the queued events and messages, rebuilding and redrawing
+ /// the widgets of the linked [`Program`] if necessary.
+ ///
+ /// Returns a list containing the instances of [`Event`] that were not
+ /// captured by any widget, and the [`Command`] obtained from [`Program`]
+ /// after updating it, only if an update was necessary.
+ pub fn update(
+ &mut self,
+ bounds: Size,
+ cursor: mouse::Cursor,
+ renderer: &mut P::Renderer,
+ theme: &<P::Renderer as iced_core::Renderer>::Theme,
+ style: &renderer::Style,
+ clipboard: &mut dyn Clipboard,
+ debug: &mut Debug,
+ ) -> (Vec<Event>, Option<Command<P::Message>>) {
+ let mut user_interfaces = build_user_interfaces(
+ &self.program,
+ self.caches.take().unwrap(),
+ renderer,
+ bounds,
+ debug,
+ );
+
+ debug.event_processing_started();
+ let mut messages = Vec::new();
+
+ let uncaptured_events = user_interfaces.iter_mut().fold(
+ vec![],
+ |mut uncaptured_events, ui| {
+ let (_, event_statuses) = ui.update(
+ &self.queued_events,
+ cursor,
+ renderer,
+ clipboard,
+ &mut messages,
+ );
+
+ uncaptured_events.extend(
+ self.queued_events
+ .iter()
+ .zip(event_statuses)
+ .filter_map(|(event, status)| {
+ matches!(status, event::Status::Ignored)
+ .then_some(event)
+ })
+ .cloned(),
+ );
+ uncaptured_events
+ },
+ );
+
+ self.queued_events.clear();
+ messages.append(&mut self.queued_messages);
+ debug.event_processing_finished();
+
+ let commands = if messages.is_empty() {
+ debug.draw_started();
+
+ for ui in &mut user_interfaces {
+ self.mouse_interaction =
+ ui.draw(renderer, theme, style, cursor);
+ }
+
+ debug.draw_finished();
+
+ self.caches = Some(
+ user_interfaces
+ .drain(..)
+ .map(UserInterface::into_cache)
+ .collect(),
+ );
+
+ None
+ } else {
+ let temp_caches = user_interfaces
+ .drain(..)
+ .map(UserInterface::into_cache)
+ .collect();
+
+ drop(user_interfaces);
+
+ let commands = Command::batch(messages.into_iter().map(|msg| {
+ debug.log_message(&msg);
+
+ debug.update_started();
+ let command = self.program.update(msg);
+ debug.update_finished();
+
+ command
+ }));
+
+ let mut user_interfaces = build_user_interfaces(
+ &self.program,
+ temp_caches,
+ renderer,
+ bounds,
+ debug,
+ );
+
+ debug.draw_started();
+ for ui in &mut user_interfaces {
+ self.mouse_interaction =
+ ui.draw(renderer, theme, style, cursor);
+ }
+ debug.draw_finished();
+
+ self.caches = Some(
+ user_interfaces
+ .drain(..)
+ .map(UserInterface::into_cache)
+ .collect(),
+ );
+
+ Some(commands)
+ };
+
+ (uncaptured_events, commands)
+ }
+
+ /// Applies widget [`Operation`]s to the [`State`].
+ pub fn operate(
+ &mut self,
+ renderer: &mut P::Renderer,
+ operations: impl Iterator<Item = Box<dyn Operation<P::Message>>>,
+ bounds: Size,
+ debug: &mut Debug,
+ ) {
+ let mut user_interfaces = build_user_interfaces(
+ &self.program,
+ self.caches.take().unwrap(),
+ renderer,
+ bounds,
+ debug,
+ );
+
+ for operation in operations {
+ let mut current_operation = Some(operation);
+
+ while let Some(mut operation) = current_operation.take() {
+ for ui in &mut user_interfaces {
+ ui.operate(renderer, operation.as_mut());
+ }
+
+ match operation.finish() {
+ operation::Outcome::None => {}
+ operation::Outcome::Some(message) => {
+ self.queued_messages.push(message);
+ }
+ operation::Outcome::Chain(next) => {
+ current_operation = Some(next);
+ }
+ };
+ }
+ }
+
+ self.caches = Some(
+ user_interfaces
+ .drain(..)
+ .map(UserInterface::into_cache)
+ .collect(),
+ );
+ }
+}
+
+fn build_user_interfaces<'a, P: Program>(
+ program: &'a P,
+ mut caches: Vec<user_interface::Cache>,
+ renderer: &mut P::Renderer,
+ size: Size,
+ debug: &mut Debug,
+) -> Vec<UserInterface<'a, P::Message, P::Renderer>> {
+ caches
+ .drain(..)
+ .map(|cache| {
+ build_user_interface(program, cache, renderer, size, debug)
+ })
+ .collect()
+}
+
+fn build_user_interface<'a, P: Program>(
+ program: &'a P,
+ cache: user_interface::Cache,
+ renderer: &mut P::Renderer,
+ size: Size,
+ debug: &mut Debug,
+) -> UserInterface<'a, P::Message, P::Renderer> {
+ debug.view_started();
+ let view = program.view();
+ debug.view_finished();
+
+ debug.layout_started();
+ let user_interface = UserInterface::build(view, size, cache, renderer);
+ debug.layout_finished();
+
+ user_interface
+}
diff --git a/runtime/src/window.rs b/runtime/src/window.rs
index 41816967..f9d943f6 100644
--- a/runtime/src/window.rs
+++ b/runtime/src/window.rs
@@ -8,95 +8,113 @@ pub use screenshot::Screenshot;
use crate::command::{self, Command};
use crate::core::time::Instant;
-use crate::core::window::{Event, Icon, Level, Mode, UserAttention};
-use crate::core::Size;
+use crate::core::window::{
+ Event, Icon, Id, Level, Mode, Settings, UserAttention,
+};
+use crate::core::{Point, Size};
use crate::futures::event;
use crate::futures::Subscription;
/// Subscribes to the frames of the window of the running application.
///
/// The resulting [`Subscription`] will produce items at a rate equal to the
-/// refresh rate of the window. Note that this rate may be variable, as it is
+/// refresh rate of the first application window. Note that this rate may be variable, as it is
/// normally managed by the graphics driver and/or the OS.
///
/// In any case, this [`Subscription`] is useful to smoothly draw application-driven
/// animations without missing any frames.
pub fn frames() -> Subscription<Instant> {
event::listen_raw(|event, _status| match event {
- iced_core::Event::Window(Event::RedrawRequested(at)) => Some(at),
+ crate::core::Event::Window(_, Event::RedrawRequested(at)) => Some(at),
_ => None,
})
}
-/// Closes the current window and exits the application.
-pub fn close<Message>() -> Command<Message> {
- Command::single(command::Action::Window(Action::Close))
+/// Spawns a new window with the given `settings`.
+///
+/// Returns the new window [`Id`] alongside the [`Command`].
+pub fn spawn<Message>(settings: Settings) -> (Id, Command<Message>) {
+ let id = Id::unique();
+
+ (
+ id,
+ Command::single(command::Action::Window(Action::Spawn(id, settings))),
+ )
+}
+
+/// Closes the window with `id`.
+pub fn close<Message>(id: Id) -> Command<Message> {
+ Command::single(command::Action::Window(Action::Close(id)))
}
/// Begins dragging the window while the left mouse button is held.
-pub fn drag<Message>() -> Command<Message> {
- Command::single(command::Action::Window(Action::Drag))
+pub fn drag<Message>(id: Id) -> Command<Message> {
+ Command::single(command::Action::Window(Action::Drag(id)))
}
/// Resizes the window to the given logical dimensions.
-pub fn resize<Message>(new_size: Size<u32>) -> Command<Message> {
- Command::single(command::Action::Window(Action::Resize(new_size)))
+pub fn resize<Message>(id: Id, new_size: Size) -> Command<Message> {
+ Command::single(command::Action::Window(Action::Resize(id, new_size)))
}
-/// Fetches the current window size in logical dimensions.
+/// Fetches the window's size in logical dimensions.
pub fn fetch_size<Message>(
- f: impl FnOnce(Size<u32>) -> Message + 'static,
+ id: Id,
+ f: impl FnOnce(Size) -> Message + 'static,
) -> Command<Message> {
- Command::single(command::Action::Window(Action::FetchSize(Box::new(f))))
+ Command::single(command::Action::Window(Action::FetchSize(id, Box::new(f))))
}
/// Maximizes the window.
-pub fn maximize<Message>(maximized: bool) -> Command<Message> {
- Command::single(command::Action::Window(Action::Maximize(maximized)))
+pub fn maximize<Message>(id: Id, maximized: bool) -> Command<Message> {
+ Command::single(command::Action::Window(Action::Maximize(id, maximized)))
}
-/// Minimes the window.
-pub fn minimize<Message>(minimized: bool) -> Command<Message> {
- Command::single(command::Action::Window(Action::Minimize(minimized)))
+/// Minimizes the window.
+pub fn minimize<Message>(id: Id, minimized: bool) -> Command<Message> {
+ Command::single(command::Action::Window(Action::Minimize(id, minimized)))
}
-/// Moves a window to the given logical coordinates.
-pub fn move_to<Message>(x: i32, y: i32) -> Command<Message> {
- Command::single(command::Action::Window(Action::Move { x, y }))
+/// Moves the window to the given logical coordinates.
+pub fn move_to<Message>(id: Id, position: Point) -> Command<Message> {
+ Command::single(command::Action::Window(Action::Move(id, position)))
}
/// Changes the [`Mode`] of the window.
-pub fn change_mode<Message>(mode: Mode) -> Command<Message> {
- Command::single(command::Action::Window(Action::ChangeMode(mode)))
+pub fn change_mode<Message>(id: Id, mode: Mode) -> Command<Message> {
+ Command::single(command::Action::Window(Action::ChangeMode(id, mode)))
}
/// Fetches the current [`Mode`] of the window.
pub fn fetch_mode<Message>(
+ id: Id,
f: impl FnOnce(Mode) -> Message + 'static,
) -> Command<Message> {
- Command::single(command::Action::Window(Action::FetchMode(Box::new(f))))
+ Command::single(command::Action::Window(Action::FetchMode(id, Box::new(f))))
}
/// Toggles the window to maximized or back.
-pub fn toggle_maximize<Message>() -> Command<Message> {
- Command::single(command::Action::Window(Action::ToggleMaximize))
+pub fn toggle_maximize<Message>(id: Id) -> Command<Message> {
+ Command::single(command::Action::Window(Action::ToggleMaximize(id)))
}
/// Toggles the window decorations.
-pub fn toggle_decorations<Message>() -> Command<Message> {
- Command::single(command::Action::Window(Action::ToggleDecorations))
+pub fn toggle_decorations<Message>(id: Id) -> Command<Message> {
+ Command::single(command::Action::Window(Action::ToggleDecorations(id)))
}
-/// Request user attention to the window, this has no effect if the application
+/// Request user attention to the window. This has no effect if the application
/// is already focused. How requesting for user attention manifests is platform dependent,
/// see [`UserAttention`] for details.
///
/// Providing `None` will unset the request for user attention. Unsetting the request for
/// user attention might not be done automatically by the WM when the window receives input.
pub fn request_user_attention<Message>(
+ id: Id,
user_attention: Option<UserAttention>,
) -> Command<Message> {
Command::single(command::Action::Window(Action::RequestUserAttention(
+ id,
user_attention,
)))
}
@@ -107,30 +125,36 @@ pub fn request_user_attention<Message>(
/// This [`Command`] steals input focus from other applications. Do not use this method unless
/// you are certain that's what the user wants. Focus stealing can cause an extremely disruptive
/// user experience.
-pub fn gain_focus<Message>() -> Command<Message> {
- Command::single(command::Action::Window(Action::GainFocus))
+pub fn gain_focus<Message>(id: Id) -> Command<Message> {
+ Command::single(command::Action::Window(Action::GainFocus(id)))
}
/// Changes the window [`Level`].
-pub fn change_level<Message>(level: Level) -> Command<Message> {
- Command::single(command::Action::Window(Action::ChangeLevel(level)))
+pub fn change_level<Message>(id: Id, level: Level) -> Command<Message> {
+ Command::single(command::Action::Window(Action::ChangeLevel(id, level)))
}
-/// Fetches an identifier unique to the window.
+/// Fetches an identifier unique to the window, provided by the underlying windowing system. This is
+/// not to be confused with [`Id`].
pub fn fetch_id<Message>(
+ id: Id,
f: impl FnOnce(u64) -> Message + 'static,
) -> Command<Message> {
- Command::single(command::Action::Window(Action::FetchId(Box::new(f))))
+ Command::single(command::Action::Window(Action::FetchId(id, Box::new(f))))
}
/// Changes the [`Icon`] of the window.
-pub fn change_icon<Message>(icon: Icon) -> Command<Message> {
- Command::single(command::Action::Window(Action::ChangeIcon(icon)))
+pub fn change_icon<Message>(id: Id, icon: Icon) -> Command<Message> {
+ Command::single(command::Action::Window(Action::ChangeIcon(id, icon)))
}
/// Captures a [`Screenshot`] from the window.
pub fn screenshot<Message>(
+ id: Id,
f: impl FnOnce(Screenshot) -> Message + Send + 'static,
) -> Command<Message> {
- Command::single(command::Action::Window(Action::Screenshot(Box::new(f))))
+ Command::single(command::Action::Window(Action::Screenshot(
+ id,
+ Box::new(f),
+ )))
}
diff --git a/runtime/src/window/action.rs b/runtime/src/window/action.rs
index b6964e36..2d98b607 100644
--- a/runtime/src/window/action.rs
+++ b/runtime/src/window/action.rs
@@ -1,5 +1,5 @@
-use crate::core::window::{Icon, Level, Mode, UserAttention};
-use crate::core::Size;
+use crate::core::window::{Icon, Id, Level, Mode, Settings, UserAttention};
+use crate::core::{Point, Size};
use crate::futures::MaybeSend;
use crate::window::Screenshot;
@@ -7,43 +7,40 @@ use std::fmt;
/// An operation to be performed on some window.
pub enum Action<T> {
- /// Close the current window and exits the application.
- Close,
+ /// Spawns a new window with some [`Settings`].
+ Spawn(Id, Settings),
+ /// Close the window and exits the application.
+ Close(Id),
/// Move the window with the left mouse button until the button is
/// released.
///
/// There’s no guarantee that this will work unless the left mouse
/// button was pressed immediately before this function is called.
- Drag,
- /// Resize the window.
- Resize(Size<u32>),
- /// Fetch the current size of the window.
- FetchSize(Box<dyn FnOnce(Size<u32>) -> T + 'static>),
+ Drag(Id),
+ /// Resize the window to the given logical dimensions.
+ Resize(Id, Size),
+ /// Fetch the current logical dimensions of the window.
+ FetchSize(Id, Box<dyn FnOnce(Size) -> T + 'static>),
/// Set the window to maximized or back
- Maximize(bool),
+ Maximize(Id, bool),
/// Set the window to minimized or back
- Minimize(bool),
- /// Move the window.
+ Minimize(Id, bool),
+ /// Move the window to the given logical coordinates.
///
/// Unsupported on Wayland.
- Move {
- /// The new logical x location of the window
- x: i32,
- /// The new logical y location of the window
- y: i32,
- },
+ Move(Id, Point),
/// Change the [`Mode`] of the window.
- ChangeMode(Mode),
+ ChangeMode(Id, Mode),
/// Fetch the current [`Mode`] of the window.
- FetchMode(Box<dyn FnOnce(Mode) -> T + 'static>),
+ FetchMode(Id, Box<dyn FnOnce(Mode) -> T + 'static>),
/// Toggle the window to maximized or back
- ToggleMaximize,
+ ToggleMaximize(Id),
/// Toggle whether window has decorations.
///
/// ## Platform-specific
/// - **X11:** Not implemented.
/// - **Web:** Unsupported.
- ToggleDecorations,
+ ToggleDecorations(Id),
/// Request user attention to the window, this has no effect if the application
/// is already focused. How requesting for user attention manifests is platform dependent,
/// see [`UserAttention`] for details.
@@ -57,7 +54,7 @@ pub enum Action<T> {
/// - **macOS:** `None` has no effect.
/// - **X11:** Requests for user attention must be manually cleared.
/// - **Wayland:** Requires `xdg_activation_v1` protocol, `None` has no effect.
- RequestUserAttention(Option<UserAttention>),
+ RequestUserAttention(Id, Option<UserAttention>),
/// Bring the window to the front and sets input focus. Has no effect if the window is
/// already in focus, minimized, or not visible.
///
@@ -68,11 +65,11 @@ pub enum Action<T> {
/// ## Platform-specific
///
/// - **Web / Wayland:** Unsupported.
- GainFocus,
+ GainFocus(Id),
/// Change the window [`Level`].
- ChangeLevel(Level),
- /// Fetch an identifier unique to the window.
- FetchId(Box<dyn FnOnce(u64) -> T + 'static>),
+ ChangeLevel(Id, Level),
+ /// Fetch the raw identifier unique to the window.
+ FetchId(Id, Box<dyn FnOnce(u64) -> T + 'static>),
/// Change the window [`Icon`].
///
/// On Windows and X11, this is typically the small icon in the top-left
@@ -87,9 +84,9 @@ pub enum Action<T> {
///
/// - **X11:** Has no universal guidelines for icon sizes, so you're at the whims of the WM. That
/// said, it's usually in the same ballpark as on Windows.
- ChangeIcon(Icon),
+ ChangeIcon(Id, Icon),
/// Screenshot the viewport of the window.
- Screenshot(Box<dyn FnOnce(Screenshot) -> T + 'static>),
+ Screenshot(Id, Box<dyn FnOnce(Screenshot) -> T + 'static>),
}
impl<T> Action<T> {
@@ -102,29 +99,35 @@ impl<T> Action<T> {
T: 'static,
{
match self {
- Self::Close => Action::Close,
- Self::Drag => Action::Drag,
- Self::Resize(size) => Action::Resize(size),
- Self::FetchSize(o) => Action::FetchSize(Box::new(move |s| f(o(s)))),
- Self::Maximize(maximized) => Action::Maximize(maximized),
- Self::Minimize(minimized) => Action::Minimize(minimized),
- Self::Move { x, y } => Action::Move { x, y },
- Self::ChangeMode(mode) => Action::ChangeMode(mode),
- Self::FetchMode(o) => Action::FetchMode(Box::new(move |s| f(o(s)))),
- Self::ToggleMaximize => Action::ToggleMaximize,
- Self::ToggleDecorations => Action::ToggleDecorations,
- Self::RequestUserAttention(attention_type) => {
- Action::RequestUserAttention(attention_type)
+ Self::Spawn(id, settings) => Action::Spawn(id, settings),
+ Self::Close(id) => Action::Close(id),
+ Self::Drag(id) => Action::Drag(id),
+ Self::Resize(id, size) => Action::Resize(id, size),
+ Self::FetchSize(id, o) => {
+ Action::FetchSize(id, Box::new(move |s| f(o(s))))
}
- Self::GainFocus => Action::GainFocus,
- Self::ChangeLevel(level) => Action::ChangeLevel(level),
- Self::FetchId(o) => Action::FetchId(Box::new(move |s| f(o(s)))),
- Self::ChangeIcon(icon) => Action::ChangeIcon(icon),
- Self::Screenshot(tag) => {
- Action::Screenshot(Box::new(move |screenshot| {
- f(tag(screenshot))
- }))
+ Self::Maximize(id, maximized) => Action::Maximize(id, maximized),
+ Self::Minimize(id, minimized) => Action::Minimize(id, minimized),
+ Self::Move(id, position) => Action::Move(id, position),
+ Self::ChangeMode(id, mode) => Action::ChangeMode(id, mode),
+ Self::FetchMode(id, o) => {
+ Action::FetchMode(id, Box::new(move |s| f(o(s))))
}
+ Self::ToggleMaximize(id) => Action::ToggleMaximize(id),
+ Self::ToggleDecorations(id) => Action::ToggleDecorations(id),
+ Self::RequestUserAttention(id, attention_type) => {
+ Action::RequestUserAttention(id, attention_type)
+ }
+ Self::GainFocus(id) => Action::GainFocus(id),
+ Self::ChangeLevel(id, level) => Action::ChangeLevel(id, level),
+ Self::FetchId(id, o) => {
+ Action::FetchId(id, Box::new(move |s| f(o(s))))
+ }
+ Self::ChangeIcon(id, icon) => Action::ChangeIcon(id, icon),
+ Self::Screenshot(id, tag) => Action::Screenshot(
+ id,
+ Box::new(move |screenshot| f(tag(screenshot))),
+ ),
}
}
}
@@ -132,35 +135,46 @@ impl<T> Action<T> {
impl<T> fmt::Debug for Action<T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
- Self::Close => write!(f, "Action::Close"),
- Self::Drag => write!(f, "Action::Drag"),
- Self::Resize(size) => write!(f, "Action::Resize({size:?})"),
- Self::FetchSize(_) => write!(f, "Action::FetchSize"),
- Self::Maximize(maximized) => {
- write!(f, "Action::Maximize({maximized})")
+ Self::Spawn(id, settings) => {
+ write!(f, "Action::Spawn({id:?}, {settings:?})")
+ }
+ Self::Close(id) => write!(f, "Action::Close({id:?})"),
+ Self::Drag(id) => write!(f, "Action::Drag({id:?})"),
+ Self::Resize(id, size) => {
+ write!(f, "Action::Resize({id:?}, {size:?})")
+ }
+ Self::FetchSize(id, _) => write!(f, "Action::FetchSize({id:?})"),
+ Self::Maximize(id, maximized) => {
+ write!(f, "Action::Maximize({id:?}, {maximized})")
+ }
+ Self::Minimize(id, minimized) => {
+ write!(f, "Action::Minimize({id:?}, {minimized}")
+ }
+ Self::Move(id, position) => {
+ write!(f, "Action::Move({id:?}, {position})")
+ }
+ Self::ChangeMode(id, mode) => {
+ write!(f, "Action::SetMode({id:?}, {mode:?})")
}
- Self::Minimize(minimized) => {
- write!(f, "Action::Minimize({minimized}")
+ Self::FetchMode(id, _) => write!(f, "Action::FetchMode({id:?})"),
+ Self::ToggleMaximize(id) => {
+ write!(f, "Action::ToggleMaximize({id:?})")
}
- Self::Move { x, y } => {
- write!(f, "Action::Move {{ x: {x}, y: {y} }}")
+ Self::ToggleDecorations(id) => {
+ write!(f, "Action::ToggleDecorations({id:?})")
}
- Self::ChangeMode(mode) => write!(f, "Action::SetMode({mode:?})"),
- Self::FetchMode(_) => write!(f, "Action::FetchMode"),
- Self::ToggleMaximize => write!(f, "Action::ToggleMaximize"),
- Self::ToggleDecorations => write!(f, "Action::ToggleDecorations"),
- Self::RequestUserAttention(_) => {
- write!(f, "Action::RequestUserAttention")
+ Self::RequestUserAttention(id, _) => {
+ write!(f, "Action::RequestUserAttention({id:?})")
}
- Self::GainFocus => write!(f, "Action::GainFocus"),
- Self::ChangeLevel(level) => {
- write!(f, "Action::ChangeLevel({level:?})")
+ Self::GainFocus(id) => write!(f, "Action::GainFocus({id:?})"),
+ Self::ChangeLevel(id, level) => {
+ write!(f, "Action::ChangeLevel({id:?}, {level:?})")
}
- Self::FetchId(_) => write!(f, "Action::FetchId"),
- Self::ChangeIcon(_icon) => {
- write!(f, "Action::ChangeIcon(icon)")
+ Self::FetchId(id, _) => write!(f, "Action::FetchId({id:?})"),
+ Self::ChangeIcon(id, _icon) => {
+ write!(f, "Action::ChangeIcon({id:?})")
}
- Self::Screenshot(_) => write!(f, "Action::Screenshot"),
+ Self::Screenshot(id, _) => write!(f, "Action::Screenshot({id:?})"),
}
}
}
diff --git a/src/lib.rs b/src/lib.rs
index 47766e6f..002d2a79 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -182,6 +182,9 @@ pub mod window;
#[cfg(feature = "advanced")]
pub mod advanced;
+#[cfg(feature = "multi-window")]
+pub mod multi_window;
+
pub use style::theme;
pub use crate::core::alignment;
diff --git a/src/multi_window.rs b/src/multi_window.rs
new file mode 100644
index 00000000..5b7a00b4
--- /dev/null
+++ b/src/multi_window.rs
@@ -0,0 +1,4 @@
+//! Leverage multi-window support in your application.
+mod application;
+
+pub use application::Application;
diff --git a/src/multi_window/application.rs b/src/multi_window/application.rs
new file mode 100644
index 00000000..4a91bdf4
--- /dev/null
+++ b/src/multi_window/application.rs
@@ -0,0 +1,245 @@
+use crate::style::application::StyleSheet;
+use crate::window;
+use crate::{Command, Element, Executor, Settings, Subscription};
+
+/// An interactive cross-platform multi-window application.
+///
+/// This trait is the main entrypoint of Iced. Once implemented, you can run
+/// your GUI application by simply calling [`run`](#method.run).
+///
+/// - On native platforms, it will run in its own windows.
+/// - On the web, it will take control of the `<title>` and the `<body>` of the
+/// document and display only the contents of the `window::Id::MAIN` window.
+///
+/// An [`Application`] can execute asynchronous actions by returning a
+/// [`Command`] in some of its methods. If you do not intend to perform any
+/// background work in your program, the [`Sandbox`] trait offers a simplified
+/// interface.
+///
+/// When using an [`Application`] with the `debug` feature enabled, a debug view
+/// can be toggled by pressing `F12`.
+///
+/// # Examples
+/// See the `examples/multi-window` example to see this multi-window `Application` trait in action.
+///
+/// ## A simple "Hello, world!"
+///
+/// If you just want to get started, here is a simple [`Application`] that
+/// says "Hello, world!":
+///
+/// ```no_run
+/// use iced::{executor, window};
+/// use iced::{Command, Element, Settings, Theme};
+/// use iced::multi_window::{self, Application};
+///
+/// pub fn main() -> iced::Result {
+/// Hello::run(Settings::default())
+/// }
+///
+/// struct Hello;
+///
+/// impl multi_window::Application for Hello {
+/// type Executor = executor::Default;
+/// type Flags = ();
+/// type Message = ();
+/// type Theme = Theme;
+///
+/// fn new(_flags: ()) -> (Hello, Command<Self::Message>) {
+/// (Hello, Command::none())
+/// }
+///
+/// fn title(&self, _window: window::Id) -> String {
+/// String::from("A cool application")
+/// }
+///
+/// fn update(&mut self, _message: Self::Message) -> Command<Self::Message> {
+/// Command::none()
+/// }
+///
+/// fn view(&self, _window: window::Id) -> Element<Self::Message> {
+/// "Hello, world!".into()
+/// }
+/// }
+/// ```
+///
+/// [`Sandbox`]: crate::Sandbox
+pub trait Application: Sized {
+ /// The [`Executor`] that will run commands and subscriptions.
+ ///
+ /// The [default executor] can be a good starting point!
+ ///
+ /// [`Executor`]: Self::Executor
+ /// [default executor]: crate::executor::Default
+ type Executor: Executor;
+
+ /// The type of __messages__ your [`Application`] will produce.
+ type Message: std::fmt::Debug + Send;
+
+ /// The theme of your [`Application`].
+ type Theme: Default + StyleSheet;
+
+ /// The data needed to initialize your [`Application`].
+ type Flags;
+
+ /// Initializes the [`Application`] with the flags provided to
+ /// [`run`] as part of the [`Settings`].
+ ///
+ /// Here is where you should return the initial state of your app.
+ ///
+ /// Additionally, you can return a [`Command`] if you need to perform some
+ /// async action in the background on startup. This is useful if you want to
+ /// load state from a file, perform an initial HTTP request, etc.
+ ///
+ /// [`run`]: Self::run
+ fn new(flags: Self::Flags) -> (Self, Command<Self::Message>);
+
+ /// Returns the current title of the `window` of the [`Application`].
+ ///
+ /// This title can be dynamic! The runtime will automatically update the
+ /// title of your window when necessary.
+ fn title(&self, window: window::Id) -> String;
+
+ /// Handles a __message__ and updates the state of the [`Application`].
+ ///
+ /// This is where you define your __update logic__. All the __messages__,
+ /// produced by either user interactions or commands, will be handled by
+ /// this method.
+ ///
+ /// Any [`Command`] returned will be executed immediately in the background.
+ fn update(&mut self, message: Self::Message) -> Command<Self::Message>;
+
+ /// Returns the widgets to display in the `window` of the [`Application`].
+ ///
+ /// These widgets can produce __messages__ based on user interaction.
+ fn view(
+ &self,
+ window: window::Id,
+ ) -> Element<'_, Self::Message, crate::Renderer<Self::Theme>>;
+
+ /// Returns the current [`Theme`] of the `window` of the [`Application`].
+ ///
+ /// [`Theme`]: Self::Theme
+ #[allow(unused_variables)]
+ fn theme(&self, window: window::Id) -> Self::Theme {
+ Self::Theme::default()
+ }
+
+ /// Returns the current `Style` of the [`Theme`].
+ ///
+ /// [`Theme`]: Self::Theme
+ fn style(&self) -> <Self::Theme as StyleSheet>::Style {
+ <Self::Theme as StyleSheet>::Style::default()
+ }
+
+ /// Returns the event [`Subscription`] for the current state of the
+ /// application.
+ ///
+ /// A [`Subscription`] will be kept alive as long as you keep returning it,
+ /// and the __messages__ produced will be handled by
+ /// [`update`](#tymethod.update).
+ ///
+ /// By default, this method returns an empty [`Subscription`].
+ fn subscription(&self) -> Subscription<Self::Message> {
+ Subscription::none()
+ }
+
+ /// Returns the scale factor of the `window` of the [`Application`].
+ ///
+ /// It can be used to dynamically control the size of the UI at runtime
+ /// (i.e. zooming).
+ ///
+ /// For instance, a scale factor of `2.0` will make widgets twice as big,
+ /// while a scale factor of `0.5` will shrink them to half their size.
+ ///
+ /// By default, it returns `1.0`.
+ #[allow(unused_variables)]
+ fn scale_factor(&self, window: window::Id) -> f64 {
+ 1.0
+ }
+
+ /// Runs the multi-window [`Application`].
+ ///
+ /// On native platforms, this method will take control of the current thread
+ /// until the [`Application`] exits.
+ ///
+ /// On the web platform, this method __will NOT return__ unless there is an
+ /// [`Error`] during startup.
+ ///
+ /// [`Error`]: crate::Error
+ fn run(settings: Settings<Self::Flags>) -> crate::Result
+ where
+ Self: 'static,
+ {
+ #[allow(clippy::needless_update)]
+ let renderer_settings = crate::renderer::Settings {
+ default_font: settings.default_font,
+ default_text_size: settings.default_text_size,
+ antialiasing: if settings.antialiasing {
+ Some(crate::graphics::Antialiasing::MSAAx4)
+ } else {
+ None
+ },
+ ..crate::renderer::Settings::default()
+ };
+
+ Ok(crate::shell::multi_window::run::<
+ Instance<Self>,
+ Self::Executor,
+ crate::renderer::Compositor<Self::Theme>,
+ >(settings.into(), renderer_settings)?)
+ }
+}
+
+struct Instance<A: Application>(A);
+
+impl<A> crate::runtime::multi_window::Program for Instance<A>
+where
+ A: Application,
+{
+ type Renderer = crate::Renderer<A::Theme>;
+ type Message = A::Message;
+
+ fn update(&mut self, message: Self::Message) -> Command<Self::Message> {
+ self.0.update(message)
+ }
+
+ fn view(
+ &self,
+ window: window::Id,
+ ) -> Element<'_, Self::Message, Self::Renderer> {
+ self.0.view(window)
+ }
+}
+
+impl<A> crate::shell::multi_window::Application for Instance<A>
+where
+ A: Application,
+{
+ type Flags = A::Flags;
+
+ fn new(flags: Self::Flags) -> (Self, Command<A::Message>) {
+ let (app, command) = A::new(flags);
+
+ (Instance(app), command)
+ }
+
+ fn title(&self, window: window::Id) -> String {
+ self.0.title(window)
+ }
+
+ fn theme(&self, window: window::Id) -> A::Theme {
+ self.0.theme(window)
+ }
+
+ fn style(&self) -> <A::Theme as StyleSheet>::Style {
+ self.0.style()
+ }
+
+ fn subscription(&self) -> Subscription<Self::Message> {
+ self.0.subscription()
+ }
+
+ fn scale_factor(&self, window: window::Id) -> f64 {
+ self.0.scale_factor(window)
+ }
+}
diff --git a/src/settings.rs b/src/settings.rs
index 42df31e4..d9476b61 100644
--- a/src/settings.rs
+++ b/src/settings.rs
@@ -46,14 +46,6 @@ pub struct Settings<Flags> {
///
/// [`Canvas`]: crate::widget::Canvas
pub antialiasing: bool,
-
- /// Whether the [`Application`] should exit when the user requests the
- /// window to close (e.g. the user presses the close button).
- ///
- /// By default, it is enabled.
- ///
- /// [`Application`]: crate::Application
- pub exit_on_close_request: bool,
}
impl<Flags> Settings<Flags> {
@@ -71,7 +63,6 @@ impl<Flags> Settings<Flags> {
default_font: default_settings.default_font,
default_text_size: default_settings.default_text_size,
antialiasing: default_settings.antialiasing,
- exit_on_close_request: default_settings.exit_on_close_request,
}
}
}
@@ -89,7 +80,6 @@ where
default_font: Font::default(),
default_text_size: Pixels(16.0),
antialiasing: false,
- exit_on_close_request: true,
}
}
}
@@ -98,10 +88,9 @@ impl<Flags> From<Settings<Flags>> for iced_winit::Settings<Flags> {
fn from(settings: Settings<Flags>) -> iced_winit::Settings<Flags> {
iced_winit::Settings {
id: settings.id,
- window: settings.window.into(),
+ window: settings.window,
flags: settings.flags,
fonts: settings.fonts,
- exit_on_close_request: settings.exit_on_close_request,
}
}
}
diff --git a/src/window.rs b/src/window.rs
index e4601575..9f96da52 100644
--- a/src/window.rs
+++ b/src/window.rs
@@ -1,12 +1,8 @@
//! Configure the window of your application in native platforms.
-mod position;
-mod settings;
pub mod icon;
pub use icon::Icon;
-pub use position::Position;
-pub use settings::{PlatformSpecific, Settings};
pub use crate::core::window::*;
pub use crate::runtime::window::*;
diff --git a/src/window/position.rs b/src/window/position.rs
deleted file mode 100644
index 6b9fac41..00000000
--- a/src/window/position.rs
+++ /dev/null
@@ -1,32 +0,0 @@
-/// The position of a window in a given screen.
-#[derive(Debug, Clone, Copy, PartialEq, Eq)]
-pub enum Position {
- /// The platform-specific default position for a new window.
- Default,
- /// The window is completely centered on the screen.
- Centered,
- /// The window is positioned with specific coordinates: `(X, Y)`.
- ///
- /// When the decorations of the window are enabled, Windows 10 will add some
- /// invisible padding to the window. This padding gets included in the
- /// position. So if you have decorations enabled and want the window to be
- /// at (0, 0) you would have to set the position to
- /// `(PADDING_X, PADDING_Y)`.
- Specific(i32, i32),
-}
-
-impl Default for Position {
- fn default() -> Self {
- Self::Default
- }
-}
-
-impl From<Position> for iced_winit::Position {
- fn from(position: Position) -> Self {
- match position {
- Position::Default => Self::Default,
- Position::Centered => Self::Centered,
- Position::Specific(x, y) => Self::Specific(x, y),
- }
- }
-}
diff --git a/tiny_skia/src/window/compositor.rs b/tiny_skia/src/window/compositor.rs
index 828e522f..87ded746 100644
--- a/tiny_skia/src/window/compositor.rs
+++ b/tiny_skia/src/window/compositor.rs
@@ -8,6 +8,7 @@ use raw_window_handle::{HasRawDisplayHandle, HasRawWindowHandle};
use std::marker::PhantomData;
pub struct Compositor<Theme> {
+ settings: Settings,
_theme: PhantomData<Theme>,
}
@@ -27,17 +28,16 @@ impl<Theme> crate::graphics::Compositor for Compositor<Theme> {
fn new<W: HasRawWindowHandle + HasRawDisplayHandle>(
settings: Self::Settings,
_compatible_window: Option<&W>,
- ) -> Result<(Self, Self::Renderer), Error> {
- let (compositor, backend) = new();
+ ) -> Result<Self, Error> {
+ Ok(new(settings))
+ }
- Ok((
- compositor,
- Renderer::new(
- backend,
- settings.default_font,
- settings.default_text_size,
- ),
- ))
+ fn create_renderer(&self) -> Self::Renderer {
+ Renderer::new(
+ Backend::new(),
+ self.settings.default_font,
+ self.settings.default_text_size,
+ )
}
fn create_surface<W: HasRawWindowHandle + HasRawDisplayHandle>(
@@ -121,13 +121,11 @@ impl<Theme> crate::graphics::Compositor for Compositor<Theme> {
}
}
-pub fn new<Theme>() -> (Compositor<Theme>, Backend) {
- (
- Compositor {
- _theme: PhantomData,
- },
- Backend::new(),
- )
+pub fn new<Theme>(settings: Settings) -> Compositor<Theme> {
+ Compositor {
+ settings,
+ _theme: PhantomData,
+ }
}
pub fn present<T: AsRef<str>>(
diff --git a/wgpu/src/window/compositor.rs b/wgpu/src/window/compositor.rs
index 90d64e17..090e0e9f 100644
--- a/wgpu/src/window/compositor.rs
+++ b/wgpu/src/window/compositor.rs
@@ -139,16 +139,14 @@ impl<Theme> Compositor<Theme> {
pub fn new<Theme, W: HasRawWindowHandle + HasRawDisplayHandle>(
settings: Settings,
compatible_window: Option<&W>,
-) -> Result<(Compositor<Theme>, Backend), Error> {
+) -> Result<Compositor<Theme>, Error> {
let compositor = futures::executor::block_on(Compositor::request(
settings,
compatible_window,
))
.ok_or(Error::GraphicsAdapterNotFound)?;
- let backend = compositor.create_backend();
-
- Ok((compositor, backend))
+ Ok(compositor)
}
/// Presents the given primitives with the given [`Compositor`] and [`Backend`].
@@ -214,17 +212,16 @@ impl<Theme> graphics::Compositor for Compositor<Theme> {
fn new<W: HasRawWindowHandle + HasRawDisplayHandle>(
settings: Self::Settings,
compatible_window: Option<&W>,
- ) -> Result<(Self, Self::Renderer), Error> {
- let (compositor, backend) = new(settings, compatible_window)?;
+ ) -> Result<Self, Error> {
+ new(settings, compatible_window)
+ }
- Ok((
- compositor,
- Renderer::new(
- backend,
- settings.default_font,
- settings.default_text_size,
- ),
- ))
+ fn create_renderer(&self) -> Self::Renderer {
+ Renderer::new(
+ self.create_backend(),
+ self.settings.default_font,
+ self.settings.default_text_size,
+ )
}
fn create_surface<W: HasRawWindowHandle + HasRawDisplayHandle>(
diff --git a/widget/src/shader.rs b/widget/src/shader.rs
index fe6214db..8e334693 100644
--- a/widget/src/shader.rs
+++ b/widget/src/shader.rs
@@ -109,7 +109,7 @@ where
Some(Event::Keyboard(keyboard_event))
}
core::Event::Touch(touch_event) => Some(Event::Touch(touch_event)),
- core::Event::Window(window::Event::RedrawRequested(instant)) => {
+ core::Event::Window(_, window::Event::RedrawRequested(instant)) => {
Some(Event::RedrawRequested(instant))
}
_ => None,
diff --git a/widget/src/text_input.rs b/widget/src/text_input.rs
index ab0e2412..65d3e1eb 100644
--- a/widget/src/text_input.rs
+++ b/widget/src/text_input.rs
@@ -1003,14 +1003,14 @@ where
state.keyboard_modifiers = modifiers;
}
- Event::Window(window::Event::Unfocused) => {
+ Event::Window(_, window::Event::Unfocused) => {
let state = state();
if let Some(focus) = &mut state.is_focused {
focus.is_window_focused = false;
}
}
- Event::Window(window::Event::Focused) => {
+ Event::Window(_, window::Event::Focused) => {
let state = state();
if let Some(focus) = &mut state.is_focused {
@@ -1020,7 +1020,7 @@ where
shell.request_redraw(window::RedrawRequest::NextFrame);
}
}
- Event::Window(window::Event::RedrawRequested(now)) => {
+ Event::Window(_, window::Event::RedrawRequested(now)) => {
let state = state();
if let Some(focus) = &mut state.is_focused {
diff --git a/winit/Cargo.toml b/winit/Cargo.toml
index 674a66d3..87e600ae 100644
--- a/winit/Cargo.toml
+++ b/winit/Cargo.toml
@@ -19,6 +19,7 @@ x11 = ["winit/x11"]
wayland = ["winit/wayland"]
wayland-dlopen = ["winit/wayland-dlopen"]
wayland-csd-adwaita = ["winit/wayland-csd-adwaita"]
+multi-window = ["iced_runtime/multi-window"]
[dependencies]
iced_graphics.workspace = true
@@ -26,7 +27,6 @@ iced_runtime.workspace = true
iced_style.workspace = true
log.workspace = true
-raw-window-handle.workspace = true
thiserror.workspace = true
tracing.workspace = true
window_clipboard.workspace = true
diff --git a/winit/src/application.rs b/winit/src/application.rs
index 2c5c864a..d9700075 100644
--- a/winit/src/application.rs
+++ b/winit/src/application.rs
@@ -1,6 +1,4 @@
//! Create interactive, native cross-platform applications.
-#[cfg(feature = "trace")]
-mod profiler;
mod state;
pub use state::State;
@@ -27,11 +25,6 @@ use futures::channel::mpsc;
use std::mem::ManuallyDrop;
-#[cfg(feature = "trace")]
-pub use profiler::Profiler;
-#[cfg(feature = "trace")]
-use tracing::{info_span, instrument::Instrument};
-
/// An interactive, native cross-platform application.
///
/// This trait is the main entrypoint of Iced. Once implemented, you can run
@@ -119,15 +112,9 @@ where
use futures::Future;
use winit::event_loop::EventLoopBuilder;
- #[cfg(feature = "trace")]
- let _guard = Profiler::init();
-
let mut debug = Debug::new();
debug.startup_started();
- #[cfg(feature = "trace")]
- let _ = info_span!("Application", "RUN").entered();
-
let event_loop = EventLoopBuilder::with_user_event().build();
let proxy = event_loop.create_proxy();
@@ -148,14 +135,15 @@ where
let target = settings.window.platform_specific.target.clone();
let should_be_visible = settings.window.visible;
- let builder = settings
- .window
- .into_builder(
- &application.title(),
- event_loop.primary_monitor(),
- settings.id,
- )
- .with_visible(false);
+ let exit_on_close_request = settings.window.exit_on_close_request;
+
+ let builder = conversion::window_settings(
+ settings.window,
+ &application.title(),
+ event_loop.primary_monitor(),
+ settings.id,
+ )
+ .with_visible(false);
log::debug!("Window builder: {builder:#?}");
@@ -193,8 +181,8 @@ where
};
}
- let (compositor, mut renderer) =
- C::new(compositor_settings, Some(&window))?;
+ let compositor = C::new(compositor_settings, Some(&window))?;
+ let mut renderer = compositor.create_renderer();
for font in settings.fonts {
use crate::core::text::Renderer;
@@ -205,28 +193,20 @@ where
let (mut event_sender, event_receiver) = mpsc::unbounded();
let (control_sender, mut control_receiver) = mpsc::unbounded();
- let mut instance = Box::pin({
- let run_instance = run_instance::<A, E, C>(
- application,
- compositor,
- renderer,
- runtime,
- proxy,
- debug,
- event_receiver,
- control_sender,
- init_command,
- window,
- should_be_visible,
- settings.exit_on_close_request,
- );
-
- #[cfg(feature = "trace")]
- let run_instance =
- run_instance.instrument(info_span!("Application", "LOOP"));
-
- run_instance
- });
+ let mut instance = Box::pin(run_instance::<A, E, C>(
+ application,
+ compositor,
+ renderer,
+ runtime,
+ proxy,
+ debug,
+ event_receiver,
+ control_sender,
+ init_command,
+ window,
+ should_be_visible,
+ exit_on_close_request,
+ ));
let mut context = task::Context::from_waker(task::noop_waker_ref());
@@ -426,6 +406,7 @@ async fn run_instance<A, E, C>(
// Then, we can use the `interface_state` here to decide if a redraw
// is needed right away, or simply wait until a specific time.
let redraw_event = Event::Window(
+ window::Id::MAIN,
window::Event::RedrawRequested(Instant::now()),
);
@@ -488,9 +469,6 @@ async fn run_instance<A, E, C>(
messages.push(message);
}
event::Event::RedrawRequested(_) => {
- #[cfg(feature = "trace")]
- let _ = info_span!("Application", "FRAME").entered();
-
let physical_size = state.physical_size();
if physical_size.width == 0 || physical_size.height == 0 {
@@ -578,6 +556,7 @@ async fn run_instance<A, E, C>(
state.update(&window, &window_event, &mut debug);
if let Some(event) = conversion::window_event(
+ window::Id::MAIN,
&window_event,
state.scale_factor(),
state.modifiers(),
@@ -629,24 +608,12 @@ pub fn build_user_interface<'a, A: Application>(
where
<A::Renderer as core::Renderer>::Theme: StyleSheet,
{
- #[cfg(feature = "trace")]
- let view_span = info_span!("Application", "VIEW").entered();
-
debug.view_started();
let view = application.view();
-
- #[cfg(feature = "trace")]
- let _ = view_span.exit();
debug.view_finished();
- #[cfg(feature = "trace")]
- let layout_span = info_span!("Application", "LAYOUT").entered();
-
debug.layout_started();
let user_interface = UserInterface::build(view, size, cache, renderer);
-
- #[cfg(feature = "trace")]
- let _ = layout_span.exit();
debug.layout_finished();
user_interface
@@ -673,16 +640,10 @@ pub fn update<A: Application, C, E: Executor>(
<A::Renderer as core::Renderer>::Theme: StyleSheet,
{
for message in messages.drain(..) {
- #[cfg(feature = "trace")]
- let update_span = info_span!("Application", "UPDATE").entered();
-
debug.log_message(&message);
debug.update_started();
let command = runtime.enter(|| application.update(message));
-
- #[cfg(feature = "trace")]
- let _ = update_span.exit();
debug.update_finished();
run_command(
@@ -752,20 +713,27 @@ pub fn run_command<A, C, E>(
}
},
command::Action::Window(action) => match action {
- window::Action::Close => {
+ window::Action::Close(_id) => {
*should_exit = true;
}
- window::Action::Drag => {
+ window::Action::Drag(_id) => {
let _res = window.drag_window();
}
- window::Action::Resize(size) => {
+ window::Action::Spawn { .. } => {
+ log::warn!(
+ "Spawning a window is only available with \
+ multi-window applications."
+ );
+ }
+ window::Action::Resize(_id, size) => {
window.set_inner_size(winit::dpi::LogicalSize {
width: size.width,
height: size.height,
});
}
- window::Action::FetchSize(callback) => {
- let size = window.inner_size();
+ window::Action::FetchSize(_id, callback) => {
+ let size =
+ window.inner_size().to_logical(window.scale_factor());
proxy
.send_event(callback(Size::new(
@@ -774,29 +742,29 @@ pub fn run_command<A, C, E>(
)))
.expect("Send message to event loop");
}
- window::Action::Maximize(maximized) => {
+ window::Action::Maximize(_id, maximized) => {
window.set_maximized(maximized);
}
- window::Action::Minimize(minimized) => {
+ window::Action::Minimize(_id, minimized) => {
window.set_minimized(minimized);
}
- window::Action::Move { x, y } => {
+ window::Action::Move(_id, position) => {
window.set_outer_position(winit::dpi::LogicalPosition {
- x,
- y,
+ x: position.x,
+ y: position.y,
});
}
- window::Action::ChangeMode(mode) => {
+ window::Action::ChangeMode(_id, mode) => {
window.set_visible(conversion::visible(mode));
window.set_fullscreen(conversion::fullscreen(
window.current_monitor(),
mode,
));
}
- window::Action::ChangeIcon(icon) => {
+ window::Action::ChangeIcon(_id, icon) => {
window.set_window_icon(conversion::icon(icon));
}
- window::Action::FetchMode(tag) => {
+ window::Action::FetchMode(_id, tag) => {
let mode = if window.is_visible().unwrap_or(true) {
conversion::mode(window.fullscreen())
} else {
@@ -807,29 +775,29 @@ pub fn run_command<A, C, E>(
.send_event(tag(mode))
.expect("Send message to event loop");
}
- window::Action::ToggleMaximize => {
+ window::Action::ToggleMaximize(_id) => {
window.set_maximized(!window.is_maximized());
}
- window::Action::ToggleDecorations => {
+ window::Action::ToggleDecorations(_id) => {
window.set_decorations(!window.is_decorated());
}
- window::Action::RequestUserAttention(user_attention) => {
+ window::Action::RequestUserAttention(_id, user_attention) => {
window.request_user_attention(
user_attention.map(conversion::user_attention),
);
}
- window::Action::GainFocus => {
+ window::Action::GainFocus(_id) => {
window.focus_window();
}
- window::Action::ChangeLevel(level) => {
+ window::Action::ChangeLevel(_id, level) => {
window.set_window_level(conversion::window_level(level));
}
- window::Action::FetchId(tag) => {
+ window::Action::FetchId(_id, tag) => {
proxy
.send_event(tag(window.id().into()))
.expect("Send message to event loop");
}
- window::Action::Screenshot(tag) => {
+ window::Action::Screenshot(_id, tag) => {
let bytes = compositor.screenshot(
renderer,
surface,
diff --git a/winit/src/application/profiler.rs b/winit/src/application/profiler.rs
deleted file mode 100644
index 7031507a..00000000
--- a/winit/src/application/profiler.rs
+++ /dev/null
@@ -1,101 +0,0 @@
-//! A simple profiler for Iced.
-use std::ffi::OsStr;
-use std::path::Path;
-use std::time::Duration;
-use tracing_subscriber::prelude::*;
-use tracing_subscriber::Registry;
-#[cfg(feature = "chrome-trace")]
-use {
- tracing_chrome::FlushGuard,
- tracing_subscriber::fmt::{format::DefaultFields, FormattedFields},
-};
-
-/// Profiler state. This will likely need to be updated or reworked when adding new tracing backends.
-#[allow(missing_debug_implementations)]
-pub struct Profiler {
- #[cfg(feature = "chrome-trace")]
- /// [`FlushGuard`] must not be dropped until the application scope is dropped for accurate tracing.
- _guard: FlushGuard,
-}
-
-impl Profiler {
- /// Initializes the [`Profiler`].
- pub fn init() -> Self {
- // Registry stores the spans & generates unique span IDs
- let subscriber = Registry::default();
-
- let default_path = Path::new(env!("CARGO_MANIFEST_DIR"));
- let curr_exe = std::env::current_exe()
- .unwrap_or_else(|_| default_path.to_path_buf());
- let out_dir = curr_exe.parent().unwrap_or(default_path).join("traces");
-
- #[cfg(feature = "chrome-trace")]
- let (chrome_layer, guard) = {
- let mut layer = tracing_chrome::ChromeLayerBuilder::new();
-
- // Optional configurable env var: CHROME_TRACE_FILE=/path/to/trace_file/file.json,
- // for uploading to chrome://tracing (old) or ui.perfetto.dev (new).
- if let Ok(path) = std::env::var("CHROME_TRACE_FILE") {
- layer = layer.file(path);
- } else if std::fs::create_dir_all(&out_dir).is_ok() {
- let time = std::time::SystemTime::now()
- .duration_since(std::time::UNIX_EPOCH)
- .unwrap_or(Duration::from_millis(0))
- .as_millis();
-
- let curr_exe_name = curr_exe
- .file_name()
- .unwrap_or_else(|| OsStr::new("trace"))
- .to_str()
- .unwrap_or("trace");
-
- let path =
- out_dir.join(format!("{curr_exe_name}_trace_{time}.json"));
-
- layer = layer.file(path);
- } else {
- layer = layer.file(env!("CARGO_MANIFEST_DIR"))
- }
-
- let (chrome_layer, guard) = layer
- .name_fn(Box::new(|event_or_span| match event_or_span {
- tracing_chrome::EventOrSpan::Event(event) => {
- event.metadata().name().into()
- }
- tracing_chrome::EventOrSpan::Span(span) => {
- if let Some(fields) = span
- .extensions()
- .get::<FormattedFields<DefaultFields>>()
- {
- format!(
- "{}: {}",
- span.metadata().name(),
- fields.fields.as_str()
- )
- } else {
- span.metadata().name().into()
- }
- }
- }))
- .build();
-
- (chrome_layer, guard)
- };
-
- let fmt_layer = tracing_subscriber::fmt::Layer::default();
- let subscriber = subscriber.with(fmt_layer);
-
- #[cfg(feature = "chrome-trace")]
- let subscriber = subscriber.with(chrome_layer);
-
- // create dispatcher which will forward span events to the subscriber
- // this can only be set once or will panic
- tracing::subscriber::set_global_default(subscriber)
- .expect("Tracer could not set the global default subscriber.");
-
- Profiler {
- #[cfg(feature = "chrome-trace")]
- _guard: guard,
- }
- }
-}
diff --git a/winit/src/conversion.rs b/winit/src/conversion.rs
index 3ecd044c..7e51a2d4 100644
--- a/winit/src/conversion.rs
+++ b/winit/src/conversion.rs
@@ -6,11 +6,128 @@ use crate::core::keyboard;
use crate::core::mouse;
use crate::core::touch;
use crate::core::window;
-use crate::core::{Event, Point};
-use crate::Position;
+use crate::core::{Event, Point, Size};
+
+/// Converts some [`window::Settings`] into a `WindowBuilder` from `winit`.
+pub fn window_settings(
+ settings: window::Settings,
+ title: &str,
+ primary_monitor: Option<winit::monitor::MonitorHandle>,
+ _id: Option<String>,
+) -> winit::window::WindowBuilder {
+ let mut window_builder = winit::window::WindowBuilder::new();
+
+ window_builder = window_builder
+ .with_title(title)
+ .with_inner_size(winit::dpi::LogicalSize {
+ width: settings.size.width,
+ height: settings.size.height,
+ })
+ .with_resizable(settings.resizable)
+ .with_enabled_buttons(if settings.resizable {
+ winit::window::WindowButtons::all()
+ } else {
+ winit::window::WindowButtons::CLOSE
+ | winit::window::WindowButtons::MINIMIZE
+ })
+ .with_decorations(settings.decorations)
+ .with_transparent(settings.transparent)
+ .with_window_icon(settings.icon.and_then(icon))
+ .with_window_level(window_level(settings.level))
+ .with_visible(settings.visible);
+
+ if let Some(position) =
+ position(primary_monitor.as_ref(), settings.size, settings.position)
+ {
+ window_builder = window_builder.with_position(position);
+ }
+
+ if let Some(min_size) = settings.min_size {
+ window_builder =
+ window_builder.with_min_inner_size(winit::dpi::LogicalSize {
+ width: min_size.width,
+ height: min_size.height,
+ });
+ }
+
+ if let Some(max_size) = settings.max_size {
+ window_builder =
+ window_builder.with_max_inner_size(winit::dpi::LogicalSize {
+ width: max_size.width,
+ height: max_size.height,
+ });
+ }
+
+ #[cfg(any(
+ target_os = "dragonfly",
+ target_os = "freebsd",
+ target_os = "netbsd",
+ target_os = "openbsd"
+ ))]
+ {
+ // `with_name` is available on both `WindowBuilderExtWayland` and `WindowBuilderExtX11` and they do
+ // exactly the same thing. We arbitrarily choose `WindowBuilderExtWayland` here.
+ use ::winit::platform::wayland::WindowBuilderExtWayland;
+
+ if let Some(id) = _id {
+ window_builder = window_builder.with_name(id.clone(), id);
+ }
+ }
+
+ #[cfg(target_os = "windows")]
+ {
+ use winit::platform::windows::WindowBuilderExtWindows;
+ #[allow(unsafe_code)]
+ unsafe {
+ window_builder = window_builder
+ .with_parent_window(settings.platform_specific.parent);
+ }
+ window_builder = window_builder
+ .with_drag_and_drop(settings.platform_specific.drag_and_drop);
+ }
+
+ #[cfg(target_os = "macos")]
+ {
+ use winit::platform::macos::WindowBuilderExtMacOS;
+
+ window_builder = window_builder
+ .with_title_hidden(settings.platform_specific.title_hidden)
+ .with_titlebar_transparent(
+ settings.platform_specific.titlebar_transparent,
+ )
+ .with_fullsize_content_view(
+ settings.platform_specific.fullsize_content_view,
+ );
+ }
+
+ #[cfg(target_os = "linux")]
+ {
+ #[cfg(feature = "x11")]
+ {
+ use winit::platform::x11::WindowBuilderExtX11;
+
+ window_builder = window_builder.with_name(
+ &settings.platform_specific.application_id,
+ &settings.platform_specific.application_id,
+ );
+ }
+ #[cfg(feature = "wayland")]
+ {
+ use winit::platform::wayland::WindowBuilderExtWayland;
+
+ window_builder = window_builder.with_name(
+ &settings.platform_specific.application_id,
+ &settings.platform_specific.application_id,
+ );
+ }
+ }
+
+ window_builder
+}
/// Converts a winit window event into an iced event.
pub fn window_event(
+ id: window::Id,
event: &winit::event::WindowEvent<'_>,
scale_factor: f64,
modifiers: winit::event::ModifiersState,
@@ -21,21 +138,27 @@ pub fn window_event(
WindowEvent::Resized(new_size) => {
let logical_size = new_size.to_logical(scale_factor);
- Some(Event::Window(window::Event::Resized {
- width: logical_size.width,
- height: logical_size.height,
- }))
+ Some(Event::Window(
+ id,
+ window::Event::Resized {
+ width: logical_size.width,
+ height: logical_size.height,
+ },
+ ))
}
WindowEvent::ScaleFactorChanged { new_inner_size, .. } => {
let logical_size = new_inner_size.to_logical(scale_factor);
- Some(Event::Window(window::Event::Resized {
- width: logical_size.width,
- height: logical_size.height,
- }))
+ Some(Event::Window(
+ id,
+ window::Event::Resized {
+ width: logical_size.width,
+ height: logical_size.height,
+ },
+ ))
}
WindowEvent::CloseRequested => {
- Some(Event::Window(window::Event::CloseRequested))
+ Some(Event::Window(id, window::Event::CloseRequested))
}
WindowEvent::CursorMoved { position, .. } => {
let position = position.to_logical::<f64>(scale_factor);
@@ -113,19 +236,22 @@ pub fn window_event(
WindowEvent::ModifiersChanged(new_modifiers) => Some(Event::Keyboard(
keyboard::Event::ModifiersChanged(self::modifiers(*new_modifiers)),
)),
- WindowEvent::Focused(focused) => Some(Event::Window(if *focused {
- window::Event::Focused
- } else {
- window::Event::Unfocused
- })),
+ WindowEvent::Focused(focused) => Some(Event::Window(
+ id,
+ if *focused {
+ window::Event::Focused
+ } else {
+ window::Event::Unfocused
+ },
+ )),
WindowEvent::HoveredFile(path) => {
- Some(Event::Window(window::Event::FileHovered(path.clone())))
+ Some(Event::Window(id, window::Event::FileHovered(path.clone())))
}
WindowEvent::DroppedFile(path) => {
- Some(Event::Window(window::Event::FileDropped(path.clone())))
+ Some(Event::Window(id, window::Event::FileDropped(path.clone())))
}
WindowEvent::HoveredFileCancelled => {
- Some(Event::Window(window::Event::FilesHoveredLeft))
+ Some(Event::Window(id, window::Event::FilesHoveredLeft))
}
WindowEvent::Touch(touch) => {
Some(Event::Touch(touch_event(*touch, scale_factor)))
@@ -134,7 +260,7 @@ pub fn window_event(
let winit::dpi::LogicalPosition { x, y } =
position.to_logical(scale_factor);
- Some(Event::Window(window::Event::Moved { x, y }))
+ Some(Event::Window(id, window::Event::Moved { x, y }))
}
_ => None,
}
@@ -153,23 +279,23 @@ pub fn window_level(level: window::Level) -> winit::window::WindowLevel {
}
}
-/// Converts a [`Position`] to a [`winit`] logical position for a given monitor.
+/// Converts a [`window::Position`] to a [`winit`] logical position for a given monitor.
///
/// [`winit`]: https://github.com/rust-windowing/winit
pub fn position(
monitor: Option<&winit::monitor::MonitorHandle>,
- (width, height): (u32, u32),
- position: Position,
+ size: Size,
+ position: window::Position,
) -> Option<winit::dpi::Position> {
match position {
- Position::Default => None,
- Position::Specific(x, y) => {
+ window::Position::Default => None,
+ window::Position::Specific(position) => {
Some(winit::dpi::Position::Logical(winit::dpi::LogicalPosition {
- x: f64::from(x),
- y: f64::from(y),
+ x: f64::from(position.x),
+ y: f64::from(position.y),
}))
}
- Position::Centered => {
+ window::Position::Centered => {
if let Some(monitor) = monitor {
let start = monitor.position();
@@ -178,8 +304,8 @@ pub fn position(
let centered: winit::dpi::PhysicalPosition<i32> =
winit::dpi::LogicalPosition {
- x: (resolution.width - f64::from(width)) / 2.0,
- y: (resolution.height - f64::from(height)) / 2.0,
+ x: (resolution.width - f64::from(size.width)) / 2.0,
+ y: (resolution.height - f64::from(size.height)) / 2.0,
}
.to_physical(monitor.scale_factor());
diff --git a/winit/src/lib.rs b/winit/src/lib.rs
index 95b55bb9..948576a2 100644
--- a/winit/src/lib.rs
+++ b/winit/src/lib.rs
@@ -33,6 +33,9 @@ pub use iced_runtime::futures;
pub use iced_style as style;
pub use winit;
+#[cfg(feature = "multi-window")]
+pub mod multi_window;
+
#[cfg(feature = "application")]
pub mod application;
pub mod clipboard;
@@ -43,17 +46,11 @@ pub mod settings;
pub mod system;
mod error;
-mod position;
mod proxy;
#[cfg(feature = "application")]
pub use application::Application;
-#[cfg(feature = "trace")]
-pub use application::Profiler;
pub use clipboard::Clipboard;
pub use error::Error;
-pub use position::Position;
pub use proxy::Proxy;
pub use settings::Settings;
-
-pub use iced_graphics::Viewport;
diff --git a/winit/src/multi_window.rs b/winit/src/multi_window.rs
new file mode 100644
index 00000000..84651d40
--- /dev/null
+++ b/winit/src/multi_window.rs
@@ -0,0 +1,1212 @@
+//! Create interactive, native cross-platform applications for WGPU.
+mod state;
+mod window_manager;
+
+pub use state::State;
+
+use crate::conversion;
+use crate::core;
+use crate::core::renderer;
+use crate::core::widget::operation;
+use crate::core::window;
+use crate::core::Size;
+use crate::futures::futures::channel::mpsc;
+use crate::futures::futures::{task, Future, StreamExt};
+use crate::futures::{Executor, Runtime, Subscription};
+use crate::graphics::{compositor, Compositor};
+use crate::multi_window::window_manager::WindowManager;
+use crate::runtime::command::{self, Command};
+use crate::runtime::multi_window::Program;
+use crate::runtime::user_interface::{self, UserInterface};
+use crate::runtime::Debug;
+use crate::style::application::StyleSheet;
+use crate::{Clipboard, Error, Proxy, Settings};
+
+use std::collections::HashMap;
+use std::mem::ManuallyDrop;
+use std::time::Instant;
+
+/// An interactive, native, cross-platform, multi-windowed application.
+///
+/// This trait is the main entrypoint of multi-window Iced. Once implemented, you can run
+/// your GUI application by simply calling [`run`]. It will run in
+/// its own window.
+///
+/// An [`Application`] can execute asynchronous actions by returning a
+/// [`Command`] in some of its methods.
+///
+/// When using an [`Application`] with the `debug` feature enabled, a debug view
+/// can be toggled by pressing `F12`.
+pub trait Application: Program
+where
+ <Self::Renderer as core::Renderer>::Theme: StyleSheet,
+{
+ /// The data needed to initialize your [`Application`].
+ type Flags;
+
+ /// Initializes the [`Application`] with the flags provided to
+ /// [`run`] as part of the [`Settings`].
+ ///
+ /// Here is where you should return the initial state of your app.
+ ///
+ /// Additionally, you can return a [`Command`] if you need to perform some
+ /// async action in the background on startup. This is useful if you want to
+ /// load state from a file, perform an initial HTTP request, etc.
+ fn new(flags: Self::Flags) -> (Self, Command<Self::Message>);
+
+ /// Returns the current title of the [`Application`].
+ ///
+ /// This title can be dynamic! The runtime will automatically update the
+ /// title of your application when necessary.
+ fn title(&self, window: window::Id) -> String;
+
+ /// Returns the current `Theme` of the [`Application`].
+ fn theme(
+ &self,
+ window: window::Id,
+ ) -> <Self::Renderer as core::Renderer>::Theme;
+
+ /// Returns the `Style` variation of the `Theme`.
+ fn style(
+ &self,
+ ) -> <<Self::Renderer as core::Renderer>::Theme as StyleSheet>::Style {
+ Default::default()
+ }
+
+ /// Returns the event `Subscription` for the current state of the
+ /// application.
+ ///
+ /// The messages produced by the `Subscription` will be handled by
+ /// [`update`](#tymethod.update).
+ ///
+ /// A `Subscription` will be kept alive as long as you keep returning it!
+ ///
+ /// By default, it returns an empty subscription.
+ fn subscription(&self) -> Subscription<Self::Message> {
+ Subscription::none()
+ }
+
+ /// Returns the scale factor of the window of the [`Application`].
+ ///
+ /// It can be used to dynamically control the size of the UI at runtime
+ /// (i.e. zooming).
+ ///
+ /// For instance, a scale factor of `2.0` will make widgets twice as big,
+ /// while a scale factor of `0.5` will shrink them to half their size.
+ ///
+ /// By default, it returns `1.0`.
+ #[allow(unused_variables)]
+ fn scale_factor(&self, window: window::Id) -> f64 {
+ 1.0
+ }
+}
+
+/// Runs an [`Application`] with an executor, compositor, and the provided
+/// settings.
+pub fn run<A, E, C>(
+ settings: Settings<A::Flags>,
+ compositor_settings: C::Settings,
+) -> Result<(), Error>
+where
+ A: Application + 'static,
+ E: Executor + 'static,
+ C: Compositor<Renderer = A::Renderer> + 'static,
+ <A::Renderer as core::Renderer>::Theme: StyleSheet,
+{
+ use winit::event_loop::EventLoopBuilder;
+
+ let mut debug = Debug::new();
+ debug.startup_started();
+
+ let event_loop = EventLoopBuilder::with_user_event().build();
+ let proxy = event_loop.create_proxy();
+
+ let runtime = {
+ let proxy = Proxy::new(event_loop.create_proxy());
+ let executor = E::new().map_err(Error::ExecutorCreationFailed)?;
+
+ Runtime::new(executor, proxy)
+ };
+
+ let (application, init_command) = {
+ let flags = settings.flags;
+
+ runtime.enter(|| A::new(flags))
+ };
+
+ let should_main_be_visible = settings.window.visible;
+ let exit_on_close_request = settings.window.exit_on_close_request;
+
+ let builder = conversion::window_settings(
+ settings.window,
+ &application.title(window::Id::MAIN),
+ event_loop.primary_monitor(),
+ settings.id,
+ )
+ .with_visible(false);
+
+ log::info!("Window builder: {:#?}", builder);
+
+ let main_window = builder
+ .build(&event_loop)
+ .map_err(Error::WindowCreationFailed)?;
+
+ #[cfg(target_arch = "wasm32")]
+ {
+ use winit::platform::web::WindowExtWebSys;
+
+ let canvas = main_window.canvas();
+
+ let window = web_sys::window().unwrap();
+ let document = window.document().unwrap();
+ let body = document.body().unwrap();
+
+ let target = target.and_then(|target| {
+ body.query_selector(&format!("#{}", target))
+ .ok()
+ .unwrap_or(None)
+ });
+
+ match target {
+ Some(node) => {
+ let _ = node
+ .replace_with_with_node_1(&canvas)
+ .expect(&format!("Could not replace #{}", node.id()));
+ }
+ None => {
+ let _ = body
+ .append_child(&canvas)
+ .expect("Append canvas to HTML body");
+ }
+ };
+ }
+
+ let mut compositor = C::new(compositor_settings, Some(&main_window))?;
+
+ let mut window_manager = WindowManager::new();
+ let _ = window_manager.insert(
+ window::Id::MAIN,
+ main_window,
+ &application,
+ &mut compositor,
+ exit_on_close_request,
+ );
+
+ let (mut event_sender, event_receiver) = mpsc::unbounded();
+ let (control_sender, mut control_receiver) = mpsc::unbounded();
+
+ let mut instance = Box::pin(run_instance::<A, E, C>(
+ application,
+ compositor,
+ runtime,
+ proxy,
+ debug,
+ event_receiver,
+ control_sender,
+ init_command,
+ window_manager,
+ should_main_be_visible,
+ ));
+
+ let mut context = task::Context::from_waker(task::noop_waker_ref());
+
+ platform::run(event_loop, move |event, window_target, control_flow| {
+ use winit::event_loop::ControlFlow;
+
+ if let ControlFlow::ExitWithCode(_) = control_flow {
+ return;
+ }
+
+ let event = match event {
+ winit::event::Event::WindowEvent {
+ event:
+ winit::event::WindowEvent::ScaleFactorChanged {
+ new_inner_size,
+ ..
+ },
+ window_id,
+ } => Some(winit::event::Event::WindowEvent {
+ event: winit::event::WindowEvent::Resized(*new_inner_size),
+ window_id,
+ }),
+ _ => event.to_static(),
+ };
+
+ if let Some(event) = event {
+ event_sender
+ .start_send(Event::EventLoopAwakened(event))
+ .expect("Send event");
+
+ loop {
+ let poll = instance.as_mut().poll(&mut context);
+
+ match poll {
+ task::Poll::Pending => match control_receiver.try_next() {
+ Ok(Some(control)) => match control {
+ Control::ChangeFlow(flow) => {
+ *control_flow = flow;
+ }
+ Control::CreateWindow {
+ id,
+ settings,
+ title,
+ monitor,
+ } => {
+ let exit_on_close_request =
+ settings.exit_on_close_request;
+
+ let window = conversion::window_settings(
+ settings, &title, monitor, None,
+ )
+ .build(window_target)
+ .expect("Failed to build window");
+
+ event_sender
+ .start_send(Event::WindowCreated {
+ id,
+ window,
+ exit_on_close_request,
+ })
+ .expect("Send event");
+ }
+ },
+ _ => {
+ break;
+ }
+ },
+ task::Poll::Ready(_) => {
+ *control_flow = ControlFlow::Exit;
+ break;
+ }
+ };
+ }
+ }
+ })
+}
+
+enum Event<Message: 'static> {
+ WindowCreated {
+ id: window::Id,
+ window: winit::window::Window,
+ exit_on_close_request: bool,
+ },
+ EventLoopAwakened(winit::event::Event<'static, Message>),
+}
+
+enum Control {
+ ChangeFlow(winit::event_loop::ControlFlow),
+ CreateWindow {
+ id: window::Id,
+ settings: window::Settings,
+ title: String,
+ monitor: Option<winit::monitor::MonitorHandle>,
+ },
+}
+
+async fn run_instance<A, E, C>(
+ mut application: A,
+ mut compositor: C,
+ mut runtime: Runtime<E, Proxy<A::Message>, A::Message>,
+ mut proxy: winit::event_loop::EventLoopProxy<A::Message>,
+ mut debug: Debug,
+ mut event_receiver: mpsc::UnboundedReceiver<Event<A::Message>>,
+ mut control_sender: mpsc::UnboundedSender<Control>,
+ init_command: Command<A::Message>,
+ mut window_manager: WindowManager<A, C>,
+ should_main_window_be_visible: bool,
+) where
+ A: Application + 'static,
+ E: Executor + 'static,
+ C: Compositor<Renderer = A::Renderer> + 'static,
+ <A::Renderer as core::Renderer>::Theme: StyleSheet,
+{
+ use winit::event;
+ use winit::event_loop::ControlFlow;
+
+ let main_window = window_manager
+ .get_mut(window::Id::MAIN)
+ .expect("Get main window");
+
+ if should_main_window_be_visible {
+ main_window.raw.set_visible(true);
+ }
+
+ let mut clipboard = Clipboard::connect(&main_window.raw);
+ let mut events = {
+ vec![(
+ Some(window::Id::MAIN),
+ core::Event::Window(
+ window::Id::MAIN,
+ window::Event::Opened {
+ position: main_window.position(),
+ size: main_window.size(),
+ },
+ ),
+ )]
+ };
+
+ let mut ui_caches = HashMap::new();
+ let mut user_interfaces = ManuallyDrop::new(build_user_interfaces(
+ &application,
+ &mut debug,
+ &mut window_manager,
+ HashMap::from_iter([(
+ window::Id::MAIN,
+ user_interface::Cache::default(),
+ )]),
+ ));
+
+ run_command(
+ &application,
+ &mut compositor,
+ init_command,
+ &mut runtime,
+ &mut clipboard,
+ &mut control_sender,
+ &mut proxy,
+ &mut debug,
+ &mut window_manager,
+ &mut ui_caches,
+ );
+
+ runtime.track(application.subscription().into_recipes());
+
+ let mut messages = Vec::new();
+ let mut redraw_pending = false;
+
+ debug.startup_finished();
+
+ 'main: while let Some(event) = event_receiver.next().await {
+ match event {
+ Event::WindowCreated {
+ id,
+ window,
+ exit_on_close_request,
+ } => {
+ let window = window_manager.insert(
+ id,
+ window,
+ &application,
+ &mut compositor,
+ exit_on_close_request,
+ );
+
+ let logical_size = window.state.logical_size();
+
+ let _ = user_interfaces.insert(
+ id,
+ build_user_interface(
+ &application,
+ user_interface::Cache::default(),
+ &mut window.renderer,
+ logical_size,
+ &mut debug,
+ id,
+ ),
+ );
+ let _ = ui_caches.insert(id, user_interface::Cache::default());
+
+ events.push((
+ Some(id),
+ core::Event::Window(
+ id,
+ window::Event::Opened {
+ position: window.position(),
+ size: window.size(),
+ },
+ ),
+ ));
+ }
+ Event::EventLoopAwakened(event) => {
+ match event {
+ event::Event::NewEvents(start_cause) => {
+ redraw_pending = matches!(
+ start_cause,
+ event::StartCause::Init
+ | event::StartCause::Poll
+ | event::StartCause::ResumeTimeReached { .. }
+ );
+ }
+ event::Event::MainEventsCleared => {
+ debug.event_processing_started();
+ let mut uis_stale = false;
+
+ for (id, window) in window_manager.iter_mut() {
+ let mut window_events = vec![];
+
+ events.retain(|(window_id, event)| {
+ if *window_id == Some(id) || window_id.is_none()
+ {
+ window_events.push(event.clone());
+ false
+ } else {
+ true
+ }
+ });
+
+ if !redraw_pending
+ && window_events.is_empty()
+ && messages.is_empty()
+ {
+ continue;
+ }
+
+ let (ui_state, statuses) = user_interfaces
+ .get_mut(&id)
+ .expect("Get user interface")
+ .update(
+ &window_events,
+ window.state.cursor(),
+ &mut window.renderer,
+ &mut clipboard,
+ &mut messages,
+ );
+
+ if !uis_stale {
+ uis_stale = matches!(
+ ui_state,
+ user_interface::State::Outdated
+ );
+ }
+
+ for (event, status) in window_events
+ .into_iter()
+ .zip(statuses.into_iter())
+ {
+ runtime.broadcast(event, status);
+ }
+ }
+
+ debug.event_processing_finished();
+
+ // TODO mw application update returns which window IDs to update
+ if !messages.is_empty() || uis_stale {
+ let mut cached_interfaces: HashMap<
+ window::Id,
+ user_interface::Cache,
+ > = ManuallyDrop::into_inner(user_interfaces)
+ .drain()
+ .map(|(id, ui)| (id, ui.into_cache()))
+ .collect();
+
+ // Update application
+ update(
+ &mut application,
+ &mut compositor,
+ &mut runtime,
+ &mut clipboard,
+ &mut control_sender,
+ &mut proxy,
+ &mut debug,
+ &mut messages,
+ &mut window_manager,
+ &mut cached_interfaces,
+ );
+
+ // we must synchronize all window states with application state after an
+ // application update since we don't know what changed
+ for (id, window) in window_manager.iter_mut() {
+ window.state.synchronize(
+ &application,
+ id,
+ &window.raw,
+ );
+ }
+
+ // rebuild UIs with the synchronized states
+ user_interfaces =
+ ManuallyDrop::new(build_user_interfaces(
+ &application,
+ &mut debug,
+ &mut window_manager,
+ cached_interfaces,
+ ));
+ }
+
+ debug.draw_started();
+
+ for (id, window) in window_manager.iter_mut() {
+ // TODO: Avoid redrawing all the time by forcing widgets to
+ // request redraws on state changes
+ //
+ // Then, we can use the `interface_state` here to decide if a redraw
+ // is needed right away, or simply wait until a specific time.
+ let redraw_event = core::Event::Window(
+ id,
+ window::Event::RedrawRequested(Instant::now()),
+ );
+
+ let cursor = window.state.cursor();
+
+ let ui = user_interfaces
+ .get_mut(&id)
+ .expect("Get user interface");
+
+ let (ui_state, _) = ui.update(
+ &[redraw_event.clone()],
+ cursor,
+ &mut window.renderer,
+ &mut clipboard,
+ &mut messages,
+ );
+
+ let new_mouse_interaction = {
+ let state = &window.state;
+
+ ui.draw(
+ &mut window.renderer,
+ state.theme(),
+ &renderer::Style {
+ text_color: state.text_color(),
+ },
+ cursor,
+ )
+ };
+
+ if new_mouse_interaction != window.mouse_interaction
+ {
+ window.raw.set_cursor_icon(
+ conversion::mouse_interaction(
+ new_mouse_interaction,
+ ),
+ );
+
+ window.mouse_interaction =
+ new_mouse_interaction;
+ }
+
+ // TODO once widgets can request to be redrawn, we can avoid always requesting a
+ // redraw
+ window.raw.request_redraw();
+
+ runtime.broadcast(
+ redraw_event.clone(),
+ core::event::Status::Ignored,
+ );
+
+ let _ = control_sender.start_send(
+ Control::ChangeFlow(match ui_state {
+ user_interface::State::Updated {
+ redraw_request: Some(redraw_request),
+ } => match redraw_request {
+ window::RedrawRequest::NextFrame => {
+ ControlFlow::Poll
+ }
+ window::RedrawRequest::At(at) => {
+ ControlFlow::WaitUntil(at)
+ }
+ },
+ _ => ControlFlow::Wait,
+ }),
+ );
+ }
+
+ redraw_pending = false;
+
+ debug.draw_finished();
+ }
+ event::Event::PlatformSpecific(
+ event::PlatformSpecific::MacOS(
+ event::MacOS::ReceivedUrl(url),
+ ),
+ ) => {
+ use crate::core::event;
+
+ events.push((
+ None,
+ event::Event::PlatformSpecific(
+ event::PlatformSpecific::MacOS(
+ event::MacOS::ReceivedUrl(url),
+ ),
+ ),
+ ));
+ }
+ event::Event::UserEvent(message) => {
+ messages.push(message);
+ }
+ event::Event::RedrawRequested(id) => {
+ let Some((id, window)) =
+ window_manager.get_mut_alias(id)
+ else {
+ continue;
+ };
+
+ let physical_size = window.state.physical_size();
+
+ if physical_size.width == 0 || physical_size.height == 0
+ {
+ continue;
+ }
+
+ debug.render_started();
+ if window.viewport_version
+ != window.state.viewport_version()
+ {
+ let logical_size = window.state.logical_size();
+
+ debug.layout_started();
+
+ let ui = user_interfaces
+ .remove(&id)
+ .expect("Remove user interface");
+
+ let _ = user_interfaces.insert(
+ id,
+ ui.relayout(logical_size, &mut window.renderer),
+ );
+
+ debug.layout_finished();
+
+ debug.draw_started();
+ let new_mouse_interaction = user_interfaces
+ .get_mut(&id)
+ .expect("Get user interface")
+ .draw(
+ &mut window.renderer,
+ window.state.theme(),
+ &renderer::Style {
+ text_color: window.state.text_color(),
+ },
+ window.state.cursor(),
+ );
+
+ if new_mouse_interaction != window.mouse_interaction
+ {
+ window.raw.set_cursor_icon(
+ conversion::mouse_interaction(
+ new_mouse_interaction,
+ ),
+ );
+
+ window.mouse_interaction =
+ new_mouse_interaction;
+ }
+ debug.draw_finished();
+
+ compositor.configure_surface(
+ &mut window.surface,
+ physical_size.width,
+ physical_size.height,
+ );
+
+ window.viewport_version =
+ window.state.viewport_version();
+ }
+
+ match compositor.present(
+ &mut window.renderer,
+ &mut window.surface,
+ window.state.viewport(),
+ window.state.background_color(),
+ &debug.overlay(),
+ ) {
+ Ok(()) => {
+ debug.render_finished();
+
+ // TODO: Handle animations!
+ // Maybe we can use `ControlFlow::WaitUntil` for this.
+ }
+ Err(error) => match error {
+ // This is an unrecoverable error.
+ compositor::SurfaceError::OutOfMemory => {
+ panic!("{:?}", error);
+ }
+ _ => {
+ debug.render_finished();
+ log::error!(
+ "Error {error:?} when presenting surface."
+ );
+
+ // Try rendering all windows again next frame.
+ for (_id, window) in
+ window_manager.iter_mut()
+ {
+ window.raw.request_redraw();
+ }
+ }
+ },
+ }
+ }
+ event::Event::WindowEvent {
+ event: window_event,
+ window_id,
+ } => {
+ let Some((id, window)) =
+ window_manager.get_mut_alias(window_id)
+ else {
+ continue;
+ };
+
+ if matches!(
+ window_event,
+ winit::event::WindowEvent::CloseRequested
+ ) && window.exit_on_close_request
+ {
+ let _ = window_manager.remove(id);
+ let _ = user_interfaces.remove(&id);
+ let _ = ui_caches.remove(&id);
+
+ events.push((
+ None,
+ core::Event::Window(id, window::Event::Closed),
+ ));
+
+ if window_manager.is_empty() {
+ break 'main;
+ }
+ } else {
+ window.state.update(
+ &window.raw,
+ &window_event,
+ &mut debug,
+ );
+
+ if let Some(event) = conversion::window_event(
+ id,
+ &window_event,
+ window.state.scale_factor(),
+ window.state.modifiers(),
+ ) {
+ events.push((Some(id), event));
+ }
+ }
+ }
+ _ => {}
+ }
+ }
+ }
+ }
+
+ let _ = ManuallyDrop::into_inner(user_interfaces);
+}
+
+/// Builds a window's [`UserInterface`] for the [`Application`].
+fn build_user_interface<'a, A: Application>(
+ application: &'a A,
+ cache: user_interface::Cache,
+ renderer: &mut A::Renderer,
+ size: Size,
+ debug: &mut Debug,
+ id: window::Id,
+) -> UserInterface<'a, A::Message, A::Renderer>
+where
+ <A::Renderer as core::Renderer>::Theme: StyleSheet,
+{
+ debug.view_started();
+ let view = application.view(id);
+ debug.view_finished();
+
+ debug.layout_started();
+ let user_interface = UserInterface::build(view, size, cache, renderer);
+ debug.layout_finished();
+
+ user_interface
+}
+
+/// Updates a multi-window [`Application`] by feeding it messages, spawning any
+/// resulting [`Command`], and tracking its [`Subscription`].
+fn update<A: Application, C, E: Executor>(
+ application: &mut A,
+ compositor: &mut C,
+ runtime: &mut Runtime<E, Proxy<A::Message>, A::Message>,
+ clipboard: &mut Clipboard,
+ control_sender: &mut mpsc::UnboundedSender<Control>,
+ proxy: &mut winit::event_loop::EventLoopProxy<A::Message>,
+ debug: &mut Debug,
+ messages: &mut Vec<A::Message>,
+ window_manager: &mut WindowManager<A, C>,
+ ui_caches: &mut HashMap<window::Id, user_interface::Cache>,
+) where
+ C: Compositor<Renderer = A::Renderer> + 'static,
+ <A::Renderer as core::Renderer>::Theme: StyleSheet,
+{
+ for message in messages.drain(..) {
+ debug.log_message(&message);
+ debug.update_started();
+
+ let command = runtime.enter(|| application.update(message));
+ debug.update_finished();
+
+ run_command(
+ application,
+ compositor,
+ command,
+ runtime,
+ clipboard,
+ control_sender,
+ proxy,
+ debug,
+ window_manager,
+ ui_caches,
+ );
+ }
+
+ let subscription = application.subscription();
+ runtime.track(subscription.into_recipes());
+}
+
+/// Runs the actions of a [`Command`].
+fn run_command<A, C, E>(
+ application: &A,
+ compositor: &mut C,
+ command: Command<A::Message>,
+ runtime: &mut Runtime<E, Proxy<A::Message>, A::Message>,
+ clipboard: &mut Clipboard,
+ control_sender: &mut mpsc::UnboundedSender<Control>,
+ proxy: &mut winit::event_loop::EventLoopProxy<A::Message>,
+ debug: &mut Debug,
+ window_manager: &mut WindowManager<A, C>,
+ ui_caches: &mut HashMap<window::Id, user_interface::Cache>,
+) where
+ A: Application,
+ E: Executor,
+ C: Compositor<Renderer = A::Renderer> + 'static,
+ <A::Renderer as core::Renderer>::Theme: StyleSheet,
+{
+ use crate::runtime::clipboard;
+ use crate::runtime::system;
+ use crate::runtime::window;
+
+ for action in command.actions() {
+ match action {
+ command::Action::Future(future) => {
+ runtime.spawn(Box::pin(future));
+ }
+ command::Action::Stream(stream) => {
+ runtime.run(Box::pin(stream));
+ }
+ command::Action::Clipboard(action) => match action {
+ clipboard::Action::Read(tag) => {
+ let message = tag(clipboard.read());
+
+ proxy
+ .send_event(message)
+ .expect("Send message to event loop");
+ }
+ clipboard::Action::Write(contents) => {
+ clipboard.write(contents);
+ }
+ },
+ command::Action::Window(action) => match action {
+ window::Action::Spawn(id, settings) => {
+ let monitor = window_manager.last_monitor();
+
+ control_sender
+ .start_send(Control::CreateWindow {
+ id,
+ settings,
+ title: application.title(id),
+ monitor,
+ })
+ .expect("Send control action");
+ }
+ window::Action::Close(id) => {
+ use winit::event_loop::ControlFlow;
+
+ let _ = window_manager.remove(id);
+ let _ = ui_caches.remove(&id);
+
+ if window_manager.is_empty() {
+ control_sender
+ .start_send(Control::ChangeFlow(
+ ControlFlow::ExitWithCode(0),
+ ))
+ .expect("Send control action");
+ }
+ }
+ window::Action::Drag(id) => {
+ if let Some(window) = window_manager.get_mut(id) {
+ let _ = window.raw.drag_window();
+ }
+ }
+ window::Action::Resize(id, size) => {
+ if let Some(window) = window_manager.get_mut(id) {
+ window.raw.set_inner_size(winit::dpi::LogicalSize {
+ width: size.width,
+ height: size.height,
+ });
+ }
+ }
+ window::Action::FetchSize(id, callback) => {
+ if let Some(window) = window_manager.get_mut(id) {
+ let size = window
+ .raw
+ .inner_size()
+ .to_logical(window.raw.scale_factor());
+
+ proxy
+ .send_event(callback(Size::new(
+ size.width,
+ size.height,
+ )))
+ .expect("Send message to event loop");
+ }
+ }
+ window::Action::Maximize(id, maximized) => {
+ if let Some(window) = window_manager.get_mut(id) {
+ window.raw.set_maximized(maximized);
+ }
+ }
+ window::Action::Minimize(id, minimized) => {
+ if let Some(window) = window_manager.get_mut(id) {
+ window.raw.set_minimized(minimized);
+ }
+ }
+ window::Action::Move(id, position) => {
+ if let Some(window) = window_manager.get_mut(id) {
+ window.raw.set_outer_position(
+ winit::dpi::LogicalPosition {
+ x: position.x,
+ y: position.y,
+ },
+ );
+ }
+ }
+ window::Action::ChangeMode(id, mode) => {
+ if let Some(window) = window_manager.get_mut(id) {
+ window.raw.set_visible(conversion::visible(mode));
+ window.raw.set_fullscreen(conversion::fullscreen(
+ window.raw.current_monitor(),
+ mode,
+ ));
+ }
+ }
+ window::Action::ChangeIcon(id, icon) => {
+ if let Some(window) = window_manager.get_mut(id) {
+ window.raw.set_window_icon(conversion::icon(icon));
+ }
+ }
+ window::Action::FetchMode(id, tag) => {
+ if let Some(window) = window_manager.get_mut(id) {
+ let mode = if window.raw.is_visible().unwrap_or(true) {
+ conversion::mode(window.raw.fullscreen())
+ } else {
+ core::window::Mode::Hidden
+ };
+
+ proxy
+ .send_event(tag(mode))
+ .expect("Event loop doesn't exist.");
+ }
+ }
+ window::Action::ToggleMaximize(id) => {
+ if let Some(window) = window_manager.get_mut(id) {
+ window.raw.set_maximized(!window.raw.is_maximized());
+ }
+ }
+ window::Action::ToggleDecorations(id) => {
+ if let Some(window) = window_manager.get_mut(id) {
+ window.raw.set_decorations(!window.raw.is_decorated());
+ }
+ }
+ window::Action::RequestUserAttention(id, attention_type) => {
+ if let Some(window) = window_manager.get_mut(id) {
+ window.raw.request_user_attention(
+ attention_type.map(conversion::user_attention),
+ );
+ }
+ }
+ window::Action::GainFocus(id) => {
+ if let Some(window) = window_manager.get_mut(id) {
+ window.raw.focus_window();
+ }
+ }
+ window::Action::ChangeLevel(id, level) => {
+ if let Some(window) = window_manager.get_mut(id) {
+ window
+ .raw
+ .set_window_level(conversion::window_level(level));
+ }
+ }
+ window::Action::FetchId(id, tag) => {
+ if let Some(window) = window_manager.get_mut(id) {
+ proxy
+ .send_event(tag(window.raw.id().into()))
+ .expect("Event loop doesn't exist.");
+ }
+ }
+ window::Action::Screenshot(id, tag) => {
+ if let Some(window) = window_manager.get_mut(id) {
+ let bytes = compositor.screenshot(
+ &mut window.renderer,
+ &mut window.surface,
+ window.state.viewport(),
+ window.state.background_color(),
+ &debug.overlay(),
+ );
+
+ proxy
+ .send_event(tag(window::Screenshot::new(
+ bytes,
+ window.state.physical_size(),
+ )))
+ .expect("Event loop doesn't exist.");
+ }
+ }
+ },
+ command::Action::System(action) => match action {
+ system::Action::QueryInformation(_tag) => {
+ #[cfg(feature = "system")]
+ {
+ let graphics_info = compositor.fetch_information();
+ let proxy = proxy.clone();
+
+ let _ = std::thread::spawn(move || {
+ let information =
+ crate::system::information(graphics_info);
+
+ let message = _tag(information);
+
+ proxy
+ .send_event(message)
+ .expect("Event loop doesn't exist.");
+ });
+ }
+ }
+ },
+ command::Action::Widget(action) => {
+ let mut current_operation = Some(action);
+
+ let mut uis = build_user_interfaces(
+ application,
+ debug,
+ window_manager,
+ std::mem::take(ui_caches),
+ );
+
+ 'operate: while let Some(mut operation) =
+ current_operation.take()
+ {
+ for (id, ui) in uis.iter_mut() {
+ if let Some(window) = window_manager.get_mut(*id) {
+ ui.operate(&window.renderer, operation.as_mut());
+
+ match operation.finish() {
+ operation::Outcome::None => {}
+ operation::Outcome::Some(message) => {
+ proxy
+ .send_event(message)
+ .expect("Event loop doesn't exist.");
+
+ // operation completed, don't need to try to operate on rest of UIs
+ break 'operate;
+ }
+ operation::Outcome::Chain(next) => {
+ current_operation = Some(next);
+ }
+ }
+ }
+ }
+ }
+
+ *ui_caches =
+ uis.drain().map(|(id, ui)| (id, ui.into_cache())).collect();
+ }
+ command::Action::LoadFont { bytes, tagger } => {
+ use crate::core::text::Renderer;
+
+ // TODO change this once we change each renderer to having a single backend reference.. :pain:
+ // TODO: Error handling (?)
+ for (_, window) in window_manager.iter_mut() {
+ window.renderer.load_font(bytes.clone());
+ }
+
+ proxy
+ .send_event(tagger(Ok(())))
+ .expect("Send message to event loop");
+ }
+ }
+ }
+}
+
+/// Build the user interface for every window.
+pub fn build_user_interfaces<'a, A: Application, C: Compositor>(
+ application: &'a A,
+ debug: &mut Debug,
+ window_manager: &mut WindowManager<A, C>,
+ mut cached_user_interfaces: HashMap<window::Id, user_interface::Cache>,
+) -> HashMap<window::Id, UserInterface<'a, A::Message, A::Renderer>>
+where
+ <A::Renderer as core::Renderer>::Theme: StyleSheet,
+ C: Compositor<Renderer = A::Renderer>,
+{
+ cached_user_interfaces
+ .drain()
+ .filter_map(|(id, cache)| {
+ let window = window_manager.get_mut(id)?;
+
+ Some((
+ id,
+ build_user_interface(
+ application,
+ cache,
+ &mut window.renderer,
+ window.state.logical_size(),
+ debug,
+ id,
+ ),
+ ))
+ })
+ .collect()
+}
+
+/// Returns true if the provided event should cause an [`Application`] to
+/// exit.
+pub fn user_force_quit(
+ event: &winit::event::WindowEvent<'_>,
+ _modifiers: winit::event::ModifiersState,
+) -> bool {
+ match event {
+ #[cfg(target_os = "macos")]
+ winit::event::WindowEvent::KeyboardInput {
+ input:
+ winit::event::KeyboardInput {
+ virtual_keycode: Some(winit::event::VirtualKeyCode::Q),
+ state: winit::event::ElementState::Pressed,
+ ..
+ },
+ ..
+ } if _modifiers.logo() => true,
+ _ => false,
+ }
+}
+
+#[cfg(not(target_arch = "wasm32"))]
+mod platform {
+ pub fn run<T, F>(
+ mut event_loop: winit::event_loop::EventLoop<T>,
+ event_handler: F,
+ ) -> Result<(), super::Error>
+ where
+ F: 'static
+ + FnMut(
+ winit::event::Event<'_, T>,
+ &winit::event_loop::EventLoopWindowTarget<T>,
+ &mut winit::event_loop::ControlFlow,
+ ),
+ {
+ use winit::platform::run_return::EventLoopExtRunReturn;
+
+ let _ = event_loop.run_return(event_handler);
+
+ Ok(())
+ }
+}
+
+#[cfg(target_arch = "wasm32")]
+mod platform {
+ pub fn run<T, F>(
+ event_loop: winit::event_loop::EventLoop<T>,
+ event_handler: F,
+ ) -> !
+ where
+ F: 'static
+ + FnMut(
+ winit::event::Event<'_, T>,
+ &winit::event_loop::EventLoopWindowTarget<T>,
+ &mut winit::event_loop::ControlFlow,
+ ),
+ {
+ event_loop.run(event_handler)
+ }
+}
diff --git a/winit/src/multi_window/state.rs b/winit/src/multi_window/state.rs
new file mode 100644
index 00000000..03da5ad7
--- /dev/null
+++ b/winit/src/multi_window/state.rs
@@ -0,0 +1,240 @@
+use crate::conversion;
+use crate::core;
+use crate::core::{mouse, window};
+use crate::core::{Color, Size};
+use crate::graphics::Viewport;
+use crate::multi_window::Application;
+use crate::style::application;
+use std::fmt::{Debug, Formatter};
+
+use iced_style::application::StyleSheet;
+use winit::event::{Touch, WindowEvent};
+use winit::window::Window;
+
+/// The state of a multi-windowed [`Application`].
+pub struct State<A: Application>
+where
+ <A::Renderer as core::Renderer>::Theme: application::StyleSheet,
+{
+ title: String,
+ scale_factor: f64,
+ viewport: Viewport,
+ viewport_version: u64,
+ cursor_position: Option<winit::dpi::PhysicalPosition<f64>>,
+ modifiers: winit::event::ModifiersState,
+ theme: <A::Renderer as core::Renderer>::Theme,
+ appearance: application::Appearance,
+}
+
+impl<A: Application> Debug for State<A>
+where
+ <A::Renderer as core::Renderer>::Theme: application::StyleSheet,
+{
+ fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
+ f.debug_struct("multi_window::State")
+ .field("title", &self.title)
+ .field("scale_factor", &self.scale_factor)
+ .field("viewport", &self.viewport)
+ .field("viewport_version", &self.viewport_version)
+ .field("cursor_position", &self.cursor_position)
+ .field("appearance", &self.appearance)
+ .finish()
+ }
+}
+
+impl<A: Application> State<A>
+where
+ <A::Renderer as core::Renderer>::Theme: application::StyleSheet,
+{
+ /// Creates a new [`State`] for the provided [`Application`]'s `window`.
+ pub fn new(
+ application: &A,
+ window_id: window::Id,
+ window: &Window,
+ ) -> Self {
+ let title = application.title(window_id);
+ let scale_factor = application.scale_factor(window_id);
+ let theme = application.theme(window_id);
+ let appearance = theme.appearance(&application.style());
+
+ let viewport = {
+ let physical_size = window.inner_size();
+
+ Viewport::with_physical_size(
+ Size::new(physical_size.width, physical_size.height),
+ window.scale_factor() * scale_factor,
+ )
+ };
+
+ Self {
+ title,
+ scale_factor,
+ viewport,
+ viewport_version: 0,
+ cursor_position: None,
+ modifiers: winit::event::ModifiersState::default(),
+ theme,
+ appearance,
+ }
+ }
+
+ /// Returns the current [`Viewport`] of the [`State`].
+ pub fn viewport(&self) -> &Viewport {
+ &self.viewport
+ }
+
+ /// Returns the version of the [`Viewport`] of the [`State`].
+ ///
+ /// The version is incremented every time the [`Viewport`] changes.
+ pub fn viewport_version(&self) -> u64 {
+ self.viewport_version
+ }
+
+ /// Returns the physical [`Size`] of the [`Viewport`] of the [`State`].
+ pub fn physical_size(&self) -> Size<u32> {
+ self.viewport.physical_size()
+ }
+
+ /// Returns the logical [`Size`] of the [`Viewport`] of the [`State`].
+ pub fn logical_size(&self) -> Size<f32> {
+ self.viewport.logical_size()
+ }
+
+ /// Returns the current scale factor of the [`Viewport`] of the [`State`].
+ pub fn scale_factor(&self) -> f64 {
+ self.viewport.scale_factor()
+ }
+
+ /// Returns the current cursor position of the [`State`].
+ pub fn cursor(&self) -> mouse::Cursor {
+ self.cursor_position
+ .map(|cursor_position| {
+ conversion::cursor_position(
+ cursor_position,
+ self.viewport.scale_factor(),
+ )
+ })
+ .map(mouse::Cursor::Available)
+ .unwrap_or(mouse::Cursor::Unavailable)
+ }
+
+ /// Returns the current keyboard modifiers of the [`State`].
+ pub fn modifiers(&self) -> winit::event::ModifiersState {
+ self.modifiers
+ }
+
+ /// Returns the current theme of the [`State`].
+ pub fn theme(&self) -> &<A::Renderer as core::Renderer>::Theme {
+ &self.theme
+ }
+
+ /// Returns the current background [`Color`] of the [`State`].
+ pub fn background_color(&self) -> Color {
+ self.appearance.background_color
+ }
+
+ /// Returns the current text [`Color`] of the [`State`].
+ pub fn text_color(&self) -> Color {
+ self.appearance.text_color
+ }
+
+ /// Processes the provided window event and updates the [`State`] accordingly.
+ pub fn update(
+ &mut self,
+ window: &Window,
+ event: &WindowEvent<'_>,
+ _debug: &mut crate::runtime::Debug,
+ ) {
+ match event {
+ WindowEvent::Resized(new_size) => {
+ let size = Size::new(new_size.width, new_size.height);
+
+ self.viewport = Viewport::with_physical_size(
+ size,
+ window.scale_factor() * self.scale_factor,
+ );
+
+ self.viewport_version = self.viewport_version.wrapping_add(1);
+ }
+ WindowEvent::ScaleFactorChanged {
+ scale_factor: new_scale_factor,
+ new_inner_size,
+ } => {
+ let size =
+ Size::new(new_inner_size.width, new_inner_size.height);
+
+ self.viewport = Viewport::with_physical_size(
+ size,
+ new_scale_factor * self.scale_factor,
+ );
+
+ self.viewport_version = self.viewport_version.wrapping_add(1);
+ }
+ WindowEvent::CursorMoved { position, .. }
+ | WindowEvent::Touch(Touch {
+ location: position, ..
+ }) => {
+ self.cursor_position = Some(*position);
+ }
+ WindowEvent::CursorLeft { .. } => {
+ self.cursor_position = None;
+ }
+ WindowEvent::ModifiersChanged(new_modifiers) => {
+ self.modifiers = *new_modifiers;
+ }
+ #[cfg(feature = "debug")]
+ WindowEvent::KeyboardInput {
+ input:
+ winit::event::KeyboardInput {
+ virtual_keycode: Some(winit::event::VirtualKeyCode::F12),
+ state: winit::event::ElementState::Pressed,
+ ..
+ },
+ ..
+ } => _debug.toggle(),
+ _ => {}
+ }
+ }
+
+ /// Synchronizes the [`State`] with its [`Application`] and its respective
+ /// window.
+ ///
+ /// Normally, an [`Application`] should be synchronized with its [`State`]
+ /// and window after calling [`State::update`].
+ pub fn synchronize(
+ &mut self,
+ application: &A,
+ window_id: window::Id,
+ window: &Window,
+ ) {
+ // Update window title
+ let new_title = application.title(window_id);
+
+ if self.title != new_title {
+ window.set_title(&new_title);
+ self.title = new_title;
+ }
+
+ // Update scale factor and size
+ let new_scale_factor = application.scale_factor(window_id);
+ let new_size = window.inner_size();
+ let current_size = self.viewport.physical_size();
+
+ if self.scale_factor != new_scale_factor
+ || (current_size.width, current_size.height)
+ != (new_size.width, new_size.height)
+ {
+ self.viewport = Viewport::with_physical_size(
+ Size::new(new_size.width, new_size.height),
+ window.scale_factor() * new_scale_factor,
+ );
+ self.viewport_version = self.viewport_version.wrapping_add(1);
+
+ self.scale_factor = new_scale_factor;
+ }
+
+ // Update theme and appearance
+ self.theme = application.theme(window_id);
+ self.appearance = self.theme.appearance(&application.style());
+ }
+}
diff --git a/winit/src/multi_window/window_manager.rs b/winit/src/multi_window/window_manager.rs
new file mode 100644
index 00000000..d54156e7
--- /dev/null
+++ b/winit/src/multi_window/window_manager.rs
@@ -0,0 +1,156 @@
+use crate::core::mouse;
+use crate::core::window::Id;
+use crate::core::{Point, Size};
+use crate::graphics::Compositor;
+use crate::multi_window::{Application, State};
+use crate::style::application::StyleSheet;
+
+use std::collections::BTreeMap;
+use winit::monitor::MonitorHandle;
+
+#[allow(missing_debug_implementations)]
+pub struct WindowManager<A: Application, C: Compositor>
+where
+ <A::Renderer as crate::core::Renderer>::Theme: StyleSheet,
+ C: Compositor<Renderer = A::Renderer>,
+{
+ aliases: BTreeMap<winit::window::WindowId, Id>,
+ entries: BTreeMap<Id, Window<A, C>>,
+}
+
+impl<A, C> WindowManager<A, C>
+where
+ A: Application,
+ C: Compositor<Renderer = A::Renderer>,
+ <A::Renderer as crate::core::Renderer>::Theme: StyleSheet,
+{
+ pub fn new() -> Self {
+ Self {
+ aliases: BTreeMap::new(),
+ entries: BTreeMap::new(),
+ }
+ }
+
+ pub fn insert(
+ &mut self,
+ id: Id,
+ window: winit::window::Window,
+ application: &A,
+ compositor: &mut C,
+ exit_on_close_request: bool,
+ ) -> &mut Window<A, C> {
+ let state = State::new(application, id, &window);
+ let viewport_version = state.viewport_version();
+ let physical_size = state.physical_size();
+ let surface = compositor.create_surface(
+ &window,
+ physical_size.width,
+ physical_size.height,
+ );
+ let renderer = compositor.create_renderer();
+
+ let _ = self.aliases.insert(window.id(), id);
+
+ let _ = self.entries.insert(
+ id,
+ Window {
+ raw: window,
+ state,
+ viewport_version,
+ exit_on_close_request,
+ surface,
+ renderer,
+ mouse_interaction: mouse::Interaction::Idle,
+ },
+ );
+
+ self.entries
+ .get_mut(&id)
+ .expect("Get window that was just inserted")
+ }
+
+ pub fn is_empty(&self) -> bool {
+ self.entries.is_empty()
+ }
+
+ pub fn iter_mut(
+ &mut self,
+ ) -> impl Iterator<Item = (Id, &mut Window<A, C>)> {
+ self.entries.iter_mut().map(|(k, v)| (*k, v))
+ }
+
+ pub fn get_mut(&mut self, id: Id) -> Option<&mut Window<A, C>> {
+ self.entries.get_mut(&id)
+ }
+
+ pub fn get_mut_alias(
+ &mut self,
+ id: winit::window::WindowId,
+ ) -> Option<(Id, &mut Window<A, C>)> {
+ let id = self.aliases.get(&id).copied()?;
+
+ Some((id, self.get_mut(id)?))
+ }
+
+ pub fn last_monitor(&self) -> Option<MonitorHandle> {
+ self.entries.values().last()?.raw.current_monitor()
+ }
+
+ pub fn remove(&mut self, id: Id) -> Option<Window<A, C>> {
+ let window = self.entries.remove(&id)?;
+ let _ = self.aliases.remove(&window.raw.id());
+
+ Some(window)
+ }
+}
+
+impl<A, C> Default for WindowManager<A, C>
+where
+ A: Application,
+ C: Compositor<Renderer = A::Renderer>,
+ <A::Renderer as crate::core::Renderer>::Theme: StyleSheet,
+{
+ fn default() -> Self {
+ Self::new()
+ }
+}
+
+#[allow(missing_debug_implementations)]
+pub struct Window<A, C>
+where
+ A: Application,
+ C: Compositor<Renderer = A::Renderer>,
+ <A::Renderer as crate::core::Renderer>::Theme: StyleSheet,
+{
+ pub raw: winit::window::Window,
+ pub state: State<A>,
+ pub viewport_version: u64,
+ pub exit_on_close_request: bool,
+ pub mouse_interaction: mouse::Interaction,
+ pub surface: C::Surface,
+ pub renderer: A::Renderer,
+}
+
+impl<A, C> Window<A, C>
+where
+ A: Application,
+ C: Compositor<Renderer = A::Renderer>,
+ <A::Renderer as crate::core::Renderer>::Theme: StyleSheet,
+{
+ pub fn position(&self) -> Option<Point> {
+ self.raw
+ .inner_position()
+ .ok()
+ .map(|position| position.to_logical(self.raw.scale_factor()))
+ .map(|position| Point {
+ x: position.x,
+ y: position.y,
+ })
+ }
+
+ pub fn size(&self) -> Size {
+ let size = self.raw.inner_size().to_logical(self.raw.scale_factor());
+
+ Size::new(size.width, size.height)
+ }
+}
diff --git a/winit/src/settings.rs b/winit/src/settings.rs
index 599f33f1..2e541128 100644
--- a/winit/src/settings.rs
+++ b/winit/src/settings.rs
@@ -1,40 +1,7 @@
//! Configure your application.
-#[cfg(target_os = "windows")]
-#[path = "settings/windows.rs"]
-mod platform;
-
-#[cfg(target_os = "macos")]
-#[path = "settings/macos.rs"]
-mod platform;
-
-#[cfg(target_os = "linux")]
-#[path = "settings/linux.rs"]
-mod platform;
-
-#[cfg(target_arch = "wasm32")]
-#[path = "settings/wasm.rs"]
-mod platform;
-
-#[cfg(not(any(
- target_os = "windows",
- target_os = "macos",
- target_os = "linux",
- target_arch = "wasm32"
-)))]
-#[path = "settings/other.rs"]
-mod platform;
-
-pub use platform::PlatformSpecific;
-
-use crate::conversion;
-use crate::core::window::{Icon, Level};
-use crate::Position;
-
-use winit::monitor::MonitorHandle;
-use winit::window::WindowBuilder;
+use crate::core::window;
use std::borrow::Cow;
-use std::fmt;
/// The settings of an application.
#[derive(Debug, Clone, Default)]
@@ -45,8 +12,8 @@ pub struct Settings<Flags> {
/// communicate with it through the windowing system.
pub id: Option<String>,
- /// The [`Window`] settings.
- pub window: Window,
+ /// The [`window::Settings`].
+ pub window: window::Settings,
/// The data needed to initialize an [`Application`].
///
@@ -55,197 +22,4 @@ pub struct Settings<Flags> {
/// The fonts to load on boot.
pub fonts: Vec<Cow<'static, [u8]>>,
-
- /// Whether the [`Application`] should exit when the user requests the
- /// window to close (e.g. the user presses the close button).
- ///
- /// [`Application`]: crate::Application
- pub exit_on_close_request: bool,
-}
-
-/// The window settings of an application.
-#[derive(Clone)]
-pub struct Window {
- /// The size of the window.
- pub size: (u32, u32),
-
- /// The position of the window.
- pub position: Position,
-
- /// The minimum size of the window.
- pub min_size: Option<(u32, u32)>,
-
- /// The maximum size of the window.
- pub max_size: Option<(u32, u32)>,
-
- /// Whether the window should be visible or not.
- pub visible: bool,
-
- /// Whether the window should be resizable or not.
- pub resizable: bool,
-
- /// Whether the window should have a border, a title bar, etc.
- pub decorations: bool,
-
- /// Whether the window should be transparent.
- pub transparent: bool,
-
- /// The window [`Level`].
- pub level: Level,
-
- /// The window icon, which is also usually used in the taskbar
- pub icon: Option<Icon>,
-
- /// Platform specific settings.
- pub platform_specific: platform::PlatformSpecific,
-}
-
-impl fmt::Debug for Window {
- fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
- f.debug_struct("Window")
- .field("size", &self.size)
- .field("position", &self.position)
- .field("min_size", &self.min_size)
- .field("max_size", &self.max_size)
- .field("visible", &self.visible)
- .field("resizable", &self.resizable)
- .field("decorations", &self.decorations)
- .field("transparent", &self.transparent)
- .field("level", &self.level)
- .field("icon", &self.icon.is_some())
- .field("platform_specific", &self.platform_specific)
- .finish()
- }
-}
-
-impl Window {
- /// Converts the window settings into a `WindowBuilder` from `winit`.
- pub fn into_builder(
- self,
- title: &str,
- primary_monitor: Option<MonitorHandle>,
- _id: Option<String>,
- ) -> WindowBuilder {
- let mut window_builder = WindowBuilder::new();
-
- let (width, height) = self.size;
-
- window_builder = window_builder
- .with_title(title)
- .with_inner_size(winit::dpi::LogicalSize { width, height })
- .with_resizable(self.resizable)
- .with_enabled_buttons(if self.resizable {
- winit::window::WindowButtons::all()
- } else {
- winit::window::WindowButtons::CLOSE
- | winit::window::WindowButtons::MINIMIZE
- })
- .with_decorations(self.decorations)
- .with_transparent(self.transparent)
- .with_window_icon(self.icon.and_then(conversion::icon))
- .with_window_level(conversion::window_level(self.level))
- .with_visible(self.visible);
-
- if let Some(position) = conversion::position(
- primary_monitor.as_ref(),
- self.size,
- self.position,
- ) {
- window_builder = window_builder.with_position(position);
- }
-
- if let Some((width, height)) = self.min_size {
- window_builder = window_builder
- .with_min_inner_size(winit::dpi::LogicalSize { width, height });
- }
-
- if let Some((width, height)) = self.max_size {
- window_builder = window_builder
- .with_max_inner_size(winit::dpi::LogicalSize { width, height });
- }
-
- #[cfg(any(
- target_os = "dragonfly",
- target_os = "freebsd",
- target_os = "netbsd",
- target_os = "openbsd"
- ))]
- {
- // `with_name` is available on both `WindowBuilderExtWayland` and `WindowBuilderExtX11` and they do
- // exactly the same thing. We arbitrarily choose `WindowBuilderExtWayland` here.
- use ::winit::platform::wayland::WindowBuilderExtWayland;
-
- if let Some(id) = _id {
- window_builder = window_builder.with_name(id.clone(), id);
- }
- }
-
- #[cfg(target_os = "windows")]
- {
- use winit::platform::windows::WindowBuilderExtWindows;
- #[allow(unsafe_code)]
- unsafe {
- window_builder = window_builder
- .with_parent_window(self.platform_specific.parent);
- }
- window_builder = window_builder
- .with_drag_and_drop(self.platform_specific.drag_and_drop);
- }
-
- #[cfg(target_os = "macos")]
- {
- use winit::platform::macos::WindowBuilderExtMacOS;
-
- window_builder = window_builder
- .with_title_hidden(self.platform_specific.title_hidden)
- .with_titlebar_transparent(
- self.platform_specific.titlebar_transparent,
- )
- .with_fullsize_content_view(
- self.platform_specific.fullsize_content_view,
- );
- }
-
- #[cfg(target_os = "linux")]
- {
- #[cfg(feature = "x11")]
- {
- use winit::platform::x11::WindowBuilderExtX11;
-
- window_builder = window_builder.with_name(
- &self.platform_specific.application_id,
- &self.platform_specific.application_id,
- );
- }
- #[cfg(feature = "wayland")]
- {
- use winit::platform::wayland::WindowBuilderExtWayland;
-
- window_builder = window_builder.with_name(
- &self.platform_specific.application_id,
- &self.platform_specific.application_id,
- );
- }
- }
-
- window_builder
- }
-}
-
-impl Default for Window {
- fn default() -> Window {
- Window {
- size: (1024, 768),
- position: Position::default(),
- min_size: None,
- max_size: None,
- visible: true,
- resizable: true,
- decorations: true,
- transparent: false,
- level: Level::default(),
- icon: None,
- platform_specific: PlatformSpecific::default(),
- }
- }
}