refactored window storage;

new helper window events (Destroyed, Created);
clippy + fmt;
This commit is contained in:
Bingus 2023-07-12 19:21:05 -07:00
parent 633f405f3f
commit d53ccc857d
No known key found for this signature in database
GPG key ID: 5F84D2AA40A9F170
56 changed files with 1508 additions and 1819 deletions

View file

@ -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!

View file

@ -41,7 +41,7 @@ system = ["iced_winit/system"]
web-colors = ["iced_renderer/web-colors"] web-colors = ["iced_renderer/web-colors"]
# Enables the advanced module # Enables the advanced module
advanced = [] advanced = []
# Enables experimental multi-window support for iced_winit + wgpu. # Enables experimental multi-window support.
multi-window = ["iced_winit/multi-window"] multi-window = ["iced_winit/multi-window"]
[badges] [badges]

View file

@ -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/

View file

@ -20,5 +20,8 @@ optional = true
[target.'cfg(target_arch = "wasm32")'.dependencies] [target.'cfg(target_arch = "wasm32")'.dependencies]
instant = "0.1" instant = "0.1"
[target.'cfg(windows)'.dependencies.raw-window-handle]
version = "0.5.2"
[dev-dependencies] [dev-dependencies]
approx = "0.5" approx = "0.5"

View file

@ -2,14 +2,20 @@
pub mod icon; pub mod icon;
mod event; mod event;
mod id;
mod level; mod level;
mod mode; mod mode;
mod position;
mod redraw_request; mod redraw_request;
mod settings;
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;

View file

@ -1,4 +1,5 @@
use crate::time::Instant; use crate::time::Instant;
use crate::Size;
use std::path::PathBuf; use std::path::PathBuf;
@ -32,6 +33,22 @@ pub enum Event {
/// occurs. /// occurs.
CloseRequested, CloseRequested,
/// A window was destroyed by the runtime.
Destroyed,
/// A window was created.
///
/// **Note:** this event is not supported on Wayland.
Created {
/// The position of the created 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.
position: (i32, i32),
/// The size of the created window. This is its "inner" size, or the size of the
/// client area, in logical pixels.
size: Size<u32>,
},
/// A window was focused. /// A window was focused.
Focused, Focused,

View file

@ -1,18 +1,17 @@
use std::collections::hash_map::DefaultHasher; use std::collections::hash_map::DefaultHasher;
use std::fmt::{Display, Formatter};
use std::hash::{Hash, Hasher}; use std::hash::{Hash, Hasher};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)] #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
/// The ID of the window. /// The id of the window.
/// ///
/// Internally Iced uses `window::Id::MAIN` as the first window spawned. /// Internally Iced reserves `window::Id::MAIN` for the first window spawned.
pub struct Id(u64); pub struct Id(u64);
impl Id { impl Id {
/// The reserved window ID for the primary window in an Iced application. /// The reserved window [`Id`] for the first window in an Iced application.
pub const MAIN: Self = Id(0); pub const MAIN: Self = Id(0);
/// Creates a new unique window ID. /// Creates a new unique window [`Id`].
pub fn new(id: impl Hash) -> Id { pub fn new(id: impl Hash) -> Id {
let mut hasher = DefaultHasher::new(); let mut hasher = DefaultHasher::new();
id.hash(&mut hasher); id.hash(&mut hasher);
@ -20,9 +19,3 @@ impl Id {
Id(hasher.finish()) Id(hasher.finish())
} }
} }
impl Display for Id {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(f, "Id({})", self.0)
}
}

View file

@ -0,0 +1,22 @@
/// 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
}
}

View file

@ -1,6 +1,26 @@
use crate::window::{Icon, Level, Position}; use crate::window::{Icon, Level, Position};
pub use iced_winit::settings::PlatformSpecific; #[cfg(target_os = "windows")]
#[path = "settings/windows.rs"]
mod platform;
#[cfg(target_os = "macos")]
#[path = "settings/macos.rs"]
mod platform;
#[cfg(target_arch = "wasm32")]
#[path = "settings/wasm.rs"]
mod platform;
#[cfg(not(any(
target_os = "windows",
target_os = "macos",
target_arch = "wasm32"
)))]
#[path = "settings/other.rs"]
mod platform;
pub use platform::PlatformSpecific;
/// The window settings of an application. /// The window settings of an application.
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
@ -56,21 +76,3 @@ impl Default for Settings {
} }
} }
} }
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,
}
}
}

View file

@ -26,7 +26,7 @@ struct Events {
enum Message { enum Message {
EventOccurred(Event), EventOccurred(Event),
Toggled(bool), Toggled(bool),
Exit(window::Id), Exit,
} }
impl Application for Events { impl Application for Events {
@ -55,7 +55,8 @@ impl Application for Events {
Command::none() Command::none()
} }
Message::EventOccurred(event) => { Message::EventOccurred(event) => {
if let Event::Window(id, window::Event::CloseRequested) = event { if let Event::Window(id, window::Event::CloseRequested) = event
{
window::close(id) window::close(id)
} else { } else {
Command::none() Command::none()
@ -66,7 +67,7 @@ impl Application for Events {
Command::none() Command::none()
} }
Message::Exit(id) => window::close(id), Message::Exit => window::close(window::Id::MAIN),
} }
} }

View file

@ -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;

View file

@ -6,8 +6,8 @@ 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::core::mouse;
use iced_winit::core::renderer; use iced_winit::core::renderer;
use iced_winit::core::{mouse, window};
use iced_winit::core::{Color, Size}; use iced_winit::core::{Color, Size};
use iced_winit::runtime::program; use iced_winit::runtime::program;
use iced_winit::runtime::Debug; use iced_winit::runtime::Debug;

View file

@ -277,7 +277,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,

View file

@ -198,7 +198,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(

View file

@ -6,4 +6,4 @@ edition = "2021"
publish = false publish = false
[dependencies] [dependencies]
iced = { path = "../..", features = ["debug", "multi-window"] } iced = { path = "../..", features = ["debug", "multi-window"] }

View file

@ -1,25 +1,32 @@
use iced::multi_window::{self, Application}; use iced::multi_window::{self, Application};
use iced::widget::{button, column, container, scrollable, text, text_input}; use iced::widget::{button, column, container, scrollable, text, text_input};
use iced::window::{Id, Position};
use iced::{ use iced::{
executor, window, Alignment, Command, Element, Length, Settings, Theme, executor, subscription, window, Alignment, Command, Element, Length,
Settings, Subscription, Theme,
}; };
use std::collections::HashMap; use std::collections::HashMap;
fn main() -> iced::Result { fn main() -> iced::Result {
Example::run(Settings::default()) Example::run(Settings {
exit_on_close_request: false,
..Default::default()
})
} }
#[derive(Default)] #[derive(Default)]
struct Example { struct Example {
windows_count: usize,
windows: HashMap<window::Id, Window>, windows: HashMap<window::Id, Window>,
next_window_pos: window::Position,
} }
#[derive(Debug)]
struct Window { struct Window {
id: window::Id,
title: String, title: String,
scale_input: String, scale_input: String,
current_scale: f64, current_scale: f64,
theme: Theme,
input_id: iced::widget::text_input::Id,
} }
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
@ -28,6 +35,8 @@ enum Message {
ScaleChanged(window::Id, String), ScaleChanged(window::Id, String),
TitleChanged(window::Id, String), TitleChanged(window::Id, String),
CloseWindow(window::Id), CloseWindow(window::Id),
WindowDestroyed(window::Id),
WindowCreated(window::Id, (i32, i32)),
NewWindow, NewWindow,
} }
@ -40,11 +49,8 @@ impl multi_window::Application for Example {
fn new(_flags: ()) -> (Self, Command<Message>) { fn new(_flags: ()) -> (Self, Command<Message>) {
( (
Example { Example {
windows_count: 0, windows: HashMap::from([(window::Id::MAIN, Window::new(1))]),
windows: HashMap::from([( next_window_pos: Position::Default,
window::Id::MAIN,
Window::new(window::Id::MAIN),
)]),
}, },
Command::none(), Command::none(),
) )
@ -82,12 +88,32 @@ impl multi_window::Application for Example {
Message::CloseWindow(id) => { Message::CloseWindow(id) => {
return window::close(id); return window::close(id);
} }
Message::NewWindow => { Message::WindowDestroyed(id) => {
self.windows_count += 1; self.windows.remove(&id);
let id = window::Id::new(self.windows_count); }
self.windows.insert(id, Window::new(id)); Message::WindowCreated(id, position) => {
self.next_window_pos = window::Position::Specific(
position.0 + 20,
position.1 + 20,
);
return window::spawn(id, window::Settings::default()); if let Some(window) = self.windows.get(&id) {
return text_input::focus(window.input_id.clone());
}
}
Message::NewWindow => {
let count = self.windows.len() + 1;
let id = window::Id::new(count);
self.windows.insert(id, Window::new(count));
return window::spawn(
id,
window::Settings {
position: self.next_window_pos,
..Default::default()
},
);
} }
} }
@ -95,13 +121,9 @@ impl multi_window::Application for Example {
} }
fn view(&self, window: window::Id) -> Element<Message> { fn view(&self, window: window::Id) -> Element<Message> {
let window = self let content = self.windows.get(&window).unwrap().view(window);
.windows
.get(&window)
.map(|window| window.view())
.unwrap();
container(window) container(content)
.width(Length::Fill) .width(Length::Fill)
.height(Length::Fill) .height(Length::Fill)
.center_x() .center_x()
@ -109,6 +131,10 @@ impl multi_window::Application for Example {
.into() .into()
} }
fn theme(&self, window: Id) -> Self::Theme {
self.windows.get(&window).unwrap().theme.clone()
}
fn scale_factor(&self, window: window::Id) -> f64 { fn scale_factor(&self, window: window::Id) -> f64 {
self.windows self.windows
.get(&window) .get(&window)
@ -116,55 +142,71 @@ impl multi_window::Application for Example {
.unwrap_or(1.0) .unwrap_or(1.0)
} }
fn close_requested(&self, window: window::Id) -> Self::Message { fn subscription(&self) -> Subscription<Self::Message> {
Message::CloseWindow(window) subscription::events_with(|event, _| {
if let iced::Event::Window(id, window_event) = event {
match window_event {
window::Event::CloseRequested => {
Some(Message::CloseWindow(id))
}
window::Event::Destroyed => {
Some(Message::WindowDestroyed(id))
}
window::Event::Created { position, .. } => {
Some(Message::WindowCreated(id, position))
}
_ => None,
}
} else {
None
}
})
} }
} }
impl Window { impl Window {
fn new(id: window::Id) -> Self { fn new(count: usize) -> Self {
Self { Self {
id, title: format!("Window_{}", count),
title: "Window".to_string(),
scale_input: "1.0".to_string(), scale_input: "1.0".to_string(),
current_scale: 1.0, current_scale: 1.0,
theme: if count % 2 == 0 {
Theme::Light
} else {
Theme::Dark
},
input_id: text_input::Id::unique(),
} }
} }
fn view(&self) -> Element<Message> { fn view(&self, id: window::Id) -> Element<Message> {
window_view(self.id, &self.scale_input, &self.title) 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()
} }
} }
fn window_view<'a>(
id: window::Id,
scale_input: &'a str,
title: &'a str,
) -> Element<'a, Message> {
let scale_input = column![
text("Window scale factor:"),
text_input("Window Scale", scale_input, move |msg| {
Message::ScaleInputChanged(id, msg)
})
.on_submit(Message::ScaleChanged(id, scale_input.to_string()))
];
let title_input = column![
text("Window title:"),
text_input("Window Title", title, move |msg| {
Message::TitleChanged(id, msg)
})
];
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()
}

