Merge pull request #1964 from bungoboingo/feat/multi-window-support
[Feature] 🪟 Multi Window 🪟 .. redux!
This commit is contained in:
commit
fc285d3e46
59 changed files with 3020 additions and 761 deletions
1
.github/ISSUE_TEMPLATE/BUG-REPORT.yml
vendored
1
.github/ISSUE_TEMPLATE/BUG-REPORT.yml
vendored
|
|
@ -25,7 +25,6 @@ body:
|
||||||
Before filing an issue...
|
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 `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!
|
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!
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -49,6 +49,8 @@ web-colors = ["iced_renderer/web-colors"]
|
||||||
webgl = ["iced_renderer/webgl"]
|
webgl = ["iced_renderer/webgl"]
|
||||||
# Enables the syntax `highlighter` module
|
# Enables the syntax `highlighter` module
|
||||||
highlighter = ["iced_highlighter"]
|
highlighter = ["iced_highlighter"]
|
||||||
|
# Enables experimental multi-window support.
|
||||||
|
multi-window = ["iced_winit/multi-window"]
|
||||||
# Enables the advanced module
|
# Enables the advanced module
|
||||||
advanced = []
|
advanced = []
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -45,7 +45,7 @@ The widgets of a _graphical_ user interface produce some primitives that eventua
|
||||||
Currently, there are two different official renderers:
|
Currently, there are two different official renderers:
|
||||||
|
|
||||||
- [`iced_wgpu`] is powered by [`wgpu`] and supports Vulkan, DirectX 12, and Metal.
|
- [`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.
|
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].
|
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:
|
As of now, there is one official shell: [`iced_winit`] implements a shell runtime on top of [`winit`].
|
||||||
|
|
||||||
- [`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].
|
|
||||||
|
|
||||||
## The web target
|
## The web target
|
||||||
The Web platform provides all the abstractions necessary to draw widgets and gather users interactions.
|
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
|
[`winit`]: https://github.com/rust-windowing/winit
|
||||||
[`glutin`]: https://github.com/rust-windowing/glutin
|
[`glutin`]: https://github.com/rust-windowing/glutin
|
||||||
[`dodrio`]: https://github.com/fitzgen/dodrio
|
[`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/
|
[The Elm Architecture]: https://guide.elm-lang.org/architecture/
|
||||||
|
|
|
||||||
|
|
@ -23,5 +23,8 @@ palette.optional = true
|
||||||
[target.'cfg(target_arch = "wasm32")'.dependencies]
|
[target.'cfg(target_arch = "wasm32")'.dependencies]
|
||||||
instant.workspace = true
|
instant.workspace = true
|
||||||
|
|
||||||
|
[target.'cfg(windows)'.dependencies]
|
||||||
|
raw-window-handle.workspace = true
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
approx = "0.5"
|
approx = "0.5"
|
||||||
|
|
|
||||||
|
|
@ -19,7 +19,7 @@ pub enum Event {
|
||||||
Mouse(mouse::Event),
|
Mouse(mouse::Event),
|
||||||
|
|
||||||
/// A window event
|
/// A window event
|
||||||
Window(window::Event),
|
Window(window::Id, window::Event),
|
||||||
|
|
||||||
/// A touch event
|
/// A touch event
|
||||||
Touch(touch::Event),
|
Touch(touch::Event),
|
||||||
|
|
|
||||||
|
|
@ -1,26 +1,34 @@
|
||||||
use crate::Vector;
|
use crate::Vector;
|
||||||
|
|
||||||
|
use num_traits::{Float, Num};
|
||||||
|
use std::fmt;
|
||||||
|
|
||||||
/// A 2D point.
|
/// A 2D point.
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Default)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
|
||||||
pub struct Point {
|
pub struct Point<T = f32> {
|
||||||
/// The X coordinate.
|
/// The X coordinate.
|
||||||
pub x: f32,
|
pub x: T,
|
||||||
|
|
||||||
/// The Y coordinate.
|
/// The Y coordinate.
|
||||||
pub y: f32,
|
pub y: T,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Point {
|
impl Point {
|
||||||
/// The origin (i.e. a [`Point`] at (0, 0)).
|
/// 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.
|
/// 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 }
|
Self { x, y }
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Computes the distance to another [`Point`].
|
/// 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 a = self.x - to.x;
|
||||||
let b = self.y - to.y;
|
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 {
|
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;
|
type Output = Self;
|
||||||
|
|
||||||
fn add(self, vector: Vector) -> Self {
|
fn add(self, vector: Vector<T>) -> Self {
|
||||||
Self {
|
Self {
|
||||||
x: self.x + vector.x,
|
x: self.x + vector.x,
|
||||||
y: self.y + vector.y,
|
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;
|
type Output = Self;
|
||||||
|
|
||||||
fn sub(self, vector: Vector) -> Self {
|
fn sub(self, vector: Vector<T>) -> Self {
|
||||||
Self {
|
Self {
|
||||||
x: self.x - vector.x,
|
x: self.x - vector.x,
|
||||||
y: self.y - vector.y,
|
y: self.y - vector.y,
|
||||||
|
|
@ -68,10 +82,22 @@ impl std::ops::Sub<Vector> for Point {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl std::ops::Sub<Point> for Point {
|
impl<T> std::ops::Sub<Point<T>> for Point<T>
|
||||||
type Output = Vector;
|
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)
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -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>(
|
pub fn diff_children<'a, Message, Renderer>(
|
||||||
&mut self,
|
&mut self,
|
||||||
new_children: &[impl Borrow<dyn Widget<Message, Renderer> + 'a>],
|
new_children: &[impl Borrow<dyn Widget<Message, Renderer> + 'a>],
|
||||||
|
|
|
||||||
|
|
@ -1,15 +1,21 @@
|
||||||
//! Build window-based GUI applications.
|
//! Build window-based GUI applications.
|
||||||
pub mod icon;
|
pub mod icon;
|
||||||
|
pub mod settings;
|
||||||
|
|
||||||
mod event;
|
mod event;
|
||||||
|
mod id;
|
||||||
mod level;
|
mod level;
|
||||||
mod mode;
|
mod mode;
|
||||||
|
mod position;
|
||||||
mod redraw_request;
|
mod redraw_request;
|
||||||
mod user_attention;
|
mod user_attention;
|
||||||
|
|
||||||
pub use event::Event;
|
pub use event::Event;
|
||||||
pub use icon::Icon;
|
pub use icon::Icon;
|
||||||
|
pub use id::Id;
|
||||||
pub use level::Level;
|
pub use level::Level;
|
||||||
pub use mode::Mode;
|
pub use mode::Mode;
|
||||||
|
pub use position::Position;
|
||||||
pub use redraw_request::RedrawRequest;
|
pub use redraw_request::RedrawRequest;
|
||||||
|
pub use settings::Settings;
|
||||||
pub use user_attention::UserAttention;
|
pub use user_attention::UserAttention;
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,27 @@
|
||||||
use crate::time::Instant;
|
use crate::time::Instant;
|
||||||
|
use crate::{Point, Size};
|
||||||
|
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
|
||||||
/// A window-related event.
|
/// A window-related event.
|
||||||
#[derive(PartialEq, Eq, Clone, Debug)]
|
#[derive(PartialEq, Clone, Debug)]
|
||||||
pub enum Event {
|
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.
|
/// A window was moved.
|
||||||
Moved {
|
Moved {
|
||||||
/// The new logical x location of the window
|
/// The new logical x location of the window
|
||||||
|
|
@ -27,9 +44,6 @@ pub enum Event {
|
||||||
RedrawRequested(Instant),
|
RedrawRequested(Instant),
|
||||||
|
|
||||||
/// The user has requested for the window to close.
|
/// The user has requested for the window to close.
|
||||||
///
|
|
||||||
/// Usually, you will want to terminate the execution whenever this event
|
|
||||||
/// occurs.
|
|
||||||
CloseRequested,
|
CloseRequested,
|
||||||
|
|
||||||
/// A window was focused.
|
/// A window was focused.
|
||||||
|
|
|
||||||
21
core/src/window/id.rs
Normal file
21
core/src/window/id.rs
Normal file
|
|
@ -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))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,5 +1,7 @@
|
||||||
|
use crate::Point;
|
||||||
|
|
||||||
/// The position of a window in a given screen.
|
/// The position of a window in a given screen.
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||||
pub enum Position {
|
pub enum Position {
|
||||||
/// The platform-specific default position for a new window.
|
/// The platform-specific default position for a new window.
|
||||||
Default,
|
Default,
|
||||||
|
|
@ -12,7 +14,7 @@ pub enum Position {
|
||||||
/// position. So if you have decorations enabled and want the window to be
|
/// position. So if you have decorations enabled and want the window to be
|
||||||
/// at (0, 0) you would have to set the position to
|
/// at (0, 0) you would have to set the position to
|
||||||
/// `(PADDING_X, PADDING_Y)`.
|
/// `(PADDING_X, PADDING_Y)`.
|
||||||
Specific(i32, i32),
|
Specific(Point),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for Position {
|
impl Default for Position {
|
||||||
|
|
@ -1,21 +1,47 @@
|
||||||
|
//! 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;
|
||||||
|
|
||||||
|
#[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::window::{Icon, Level, Position};
|
||||||
|
use crate::Size;
|
||||||
|
|
||||||
pub use iced_winit::settings::PlatformSpecific;
|
pub use platform::PlatformSpecific;
|
||||||
|
|
||||||
/// The window settings of an application.
|
/// The window settings of an application.
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct Settings {
|
pub struct Settings {
|
||||||
/// The initial size of the window.
|
/// The initial logical dimensions of the window.
|
||||||
pub size: (u32, u32),
|
pub size: Size,
|
||||||
|
|
||||||
/// The initial position of the window.
|
/// The initial position of the window.
|
||||||
pub position: Position,
|
pub position: Position,
|
||||||
|
|
||||||
/// The minimum size of the window.
|
/// The minimum size of the window.
|
||||||
pub min_size: Option<(u32, u32)>,
|
pub min_size: Option<Size>,
|
||||||
|
|
||||||
/// The maximum size of the window.
|
/// 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.
|
/// Whether the window should be visible or not.
|
||||||
pub visible: bool,
|
pub visible: bool,
|
||||||
|
|
@ -37,12 +63,22 @@ pub struct Settings {
|
||||||
|
|
||||||
/// Platform specific settings.
|
/// Platform specific settings.
|
||||||
pub platform_specific: PlatformSpecific,
|
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 {
|
impl Default for Settings {
|
||||||
fn default() -> Settings {
|
fn default() -> Self {
|
||||||
Settings {
|
Self {
|
||||||
size: (1024, 768),
|
size: Size::new(1024.0, 768.0),
|
||||||
position: Position::default(),
|
position: Position::default(),
|
||||||
min_size: None,
|
min_size: None,
|
||||||
max_size: None,
|
max_size: None,
|
||||||
|
|
@ -52,25 +88,8 @@ impl Default for Settings {
|
||||||
transparent: false,
|
transparent: false,
|
||||||
level: Level::default(),
|
level: Level::default(),
|
||||||
icon: None,
|
icon: None,
|
||||||
|
exit_on_close_request: true,
|
||||||
platform_specific: PlatformSpecific::default(),
|
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,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -10,7 +10,10 @@ use iced::{
|
||||||
|
|
||||||
pub fn main() -> iced::Result {
|
pub fn main() -> iced::Result {
|
||||||
Events::run(Settings {
|
Events::run(Settings {
|
||||||
exit_on_close_request: false,
|
window: window::Settings {
|
||||||
|
exit_on_close_request: false,
|
||||||
|
..window::Settings::default()
|
||||||
|
},
|
||||||
..Settings::default()
|
..Settings::default()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
@ -54,8 +57,9 @@ impl Application for Events {
|
||||||
Command::none()
|
Command::none()
|
||||||
}
|
}
|
||||||
Message::EventOccurred(event) => {
|
Message::EventOccurred(event) => {
|
||||||
if let Event::Window(window::Event::CloseRequested) = event {
|
if let Event::Window(id, window::Event::CloseRequested) = event
|
||||||
window::close()
|
{
|
||||||
|
window::close(id)
|
||||||
} else {
|
} else {
|
||||||
Command::none()
|
Command::none()
|
||||||
}
|
}
|
||||||
|
|
@ -65,7 +69,7 @@ impl Application for Events {
|
||||||
|
|
||||||
Command::none()
|
Command::none()
|
||||||
}
|
}
|
||||||
Message::Exit => window::close(),
|
Message::Exit => window::close(window::Id::MAIN),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -34,7 +34,7 @@ impl Application for Exit {
|
||||||
|
|
||||||
fn update(&mut self, message: Message) -> Command<Message> {
|
fn update(&mut self, message: Message) -> Command<Message> {
|
||||||
match message {
|
match message {
|
||||||
Message::Confirm => window::close(),
|
Message::Confirm => window::close(window::Id::MAIN),
|
||||||
Message::Exit => {
|
Message::Exit => {
|
||||||
self.show_confirm = true;
|
self.show_confirm = true;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -6,13 +6,17 @@ use scene::Scene;
|
||||||
|
|
||||||
use iced_wgpu::graphics::Viewport;
|
use iced_wgpu::graphics::Viewport;
|
||||||
use iced_wgpu::{wgpu, Backend, Renderer, Settings};
|
use iced_wgpu::{wgpu, Backend, Renderer, Settings};
|
||||||
|
use iced_winit::conversion;
|
||||||
use iced_winit::core::mouse;
|
use iced_winit::core::mouse;
|
||||||
use iced_winit::core::renderer;
|
use iced_winit::core::renderer;
|
||||||
|
use iced_winit::core::window;
|
||||||
use iced_winit::core::{Color, Font, Pixels, Size};
|
use iced_winit::core::{Color, Font, Pixels, Size};
|
||||||
|
use iced_winit::futures;
|
||||||
use iced_winit::runtime::program;
|
use iced_winit::runtime::program;
|
||||||
use iced_winit::runtime::Debug;
|
use iced_winit::runtime::Debug;
|
||||||
use iced_winit::style::Theme;
|
use iced_winit::style::Theme;
|
||||||
use iced_winit::{conversion, futures, winit, Clipboard};
|
use iced_winit::winit;
|
||||||
|
use iced_winit::Clipboard;
|
||||||
|
|
||||||
use winit::{
|
use winit::{
|
||||||
event::{Event, ModifiersState, WindowEvent},
|
event::{Event, ModifiersState, WindowEvent},
|
||||||
|
|
@ -180,6 +184,7 @@ pub fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
|
|
||||||
// Map window event to iced event
|
// Map window event to iced event
|
||||||
if let Some(event) = iced_winit::conversion::window_event(
|
if let Some(event) = iced_winit::conversion::window_event(
|
||||||
|
window::Id::MAIN,
|
||||||
&event,
|
&event,
|
||||||
window.scale_factor(),
|
window.scale_factor(),
|
||||||
modifiers,
|
modifiers,
|
||||||
|
|
|
||||||
|
|
@ -279,7 +279,7 @@ where
|
||||||
|
|
||||||
let state = tree.state.downcast_mut::<State>();
|
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(
|
state.animation = state.animation.timed_transition(
|
||||||
self.cycle_duration,
|
self.cycle_duration,
|
||||||
self.rotation_duration,
|
self.rotation_duration,
|
||||||
|
|
|
||||||
|
|
@ -200,7 +200,7 @@ where
|
||||||
|
|
||||||
let state = tree.state.downcast_mut::<State>();
|
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);
|
*state = state.timed_transition(self.cycle_duration, now);
|
||||||
|
|
||||||
shell.request_redraw(RedrawRequest::At(
|
shell.request_redraw(RedrawRequest::At(
|
||||||
|
|
|
||||||
9
examples/multi_window/Cargo.toml
Normal file
9
examples/multi_window/Cargo.toml
Normal file
|
|
@ -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"] }
|
||||||
215
examples/multi_window/src/main.rs
Normal file
215
examples/multi_window/src/main.rs
Normal file
|
|
@ -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()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,8 +1,8 @@
|
||||||
use iced::alignment;
|
|
||||||
use iced::keyboard::KeyCode;
|
use iced::keyboard::KeyCode;
|
||||||
use iced::theme::{Button, Container};
|
use iced::theme::{Button, Container};
|
||||||
use iced::widget::{button, column, container, image, row, text, text_input};
|
use iced::widget::{button, column, container, image, row, text, text_input};
|
||||||
use iced::window::screenshot::{self, Screenshot};
|
use iced::window::screenshot::{self, Screenshot};
|
||||||
|
use iced::{alignment, window};
|
||||||
use iced::{
|
use iced::{
|
||||||
event, executor, keyboard, Alignment, Application, Command, ContentFit,
|
event, executor, keyboard, Alignment, Application, Command, ContentFit,
|
||||||
Element, Event, Length, Rectangle, Renderer, Subscription, Theme,
|
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> {
|
fn update(&mut self, message: Self::Message) -> Command<Self::Message> {
|
||||||
match message {
|
match message {
|
||||||
Message::Screenshot => {
|
Message::Screenshot => {
|
||||||
return iced::window::screenshot(Message::ScreenshotData);
|
return iced::window::screenshot(
|
||||||
|
window::Id::MAIN,
|
||||||
|
Message::ScreenshotData,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
Message::ScreenshotData(screenshot) => {
|
Message::ScreenshotData(screenshot) => {
|
||||||
self.screenshot = Some(screenshot);
|
self.screenshot = Some(screenshot);
|
||||||
|
|
|
||||||
|
|
@ -114,14 +114,14 @@ impl State {
|
||||||
|
|
||||||
pub fn new() -> State {
|
pub fn new() -> State {
|
||||||
let now = Instant::now();
|
let now = Instant::now();
|
||||||
let (width, height) = window::Settings::default().size;
|
let size = window::Settings::default().size;
|
||||||
|
|
||||||
State {
|
State {
|
||||||
space_cache: canvas::Cache::default(),
|
space_cache: canvas::Cache::default(),
|
||||||
system_cache: canvas::Cache::default(),
|
system_cache: canvas::Cache::default(),
|
||||||
start: now,
|
start: now,
|
||||||
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();
|
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;
|
use rand::Rng;
|
||||||
|
|
||||||
let mut rng = rand::thread_rng();
|
let mut rng = rand::thread_rng();
|
||||||
|
|
@ -139,12 +139,8 @@ impl State {
|
||||||
.map(|_| {
|
.map(|_| {
|
||||||
(
|
(
|
||||||
Point::new(
|
Point::new(
|
||||||
rng.gen_range(
|
rng.gen_range((-width / 2.0)..(width / 2.0)),
|
||||||
(-(width as f32) / 2.0)..(width as f32 / 2.0),
|
rng.gen_range((-height / 2.0)..(height / 2.0)),
|
||||||
),
|
|
||||||
rng.gen_range(
|
|
||||||
(-(height as f32) / 2.0)..(height as f32 / 2.0),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
rng.gen_range(0.5..1.0),
|
rng.gen_range(0.5..1.0),
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -539,7 +539,9 @@ mod toast {
|
||||||
clipboard: &mut dyn Clipboard,
|
clipboard: &mut dyn Clipboard,
|
||||||
shell: &mut Shell<'_, Message>,
|
shell: &mut Shell<'_, Message>,
|
||||||
) -> event::Status {
|
) -> 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;
|
let mut next_redraw: Option<window::RedrawRequest> = None;
|
||||||
|
|
||||||
self.instants.iter_mut().enumerate().for_each(
|
self.instants.iter_mut().enumerate().for_each(
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,7 @@ use iced::widget::{
|
||||||
};
|
};
|
||||||
use iced::window;
|
use iced::window;
|
||||||
use iced::{Application, Element};
|
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 once_cell::sync::Lazy;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
@ -22,7 +22,7 @@ pub fn main() -> iced::Result {
|
||||||
|
|
||||||
Todos::run(Settings {
|
Todos::run(Settings {
|
||||||
window: window::Settings {
|
window: window::Settings {
|
||||||
size: (500, 800),
|
size: Size::new(500.0, 800.0),
|
||||||
..window::Settings::default()
|
..window::Settings::default()
|
||||||
},
|
},
|
||||||
..Settings::default()
|
..Settings::default()
|
||||||
|
|
@ -54,7 +54,7 @@ enum Message {
|
||||||
FilterChanged(Filter),
|
FilterChanged(Filter),
|
||||||
TaskMessage(usize, TaskMessage),
|
TaskMessage(usize, TaskMessage),
|
||||||
TabPressed { shift: bool },
|
TabPressed { shift: bool },
|
||||||
ChangeWindowMode(window::Mode),
|
ToggleFullscreen(window::Mode),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Application for Todos {
|
impl Application for Todos {
|
||||||
|
|
@ -165,8 +165,8 @@ impl Application for Todos {
|
||||||
widget::focus_next()
|
widget::focus_next()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Message::ChangeWindowMode(mode) => {
|
Message::ToggleFullscreen(mode) => {
|
||||||
window::change_mode(mode)
|
window::change_mode(window::Id::MAIN, mode)
|
||||||
}
|
}
|
||||||
_ => Command::none(),
|
_ => Command::none(),
|
||||||
};
|
};
|
||||||
|
|
@ -272,10 +272,10 @@ impl Application for Todos {
|
||||||
shift: modifiers.shift(),
|
shift: modifiers.shift(),
|
||||||
}),
|
}),
|
||||||
(keyboard::KeyCode::Up, keyboard::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) => {
|
(keyboard::KeyCode::Down, keyboard::Modifiers::SHIFT) => {
|
||||||
Some(Message::ChangeWindowMode(window::Mode::Windowed))
|
Some(Message::ToggleFullscreen(window::Mode::Windowed))
|
||||||
}
|
}
|
||||||
_ => None,
|
_ => None,
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -167,7 +167,7 @@ impl Application for Example {
|
||||||
Event::Mouse(mouse::Event::CursorMoved { position }) => {
|
Event::Mouse(mouse::Event::CursorMoved { position }) => {
|
||||||
Some(Message::MouseMoved(position))
|
Some(Message::MouseMoved(position))
|
||||||
}
|
}
|
||||||
Event::Window(window::Event::Resized { .. }) => {
|
Event::Window(_, window::Event::Resized { .. }) => {
|
||||||
Some(Message::WindowResized)
|
Some(Message::WindowResized)
|
||||||
}
|
}
|
||||||
_ => None,
|
_ => None,
|
||||||
|
|
|
||||||
|
|
@ -35,7 +35,7 @@ where
|
||||||
subscription::filter_map(
|
subscription::filter_map(
|
||||||
(EventsWith, f),
|
(EventsWith, f),
|
||||||
move |event, status| match event {
|
move |event, status| match event {
|
||||||
Event::Window(window::Event::RedrawRequested(_)) => None,
|
Event::Window(_, window::Event::RedrawRequested(_)) => None,
|
||||||
_ => f(event, status),
|
_ => f(event, status),
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -22,7 +22,10 @@ pub trait Compositor: Sized {
|
||||||
fn new<W: HasRawWindowHandle + HasRawDisplayHandle>(
|
fn new<W: HasRawWindowHandle + HasRawDisplayHandle>(
|
||||||
settings: Self::Settings,
|
settings: Self::Settings,
|
||||||
compatible_window: Option<&W>,
|
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.
|
/// Crates a new [`Surface`] for the given window.
|
||||||
///
|
///
|
||||||
|
|
|
||||||
|
|
@ -26,7 +26,7 @@ impl<Theme> crate::graphics::Compositor for Compositor<Theme> {
|
||||||
fn new<W: HasRawWindowHandle + HasRawDisplayHandle>(
|
fn new<W: HasRawWindowHandle + HasRawDisplayHandle>(
|
||||||
settings: Self::Settings,
|
settings: Self::Settings,
|
||||||
compatible_window: Option<&W>,
|
compatible_window: Option<&W>,
|
||||||
) -> Result<(Self, Self::Renderer), Error> {
|
) -> Result<Self, Error> {
|
||||||
let candidates =
|
let candidates =
|
||||||
Candidate::list_from_env().unwrap_or(Candidate::default_list());
|
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 {
|
for candidate in candidates {
|
||||||
match candidate.build(settings, compatible_window) {
|
match candidate.build(settings, compatible_window) {
|
||||||
Ok((compositor, renderer)) => {
|
Ok(compositor) => return Ok(compositor),
|
||||||
return Ok((compositor, renderer))
|
|
||||||
}
|
|
||||||
Err(new_error) => {
|
Err(new_error) => {
|
||||||
error = new_error;
|
error = new_error;
|
||||||
}
|
}
|
||||||
|
|
@ -46,6 +44,18 @@ impl<Theme> crate::graphics::Compositor for Compositor<Theme> {
|
||||||
Err(error)
|
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>(
|
fn create_surface<W: HasRawWindowHandle + HasRawDisplayHandle>(
|
||||||
&mut self,
|
&mut self,
|
||||||
window: &W,
|
window: &W,
|
||||||
|
|
@ -220,24 +230,21 @@ impl Candidate {
|
||||||
self,
|
self,
|
||||||
settings: Settings,
|
settings: Settings,
|
||||||
_compatible_window: Option<&W>,
|
_compatible_window: Option<&W>,
|
||||||
) -> Result<(Compositor<Theme>, Renderer<Theme>), Error> {
|
) -> Result<Compositor<Theme>, Error> {
|
||||||
match self {
|
match self {
|
||||||
Self::TinySkia => {
|
Self::TinySkia => {
|
||||||
let (compositor, backend) =
|
let compositor = iced_tiny_skia::window::compositor::new(
|
||||||
iced_tiny_skia::window::compositor::new();
|
iced_tiny_skia::Settings {
|
||||||
|
default_font: settings.default_font,
|
||||||
|
default_text_size: settings.default_text_size,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
Ok((
|
Ok(Compositor::TinySkia(compositor))
|
||||||
Compositor::TinySkia(compositor),
|
|
||||||
Renderer::TinySkia(iced_tiny_skia::Renderer::new(
|
|
||||||
backend,
|
|
||||||
settings.default_font,
|
|
||||||
settings.default_text_size,
|
|
||||||
)),
|
|
||||||
))
|
|
||||||
}
|
}
|
||||||
#[cfg(feature = "wgpu")]
|
#[cfg(feature = "wgpu")]
|
||||||
Self::Wgpu => {
|
Self::Wgpu => {
|
||||||
let (compositor, backend) = iced_wgpu::window::compositor::new(
|
let compositor = iced_wgpu::window::compositor::new(
|
||||||
iced_wgpu::Settings {
|
iced_wgpu::Settings {
|
||||||
default_font: settings.default_font,
|
default_font: settings.default_font,
|
||||||
default_text_size: settings.default_text_size,
|
default_text_size: settings.default_text_size,
|
||||||
|
|
@ -247,14 +254,7 @@ impl Candidate {
|
||||||
_compatible_window,
|
_compatible_window,
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
Ok((
|
Ok(Compositor::Wgpu(compositor))
|
||||||
Compositor::Wgpu(compositor),
|
|
||||||
Renderer::Wgpu(iced_wgpu::Renderer::new(
|
|
||||||
backend,
|
|
||||||
settings.default_font,
|
|
||||||
settings.default_text_size,
|
|
||||||
)),
|
|
||||||
))
|
|
||||||
}
|
}
|
||||||
#[cfg(not(feature = "wgpu"))]
|
#[cfg(not(feature = "wgpu"))]
|
||||||
Self::Wgpu => {
|
Self::Wgpu => {
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,7 @@ keywords.workspace = true
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
debug = []
|
debug = []
|
||||||
|
multi-window = []
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
iced_core.workspace = true
|
iced_core.workspace = true
|
||||||
|
|
|
||||||
|
|
@ -84,7 +84,9 @@ impl<T> fmt::Debug for Action<T> {
|
||||||
Self::Clipboard(action) => {
|
Self::Clipboard(action) => {
|
||||||
write!(f, "Action::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::System(action) => write!(f, "Action::System({action:?})"),
|
||||||
Self::Widget(_action) => write!(f, "Action::Widget"),
|
Self::Widget(_action) => write!(f, "Action::Widget"),
|
||||||
Self::LoadFont { .. } => write!(f, "Action::LoadFont"),
|
Self::LoadFont { .. } => write!(f, "Action::LoadFont"),
|
||||||
|
|
|
||||||
|
|
@ -26,6 +26,9 @@ pub mod system;
|
||||||
pub mod user_interface;
|
pub mod user_interface;
|
||||||
pub mod window;
|
pub mod window;
|
||||||
|
|
||||||
|
#[cfg(feature = "multi-window")]
|
||||||
|
pub mod multi_window;
|
||||||
|
|
||||||
// We disable debug capabilities on release builds unless the `debug` feature
|
// We disable debug capabilities on release builds unless the `debug` feature
|
||||||
// is explicitly enabled.
|
// is explicitly enabled.
|
||||||
#[cfg(feature = "debug")]
|
#[cfg(feature = "debug")]
|
||||||
|
|
|
||||||
6
runtime/src/multi_window.rs
Normal file
6
runtime/src/multi_window.rs
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
//! A multi-window application.
|
||||||
|
pub mod program;
|
||||||
|
pub mod state;
|
||||||
|
|
||||||
|
pub use program::Program;
|
||||||
|
pub use state::State;
|
||||||
32
runtime/src/multi_window/program.rs
Normal file
32
runtime/src/multi_window/program.rs
Normal file
|
|
@ -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>;
|
||||||
|
}
|
||||||
280
runtime/src/multi_window/state.rs
Normal file
280
runtime/src/multi_window/state.rs
Normal file
|
|
@ -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
|
||||||
|
}
|
||||||
|
|
@ -8,95 +8,113 @@ pub use screenshot::Screenshot;
|
||||||
|
|
||||||
use crate::command::{self, Command};
|
use crate::command::{self, Command};
|
||||||
use crate::core::time::Instant;
|
use crate::core::time::Instant;
|
||||||
use crate::core::window::{Event, Icon, Level, Mode, UserAttention};
|
use crate::core::window::{
|
||||||
use crate::core::Size;
|
Event, Icon, Id, Level, Mode, Settings, UserAttention,
|
||||||
|
};
|
||||||
|
use crate::core::{Point, Size};
|
||||||
use crate::futures::event;
|
use crate::futures::event;
|
||||||
use crate::futures::Subscription;
|
use crate::futures::Subscription;
|
||||||
|
|
||||||
/// Subscribes to the frames of the window of the running application.
|
/// Subscribes to the frames of the window of the running application.
|
||||||
///
|
///
|
||||||
/// The resulting [`Subscription`] will produce items at a rate equal to the
|
/// 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.
|
/// normally managed by the graphics driver and/or the OS.
|
||||||
///
|
///
|
||||||
/// In any case, this [`Subscription`] is useful to smoothly draw application-driven
|
/// In any case, this [`Subscription`] is useful to smoothly draw application-driven
|
||||||
/// animations without missing any frames.
|
/// animations without missing any frames.
|
||||||
pub fn frames() -> Subscription<Instant> {
|
pub fn frames() -> Subscription<Instant> {
|
||||||
event::listen_raw(|event, _status| match event {
|
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,
|
_ => None,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Closes the current window and exits the application.
|
/// Spawns a new window with the given `settings`.
|
||||||
pub fn close<Message>() -> Command<Message> {
|
///
|
||||||
Command::single(command::Action::Window(Action::Close))
|
/// 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.
|
/// Begins dragging the window while the left mouse button is held.
|
||||||
pub fn drag<Message>() -> Command<Message> {
|
pub fn drag<Message>(id: Id) -> Command<Message> {
|
||||||
Command::single(command::Action::Window(Action::Drag))
|
Command::single(command::Action::Window(Action::Drag(id)))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Resizes the window to the given logical dimensions.
|
/// Resizes the window to the given logical dimensions.
|
||||||
pub fn resize<Message>(new_size: Size<u32>) -> Command<Message> {
|
pub fn resize<Message>(id: Id, new_size: Size) -> Command<Message> {
|
||||||
Command::single(command::Action::Window(Action::Resize(new_size)))
|
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>(
|
pub fn fetch_size<Message>(
|
||||||
f: impl FnOnce(Size<u32>) -> Message + 'static,
|
id: Id,
|
||||||
|
f: impl FnOnce(Size) -> Message + 'static,
|
||||||
) -> Command<Message> {
|
) -> 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.
|
/// Maximizes the window.
|
||||||
pub fn maximize<Message>(maximized: bool) -> Command<Message> {
|
pub fn maximize<Message>(id: Id, maximized: bool) -> Command<Message> {
|
||||||
Command::single(command::Action::Window(Action::Maximize(maximized)))
|
Command::single(command::Action::Window(Action::Maximize(id, maximized)))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Minimes the window.
|
/// Minimizes the window.
|
||||||
pub fn minimize<Message>(minimized: bool) -> Command<Message> {
|
pub fn minimize<Message>(id: Id, minimized: bool) -> Command<Message> {
|
||||||
Command::single(command::Action::Window(Action::Minimize(minimized)))
|
Command::single(command::Action::Window(Action::Minimize(id, minimized)))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Moves a window to the given logical coordinates.
|
/// Moves the window to the given logical coordinates.
|
||||||
pub fn move_to<Message>(x: i32, y: i32) -> Command<Message> {
|
pub fn move_to<Message>(id: Id, position: Point) -> Command<Message> {
|
||||||
Command::single(command::Action::Window(Action::Move { x, y }))
|
Command::single(command::Action::Window(Action::Move(id, position)))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Changes the [`Mode`] of the window.
|
/// Changes the [`Mode`] of the window.
|
||||||
pub fn change_mode<Message>(mode: Mode) -> Command<Message> {
|
pub fn change_mode<Message>(id: Id, mode: Mode) -> Command<Message> {
|
||||||
Command::single(command::Action::Window(Action::ChangeMode(mode)))
|
Command::single(command::Action::Window(Action::ChangeMode(id, mode)))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Fetches the current [`Mode`] of the window.
|
/// Fetches the current [`Mode`] of the window.
|
||||||
pub fn fetch_mode<Message>(
|
pub fn fetch_mode<Message>(
|
||||||
|
id: Id,
|
||||||
f: impl FnOnce(Mode) -> Message + 'static,
|
f: impl FnOnce(Mode) -> Message + 'static,
|
||||||
) -> Command<Message> {
|
) -> 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.
|
/// Toggles the window to maximized or back.
|
||||||
pub fn toggle_maximize<Message>() -> Command<Message> {
|
pub fn toggle_maximize<Message>(id: Id) -> Command<Message> {
|
||||||
Command::single(command::Action::Window(Action::ToggleMaximize))
|
Command::single(command::Action::Window(Action::ToggleMaximize(id)))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Toggles the window decorations.
|
/// Toggles the window decorations.
|
||||||
pub fn toggle_decorations<Message>() -> Command<Message> {
|
pub fn toggle_decorations<Message>(id: Id) -> Command<Message> {
|
||||||
Command::single(command::Action::Window(Action::ToggleDecorations))
|
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,
|
/// is already focused. How requesting for user attention manifests is platform dependent,
|
||||||
/// see [`UserAttention`] for details.
|
/// see [`UserAttention`] for details.
|
||||||
///
|
///
|
||||||
/// Providing `None` will unset the request for user attention. Unsetting the request for
|
/// 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.
|
/// user attention might not be done automatically by the WM when the window receives input.
|
||||||
pub fn request_user_attention<Message>(
|
pub fn request_user_attention<Message>(
|
||||||
|
id: Id,
|
||||||
user_attention: Option<UserAttention>,
|
user_attention: Option<UserAttention>,
|
||||||
) -> Command<Message> {
|
) -> Command<Message> {
|
||||||
Command::single(command::Action::Window(Action::RequestUserAttention(
|
Command::single(command::Action::Window(Action::RequestUserAttention(
|
||||||
|
id,
|
||||||
user_attention,
|
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
|
/// 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
|
/// you are certain that's what the user wants. Focus stealing can cause an extremely disruptive
|
||||||
/// user experience.
|
/// user experience.
|
||||||
pub fn gain_focus<Message>() -> Command<Message> {
|
pub fn gain_focus<Message>(id: Id) -> Command<Message> {
|
||||||
Command::single(command::Action::Window(Action::GainFocus))
|
Command::single(command::Action::Window(Action::GainFocus(id)))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Changes the window [`Level`].
|
/// Changes the window [`Level`].
|
||||||
pub fn change_level<Message>(level: Level) -> Command<Message> {
|
pub fn change_level<Message>(id: Id, level: Level) -> Command<Message> {
|
||||||
Command::single(command::Action::Window(Action::ChangeLevel(level)))
|
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>(
|
pub fn fetch_id<Message>(
|
||||||
|
id: Id,
|
||||||
f: impl FnOnce(u64) -> Message + 'static,
|
f: impl FnOnce(u64) -> Message + 'static,
|
||||||
) -> Command<Message> {
|
) -> 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.
|
/// Changes the [`Icon`] of the window.
|
||||||
pub fn change_icon<Message>(icon: Icon) -> Command<Message> {
|
pub fn change_icon<Message>(id: Id, icon: Icon) -> Command<Message> {
|
||||||
Command::single(command::Action::Window(Action::ChangeIcon(icon)))
|
Command::single(command::Action::Window(Action::ChangeIcon(id, icon)))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Captures a [`Screenshot`] from the window.
|
/// Captures a [`Screenshot`] from the window.
|
||||||
pub fn screenshot<Message>(
|
pub fn screenshot<Message>(
|
||||||
|
id: Id,
|
||||||
f: impl FnOnce(Screenshot) -> Message + Send + 'static,
|
f: impl FnOnce(Screenshot) -> Message + Send + 'static,
|
||||||
) -> Command<Message> {
|
) -> Command<Message> {
|
||||||
Command::single(command::Action::Window(Action::Screenshot(Box::new(f))))
|
Command::single(command::Action::Window(Action::Screenshot(
|
||||||
|
id,
|
||||||
|
Box::new(f),
|
||||||
|
)))
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
use crate::core::window::{Icon, Level, Mode, UserAttention};
|
use crate::core::window::{Icon, Id, Level, Mode, Settings, UserAttention};
|
||||||
use crate::core::Size;
|
use crate::core::{Point, Size};
|
||||||
use crate::futures::MaybeSend;
|
use crate::futures::MaybeSend;
|
||||||
use crate::window::Screenshot;
|
use crate::window::Screenshot;
|
||||||
|
|
||||||
|
|
@ -7,43 +7,40 @@ use std::fmt;
|
||||||
|
|
||||||
/// An operation to be performed on some window.
|
/// An operation to be performed on some window.
|
||||||
pub enum Action<T> {
|
pub enum Action<T> {
|
||||||
/// Close the current window and exits the application.
|
/// Spawns a new window with some [`Settings`].
|
||||||
Close,
|
Spawn(Id, Settings),
|
||||||
|
/// Close the window and exits the application.
|
||||||
|
Close(Id),
|
||||||
/// Move the window with the left mouse button until the button is
|
/// Move the window with the left mouse button until the button is
|
||||||
/// released.
|
/// released.
|
||||||
///
|
///
|
||||||
/// There’s no guarantee that this will work unless the left mouse
|
/// There’s no guarantee that this will work unless the left mouse
|
||||||
/// button was pressed immediately before this function is called.
|
/// button was pressed immediately before this function is called.
|
||||||
Drag,
|
Drag(Id),
|
||||||
/// Resize the window.
|
/// Resize the window to the given logical dimensions.
|
||||||
Resize(Size<u32>),
|
Resize(Id, Size),
|
||||||
/// Fetch the current size of the window.
|
/// Fetch the current logical dimensions of the window.
|
||||||
FetchSize(Box<dyn FnOnce(Size<u32>) -> T + 'static>),
|
FetchSize(Id, Box<dyn FnOnce(Size) -> T + 'static>),
|
||||||
/// Set the window to maximized or back
|
/// Set the window to maximized or back
|
||||||
Maximize(bool),
|
Maximize(Id, bool),
|
||||||
/// Set the window to minimized or back
|
/// Set the window to minimized or back
|
||||||
Minimize(bool),
|
Minimize(Id, bool),
|
||||||
/// Move the window.
|
/// Move the window to the given logical coordinates.
|
||||||
///
|
///
|
||||||
/// Unsupported on Wayland.
|
/// Unsupported on Wayland.
|
||||||
Move {
|
Move(Id, Point),
|
||||||
/// The new logical x location of the window
|
|
||||||
x: i32,
|
|
||||||
/// The new logical y location of the window
|
|
||||||
y: i32,
|
|
||||||
},
|
|
||||||
/// Change the [`Mode`] of the window.
|
/// Change the [`Mode`] of the window.
|
||||||
ChangeMode(Mode),
|
ChangeMode(Id, Mode),
|
||||||
/// Fetch the current [`Mode`] of the window.
|
/// 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
|
/// Toggle the window to maximized or back
|
||||||
ToggleMaximize,
|
ToggleMaximize(Id),
|
||||||
/// Toggle whether window has decorations.
|
/// Toggle whether window has decorations.
|
||||||
///
|
///
|
||||||
/// ## Platform-specific
|
/// ## Platform-specific
|
||||||
/// - **X11:** Not implemented.
|
/// - **X11:** Not implemented.
|
||||||
/// - **Web:** Unsupported.
|
/// - **Web:** Unsupported.
|
||||||
ToggleDecorations,
|
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,
|
/// is already focused. How requesting for user attention manifests is platform dependent,
|
||||||
/// see [`UserAttention`] for details.
|
/// see [`UserAttention`] for details.
|
||||||
|
|
@ -57,7 +54,7 @@ pub enum Action<T> {
|
||||||
/// - **macOS:** `None` has no effect.
|
/// - **macOS:** `None` has no effect.
|
||||||
/// - **X11:** Requests for user attention must be manually cleared.
|
/// - **X11:** Requests for user attention must be manually cleared.
|
||||||
/// - **Wayland:** Requires `xdg_activation_v1` protocol, `None` has no effect.
|
/// - **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
|
/// Bring the window to the front and sets input focus. Has no effect if the window is
|
||||||
/// already in focus, minimized, or not visible.
|
/// already in focus, minimized, or not visible.
|
||||||
///
|
///
|
||||||
|
|
@ -68,11 +65,11 @@ pub enum Action<T> {
|
||||||
/// ## Platform-specific
|
/// ## Platform-specific
|
||||||
///
|
///
|
||||||
/// - **Web / Wayland:** Unsupported.
|
/// - **Web / Wayland:** Unsupported.
|
||||||
GainFocus,
|
GainFocus(Id),
|
||||||
/// Change the window [`Level`].
|
/// Change the window [`Level`].
|
||||||
ChangeLevel(Level),
|
ChangeLevel(Id, Level),
|
||||||
/// Fetch an identifier unique to the window.
|
/// Fetch the raw identifier unique to the window.
|
||||||
FetchId(Box<dyn FnOnce(u64) -> T + 'static>),
|
FetchId(Id, Box<dyn FnOnce(u64) -> T + 'static>),
|
||||||
/// Change the window [`Icon`].
|
/// Change the window [`Icon`].
|
||||||
///
|
///
|
||||||
/// On Windows and X11, this is typically the small icon in the top-left
|
/// 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
|
/// - **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.
|
/// said, it's usually in the same ballpark as on Windows.
|
||||||
ChangeIcon(Icon),
|
ChangeIcon(Id, Icon),
|
||||||
/// Screenshot the viewport of the window.
|
/// 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> {
|
impl<T> Action<T> {
|
||||||
|
|
@ -102,29 +99,35 @@ impl<T> Action<T> {
|
||||||
T: 'static,
|
T: 'static,
|
||||||
{
|
{
|
||||||
match self {
|
match self {
|
||||||
Self::Close => Action::Close,
|
Self::Spawn(id, settings) => Action::Spawn(id, settings),
|
||||||
Self::Drag => Action::Drag,
|
Self::Close(id) => Action::Close(id),
|
||||||
Self::Resize(size) => Action::Resize(size),
|
Self::Drag(id) => Action::Drag(id),
|
||||||
Self::FetchSize(o) => Action::FetchSize(Box::new(move |s| f(o(s)))),
|
Self::Resize(id, size) => Action::Resize(id, size),
|
||||||
Self::Maximize(maximized) => Action::Maximize(maximized),
|
Self::FetchSize(id, o) => {
|
||||||
Self::Minimize(minimized) => Action::Minimize(minimized),
|
Action::FetchSize(id, Box::new(move |s| f(o(s))))
|
||||||
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::GainFocus => Action::GainFocus,
|
Self::Maximize(id, maximized) => Action::Maximize(id, maximized),
|
||||||
Self::ChangeLevel(level) => Action::ChangeLevel(level),
|
Self::Minimize(id, minimized) => Action::Minimize(id, minimized),
|
||||||
Self::FetchId(o) => Action::FetchId(Box::new(move |s| f(o(s)))),
|
Self::Move(id, position) => Action::Move(id, position),
|
||||||
Self::ChangeIcon(icon) => Action::ChangeIcon(icon),
|
Self::ChangeMode(id, mode) => Action::ChangeMode(id, mode),
|
||||||
Self::Screenshot(tag) => {
|
Self::FetchMode(id, o) => {
|
||||||
Action::Screenshot(Box::new(move |screenshot| {
|
Action::FetchMode(id, Box::new(move |s| f(o(s))))
|
||||||
f(tag(screenshot))
|
|
||||||
}))
|
|
||||||
}
|
}
|
||||||
|
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> {
|
impl<T> fmt::Debug for Action<T> {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
match self {
|
match self {
|
||||||
Self::Close => write!(f, "Action::Close"),
|
Self::Spawn(id, settings) => {
|
||||||
Self::Drag => write!(f, "Action::Drag"),
|
write!(f, "Action::Spawn({id:?}, {settings:?})")
|
||||||
Self::Resize(size) => write!(f, "Action::Resize({size:?})"),
|
|
||||||
Self::FetchSize(_) => write!(f, "Action::FetchSize"),
|
|
||||||
Self::Maximize(maximized) => {
|
|
||||||
write!(f, "Action::Maximize({maximized})")
|
|
||||||
}
|
}
|
||||||
Self::Minimize(minimized) => {
|
Self::Close(id) => write!(f, "Action::Close({id:?})"),
|
||||||
write!(f, "Action::Minimize({minimized}")
|
Self::Drag(id) => write!(f, "Action::Drag({id:?})"),
|
||||||
|
Self::Resize(id, size) => {
|
||||||
|
write!(f, "Action::Resize({id:?}, {size:?})")
|
||||||
}
|
}
|
||||||
Self::Move { x, y } => {
|
Self::FetchSize(id, _) => write!(f, "Action::FetchSize({id:?})"),
|
||||||
write!(f, "Action::Move {{ x: {x}, y: {y} }}")
|
Self::Maximize(id, maximized) => {
|
||||||
|
write!(f, "Action::Maximize({id:?}, {maximized})")
|
||||||
}
|
}
|
||||||
Self::ChangeMode(mode) => write!(f, "Action::SetMode({mode:?})"),
|
Self::Minimize(id, minimized) => {
|
||||||
Self::FetchMode(_) => write!(f, "Action::FetchMode"),
|
write!(f, "Action::Minimize({id:?}, {minimized}")
|
||||||
Self::ToggleMaximize => write!(f, "Action::ToggleMaximize"),
|
|
||||||
Self::ToggleDecorations => write!(f, "Action::ToggleDecorations"),
|
|
||||||
Self::RequestUserAttention(_) => {
|
|
||||||
write!(f, "Action::RequestUserAttention")
|
|
||||||
}
|
}
|
||||||
Self::GainFocus => write!(f, "Action::GainFocus"),
|
Self::Move(id, position) => {
|
||||||
Self::ChangeLevel(level) => {
|
write!(f, "Action::Move({id:?}, {position})")
|
||||||
write!(f, "Action::ChangeLevel({level:?})")
|
|
||||||
}
|
}
|
||||||
Self::FetchId(_) => write!(f, "Action::FetchId"),
|
Self::ChangeMode(id, mode) => {
|
||||||
Self::ChangeIcon(_icon) => {
|
write!(f, "Action::SetMode({id:?}, {mode:?})")
|
||||||
write!(f, "Action::ChangeIcon(icon)")
|
|
||||||
}
|
}
|
||||||
Self::Screenshot(_) => write!(f, "Action::Screenshot"),
|
Self::FetchMode(id, _) => write!(f, "Action::FetchMode({id:?})"),
|
||||||
|
Self::ToggleMaximize(id) => {
|
||||||
|
write!(f, "Action::ToggleMaximize({id:?})")
|
||||||
|
}
|
||||||
|
Self::ToggleDecorations(id) => {
|
||||||
|
write!(f, "Action::ToggleDecorations({id:?})")
|
||||||
|
}
|
||||||
|
Self::RequestUserAttention(id, _) => {
|
||||||
|
write!(f, "Action::RequestUserAttention({id:?})")
|
||||||
|
}
|
||||||
|
Self::GainFocus(id) => write!(f, "Action::GainFocus({id:?})"),
|
||||||
|
Self::ChangeLevel(id, level) => {
|
||||||
|
write!(f, "Action::ChangeLevel({id:?}, {level:?})")
|
||||||
|
}
|
||||||
|
Self::FetchId(id, _) => write!(f, "Action::FetchId({id:?})"),
|
||||||
|
Self::ChangeIcon(id, _icon) => {
|
||||||
|
write!(f, "Action::ChangeIcon({id:?})")
|
||||||
|
}
|
||||||
|
Self::Screenshot(id, _) => write!(f, "Action::Screenshot({id:?})"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -182,6 +182,9 @@ pub mod window;
|
||||||
#[cfg(feature = "advanced")]
|
#[cfg(feature = "advanced")]
|
||||||
pub mod advanced;
|
pub mod advanced;
|
||||||
|
|
||||||
|
#[cfg(feature = "multi-window")]
|
||||||
|
pub mod multi_window;
|
||||||
|
|
||||||
pub use style::theme;
|
pub use style::theme;
|
||||||
|
|
||||||
pub use crate::core::alignment;
|
pub use crate::core::alignment;
|
||||||
|
|
|
||||||
4
src/multi_window.rs
Normal file
4
src/multi_window.rs
Normal file
|
|
@ -0,0 +1,4 @@
|
||||||
|
//! Leverage multi-window support in your application.
|
||||||
|
mod application;
|
||||||
|
|
||||||
|
pub use application::Application;
|
||||||
245
src/multi_window/application.rs
Normal file
245
src/multi_window/application.rs
Normal file
|
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -46,14 +46,6 @@ pub struct Settings<Flags> {
|
||||||
///
|
///
|
||||||
/// [`Canvas`]: crate::widget::Canvas
|
/// [`Canvas`]: crate::widget::Canvas
|
||||||
pub antialiasing: bool,
|
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> {
|
impl<Flags> Settings<Flags> {
|
||||||
|
|
@ -71,7 +63,6 @@ impl<Flags> Settings<Flags> {
|
||||||
default_font: default_settings.default_font,
|
default_font: default_settings.default_font,
|
||||||
default_text_size: default_settings.default_text_size,
|
default_text_size: default_settings.default_text_size,
|
||||||
antialiasing: default_settings.antialiasing,
|
antialiasing: default_settings.antialiasing,
|
||||||
exit_on_close_request: default_settings.exit_on_close_request,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -89,7 +80,6 @@ where
|
||||||
default_font: Font::default(),
|
default_font: Font::default(),
|
||||||
default_text_size: Pixels(16.0),
|
default_text_size: Pixels(16.0),
|
||||||
antialiasing: false,
|
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> {
|
fn from(settings: Settings<Flags>) -> iced_winit::Settings<Flags> {
|
||||||
iced_winit::Settings {
|
iced_winit::Settings {
|
||||||
id: settings.id,
|
id: settings.id,
|
||||||
window: settings.window.into(),
|
window: settings.window,
|
||||||
flags: settings.flags,
|
flags: settings.flags,
|
||||||
fonts: settings.fonts,
|
fonts: settings.fonts,
|
||||||
exit_on_close_request: settings.exit_on_close_request,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,8 @@
|
||||||
//! Configure the window of your application in native platforms.
|
//! Configure the window of your application in native platforms.
|
||||||
mod position;
|
|
||||||
mod settings;
|
|
||||||
|
|
||||||
pub mod icon;
|
pub mod icon;
|
||||||
|
|
||||||
pub use icon::Icon;
|
pub use icon::Icon;
|
||||||
pub use position::Position;
|
|
||||||
pub use settings::{PlatformSpecific, Settings};
|
|
||||||
|
|
||||||
pub use crate::core::window::*;
|
pub use crate::core::window::*;
|
||||||
pub use crate::runtime::window::*;
|
pub use crate::runtime::window::*;
|
||||||
|
|
|
||||||
|
|
@ -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),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -8,6 +8,7 @@ use raw_window_handle::{HasRawDisplayHandle, HasRawWindowHandle};
|
||||||
use std::marker::PhantomData;
|
use std::marker::PhantomData;
|
||||||
|
|
||||||
pub struct Compositor<Theme> {
|
pub struct Compositor<Theme> {
|
||||||
|
settings: Settings,
|
||||||
_theme: PhantomData<Theme>,
|
_theme: PhantomData<Theme>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -27,17 +28,16 @@ impl<Theme> crate::graphics::Compositor for Compositor<Theme> {
|
||||||
fn new<W: HasRawWindowHandle + HasRawDisplayHandle>(
|
fn new<W: HasRawWindowHandle + HasRawDisplayHandle>(
|
||||||
settings: Self::Settings,
|
settings: Self::Settings,
|
||||||
_compatible_window: Option<&W>,
|
_compatible_window: Option<&W>,
|
||||||
) -> Result<(Self, Self::Renderer), Error> {
|
) -> Result<Self, Error> {
|
||||||
let (compositor, backend) = new();
|
Ok(new(settings))
|
||||||
|
}
|
||||||
|
|
||||||
Ok((
|
fn create_renderer(&self) -> Self::Renderer {
|
||||||
compositor,
|
Renderer::new(
|
||||||
Renderer::new(
|
Backend::new(),
|
||||||
backend,
|
self.settings.default_font,
|
||||||
settings.default_font,
|
self.settings.default_text_size,
|
||||||
settings.default_text_size,
|
)
|
||||||
),
|
|
||||||
))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn create_surface<W: HasRawWindowHandle + HasRawDisplayHandle>(
|
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) {
|
pub fn new<Theme>(settings: Settings) -> Compositor<Theme> {
|
||||||
(
|
Compositor {
|
||||||
Compositor {
|
settings,
|
||||||
_theme: PhantomData,
|
_theme: PhantomData,
|
||||||
},
|
}
|
||||||
Backend::new(),
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn present<T: AsRef<str>>(
|
pub fn present<T: AsRef<str>>(
|
||||||
|
|
|
||||||
|
|
@ -139,16 +139,14 @@ impl<Theme> Compositor<Theme> {
|
||||||
pub fn new<Theme, W: HasRawWindowHandle + HasRawDisplayHandle>(
|
pub fn new<Theme, W: HasRawWindowHandle + HasRawDisplayHandle>(
|
||||||
settings: Settings,
|
settings: Settings,
|
||||||
compatible_window: Option<&W>,
|
compatible_window: Option<&W>,
|
||||||
) -> Result<(Compositor<Theme>, Backend), Error> {
|
) -> Result<Compositor<Theme>, Error> {
|
||||||
let compositor = futures::executor::block_on(Compositor::request(
|
let compositor = futures::executor::block_on(Compositor::request(
|
||||||
settings,
|
settings,
|
||||||
compatible_window,
|
compatible_window,
|
||||||
))
|
))
|
||||||
.ok_or(Error::GraphicsAdapterNotFound)?;
|
.ok_or(Error::GraphicsAdapterNotFound)?;
|
||||||
|
|
||||||
let backend = compositor.create_backend();
|
Ok(compositor)
|
||||||
|
|
||||||
Ok((compositor, backend))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Presents the given primitives with the given [`Compositor`] and [`Backend`].
|
/// 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>(
|
fn new<W: HasRawWindowHandle + HasRawDisplayHandle>(
|
||||||
settings: Self::Settings,
|
settings: Self::Settings,
|
||||||
compatible_window: Option<&W>,
|
compatible_window: Option<&W>,
|
||||||
) -> Result<(Self, Self::Renderer), Error> {
|
) -> Result<Self, Error> {
|
||||||
let (compositor, backend) = new(settings, compatible_window)?;
|
new(settings, compatible_window)
|
||||||
|
}
|
||||||
|
|
||||||
Ok((
|
fn create_renderer(&self) -> Self::Renderer {
|
||||||
compositor,
|
Renderer::new(
|
||||||
Renderer::new(
|
self.create_backend(),
|
||||||
backend,
|
self.settings.default_font,
|
||||||
settings.default_font,
|
self.settings.default_text_size,
|
||||||
settings.default_text_size,
|
)
|
||||||
),
|
|
||||||
))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn create_surface<W: HasRawWindowHandle + HasRawDisplayHandle>(
|
fn create_surface<W: HasRawWindowHandle + HasRawDisplayHandle>(
|
||||||
|
|
|
||||||
|
|
@ -109,7 +109,7 @@ where
|
||||||
Some(Event::Keyboard(keyboard_event))
|
Some(Event::Keyboard(keyboard_event))
|
||||||
}
|
}
|
||||||
core::Event::Touch(touch_event) => Some(Event::Touch(touch_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))
|
Some(Event::RedrawRequested(instant))
|
||||||
}
|
}
|
||||||
_ => None,
|
_ => None,
|
||||||
|
|
|
||||||
|
|
@ -1003,14 +1003,14 @@ where
|
||||||
|
|
||||||
state.keyboard_modifiers = modifiers;
|
state.keyboard_modifiers = modifiers;
|
||||||
}
|
}
|
||||||
Event::Window(window::Event::Unfocused) => {
|
Event::Window(_, window::Event::Unfocused) => {
|
||||||
let state = state();
|
let state = state();
|
||||||
|
|
||||||
if let Some(focus) = &mut state.is_focused {
|
if let Some(focus) = &mut state.is_focused {
|
||||||
focus.is_window_focused = false;
|
focus.is_window_focused = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Event::Window(window::Event::Focused) => {
|
Event::Window(_, window::Event::Focused) => {
|
||||||
let state = state();
|
let state = state();
|
||||||
|
|
||||||
if let Some(focus) = &mut state.is_focused {
|
if let Some(focus) = &mut state.is_focused {
|
||||||
|
|
@ -1020,7 +1020,7 @@ where
|
||||||
shell.request_redraw(window::RedrawRequest::NextFrame);
|
shell.request_redraw(window::RedrawRequest::NextFrame);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Event::Window(window::Event::RedrawRequested(now)) => {
|
Event::Window(_, window::Event::RedrawRequested(now)) => {
|
||||||
let state = state();
|
let state = state();
|
||||||
|
|
||||||
if let Some(focus) = &mut state.is_focused {
|
if let Some(focus) = &mut state.is_focused {
|
||||||
|
|
|
||||||
|
|
@ -19,6 +19,7 @@ x11 = ["winit/x11"]
|
||||||
wayland = ["winit/wayland"]
|
wayland = ["winit/wayland"]
|
||||||
wayland-dlopen = ["winit/wayland-dlopen"]
|
wayland-dlopen = ["winit/wayland-dlopen"]
|
||||||
wayland-csd-adwaita = ["winit/wayland-csd-adwaita"]
|
wayland-csd-adwaita = ["winit/wayland-csd-adwaita"]
|
||||||
|
multi-window = ["iced_runtime/multi-window"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
iced_graphics.workspace = true
|
iced_graphics.workspace = true
|
||||||
|
|
@ -26,7 +27,6 @@ iced_runtime.workspace = true
|
||||||
iced_style.workspace = true
|
iced_style.workspace = true
|
||||||
|
|
||||||
log.workspace = true
|
log.workspace = true
|
||||||
raw-window-handle.workspace = true
|
|
||||||
thiserror.workspace = true
|
thiserror.workspace = true
|
||||||
tracing.workspace = true
|
tracing.workspace = true
|
||||||
window_clipboard.workspace = true
|
window_clipboard.workspace = true
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,4 @@
|
||||||
//! Create interactive, native cross-platform applications.
|
//! Create interactive, native cross-platform applications.
|
||||||
#[cfg(feature = "trace")]
|
|
||||||
mod profiler;
|
|
||||||
mod state;
|
mod state;
|
||||||
|
|
||||||
pub use state::State;
|
pub use state::State;
|
||||||
|
|
@ -27,11 +25,6 @@ use futures::channel::mpsc;
|
||||||
|
|
||||||
use std::mem::ManuallyDrop;
|
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.
|
/// An interactive, native cross-platform application.
|
||||||
///
|
///
|
||||||
/// This trait is the main entrypoint of Iced. Once implemented, you can run
|
/// This trait is the main entrypoint of Iced. Once implemented, you can run
|
||||||
|
|
@ -119,15 +112,9 @@ where
|
||||||
use futures::Future;
|
use futures::Future;
|
||||||
use winit::event_loop::EventLoopBuilder;
|
use winit::event_loop::EventLoopBuilder;
|
||||||
|
|
||||||
#[cfg(feature = "trace")]
|
|
||||||
let _guard = Profiler::init();
|
|
||||||
|
|
||||||
let mut debug = Debug::new();
|
let mut debug = Debug::new();
|
||||||
debug.startup_started();
|
debug.startup_started();
|
||||||
|
|
||||||
#[cfg(feature = "trace")]
|
|
||||||
let _ = info_span!("Application", "RUN").entered();
|
|
||||||
|
|
||||||
let event_loop = EventLoopBuilder::with_user_event().build();
|
let event_loop = EventLoopBuilder::with_user_event().build();
|
||||||
let proxy = event_loop.create_proxy();
|
let proxy = event_loop.create_proxy();
|
||||||
|
|
||||||
|
|
@ -148,14 +135,15 @@ where
|
||||||
let target = settings.window.platform_specific.target.clone();
|
let target = settings.window.platform_specific.target.clone();
|
||||||
|
|
||||||
let should_be_visible = settings.window.visible;
|
let should_be_visible = settings.window.visible;
|
||||||
let builder = settings
|
let exit_on_close_request = settings.window.exit_on_close_request;
|
||||||
.window
|
|
||||||
.into_builder(
|
let builder = conversion::window_settings(
|
||||||
&application.title(),
|
settings.window,
|
||||||
event_loop.primary_monitor(),
|
&application.title(),
|
||||||
settings.id,
|
event_loop.primary_monitor(),
|
||||||
)
|
settings.id,
|
||||||
.with_visible(false);
|
)
|
||||||
|
.with_visible(false);
|
||||||
|
|
||||||
log::debug!("Window builder: {builder:#?}");
|
log::debug!("Window builder: {builder:#?}");
|
||||||
|
|
||||||
|
|
@ -193,8 +181,8 @@ where
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
let (compositor, mut renderer) =
|
let compositor = C::new(compositor_settings, Some(&window))?;
|
||||||
C::new(compositor_settings, Some(&window))?;
|
let mut renderer = compositor.create_renderer();
|
||||||
|
|
||||||
for font in settings.fonts {
|
for font in settings.fonts {
|
||||||
use crate::core::text::Renderer;
|
use crate::core::text::Renderer;
|
||||||
|
|
@ -205,28 +193,20 @@ where
|
||||||
let (mut event_sender, event_receiver) = mpsc::unbounded();
|
let (mut event_sender, event_receiver) = mpsc::unbounded();
|
||||||
let (control_sender, mut control_receiver) = mpsc::unbounded();
|
let (control_sender, mut control_receiver) = mpsc::unbounded();
|
||||||
|
|
||||||
let mut instance = Box::pin({
|
let mut instance = Box::pin(run_instance::<A, E, C>(
|
||||||
let run_instance = run_instance::<A, E, C>(
|
application,
|
||||||
application,
|
compositor,
|
||||||
compositor,
|
renderer,
|
||||||
renderer,
|
runtime,
|
||||||
runtime,
|
proxy,
|
||||||
proxy,
|
debug,
|
||||||
debug,
|
event_receiver,
|
||||||
event_receiver,
|
control_sender,
|
||||||
control_sender,
|
init_command,
|
||||||
init_command,
|
window,
|
||||||
window,
|
should_be_visible,
|
||||||
should_be_visible,
|
exit_on_close_request,
|
||||||
settings.exit_on_close_request,
|
));
|
||||||
);
|
|
||||||
|
|
||||||
#[cfg(feature = "trace")]
|
|
||||||
let run_instance =
|
|
||||||
run_instance.instrument(info_span!("Application", "LOOP"));
|
|
||||||
|
|
||||||
run_instance
|
|
||||||
});
|
|
||||||
|
|
||||||
let mut context = task::Context::from_waker(task::noop_waker_ref());
|
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
|
// Then, we can use the `interface_state` here to decide if a redraw
|
||||||
// is needed right away, or simply wait until a specific time.
|
// is needed right away, or simply wait until a specific time.
|
||||||
let redraw_event = Event::Window(
|
let redraw_event = Event::Window(
|
||||||
|
window::Id::MAIN,
|
||||||
window::Event::RedrawRequested(Instant::now()),
|
window::Event::RedrawRequested(Instant::now()),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
@ -488,9 +469,6 @@ async fn run_instance<A, E, C>(
|
||||||
messages.push(message);
|
messages.push(message);
|
||||||
}
|
}
|
||||||
event::Event::RedrawRequested(_) => {
|
event::Event::RedrawRequested(_) => {
|
||||||
#[cfg(feature = "trace")]
|
|
||||||
let _ = info_span!("Application", "FRAME").entered();
|
|
||||||
|
|
||||||
let physical_size = state.physical_size();
|
let physical_size = state.physical_size();
|
||||||
|
|
||||||
if physical_size.width == 0 || physical_size.height == 0 {
|
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);
|
state.update(&window, &window_event, &mut debug);
|
||||||
|
|
||||||
if let Some(event) = conversion::window_event(
|
if let Some(event) = conversion::window_event(
|
||||||
|
window::Id::MAIN,
|
||||||
&window_event,
|
&window_event,
|
||||||
state.scale_factor(),
|
state.scale_factor(),
|
||||||
state.modifiers(),
|
state.modifiers(),
|
||||||
|
|
@ -629,24 +608,12 @@ pub fn build_user_interface<'a, A: Application>(
|
||||||
where
|
where
|
||||||
<A::Renderer as core::Renderer>::Theme: StyleSheet,
|
<A::Renderer as core::Renderer>::Theme: StyleSheet,
|
||||||
{
|
{
|
||||||
#[cfg(feature = "trace")]
|
|
||||||
let view_span = info_span!("Application", "VIEW").entered();
|
|
||||||
|
|
||||||
debug.view_started();
|
debug.view_started();
|
||||||
let view = application.view();
|
let view = application.view();
|
||||||
|
|
||||||
#[cfg(feature = "trace")]
|
|
||||||
let _ = view_span.exit();
|
|
||||||
debug.view_finished();
|
debug.view_finished();
|
||||||
|
|
||||||
#[cfg(feature = "trace")]
|
|
||||||
let layout_span = info_span!("Application", "LAYOUT").entered();
|
|
||||||
|
|
||||||
debug.layout_started();
|
debug.layout_started();
|
||||||
let user_interface = UserInterface::build(view, size, cache, renderer);
|
let user_interface = UserInterface::build(view, size, cache, renderer);
|
||||||
|
|
||||||
#[cfg(feature = "trace")]
|
|
||||||
let _ = layout_span.exit();
|
|
||||||
debug.layout_finished();
|
debug.layout_finished();
|
||||||
|
|
||||||
user_interface
|
user_interface
|
||||||
|
|
@ -673,16 +640,10 @@ pub fn update<A: Application, C, E: Executor>(
|
||||||
<A::Renderer as core::Renderer>::Theme: StyleSheet,
|
<A::Renderer as core::Renderer>::Theme: StyleSheet,
|
||||||
{
|
{
|
||||||
for message in messages.drain(..) {
|
for message in messages.drain(..) {
|
||||||
#[cfg(feature = "trace")]
|
|
||||||
let update_span = info_span!("Application", "UPDATE").entered();
|
|
||||||
|
|
||||||
debug.log_message(&message);
|
debug.log_message(&message);
|
||||||
|
|
||||||
debug.update_started();
|
debug.update_started();
|
||||||
let command = runtime.enter(|| application.update(message));
|
let command = runtime.enter(|| application.update(message));
|
||||||
|
|
||||||
#[cfg(feature = "trace")]
|
|
||||||
let _ = update_span.exit();
|
|
||||||
debug.update_finished();
|
debug.update_finished();
|
||||||
|
|
||||||
run_command(
|
run_command(
|
||||||
|
|
@ -752,20 +713,27 @@ pub fn run_command<A, C, E>(
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
command::Action::Window(action) => match action {
|
command::Action::Window(action) => match action {
|
||||||
window::Action::Close => {
|
window::Action::Close(_id) => {
|
||||||
*should_exit = true;
|
*should_exit = true;
|
||||||
}
|
}
|
||||||
window::Action::Drag => {
|
window::Action::Drag(_id) => {
|
||||||
let _res = window.drag_window();
|
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 {
|
window.set_inner_size(winit::dpi::LogicalSize {
|
||||||
width: size.width,
|
width: size.width,
|
||||||
height: size.height,
|
height: size.height,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
window::Action::FetchSize(callback) => {
|
window::Action::FetchSize(_id, callback) => {
|
||||||
let size = window.inner_size();
|
let size =
|
||||||
|
window.inner_size().to_logical(window.scale_factor());
|
||||||
|
|
||||||
proxy
|
proxy
|
||||||
.send_event(callback(Size::new(
|
.send_event(callback(Size::new(
|
||||||
|
|
@ -774,29 +742,29 @@ pub fn run_command<A, C, E>(
|
||||||
)))
|
)))
|
||||||
.expect("Send message to event loop");
|
.expect("Send message to event loop");
|
||||||
}
|
}
|
||||||
window::Action::Maximize(maximized) => {
|
window::Action::Maximize(_id, maximized) => {
|
||||||
window.set_maximized(maximized);
|
window.set_maximized(maximized);
|
||||||
}
|
}
|
||||||
window::Action::Minimize(minimized) => {
|
window::Action::Minimize(_id, minimized) => {
|
||||||
window.set_minimized(minimized);
|
window.set_minimized(minimized);
|
||||||
}
|
}
|
||||||
window::Action::Move { x, y } => {
|
window::Action::Move(_id, position) => {
|
||||||
window.set_outer_position(winit::dpi::LogicalPosition {
|
window.set_outer_position(winit::dpi::LogicalPosition {
|
||||||
x,
|
x: position.x,
|
||||||
y,
|
y: position.y,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
window::Action::ChangeMode(mode) => {
|
window::Action::ChangeMode(_id, mode) => {
|
||||||
window.set_visible(conversion::visible(mode));
|
window.set_visible(conversion::visible(mode));
|
||||||
window.set_fullscreen(conversion::fullscreen(
|
window.set_fullscreen(conversion::fullscreen(
|
||||||
window.current_monitor(),
|
window.current_monitor(),
|
||||||
mode,
|
mode,
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
window::Action::ChangeIcon(icon) => {
|
window::Action::ChangeIcon(_id, icon) => {
|
||||||
window.set_window_icon(conversion::icon(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) {
|
let mode = if window.is_visible().unwrap_or(true) {
|
||||||
conversion::mode(window.fullscreen())
|
conversion::mode(window.fullscreen())
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -807,29 +775,29 @@ pub fn run_command<A, C, E>(
|
||||||
.send_event(tag(mode))
|
.send_event(tag(mode))
|
||||||
.expect("Send message to event loop");
|
.expect("Send message to event loop");
|
||||||
}
|
}
|
||||||
window::Action::ToggleMaximize => {
|
window::Action::ToggleMaximize(_id) => {
|
||||||
window.set_maximized(!window.is_maximized());
|
window.set_maximized(!window.is_maximized());
|
||||||
}
|
}
|
||||||
window::Action::ToggleDecorations => {
|
window::Action::ToggleDecorations(_id) => {
|
||||||
window.set_decorations(!window.is_decorated());
|
window.set_decorations(!window.is_decorated());
|
||||||
}
|
}
|
||||||
window::Action::RequestUserAttention(user_attention) => {
|
window::Action::RequestUserAttention(_id, user_attention) => {
|
||||||
window.request_user_attention(
|
window.request_user_attention(
|
||||||
user_attention.map(conversion::user_attention),
|
user_attention.map(conversion::user_attention),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
window::Action::GainFocus => {
|
window::Action::GainFocus(_id) => {
|
||||||
window.focus_window();
|
window.focus_window();
|
||||||
}
|
}
|
||||||
window::Action::ChangeLevel(level) => {
|
window::Action::ChangeLevel(_id, level) => {
|
||||||
window.set_window_level(conversion::window_level(level));
|
window.set_window_level(conversion::window_level(level));
|
||||||
}
|
}
|
||||||
window::Action::FetchId(tag) => {
|
window::Action::FetchId(_id, tag) => {
|
||||||
proxy
|
proxy
|
||||||
.send_event(tag(window.id().into()))
|
.send_event(tag(window.id().into()))
|
||||||
.expect("Send message to event loop");
|
.expect("Send message to event loop");
|
||||||
}
|
}
|
||||||
window::Action::Screenshot(tag) => {
|
window::Action::Screenshot(_id, tag) => {
|
||||||
let bytes = compositor.screenshot(
|
let bytes = compositor.screenshot(
|
||||||
renderer,
|
renderer,
|
||||||
surface,
|
surface,
|
||||||
|
|
|
||||||
|
|
@ -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,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -6,11 +6,128 @@ use crate::core::keyboard;
|
||||||
use crate::core::mouse;
|
use crate::core::mouse;
|
||||||
use crate::core::touch;
|
use crate::core::touch;
|
||||||
use crate::core::window;
|
use crate::core::window;
|
||||||
use crate::core::{Event, Point};
|
use crate::core::{Event, Point, Size};
|
||||||
use crate::Position;
|
|
||||||
|
/// 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.
|
/// Converts a winit window event into an iced event.
|
||||||
pub fn window_event(
|
pub fn window_event(
|
||||||
|
id: window::Id,
|
||||||
event: &winit::event::WindowEvent<'_>,
|
event: &winit::event::WindowEvent<'_>,
|
||||||
scale_factor: f64,
|
scale_factor: f64,
|
||||||
modifiers: winit::event::ModifiersState,
|
modifiers: winit::event::ModifiersState,
|
||||||
|
|
@ -21,21 +138,27 @@ pub fn window_event(
|
||||||
WindowEvent::Resized(new_size) => {
|
WindowEvent::Resized(new_size) => {
|
||||||
let logical_size = new_size.to_logical(scale_factor);
|
let logical_size = new_size.to_logical(scale_factor);
|
||||||
|
|
||||||
Some(Event::Window(window::Event::Resized {
|
Some(Event::Window(
|
||||||
width: logical_size.width,
|
id,
|
||||||
height: logical_size.height,
|
window::Event::Resized {
|
||||||
}))
|
width: logical_size.width,
|
||||||
|
height: logical_size.height,
|
||||||
|
},
|
||||||
|
))
|
||||||
}
|
}
|
||||||
WindowEvent::ScaleFactorChanged { new_inner_size, .. } => {
|
WindowEvent::ScaleFactorChanged { new_inner_size, .. } => {
|
||||||
let logical_size = new_inner_size.to_logical(scale_factor);
|
let logical_size = new_inner_size.to_logical(scale_factor);
|
||||||
|
|
||||||
Some(Event::Window(window::Event::Resized {
|
Some(Event::Window(
|
||||||
width: logical_size.width,
|
id,
|
||||||
height: logical_size.height,
|
window::Event::Resized {
|
||||||
}))
|
width: logical_size.width,
|
||||||
|
height: logical_size.height,
|
||||||
|
},
|
||||||
|
))
|
||||||
}
|
}
|
||||||
WindowEvent::CloseRequested => {
|
WindowEvent::CloseRequested => {
|
||||||
Some(Event::Window(window::Event::CloseRequested))
|
Some(Event::Window(id, window::Event::CloseRequested))
|
||||||
}
|
}
|
||||||
WindowEvent::CursorMoved { position, .. } => {
|
WindowEvent::CursorMoved { position, .. } => {
|
||||||
let position = position.to_logical::<f64>(scale_factor);
|
let position = position.to_logical::<f64>(scale_factor);
|
||||||
|
|
@ -113,19 +236,22 @@ pub fn window_event(
|
||||||
WindowEvent::ModifiersChanged(new_modifiers) => Some(Event::Keyboard(
|
WindowEvent::ModifiersChanged(new_modifiers) => Some(Event::Keyboard(
|
||||||
keyboard::Event::ModifiersChanged(self::modifiers(*new_modifiers)),
|
keyboard::Event::ModifiersChanged(self::modifiers(*new_modifiers)),
|
||||||
)),
|
)),
|
||||||
WindowEvent::Focused(focused) => Some(Event::Window(if *focused {
|
WindowEvent::Focused(focused) => Some(Event::Window(
|
||||||
window::Event::Focused
|
id,
|
||||||
} else {
|
if *focused {
|
||||||
window::Event::Unfocused
|
window::Event::Focused
|
||||||
})),
|
} else {
|
||||||
|
window::Event::Unfocused
|
||||||
|
},
|
||||||
|
)),
|
||||||
WindowEvent::HoveredFile(path) => {
|
WindowEvent::HoveredFile(path) => {
|
||||||
Some(Event::Window(window::Event::FileHovered(path.clone())))
|
Some(Event::Window(id, window::Event::FileHovered(path.clone())))
|
||||||
}
|
}
|
||||||
WindowEvent::DroppedFile(path) => {
|
WindowEvent::DroppedFile(path) => {
|
||||||
Some(Event::Window(window::Event::FileDropped(path.clone())))
|
Some(Event::Window(id, window::Event::FileDropped(path.clone())))
|
||||||
}
|
}
|
||||||
WindowEvent::HoveredFileCancelled => {
|
WindowEvent::HoveredFileCancelled => {
|
||||||
Some(Event::Window(window::Event::FilesHoveredLeft))
|
Some(Event::Window(id, window::Event::FilesHoveredLeft))
|
||||||
}
|
}
|
||||||
WindowEvent::Touch(touch) => {
|
WindowEvent::Touch(touch) => {
|
||||||
Some(Event::Touch(touch_event(*touch, scale_factor)))
|
Some(Event::Touch(touch_event(*touch, scale_factor)))
|
||||||
|
|
@ -134,7 +260,7 @@ pub fn window_event(
|
||||||
let winit::dpi::LogicalPosition { x, y } =
|
let winit::dpi::LogicalPosition { x, y } =
|
||||||
position.to_logical(scale_factor);
|
position.to_logical(scale_factor);
|
||||||
|
|
||||||
Some(Event::Window(window::Event::Moved { x, y }))
|
Some(Event::Window(id, window::Event::Moved { x, y }))
|
||||||
}
|
}
|
||||||
_ => None,
|
_ => 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
|
/// [`winit`]: https://github.com/rust-windowing/winit
|
||||||
pub fn position(
|
pub fn position(
|
||||||
monitor: Option<&winit::monitor::MonitorHandle>,
|
monitor: Option<&winit::monitor::MonitorHandle>,
|
||||||
(width, height): (u32, u32),
|
size: Size,
|
||||||
position: Position,
|
position: window::Position,
|
||||||
) -> Option<winit::dpi::Position> {
|
) -> Option<winit::dpi::Position> {
|
||||||
match position {
|
match position {
|
||||||
Position::Default => None,
|
window::Position::Default => None,
|
||||||
Position::Specific(x, y) => {
|
window::Position::Specific(position) => {
|
||||||
Some(winit::dpi::Position::Logical(winit::dpi::LogicalPosition {
|
Some(winit::dpi::Position::Logical(winit::dpi::LogicalPosition {
|
||||||
x: f64::from(x),
|
x: f64::from(position.x),
|
||||||
y: f64::from(y),
|
y: f64::from(position.y),
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
Position::Centered => {
|
window::Position::Centered => {
|
||||||
if let Some(monitor) = monitor {
|
if let Some(monitor) = monitor {
|
||||||
let start = monitor.position();
|
let start = monitor.position();
|
||||||
|
|
||||||
|
|
@ -178,8 +304,8 @@ pub fn position(
|
||||||
|
|
||||||
let centered: winit::dpi::PhysicalPosition<i32> =
|
let centered: winit::dpi::PhysicalPosition<i32> =
|
||||||
winit::dpi::LogicalPosition {
|
winit::dpi::LogicalPosition {
|
||||||
x: (resolution.width - f64::from(width)) / 2.0,
|
x: (resolution.width - f64::from(size.width)) / 2.0,
|
||||||
y: (resolution.height - f64::from(height)) / 2.0,
|
y: (resolution.height - f64::from(size.height)) / 2.0,
|
||||||
}
|
}
|
||||||
.to_physical(monitor.scale_factor());
|
.to_physical(monitor.scale_factor());
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -33,6 +33,9 @@ pub use iced_runtime::futures;
|
||||||
pub use iced_style as style;
|
pub use iced_style as style;
|
||||||
pub use winit;
|
pub use winit;
|
||||||
|
|
||||||
|
#[cfg(feature = "multi-window")]
|
||||||
|
pub mod multi_window;
|
||||||
|
|
||||||
#[cfg(feature = "application")]
|
#[cfg(feature = "application")]
|
||||||
pub mod application;
|
pub mod application;
|
||||||
pub mod clipboard;
|
pub mod clipboard;
|
||||||
|
|
@ -43,17 +46,11 @@ pub mod settings;
|
||||||
pub mod system;
|
pub mod system;
|
||||||
|
|
||||||
mod error;
|
mod error;
|
||||||
mod position;
|
|
||||||
mod proxy;
|
mod proxy;
|
||||||
|
|
||||||
#[cfg(feature = "application")]
|
#[cfg(feature = "application")]
|
||||||
pub use application::Application;
|
pub use application::Application;
|
||||||
#[cfg(feature = "trace")]
|
|
||||||
pub use application::Profiler;
|
|
||||||
pub use clipboard::Clipboard;
|
pub use clipboard::Clipboard;
|
||||||
pub use error::Error;
|
pub use error::Error;
|
||||||
pub use position::Position;
|
|
||||||
pub use proxy::Proxy;
|
pub use proxy::Proxy;
|
||||||
pub use settings::Settings;
|
pub use settings::Settings;
|
||||||
|
|
||||||
pub use iced_graphics::Viewport;
|
|
||||||
|
|
|
||||||
1212
winit/src/multi_window.rs
Normal file
1212
winit/src/multi_window.rs
Normal file
File diff suppressed because it is too large
Load diff
240
winit/src/multi_window/state.rs
Normal file
240
winit/src/multi_window/state.rs
Normal file
|
|
@ -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());
|
||||||
|
}
|
||||||
|
}
|
||||||
156
winit/src/multi_window/window_manager.rs
Normal file
156
winit/src/multi_window/window_manager.rs
Normal file
|
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,40 +1,7 @@
|
||||||
//! Configure your application.
|
//! Configure your application.
|
||||||
#[cfg(target_os = "windows")]
|
use crate::core::window;
|
||||||
#[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 std::borrow::Cow;
|
use std::borrow::Cow;
|
||||||
use std::fmt;
|
|
||||||
|
|
||||||
/// The settings of an application.
|
/// The settings of an application.
|
||||||
#[derive(Debug, Clone, Default)]
|
#[derive(Debug, Clone, Default)]
|
||||||
|
|
@ -45,8 +12,8 @@ pub struct Settings<Flags> {
|
||||||
/// communicate with it through the windowing system.
|
/// communicate with it through the windowing system.
|
||||||
pub id: Option<String>,
|
pub id: Option<String>,
|
||||||
|
|
||||||
/// The [`Window`] settings.
|
/// The [`window::Settings`].
|
||||||
pub window: Window,
|
pub window: window::Settings,
|
||||||
|
|
||||||
/// The data needed to initialize an [`Application`].
|
/// The data needed to initialize an [`Application`].
|
||||||
///
|
///
|
||||||
|
|
@ -55,197 +22,4 @@ pub struct Settings<Flags> {
|
||||||
|
|
||||||
/// The fonts to load on boot.
|
/// The fonts to load on boot.
|
||||||
pub fonts: Vec<Cow<'static, [u8]>>,
|
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(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue