summaryrefslogtreecommitdiffstats
path: root/glutin
diff options
context:
space:
mode:
Diffstat (limited to 'glutin')
-rw-r--r--glutin/src/multi_window.rs264
-rw-r--r--glutin/src/multi_window/state.rs241
2 files changed, 491 insertions, 14 deletions
diff --git a/glutin/src/multi_window.rs b/glutin/src/multi_window.rs
index c3b9e74f..4949219f 100644
--- a/glutin/src/multi_window.rs
+++ b/glutin/src/multi_window.rs
@@ -1,15 +1,18 @@
//! Create interactive, native cross-platform applications.
+mod state;
+
+pub use state::State;
+
use crate::mouse;
use crate::{Error, Executor, Runtime};
-pub use iced_winit::multi_window::{
- self, Application, Event, State, StyleSheet,
-};
+pub use iced_winit::multi_window::{self, Application, StyleSheet};
use iced_winit::conversion;
use iced_winit::futures;
use iced_winit::futures::channel::mpsc;
use iced_winit::renderer;
+use iced_winit::settings;
use iced_winit::user_interface;
use iced_winit::window;
use iced_winit::{Clipboard, Command, Debug, Proxy, Settings};
@@ -238,7 +241,7 @@ async fn run_instance<A, E, C>(
{
let state = states.get(&window::Id::MAIN).unwrap();
- multi_window::run_command(
+ run_command(
&application,
&mut cache,
state,
@@ -324,7 +327,7 @@ async fn run_instance<A, E, C>(
.collect();
// Update application
- multi_window::update(
+ update(
&mut application,
&mut cache,
state,
@@ -343,15 +346,13 @@ async fn run_instance<A, E, C>(
let should_exit = application.should_exit();
- interfaces = ManuallyDrop::new(
- multi_window::build_user_interfaces(
- &application,
- &mut renderer,
- &mut debug,
- &states,
- pure_states,
- ),
- );
+ interfaces = ManuallyDrop::new(build_user_interfaces(
+ &application,
+ &mut renderer,
+ &mut debug,
+ &states,
+ pure_states,
+ ));
if should_exit {
break 'main;
@@ -571,3 +572,238 @@ async fn run_instance<A, E, C>(
// Manually drop the user interface
// drop(ManuallyDrop::into_inner(user_interface));
}
+
+/// TODO(derezzedex):
+// This is the an wrapper around the `Application::Message` associate type
+// to allows the `shell` to create internal messages, while still having
+// the current user specified custom messages.
+#[derive(Debug)]
+pub enum Event<Message> {
+ /// An [`Application`] generated message
+ Application(Message),
+
+ /// TODO(derezzedex)
+ // Create a wrapper variant of `window::Event` type instead
+ // (maybe we should also allow users to listen/react to those internal messages?)
+ NewWindow(window::Id, settings::Window),
+ /// TODO(derezzedex)
+ CloseWindow(window::Id),
+ /// TODO(derezzedex)
+ WindowCreated(window::Id, glutin::window::Window),
+}
+
+/// 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,
+ cache: &mut user_interface::Cache,
+ state: &State<A>,
+ renderer: &mut A::Renderer,
+ runtime: &mut Runtime<E, Proxy<Event<A::Message>>, Event<A::Message>>,
+ clipboard: &mut Clipboard,
+ proxy: &mut glutin::event_loop::EventLoopProxy<Event<A::Message>>,
+ debug: &mut Debug,
+ messages: &mut Vec<A::Message>,
+ windows: &HashMap<window::Id, glutin::window::Window>,
+ graphics_info: impl FnOnce() -> iced_graphics::compositor::Information + Copy,
+) where
+ <A::Renderer as crate::Renderer>::Theme: StyleSheet,
+{
+ for message in messages.drain(..) {
+ debug.log_message(&message);
+
+ debug.update_started();
+ let command = runtime.enter(|| application.update(message));
+ debug.update_finished();
+
+ run_command(
+ application,
+ cache,
+ state,
+ renderer,
+ command,
+ runtime,
+ clipboard,
+ proxy,
+ debug,
+ windows,
+ graphics_info,
+ );
+ }
+
+ let subscription = application.subscription().map(Event::Application);
+ runtime.track(subscription);
+}
+
+/// Runs the actions of a [`Command`].
+pub fn run_command<A, E>(
+ application: &A,
+ cache: &mut user_interface::Cache,
+ state: &State<A>,
+ renderer: &mut A::Renderer,
+ command: Command<A::Message>,
+ runtime: &mut Runtime<E, Proxy<Event<A::Message>>, Event<A::Message>>,
+ clipboard: &mut Clipboard,
+ proxy: &mut glutin::event_loop::EventLoopProxy<Event<A::Message>>,
+ debug: &mut Debug,
+ windows: &HashMap<window::Id, glutin::window::Window>,
+ _graphics_info: impl FnOnce() -> iced_graphics::compositor::Information + Copy,
+) where
+ A: Application,
+ E: Executor,
+ <A::Renderer as crate::Renderer>::Theme: StyleSheet,
+{
+ use iced_native::command;
+ use iced_native::system;
+ use iced_native::window;
+ use iced_winit::clipboard;
+ use iced_winit::futures::FutureExt;
+
+ for action in command.actions() {
+ match action {
+ command::Action::Future(future) => {
+ runtime.spawn(Box::pin(future.map(Event::Application)));
+ }
+ command::Action::Clipboard(action) => match action {
+ clipboard::Action::Read(tag) => {
+ let message = tag(clipboard.read());
+
+ proxy
+ .send_event(Event::Application(message))
+ .expect("Send message to event loop");
+ }
+ clipboard::Action::Write(contents) => {
+ clipboard.write(contents);
+ }
+ },
+ command::Action::Window(id, action) => {
+ let window = windows.get(&id).expect("No window found");
+
+ match action {
+ window::Action::Resize { width, height } => {
+ window.set_inner_size(glutin::dpi::LogicalSize {
+ width,
+ height,
+ });
+ }
+ window::Action::Move { x, y } => {
+ window.set_outer_position(
+ glutin::dpi::LogicalPosition { x, y },
+ );
+ }
+ window::Action::SetMode(mode) => {
+ window.set_visible(conversion::visible(mode));
+ window.set_fullscreen(conversion::fullscreen(
+ window.primary_monitor(),
+ mode,
+ ));
+ }
+ window::Action::FetchMode(tag) => {
+ let mode = if window.is_visible().unwrap_or(true) {
+ conversion::mode(window.fullscreen())
+ } else {
+ window::Mode::Hidden
+ };
+
+ proxy
+ .send_event(Event::Application(tag(mode)))
+ .expect("Send message to event loop");
+ }
+ }
+ }
+ command::Action::System(action) => match action {
+ system::Action::QueryInformation(_tag) => {
+ #[cfg(feature = "iced_winit/system")]
+ {
+ let graphics_info = _graphics_info();
+ let proxy = proxy.clone();
+
+ let _ = std::thread::spawn(move || {
+ let information =
+ crate::system::information(graphics_info);
+
+ let message = _tag(information);
+
+ proxy
+ .send_event(Event::Application(message))
+ .expect("Send message to event loop")
+ });
+ }
+ }
+ },
+ command::Action::Widget(action) => {
+ use crate::widget::operation;
+
+ let mut current_cache = std::mem::take(cache);
+ let mut current_operation = Some(action.into_operation());
+
+ let mut user_interface = multi_window::build_user_interface(
+ application,
+ current_cache,
+ renderer,
+ state.logical_size(),
+ debug,
+ window::Id::MAIN, // TODO(derezzedex): run the operation on every widget tree
+ );
+
+ while let Some(mut operation) = current_operation.take() {
+ user_interface.operate(renderer, operation.as_mut());
+
+ match operation.finish() {
+ operation::Outcome::None => {}
+ operation::Outcome::Some(message) => {
+ proxy
+ .send_event(Event::Application(message))
+ .expect("Send message to event loop");
+ }
+ operation::Outcome::Chain(next) => {
+ current_operation = Some(next);
+ }
+ }
+ }
+
+ current_cache = user_interface.into_cache();
+ *cache = current_cache;
+ }
+ }
+ }
+}
+
+/// TODO(derezzedex)
+pub fn build_user_interfaces<'a, A>(
+ application: &'a A,
+ renderer: &mut A::Renderer,
+ debug: &mut Debug,
+ states: &HashMap<window::Id, State<A>>,
+ mut pure_states: HashMap<window::Id, user_interface::Cache>,
+) -> HashMap<
+ window::Id,
+ iced_winit::UserInterface<
+ 'a,
+ <A as Application>::Message,
+ <A as Application>::Renderer,
+ >,
+>
+where
+ A: Application + 'static,
+ <A::Renderer as crate::Renderer>::Theme: StyleSheet,
+{
+ let mut interfaces = HashMap::new();
+
+ for (id, pure_state) in pure_states.drain() {
+ let state = &states.get(&id).unwrap();
+
+ let user_interface = multi_window::build_user_interface(
+ application,
+ pure_state,
+ renderer,
+ state.logical_size(),
+ debug,
+ id,
+ );
+
+ let _ = interfaces.insert(id, user_interface);
+ }
+
+ interfaces
+}
diff --git a/glutin/src/multi_window/state.rs b/glutin/src/multi_window/state.rs
new file mode 100644
index 00000000..163f46bd
--- /dev/null
+++ b/glutin/src/multi_window/state.rs
@@ -0,0 +1,241 @@
+use crate::application::{self, StyleSheet as _};
+use crate::conversion;
+use crate::multi_window::{Application, Event};
+use crate::window;
+use crate::{Color, Debug, Point, Size, Viewport};
+
+use glutin::event::{Touch, WindowEvent};
+use glutin::event_loop::EventLoopProxy;
+use glutin::window::Window;
+use std::collections::HashMap;
+use std::marker::PhantomData;
+
+/// The state of a windowed [`Application`].
+#[allow(missing_debug_implementations)]
+pub struct State<A: Application>
+where
+ <A::Renderer as crate::Renderer>::Theme: application::StyleSheet,
+{
+ title: String,
+ scale_factor: f64,
+ viewport: Viewport,
+ viewport_changed: bool,
+ cursor_position: glutin::dpi::PhysicalPosition<f64>,
+ modifiers: glutin::event::ModifiersState,
+ theme: <A::Renderer as crate::Renderer>::Theme,
+ appearance: iced_winit::application::Appearance,
+ application: PhantomData<A>,
+}
+
+impl<A: Application> State<A>
+where
+ <A::Renderer as crate::Renderer>::Theme: application::StyleSheet,
+{
+ /// Creates a new [`State`] for the provided [`Application`] and window.
+ pub fn new(application: &A, window: &Window) -> Self {
+ let title = application.title();
+ let scale_factor = application.scale_factor();
+ let theme = application.theme();
+ let appearance = theme.appearance(application.style());
+
+ let viewport = {
+ let physical_size = window.inner_size();
+
+ Viewport::with_physical_size(
+ Size::new(physical_size.width, physical_size.height),
+ window.scale_factor() * scale_factor,
+ )
+ };
+
+ Self {
+ title,
+ scale_factor,
+ viewport,
+ viewport_changed: false,
+ // TODO: Encode cursor availability in the type-system
+ cursor_position: glutin::dpi::PhysicalPosition::new(-1.0, -1.0),
+ modifiers: glutin::event::ModifiersState::default(),
+ theme,
+ appearance,
+ application: PhantomData,
+ }
+ }
+
+ /// Returns the current [`Viewport`] of the [`State`].
+ pub fn viewport(&self) -> &Viewport {
+ &self.viewport
+ }
+
+ /// TODO(derezzedex)
+ pub fn viewport_changed(&self) -> bool {
+ self.viewport_changed
+ }
+
+ /// Returns the physical [`Size`] of the [`Viewport`] of the [`State`].
+ pub fn physical_size(&self) -> Size<u32> {
+ self.viewport.physical_size()
+ }
+
+ /// Returns the logical [`Size`] of the [`Viewport`] of the [`State`].
+ pub fn logical_size(&self) -> Size<f32> {
+ self.viewport.logical_size()
+ }
+
+ /// Returns the current scale factor of the [`Viewport`] of the [`State`].
+ pub fn scale_factor(&self) -> f64 {
+ self.viewport.scale_factor()
+ }
+
+ /// Returns the current cursor position of the [`State`].
+ pub fn cursor_position(&self) -> Point {
+ conversion::cursor_position(
+ self.cursor_position,
+ self.viewport.scale_factor(),
+ )
+ }
+
+ /// Returns the current keyboard modifiers of the [`State`].
+ pub fn modifiers(&self) -> glutin::event::ModifiersState {
+ self.modifiers
+ }
+
+ /// Returns the current theme of the [`State`].
+ pub fn theme(&self) -> &<A::Renderer as crate::Renderer>::Theme {
+ &self.theme
+ }
+
+ /// Returns the current background [`Color`] of the [`State`].
+ pub fn background_color(&self) -> Color {
+ self.appearance.background_color
+ }
+
+ /// Returns the current text [`Color`] of the [`State`].
+ pub fn text_color(&self) -> Color {
+ self.appearance.text_color
+ }
+
+ /// Processes the provided window event and updates the [`State`]
+ /// accordingly.
+ pub fn update(
+ &mut self,
+ window: &Window,
+ event: &WindowEvent<'_>,
+ _debug: &mut Debug,
+ ) {
+ match event {
+ WindowEvent::Resized(new_size) => {
+ let size = Size::new(new_size.width, new_size.height);
+
+ self.viewport = Viewport::with_physical_size(
+ size,
+ window.scale_factor() * self.scale_factor,
+ );
+
+ self.viewport_changed = true;
+ }
+ WindowEvent::ScaleFactorChanged {
+ scale_factor: new_scale_factor,
+ new_inner_size,
+ } => {
+ let size =
+ Size::new(new_inner_size.width, new_inner_size.height);
+
+ self.viewport = Viewport::with_physical_size(
+ size,
+ new_scale_factor * self.scale_factor,
+ );
+
+ self.viewport_changed = true;
+ }
+ WindowEvent::CursorMoved { position, .. }
+ | WindowEvent::Touch(Touch {
+ location: position, ..
+ }) => {
+ self.cursor_position = *position;
+ }
+ WindowEvent::CursorLeft { .. } => {
+ // TODO: Encode cursor availability in the type-system
+ self.cursor_position =
+ glutin::dpi::PhysicalPosition::new(-1.0, -1.0);
+ }
+ WindowEvent::ModifiersChanged(new_modifiers) => {
+ self.modifiers = *new_modifiers;
+ }
+ #[cfg(feature = "debug")]
+ WindowEvent::KeyboardInput {
+ input:
+ glutin::event::KeyboardInput {
+ virtual_keycode:
+ Some(glutin::event::VirtualKeyCode::F12),
+ state: glutin::event::ElementState::Pressed,
+ ..
+ },
+ ..
+ } => _debug.toggle(),
+ _ => {}
+ }
+ }
+
+ /// Synchronizes the [`State`] with its [`Application`] and its respective
+ /// window.
+ ///
+ /// Normally an [`Application`] should be synchronized with its [`State`]
+ /// and window after calling [`Application::update`].
+ ///
+ /// [`Application::update`]: crate::Program::update
+ pub fn synchronize(
+ &mut self,
+ application: &A,
+ windows: &HashMap<window::Id, Window>,
+ proxy: &EventLoopProxy<Event<A::Message>>,
+ ) {
+ let new_windows = application.windows();
+
+ // Check for windows to close
+ for window_id in windows.keys() {
+ if !new_windows.iter().any(|(id, _)| id == window_id) {
+ proxy
+ .send_event(Event::CloseWindow(*window_id))
+ .expect("Failed to send message");
+ }
+ }
+
+ // Check for windows to spawn
+ for (id, settings) in new_windows {
+ if !windows.contains_key(&id) {
+ proxy
+ .send_event(Event::NewWindow(id, settings))
+ .expect("Failed to send message");
+ }
+ }
+
+ let window = windows.values().next().expect("No window found");
+
+ // Update window title
+ let new_title = application.title();
+
+ if self.title != new_title {
+ window.set_title(&new_title);
+
+ self.title = new_title;
+ }
+
+ // Update scale factor
+ let new_scale_factor = application.scale_factor();
+
+ if self.scale_factor != new_scale_factor {
+ let size = window.inner_size();
+
+ self.viewport = Viewport::with_physical_size(
+ Size::new(size.width, size.height),
+ window.scale_factor() * new_scale_factor,
+ );
+
+ self.scale_factor = new_scale_factor;
+ }
+
+ // Update theme and appearance
+ self.theme = application.theme();
+ self.appearance = self.theme.appearance(application.style());
+ }
+}