View file

@ -1,12 +0,0 @@
[package]
name = "multi_window_panes"
version = "0.1.0"
authors = ["Richard Custodio <richardsoncusto@gmail.com>"]
edition = "2021"
publish = false
[dependencies]
iced = { path = "../..", features = ["debug", "multi-window", "tokio"] }
env_logger = "0.10.0"
iced_native = { path = "../../native" }
iced_lazy = { path = "../../lazy" }

View file

@ -1,639 +0,0 @@
use iced::alignment::{self, Alignment};
use iced::keyboard;
use iced::multi_window::Application;
use iced::theme::{self, Theme};
use iced::widget::pane_grid::{self, PaneGrid};
use iced::widget::{
button, column, container, pick_list, row, scrollable, text, text_input,
};
use iced::window;
use iced::{executor, time};
use iced::{Color, Command, Element, Length, Settings, Size, Subscription};
use iced_lazy::responsive;
use iced_native::{event, subscription, Event};
use iced_native::widget::scrollable::{Properties, RelativeOffset};
use iced_native::window::Id;
use std::collections::HashMap;
use std::time::{Duration, Instant};
pub fn main() -> iced::Result {
env_logger::init();
Example::run(Settings::default())
}
struct Example {
windows: HashMap<window::Id, Window>,
panes_created: usize,
count: usize,
_focused: window::Id,
}
#[derive(Debug)]
struct Window {
title: String,
scale: f64,
theme: Theme,
panes: pane_grid::State<Pane>,
focus: Option<pane_grid::Pane>,
}
#[derive(Debug, Clone)]
enum Message {
Window(window::Id, WindowMessage),
CountIncremented(Instant),
}
#[derive(Debug, Clone)]
enum WindowMessage {
Split(pane_grid::Axis, pane_grid::Pane),
SplitFocused(pane_grid::Axis),
FocusAdjacent(pane_grid::Direction),
Clicked(pane_grid::Pane),
Dragged(pane_grid::DragEvent),
PopOut(pane_grid::Pane),
Resized(pane_grid::ResizeEvent),
TitleChanged(String),
ToggleMoving(pane_grid::Pane),
TogglePin(pane_grid::Pane),
Close(pane_grid::Pane),
CloseFocused,
SelectedWindow(pane_grid::Pane, SelectableWindow),
CloseWindow,
SnapToggle,
}
impl Application for Example {
type Executor = executor::Default;
type Message = Message;
type Theme = Theme;
type Flags = ();
fn new(_flags: ()) -> (Self, Command<Message>) {
let (panes, _) =
pane_grid::State::new(Pane::new(0, pane_grid::Axis::Horizontal));
let window = Window {
panes,
focus: None,
title: String::from("Default window"),
scale: 1.0,
theme: Theme::default(),
};
(
Example {
windows: HashMap::from([(window::Id::MAIN, window)]),
panes_created: 1,
count: 0,
_focused: window::Id::MAIN,
},
Command::none(),
)
}
fn title(&self, window: window::Id) -> String {
self.windows
.get(&window)
.map(|w| w.title.clone())
.unwrap_or(String::from("New Window"))
}
fn update(&mut self, message: Message) -> Command<Message> {
match message {
Message::Window(id, message) => match message {
WindowMessage::SnapToggle => {
let window = self.windows.get_mut(&id).unwrap();
if let Some(focused) = &window.focus {
let pane = window.panes.get_mut(focused).unwrap();
let cmd = scrollable::snap_to(
pane.scrollable_id.clone(),
if pane.snapped {
RelativeOffset::START
} else {
RelativeOffset::END
},
);
pane.snapped = !pane.snapped;
return cmd;
}
}
WindowMessage::Split(axis, pane) => {
let window = self.windows.get_mut(&id).unwrap();
let result = window.panes.split(
axis,
&pane,
Pane::new(self.panes_created, axis),
);
if let Some((pane, _)) = result {
window.focus = Some(pane);
}
self.panes_created += 1;
}
WindowMessage::SplitFocused(axis) => {
let window = self.windows.get_mut(&id).unwrap();
if let Some(pane) = window.focus {
let result = window.panes.split(
axis,
&pane,
Pane::new(self.panes_created, axis),
);
if let Some((pane, _)) = result {
window.focus = Some(pane);
}
self.panes_created += 1;
}
}
WindowMessage::FocusAdjacent(direction) => {
let window = self.windows.get_mut(&id).unwrap();
if let Some(pane) = window.focus {
if let Some(adjacent) =
window.panes.adjacent(&pane, direction)
{
window.focus = Some(adjacent);
}
}
}
WindowMessage::Clicked(pane) => {
let window = self.windows.get_mut(&id).unwrap();
window.focus = Some(pane);
}
WindowMessage::CloseWindow => {
let _ = self.windows.remove(&id);
return window::close(id);
}
WindowMessage::Resized(pane_grid::ResizeEvent {
split,
ratio,
}) => {
let window = self.windows.get_mut(&id).unwrap();
window.panes.resize(&split, ratio);
}
WindowMessage::SelectedWindow(pane, selected) => {
let window = self.windows.get_mut(&id).unwrap();
let (mut pane, _) = window.panes.close(&pane).unwrap();
pane.is_moving = false;
if let Some(window) = self.windows.get_mut(&selected.0) {
let (&first_pane, _) =
window.panes.iter().next().unwrap();
let result =
window.panes.split(pane.axis, &first_pane, pane);
if let Some((pane, _)) = result {
window.focus = Some(pane);
}
}
}
WindowMessage::ToggleMoving(pane) => {
let window = self.windows.get_mut(&id).unwrap();
if let Some(pane) = window.panes.get_mut(&pane) {
pane.is_moving = !pane.is_moving;
}
}
WindowMessage::TitleChanged(title) => {
let window = self.windows.get_mut(&id).unwrap();
window.title = title;
}
WindowMessage::PopOut(pane) => {
let window = self.windows.get_mut(&id).unwrap();
if let Some((popped, sibling)) = window.panes.close(&pane) {
window.focus = Some(sibling);
let (panes, _) = pane_grid::State::new(popped);
let window = Window {
panes,
focus: None,
title: format!(
"New window ({})",
self.windows.len()
),
scale: 1.0 + (self.windows.len() as f64 / 10.0),
theme: if self.windows.len() % 2 == 0 {
Theme::Light
} else {
Theme::Dark
},
};
let window_id = window::Id::new(self.windows.len());
self.windows.insert(window_id, window);
return window::spawn(window_id, Default::default());
}
}
WindowMessage::Dragged(pane_grid::DragEvent::Dropped {
pane,
target,
}) => {
let window = self.windows.get_mut(&id).unwrap();
window.panes.swap(&pane, &target);
}
WindowMessage::Dragged(_) => {}
WindowMessage::TogglePin(pane) => {
let window = self.windows.get_mut(&id).unwrap();
if let Some(Pane { is_pinned, .. }) =
window.panes.get_mut(&pane)
{
*is_pinned = !*is_pinned;
}
}
WindowMessage::Close(pane) => {
let window = self.windows.get_mut(&id).unwrap();
if let Some((_, sibling)) = window.panes.close(&pane) {
window.focus = Some(sibling);
}
}
WindowMessage::CloseFocused => {
let window = self.windows.get_mut(&id).unwrap();
if let Some(pane) = window.focus {
if let Some(Pane { is_pinned, .. }) =
window.panes.get(&pane)
{
if !is_pinned {
if let Some((_, sibling)) =
window.panes.close(&pane)
{
window.focus = Some(sibling);
}
}
}
}
}
},
Message::CountIncremented(_) => {
self.count += 1;
}
}
Command::none()
}
fn subscription(&self) -> Subscription<Message> {
Subscription::batch(vec![
subscription::events_with(|event, status| {
if let event::Status::Captured = status {
return None;
}
match event {
Event::Keyboard(keyboard::Event::KeyPressed {
modifiers,
key_code,
}) if modifiers.command() => {
handle_hotkey(key_code).map(|message| {
Message::Window(window::Id::new(0usize), message)
})
} // TODO(derezzedex)
_ => None,
}
}),
time::every(Duration::from_secs(1)).map(Message::CountIncremented),
])
}
fn view(&self, window: window::Id) -> Element<Message> {
let window_id = window;
if let Some(window) = self.windows.get(&window) {
let focus = window.focus;
let total_panes = window.panes.len();
let window_controls = row![
text_input(
"Window title",
&window.title,
WindowMessage::TitleChanged,
),
button(text("Close"))
.on_press(WindowMessage::CloseWindow)
.style(theme::Button::Destructive),
]
.spacing(5)
.align_items(Alignment::Center);
let pane_grid = PaneGrid::new(&window.panes, |id, pane, _| {
let is_focused = focus == Some(id);
let pin_button = button(
text(if pane.is_pinned { "Unpin" } else { "Pin" }).size(14),
)
.on_press(WindowMessage::TogglePin(id))
.padding(3);
let title = row![
pin_button,
"Pane",
text(pane.id.to_string()).style(if is_focused {
PANE_ID_COLOR_FOCUSED
} else {
PANE_ID_COLOR_UNFOCUSED
}),
]
.spacing(5);
let title_bar = pane_grid::TitleBar::new(title)
.controls(view_controls(
id,
total_panes,
pane.is_pinned,
pane.is_moving,
&window.title,
window_id,
&self.windows,
))
.padding(10)
.style(if is_focused {
style::title_bar_focused
} else {
style::title_bar_active
});
pane_grid::Content::new(responsive(move |size| {
view_content(
id,
pane.scrollable_id.clone(),
self.count,
total_panes,
pane.is_pinned,
size,
)
}))
.title_bar(title_bar)
.style(if is_focused {
style::pane_focused
} else {
style::pane_active
})
})
.width(Length::Fill)
.height(Length::Fill)
.spacing(10)
.on_click(WindowMessage::Clicked)
.on_drag(WindowMessage::Dragged)
.on_resize(10, WindowMessage::Resized);
let content: Element<_> = column![window_controls, pane_grid]
.width(Length::Fill)
.height(Length::Fill)
.padding(10)
.into();
return content
.map(move |message| Message::Window(window_id, message));
}
container(text("This shouldn't be possible!").size(20))
.center_x()
.center_y()
.into()
}
fn close_requested(&self, window: window::Id) -> Self::Message {
Message::Window(window, WindowMessage::CloseWindow)
}
fn scale_factor(&self, window: Id) -> f64 {
self.windows.get(&window).map(|w| w.scale).unwrap_or(1.0)
}
fn theme(&self, window: Id) -> Theme {
self.windows.get(&window).expect("Window not found!").theme.clone()
}
}
const PANE_ID_COLOR_UNFOCUSED: Color = Color::from_rgb(
0xFF as f32 / 255.0,
0xC7 as f32 / 255.0,
0xC7 as f32 / 255.0,
);
const PANE_ID_COLOR_FOCUSED: Color = Color::from_rgb(
0xFF as f32 / 255.0,
0x47 as f32 / 255.0,
0x47 as f32 / 255.0,
);
fn handle_hotkey(key_code: keyboard::KeyCode) -> Option<WindowMessage> {
use keyboard::KeyCode;
use pane_grid::{Axis, Direction};
let direction = match key_code {
KeyCode::Up => Some(Direction::Up),
KeyCode::Down => Some(Direction::Down),
KeyCode::Left => Some(Direction::Left),
KeyCode::Right => Some(Direction::Right),
_ => None,
};
match key_code {
KeyCode::V => Some(WindowMessage::SplitFocused(Axis::Vertical)),
KeyCode::H => Some(WindowMessage::SplitFocused(Axis::Horizontal)),
KeyCode::W => Some(WindowMessage::CloseFocused),
_ => direction.map(WindowMessage::FocusAdjacent),
}
}
#[derive(Debug, Clone)]
struct SelectableWindow(window::Id, String);
impl PartialEq for SelectableWindow {
fn eq(&self, other: &Self) -> bool {
self.0 == other.0
}
}
impl Eq for SelectableWindow {}
impl std::fmt::Display for SelectableWindow {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
self.1.fmt(f)
}
}
#[derive(Debug)]
struct Pane {
id: usize,
pub scrollable_id: scrollable::Id,
pub axis: pane_grid::Axis,
pub is_pinned: bool,
pub is_moving: bool,
pub snapped: bool,
}
impl Pane {
fn new(id: usize, axis: pane_grid::Axis) -> Self {
Self {
id,
scrollable_id: scrollable::Id::unique(),
axis,
is_pinned: false,
is_moving: false,
snapped: false,
}
}
}
fn view_content<'a>(
pane: pane_grid::Pane,
scrollable_id: scrollable::Id,
count: usize,
total_panes: usize,
is_pinned: bool,
size: Size,
) -> Element<'a, WindowMessage> {
let button = |label, message| {
button(
text(label)
.width(Length::Fill)
.horizontal_alignment(alignment::Horizontal::Center)
.size(16),
)
.width(Length::Fill)
.padding(8)
.on_press(message)
};
let mut controls = column![
button(
"Split horizontally",
WindowMessage::Split(pane_grid::Axis::Horizontal, pane),
),
button(
"Split vertically",
WindowMessage::Split(pane_grid::Axis::Vertical, pane),
),
button("Snap", WindowMessage::SnapToggle,)
]
.spacing(5)
.max_width(150);
if total_panes > 1 && !is_pinned {
controls = controls.push(
button("Close", WindowMessage::Close(pane))
.style(theme::Button::Destructive),
);
}
let content = column![
text(format!("{}x{}", size.width, size.height)).size(24),
controls,
text(format!("{count}")).size(48),
]
.width(Length::Fill)
.height(800)
.spacing(10)
.align_items(Alignment::Center);
container(
scrollable(content)
.height(Length::Fill)
.vertical_scroll(Properties::new())
.id(scrollable_id),
)
.width(Length::Fill)
.height(Length::Fill)
.padding(5)
.center_y()
.into()
}
fn view_controls<'a>(
pane: pane_grid::Pane,
total_panes: usize,
is_pinned: bool,
is_moving: bool,
window_title: &'a str,
window_id: window::Id,
windows: &HashMap<window::Id, Window>,
) -> Element<'a, WindowMessage> {
let window_selector = {
let options: Vec<_> = windows
.iter()
.map(|(id, window)| SelectableWindow(*id, window.title.clone()))
.collect();
pick_list(
options,
Some(SelectableWindow(window_id, window_title.to_string())),
move |window| WindowMessage::SelectedWindow(pane, window),
)
};
let mut move_to = button(text("Move to").size(14)).padding(3);
let mut pop_out = button(text("Pop Out").size(14)).padding(3);
let mut close = button(text("Close").size(14))
.style(theme::Button::Destructive)
.padding(3);
if total_panes > 1 && !is_pinned {
close = close.on_press(WindowMessage::Close(pane));
pop_out = pop_out.on_press(WindowMessage::PopOut(pane));
}
if windows.len() > 1 && total_panes > 1 && !is_pinned {
move_to = move_to.on_press(WindowMessage::ToggleMoving(pane));
}
let mut content = row![].spacing(10);
if is_moving {
content = content.push(pop_out).push(window_selector).push(close);
} else {
content = content.push(pop_out).push(move_to).push(close);
}
content.into()
}
mod style {
use iced::widget::container;
use iced::Theme;
pub fn title_bar_active(theme: &Theme) -> container::Appearance {
let palette = theme.extended_palette();
container::Appearance {
text_color: Some(palette.background.strong.text),
background: Some(palette.background.strong.color.into()),
..Default::default()
}
}
pub fn title_bar_focused(theme: &Theme) -> container::Appearance {
let palette = theme.extended_palette();
container::Appearance {
text_color: Some(palette.primary.strong.text),
background: Some(palette.primary.strong.color.into()),
..Default::default()
}
}
pub fn pane_active(theme: &Theme) -> container::Appearance {
let palette = theme.extended_palette();
container::Appearance {
background: Some(palette.background.weak.color.into()),
border_width: 2.0,
border_color: palette.background.strong.color,
..Default::default()
}
}
pub fn pane_focused(theme: &Theme) -> container::Appearance {
let palette = theme.extended_palette();
container::Appearance {
background: Some(palette.background.weak.color.into()),
border_width: 2.0,
border_color: palette.primary.strong.color,
..Default::default()
}
}
}

View file

@ -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, subscription, Alignment, Application, Command, event, executor, keyboard, subscription, Alignment, Application, Command,
ContentFit, Element, Event, Length, Rectangle, Renderer, Subscription, ContentFit, Element, Event, Length, Rectangle, Renderer, Subscription,
@ -71,7 +71,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);

View file

@ -528,7 +528,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(

View file

@ -164,7 +164,7 @@ impl Application for Todos {
} }
} }
Message::ToggleFullscreen(mode) => { Message::ToggleFullscreen(mode) => {
window::change_mode(mode) window::change_mode(window::Id::MAIN, mode)
} }
_ => Command::none(), _ => Command::none(),
}; };

View file

@ -251,7 +251,7 @@ where
events.filter_map(move |(event, status)| { events.filter_map(move |(event, status)| {
future::ready(match event { future::ready(match event {
Event::Window(window::Event::RedrawRequested(_)) => None, Event::Window(_, window::Event::RedrawRequested(_)) => None,
_ => f(event, status), _ => f(event, status),
}) })
}) })

View file

@ -12,7 +12,6 @@ categories = ["gui"]
[features] [features]
geometry = ["lyon_path"] geometry = ["lyon_path"]
opengl = []
image = ["dep:image", "kamadak-exif"] image = ["dep:image", "kamadak-exif"]
web-colors = [] web-colors = []

