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.rs565
1 files changed, 279 insertions, 286 deletions
diff --git a/winit/src/application.rs b/winit/src/application.rs
index f6bc8fcc..d1a94864 100644
--- a/winit/src/application.rs
+++ b/winit/src/application.rs
@@ -1,39 +1,36 @@
+//! Create interactive, native cross-platform applications.
+mod state;
+
+pub use state::State;
+
+use crate::conversion;
+use crate::mouse;
use crate::{
- conversion, mouse, size::Size, window, Cache, Clipboard, Command, Debug,
- Element, Executor, Mode, Proxy, Runtime, Settings, Subscription,
- UserInterface,
+ Clipboard, Color, Command, Debug, Error, Executor, Mode, Proxy, Runtime,
+ Settings, Size, Subscription,
};
+use iced_futures::futures;
+use iced_futures::futures::channel::mpsc;
+use iced_graphics::window;
+use iced_native::program::Program;
+use iced_native::{Cache, UserInterface};
+
+use std::mem::ManuallyDrop;
+
/// An interactive, native cross-platform application.
///
/// This trait is the main entrypoint of Iced. Once implemented, you can run
/// your GUI application by simply calling [`run`](#method.run). It will run in
/// its own window.
///
-/// An [`Application`](trait.Application.html) can execute asynchronous actions
-/// by returning a [`Command`](struct.Command.html) in some of its methods.
+/// An [`Application`] can execute asynchronous actions by returning a
+/// [`Command`] in some of its methods.
///
/// When using an [`Application`] with the `debug` feature enabled, a debug view
/// can be toggled by pressing `F12`.
-pub trait Application: Sized {
- /// The graphics backend to use to draw the [`Application`].
- ///
- /// [`Application`]: trait.Application.html
- 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.
- ///
- /// [`Application`]: trait.Application.html
- type Message: std::fmt::Debug + Send;
-
+pub trait Application: Program {
/// The data needed to initialize your [`Application`].
- ///
- /// [`Application`]: trait.Application.html
type Flags;
/// Initializes the [`Application`] with the flags provided to
@@ -41,36 +38,17 @@ pub trait Application: Sized {
///
/// Here is where you should return the initial state of your app.
///
- /// Additionally, you can return a [`Command`](struct.Command.html) if you
- /// need to perform some async action in the background on startup. This is
- /// useful if you want to load state from a file, perform an initial HTTP
- /// request, etc.
- ///
- /// [`Application`]: trait.Application.html
- /// [`run`]: #method.run.html
- /// [`Settings`]: struct.Settings.html
+ /// Additionally, you can return a [`Command`] if you need to perform some
+ /// async action in the background on startup. This is useful if you want to
+ /// load state from a file, perform an initial HTTP request, etc.
fn new(flags: Self::Flags) -> (Self, Command<Self::Message>);
/// Returns the current title of the [`Application`].
///
/// This title can be dynamic! The runtime will automatically update the
/// title of your application when necessary.
- ///
- /// [`Application`]: trait.Application.html
fn title(&self) -> String;
- /// Handles a __message__ and updates the state of the [`Application`].
- ///
- /// This is where you define your __update logic__. All the __messages__,
- /// produced by either user interactions or commands, will be handled by
- /// this method.
- ///
- /// Any [`Command`] returned will be executed immediately in the background.
- ///
- /// [`Application`]: trait.Application.html
- /// [`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.
///
@@ -78,16 +56,11 @@ pub trait Application: Sized {
/// [`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::Backend as window::Backend>::Renderer>;
+ /// By default, it returns an empty subscription.
+ fn subscription(&self) -> Subscription<Self::Message> {
+ Subscription::none()
+ }
/// Returns the current [`Application`] mode.
///
@@ -95,244 +68,255 @@ pub trait Application: Sized {
/// is returned.
///
/// By default, an application will run in windowed mode.
- ///
- /// [`Application`]: trait.Application.html
fn mode(&self) -> Mode {
Mode::Windowed
}
- /// Runs the [`Application`] with the provided [`Settings`].
+ /// Returns the background [`Color`] of the [`Application`].
///
- /// On native platforms, this method will take control of the current thread
- /// and __will NOT return__.
+ /// By default, it returns [`Color::WHITE`].
+ fn background_color(&self) -> Color {
+ Color::WHITE
+ }
+
+ /// Returns the scale factor of the [`Application`].
///
- /// It should probably be that last thing you call in your `main` function.
+ /// It can be used to dynamically control the size of the UI at runtime
+ /// (i.e. zooming).
///
- /// [`Application`]: trait.Application.html
- /// [`Settings`]: struct.Settings.html
- fn run(
- settings: Settings<Self::Flags>,
- backend_settings: <Self::Backend as window::Backend>::Settings,
- ) where
- Self: 'static,
- {
- use window::Backend as _;
- use winit::{
- event::{self, WindowEvent},
- event_loop::{ControlFlow, EventLoop},
- window::WindowBuilder,
- };
-
- let mut debug = Debug::new();
-
- debug.startup_started();
- let event_loop = EventLoop::with_user_event();
- let mut external_messages = Vec::new();
-
- let mut runtime = {
- let executor = Self::Executor::new().expect("Create executor");
-
- Runtime::new(executor, Proxy::new(event_loop.create_proxy()))
- };
+ /// For instance, a scale factor of `2.0` will make widgets twice as big,
+ /// while a scale factor of `0.5` will shrink them to half their size.
+ ///
+ /// By default, it returns `1.0`.
+ fn scale_factor(&self) -> f64 {
+ 1.0
+ }
+}
- let flags = settings.flags;
- let (mut application, init_command) =
- runtime.enter(|| Self::new(flags));
- runtime.spawn(init_command);
+/// Runs an [`Application`] with an executor, compositor, and the provided
+/// settings.
+pub fn run<A, E, C>(
+ settings: Settings<A::Flags>,
+ compositor_settings: C::Settings,
+) -> Result<(), Error>
+where
+ A: Application + 'static,
+ E: Executor + 'static,
+ C: window::Compositor<Renderer = A::Renderer> + 'static,
+{
+ use futures::task;
+ use futures::Future;
+ use winit::event_loop::EventLoop;
- let subscription = application.subscription();
- runtime.track(subscription);
+ let mut debug = Debug::new();
+ debug.startup_started();
- let mut title = application.title();
- let mut mode = application.mode();
+ let (compositor, renderer) = C::new(compositor_settings)?;
- let window = {
- let mut window_builder = WindowBuilder::new();
+ let event_loop = EventLoop::with_user_event();
- let (width, height) = settings.window.size;
+ let mut runtime = {
+ let proxy = Proxy::new(event_loop.create_proxy());
+ let executor = E::new().map_err(Error::ExecutorCreationFailed)?;
- 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,
- ));
+ Runtime::new(executor, proxy)
+ };
- #[cfg(target_os = "windows")]
- {
- use winit::platform::windows::WindowBuilderExtWindows;
+ let (application, init_command) = {
+ let flags = settings.flags;
- if let Some(parent) = settings.window.platform_specific.parent {
- window_builder = window_builder.with_parent_window(parent);
- }
- }
+ runtime.enter(|| A::new(flags))
+ };
- window_builder.build(&event_loop).expect("Open window")
- };
+ let subscription = application.subscription();
- let mut size = Size::new(window.inner_size(), window.scale_factor());
- let mut resized = false;
+ runtime.spawn(init_command);
+ runtime.track(subscription);
- let clipboard = Clipboard::new(&window);
- let (mut backend, mut renderer) = Self::Backend::new(backend_settings);
+ let window = settings
+ .window
+ .into_builder(
+ &application.title(),
+ application.mode(),
+ event_loop.primary_monitor(),
+ )
+ .build(&event_loop)
+ .map_err(Error::WindowCreationFailed)?;
- let surface = backend.create_surface(&window);
+ let (mut sender, receiver) = mpsc::unbounded();
- let mut swap_chain = {
- let physical_size = size.physical();
+ let mut instance = Box::pin(run_instance::<A, E, C>(
+ application,
+ compositor,
+ renderer,
+ window,
+ runtime,
+ debug,
+ receiver,
+ ));
+
+ let mut context = task::Context::from_waker(task::noop_waker_ref());
- backend.create_swap_chain(
- &surface,
- physical_size.width,
- physical_size.height,
- )
- };
+ event_loop.run(move |event, _, control_flow| {
+ use winit::event_loop::ControlFlow;
- let user_interface = build_user_interface(
- &mut application,
- Cache::default(),
- &mut renderer,
- size.logical(),
- &mut debug,
- );
+ if let ControlFlow::Exit = control_flow {
+ return;
+ }
- debug.draw_started();
- let mut primitive = user_interface.draw(&mut renderer);
- debug.draw_finished();
+ if let Some(event) = event.to_static() {
+ sender.start_send(event).expect("Send event");
- let mut cache = Some(user_interface.into_cache());
- let mut events = Vec::new();
- let mut mouse_interaction = mouse::Interaction::default();
- let mut modifiers = winit::event::ModifiersState::default();
- debug.startup_finished();
+ let poll = instance.as_mut().poll(&mut context);
- window.request_redraw();
+ *control_flow = match poll {
+ task::Poll::Pending => ControlFlow::Wait,
+ task::Poll::Ready(_) => ControlFlow::Exit,
+ };
+ }
+ });
+}
- event_loop.run(move |event, _, control_flow| match event {
+async fn run_instance<A, E, C>(
+ mut application: A,
+ mut compositor: C,
+ mut renderer: A::Renderer,
+ window: winit::window::Window,
+ mut runtime: Runtime<E, Proxy<A::Message>, A::Message>,
+ mut debug: Debug,
+ mut receiver: mpsc::UnboundedReceiver<winit::event::Event<'_, A::Message>>,
+) where
+ A: Application + 'static,
+ E: Executor + 'static,
+ C: window::Compositor<Renderer = A::Renderer> + 'static,
+{
+ use iced_futures::futures::stream::StreamExt;
+ use winit::event;
+
+ let surface = compositor.create_surface(&window);
+ let clipboard = Clipboard::new(&window);
+
+ let mut state = State::new(&application, &window);
+ let mut viewport_version = state.viewport_version();
+ let mut swap_chain = {
+ let physical_size = state.physical_size();
+
+ compositor.create_swap_chain(
+ &surface,
+ physical_size.width,
+ physical_size.height,
+ )
+ };
+
+ let mut user_interface = ManuallyDrop::new(build_user_interface(
+ &mut application,
+ Cache::default(),
+ &mut renderer,
+ state.logical_size(),
+ &mut debug,
+ ));
+
+ let mut primitive =
+ user_interface.draw(&mut renderer, state.cursor_position());
+ let mut mouse_interaction = mouse::Interaction::default();
+
+ let mut events = Vec::new();
+ let mut messages = Vec::new();
+
+ debug.startup_finished();
+
+ while let Some(event) = receiver.next().await {
+ match event {
event::Event::MainEventsCleared => {
- if events.is_empty() && external_messages.is_empty() {
- return;
+ if events.is_empty() && messages.is_empty() {
+ continue;
}
- // 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.
- let mut user_interface = build_user_interface(
- &mut application,
- cache.take().unwrap(),
+ debug.event_processing_started();
+
+ let statuses = user_interface.update(
+ &events,
+ state.cursor_position(),
+ clipboard.as_ref().map(|c| c as _),
&mut renderer,
- size.logical(),
- &mut debug,
+ &mut messages,
);
- debug.event_processing_started();
- 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();
- if messages.is_empty() {
- debug.draw_started();
- primitive = user_interface.draw(&mut renderer);
- debug.draw_finished();
-
- cache = Some(user_interface.into_cache());
- } else {
- // When there are messages, we are forced to rebuild twice
- // for now :^)
- let temp_cache = user_interface.into_cache();
-
- for message in messages {
- log::debug!("Updating");
-
- debug.log_message(&message);
-
- debug.update_started();
- 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();
-
- if title != new_title {
- window.set_title(&new_title);
-
- title = new_title;
- }
-
- // Update window mode
- let new_mode = application.mode();
-
- if mode != new_mode {
- window.set_fullscreen(conversion::fullscreen(
- window.current_monitor(),
- new_mode,
- ));
+ for event in events.drain(..).zip(statuses.into_iter()) {
+ runtime.broadcast(event);
+ }
- mode = new_mode;
- }
+ if !messages.is_empty() {
+ let cache =
+ ManuallyDrop::into_inner(user_interface).into_cache();
- let user_interface = build_user_interface(
+ // Update application
+ update(
&mut application,
- temp_cache,
- &mut renderer,
- size.logical(),
+ &mut runtime,
&mut debug,
+ &mut messages,
);
- debug.draw_started();
- primitive = user_interface.draw(&mut renderer);
- debug.draw_finished();
+ // Update window
+ state.synchronize(&application, &window);
- cache = Some(user_interface.into_cache());
+ user_interface = ManuallyDrop::new(build_user_interface(
+ &mut application,
+ cache,
+ &mut renderer,
+ state.logical_size(),
+ &mut debug,
+ ));
}
+ debug.draw_started();
+ primitive =
+ user_interface.draw(&mut renderer, state.cursor_position());
+ debug.draw_finished();
+
window.request_redraw();
}
event::Event::UserEvent(message) => {
- external_messages.push(message);
+ messages.push(message);
}
event::Event::RedrawRequested(_) => {
debug.render_started();
+ let current_viewport_version = state.viewport_version();
- if resized {
- let physical_size = size.physical();
+ if viewport_version != current_viewport_version {
+ let physical_size = state.physical_size();
+ let logical_size = state.logical_size();
- swap_chain = backend.create_swap_chain(
+ debug.layout_started();
+ user_interface = ManuallyDrop::new(
+ ManuallyDrop::into_inner(user_interface)
+ .relayout(logical_size, &mut renderer),
+ );
+ debug.layout_finished();
+
+ debug.draw_started();
+ primitive = user_interface
+ .draw(&mut renderer, state.cursor_position());
+ debug.draw_finished();
+
+ swap_chain = compositor.create_swap_chain(
&surface,
physical_size.width,
physical_size.height,
);
- resized = false;
+ viewport_version = current_viewport_version;
}
- let new_mouse_interaction = backend.draw(
+ let new_mouse_interaction = compositor.draw(
&mut renderer,
&mut swap_chain,
+ state.viewport(),
+ state.background_color(),
&primitive,
- size.scale_factor(),
&debug.overlay(),
);
@@ -353,81 +337,90 @@ pub trait Application: Sized {
event: window_event,
..
} => {
- match window_event {
- WindowEvent::Resized(new_size) => {
- size = Size::new(new_size, window.scale_factor());
- resized = true;
- }
- WindowEvent::CloseRequested => {
- *control_flow = ControlFlow::Exit;
- }
- WindowEvent::ModifiersChanged(new_modifiers) => {
- modifiers = new_modifiers;
- }
- #[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;
- }
- #[cfg(feature = "debug")]
- WindowEvent::KeyboardInput {
- input:
- winit::event::KeyboardInput {
- virtual_keycode:
- Some(winit::event::VirtualKeyCode::F12),
- state: winit::event::ElementState::Pressed,
- ..
- },
- ..
- } => debug.toggle(),
- _ => {}
+ if requests_exit(&window_event, state.modifiers()) {
+ break;
}
+ state.update(&window, &window_event, &mut debug);
+
if let Some(event) = conversion::window_event(
&window_event,
- size.scale_factor(),
- modifiers,
+ state.scale_factor(),
+ state.modifiers(),
) {
events.push(event);
}
}
- _ => {
- *control_flow = ControlFlow::Wait;
- }
- })
+ _ => {}
+ }
}
+
+ // Manually drop the user interface
+ drop(ManuallyDrop::into_inner(user_interface));
}
-fn build_user_interface<'a, A: Application>(
+/// Returns true if the provided event should cause an [`Application`] to
+/// exit.
+pub fn requests_exit(
+ event: &winit::event::WindowEvent<'_>,
+ _modifiers: winit::event::ModifiersState,
+) -> bool {
+ use winit::event::WindowEvent;
+
+ match event {
+ WindowEvent::CloseRequested => true,
+ #[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() => true,
+ _ => false,
+ }
+}
+
+/// Builds a [`UserInterface`] for the provided [`Application`], logging
+/// [`struct@Debug`] information accordingly.
+pub 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>,
+ renderer: &mut A::Renderer,
+ size: Size,
debug: &mut Debug,
-) -> UserInterface<'a, A::Message, <A::Backend as window::Backend>::Renderer> {
+) -> UserInterface<'a, A::Message, A::Renderer> {
debug.view_started();
let view = application.view();
debug.view_finished();
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,
- );
+ let user_interface = UserInterface::build(view, size, cache, renderer);
debug.layout_finished();
user_interface
}
+
+/// Updates an [`Application`] by feeding it the provided messages, spawning any
+/// resulting [`Command`], and tracking its [`Subscription`].
+pub fn update<A: Application, E: Executor>(
+ application: &mut A,
+ runtime: &mut Runtime<E, Proxy<A::Message>, A::Message>,
+ debug: &mut Debug,
+ messages: &mut Vec<A::Message>,
+) {
+ for message in messages.drain(..) {
+ debug.log_message(&message);
+
+ debug.update_started();
+ let command = runtime.enter(|| application.update(message));
+ debug.update_finished();
+
+ runtime.spawn(command);
+ }
+
+ let subscription = application.subscription();
+ runtime.track(subscription);
+}