summaryrefslogtreecommitdiffstats
path: root/glutin/src
diff options
context:
space:
mode:
authorLibravatar Héctor Ramón Jiménez <hector0193@gmail.com>2020-05-21 04:27:31 +0200
committerLibravatar Héctor Ramón Jiménez <hector0193@gmail.com>2020-05-21 04:27:31 +0200
commitae5e2c6c734894d71b2034a498a858b7997c5d3d (patch)
tree19d416e08287542fc17496ab58d2d16d6704a6e6 /glutin/src
parentd77492c0c37dec1207049b340a318e263cb96b82 (diff)
downloadiced-ae5e2c6c734894d71b2034a498a858b7997c5d3d.tar.gz
iced-ae5e2c6c734894d71b2034a498a858b7997c5d3d.tar.bz2
iced-ae5e2c6c734894d71b2034a498a858b7997c5d3d.zip
Introduce `Program` and `State`
Diffstat (limited to 'glutin/src')
-rw-r--r--glutin/src/application.rs575
-rw-r--r--glutin/src/lib.rs2
2 files changed, 175 insertions, 402 deletions
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};
-
-/// 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;
-
- /// 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;
-
- /// The data needed to initialize your [`Application`].
- ///
- /// [`Application`]: trait.Application.html
- type Flags;
-
- /// 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<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.
- ///
- /// 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<Self::Message> {
- 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,
- <Self::Compositor as window::GLCompositor>::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<Self::Flags>,
- backend_settings: <Self::Compositor as window::GLCompositor>::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(),
+use iced_winit::{program, Clipboard, Debug, Proxy, Settings};
+
+pub use iced_winit::Application;
+
+pub fn run<A, E, C>(
+ settings: Settings<A::Flags>,
+ compositor_settings: C::Settings,
+) where
+ A: Application + 'static,
+ E: Executor + 'static,
+ C: window::GLCompositor<Renderer = A::Renderer> + 'static,
+{
+ use glutin::{
+ event,
+ event_loop::{ControlFlow, EventLoop},
+ ContextBuilder,
+ };
+
+ let mut debug = Debug::new();
+ debug.startup_started();
+
+ 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());
+
+ Runtime::new(executor, proxy)
+ };
+
+ let flags = settings.flags;
+ let (application, init_command) = runtime.enter(|| A::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 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)
- })
- };
-
- let user_interface = build_user_interface(
- &mut application,
- Cache::default(),
- &mut renderer,
- viewport.logical_size(),
- &mut debug,
- );
+ unsafe {
+ context.make_current().expect("Make OpenGL context current")
+ }
+ };
+
+ let clipboard = Clipboard::new(&context.window());
+ let mut mouse_interaction = mouse::Interaction::default();
+ let mut modifiers = glutin::event::ModifiersState::default();
+
+ 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;
+
+ #[allow(unsafe_code)]
+ let (mut compositor, mut renderer) = unsafe {
+ C::new(compositor_settings, |address| {
+ context.get_proc_address(address)
+ })
+ };
+
+ 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,
+ )
+ });
- debug.draw_started();
- let mut primitive = user_interface.draw(&mut renderer);
- debug.draw_finished();
+ // If the application was updated
+ if let Some(command) = command {
+ runtime.spawn(command);
- 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();
+ let program = state.program();
- context.window().request_redraw();
+ // Update subscriptions
+ let subscription = program.subscription();
+ runtime.track(subscription);
- event_loop.run(move |event, _, control_flow| match event {
- event::Event::MainEventsCleared => {
- if events.is_empty() && external_messages.is_empty() {
- return;
- }
+ // Update window title
+ let new_title = program.title();
- let mut user_interface = build_user_interface(
- &mut application,
- cache.take().unwrap(),
- &mut renderer,
- viewport.logical_size(),
- &mut debug,
- );
+ if title != new_title {
+ context.window().set_title(&new_title);
- 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());
+ title = new_title;
}
- 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();
+ // Update window mode
+ let new_mode = program.mode();
- context.resize(glutin::dpi::PhysicalSize {
- width: physical_size.width,
- height: physical_size.height,
- });
- compositor.resize_viewport(physical_size);
+ if mode != new_mode {
+ context.window().set_fullscreen(conversion::fullscreen(
+ context.window().current_monitor(),
+ new_mode,
+ ));
- resized = false;
+ mode = new_mode;
}
+ }
- let new_mouse_interaction = compositor.draw(
- &mut renderer,
- &viewport,
- &primitive,
- &debug.overlay(),
- );
+ context.window().request_redraw();
+ }
+ event::Event::UserEvent(message) => {
+ state.queue_message(message);
+ }
+ event::Event::RedrawRequested(_) => {
+ debug.render_started();
- context.swap_buffers().expect("Swap buffers");
- debug.render_finished();
+ if resized {
+ let physical_size = viewport.physical_size();
- if new_mouse_interaction != mouse_interaction {
- context.window().set_cursor_icon(
- conversion::mouse_interaction(new_mouse_interaction),
- );
+ context.resize(glutin::dpi::PhysicalSize::new(
+ physical_size.width,
+ physical_size.height,
+ ));
- mouse_interaction = new_mouse_interaction;
- }
+ compositor.resize_viewport(physical_size);
- // TODO: Handle animations!
- // Maybe we can use `ControlFlow::WaitUntil` for this.
+ resized = false;
}
- 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;
+ 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;
}
- })
- }
-}
-fn build_user_interface<'a, A: Application>(
- application: &'a mut A,
- cache: Cache,
- renderer: &mut <A::Compositor as window::GLCompositor>::Renderer,
- size: Size,
- debug: &mut Debug,
-) -> UserInterface<
- 'a,
- A::Message,
- <A::Compositor as window::GLCompositor>::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
+ // 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;