refactored window storage;
new helper window events (Destroyed, Created); clippy + fmt;
This commit is contained in:
parent
633f405f3f
commit
d53ccc857d
56 changed files with 1508 additions and 1819 deletions
1
.github/ISSUE_TEMPLATE/BUG-REPORT.yml
vendored
1
.github/ISSUE_TEMPLATE/BUG-REPORT.yml
vendored
|
|
@ -25,7 +25,6 @@ body:
|
||||||
Before filing an issue...
|
Before filing an issue...
|
||||||
|
|
||||||
- If you are using `wgpu`, you need an environment that supports Vulkan, Metal, or DirectX 12. Please, make sure you can run [the `wgpu` examples].
|
- If you are using `wgpu`, you need an environment that supports Vulkan, Metal, or DirectX 12. Please, make sure you can run [the `wgpu` examples].
|
||||||
- If you are using `glow`, you need support for OpenGL 2.1+. Please, make sure you can run [the `glow` examples].
|
|
||||||
|
|
||||||
If you have any issues running any of the examples, make sure your graphics drivers are up-to-date. If the issues persist, please report them to the authors of the libraries directly!
|
If you have any issues running any of the examples, make sure your graphics drivers are up-to-date. If the issues persist, please report them to the authors of the libraries directly!
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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]
|
||||||
|
|
|
||||||
|
|
@ -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/
|
||||||
|
|
|
||||||
|
|
@ -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"
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -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),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -34,7 +34,7 @@ impl Application for Exit {
|
||||||
|
|
||||||
fn update(&mut self, message: Message) -> Command<Message> {
|
fn update(&mut self, message: Message) -> Command<Message> {
|
||||||
match message {
|
match message {
|
||||||
Message::Confirm => window::close(),
|
Message::Confirm => window::close(window::Id::MAIN),
|
||||||
Message::Exit => {
|
Message::Exit => {
|
||||||
self.show_confirm = true;
|
self.show_confirm = true;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -6,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;
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
|
|
|
||||||
|
|
@ -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(
|
||||||
|
|
|
||||||
|
|
@ -6,4 +6,4 @@ edition = "2021"
|
||||||
publish = false
|
publish = false
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
iced = { path = "../..", features = ["debug", "multi-window"] }
|
iced = { path = "../..", features = ["debug", "multi-window"] }
|
||||||
|
|
|
||||||
|
|
@ -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()
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -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" }
|
|
||||||
|
|
@ -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()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -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);
|
||||||
|
|
|
||||||
|
|
@ -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(
|
||||||
|
|
|
||||||
|
|
@ -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(),
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -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),
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
|
||||||
|
|
@ -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 = []
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,7 @@ repository = "https://github.com/iced-rs/iced"
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
debug = []
|
debug = []
|
||||||
|
multi-window = []
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
thiserror = "1"
|
thiserror = "1"
|
||||||
|
|
|
||||||
|
|
@ -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")]
|
||||||
|
|
|
||||||
6
runtime/src/multi_window.rs
Normal file
6
runtime/src/multi_window.rs
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
//! A multi-window application.
|
||||||
|
pub mod program;
|
||||||
|
pub mod state;
|
||||||
|
|
||||||
|
pub use program::Program;
|
||||||
|
pub use state::State;
|
||||||
32
runtime/src/multi_window/program.rs
Normal file
32
runtime/src/multi_window/program.rs
Normal file
|
|
@ -0,0 +1,32 @@
|
||||||
|
//! Build interactive programs using The Elm Architecture.
|
||||||
|
use crate::{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>;
|
||||||
|
}
|
||||||
280
runtime/src/multi_window/state.rs
Normal file
280
runtime/src/multi_window/state.rs
Normal file
|
|
@ -0,0 +1,280 @@
|
||||||
|
//! The internal state of a multi-window [`Program`].
|
||||||
|
use crate::core::event::{self, Event};
|
||||||
|
use crate::core::mouse;
|
||||||
|
use crate::core::renderer;
|
||||||
|
use crate::core::widget::operation::{self, Operation};
|
||||||
|
use crate::core::{Clipboard, Size};
|
||||||
|
use crate::user_interface::{self, UserInterface};
|
||||||
|
use crate::{Command, Debug, Program};
|
||||||
|
|
||||||
|
/// The execution state of a multi-window [`Program`]. It leverages caching, event
|
||||||
|
/// processing, and rendering primitive storage.
|
||||||
|
#[allow(missing_debug_implementations)]
|
||||||
|
pub struct State<P>
|
||||||
|
where
|
||||||
|
P: Program + 'static,
|
||||||
|
{
|
||||||
|
program: P,
|
||||||
|
caches: Option<Vec<user_interface::Cache>>,
|
||||||
|
queued_events: Vec<Event>,
|
||||||
|
queued_messages: Vec<P::Message>,
|
||||||
|
mouse_interaction: mouse::Interaction,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<P> State<P>
|
||||||
|
where
|
||||||
|
P: Program + 'static,
|
||||||
|
{
|
||||||
|
/// Creates a new [`State`] with the provided [`Program`], initializing its
|
||||||
|
/// primitive with the given logical bounds and renderer.
|
||||||
|
pub fn new(
|
||||||
|
program: P,
|
||||||
|
bounds: Size,
|
||||||
|
renderer: &mut P::Renderer,
|
||||||
|
debug: &mut Debug,
|
||||||
|
) -> Self {
|
||||||
|
let user_interface = build_user_interface(
|
||||||
|
&program,
|
||||||
|
user_interface::Cache::default(),
|
||||||
|
renderer,
|
||||||
|
bounds,
|
||||||
|
debug,
|
||||||
|
);
|
||||||
|
|
||||||
|
let caches = Some(vec![user_interface.into_cache()]);
|
||||||
|
|
||||||
|
State {
|
||||||
|
program,
|
||||||
|
caches,
|
||||||
|
queued_events: Vec::new(),
|
||||||
|
queued_messages: Vec::new(),
|
||||||
|
mouse_interaction: mouse::Interaction::Idle,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns a reference to the [`Program`] of the [`State`].
|
||||||
|
pub fn program(&self) -> &P {
|
||||||
|
&self.program
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Queues an event in the [`State`] for processing during an [`update`].
|
||||||
|
///
|
||||||
|
/// [`update`]: Self::update
|
||||||
|
pub fn queue_event(&mut self, event: Event) {
|
||||||
|
self.queued_events.push(event);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Queues a message in the [`State`] for processing during an [`update`].
|
||||||
|
///
|
||||||
|
/// [`update`]: Self::update
|
||||||
|
pub fn queue_message(&mut self, message: P::Message) {
|
||||||
|
self.queued_messages.push(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns whether the event queue of the [`State`] is empty or not.
|
||||||
|
pub fn is_queue_empty(&self) -> bool {
|
||||||
|
self.queued_events.is_empty() && self.queued_messages.is_empty()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the current [`mouse::Interaction`] of the [`State`].
|
||||||
|
pub fn mouse_interaction(&self) -> mouse::Interaction {
|
||||||
|
self.mouse_interaction
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Processes all the queued events and messages, rebuilding and redrawing
|
||||||
|
/// the widgets of the linked [`Program`] if necessary.
|
||||||
|
///
|
||||||
|
/// Returns a list containing the instances of [`Event`] that were not
|
||||||
|
/// captured by any widget, and the [`Command`] obtained from [`Program`]
|
||||||
|
/// after updating it, only if an update was necessary.
|
||||||
|
pub fn update(
|
||||||
|
&mut self,
|
||||||
|
bounds: Size,
|
||||||
|
cursor: mouse::Cursor,
|
||||||
|
renderer: &mut P::Renderer,
|
||||||
|
theme: &<P::Renderer as iced_core::Renderer>::Theme,
|
||||||
|
style: &renderer::Style,
|
||||||
|
clipboard: &mut dyn Clipboard,
|
||||||
|
debug: &mut Debug,
|
||||||
|
) -> (Vec<Event>, Option<Command<P::Message>>) {
|
||||||
|
let mut user_interfaces = build_user_interfaces(
|
||||||
|
&self.program,
|
||||||
|
self.caches.take().unwrap(),
|
||||||
|
renderer,
|
||||||
|
bounds,
|
||||||
|
debug,
|
||||||
|
);
|
||||||
|
|
||||||
|
debug.event_processing_started();
|
||||||
|
let mut messages = Vec::new();
|
||||||
|
|
||||||
|
let uncaptured_events = user_interfaces.iter_mut().fold(
|
||||||
|
vec![],
|
||||||
|
|mut uncaptured_events, ui| {
|
||||||
|
let (_, event_statuses) = ui.update(
|
||||||
|
&self.queued_events,
|
||||||
|
cursor,
|
||||||
|
renderer,
|
||||||
|
clipboard,
|
||||||
|
&mut messages,
|
||||||
|
);
|
||||||
|
|
||||||
|
uncaptured_events.extend(
|
||||||
|
self.queued_events
|
||||||
|
.iter()
|
||||||
|
.zip(event_statuses)
|
||||||
|
.filter_map(|(event, status)| {
|
||||||
|
matches!(status, event::Status::Ignored)
|
||||||
|
.then_some(event)
|
||||||
|
})
|
||||||
|
.cloned(),
|
||||||
|
);
|
||||||
|
uncaptured_events
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
self.queued_events.clear();
|
||||||
|
messages.append(&mut self.queued_messages);
|
||||||
|
debug.event_processing_finished();
|
||||||
|
|
||||||
|
let commands = if messages.is_empty() {
|
||||||
|
debug.draw_started();
|
||||||
|
|
||||||
|
for ui in &mut user_interfaces {
|
||||||
|
self.mouse_interaction =
|
||||||
|
ui.draw(renderer, theme, style, cursor);
|
||||||
|
}
|
||||||
|
|
||||||
|
debug.draw_finished();
|
||||||
|
|
||||||
|
self.caches = Some(
|
||||||
|
user_interfaces
|
||||||
|
.drain(..)
|
||||||
|
.map(UserInterface::into_cache)
|
||||||
|
.collect(),
|
||||||
|
);
|
||||||
|
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
let temp_caches = user_interfaces
|
||||||
|
.drain(..)
|
||||||
|
.map(UserInterface::into_cache)
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
drop(user_interfaces);
|
||||||
|
|
||||||
|
let commands = Command::batch(messages.into_iter().map(|msg| {
|
||||||
|
debug.log_message(&msg);
|
||||||
|
|
||||||
|
debug.update_started();
|
||||||
|
let command = self.program.update(msg);
|
||||||
|
debug.update_finished();
|
||||||
|
|
||||||
|
command
|
||||||
|
}));
|
||||||
|
|
||||||
|
let mut user_interfaces = build_user_interfaces(
|
||||||
|
&self.program,
|
||||||
|
temp_caches,
|
||||||
|
renderer,
|
||||||
|
bounds,
|
||||||
|
debug,
|
||||||
|
);
|
||||||
|
|
||||||
|
debug.draw_started();
|
||||||
|
for ui in &mut user_interfaces {
|
||||||
|
self.mouse_interaction =
|
||||||
|
ui.draw(renderer, theme, style, cursor);
|
||||||
|
}
|
||||||
|
debug.draw_finished();
|
||||||
|
|
||||||
|
self.caches = Some(
|
||||||
|
user_interfaces
|
||||||
|
.drain(..)
|
||||||
|
.map(UserInterface::into_cache)
|
||||||
|
.collect(),
|
||||||
|
);
|
||||||
|
|
||||||
|
Some(commands)
|
||||||
|
};
|
||||||
|
|
||||||
|
(uncaptured_events, commands)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Applies [`widget::Operation`]s to the [`State`]
|
||||||
|
pub fn operate(
|
||||||
|
&mut self,
|
||||||
|
renderer: &mut P::Renderer,
|
||||||
|
operations: impl Iterator<Item = Box<dyn Operation<P::Message>>>,
|
||||||
|
bounds: Size,
|
||||||
|
debug: &mut Debug,
|
||||||
|
) {
|
||||||
|
let mut user_interfaces = build_user_interfaces(
|
||||||
|
&self.program,
|
||||||
|
self.caches.take().unwrap(),
|
||||||
|
renderer,
|
||||||
|
bounds,
|
||||||
|
debug,
|
||||||
|
);
|
||||||
|
|
||||||
|
for operation in operations {
|
||||||
|
let mut current_operation = Some(operation);
|
||||||
|
|
||||||
|
while let Some(mut operation) = current_operation.take() {
|
||||||
|
for ui in &mut user_interfaces {
|
||||||
|
ui.operate(renderer, operation.as_mut());
|
||||||
|
}
|
||||||
|
|
||||||
|
match operation.finish() {
|
||||||
|
operation::Outcome::None => {}
|
||||||
|
operation::Outcome::Some(message) => {
|
||||||
|
self.queued_messages.push(message)
|
||||||
|
}
|
||||||
|
operation::Outcome::Chain(next) => {
|
||||||
|
current_operation = Some(next);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
self.caches = Some(
|
||||||
|
user_interfaces
|
||||||
|
.drain(..)
|
||||||
|
.map(UserInterface::into_cache)
|
||||||
|
.collect(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn build_user_interfaces<'a, P: Program>(
|
||||||
|
program: &'a P,
|
||||||
|
mut caches: Vec<user_interface::Cache>,
|
||||||
|
renderer: &mut P::Renderer,
|
||||||
|
size: Size,
|
||||||
|
debug: &mut Debug,
|
||||||
|
) -> Vec<UserInterface<'a, P::Message, P::Renderer>> {
|
||||||
|
caches
|
||||||
|
.drain(..)
|
||||||
|
.map(|cache| {
|
||||||
|
build_user_interface(program, cache, renderer, size, debug)
|
||||||
|
})
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn build_user_interface<'a, P: Program>(
|
||||||
|
program: &'a P,
|
||||||
|
cache: user_interface::Cache,
|
||||||
|
renderer: &mut P::Renderer,
|
||||||
|
size: Size,
|
||||||
|
debug: &mut Debug,
|
||||||
|
) -> UserInterface<'a, P::Message, P::Renderer> {
|
||||||
|
debug.view_started();
|
||||||
|
let view = program.view();
|
||||||
|
debug.view_finished();
|
||||||
|
|
||||||
|
debug.layout_started();
|
||||||
|
let user_interface = UserInterface::build(view, size, cache, renderer);
|
||||||
|
debug.layout_finished();
|
||||||
|
|
||||||
|
user_interface
|
||||||
|
}
|
||||||
|
|
@ -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)),
|
||||||
|
))
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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> {
|
||||||
/// There’s no guarantee that this will work unless the left mouse
|
/// There’s no guarantee that this will work unless the left mouse
|
||||||
/// button was pressed immediately before this function is called.
|
/// button was pressed immediately before this function is called.
|
||||||
Drag,
|
Drag,
|
||||||
/// 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,
|
||||||
|
|
|
||||||
|
|
@ -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)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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::*;
|
||||||
|
|
|
||||||
|
|
@ -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),
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
|
|
|
||||||
|
|
@ -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"
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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) => {
|
||||||
|
|
|
||||||
|
|
@ -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();
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
170
winit/src/multi_window/windows.rs
Normal file
170
winit/src/multi_window/windows.rs
Normal 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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -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(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue