summaryrefslogtreecommitdiffstats
path: root/winit/src/application.rs
diff options
context:
space:
mode:
Diffstat (limited to 'winit/src/application.rs')
-rw-r--r--winit/src/application.rs398
1 files changed, 212 insertions, 186 deletions
diff --git a/winit/src/application.rs b/winit/src/application.rs
index 1042b412..f5aa799c 100644
--- a/winit/src/application.rs
+++ b/winit/src/application.rs
@@ -1,9 +1,7 @@
use crate::{
- conversion,
- input::{keyboard, mouse},
- renderer::{Target, Windowed},
- Cache, Command, Container, Debug, Element, Event, Length, MouseCursor,
- Settings, UserInterface,
+ conversion, size::Size, window, Cache, Clipboard, Command, Debug, Element,
+ Executor, Mode, MouseCursor, Proxy, Runtime, Settings, Subscription,
+ UserInterface,
};
/// An interactive, native cross-platform application.
@@ -15,10 +13,15 @@ use crate::{
/// An [`Application`](trait.Application.html) can execute asynchronous actions
/// by returning a [`Command`](struct.Command.html) in some of its methods.
pub trait Application: Sized {
- /// The renderer to use to draw the [`Application`].
+ /// The graphics backend to use to draw the [`Application`].
///
/// [`Application`]: trait.Application.html
- type Renderer: Windowed;
+ type Backend: window::Backend;
+
+ /// The [`Executor`] that will run commands and subscriptions.
+ ///
+ /// [`Executor`]: trait.Executor.html
+ type Executor: Executor;
/// The type of __messages__ your [`Application`] will produce.
///
@@ -57,12 +60,35 @@ pub trait Application: Sized {
/// [`Command`]: struct.Command.html
fn update(&mut self, message: Self::Message) -> Command<Self::Message>;
+ /// Returns the event `Subscription` for the current state of the
+ /// application.
+ ///
+ /// The messages produced by the `Subscription` will be handled by
+ /// [`update`](#tymethod.update).
+ ///
+ /// A `Subscription` will be kept alive as long as you keep returning it!
+ fn subscription(&self) -> Subscription<Self::Message>;
+
/// Returns the widgets to display in the [`Application`].
///
/// These widgets can produce __messages__ based on user interaction.
///
/// [`Application`]: trait.Application.html
- fn view(&mut self) -> Element<'_, Self::Message, Self::Renderer>;
+ fn view(
+ &mut self,
+ ) -> Element<'_, Self::Message, <Self::Backend as window::Backend>::Renderer>;
+
+ /// Returns the current [`Application`] mode.
+ ///
+ /// The runtime will automatically transition your application if a new mode
+ /// is returned.
+ ///
+ /// By default, an application will run in windowed mode.
+ ///
+ /// [`Application`]: trait.Application.html
+ fn mode(&self) -> Mode {
+ Mode::Windowed
+ }
/// Runs the [`Application`].
///
@@ -72,10 +98,13 @@ pub trait Application: Sized {
/// It should probably be that last thing you call in your `main` function.
///
/// [`Application`]: trait.Application.html
- fn run(settings: Settings)
- where
+ fn run(
+ settings: Settings,
+ backend_settings: <Self::Backend as window::Backend>::Settings,
+ ) where
Self: 'static,
{
+ use window::Backend as _;
use winit::{
event::{self, WindowEvent},
event_loop::{ControlFlow, EventLoop},
@@ -86,49 +115,75 @@ pub trait Application: Sized {
debug.startup_started();
let event_loop = EventLoop::with_user_event();
- let proxy = event_loop.create_proxy();
- let mut thread_pool =
- futures::executor::ThreadPool::new().expect("Create thread pool");
let mut external_messages = Vec::new();
- let (mut application, init_command) = Self::new();
- spawn(init_command, &mut thread_pool, &proxy);
+ let mut runtime = {
+ let executor = Self::Executor::new().expect("Create executor");
+
+ Runtime::new(executor, Proxy::new(event_loop.create_proxy()))
+ };
+
+ let (mut application, init_command) = runtime.enter(|| Self::new());
+ runtime.spawn(init_command);
+
+ let subscription = application.subscription();
+ runtime.track(subscription);
let mut title = application.title();
+ let mut mode = application.mode();
+
+ let window = {
+ let mut window_builder = WindowBuilder::new();
- let (width, height) = settings.window.size;
+ let (width, height) = settings.window.size;
- let window = WindowBuilder::new()
- .with_title(&title)
- .with_inner_size(winit::dpi::LogicalSize {
- width: f64::from(width),
- height: f64::from(height),
- })
- .with_resizable(settings.window.resizable)
- .build(&event_loop)
- .expect("Open window");
+ window_builder = window_builder
+ .with_title(&title)
+ .with_inner_size(winit::dpi::LogicalSize { width, height })
+ .with_resizable(settings.window.resizable)
+ .with_decorations(settings.window.decorations)
+ .with_fullscreen(conversion::fullscreen(
+ event_loop.primary_monitor(),
+ mode,
+ ));
- let dpi = window.hidpi_factor();
- let mut size = window.inner_size();
- let mut new_size: Option<winit::dpi::LogicalSize> = None;
+ #[cfg(target_os = "windows")]
+ {
+ use winit::platform::windows::WindowBuilderExtWindows;
- let mut renderer = Self::Renderer::new();
+ if let Some(parent) = settings.window.platform_specific.parent {
+ window_builder = window_builder.with_parent_window(parent);
+ }
+ }
+
+ window_builder.build(&event_loop).expect("Open window")
+ };
- let mut target = {
- let (width, height) = to_physical(size, dpi);
+ let mut size = Size::new(window.inner_size(), window.scale_factor());
+ let mut resized = false;
- <Self::Renderer as Windowed>::Target::new(
- &window, width, height, dpi as f32, &renderer,
+ let clipboard = Clipboard::new(&window);
+ let (mut backend, mut renderer) = Self::Backend::new(backend_settings);
+
+ let surface = backend.create_surface(&window);
+
+ let mut swap_chain = {
+ let physical_size = size.physical();
+
+ backend.create_swap_chain(
+ &surface,
+ physical_size.width,
+ physical_size.height,
)
};
- debug.layout_started();
- let user_interface = UserInterface::build(
- document(&mut application, size, &mut debug),
+ let user_interface = build_user_interface(
+ &mut application,
Cache::default(),
&mut renderer,
+ size.logical(),
+ &mut debug,
);
- debug.layout_finished();
debug.draw_started();
let mut primitive = user_interface.draw(&mut renderer);
@@ -137,28 +192,43 @@ pub trait Application: Sized {
let mut cache = Some(user_interface.into_cache());
let mut events = Vec::new();
let mut mouse_cursor = MouseCursor::OutOfBounds;
+ let mut modifiers = winit::event::ModifiersState::default();
debug.startup_finished();
window.request_redraw();
event_loop.run(move |event, _, control_flow| match event {
event::Event::MainEventsCleared => {
+ if events.is_empty() && external_messages.is_empty() {
+ return;
+ }
+
// TODO: We should be able to keep a user interface alive
// between events once we remove state references.
//
// This will allow us to rebuild it only when a message is
// handled.
- debug.layout_started();
- let mut user_interface = UserInterface::build(
- document(&mut application, size, &mut debug),
+ let mut user_interface = build_user_interface(
+ &mut application,
cache.take().unwrap(),
&mut renderer,
+ size.logical(),
+ &mut debug,
);
- debug.layout_finished();
debug.event_processing_started();
- let mut messages =
- user_interface.update(&renderer, events.drain(..));
+ events
+ .iter()
+ .cloned()
+ .for_each(|event| runtime.broadcast(event));
+
+ let mut messages = user_interface.update(
+ events.drain(..),
+ clipboard
+ .as_ref()
+ .map(|c| c as &dyn iced_native::Clipboard),
+ &renderer,
+ );
messages.extend(external_messages.drain(..));
debug.event_processing_finished();
@@ -179,12 +249,15 @@ pub trait Application: Sized {
debug.log_message(&message);
debug.update_started();
- let command = application.update(message);
-
- spawn(command, &mut thread_pool, &proxy);
+ let command =
+ runtime.enter(|| application.update(message));
+ runtime.spawn(command);
debug.update_finished();
}
+ let subscription = application.subscription();
+ runtime.track(subscription);
+
// Update window title
let new_title = application.title();
@@ -194,13 +267,25 @@ pub trait Application: Sized {
title = new_title;
}
- debug.layout_started();
- let user_interface = UserInterface::build(
- document(&mut application, size, &mut debug),
+ // Update window mode
+ let new_mode = application.mode();
+
+ if mode != new_mode {
+ window.set_fullscreen(conversion::fullscreen(
+ window.current_monitor(),
+ new_mode,
+ ));
+
+ mode = new_mode;
+ }
+
+ let user_interface = build_user_interface(
+ &mut application,
temp_cache,
&mut renderer,
+ size.logical(),
+ &mut debug,
);
- debug.layout_finished();
debug.draw_started();
primitive = user_interface.draw(&mut renderer);
@@ -217,22 +302,25 @@ pub trait Application: Sized {
event::Event::RedrawRequested(_) => {
debug.render_started();
- if let Some(new_size) = new_size.take() {
- let dpi = window.hidpi_factor();
- let (width, height) = to_physical(new_size, dpi);
+ if resized {
+ let physical_size = size.physical();
- target.resize(
- width,
- height,
- window.hidpi_factor() as f32,
- &renderer,
+ swap_chain = backend.create_swap_chain(
+ &surface,
+ physical_size.width,
+ physical_size.height,
);
- size = new_size;
+ resized = false;
}
- let new_mouse_cursor =
- renderer.draw(&primitive, &debug.overlay(), &mut target);
+ let new_mouse_cursor = backend.draw(
+ &mut renderer,
+ &mut swap_chain,
+ &primitive,
+ size.scale_factor(),
+ &debug.overlay(),
+ );
debug.render_finished();
@@ -250,83 +338,56 @@ pub trait Application: Sized {
event::Event::WindowEvent {
event: window_event,
..
- } => match window_event {
- WindowEvent::CursorMoved { position, .. } => {
- events.push(Event::Mouse(mouse::Event::CursorMoved {
- x: position.x as f32,
- y: position.y as f32,
- }));
- }
- WindowEvent::MouseInput { button, state, .. } => {
- events.push(Event::Mouse(mouse::Event::Input {
- button: conversion::mouse_button(button),
- state: conversion::button_state(state),
- }));
- }
- WindowEvent::MouseWheel { delta, .. } => match delta {
- winit::event::MouseScrollDelta::LineDelta(
- delta_x,
- delta_y,
- ) => {
- events.push(Event::Mouse(
- mouse::Event::WheelScrolled {
- delta: mouse::ScrollDelta::Lines {
- x: delta_x,
- y: delta_y,
- },
- },
- ));
+ } => {
+ match window_event {
+ WindowEvent::Resized(new_size) => {
+ size = Size::new(new_size, window.scale_factor());
+ resized = true;
}
- winit::event::MouseScrollDelta::PixelDelta(position) => {
- events.push(Event::Mouse(
- mouse::Event::WheelScrolled {
- delta: mouse::ScrollDelta::Pixels {
- x: position.x as f32,
- y: position.y as f32,
- },
- },
- ));
+ WindowEvent::CloseRequested => {
+ *control_flow = ControlFlow::Exit;
}
- },
- WindowEvent::ReceivedCharacter(c)
- if !is_private_use_character(c) =>
- {
- events.push(Event::Keyboard(
- keyboard::Event::CharacterReceived(c),
- ));
- }
- WindowEvent::KeyboardInput {
- input:
- winit::event::KeyboardInput {
- virtual_keycode: Some(virtual_keycode),
- state,
- ..
- },
- ..
- } => {
- match (virtual_keycode, state) {
- (
- winit::event::VirtualKeyCode::F12,
- winit::event::ElementState::Pressed,
- ) => debug.toggle(),
- _ => {}
+ #[cfg(target_os = "macos")]
+ WindowEvent::KeyboardInput {
+ input:
+ winit::event::KeyboardInput {
+ virtual_keycode:
+ Some(winit::event::VirtualKeyCode::Q),
+ state: winit::event::ElementState::Pressed,
+ ..
+ },
+ ..
+ } if modifiers.logo() => {
+ *control_flow = ControlFlow::Exit;
}
-
- events.push(Event::Keyboard(keyboard::Event::Input {
- key_code: conversion::key_code(virtual_keycode),
- state: conversion::button_state(state),
- }));
- }
- WindowEvent::CloseRequested => {
- *control_flow = ControlFlow::Exit;
+ #[cfg(feature = "debug")]
+ WindowEvent::KeyboardInput {
+ input:
+ winit::event::KeyboardInput {
+ virtual_keycode:
+ Some(winit::event::VirtualKeyCode::F12),
+ state: winit::event::ElementState::Pressed,
+ ..
+ },
+ ..
+ } => debug.toggle(),
+ _ => {}
}
- WindowEvent::Resized(size) => {
- new_size = Some(size.into());
- log::debug!("Resized: {:?}", new_size);
+ if let Some(event) = conversion::window_event(
+ window_event,
+ size.scale_factor(),
+ modifiers,
+ ) {
+ events.push(event);
}
- _ => {}
- },
+ }
+ event::Event::DeviceEvent {
+ event: event::DeviceEvent::ModifiersChanged(new_modifiers),
+ ..
+ } => {
+ modifiers = new_modifiers;
+ }
_ => {
*control_flow = ControlFlow::Wait;
}
@@ -334,63 +395,28 @@ pub trait Application: Sized {
}
}
-fn to_physical(size: winit::dpi::LogicalSize, dpi: f64) -> (u16, u16) {
- let physical_size = size.to_physical(dpi);
-
- (
- physical_size.width.round() as u16,
- physical_size.height.round() as u16,
- )
-}
-
-fn document<'a, Application>(
- application: &'a mut Application,
- size: winit::dpi::LogicalSize,
+fn build_user_interface<'a, A: Application>(
+ application: &'a mut A,
+ cache: Cache,
+ renderer: &mut <A::Backend as window::Backend>::Renderer,
+ size: winit::dpi::LogicalSize<f64>,
debug: &mut Debug,
-) -> Element<'a, Application::Message, Application::Renderer>
-where
- Application: self::Application,
- Application::Message: 'static,
-{
+) -> UserInterface<'a, A::Message, <A::Backend as window::Backend>::Renderer> {
debug.view_started();
let view = application.view();
debug.view_finished();
- Container::new(view)
- .width(Length::Units(size.width.round() as u16))
- .height(Length::Units(size.height.round() as u16))
- .into()
-}
-
-fn spawn<Message: Send>(
- command: Command<Message>,
- thread_pool: &mut futures::executor::ThreadPool,
- proxy: &winit::event_loop::EventLoopProxy<Message>,
-) {
- use futures::FutureExt;
-
- let futures = command.futures();
-
- for future in futures {
- let proxy = proxy.clone();
-
- let future = future.map(move |message| {
- proxy
- .send_event(message)
- .expect("Send command result to event loop");
- });
-
- thread_pool.spawn_ok(future);
- }
-}
-
-// As defined in: http://www.unicode.org/faq/private_use.html
-// TODO: Remove once https://github.com/rust-windowing/winit/pull/1254 lands
-fn is_private_use_character(c: char) -> bool {
- match c {
- '\u{E000}'..='\u{F8FF}'
- | '\u{F0000}'..='\u{FFFFD}'
- | '\u{100000}'..='\u{10FFFD}' => true,
- _ => false,
- }
+ debug.layout_started();
+ let user_interface = UserInterface::build(
+ view,
+ iced_native::Size::new(
+ size.width.round() as f32,
+ size.height.round() as f32,
+ ),
+ cache,
+ renderer,
+ );
+ debug.layout_finished();
+
+ user_interface
}