View file

@ -24,6 +24,9 @@ pub trait Compositor: Sized {
compatible_window: Option<&W>, compatible_window: Option<&W>,
) -> Result<(Self, Self::Renderer), Error>; ) -> Result<(Self, Self::Renderer), Error>;
/// Creates a [`Renderer`] for the [`Compositor`].
fn renderer(&self) -> Self::Renderer;
/// Crates a new [`Surface`] for the given window. /// Crates a new [`Surface`] for the given window.
/// ///
/// [`Surface`]: Self::Surface /// [`Surface`]: Self::Surface

View file

View file

@ -46,6 +46,22 @@ impl<Theme> crate::graphics::Compositor for Compositor<Theme> {
Err(error) Err(error)
} }
fn renderer(&self) -> Self::Renderer {
match self {
Compositor::TinySkia(compositor) => {
Renderer::TinySkia(compositor.renderer())
}
#[cfg(feature = "wgpu")]
Compositor::Wgpu(compositor) => {
Renderer::Wgpu(compositor.renderer())
}
#[cfg(not(feature = "wgpu"))]
Self::Wgpu => {
panic!("`wgpu` feature was not enabled in `iced_renderer`")
}
}
}
fn create_surface<W: HasRawWindowHandle + HasRawDisplayHandle>( fn create_surface<W: HasRawWindowHandle + HasRawDisplayHandle>(
&mut self, &mut self,
window: &W, window: &W,

View file

@ -9,6 +9,7 @@ repository = "https://github.com/iced-rs/iced"
[features] [features]
debug = [] debug = []
multi-window = []
[dependencies] [dependencies]
thiserror = "1" thiserror = "1"

View file

@ -53,6 +53,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")]

View file

@ -0,0 +1,6 @@
//! A multi-window application.
pub mod program;
pub mod state;
pub use program::Program;
pub use state::State;

View file

@ -0,0 +1,32 @@
//! Build interactive programs using The Elm Architecture.
use crate::{window, Command};
use crate::core::text;
use crate::core::{Element, Renderer};
/// 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>;
}

View 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
}

View file

