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

View file

@ -41,7 +41,7 @@ system = ["iced_winit/system"]
web-colors = ["iced_renderer/web-colors"]
# Enables the advanced module
advanced = []
# Enables experimental multi-window support for iced_winit + wgpu.
# Enables experimental multi-window support.
multi-window = ["iced_winit/multi-window"]
[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:
- [`iced_wgpu`] is powered by [`wgpu`] and supports Vulkan, DirectX 12, and Metal.
- [`iced_glow`] is powered by [`glow`] and supports OpenGL 2.1+ and OpenGL ES 2.0+.
- [`tiny-skia`] is used as a fallback software renderer when `wgpu` is not supported.
Additionally, the [`iced_graphics`] subcrate contains a bunch of backend-agnostic types that can be leveraged to build renderers. Both of the renderers rely on the graphical foundations provided by this crate.
@ -54,10 +54,7 @@ The widgets of a graphical user _interface_ are interactive. __Shells__ gather a
Normally, a shell will be responsible of creating a window and managing the lifecycle of a user interface, implementing a runtime of [The Elm Architecture].
As of now, there are two official shells:
- [`iced_winit`] implements a shell runtime on top of [`winit`].
- [`iced_glutin`] is similar to [`iced_winit`], but it also deals with [OpenGL context creation].
As of now, there is one official shell: [`iced_winit`] implements a shell runtime on top of [`winit`].
## The web target
The Web platform provides all the abstractions necessary to draw widgets and gather users interactions.
@ -91,5 +88,4 @@ Finally, [`iced`] unifies everything into a simple abstraction to create cross-p
[`winit`]: https://github.com/rust-windowing/winit
[`glutin`]: https://github.com/rust-windowing/glutin
[`dodrio`]: https://github.com/fitzgen/dodrio
[OpenGL context creation]: https://www.khronos.org/opengl/wiki/Creating_an_OpenGL_Context
[The Elm Architecture]: https://guide.elm-lang.org/architecture/

View file

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

View file

@ -2,14 +2,20 @@
pub mod icon;
mod event;
mod id;
mod level;
mod mode;
mod position;
mod redraw_request;
mod settings;
mod user_attention;
pub use event::Event;
pub use icon::Icon;
pub use id::Id;
pub use level::Level;
pub use mode::Mode;
pub use position::Position;
pub use redraw_request::RedrawRequest;
pub use settings::Settings;
pub use user_attention::UserAttention;

View file

@ -1,4 +1,5 @@
use crate::time::Instant;
use crate::Size;
use std::path::PathBuf;
@ -32,6 +33,22 @@ pub enum Event {
/// occurs.
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.
Focused,

View file

@ -1,18 +1,17 @@
use std::collections::hash_map::DefaultHasher;
use std::fmt::{Display, Formatter};
use std::hash::{Hash, Hasher};
#[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);
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);
/// Creates a new unique window ID.
/// Creates a new unique window [`Id`].
pub fn new(id: impl Hash) -> Id {
let mut hasher = DefaultHasher::new();
id.hash(&mut hasher);
@ -20,9 +19,3 @@ impl Id {
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};
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.
#[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 {
EventOccurred(Event),
Toggled(bool),
Exit(window::Id),
Exit,
}
impl Application for Events {
@ -55,7 +55,8 @@ impl Application for Events {
Command::none()
}
Message::EventOccurred(event) => {
if let Event::Window(id, window::Event::CloseRequested) = event {
if let Event::Window(id, window::Event::CloseRequested) = event
{
window::close(id)
} else {
Command::none()
@ -66,7 +67,7 @@ impl Application for Events {
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> {
match message {
Message::Confirm => window::close(),
Message::Confirm => window::close(window::Id::MAIN),
Message::Exit => {
self.show_confirm = true;

View file

@ -6,8 +6,8 @@ use scene::Scene;
use iced_wgpu::graphics::Viewport;
use iced_wgpu::{wgpu, Backend, Renderer, Settings};
use iced_winit::core::mouse;
use iced_winit::core::renderer;
use iced_winit::core::{mouse, window};
use iced_winit::core::{Color, Size};
use iced_winit::runtime::program;
use iced_winit::runtime::Debug;

View file

@ -277,7 +277,7 @@ where
let state = tree.state.downcast_mut::<State>();
if let Event::Window(window::Event::RedrawRequested(now)) = event {
if let Event::Window(_, window::Event::RedrawRequested(now)) = event {
state.animation = state.animation.timed_transition(
self.cycle_duration,
self.rotation_duration,

View file

@ -198,7 +198,7 @@ where
let state = tree.state.downcast_mut::<State>();
if let Event::Window(window::Event::RedrawRequested(now)) = event {
if let Event::Window(_, window::Event::RedrawRequested(now)) = event {
*state = state.timed_transition(self.cycle_duration, now);
shell.request_redraw(RedrawRequest::At(

View file

@ -6,4 +6,4 @@ edition = "2021"
publish = false
[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::widget::{button, column, container, scrollable, text, text_input};
use iced::window::{Id, Position};
use iced::{
executor, window, Alignment, Command, Element, Length, Settings, Theme,
executor, subscription, window, Alignment, Command, Element, Length,
Settings, Subscription, Theme,
};
use std::collections::HashMap;
fn main() -> iced::Result {
Example::run(Settings::default())
Example::run(Settings {
exit_on_close_request: false,
..Default::default()
})
}
#[derive(Default)]
struct Example {
windows_count: usize,
windows: HashMap<window::Id, Window>,
next_window_pos: window::Position,
}
#[derive(Debug)]
struct Window {
id: window::Id,
title: String,
scale_input: String,
current_scale: f64,
theme: Theme,
input_id: iced::widget::text_input::Id,
}
#[derive(Debug, Clone)]
@ -28,6 +35,8 @@ enum Message {
ScaleChanged(window::Id, String),
TitleChanged(window::Id, String),
CloseWindow(window::Id),
WindowDestroyed(window::Id),
WindowCreated(window::Id, (i32, i32)),
NewWindow,
}
@ -40,11 +49,8 @@ impl multi_window::Application for Example {
fn new(_flags: ()) -> (Self, Command<Message>) {
(
Example {
windows_count: 0,
windows: HashMap::from([(
window::Id::MAIN,
Window::new(window::Id::MAIN),
)]),
windows: HashMap::from([(window::Id::MAIN, Window::new(1))]),
next_window_pos: Position::Default,
},
Command::none(),
)
@ -82,12 +88,32 @@ impl multi_window::Application for Example {
Message::CloseWindow(id) => {
return window::close(id);
}
Message::NewWindow => {
self.windows_count += 1;
let id = window::Id::new(self.windows_count);
self.windows.insert(id, Window::new(id));
Message::WindowDestroyed(id) => {
self.windows.remove(&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> {
let window = self
.windows
.get(&window)
.map(|window| window.view())
.unwrap();
let content = self.windows.get(&window).unwrap().view(window);
container(window)
container(content)
.width(Length::Fill)
.height(Length::Fill)
.center_x()
@ -109,6 +131,10 @@ impl multi_window::Application for Example {
.into()
}
fn theme(&self, window: Id) -> Self::Theme {
self.windows.get(&window).unwrap().theme.clone()
}
fn scale_factor(&self, window: window::Id) -> f64 {
self.windows
.get(&window)
@ -116,55 +142,71 @@ impl multi_window::Application for Example {
.unwrap_or(1.0)
}
fn close_requested(&self, window: window::Id) -> Self::Message {
Message::CloseWindow(window)
fn subscription(&self) -> Subscription<Self::Message> {
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 {
fn new(id: window::Id) -> Self {
fn new(count: usize) -> Self {
Self {
id,
title: "Window".to_string(),
title: format!("Window_{}", count),
scale_input: "1.0".to_string(),
current_scale: 1.0,
theme: if count % 2 == 0 {
Theme::Light
} else {
Theme::Dark
},
input_id: text_input::Id::unique(),
}
}
fn view(&self) -> Element<Message> {
window_view(self.id, &self.scale_input, &self.title)
fn view(&self, id: window::Id) -> Element<Message> {
let scale_input = column![
text("Window scale factor:"),
text_input("Window Scale", &self.scale_input)
.on_input(move |msg| { Message::ScaleInputChanged(id, msg) })
.on_submit(Message::ScaleChanged(
id,
self.scale_input.to_string()
))
];
let title_input = column![
text("Window title:"),
text_input("Window Title", &self.title)
.on_input(move |msg| { Message::TitleChanged(id, msg) })
.id(self.input_id.clone())
];
let new_window_button =
button(text("New Window")).on_press(Message::NewWindow);
let content = scrollable(
column![scale_input, title_input, new_window_button]
.spacing(50)
.width(Length::Fill)
.align_items(Alignment::Center),
);
container(content).width(200).center_x().into()
}
}
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::theme::{Button, Container};
use iced::widget::{button, column, container, image, row, text, text_input};
use iced::window::screenshot::{self, Screenshot};
use iced::{alignment, window};
use iced::{
event, executor, keyboard, subscription, Alignment, Application, Command,
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> {
match message {
Message::Screenshot => {
return iced::window::screenshot(Message::ScreenshotData);
return iced::window::screenshot(
window::Id::MAIN,
Message::ScreenshotData,
);
}
Message::ScreenshotData(screenshot) => {
self.screenshot = Some(screenshot);

View file

@ -528,7 +528,9 @@ mod toast {
clipboard: &mut dyn Clipboard,
shell: &mut Shell<'_, Message>,
) -> event::Status {
if let Event::Window(window::Event::RedrawRequested(now)) = &event {
if let Event::Window(_, window::Event::RedrawRequested(now)) =
&event
{
let mut next_redraw: Option<window::RedrawRequest> = None;
self.instants.iter_mut().enumerate().for_each(

View file

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

View file

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

View file

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

View file

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

View file

View file

@ -46,6 +46,22 @@ impl<Theme> crate::graphics::Compositor for Compositor<Theme> {
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>(
&mut self,
window: &W,

View file

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

View file

@ -53,6 +53,9 @@ pub mod system;
pub mod user_interface;
pub mod window;
#[cfg(feature = "multi-window")]
pub mod multi_window;
// We disable debug capabilities on release builds unless the `debug` feature
// is explicitly enabled.
#[cfg(feature = "debug")]

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 use crate::core::window::Id;
pub use action::Action;
pub use screenshot::Screenshot;
use crate::command::{self, Command};
use crate::core::time::Instant;
use crate::core::window::{Event, Icon, Level, Mode, UserAttention};
use crate::core::window::{self, Event, Icon, Level, Mode, UserAttention};
use crate::core::Size;
use crate::futures::subscription::{self, Subscription};
/// Subscribes to the frames of the window of the running application.
///
/// The resulting [`Subscription`] will produce items at a rate equal to the
/// refresh rate of the window. Note that this rate may be variable, as it is
/// refresh rate of the first application window. Note that this rate may be variable, as it is
/// normally managed by the graphics driver and/or the OS.
///
/// In any case, this [`Subscription`] is useful to smoothly draw application-driven
/// animations without missing any frames.
pub fn frames() -> Subscription<Instant> {
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,
})
}
/// Closes the current window and exits the application.
pub fn close<Message>() -> Command<Message> {
Command::single(command::Action::Window(Action::Close))
/// Spawns a new window with the given `id` and `settings`.
pub fn spawn<Message>(
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.
pub fn drag<Message>() -> Command<Message> {
Command::single(command::Action::Window(Action::Drag))
pub fn drag<Message>(id: window::Id) -> Command<Message> {
Command::single(command::Action::Window(id, Action::Drag))
}
/// Resizes the window to the given logical dimensions.
pub fn resize<Message>(new_size: Size<u32>) -> Command<Message> {
Command::single(command::Action::Window(Action::Resize(new_size)))
pub fn resize<Message>(
id: 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>(
id: window::Id,
f: impl FnOnce(Size<u32>) -> Message + 'static,
) -> 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.
pub fn maximize<Message>(maximized: bool) -> Command<Message> {
Command::single(command::Action::Window(Action::Maximize(maximized)))
pub fn maximize<Message>(id: window::Id, maximized: bool) -> Command<Message> {
Command::single(command::Action::Window(id, Action::Maximize(maximized)))
}
/// Minimes the window.
pub fn minimize<Message>(minimized: bool) -> Command<Message> {
Command::single(command::Action::Window(Action::Minimize(minimized)))
/// Minimizes the window.
pub fn minimize<Message>(id: window::Id, minimized: bool) -> Command<Message> {
Command::single(command::Action::Window(id, Action::Minimize(minimized)))
}
/// Moves a window to the given logical coordinates.
pub fn move_to<Message>(x: i32, y: i32) -> Command<Message> {
Command::single(command::Action::Window(Action::Move { x, y }))
/// Moves the window to the given logical coordinates.
pub fn move_to<Message>(id: window::Id, x: i32, y: i32) -> Command<Message> {
Command::single(command::Action::Window(id, Action::Move { x, y }))
}
/// Changes the [`Mode`] of the window.
pub fn change_mode<Message>(mode: Mode) -> Command<Message> {
Command::single(command::Action::Window(Action::ChangeMode(mode)))
pub fn change_mode<Message>(id: window::Id, mode: Mode) -> Command<Message> {
Command::single(command::Action::Window(id, Action::ChangeMode(mode)))
}
/// Fetches the current [`Mode`] of the window.
pub fn fetch_mode<Message>(
id: window::Id,
f: impl FnOnce(Mode) -> Message + 'static,
) -> 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.
pub fn toggle_maximize<Message>() -> Command<Message> {
Command::single(command::Action::Window(Action::ToggleMaximize))
pub fn toggle_maximize<Message>(id: window::Id) -> Command<Message> {
Command::single(command::Action::Window(id, Action::ToggleMaximize))
}
/// Toggles the window decorations.
pub fn toggle_decorations<Message>() -> Command<Message> {
Command::single(command::Action::Window(Action::ToggleDecorations))
pub fn toggle_decorations<Message>(id: window::Id) -> Command<Message> {
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,
/// see [`UserAttention`] for details.
///
/// Providing `None` will unset the request for user attention. Unsetting the request for
/// user attention might not be done automatically by the WM when the window receives input.
pub fn request_user_attention<Message>(
id: window::Id,
user_attention: Option<UserAttention>,
) -> Command<Message> {
Command::single(command::Action::Window(Action::RequestUserAttention(
user_attention,
)))
Command::single(command::Action::Window(
id,
Action::RequestUserAttention(user_attention),
))
}
/// 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
/// you are certain that's what the user wants. Focus stealing can cause an extremely disruptive
/// user experience.
pub fn gain_focus<Message>() -> Command<Message> {
Command::single(command::Action::Window(Action::GainFocus))
pub fn gain_focus<Message>(id: window::Id) -> Command<Message> {
Command::single(command::Action::Window(id, Action::GainFocus))
}
/// Changes the window [`Level`].
pub fn change_level<Message>(level: Level) -> Command<Message> {
Command::single(command::Action::Window(Action::ChangeLevel(level)))
pub fn change_level<Message>(id: window::Id, level: Level) -> Command<Message> {
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>(
id: window::Id,
f: impl FnOnce(u64) -> Message + 'static,
) -> 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.
pub fn change_icon<Message>(icon: Icon) -> Command<Message> {
Command::single(command::Action::Window(Action::ChangeIcon(icon)))
pub fn change_icon<Message>(id: window::Id, icon: Icon) -> Command<Message> {
Command::single(command::Action::Window(id, Action::ChangeIcon(icon)))
}
/// Captures a [`Screenshot`] from the window.
pub fn screenshot<Message>(
id: window::Id,
f: impl FnOnce(Screenshot) -> Message + Send + 'static,
) -> Command<Message> {
Command::single(command::Action::Window(Action::Screenshot(Box::new(f))))
Command::single(command::Action::Window(
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::futures::MaybeSend;
use crate::window::Screenshot;
@ -15,7 +15,7 @@ pub enum Action<T> {
/// Theres no guarantee that this will work unless the left mouse
/// button was pressed immediately before this function is called.
Drag,
/// Spawns a new window with the provided [`window::Settings`].
/// Spawns a new window.
Spawn {
/// The settings of the [`Window`].
settings: Settings,

View file

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

View file

@ -1,12 +1,8 @@
//! Configure the window of your application in native platforms.
mod position;
mod settings;
pub mod icon;
pub use icon::Icon;
pub use position::Position;
pub use settings::{PlatformSpecific, Settings};
pub use crate::core::window::*;
pub use crate::runtime::window::*;

View file

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

View file

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

View file

@ -12,8 +12,6 @@ categories = ["gui"]
[features]
default = ["x11", "wayland", "wayland-dlopen", "wayland-csd-adwaita"]
trace = ["tracing", "tracing-core", "tracing-subscriber"]
chrome-trace = ["trace", "tracing-chrome"]
debug = ["iced_runtime/debug"]
system = ["sysinfo"]
application = []
@ -21,7 +19,7 @@ x11 = ["winit/x11"]
wayland = ["winit/wayland"]
wayland-dlopen = ["winit/wayland-dlopen"]
wayland-csd-adwaita = ["winit/wayland-csd-adwaita"]
multi-window = []
multi-window = ["iced_runtime/multi-window"]
[dependencies]
window_clipboard = "0.3"
@ -47,24 +45,6 @@ path = "../graphics"
version = "0.8"
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]
version = "0.3.6"

View file

@ -18,6 +18,7 @@ use crate::runtime::clipboard;
use crate::runtime::program::Program;
use crate::runtime::user_interface::{self, UserInterface};
use crate::runtime::{Command, Debug};
use crate::settings;
use crate::style::application::{Appearance, StyleSheet};
use crate::{Clipboard, Error, Proxy, Settings};
@ -25,11 +26,6 @@ use futures::channel::mpsc;
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.
///
/// This trait is the main entrypoint of Iced. Once implemented, you can run
@ -117,15 +113,9 @@ where
use futures::Future;
use winit::event_loop::EventLoopBuilder;
#[cfg(feature = "trace")]
let _guard = Profiler::init();
let mut debug = Debug::new();
debug.startup_started();
#[cfg(feature = "trace")]
let _ = info_span!("Application", "RUN").entered();
let event_loop = EventLoopBuilder::with_user_event().build();
let proxy = event_loop.create_proxy();
@ -146,14 +136,13 @@ where
let target = settings.window.platform_specific.target.clone();
let should_be_visible = settings.window.visible;
let builder = settings
.window
.into_builder(
&application.title(),
event_loop.primary_monitor(),
settings.id,
)
.with_visible(false);
let builder = settings::window_builder(
settings.window,
&application.title(),
event_loop.primary_monitor(),
settings.id,
)
.with_visible(false);
log::debug!("Window builder: {:#?}", builder);
@ -196,28 +185,20 @@ where
let (mut event_sender, event_receiver) = mpsc::unbounded();
let (control_sender, mut control_receiver) = mpsc::unbounded();
let mut instance = Box::pin({
let run_instance = run_instance::<A, E, C>(
application,
compositor,
renderer,
runtime,
proxy,
debug,
event_receiver,
control_sender,
init_command,
window,
should_be_visible,
settings.exit_on_close_request,
);
#[cfg(feature = "trace")]
let run_instance =
run_instance.instrument(info_span!("Application", "LOOP"));
run_instance
});
let mut instance = Box::pin(run_instance::<A, E, C>(
application,
compositor,
renderer,
runtime,
proxy,
debug,
event_receiver,
control_sender,
init_command,
window,
should_be_visible,
settings.exit_on_close_request,
));
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);
}
event::Event::RedrawRequested(_) => {
#[cfg(feature = "trace")]
let _ = info_span!("Application", "FRAME").entered();
let physical_size = state.physical_size();
if physical_size.width == 0 || physical_size.height == 0 {
@ -622,24 +600,12 @@ pub fn build_user_interface<'a, A: Application>(
where
<A::Renderer as core::Renderer>::Theme: StyleSheet,
{
#[cfg(feature = "trace")]
let view_span = info_span!("Application", "VIEW").entered();
debug.view_started();
let view = application.view();
#[cfg(feature = "trace")]
let _ = view_span.exit();
debug.view_finished();
#[cfg(feature = "trace")]
let layout_span = info_span!("Application", "LAYOUT").entered();
debug.layout_started();
let user_interface = UserInterface::build(view, size, cache, renderer);
#[cfg(feature = "trace")]
let _ = layout_span.exit();
debug.layout_finished();
user_interface
@ -666,16 +632,10 @@ pub fn update<A: Application, C, E: Executor>(
<A::Renderer as core::Renderer>::Theme: StyleSheet,
{
for message in messages.drain(..) {
#[cfg(feature = "trace")]
let update_span = info_span!("Application", "UPDATE").entered();
debug.log_message(&message);
debug.update_started();
let command = runtime.enter(|| application.update(message));
#[cfg(feature = "trace")]
let _ = update_span.exit();
debug.update_finished();
run_command(
@ -750,7 +710,7 @@ pub fn run_command<A, C, E>(
}
window::Action::Spawn { .. } => {
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) => {

View file

@ -7,7 +7,6 @@ use crate::core::mouse;
use crate::core::touch;
use crate::core::window;
use crate::core::{Event, Point};
use crate::Position;
/// Converts a winit window event into an iced event.
pub fn window_event(
@ -169,17 +168,17 @@ pub fn window_level(level: window::Level) -> winit::window::WindowLevel {
pub fn position(
monitor: Option<&winit::monitor::MonitorHandle>,
(width, height): (u32, u32),
position: Position,
position: window::Position,
) -> Option<winit::dpi::Position> {
match position {
Position::Default => None,
Position::Specific(x, y) => {
window::Position::Default => None,
window::Position::Specific(x, y) => {
Some(winit::dpi::Position::Logical(winit::dpi::LogicalPosition {
x: f64::from(x),
y: f64::from(y),
}))
}
Position::Centered => {
window::Position::Centered => {
if let Some(monitor) = monitor {
let start = monitor.position();

View file

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

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.
#[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::core::window::{Icon, Level};
use crate::Position;
use crate::core::window;
use winit::monitor::MonitorHandle;
use winit::window::WindowBuilder;
use std::fmt;
/// The settings of an application.
#[derive(Debug, Clone, Default)]
pub struct Settings<Flags> {
@ -40,7 +15,7 @@ pub struct Settings<Flags> {
pub id: Option<String>,
/// The [`Window`] settings.
pub window: Window,
pub window: window::Settings,
/// 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
/// 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
pub exit_on_close_request: bool,
}
/// The window settings of an application.
#[derive(Clone)]
pub struct Window {
/// The size of the window.
pub size: (u32, u32),
/// Converts the window settings into a `WindowBuilder` from `winit`.
pub fn window_builder(
settings: window::Settings,
title: &str,
monitor: Option<MonitorHandle>,
_id: Option<String>,
) -> WindowBuilder {
let mut window_builder = WindowBuilder::new();
/// The position of the window.
pub position: Position,
let (width, height) = settings.size;
/// The minimum size of the window.
pub min_size: Option<(u32, u32)>,
window_builder = window_builder
.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.
pub max_size: Option<(u32, u32)>,
/// Whether the window should be visible or not.
pub visible: bool,
/// Whether the window should be resizable or not.
pub resizable: bool,
/// Whether the window should have a border, a title bar, etc.
pub decorations: bool,
/// Whether the window should be transparent.
pub transparent: bool,
/// The window [`Level`].
pub level: Level,
/// The window icon, which is also usually used in the taskbar
pub icon: Option<Icon>,
/// Platform specific settings.
pub platform_specific: platform::PlatformSpecific,
}
impl fmt::Debug for Window {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Window")
.field("size", &self.size)
.field("position", &self.position)
.field("min_size", &self.min_size)
.field("max_size", &self.max_size)
.field("visible", &self.visible)
.field("resizable", &self.resizable)
.field("decorations", &self.decorations)
.field("transparent", &self.transparent)
.field("level", &self.level)
.field("icon", &self.icon.is_some())
.field("platform_specific", &self.platform_specific)
.finish()
if let Some(position) =
conversion::position(monitor.as_ref(), settings.size, settings.position)
{
window_builder = window_builder.with_position(position);
}
}
impl Window {
/// Converts the window settings into a `WindowBuilder` from `winit`.
pub fn into_builder(
self,
title: &str,
primary_monitor: Option<MonitorHandle>,
_id: Option<String>,
) -> WindowBuilder {
let mut window_builder = WindowBuilder::new();
if let Some((width, height)) = settings.min_size {
window_builder = window_builder
.with_min_inner_size(winit::dpi::LogicalSize { width, height });
}
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
.with_title(title)
.with_inner_size(winit::dpi::LogicalSize { width, height })
.with_resizable(self.resizable)
.with_decorations(self.decorations)
.with_transparent(self.transparent)
.with_window_icon(self.icon.and_then(conversion::icon))
.with_window_level(conversion::window_level(self.level))
.with_visible(self.visible);
if let Some(position) = conversion::position(
primary_monitor.as_ref(),
self.size,
self.position,
) {
window_builder = window_builder.with_position(position);
}
if let Some((width, height)) = self.min_size {
window_builder = window_builder
.with_min_inner_size(winit::dpi::LogicalSize { width, height });
}
if let Some((width, height)) = self.max_size {
window_builder = window_builder
.with_max_inner_size(winit::dpi::LogicalSize { width, height });
}
#[cfg(any(
target_os = "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
.with_title_hidden(settings.platform_specific.title_hidden)
.with_titlebar_transparent(
settings.platform_specific.titlebar_transparent,
)
.with_fullsize_content_view(
settings.platform_specific.fullsize_content_view,
);
}
}
impl Default for Window {
fn default() -> Window {
Window {
size: (1024, 768),
position: Position::default(),
min_size: None,
max_size: None,
visible: true,
resizable: true,
decorations: true,
transparent: false,
level: Level::default(),
icon: None,
platform_specific: Default::default(),
}
}
window_builder
}

View file