From ae5e2c6c734894d71b2034a498a858b7997c5d3d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Thu, 21 May 2020 04:27:31 +0200 Subject: [PATCH] Introduce `Program` and `State` --- glutin/src/application.rs | 589 ++++++++------------------- glutin/src/lib.rs | 2 +- graphics/src/window/compositor.rs | 9 +- native/Cargo.toml | 3 + {winit => native}/src/debug/basic.rs | 0 {winit => native}/src/debug/null.rs | 0 native/src/lib.rs | 14 +- native/src/program.rs | 39 ++ native/src/program/state.rs | 154 +++++++ src/application.rs | 37 +- wgpu/src/window/compositor.rs | 22 +- winit/Cargo.toml | 2 +- winit/src/application.rs | 535 ++++++++++-------------- winit/src/lib.rs | 14 +- 14 files changed, 643 insertions(+), 777 deletions(-) rename {winit => native}/src/debug/basic.rs (100%) rename {winit => native}/src/debug/null.rs (100%) create mode 100644 native/src/program.rs create mode 100644 native/src/program/state.rs diff --git a/glutin/src/application.rs b/glutin/src/application.rs index 919897a8..3bb7478a 100644 --- a/glutin/src/application.rs +++ b/glutin/src/application.rs @@ -1,429 +1,202 @@ -use crate::{ - mouse, Cache, Command, Element, Executor, Runtime, Size, Subscription, - UserInterface, -}; +use crate::{mouse, Executor, Runtime, Size}; use iced_graphics::window; use iced_graphics::Viewport; +use iced_winit::application; use iced_winit::conversion; -use iced_winit::{Clipboard, Debug, Mode, Proxy, Settings}; +use iced_winit::{program, Clipboard, Debug, Proxy, Settings}; -/// 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. -/// -/// 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 Compositor: window::GLCompositor; +pub use iced_winit::Application; - /// The [`Executor`] that will run commands and subscriptions. - /// - /// [`Executor`]: trait.Executor.html - type Executor: Executor; +pub fn run( + settings: Settings, + compositor_settings: C::Settings, +) where + A: Application + 'static, + E: Executor + 'static, + C: window::GLCompositor + 'static, +{ + use glutin::{ + event, + event_loop::{ControlFlow, EventLoop}, + ContextBuilder, + }; - /// The type of __messages__ your [`Application`] will produce. - /// - /// [`Application`]: trait.Application.html - type Message: std::fmt::Debug + Send; + let mut debug = Debug::new(); + debug.startup_started(); - /// The data needed to initialize your [`Application`]. - /// - /// [`Application`]: trait.Application.html - type Flags; + let event_loop = EventLoop::with_user_event(); + let mut runtime = { + let executor = E::new().expect("Create executor"); + let proxy = Proxy::new(event_loop.create_proxy()); - /// Initializes the [`Application`] with the flags provided to - /// [`run`] as part of the [`Settings`]. - /// - /// 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 - fn new(flags: Self::Flags) -> (Self, Command); + Runtime::new(executor, proxy) + }; - /// 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; + let flags = settings.flags; + let (application, init_command) = runtime.enter(|| A::new(flags)); + runtime.spawn(init_command); - /// 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; + let subscription = application.subscription(); + runtime.track(subscription); - /// 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! - /// - /// By default, it returns an empty subscription. - fn subscription(&self) -> Subscription { - Subscription::none() - } + let mut title = application.title(); + let mut mode = application.mode(); - /// 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, - ::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`] with the provided [`Settings`]. - /// - /// On native platforms, this method will take control of the current thread - /// and __will NOT return__. - /// - /// It should probably be that last thing you call in your `main` function. - /// - /// [`Application`]: trait.Application.html - /// [`Settings`]: struct.Settings.html - fn run( - settings: Settings, - backend_settings: ::Settings, - ) where - Self: 'static, - { - use glutin::{ - event::{self, WindowEvent}, - event_loop::{ControlFlow, EventLoop}, - ContextBuilder, - }; - use iced_graphics::window::GLCompositor as _; - - 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())) - }; - - let flags = settings.flags; - let (mut application, init_command) = - runtime.enter(|| Self::new(flags)); - runtime.spawn(init_command); - - let subscription = application.subscription(); - runtime.track(subscription); - - let mut title = application.title(); - let mut mode = application.mode(); - - let context = { - let window_builder = settings.window.into_builder( - &title, - mode, - event_loop.primary_monitor(), - ); - - let context = ContextBuilder::new() - .with_vsync(true) - .build_windowed(window_builder, &event_loop) - .expect("Open window"); - - #[allow(unsafe_code)] - unsafe { - context.make_current().expect("Make OpenGL context current") - } - }; - - let physical_size = context.window().inner_size(); - let mut viewport = Viewport::with_physical_size( - Size::new(physical_size.width, physical_size.height), - context.window().scale_factor(), + let context = { + let builder = settings.window.into_builder( + &title, + mode, + event_loop.primary_monitor(), ); - let mut resized = false; - let clipboard = Clipboard::new(&context.window()); + let context = ContextBuilder::new() + .with_vsync(true) + .build_windowed(builder, &event_loop) + .expect("Open window"); #[allow(unsafe_code)] - let (mut compositor, mut renderer) = unsafe { - Self::Compositor::new(backend_settings, |address| { - context.get_proc_address(address) - }) - }; + unsafe { + context.make_current().expect("Make OpenGL context current") + } + }; - let user_interface = build_user_interface( - &mut application, - Cache::default(), - &mut renderer, - viewport.logical_size(), - &mut debug, - ); + let clipboard = Clipboard::new(&context.window()); + let mut mouse_interaction = mouse::Interaction::default(); + let mut modifiers = glutin::event::ModifiersState::default(); - debug.draw_started(); - let mut primitive = user_interface.draw(&mut renderer); - debug.draw_finished(); + let physical_size = context.window().inner_size(); + let mut viewport = Viewport::with_physical_size( + Size::new(physical_size.width, physical_size.height), + context.window().scale_factor(), + ); + let mut resized = false; - let mut cache = Some(user_interface.into_cache()); - let mut events = Vec::new(); - let mut mouse_interaction = mouse::Interaction::default(); - let mut modifiers = glutin::event::ModifiersState::default(); - debug.startup_finished(); - - context.window().request_redraw(); - - event_loop.run(move |event, _, control_flow| match event { - event::Event::MainEventsCleared => { - if events.is_empty() && external_messages.is_empty() { - return; - } - - let mut user_interface = build_user_interface( - &mut application, - cache.take().unwrap(), - &mut renderer, - viewport.logical_size(), - &mut debug, - ); - - 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 { - 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 { - context.window().set_title(&new_title); - - title = new_title; - } - - // Update window mode - let new_mode = application.mode(); - - if mode != new_mode { - context.window().set_fullscreen( - conversion::fullscreen( - context.window().current_monitor(), - new_mode, - ), - ); - - mode = new_mode; - } - - let user_interface = build_user_interface( - &mut application, - temp_cache, - &mut renderer, - viewport.logical_size(), - &mut debug, - ); - - debug.draw_started(); - primitive = user_interface.draw(&mut renderer); - debug.draw_finished(); - - cache = Some(user_interface.into_cache()); - } - - context.window().request_redraw(); - } - event::Event::UserEvent(message) => { - external_messages.push(message); - } - event::Event::RedrawRequested(_) => { - debug.render_started(); - - if resized { - let physical_size = viewport.physical_size(); - - context.resize(glutin::dpi::PhysicalSize { - width: physical_size.width, - height: physical_size.height, - }); - compositor.resize_viewport(physical_size); - - resized = false; - } - - let new_mouse_interaction = compositor.draw( - &mut renderer, - &viewport, - &primitive, - &debug.overlay(), - ); - - context.swap_buffers().expect("Swap buffers"); - debug.render_finished(); - - if new_mouse_interaction != mouse_interaction { - context.window().set_cursor_icon( - conversion::mouse_interaction(new_mouse_interaction), - ); - - mouse_interaction = new_mouse_interaction; - } - - // TODO: Handle animations! - // Maybe we can use `ControlFlow::WaitUntil` for this. - } - event::Event::WindowEvent { - event: window_event, - .. - } => { - match window_event { - WindowEvent::Resized(new_size) => { - let size = Size::new(new_size.width, new_size.height); - - viewport = Viewport::with_physical_size( - size, - context.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: - glutin::event::KeyboardInput { - virtual_keycode: - Some(glutin::event::VirtualKeyCode::Q), - state: glutin::event::ElementState::Pressed, - .. - }, - .. - } if modifiers.logo() => { - *control_flow = ControlFlow::Exit; - } - #[cfg(feature = "debug")] - WindowEvent::KeyboardInput { - input: - glutin::event::KeyboardInput { - virtual_keycode: - Some(glutin::event::VirtualKeyCode::F12), - state: glutin::event::ElementState::Pressed, - .. - }, - .. - } => debug.toggle(), - _ => {} - } - - if let Some(event) = conversion::window_event( - &window_event, - viewport.scale_factor(), - modifiers, - ) { - events.push(event); - } - } - _ => { - *control_flow = ControlFlow::Wait; - } + #[allow(unsafe_code)] + let (mut compositor, mut renderer) = unsafe { + C::new(compositor_settings, |address| { + context.get_proc_address(address) }) - } -} - -fn build_user_interface<'a, A: Application>( - application: &'a mut A, - cache: Cache, - renderer: &mut ::Renderer, - size: Size, - debug: &mut Debug, -) -> UserInterface< - 'a, - A::Message, - ::Renderer, -> { - debug.view_started(); - let view = application.view(); - debug.view_finished(); - - debug.layout_started(); - let user_interface = UserInterface::build(view, size, cache, renderer); - debug.layout_finished(); - - user_interface + }; + + let mut state = program::State::new( + application, + viewport.logical_size(), + &mut renderer, + &mut debug, + ); + debug.startup_finished(); + + event_loop.run(move |event, _, control_flow| match event { + event::Event::MainEventsCleared => { + let command = runtime.enter(|| { + state.update( + clipboard.as_ref().map(|c| c as _), + viewport.logical_size(), + &mut renderer, + &mut debug, + ) + }); + + // If the application was updated + if let Some(command) = command { + runtime.spawn(command); + + let program = state.program(); + + // Update subscriptions + let subscription = program.subscription(); + runtime.track(subscription); + + // Update window title + let new_title = program.title(); + + if title != new_title { + context.window().set_title(&new_title); + + title = new_title; + } + + // Update window mode + let new_mode = program.mode(); + + if mode != new_mode { + context.window().set_fullscreen(conversion::fullscreen( + context.window().current_monitor(), + new_mode, + )); + + mode = new_mode; + } + } + + context.window().request_redraw(); + } + event::Event::UserEvent(message) => { + state.queue_message(message); + } + event::Event::RedrawRequested(_) => { + debug.render_started(); + + if resized { + let physical_size = viewport.physical_size(); + + context.resize(glutin::dpi::PhysicalSize::new( + physical_size.width, + physical_size.height, + )); + + compositor.resize_viewport(physical_size); + + resized = false; + } + + let new_mouse_interaction = compositor.draw( + &mut renderer, + &viewport, + state.primitive(), + &debug.overlay(), + ); + + context.swap_buffers().expect("Swap buffers"); + + debug.render_finished(); + + if new_mouse_interaction != mouse_interaction { + context.window().set_cursor_icon( + conversion::mouse_interaction(new_mouse_interaction), + ); + + mouse_interaction = new_mouse_interaction; + } + + // TODO: Handle animations! + // Maybe we can use `ControlFlow::WaitUntil` for this. + } + event::Event::WindowEvent { + event: window_event, + .. + } => { + application::handle_window_event( + &window_event, + context.window(), + control_flow, + &mut modifiers, + &mut viewport, + &mut resized, + &mut debug, + ); + + if let Some(event) = conversion::window_event( + &window_event, + viewport.scale_factor(), + modifiers, + ) { + state.queue_event(event.clone()); + runtime.broadcast(event); + } + } + _ => { + *control_flow = ControlFlow::Wait; + } + }) } diff --git a/glutin/src/lib.rs b/glutin/src/lib.rs index 2e2d03fc..829fe02a 100644 --- a/glutin/src/lib.rs +++ b/glutin/src/lib.rs @@ -7,7 +7,7 @@ #[doc(no_inline)] pub use iced_native::*; -mod application; +pub mod application; pub use application::Application; diff --git a/graphics/src/window/compositor.rs b/graphics/src/window/compositor.rs index 82faa6e1..d5920c95 100644 --- a/graphics/src/window/compositor.rs +++ b/graphics/src/window/compositor.rs @@ -5,7 +5,7 @@ use raw_window_handle::HasRawWindowHandle; /// A graphics compositor that can draw to windows. pub trait Compositor: Sized { /// The settings of the backend. - type Settings: Default + Clone; + type Settings: Default; /// The iced renderer of the backend. type Renderer: iced_native::Renderer; @@ -19,7 +19,7 @@ pub trait Compositor: Sized { /// Creates a new [`Backend`]. /// /// [`Backend`]: trait.Backend.html - fn new(settings: Self::Settings) -> Self; + fn new(settings: Self::Settings) -> (Self, Self::Renderer); /// Crates a new [`Surface`] for the given window. /// @@ -29,11 +29,6 @@ pub trait Compositor: Sized { window: &W, ) -> Self::Surface; - /// Crates a new [`Renderer`]. - /// - /// [`Renderer`]: #associatedtype.Renderer - fn create_renderer(&mut self, settings: Self::Settings) -> Self::Renderer; - /// Crates a new [`SwapChain`] for the given [`Surface`]. /// /// [`SwapChain`]: #associatedtype.SwapChain diff --git a/native/Cargo.toml b/native/Cargo.toml index 067b8708..75b4a56b 100644 --- a/native/Cargo.toml +++ b/native/Cargo.toml @@ -7,6 +7,9 @@ description = "A renderer-agnostic library for native GUIs" license = "MIT" repository = "https://github.com/hecrj/iced" +[features] +debug = [] + [dependencies] twox-hash = "1.5" unicode-segmentation = "1.6" diff --git a/winit/src/debug/basic.rs b/native/src/debug/basic.rs similarity index 100% rename from winit/src/debug/basic.rs rename to native/src/debug/basic.rs diff --git a/winit/src/debug/null.rs b/native/src/debug/null.rs similarity index 100% rename from winit/src/debug/null.rs rename to native/src/debug/null.rs diff --git a/native/src/lib.rs b/native/src/lib.rs index 9882803f..bea7d17e 100644 --- a/native/src/lib.rs +++ b/native/src/lib.rs @@ -34,7 +34,7 @@ //! [`window::Backend`]: window/trait.Backend.html //! [`UserInterface`]: struct.UserInterface.html //! [renderer]: renderer/index.html -#![deny(missing_docs)] +//#![deny(missing_docs)] #![deny(missing_debug_implementations)] #![deny(unused_results)] #![forbid(unsafe_code)] @@ -42,6 +42,7 @@ pub mod keyboard; pub mod layout; pub mod mouse; +pub mod program; pub mod renderer; pub mod subscription; pub mod widget; @@ -54,6 +55,15 @@ mod hasher; mod runtime; mod user_interface; +// We disable debug capabilities on release builds unless the `debug` feature +// is explicitly enabled. +#[cfg(feature = "debug")] +#[path = "debug/basic.rs"] +mod debug; +#[cfg(not(feature = "debug"))] +#[path = "debug/null.rs"] +mod debug; + pub use iced_core::{ Align, Background, Color, Font, HorizontalAlignment, Length, Point, Rectangle, Size, Vector, VerticalAlignment, @@ -64,10 +74,12 @@ pub use iced_futures::{executor, futures, Command}; pub use executor::Executor; pub use clipboard::Clipboard; +pub use debug::Debug; pub use element::Element; pub use event::Event; pub use hasher::Hasher; pub use layout::Layout; +pub use program::Program; pub use renderer::Renderer; pub use runtime::Runtime; pub use subscription::Subscription; diff --git a/native/src/program.rs b/native/src/program.rs new file mode 100644 index 00000000..2652dee9 --- /dev/null +++ b/native/src/program.rs @@ -0,0 +1,39 @@ +//! Build interactive programs using The Elm Architecture. +use crate::{Command, Element, Renderer}; + +mod state; + +pub use state::State; + +/// An interactive, native cross-platform program. +pub trait Program: Sized { + /// The graphics backend to use to draw the [`Program`]. + /// + /// [`Program`]: trait.Program.html + type Renderer: Renderer; + + /// The type of __messages__ your [`Program`] will produce. + /// + /// [`Application`]: trait.Program.html + 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. + /// + /// [`Program`]: trait.Application.html + /// [`Command`]: struct.Command.html + fn update(&mut self, message: Self::Message) -> Command; + + /// Returns the widgets to display in the [`Program`]. + /// + /// These widgets can produce __messages__ based on user interaction. + /// + /// [`Program`]: trait.Application.html + fn view(&mut self) -> Element<'_, Self::Message, Self::Renderer>; +} diff --git a/native/src/program/state.rs b/native/src/program/state.rs new file mode 100644 index 00000000..bcb7212d --- /dev/null +++ b/native/src/program/state.rs @@ -0,0 +1,154 @@ +use crate::{ + Cache, Clipboard, Command, Debug, Event, Program, Renderer, Size, + UserInterface, +}; + +#[allow(missing_debug_implementations)] +pub struct State

