diff options
Diffstat (limited to 'glutin')
-rw-r--r-- | glutin/Cargo.toml | 30 | ||||
-rw-r--r-- | glutin/README.md | 29 | ||||
-rw-r--r-- | glutin/src/application.rs | 297 | ||||
-rw-r--r-- | glutin/src/lib.rs | 25 |
4 files changed, 381 insertions, 0 deletions
diff --git a/glutin/Cargo.toml b/glutin/Cargo.toml new file mode 100644 index 00000000..505ee7e5 --- /dev/null +++ b/glutin/Cargo.toml @@ -0,0 +1,30 @@ +[package] +name = "iced_glutin" +version = "0.1.0" +authors = ["Héctor Ramón Jiménez <hector0193@gmail.com>"] +edition = "2018" +description = "A glutin runtime for Iced" +license = "MIT" +repository = "https://github.com/hecrj/iced" +documentation = "https://docs.rs/iced_glutin" +keywords = ["gui", "ui", "graphics", "interface", "widgets"] +categories = ["gui"] + +[features] +debug = ["iced_winit/debug"] + +[dependencies] +glutin = "0.26" + +[dependencies.iced_native] +version = "0.3" +path = "../native" + +[dependencies.iced_winit] +version = "0.2" +path = "../winit" + +[dependencies.iced_graphics] +version = "0.1" +path = "../graphics" +features = ["opengl"] diff --git a/glutin/README.md b/glutin/README.md new file mode 100644 index 00000000..addb9228 --- /dev/null +++ b/glutin/README.md @@ -0,0 +1,29 @@ +# `iced_glutin` +[][documentation] +[](https://crates.io/crates/iced_glutin) +[](https://github.com/hecrj/iced/blob/master/LICENSE) +[](https://iced.zulipchat.com) + +`iced_glutin` offers some convenient abstractions on top of [`iced_native`] to quickstart development when using [`glutin`]. + +It exposes a renderer-agnostic `Application` trait that can be implemented and then run with a simple call. The use of this trait is optional. A `conversion` module is provided for users that decide to implement a custom event loop. + +<p align="center"> + <img alt="The native target" src="../docs/graphs/native.png" width="80%"> +</p> + +[documentation]: https://docs.rs/iced_glutin +[`iced_native`]: ../native +[`glutin`]: https://github.com/rust-windowing/glutin + +## Installation +Add `iced_glutin` as a dependency in your `Cargo.toml`: + +```toml +iced_glutin = "0.1" +``` + +__Iced moves fast and the `master` branch can contain breaking changes!__ If +you want to learn about a specific release, check out [the release list]. + +[the release list]: https://github.com/hecrj/iced/releases diff --git a/glutin/src/application.rs b/glutin/src/application.rs new file mode 100644 index 00000000..42513feb --- /dev/null +++ b/glutin/src/application.rs @@ -0,0 +1,297 @@ +//! Create interactive, native cross-platform applications. +use crate::{mouse, Error, Executor, Runtime}; + +pub use iced_winit::Application; + +use iced_graphics::window; +use iced_winit::application; +use iced_winit::conversion; +use iced_winit::futures; +use iced_winit::futures::channel::mpsc; +use iced_winit::{Cache, Clipboard, Debug, Proxy, Settings}; + +use glutin::window::Window; +use std::mem::ManuallyDrop; + +/// 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::GLCompositor<Renderer = A::Renderer> + 'static, +{ + use futures::task; + use futures::Future; + use glutin::event_loop::EventLoop; + use glutin::ContextBuilder; + + let mut debug = Debug::new(); + debug.startup_started(); + + let event_loop = EventLoop::with_user_event(); + let mut runtime = { + let executor = E::new().map_err(Error::ExecutorCreationFailed)?; + let proxy = Proxy::new(event_loop.create_proxy()); + + Runtime::new(executor, proxy) + }; + + let (application, init_command) = { + let flags = settings.flags; + + runtime.enter(|| A::new(flags)) + }; + + let subscription = application.subscription(); + + runtime.spawn(init_command); + runtime.track(subscription); + + let context = { + let builder = settings.window.into_builder( + &application.title(), + application.mode(), + event_loop.primary_monitor(), + ); + + let context = ContextBuilder::new() + .with_vsync(true) + .with_multisampling(C::sample_count(&compositor_settings) as u16) + .build_windowed(builder, &event_loop) + .map_err(|error| { + use glutin::CreationError; + + match error { + CreationError::Window(error) => { + Error::WindowCreationFailed(error) + } + _ => Error::GraphicsAdapterNotFound, + } + })?; + + #[allow(unsafe_code)] + unsafe { + context.make_current().expect("Make OpenGL context current") + } + }; + + #[allow(unsafe_code)] + let (compositor, renderer) = unsafe { + C::new(compositor_settings, |address| { + context.get_proc_address(address) + })? + }; + + let (mut sender, receiver) = mpsc::unbounded(); + + let mut instance = Box::pin(run_instance::<A, E, C>( + application, + compositor, + renderer, + context, + runtime, + debug, + receiver, + )); + + let mut context = task::Context::from_waker(task::noop_waker_ref()); + + event_loop.run(move |event, _, control_flow| { + use glutin::event_loop::ControlFlow; + + if let ControlFlow::Exit = control_flow { + return; + } + + if let Some(event) = event.to_static() { + sender.start_send(event).expect("Send event"); + + let poll = instance.as_mut().poll(&mut context); + + *control_flow = match poll { + task::Poll::Pending => ControlFlow::Wait, + task::Poll::Ready(_) => ControlFlow::Exit, + }; + } + }); +} + +async fn run_instance<A, E, C>( + mut application: A, + mut compositor: C, + mut renderer: A::Renderer, + context: glutin::ContextWrapper<glutin::PossiblyCurrent, Window>, + mut runtime: Runtime<E, Proxy<A::Message>, A::Message>, + mut debug: Debug, + mut receiver: mpsc::UnboundedReceiver<glutin::event::Event<'_, A::Message>>, +) where + A: Application + 'static, + E: Executor + 'static, + C: window::GLCompositor<Renderer = A::Renderer> + 'static, +{ + use glutin::event; + use iced_winit::futures::stream::StreamExt; + + let clipboard = Clipboard::new(context.window()); + + let mut state = application::State::new(&application, context.window()); + let mut viewport_version = state.viewport_version(); + let mut user_interface = + ManuallyDrop::new(application::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() && messages.is_empty() { + continue; + } + + debug.event_processing_started(); + + let statuses = user_interface.update( + &events, + state.cursor_position(), + clipboard.as_ref().map(|c| c as _), + &mut renderer, + &mut messages, + ); + + debug.event_processing_finished(); + + for event in events.drain(..).zip(statuses.into_iter()) { + runtime.broadcast(event); + } + + if !messages.is_empty() { + let cache = + ManuallyDrop::into_inner(user_interface).into_cache(); + + // Update application + application::update( + &mut application, + &mut runtime, + &mut debug, + &mut messages, + ); + + // Update window + state.synchronize(&application, context.window()); + + user_interface = + ManuallyDrop::new(application::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(); + + context.window().request_redraw(); + } + event::Event::UserEvent(message) => { + messages.push(message); + } + event::Event::RedrawRequested(_) => { + debug.render_started(); + let current_viewport_version = state.viewport_version(); + + if viewport_version != current_viewport_version { + let physical_size = state.physical_size(); + let logical_size = state.logical_size(); + + 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(); + + context.resize(glutin::dpi::PhysicalSize::new( + physical_size.width, + physical_size.height, + )); + + compositor.resize_viewport(physical_size); + + viewport_version = current_viewport_version; + } + + let new_mouse_interaction = compositor.draw( + &mut renderer, + state.viewport(), + state.background_color(), + &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, + .. + } => { + if application::requests_exit(&window_event, state.modifiers()) + { + break; + } + + state.update(context.window(), &window_event, &mut debug); + + if let Some(event) = conversion::window_event( + &window_event, + state.scale_factor(), + state.modifiers(), + ) { + events.push(event); + } + } + _ => {} + } + } + + // Manually drop the user interface + drop(ManuallyDrop::into_inner(user_interface)); +} diff --git a/glutin/src/lib.rs b/glutin/src/lib.rs new file mode 100644 index 00000000..f2c0102a --- /dev/null +++ b/glutin/src/lib.rs @@ -0,0 +1,25 @@ +//! A windowing shell for [`iced`], on top of [`glutin`]. +//! +//!  +//! +//! [`iced`]: https://github.com/hecrj/iced +//! [`glutin`]: https://github.com/rust-windowing/glutin +#![deny(missing_docs)] +#![deny(missing_debug_implementations)] +#![deny(unused_results)] +#![deny(unsafe_code)] +#![forbid(rust_2018_idioms)] + +pub use glutin; +#[doc(no_inline)] +pub use iced_native::*; + +pub mod application; + +pub use iced_winit::settings; +pub use iced_winit::{Error, Mode}; + +#[doc(no_inline)] +pub use application::Application; +#[doc(no_inline)] +pub use settings::Settings; |