@ -3,101 +3,117 @@ mod action;
pub mod screenshot; pub mod screenshot;
pub use crate::core::window::Id;
pub use action::Action; pub use action::Action;
pub use screenshot::Screenshot; 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::{self, Event, Icon, Level, Mode, UserAttention};
use crate::core::Size; use crate::core::Size;
use crate::futures::subscription::{self, Subscription}; use crate::futures::subscription::{self, 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> {
subscription::raw_events(|event, _status| match event { subscription::raw_events(|event, _status| match event {
iced_core::Event::Window(Event::RedrawRequested(at)) => Some(at), iced_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 `id` and `settings`.
pub fn close<Message>() -> Command<Message> { pub fn spawn<Message>(
Command::single(command::Action::Window(Action::Close)) id: window::Id,
settings: window::Settings,
) -> Command<Message> {
Command::single(command::Action::Window(id, Action::Spawn { settings }))
}
/// Closes the window with `id`.
pub fn close<Message>(id: window::Id) -> Command<Message> {
Command::single(command::Action::Window(id, Action::Close))
} }
/// 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: window::Id) -> Command<Message> {
Command::single(command::Action::Window(Action::Drag)) Command::single(command::Action::Window(id, Action::Drag))
} }
/// 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>(
Command::single(command::Action::Window(Action::Resize(new_size))) id: window::Id,
new_size: Size<u32>,
) -> Command<Message> {
Command::single(command::Action::Window(id, Action::Resize(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>(
id: window::Id,
f: impl FnOnce(Size<u32>) -> Message + 'static, f: impl FnOnce(Size<u32>) -> Message + 'static,
) -> Command<Message> { ) -> Command<Message> {
Command::single(command::Action::Window(Action::FetchSize(Box::new(f)))) Command::single(command::Action::Window(id, Action::FetchSize(Box::new(f))))
} }
/// Maximizes the window. /// Maximizes the window.
pub fn maximize<Message>(maximized: bool) -> Command<Message> { pub fn maximize<Message>(id: window::Id, maximized: bool) -> Command<Message> {
Command::single(command::Action::Window(Action::Maximize(maximized))) Command::single(command::Action::Window(id, Action::Maximize(maximized)))
} }
/// Minimes the window. /// Minimizes the window.
pub fn minimize<Message>(minimized: bool) -> Command<Message> { pub fn minimize<Message>(id: window::Id, minimized: bool) -> Command<Message> {
Command::single(command::Action::Window(Action::Minimize(minimized))) Command::single(command::Action::Window(id, Action::Minimize(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: window::Id, x: i32, y: i32) -> Command<Message> {
Command::single(command::Action::Window(Action::Move { x, y })) Command::single(command::Action::Window(id, Action::Move { x, y }))
} }
/// 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: window::Id, mode: Mode) -> Command<Message> {
Command::single(command::Action::Window(Action::ChangeMode(mode))) Command::single(command::Action::Window(id, Action::ChangeMode(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: window::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(id, Action::FetchMode(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: window::Id) -> Command<Message> {
Command::single(command::Action::Window(Action::ToggleMaximize)) Command::single(command::Action::Window(id, Action::ToggleMaximize))
} }
/// Toggles the window decorations. /// Toggles the window decorations.
pub fn toggle_decorations<Message>() -> Command<Message> { pub fn toggle_decorations<Message>(id: window::Id) -> Command<Message> {
Command::single(command::Action::Window(Action::ToggleDecorations)) Command::single(command::Action::Window(id, Action::ToggleDecorations))
} }
/// 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: window::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(
user_attention, id,
))) Action::RequestUserAttention(user_attention),
))
} }
/// Brings the window to the front and sets input focus. Has no effect if the window is /// Brings the window to the front and sets input focus. Has no effect if the window is
@ -106,30 +122,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: window::Id) -> Command<Message> {
Command::single(command::Action::Window(Action::GainFocus)) Command::single(command::Action::Window(id, Action::GainFocus))
} }
/// Changes the window [`Level`]. /// Changes the window [`Level`].
pub fn change_level<Message>(level: Level) -> Command<Message> { pub fn change_level<Message>(id: window::Id, level: Level) -> Command<Message> {
Command::single(command::Action::Window(Action::ChangeLevel(level))) Command::single(command::Action::Window(id, Action::ChangeLevel(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 [`window::Id`].
pub fn fetch_id<Message>( pub fn fetch_id<Message>(
id: window::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(id, Action::FetchId(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: window::Id, icon: Icon) -> Command<Message> {
Command::single(command::Action::Window(Action::ChangeIcon(icon))) Command::single(command::Action::Window(id, Action::ChangeIcon(icon)))
} }
/// Captures a [`Screenshot`] from the window. /// Captures a [`Screenshot`] from the window.
pub fn screenshot<Message>( pub fn screenshot<Message>(
id: window::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(
id,
Action::Screenshot(Box::new(f)),
))
} }

View file

@ -1,4 +1,4 @@
use crate::core::window::{Icon, Level, Mode, UserAttention, Settings}; use crate::core::window::{Icon, Level, Mode, Settings, UserAttention};
use crate::core::Size; use crate::core::Size;
use crate::futures::MaybeSend; use crate::futures::MaybeSend;
use crate::window::Screenshot; use crate::window::Screenshot;
@ -15,7 +15,7 @@ pub enum Action<T> {
/// Theres no guarantee that this will work unless the left mouse /// Theres 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,
/// Spawns a new window with the provided [`window::Settings`]. /// Spawns a new window.
Spawn { Spawn {
/// The settings of the [`Window`]. /// The settings of the [`Window`].
settings: Settings, settings: Settings,

View file

@ -1,30 +1,37 @@
use crate::window; use crate::window;
use crate::{Command, Element, Executor, Settings, Subscription}; use crate::{Command, Element, Executor, Settings, Subscription};
pub use iced_native::application::{Appearance, StyleSheet}; pub use crate::style::application::{Appearance, StyleSheet};
/// An interactive cross-platform multi-window application. /// An interactive cross-platform multi-window 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
/// your GUI application by simply calling [`run`](#method.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 /// An [`Application`] can execute asynchronous actions by returning a
/// [`Command`] in some of its methods. For example, to spawn a new window, you /// [`Command`] in some of its methods. If you do not intend to perform any
/// can use the `iced_winit::window::spawn()` [`Command`]. /// background work in your program, the [`Sandbox`] trait offers a simplified
/// interface.
/// ///
/// When using an [`Application`] with the `debug` feature enabled, a debug view /// When using an [`Application`] with the `debug` feature enabled, a debug view
/// can be toggled by pressing `F12`. /// 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!" /// ## A simple "Hello, world!"
/// ///
/// If you just want to get started, here is a simple [`Application`] that /// If you just want to get started, here is a simple [`Application`] that
/// says "Hello, world!": /// says "Hello, world!":
/// ///
/// ```no_run /// ```no_run
/// use iced::executor; /// use iced::{executor, window};
/// use iced::multi_window::Application;
/// use iced::window;
/// use iced::{Command, Element, Settings, Theme}; /// use iced::{Command, Element, Settings, Theme};
/// use iced::multi_window::{self, Application};
/// ///
/// pub fn main() -> iced::Result { /// pub fn main() -> iced::Result {
/// Hello::run(Settings::default()) /// Hello::run(Settings::default())
@ -32,17 +39,17 @@ pub use iced_native::application::{Appearance, StyleSheet};
/// ///
/// struct Hello; /// struct Hello;
/// ///
/// impl Application for Hello { /// impl multi_window::Application for Hello {
/// type Executor = executor::Default; /// type Executor = executor::Default;
/// type Flags = ();
/// type Message = (); /// type Message = ();
/// type Theme = Theme; /// type Theme = Theme;
/// type Flags = ();
/// ///
/// fn new(_flags: ()) -> (Hello, Command<Self::Message>) { /// fn new(_flags: ()) -> (Hello, Command<Self::Message>) {
/// (Hello, Command::none()) /// (Hello, Command::none())
/// } /// }
/// ///
/// fn title(&self, window: window::Id) -> String { /// fn title(&self, _window: window::Id) -> String {
/// String::from("A cool application") /// String::from("A cool application")
/// } /// }
/// ///
@ -50,13 +57,9 @@ pub use iced_native::application::{Appearance, StyleSheet};
/// Command::none() /// Command::none()
/// } /// }
/// ///
/// fn view(&self, window: window::Id) -> Element<Self::Message> { /// fn view(&self, _window: window::Id) -> Element<Self::Message> {
/// "Hello, world!".into() /// "Hello, world!".into()
/// } /// }
///
/// fn close_requested(&self, window: window::Id) -> Self::Message {
/// ()
/// }
/// } /// }
/// ``` /// ```
pub trait Application: Sized { pub trait Application: Sized {
@ -89,10 +92,10 @@ pub trait Application: Sized {
/// [`run`]: Self::run /// [`run`]: Self::run
fn new(flags: Self::Flags) -> (Self, Command<Self::Message>); fn new(flags: Self::Flags) -> (Self, Command<Self::Message>);
/// Returns the current title of the [`Application`]. /// Returns the current title of the `window` of the [`Application`].
/// ///
/// This title can be dynamic! The runtime will automatically update the /// This title can be dynamic! The runtime will automatically update the
/// title of your application when necessary. /// title of your window when necessary.
fn title(&self, window: window::Id) -> String; fn title(&self, window: window::Id) -> String;
/// Handles a __message__ and updates the state of the [`Application`]. /// Handles a __message__ and updates the state of the [`Application`].
@ -104,7 +107,15 @@ pub trait Application: Sized {
/// Any [`Command`] returned will be executed immediately in the background. /// Any [`Command`] returned will be executed immediately in the background.
fn update(&mut self, message: Self::Message) -> Command<Self::Message>; fn update(&mut self, message: Self::Message) -> Command<Self::Message>;
/// Returns the current [`Theme`] of the [`Application`]. /// 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 /// [`Theme`]: Self::Theme
#[allow(unused_variables)] #[allow(unused_variables)]
@ -112,9 +123,8 @@ pub trait Application: Sized {
Self::Theme::default() Self::Theme::default()
} }
/// Returns the current [`Style`] of the [`Theme`]. /// Returns the current `Style` of the [`Theme`].
/// ///
/// [`Style`]: <Self::Theme as StyleSheet>::Style
/// [`Theme`]: Self::Theme /// [`Theme`]: Self::Theme
fn style(&self) -> <Self::Theme as StyleSheet>::Style { fn style(&self) -> <Self::Theme as StyleSheet>::Style {
<Self::Theme as StyleSheet>::Style::default() <Self::Theme as StyleSheet>::Style::default()
@ -132,14 +142,6 @@ pub trait Application: Sized {
Subscription::none() Subscription::none()
} }
/// Returns the widgets to display in 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 scale factor of the `window` of the [`Application`]. /// Returns the scale factor of the `window` of the [`Application`].
/// ///
/// It can be used to dynamically control the size of the UI at runtime /// It can be used to dynamically control the size of the UI at runtime
@ -154,18 +156,7 @@ pub trait Application: Sized {
1.0 1.0
} }
/// Returns whether the [`Application`] should be terminated. /// Runs the multi-window [`Application`].
///
/// By default, it returns `false`.
fn should_exit(&self) -> bool {
false
}
/// Returns the `Self::Message` that should be processed when a `window` is requested to
/// be closed.
fn close_requested(&self, window: window::Id) -> Self::Message;
/// Runs the [`Application`].
/// ///
/// On native platforms, this method will take control of the current thread /// On native platforms, this method will take control of the current thread
/// until the [`Application`] exits. /// until the [`Application`] exits.
@ -182,30 +173,28 @@ pub trait Application: Sized {
let renderer_settings = crate::renderer::Settings { let renderer_settings = crate::renderer::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,
text_multithreading: settings.text_multithreading,
antialiasing: if settings.antialiasing { antialiasing: if settings.antialiasing {
Some(crate::renderer::settings::Antialiasing::MSAAx4) Some(crate::graphics::Antialiasing::MSAAx4)
} else { } else {
None None
}, },
..crate::renderer::Settings::from_env() ..crate::renderer::Settings::default()
}; };
Ok(crate::runtime::multi_window::run::< Ok(crate::shell::multi_window::run::<
Instance<Self>, Instance<Self>,
Self::Executor, Self::Executor,
crate::renderer::window::Compositor<Self::Theme>, crate::renderer::Compositor<Self::Theme>,
>(settings.into(), renderer_settings)?) >(settings.into(), renderer_settings)?)
} }
} }
struct Instance<A: Application>(A); struct Instance<A: Application>(A);
impl<A> crate::runtime::multi_window::Application for Instance<A> impl<A> crate::runtime::multi_window::Program for Instance<A>
where where
A: Application, A: Application,
{ {
type Flags = A::Flags;
type Renderer = crate::Renderer<A::Theme>; type Renderer = crate::Renderer<A::Theme>;
type Message = A::Message; type Message = A::Message;
@ -219,6 +208,13 @@ where
) -> Element<'_, Self::Message, Self::Renderer> { ) -> Element<'_, Self::Message, Self::Renderer> {
self.0.view(window) 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>) { fn new(flags: Self::Flags) -> (Self, Command<A::Message>) {
let (app, command) = A::new(flags); let (app, command) = A::new(flags);
@ -245,12 +241,4 @@ where
fn scale_factor(&self, window: window::Id) -> f64 { fn scale_factor(&self, window: window::Id) -> f64 {
self.0.scale_factor(window) self.0.scale_factor(window)
} }
fn should_exit(&self) -> bool {
self.0.should_exit()
}
fn close_requested(&self, window: window::Id) -> Self::Message {
self.0.close_requested(window)
}
} }

View file

@ -91,7 +91,7 @@ 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,
exit_on_close_request: settings.exit_on_close_request, exit_on_close_request: settings.exit_on_close_request,
} }

View file

@ -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::*;

View file

@ -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>,
} }
@ -33,6 +34,10 @@ impl<Theme> crate::graphics::Compositor for Compositor<Theme> {
Ok((compositor, Renderer::new(backend))) Ok((compositor, Renderer::new(backend)))
} }
fn renderer(&self) -> Self::Renderer {
Renderer::new(Backend::new(self.settings))
}
fn create_surface<W: HasRawWindowHandle + HasRawDisplayHandle>( fn create_surface<W: HasRawWindowHandle + HasRawDisplayHandle>(
&mut self, &mut self,
window: &W, window: &W,
@ -116,6 +121,7 @@ impl<Theme> crate::graphics::Compositor for Compositor<Theme> {
pub fn new<Theme>(settings: Settings) -> (Compositor<Theme>, Backend) { pub fn new<Theme>(settings: Settings) -> (Compositor<Theme>, Backend) {
( (
Compositor { Compositor {
settings,
_theme: PhantomData, _theme: PhantomData,
}, },
Backend::new(settings), Backend::new(settings),

View file

@ -219,6 +219,10 @@ impl<Theme> graphics::Compositor for Compositor<Theme> {
Ok((compositor, Renderer::new(backend))) Ok((compositor, Renderer::new(backend)))
} }
fn renderer(&self) -> Self::Renderer {
Renderer::new(self.create_backend())
}
fn create_surface<W: HasRawWindowHandle + HasRawDisplayHandle>( fn create_surface<W: HasRawWindowHandle + HasRawDisplayHandle>(
&mut self, &mut self,
window: &W, window: &W,

View file

@ -12,8 +12,6 @@ categories = ["gui"]
[features] [features]
default = ["x11", "wayland", "wayland-dlopen", "wayland-csd-adwaita"] default = ["x11", "wayland", "wayland-dlopen", "wayland-csd-adwaita"]
trace = ["tracing", "tracing-core", "tracing-subscriber"]
chrome-trace = ["trace", "tracing-chrome"]
debug = ["iced_runtime/debug"] debug = ["iced_runtime/debug"]
system = ["sysinfo"] system = ["sysinfo"]
application = [] application = []
@ -21,7 +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 = [] multi-window = ["iced_runtime/multi-window"]
[dependencies] [dependencies]
window_clipboard = "0.3" window_clipboard = "0.3"
@ -47,24 +45,6 @@ path = "../graphics"
version = "0.8" version = "0.8"
path = "../style" path = "../style"
[dependencies.tracing]
version = "0.1.37"
optional = true
features = ["std"]
[dependencies.tracing-core]
version = "0.1.30"
optional = true
[dependencies.tracing-subscriber]
version = "0.3.16"
optional = true
features = ["registry"]
[dependencies.tracing-chrome]
version = "0.7.0"
optional = true
[target.'cfg(target_os = "windows")'.dependencies.winapi] [target.'cfg(target_os = "windows")'.dependencies.winapi]
version = "0.3.6" version = "0.3.6"

View file

@ -18,6 +18,7 @@ use crate::runtime::clipboard;
use crate::runtime::program::Program; use crate::runtime::program::Program;
use crate::runtime::user_interface::{self, UserInterface}; use crate::runtime::user_interface::{self, UserInterface};
use crate::runtime::{Command, Debug}; use crate::runtime::{Command, Debug};
use crate::settings;
use crate::style::application::{Appearance, StyleSheet}; use crate::style::application::{Appearance, StyleSheet};
use crate::{Clipboard, Error, Proxy, Settings}; use crate::{Clipboard, Error, Proxy, Settings};
@ -25,11 +26,6 @@ use futures::channel::mpsc;
use std::mem::ManuallyDrop; use std::mem::ManuallyDrop;
#[cfg(feature = "trace")]
pub use crate::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
@ -117,15 +113,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();
@ -146,14 +136,13 @@ 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 builder = settings::window_builder(
.window settings.window,
.into_builder( &application.title(),
&application.title(), event_loop.primary_monitor(),
event_loop.primary_monitor(), settings.id,
settings.id, )
) .with_visible(false);
.with_visible(false);
log::debug!("Window builder: {:#?}", builder); log::debug!("Window builder: {:#?}", builder);
@ -196,28 +185,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, settings.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());
@ -480,9 +461,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 {
@ -622,24 +600,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
@ -666,16 +632,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(
@ -750,7 +710,7 @@ pub fn run_command<A, C, E>(
} }
window::Action::Spawn { .. } => { window::Action::Spawn { .. } => {
log::info!( log::info!(
"Spawning a window is only available with `multi_window::Application`s." "Spawning a window is only available with multi-window applications."
) )
} }
window::Action::Resize(size) => { window::Action::Resize(size) => {

View file

@ -7,7 +7,6 @@ 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};
use crate::Position;
/// 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(
@ -169,17 +168,17 @@ pub fn window_level(level: window::Level) -> winit::window::WindowLevel {
pub fn position( pub fn position(
monitor: Option<&winit::monitor::MonitorHandle>, monitor: Option<&winit::monitor::MonitorHandle>,
(width, height): (u32, u32), (width, height): (u32, u32),
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(x, y) => {
Some(winit::dpi::Position::Logical(winit::dpi::LogicalPosition { Some(winit::dpi::Position::Logical(winit::dpi::LogicalPosition {
x: f64::from(x), x: f64::from(x),
y: f64::from(y), y: f64::from(y),
})) }))
} }
Position::Centered => { window::Position::Centered => {
if let Some(monitor) = monitor { if let Some(monitor) = monitor {
let start = monitor.position(); let start = monitor.position();

View file

@ -51,20 +51,14 @@ pub mod settings;
pub mod system; pub mod system;
mod error; mod error;
mod icon;
mod proxy; mod proxy;
#[cfg(feature = "trace")]
mod profiler;
#[cfg(feature = "application")] #[cfg(feature = "application")]
pub use application::Application; pub use application::Application;
#[cfg(feature = "trace")]
pub use profiler::Profiler;
pub use clipboard::Clipboard; pub use clipboard::Clipboard;
pub use error::Error; pub use error::Error;
pub use icon::Icon;
pub use proxy::Proxy; pub use proxy::Proxy;
pub use settings::Settings; pub use settings::Settings;
pub use crate::core::window::*;
pub use iced_graphics::Viewport; pub use iced_graphics::Viewport;
pub use iced_native::window::Position;

File diff suppressed because it is too large Load diff

View file

@ -1,33 +1,50 @@
use crate::application::{self, StyleSheet as _};
use crate::conversion; 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::multi_window::Application;
use crate::window; use crate::style::application;
use crate::{Color, Debug, Point, Size, Viewport}; use std::fmt::{Debug, Formatter};
use std::marker::PhantomData; use iced_style::application::StyleSheet;
use winit::event::{Touch, WindowEvent}; use winit::event::{Touch, WindowEvent};
use winit::window::Window; use winit::window::Window;
/// The state of a multi-windowed [`Application`]. /// The state of a multi-windowed [`Application`].
#[allow(missing_debug_implementations)]
pub struct State<A: Application> pub struct State<A: Application>
where where
<A::Renderer as crate::Renderer>::Theme: application::StyleSheet, <A::Renderer as core::Renderer>::Theme: application::StyleSheet,
{ {
title: String, title: String,
scale_factor: f64, scale_factor: f64,
viewport: Viewport, viewport: Viewport,
viewport_changed: bool, viewport_version: usize,
cursor_position: winit::dpi::PhysicalPosition<f64>, cursor_position: Option<winit::dpi::PhysicalPosition<f64>>,
modifiers: winit::event::ModifiersState, modifiers: winit::event::ModifiersState,
theme: <A::Renderer as crate::Renderer>::Theme, theme: <A::Renderer as core::Renderer>::Theme,
appearance: application::Appearance, appearance: application::Appearance,
application: PhantomData<A>, }
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> impl<A: Application> State<A>
where where
<A::Renderer as crate::Renderer>::Theme: application::StyleSheet, <A::Renderer as core::Renderer>::Theme: application::StyleSheet,
{ {
/// Creates a new [`State`] for the provided [`Application`]'s `window`. /// Creates a new [`State`] for the provided [`Application`]'s `window`.
pub fn new( pub fn new(
@ -53,13 +70,11 @@ where
title, title,
scale_factor, scale_factor,
viewport, viewport,
viewport_changed: false, viewport_version: 0,
// TODO: Encode cursor availability in the type-system cursor_position: None,
cursor_position: winit::dpi::PhysicalPosition::new(-1.0, -1.0),
modifiers: winit::event::ModifiersState::default(), modifiers: winit::event::ModifiersState::default(),
theme, theme,
appearance, appearance,
application: PhantomData,
} }
} }
@ -68,9 +83,11 @@ where
&self.viewport &self.viewport
} }
/// Returns whether or not the viewport changed. /// Returns the version of the [`Viewport`] of the [`State`].
pub fn viewport_changed(&self) -> bool { ///
self.viewport_changed /// The version is incremented every time the [`Viewport`] changes.
pub fn viewport_version(&self) -> usize {
self.viewport_version
} }
/// Returns the physical [`Size`] of the [`Viewport`] of the [`State`]. /// Returns the physical [`Size`] of the [`Viewport`] of the [`State`].
@ -89,11 +106,16 @@ where
} }
/// Returns the current cursor position of the [`State`]. /// Returns the current cursor position of the [`State`].
pub fn cursor_position(&self) -> Point { pub fn cursor(&self) -> mouse::Cursor {
conversion::cursor_position( self.cursor_position
self.cursor_position, .map(|cursor_position| {
self.viewport.scale_factor(), 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`]. /// Returns the current keyboard modifiers of the [`State`].
@ -102,7 +124,7 @@ where
} }
/// Returns the current theme of the [`State`]. /// Returns the current theme of the [`State`].
pub fn theme(&self) -> &<A::Renderer as crate::Renderer>::Theme { pub fn theme(&self) -> &<A::Renderer as core::Renderer>::Theme {
&self.theme &self.theme
} }
@ -121,7 +143,7 @@ where
&mut self, &mut self,
window: &Window, window: &Window,
event: &WindowEvent<'_>, event: &WindowEvent<'_>,
_debug: &mut Debug, _debug: &mut crate::runtime::Debug,
) { ) {
match event { match event {
WindowEvent::Resized(new_size) => { WindowEvent::Resized(new_size) => {
@ -132,7 +154,7 @@ where
window.scale_factor() * self.scale_factor, window.scale_factor() * self.scale_factor,
); );
self.viewport_changed = true; self.viewport_version = self.viewport_version.wrapping_add(1);
} }
WindowEvent::ScaleFactorChanged { WindowEvent::ScaleFactorChanged {
scale_factor: new_scale_factor, scale_factor: new_scale_factor,
@ -146,18 +168,16 @@ where
new_scale_factor * self.scale_factor, new_scale_factor * self.scale_factor,
); );
self.viewport_changed = true; self.viewport_version = self.viewport_version.wrapping_add(1);
} }
WindowEvent::CursorMoved { position, .. } WindowEvent::CursorMoved { position, .. }
| WindowEvent::Touch(Touch { | WindowEvent::Touch(Touch {
location: position, .. location: position, ..
}) => { }) => {
self.cursor_position = *position; self.cursor_position = Some(*position);
} }
WindowEvent::CursorLeft { .. } => { WindowEvent::CursorLeft { .. } => {
// TODO: Encode cursor availability in the type-system self.cursor_position = None;
self.cursor_position =
winit::dpi::PhysicalPosition::new(-1.0, -1.0);
} }
WindowEvent::ModifiersChanged(new_modifiers) => { WindowEvent::ModifiersChanged(new_modifiers) => {
self.modifiers = *new_modifiers; self.modifiers = *new_modifiers;
@ -197,16 +217,20 @@ where
self.title = new_title; self.title = new_title;
} }
// Update scale factor // Update scale factor and size
let new_scale_factor = application.scale_factor(window_id); 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 { if self.scale_factor != new_scale_factor
let size = window.inner_size(); || (current_size.width, current_size.height)
!= (new_size.width, new_size.height)
{
self.viewport = Viewport::with_physical_size( self.viewport = Viewport::with_physical_size(
Size::new(size.width, size.height), Size::new(new_size.width, new_size.height),
window.scale_factor() * new_scale_factor, window.scale_factor() * new_scale_factor,
); );
self.viewport_version = self.viewport_version.wrapping_add(1);
self.scale_factor = new_scale_factor; self.scale_factor = new_scale_factor;
} }

View file

@ -0,0 +1,170 @@
use crate::core::{window, Size};
use crate::multi_window::{Application, State};
use iced_graphics::Compositor;
use iced_style::application::StyleSheet;
use std::fmt::{Debug, Formatter};
use winit::monitor::MonitorHandle;
pub struct Windows<A: Application, C: Compositor>
where
<A::Renderer as crate::core::Renderer>::Theme: StyleSheet,
C: Compositor<Renderer = A::Renderer>,
{
pub ids: Vec<window::Id>,
pub raw: Vec<winit::window::Window>,
pub states: Vec<State<A>>,
pub viewport_versions: Vec<usize>,
pub surfaces: Vec<C::Surface>,
pub renderers: Vec<A::Renderer>,
pub pending_destroy: Vec<(window::Id, winit::window::WindowId)>,
}
impl<A: Application, C: Compositor> Debug for Windows<A, C>
where
<A::Renderer as crate::core::Renderer>::Theme: StyleSheet,
C: Compositor<Renderer = A::Renderer>,
{
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Windows")
.field("ids", &self.ids)
.field(
"raw",
&self
.raw
.iter()
.map(|raw| raw.id())
.collect::<Vec<winit::window::WindowId>>(),
)
.field("states", &self.states)
.field("viewport_versions", &self.viewport_versions)
.finish()
}
}
impl<A: Application, C: Compositor> Windows<A, C>
where
<A::Renderer as crate::core::Renderer>::Theme: StyleSheet,
C: Compositor<Renderer = A::Renderer>,
{
/// Creates a new [`Windows`] with a single `window::Id::MAIN` window.
pub fn new(
application: &A,
compositor: &mut C,
renderer: A::Renderer,
main: winit::window::Window,
) -> Self {
let state = State::new(application, window::Id::MAIN, &main);
let viewport_version = state.viewport_version();
let physical_size = state.physical_size();
let surface = compositor.create_surface(
&main,
physical_size.width,
physical_size.height,
);
Self {
ids: vec![window::Id::MAIN],
raw: vec![main],
states: vec![state],
viewport_versions: vec![viewport_version],
surfaces: vec![surface],
renderers: vec![renderer],
pending_destroy: vec![],
}
}
/// Adds a new window to [`Windows`]. Returns the size of the newly created window in logical
/// pixels & the index of the window within [`Windows`].
pub fn add(
&mut self,
application: &A,
compositor: &mut C,
id: window::Id,
window: winit::window::Window,
) -> (Size, usize) {
let state = State::new(application, id, &window);
let window_size = state.logical_size();
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.renderer();
self.ids.push(id);
self.raw.push(window);
self.states.push(state);
self.viewport_versions.push(viewport_version);
self.surfaces.push(surface);
self.renderers.push(renderer);
(window_size, self.ids.len() - 1)
}
pub fn is_empty(&self) -> bool {
self.ids.is_empty()
}
pub fn main(&self) -> &winit::window::Window {
&self.raw[0]
}
pub fn index_from_raw(&self, id: winit::window::WindowId) -> usize {
self.raw
.iter()
.position(|window| window.id() == id)
.expect("No raw window in multi_window::Windows")
}
pub fn index_from_id(&self, id: window::Id) -> usize {
self.ids
.iter()
.position(|window_id| *window_id == id)
.expect("No window in multi_window::Windows")
}
pub fn last_monitor(&self) -> Option<MonitorHandle> {
self.raw.last().and_then(|w| w.current_monitor())
}
pub fn last(&self) -> usize {
self.ids.len() - 1
}
pub fn with_raw(&self, id: window::Id) -> &winit::window::Window {
let i = self.index_from_id(id);
&self.raw[i]
}
/// Deletes the window with `id` from [`Windows`]. Returns the index that the window had.
pub fn delete(&mut self, id: window::Id) -> usize {
let i = self.index_from_id(id);
let id = self.ids.remove(i);
let window = self.raw.remove(i);
let _ = self.states.remove(i);
let _ = self.viewport_versions.remove(i);
let _ = self.surfaces.remove(i);
self.pending_destroy.push((id, window.id()));
i
}
/// Gets the winit `window` that is pending to be destroyed if it exists.
pub fn get_pending_destroy(
&mut self,
window: winit::window::WindowId,
) -> window::Id {
let i = self
.pending_destroy
.iter()
.position(|(_, window_id)| window == *window_id)
.unwrap();
let (id, _) = self.pending_destroy.remove(i);
id
}
}

View file

@ -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,
}
}
}

View file

@ -1,35 +1,10 @@
//! Configure your application. //! Configure your application.
#[cfg(target_os = "windows")]
#[path = "settings/windows.rs"]
mod platform;
#[cfg(target_os = "macos")]
#[path = "settings/macos.rs"]
mod platform;
#[cfg(target_arch = "wasm32")]
#[path = "settings/wasm.rs"]
mod platform;
#[cfg(not(any(
target_os = "windows",
target_os = "macos",
target_arch = "wasm32"
)))]
#[path = "settings/other.rs"]
mod platform;
pub use platform::PlatformSpecific;
use crate::conversion; use crate::conversion;
use crate::core::window::{Icon, Level}; use crate::core::window;
use crate::Position;
use winit::monitor::MonitorHandle; use winit::monitor::MonitorHandle;
use winit::window::WindowBuilder; use winit::window::WindowBuilder;
use std::fmt;
/// The settings of an application. /// The settings of an application.
#[derive(Debug, Clone, Default)] #[derive(Debug, Clone, Default)]
pub struct Settings<Flags> { pub struct Settings<Flags> {
@ -40,7 +15,7 @@ pub struct Settings<Flags> {
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`].
/// ///
@ -50,166 +25,93 @@ pub struct Settings<Flags> {
/// Whether the [`Application`] should exit when the user requests the /// Whether the [`Application`] should exit when the user requests the
/// window to close (e.g. the user presses the close button). /// window to close (e.g. the user presses the close button).
/// ///
/// With a [`multi_window::Application`] this will instead be used to determine whether the
/// application should exit when the "main"" window is closed, i.e. the first window created on
/// app launch.
///
/// [`Application`]: crate::Application /// [`Application`]: crate::Application
pub exit_on_close_request: bool, pub exit_on_close_request: bool,
} }
/// The window settings of an application. /// Converts the window settings into a `WindowBuilder` from `winit`.
#[derive(Clone)] pub fn window_builder(
pub struct Window { settings: window::Settings,
/// The size of the window. title: &str,
pub size: (u32, u32), monitor: Option<MonitorHandle>,
_id: Option<String>,
) -> WindowBuilder {
let mut window_builder = WindowBuilder::new();
/// The position of the window. let (width, height) = settings.size;
pub position: Position,
/// The minimum size of the window. window_builder = window_builder
pub min_size: Option<(u32, u32)>, .with_title(title)
.with_inner_size(winit::dpi::LogicalSize { width, height })
.with_resizable(settings.resizable)
.with_decorations(settings.decorations)
.with_transparent(settings.transparent)
.with_window_icon(settings.icon.and_then(conversion::icon))
.with_window_level(conversion::window_level(settings.level))
.with_visible(settings.visible);
/// The maximum size of the window. if let Some(position) =
pub max_size: Option<(u32, u32)>, conversion::position(monitor.as_ref(), settings.size, settings.position)
{
/// Whether the window should be visible or not. window_builder = window_builder.with_position(position);
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 { if let Some((width, height)) = settings.min_size {
/// Converts the window settings into a `WindowBuilder` from `winit`. window_builder = window_builder
pub fn into_builder( .with_min_inner_size(winit::dpi::LogicalSize { width, height });
self, }
title: &str,
primary_monitor: Option<MonitorHandle>,
_id: Option<String>,
) -> WindowBuilder {
let mut window_builder = WindowBuilder::new();
let (width, height) = self.size; if let Some((width, height)) = settings.max_size {
window_builder = window_builder
.with_max_inner_size(winit::dpi::LogicalSize { width, height });
}
#[cfg(any(
target_os = "linux",
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 window_builder = window_builder
.with_title(title) .with_title_hidden(settings.platform_specific.title_hidden)
.with_inner_size(winit::dpi::LogicalSize { width, height }) .with_titlebar_transparent(
.with_resizable(self.resizable) settings.platform_specific.titlebar_transparent,
.with_decorations(self.decorations) )
.with_transparent(self.transparent) .with_fullsize_content_view(
.with_window_icon(self.icon.and_then(conversion::icon)) settings.platform_specific.fullsize_content_view,
.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 = "linux",
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,
);
}
window_builder
} }
}
impl Default for Window { window_builder
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: Default::default(),
}
}
} }

View file