+where + P: Program + 'static, +{ + program: P, + cache: Option, + primitive: ::Output, + queued_events: Vec, + queued_messages: Vec, +} + +impl

State

+where + P: Program + 'static, +{ + pub fn new( + mut program: P, + bounds: Size, + renderer: &mut P::Renderer, + debug: &mut Debug, + ) -> Self { + let user_interface = build_user_interface( + &mut program, + Cache::default(), + renderer, + bounds, + debug, + ); + + debug.draw_started(); + let primitive = user_interface.draw(renderer); + debug.draw_finished(); + + let cache = Some(user_interface.into_cache()); + + State { + program, + cache, + primitive, + queued_events: Vec::new(), + queued_messages: Vec::new(), + } + } + + pub fn program(&self) -> &P { + &self.program + } + + pub fn primitive(&self) -> &::Output { + &self.primitive + } + + pub fn queue_event(&mut self, event: Event) { + self.queued_events.push(event); + } + + pub fn queue_message(&mut self, message: P::Message) { + self.queued_messages.push(message); + } + + pub fn update( + &mut self, + clipboard: Option<&dyn Clipboard>, + bounds: Size, + renderer: &mut P::Renderer, + debug: &mut Debug, + ) -> Option> { + if self.queued_events.is_empty() && self.queued_messages.is_empty() { + return None; + } + + let mut user_interface = build_user_interface( + &mut self.program, + self.cache.take().unwrap(), + renderer, + bounds, + debug, + ); + + debug.event_processing_started(); + let mut messages = user_interface.update( + self.queued_events.drain(..), + clipboard, + renderer, + ); + messages.extend(self.queued_messages.drain(..)); + debug.event_processing_finished(); + + if messages.is_empty() { + debug.draw_started(); + self.primitive = user_interface.draw(renderer); + debug.draw_finished(); + + self.cache = Some(user_interface.into_cache()); + + None + } else { + // When there are messages, we are forced to rebuild twice + // for now :^) + let temp_cache = user_interface.into_cache(); + + let commands = + Command::batch(messages.into_iter().map(|message| { + debug.log_message(&message); + + debug.update_started(); + let command = self.program.update(message); + debug.update_finished(); + + command + })); + + let user_interface = build_user_interface( + &mut self.program, + temp_cache, + renderer, + bounds, + debug, + ); + + debug.draw_started(); + self.primitive = user_interface.draw(renderer); + debug.draw_finished(); + + self.cache = Some(user_interface.into_cache()); + + Some(commands) + } + } +} + +fn build_user_interface<'a, P: Program>( + program: &'a mut P, + cache: 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 +} diff --git a/src/application.rs b/src/application.rs index 644a4824..b6f2227e 100644 --- a/src/application.rs +++ b/src/application.rs @@ -198,10 +198,11 @@ pub trait Application: Sized { ..iced_glow::Settings::default() }; - as iced_glutin::Application>::run( - settings.into(), - glow_settings, - ); + iced_glutin::application::run::< + Instance, + Self::Executor, + iced_glow::window::Compositor, + >(settings.into(), glow_settings); } #[cfg(target_arch = "wasm32")] @@ -211,15 +212,29 @@ pub trait Application: Sized { struct Instance(A); +#[cfg(not(target_arch = "wasm32"))] +impl iced_glutin::Program for Instance +where + A: Application, +{ + type Renderer = iced_glow::Renderer; + type Message = A::Message; + + fn update(&mut self, message: Self::Message) -> Command { + self.0.update(message) + } + + fn view(&mut self) -> Element<'_, Self::Message> { + self.0.view() + } +} + #[cfg(not(target_arch = "wasm32"))] impl iced_glutin::Application for Instance where A: Application, { - type Compositor = iced_glow::window::Compositor; - type Executor = A::Executor; type Flags = A::Flags; - type Message = A::Message; fn new(flags: Self::Flags) -> (Self, Command) { let (app, command) = A::new(flags); @@ -238,17 +253,9 @@ where } } - fn update(&mut self, message: Self::Message) -> Command { - self.0.update(message) - } - fn subscription(&self) -> Subscription { self.0.subscription() } - - fn view(&mut self) -> Element<'_, Self::Message> { - self.0.view() - } } #[cfg(target_arch = "wasm32")] diff --git a/wgpu/src/window/compositor.rs b/wgpu/src/window/compositor.rs index 7eba8924..600bc81c 100644 --- a/wgpu/src/window/compositor.rs +++ b/wgpu/src/window/compositor.rs @@ -18,8 +18,8 @@ impl iced_graphics::window::Compositor for Compositor { type Surface = wgpu::Surface; type SwapChain = wgpu::SwapChain; - fn new(settings: Self::Settings) -> Self { - let (device, queue) = futures::executor::block_on(async { + fn new(settings: Self::Settings) -> (Self, Renderer) { + let (mut device, queue) = futures::executor::block_on(async { let adapter = wgpu::Adapter::request( &wgpu::RequestAdapterOptions { power_preference: if settings.antialiasing.is_none() { @@ -44,15 +44,17 @@ impl iced_graphics::window::Compositor for Compositor { .await }); - Self { - device, - queue, - format: settings.format, - } - } + let renderer = + Renderer::new(crate::Backend::new(&mut device, settings)); - fn create_renderer(&mut self, settings: Settings) -> Renderer { - Renderer::new(crate::Backend::new(&mut self.device, settings)) + ( + Self { + device, + queue, + format: settings.format, + }, + renderer, + ) } fn create_surface( diff --git a/winit/Cargo.toml b/winit/Cargo.toml index 1bf2d7b3..7fe83b96 100644 --- a/winit/Cargo.toml +++ b/winit/Cargo.toml @@ -11,7 +11,7 @@ keywords = ["gui", "ui", "graphics", "interface", "widgets"] categories = ["gui"] [features] -debug = [] +debug = ["iced_native/debug"] [dependencies] winit = "0.22" diff --git a/winit/src/application.rs b/winit/src/application.rs index ccab19f1..fcba47b3 100644 --- a/winit/src/application.rs +++ b/winit/src/application.rs @@ -1,9 +1,10 @@ use crate::{ - conversion, mouse, Cache, Clipboard, Command, Debug, Element, Executor, - Mode, Proxy, Runtime, Settings, Size, Subscription, UserInterface, + conversion, mouse, Clipboard, Command, Debug, Executor, Mode, Proxy, + Runtime, Settings, Size, Subscription, }; use iced_graphics::window; use iced_graphics::Viewport; +use iced_native::program::{self, Program}; /// An interactive, native cross-platform application. /// @@ -16,22 +17,7 @@ use iced_graphics::Viewport; /// /// 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 Compositor: window::Compositor; - - /// 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 @@ -60,18 +46,6 @@ pub trait Application: Sized { /// [`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; - /// Returns the event `Subscription` for the current state of the /// application. /// @@ -85,19 +59,6 @@ pub trait Application: Sized { Subscription::none() } - /// 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, - ::Renderer, - >; - /// Returns the current [`Application`] mode. /// /// The runtime will automatically transition your application if a new mode @@ -109,309 +70,237 @@ pub trait Application: Sized { fn mode(&self) -> Mode { Mode::Windowed } +} - /// Runs the [`Application`] with the provided [`Settings`]. - /// - /// On native platforms, this method will take control of the current thread - /// and __will NOT return__. - /// - /// It should probably be that last thing you call in your `main` function. - /// - /// [`Application`]: trait.Application.html - /// [`Settings`]: struct.Settings.html - fn run( - settings: Settings, - backend_settings: ::Settings, - ) where - Self: 'static, - { - use iced_graphics::window::Compositor as _; - use winit::{ - event::{self, WindowEvent}, - event_loop::{ControlFlow, EventLoop}, - }; +pub fn run( + settings: Settings, + compositor_settings: C::Settings, +) where + A: Application + 'static, + E: Executor + 'static, + C: window::Compositor + 'static, +{ + use winit::{ + event, + event_loop::{ControlFlow, EventLoop}, + }; - let mut debug = Debug::new(); + let mut debug = Debug::new(); + debug.startup_started(); - debug.startup_started(); - let event_loop = EventLoop::with_user_event(); - let mut external_messages = Vec::new(); + let event_loop = EventLoop::with_user_event(); + let mut runtime = { + let executor = E::new().expect("Create executor"); + let proxy = Proxy::new(event_loop.create_proxy()); - let mut runtime = { - let executor = Self::Executor::new().expect("Create executor"); + Runtime::new(executor, proxy) + }; - Runtime::new(executor, Proxy::new(event_loop.create_proxy())) - }; + let flags = settings.flags; + let (application, init_command) = runtime.enter(|| A::new(flags)); + runtime.spawn(init_command); - let flags = settings.flags; - let (mut application, init_command) = - runtime.enter(|| Self::new(flags)); - runtime.spawn(init_command); + let subscription = application.subscription(); + runtime.track(subscription); - let subscription = application.subscription(); - runtime.track(subscription); + let mut title = application.title(); + let mut mode = application.mode(); - let mut title = application.title(); - let mut mode = application.mode(); + let window = settings + .window + .into_builder(&title, mode, event_loop.primary_monitor()) + .build(&event_loop) + .expect("Open window"); - let window = settings - .window - .into_builder(&title, mode, event_loop.primary_monitor()) - .build(&event_loop) - .expect("Open window"); + let clipboard = Clipboard::new(&window); + let mut mouse_interaction = mouse::Interaction::default(); + let mut modifiers = winit::event::ModifiersState::default(); - let physical_size = window.inner_size(); - let mut viewport = Viewport::with_physical_size( - Size::new(physical_size.width, physical_size.height), - window.scale_factor(), - ); - let mut resized = false; + let physical_size = window.inner_size(); + let mut viewport = Viewport::with_physical_size( + Size::new(physical_size.width, physical_size.height), + window.scale_factor(), + ); + let mut resized = false; - let clipboard = Clipboard::new(&window); - let mut compositor = Self::Compositor::new(backend_settings.clone()); + let (mut compositor, mut renderer) = C::new(compositor_settings); - let surface = compositor.create_surface(&window); - let mut renderer = compositor.create_renderer(backend_settings); + let surface = compositor.create_surface(&window); - let mut swap_chain = compositor.create_swap_chain( - &surface, - physical_size.width, - physical_size.height, - ); + let mut swap_chain = compositor.create_swap_chain( + &surface, + physical_size.width, + physical_size.height, + ); - let user_interface = build_user_interface( - &mut application, - Cache::default(), - &mut renderer, - viewport.logical_size(), - &mut debug, - ); + let mut state = program::State::new( + application, + viewport.logical_size(), + &mut renderer, + &mut debug, + ); + debug.startup_finished(); - debug.draw_started(); - let mut primitive = user_interface.draw(&mut renderer); - debug.draw_finished(); - - 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(); - - window.request_redraw(); - - event_loop.run(move |event, _, control_flow| match event { - event::Event::MainEventsCleared => { - if events.is_empty() && external_messages.is_empty() { - return; - } - - let mut user_interface = build_user_interface( - &mut application, - cache.take().unwrap(), - &mut renderer, + event_loop.run(move |event, _, control_flow| match event { + event::Event::MainEventsCleared => { + let command = runtime.enter(|| { + state.update( + clipboard.as_ref().map(|c| c as _), viewport.logical_size(), - &mut debug, - ); - - 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, - )); - - mode = new_mode; - } - - let user_interface = build_user_interface( - &mut application, - temp_cache, - &mut renderer, - viewport.logical_size(), - &mut debug, - ); - - debug.draw_started(); - primitive = user_interface.draw(&mut renderer); - debug.draw_finished(); - - cache = Some(user_interface.into_cache()); - } - - window.request_redraw(); - } - event::Event::UserEvent(message) => { - external_messages.push(message); - } - event::Event::RedrawRequested(_) => { - debug.render_started(); - - if resized { - let physical_size = viewport.physical_size(); - - swap_chain = compositor.create_swap_chain( - &surface, - physical_size.width, - physical_size.height, - ); - - resized = false; - } - - let new_mouse_interaction = compositor.draw( &mut renderer, - &mut swap_chain, - &viewport, - &primitive, - &debug.overlay(), - ); + &mut debug, + ) + }); - debug.render_finished(); + // If the application was updated + if let Some(command) = command { + runtime.spawn(command); - if new_mouse_interaction != mouse_interaction { - window.set_cursor_icon(conversion::mouse_interaction( - new_mouse_interaction, + let program = state.program(); + + // Update subscriptions + let subscription = program.subscription(); + runtime.track(subscription); + + // Update window title + let new_title = program.title(); + + if title != new_title { + window.set_title(&new_title); + + title = new_title; + } + + // Update window mode + let new_mode = program.mode(); + + if mode != new_mode { + window.set_fullscreen(conversion::fullscreen( + window.current_monitor(), + new_mode, )); - mouse_interaction = new_mouse_interaction; - } - - // TODO: Handle animations! - // Maybe we can use `ControlFlow::WaitUntil` for this. - } - event::Event::WindowEvent { - event: window_event, - .. - } => { - match window_event { - WindowEvent::Resized(new_size) => { - let size = Size::new(new_size.width, new_size.height); - - viewport = Viewport::with_physical_size( - 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 let Some(event) = conversion::window_event( - &window_event, - viewport.scale_factor(), - modifiers, - ) { - events.push(event); + mode = new_mode; } } - _ => { - *control_flow = ControlFlow::Wait; + + window.request_redraw(); + } + event::Event::UserEvent(message) => { + state.queue_message(message); + } + event::Event::RedrawRequested(_) => { + debug.render_started(); + + if resized { + let physical_size = viewport.physical_size(); + + swap_chain = compositor.create_swap_chain( + &surface, + physical_size.width, + physical_size.height, + ); + + resized = false; } - }) + + let new_mouse_interaction = compositor.draw( + &mut renderer, + &mut swap_chain, + &viewport, + state.primitive(), + &debug.overlay(), + ); + + debug.render_finished(); + + if new_mouse_interaction != mouse_interaction { + window.set_cursor_icon(conversion::mouse_interaction( + new_mouse_interaction, + )); + + mouse_interaction = new_mouse_interaction; + } + + // TODO: Handle animations! + // Maybe we can use `ControlFlow::WaitUntil` for this. + } + event::Event::WindowEvent { + event: window_event, + .. + } => { + handle_window_event( + &window_event, + &window, + control_flow, + &mut modifiers, + &mut viewport, + &mut resized, + &mut debug, + ); + + if let Some(event) = conversion::window_event( + &window_event, + viewport.scale_factor(), + modifiers, + ) { + state.queue_event(event.clone()); + runtime.broadcast(event); + } + } + _ => { + *control_flow = ControlFlow::Wait; + } + }) +} + +pub fn handle_window_event( + event: &winit::event::WindowEvent<'_>, + window: &winit::window::Window, + control_flow: &mut winit::event_loop::ControlFlow, + modifiers: &mut winit::event::ModifiersState, + viewport: &mut Viewport, + resized: &mut bool, + _debug: &mut Debug, +) { + use winit::{event::WindowEvent, event_loop::ControlFlow}; + + match event { + WindowEvent::Resized(new_size) => { + let size = Size::new(new_size.width, new_size.height); + + *viewport = + Viewport::with_physical_size(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(), + _ => {} } } - -fn build_user_interface<'a, A: Application>( - application: &'a mut A, - cache: Cache, - renderer: &mut ::Renderer, - size: Size, - debug: &mut Debug, -) -> UserInterface< - 'a, - A::Message, - ::Renderer, -> { - debug.view_started(); - let view = application.view(); - debug.view_finished(); - - debug.layout_started(); - let user_interface = UserInterface::build(view, size, cache, renderer); - debug.layout_finished(); - - user_interface -} diff --git a/winit/src/lib.rs b/winit/src/lib.rs index 9cf5dc0d..d3af9ae9 100644 --- a/winit/src/lib.rs +++ b/winit/src/lib.rs @@ -25,26 +25,18 @@ pub use iced_native::*; pub use winit; +pub mod application; pub mod conversion; pub mod settings; -mod application; mod clipboard; mod mode; mod proxy; -// We disable debug capabilities on release builds unless the `debug` feature -// is explicitly enabled. -#[cfg(feature = "debug")] -#[path = "debug/basic.rs"] -mod debug; -#[cfg(not(feature = "debug"))] -#[path = "debug/null.rs"] -mod debug; - pub use application::Application; pub use clipboard::Clipboard; -pub use debug::Debug; pub use mode::Mode; pub use proxy::Proxy; pub use settings::Settings; + +pub use iced_graphics::Viewport;