diff options
Diffstat (limited to 'winit')
-rw-r--r-- | winit/CHANGELOG.md | 14 | ||||
-rw-r--r-- | winit/Cargo.toml | 17 | ||||
-rw-r--r-- | winit/README.md | 8 | ||||
-rw-r--r-- | winit/src/application.rs | 602 | ||||
-rw-r--r-- | winit/src/application/state.rs | 202 | ||||
-rw-r--r-- | winit/src/clipboard.rs | 2 | ||||
-rw-r--r-- | winit/src/conversion.rs | 151 | ||||
-rw-r--r-- | winit/src/debug/basic.rs | 211 | ||||
-rw-r--r-- | winit/src/debug/null.rs | 46 | ||||
-rw-r--r-- | winit/src/error.rs | 27 | ||||
-rw-r--r-- | winit/src/lib.rs | 23 | ||||
-rw-r--r-- | winit/src/proxy.rs | 3 | ||||
-rw-r--r-- | winit/src/settings.rs | 118 | ||||
-rw-r--r-- | winit/src/settings/mod.rs | 53 | ||||
-rw-r--r-- | winit/src/size.rs | 30 |
15 files changed, 778 insertions, 729 deletions
diff --git a/winit/CHANGELOG.md b/winit/CHANGELOG.md deleted file mode 100644 index 1289a45a..00000000 --- a/winit/CHANGELOG.md +++ /dev/null @@ -1,14 +0,0 @@ -# Changelog -All notable changes to this project will be documented in this file. - -The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), -and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). - -## [Unreleased] - -## [0.1.0-alpha] - 2019-11-25 -### Added -- First release! :tada: - -[Unreleased]: https://github.com/hecrj/iced/compare/winit-0.1.0-alpha...HEAD -[0.1.0-alpha]: https://github.com/hecrj/iced/releases/tag/winit-0.1.0-alpha diff --git a/winit/Cargo.toml b/winit/Cargo.toml index ca2018c7..39a6a5fa 100644 --- a/winit/Cargo.toml +++ b/winit/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "iced_winit" -version = "0.1.0-alpha" +version = "0.2.0" authors = ["Héctor Ramón Jiménez <hector0193@gmail.com>"] edition = "2018" description = "A winit runtime for Iced" @@ -11,16 +11,25 @@ keywords = ["gui", "ui", "graphics", "interface", "widgets"] categories = ["gui"] [features] -debug = [] +debug = ["iced_native/debug"] [dependencies] -winit = "0.22" +winit = "0.24" window_clipboard = "0.1" log = "0.4" +thiserror = "1.0" [dependencies.iced_native] -version = "0.1.0-alpha" +version = "0.3" path = "../native" +[dependencies.iced_graphics] +version = "0.1" +path = "../graphics" + +[dependencies.iced_futures] +version = "0.2" +path = "../futures" + [target.'cfg(target_os = "windows")'.dependencies.winapi] version = "0.3.6" diff --git a/winit/README.md b/winit/README.md index d3309e49..721baa14 100644 --- a/winit/README.md +++ b/winit/README.md @@ -8,9 +8,11 @@ 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_winit/0.1.0-alpha.1/iced_winit/ +[documentation]: https://docs.rs/iced_winit [`iced_native`]: ../native [`winit`]: https://github.com/rust-windowing/winit @@ -18,7 +20,7 @@ It exposes a renderer-agnostic `Application` trait that can be implemented and t Add `iced_winit` as a dependency in your `Cargo.toml`: ```toml -iced_winit = "0.1.0-alpha" +iced_winit = "0.2" ``` __Iced moves fast and the `master` branch can contain breaking changes!__ If diff --git a/winit/src/application.rs b/winit/src/application.rs index 65bffd5b..d1a94864 100644 --- a/winit/src/application.rs +++ b/winit/src/application.rs @@ -1,65 +1,54 @@ +//! Create interactive, native cross-platform applications. +mod state; + +pub use state::State; + +use crate::conversion; +use crate::mouse; use crate::{ - conversion, size::Size, window, Cache, Clipboard, Command, Debug, Element, - Executor, Mode, MouseCursor, 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. -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; - - /// Initializes the [`Application`]. +/// 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: Program { + /// The data needed to initialize your [`Application`]. + 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 - fn new() -> (Self, Command<Self::Message>); + /// 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. /// @@ -67,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. /// @@ -84,251 +68,266 @@ 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`]. + /// Returns the background [`Color`] of the [`Application`]. /// - /// 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 - fn run( - settings: Settings, - 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())) - }; - - let (mut application, init_command) = runtime.enter(|| Self::new()); - runtime.spawn(init_command); - - let subscription = application.subscription(); - runtime.track(subscription); - - let mut title = application.title(); - let mut mode = application.mode(); - - let window = { - let mut window_builder = WindowBuilder::new(); - - let (width, height) = settings.window.size; - - 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, - )); - - #[cfg(target_os = "windows")] - { - use winit::platform::windows::WindowBuilderExtWindows; - - if let Some(parent) = settings.window.platform_specific.parent { - window_builder = window_builder.with_parent_window(parent); - } - } - - window_builder.build(&event_loop).expect("Open window") - }; - - let mut size = Size::new(window.inner_size(), window.scale_factor()); - let mut resized = false; - - let clipboard = Clipboard::new(&window); - let (mut backend, mut renderer) = Self::Backend::new(backend_settings); + /// 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 surface = backend.create_surface(&window); +/// 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 mut debug = Debug::new(); + debug.startup_started(); + + let (compositor, renderer) = C::new(compositor_settings)?; + + let event_loop = EventLoop::with_user_event(); + + let mut runtime = { + let proxy = Proxy::new(event_loop.create_proxy()); + let executor = E::new().map_err(Error::ExecutorCreationFailed)?; + + 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 window = settings + .window + .into_builder( + &application.title(), + application.mode(), + event_loop.primary_monitor(), + ) + .build(&event_loop) + .map_err(Error::WindowCreationFailed)?; + + let (mut sender, receiver) = mpsc::unbounded(); + + let mut instance = Box::pin(run_instance::<A, E, C>( + application, + compositor, + renderer, + window, + runtime, + debug, + receiver, + )); - let mut swap_chain = { - let physical_size = size.physical(); + 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_cursor = MouseCursor::OutOfBounds; - 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_cursor = backend.draw( + let new_mouse_interaction = compositor.draw( &mut renderer, &mut swap_chain, + state.viewport(), + state.background_color(), &primitive, - size.scale_factor(), &debug.overlay(), ); debug.render_finished(); - if new_mouse_cursor != mouse_cursor { - window.set_cursor_icon(conversion::mouse_cursor( - new_mouse_cursor, + if new_mouse_interaction != mouse_interaction { + window.set_cursor_icon(conversion::mouse_interaction( + new_mouse_interaction, )); - mouse_cursor = new_mouse_cursor; + mouse_interaction = new_mouse_interaction; } // TODO: Handle animations! @@ -338,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, + &window_event, + 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); +} diff --git a/winit/src/application/state.rs b/winit/src/application/state.rs new file mode 100644 index 00000000..58bc7ed6 --- /dev/null +++ b/winit/src/application/state.rs @@ -0,0 +1,202 @@ +use crate::conversion; +use crate::{Application, Color, Debug, Mode, Point, Size, Viewport}; + +use std::marker::PhantomData; +use winit::event::WindowEvent; +use winit::window::Window; + +/// The state of a windowed [`Application`]. +#[derive(Debug, Clone)] +pub struct State<A: Application> { + title: String, + mode: Mode, + background_color: Color, + scale_factor: f64, + viewport: Viewport, + viewport_version: usize, + cursor_position: winit::dpi::PhysicalPosition<f64>, + modifiers: winit::event::ModifiersState, + application: PhantomData<A>, +} + +impl<A: Application> State<A> { + /// Creates a new [`State`] for the provided [`Application`] and window. + pub fn new(application: &A, window: &Window) -> Self { + let title = application.title(); + let mode = application.mode(); + let background_color = application.background_color(); + let scale_factor = application.scale_factor(); + + 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, + mode, + background_color, + scale_factor, + viewport, + viewport_version: 0, + // TODO: Encode cursor availability in the type-system + cursor_position: winit::dpi::PhysicalPosition::new(-1.0, -1.0), + modifiers: winit::event::ModifiersState::default(), + application: PhantomData, + } + } + + /// Returns the current background [`Color`] of the [`State`]. + pub fn background_color(&self) -> Color { + self.background_color + } + + /// Returns the current [`Viewport`] of the [`State`]. + pub fn viewport(&self) -> &Viewport { + &self.viewport + } + + /// Returns the version of the [`Viewport`] of the [`State`]. + /// + /// The version is incremented every time the [`Viewport`] changes. + pub fn viewport_version(&self) -> usize { + self.viewport_version + } + + /// 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) -> winit::event::ModifiersState { + self.modifiers + } + + /// 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_version = self.viewport_version.wrapping_add(1); + } + 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_version = self.viewport_version.wrapping_add(1); + } + WindowEvent::CursorMoved { position, .. } => { + self.cursor_position = *position; + } + WindowEvent::CursorLeft { .. } => { + // TODO: Encode cursor availability in the type-system + self.cursor_position = + winit::dpi::PhysicalPosition::new(-1.0, -1.0); + } + WindowEvent::ModifiersChanged(new_modifiers) => { + self.modifiers = *new_modifiers; + } + #[cfg(feature = "debug")] + WindowEvent::KeyboardInput { + input: + winit::event::KeyboardInput { + virtual_keycode: Some(winit::event::VirtualKeyCode::F12), + state: winit::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, window: &Window) { + // Update window title + let new_title = application.title(); + + if self.title != new_title { + window.set_title(&new_title); + + self.title = new_title; + } + + // Update window mode + let new_mode = application.mode(); + + if self.mode != new_mode { + window.set_fullscreen(conversion::fullscreen( + window.current_monitor(), + new_mode, + )); + + self.mode = new_mode; + } + + // Update background color + self.background_color = application.background_color(); + + // 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; + } + } +} diff --git a/winit/src/clipboard.rs b/winit/src/clipboard.rs index 1ff029ab..93d53b11 100644 --- a/winit/src/clipboard.rs +++ b/winit/src/clipboard.rs @@ -5,8 +5,6 @@ pub struct Clipboard(window_clipboard::Clipboard); impl Clipboard { /// Creates a new [`Clipboard`] for the given window. - /// - /// [`Clipboard`]: struct.Clipboard.html pub fn new(window: &winit::window::Window) -> Option<Clipboard> { window_clipboard::Clipboard::new(window).map(Clipboard).ok() } diff --git a/winit/src/conversion.rs b/winit/src/conversion.rs index 1d008d05..e6fc4b96 100644 --- a/winit/src/conversion.rs +++ b/winit/src/conversion.rs @@ -1,18 +1,17 @@ -//! Convert [`winit`] types to [`iced_native`] types, and viceversa. +//! Convert [`winit`] types into [`iced_native`] types, and viceversa. //! //! [`winit`]: https://github.com/rust-windowing/winit //! [`iced_native`]: https://github.com/hecrj/iced/tree/master/native use crate::{ - input::{ - keyboard::{self, KeyCode, ModifiersState}, - mouse, touch, ButtonState, Touch, - }, - window, Event, Mode, MouseCursor, Point, + keyboard::{self, KeyCode, Modifiers}, + mouse, + touch::{self, Touch}, + window, Event, Mode, Point, }; /// Converts a winit window event into an iced event. pub fn window_event( - event: winit::event::WindowEvent<'_>, + event: &winit::event::WindowEvent<'_>, scale_factor: f64, modifiers: winit::event::ModifiersState, ) -> Option<Event> { @@ -27,6 +26,14 @@ pub fn window_event( height: logical_size.height, })) } + WindowEvent::ScaleFactorChanged { new_inner_size, .. } => { + let logical_size = new_inner_size.to_logical(scale_factor); + + Some(Event::Window(window::Event::Resized { + width: logical_size.width, + height: logical_size.height, + })) + } WindowEvent::CursorMoved { position, .. } => { let position = position.to_logical::<f64>(scale_factor); @@ -34,18 +41,30 @@ pub fn window_event( position: Point::new(position.x as f32, position.y as f32), })) } + WindowEvent::CursorEntered { .. } => { + Some(Event::Mouse(mouse::Event::CursorEntered)) + } + WindowEvent::CursorLeft { .. } => { + Some(Event::Mouse(mouse::Event::CursorLeft)) + } WindowEvent::MouseInput { button, state, .. } => { - Some(Event::Mouse(mouse::Event::Input { - button: mouse_button(button), - state: button_state(state), + let button = mouse_button(*button); + + Some(Event::Mouse(match state { + winit::event::ElementState::Pressed => { + mouse::Event::ButtonPressed(button) + } + winit::event::ElementState::Released => { + mouse::Event::ButtonReleased(button) + } })) } WindowEvent::MouseWheel { delta, .. } => match delta { winit::event::MouseScrollDelta::LineDelta(delta_x, delta_y) => { Some(Event::Mouse(mouse::Event::WheelScrolled { delta: mouse::ScrollDelta::Lines { - x: delta_x, - y: delta_y, + x: *delta_x, + y: *delta_y, }, })) } @@ -58,8 +77,8 @@ pub fn window_event( })) } }, - WindowEvent::ReceivedCharacter(c) if !is_private_use_character(c) => { - Some(Event::Keyboard(keyboard::Event::CharacterReceived(c))) + WindowEvent::ReceivedCharacter(c) if !is_private_use_character(*c) => { + Some(Event::Keyboard(keyboard::Event::CharacterReceived(*c))) } WindowEvent::KeyboardInput { input: @@ -69,30 +88,47 @@ pub fn window_event( .. }, .. - } => Some(Event::Keyboard(keyboard::Event::Input { - key_code: key_code(virtual_keycode), - state: button_state(state), - modifiers: modifiers_state(modifiers), + } => Some(Event::Keyboard({ + let key_code = key_code(*virtual_keycode); + let modifiers = self::modifiers(modifiers); + + match state { + winit::event::ElementState::Pressed => { + keyboard::Event::KeyPressed { + key_code, + modifiers, + } + } + winit::event::ElementState::Released => { + keyboard::Event::KeyReleased { + key_code, + modifiers, + } + } + } })), + WindowEvent::ModifiersChanged(new_modifiers) => Some(Event::Keyboard( + keyboard::Event::ModifiersChanged(self::modifiers(*new_modifiers)), + )), WindowEvent::HoveredFile(path) => { - Some(Event::Window(window::Event::FileHovered(path))) + Some(Event::Window(window::Event::FileHovered(path.clone()))) } WindowEvent::DroppedFile(path) => { - Some(Event::Window(window::Event::FileDropped(path))) + Some(Event::Window(window::Event::FileDropped(path.clone()))) } WindowEvent::HoveredFileCancelled => { Some(Event::Window(window::Event::FilesHoveredLeft)) } - WindowEvent::Touch(touch) => Some(Event::Touch(touch_event(touch))), + WindowEvent::Touch(touch) => Some(Event::Touch(touch_event(*touch))), _ => None, } } /// Converts a [`Mode`] to a [`winit`] fullscreen mode. /// -/// [`Mode`]: +/// [`winit`]: https://github.com/rust-windowing/winit pub fn fullscreen( - monitor: winit::monitor::MonitorHandle, + monitor: Option<winit::monitor::MonitorHandle>, mode: Mode, ) -> Option<winit::window::Fullscreen> { match mode { @@ -107,15 +143,23 @@ pub fn fullscreen( /// /// [`winit`]: https://github.com/rust-windowing/winit /// [`iced_native`]: https://github.com/hecrj/iced/tree/master/native -pub fn mouse_cursor(mouse_cursor: MouseCursor) -> winit::window::CursorIcon { - match mouse_cursor { - MouseCursor::OutOfBounds => winit::window::CursorIcon::Default, - MouseCursor::Idle => winit::window::CursorIcon::Default, - MouseCursor::Pointer => winit::window::CursorIcon::Hand, - MouseCursor::Working => winit::window::CursorIcon::Progress, - MouseCursor::Grab => winit::window::CursorIcon::Grab, - MouseCursor::Grabbing => winit::window::CursorIcon::Grabbing, - MouseCursor::Text => winit::window::CursorIcon::Text, +pub fn mouse_interaction( + interaction: mouse::Interaction, +) -> winit::window::CursorIcon { + use mouse::Interaction; + + match interaction { + Interaction::Idle => winit::window::CursorIcon::Default, + Interaction::Pointer => winit::window::CursorIcon::Hand, + Interaction::Working => winit::window::CursorIcon::Progress, + Interaction::Grab => winit::window::CursorIcon::Grab, + Interaction::Grabbing => winit::window::CursorIcon::Grabbing, + Interaction::Crosshair => winit::window::CursorIcon::Crosshair, + Interaction::Text => winit::window::CursorIcon::Text, + Interaction::ResizingHorizontally => { + winit::window::CursorIcon::EwResize + } + Interaction::ResizingVertically => winit::window::CursorIcon::NsResize, } } @@ -128,18 +172,9 @@ pub fn mouse_button(mouse_button: winit::event::MouseButton) -> mouse::Button { winit::event::MouseButton::Left => mouse::Button::Left, winit::event::MouseButton::Right => mouse::Button::Right, winit::event::MouseButton::Middle => mouse::Button::Middle, - winit::event::MouseButton::Other(other) => mouse::Button::Other(other), - } -} - -/// Converts an `ElementState` from [`winit`] to an [`iced_native`] button state. -/// -/// [`winit`]: https://github.com/rust-windowing/winit -/// [`iced_native`]: https://github.com/hecrj/iced/tree/master/native -pub fn button_state(element_state: winit::event::ElementState) -> ButtonState { - match element_state { - winit::event::ElementState::Pressed => ButtonState::Pressed, - winit::event::ElementState::Released => ButtonState::Released, + winit::event::MouseButton::Other(other) => { + mouse::Button::Other(other as u8) + } } } @@ -148,10 +183,8 @@ pub fn button_state(element_state: winit::event::ElementState) -> ButtonState { /// /// [`winit`]: https://github.com/rust-windowing/winit /// [`iced_native`]: https://github.com/hecrj/iced/tree/master/native -pub fn modifiers_state( - modifiers: winit::event::ModifiersState, -) -> ModifiersState { - ModifiersState { +pub fn modifiers(modifiers: winit::event::ModifiersState) -> Modifiers { + Modifiers { shift: modifiers.shift(), control: modifiers.ctrl(), alt: modifiers.alt(), @@ -159,6 +192,16 @@ pub fn modifiers_state( } } +/// Converts a physical cursor position to a logical `Point`. +pub fn cursor_position( + position: winit::dpi::PhysicalPosition<f64>, + scale_factor: f64, +) -> Point { + let logical_position = position.to_logical(scale_factor); + + Point::new(logical_position.x, logical_position.y) +} + /// Converts a `Touch` from [`winit`] to an [`iced_native`] touch event. /// /// [`winit`]: https://github.com/rust-windowing/winit @@ -276,7 +319,8 @@ pub fn key_code(virtual_keycode: winit::event::VirtualKeyCode) -> KeyCode { winit::event::VirtualKeyCode::Numpad9 => KeyCode::Numpad9, winit::event::VirtualKeyCode::AbntC1 => KeyCode::AbntC1, winit::event::VirtualKeyCode::AbntC2 => KeyCode::AbntC2, - winit::event::VirtualKeyCode::Add => KeyCode::Add, + winit::event::VirtualKeyCode::NumpadAdd => KeyCode::NumpadAdd, + winit::event::VirtualKeyCode::Plus => KeyCode::Plus, winit::event::VirtualKeyCode::Apostrophe => KeyCode::Apostrophe, winit::event::VirtualKeyCode::Apps => KeyCode::Apps, winit::event::VirtualKeyCode::At => KeyCode::At, @@ -287,8 +331,8 @@ pub fn key_code(virtual_keycode: winit::event::VirtualKeyCode) -> KeyCode { winit::event::VirtualKeyCode::Colon => KeyCode::Colon, winit::event::VirtualKeyCode::Comma => KeyCode::Comma, winit::event::VirtualKeyCode::Convert => KeyCode::Convert, - winit::event::VirtualKeyCode::Decimal => KeyCode::Decimal, - winit::event::VirtualKeyCode::Divide => KeyCode::Divide, + winit::event::VirtualKeyCode::NumpadDecimal => KeyCode::NumpadDecimal, + winit::event::VirtualKeyCode::NumpadDivide => KeyCode::NumpadDivide, winit::event::VirtualKeyCode::Equals => KeyCode::Equals, winit::event::VirtualKeyCode::Grave => KeyCode::Grave, winit::event::VirtualKeyCode::Kana => KeyCode::Kana, @@ -302,7 +346,7 @@ pub fn key_code(virtual_keycode: winit::event::VirtualKeyCode) -> KeyCode { winit::event::VirtualKeyCode::MediaSelect => KeyCode::MediaSelect, winit::event::VirtualKeyCode::MediaStop => KeyCode::MediaStop, winit::event::VirtualKeyCode::Minus => KeyCode::Minus, - winit::event::VirtualKeyCode::Multiply => KeyCode::Multiply, + winit::event::VirtualKeyCode::NumpadMultiply => KeyCode::NumpadMultiply, winit::event::VirtualKeyCode::Mute => KeyCode::Mute, winit::event::VirtualKeyCode::MyComputer => KeyCode::MyComputer, winit::event::VirtualKeyCode::NavigateForward => { @@ -330,7 +374,7 @@ pub fn key_code(virtual_keycode: winit::event::VirtualKeyCode) -> KeyCode { winit::event::VirtualKeyCode::Slash => KeyCode::Slash, winit::event::VirtualKeyCode::Sleep => KeyCode::Sleep, winit::event::VirtualKeyCode::Stop => KeyCode::Stop, - winit::event::VirtualKeyCode::Subtract => KeyCode::Subtract, + winit::event::VirtualKeyCode::NumpadSubtract => KeyCode::NumpadSubtract, winit::event::VirtualKeyCode::Sysrq => KeyCode::Sysrq, winit::event::VirtualKeyCode::Tab => KeyCode::Tab, winit::event::VirtualKeyCode::Underline => KeyCode::Underline, @@ -349,6 +393,7 @@ pub fn key_code(virtual_keycode: winit::event::VirtualKeyCode) -> KeyCode { winit::event::VirtualKeyCode::Copy => KeyCode::Copy, winit::event::VirtualKeyCode::Paste => KeyCode::Paste, winit::event::VirtualKeyCode::Cut => KeyCode::Cut, + winit::event::VirtualKeyCode::Asterisk => KeyCode::Asterisk, } } diff --git a/winit/src/debug/basic.rs b/winit/src/debug/basic.rs deleted file mode 100644 index d46edba6..00000000 --- a/winit/src/debug/basic.rs +++ /dev/null @@ -1,211 +0,0 @@ -use std::{collections::VecDeque, time}; - -#[derive(Debug)] -pub struct Debug { - is_enabled: bool, - - startup_start: time::Instant, - startup_duration: time::Duration, - - update_start: time::Instant, - update_durations: TimeBuffer, - - view_start: time::Instant, - view_durations: TimeBuffer, - - layout_start: time::Instant, - layout_durations: TimeBuffer, - - event_start: time::Instant, - event_durations: TimeBuffer, - - draw_start: time::Instant, - draw_durations: TimeBuffer, - - render_start: time::Instant, - render_durations: TimeBuffer, - - message_count: usize, - last_messages: VecDeque<String>, -} - -impl Debug { - pub fn new() -> Self { - let now = time::Instant::now(); - - Self { - is_enabled: false, - startup_start: now, - startup_duration: time::Duration::from_secs(0), - - update_start: now, - update_durations: TimeBuffer::new(200), - - view_start: now, - view_durations: TimeBuffer::new(200), - - layout_start: now, - layout_durations: TimeBuffer::new(200), - - event_start: now, - event_durations: TimeBuffer::new(200), - - draw_start: now, - draw_durations: TimeBuffer::new(200), - - render_start: now, - render_durations: TimeBuffer::new(50), - - message_count: 0, - last_messages: VecDeque::new(), - } - } - - pub fn toggle(&mut self) { - self.is_enabled = !self.is_enabled; - } - - pub fn startup_started(&mut self) { - self.startup_start = time::Instant::now(); - } - - pub fn startup_finished(&mut self) { - self.startup_duration = time::Instant::now() - self.startup_start; - } - - pub fn update_started(&mut self) { - self.update_start = time::Instant::now(); - } - - pub fn update_finished(&mut self) { - self.update_durations - .push(time::Instant::now() - self.update_start); - } - - pub fn view_started(&mut self) { - self.view_start = time::Instant::now(); - } - - pub fn view_finished(&mut self) { - self.view_durations - .push(time::Instant::now() - self.view_start); - } - - pub fn layout_started(&mut self) { - self.layout_start = time::Instant::now(); - } - - pub fn layout_finished(&mut self) { - self.layout_durations - .push(time::Instant::now() - self.layout_start); - } - - pub fn event_processing_started(&mut self) { - self.event_start = time::Instant::now(); - } - - pub fn event_processing_finished(&mut self) { - self.event_durations - .push(time::Instant::now() - self.event_start); - } - - pub fn draw_started(&mut self) { - self.draw_start = time::Instant::now(); - } - - pub fn draw_finished(&mut self) { - self.draw_durations - .push(time::Instant::now() - self.draw_start); - } - - pub fn render_started(&mut self) { - self.render_start = time::Instant::now(); - } - - pub fn render_finished(&mut self) { - self.render_durations - .push(time::Instant::now() - self.render_start); - } - - pub fn log_message<Message: std::fmt::Debug>(&mut self, message: &Message) { - self.last_messages.push_back(format!("{:?}", message)); - - if self.last_messages.len() > 10 { - let _ = self.last_messages.pop_front(); - } - - self.message_count += 1; - } - - pub fn overlay(&self) -> Vec<String> { - if !self.is_enabled { - return Vec::new(); - } - - let mut lines = Vec::new(); - - fn key_value<T: std::fmt::Debug>(key: &str, value: T) -> String { - format!("{} {:?}", key, value) - } - - lines.push(format!( - "{} {} - {}", - env!("CARGO_PKG_NAME"), - env!("CARGO_PKG_VERSION"), - env!("CARGO_PKG_REPOSITORY"), - )); - lines.push(key_value("Startup:", self.startup_duration)); - lines.push(key_value("Update:", self.update_durations.average())); - lines.push(key_value("View:", self.view_durations.average())); - lines.push(key_value("Layout:", self.layout_durations.average())); - lines.push(key_value( - "Event processing:", - self.event_durations.average(), - )); - lines.push(key_value( - "Primitive generation:", - self.draw_durations.average(), - )); - lines.push(key_value("Render:", self.render_durations.average())); - lines.push(key_value("Message count:", self.message_count)); - lines.push(String::from("Last messages:")); - lines.extend( - self.last_messages.iter().map(|msg| format!(" {}", msg)), - ); - - lines - } -} - -#[derive(Debug)] -struct TimeBuffer { - head: usize, - size: usize, - contents: Vec<time::Duration>, -} - -impl TimeBuffer { - fn new(capacity: usize) -> TimeBuffer { - TimeBuffer { - head: 0, - size: 0, - contents: vec![time::Duration::from_secs(0); capacity], - } - } - - fn push(&mut self, duration: time::Duration) { - self.head = (self.head + 1) % self.contents.len(); - self.contents[self.head] = duration; - self.size = (self.size + 1).min(self.contents.len()); - } - - fn average(&self) -> time::Duration { - let sum: time::Duration = if self.size == self.contents.len() { - self.contents[..].iter().sum() - } else { - self.contents[..self.size].iter().sum() - }; - - sum / self.size.max(1) as u32 - } -} diff --git a/winit/src/debug/null.rs b/winit/src/debug/null.rs deleted file mode 100644 index 2a9430cd..00000000 --- a/winit/src/debug/null.rs +++ /dev/null @@ -1,46 +0,0 @@ -#[derive(Debug)] -pub struct Debug; - -impl Debug { - pub fn new() -> Self { - Self - } - - pub fn startup_started(&mut self) {} - - pub fn startup_finished(&mut self) {} - - pub fn update_started(&mut self) {} - - pub fn update_finished(&mut self) {} - - pub fn view_started(&mut self) {} - - pub fn view_finished(&mut self) {} - - pub fn layout_started(&mut self) {} - - pub fn layout_finished(&mut self) {} - - pub fn event_processing_started(&mut self) {} - - pub fn event_processing_finished(&mut self) {} - - pub fn draw_started(&mut self) {} - - pub fn draw_finished(&mut self) {} - - pub fn render_started(&mut self) {} - - pub fn render_finished(&mut self) {} - - pub fn log_message<Message: std::fmt::Debug>( - &mut self, - _message: &Message, - ) { - } - - pub fn overlay(&self) -> Vec<String> { - Vec::new() - } -} diff --git a/winit/src/error.rs b/winit/src/error.rs new file mode 100644 index 00000000..8e1d20e8 --- /dev/null +++ b/winit/src/error.rs @@ -0,0 +1,27 @@ +use iced_futures::futures; + +/// An error that occurred while running an application. +#[derive(Debug, thiserror::Error)] +pub enum Error { + /// The futures executor could not be created. + #[error("the futures executor could not be created")] + ExecutorCreationFailed(futures::io::Error), + + /// The application window could not be created. + #[error("the application window could not be created")] + WindowCreationFailed(winit::error::OsError), + + /// A suitable graphics adapter or device could not be found. + #[error("a suitable graphics adapter or device could not be found")] + GraphicsAdapterNotFound, +} + +impl From<iced_graphics::Error> for Error { + fn from(error: iced_graphics::Error) -> Error { + match error { + iced_graphics::Error::AdapterNotFound => { + Error::GraphicsAdapterNotFound + } + } + } +} diff --git a/winit/src/lib.rs b/winit/src/lib.rs index f99e1290..c9f324dd 100644 --- a/winit/src/lib.rs +++ b/winit/src/lib.rs @@ -1,6 +1,6 @@ //! A windowing shell for Iced, on top of [`winit`]. //! -//!  +//!  //! //! `iced_winit` offers some convenient abstractions on top of [`iced_native`] //! to quickstart development when using [`winit`]. @@ -13,8 +13,7 @@ //! //! [`iced_native`]: https://github.com/hecrj/iced/tree/master/native //! [`winit`]: https://github.com/rust-windowing/winit -//! [`Application`]: trait.Application.html -//! [`conversion`]: conversion +//! [`conversion`]: crate::conversion #![deny(missing_docs)] #![deny(missing_debug_implementations)] #![deny(unused_results)] @@ -25,28 +24,20 @@ pub use iced_native::*; pub use winit; +pub mod application; pub mod conversion; pub mod settings; -mod application; mod clipboard; +mod error; mod mode; mod proxy; -mod size; - -// 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 error::Error; pub use mode::Mode; +pub use proxy::Proxy; pub use settings::Settings; -use debug::Debug; -use proxy::Proxy; +pub use iced_graphics::Viewport; diff --git a/winit/src/proxy.rs b/winit/src/proxy.rs index cff6ca72..7b9074d7 100644 --- a/winit/src/proxy.rs +++ b/winit/src/proxy.rs @@ -5,6 +5,8 @@ use iced_native::futures::{ }; use std::pin::Pin; +/// An event loop proxy that implements `Sink`. +#[derive(Debug)] pub struct Proxy<Message: 'static> { raw: winit::event_loop::EventLoopProxy<Message>, } @@ -18,6 +20,7 @@ impl<Message: 'static> Clone for Proxy<Message> { } impl<Message: 'static> Proxy<Message> { + /// Creates a new [`Proxy`] from an `EventLoopProxy`. pub fn new(raw: winit::event_loop::EventLoopProxy<Message>) -> Self { Self { raw } } diff --git a/winit/src/settings.rs b/winit/src/settings.rs new file mode 100644 index 00000000..2e8715cd --- /dev/null +++ b/winit/src/settings.rs @@ -0,0 +1,118 @@ +//! Configure your application. +#[cfg(target_os = "windows")] +#[path = "settings/windows.rs"] +mod platform; +#[cfg(not(target_os = "windows"))] +#[path = "settings/not_windows.rs"] +mod platform; + +pub use platform::PlatformSpecific; + +use crate::conversion; +use crate::Mode; +use winit::monitor::MonitorHandle; +use winit::window::WindowBuilder; + +/// The settings of an application. +#[derive(Debug, Clone, Default)] +pub struct Settings<Flags> { + /// The [`Window`] settings + pub window: Window, + + /// The data needed to initialize an [`Application`]. + /// + /// [`Application`]: crate::Application + pub flags: Flags, +} + +/// The window settings of an application. +#[derive(Debug, Clone)] +pub struct Window { + /// The size of the window. + pub size: (u32, u32), + + /// The minimum size of the window. + pub min_size: Option<(u32, u32)>, + + /// The maximum size of the window. + pub max_size: Option<(u32, u32)>, + + /// Whether the window should be resizable or not. + pub resizable: bool, + + /// Whether the window should have a border, a title bar, etc. + pub decorations: bool, + + /// Whether the window should be transparent. + pub transparent: bool, + + /// Whether the window will always be on top of other windows. + pub always_on_top: bool, + + /// The window icon, which is also usually used in the taskbar + pub icon: Option<winit::window::Icon>, + + /// Platform specific settings. + pub platform_specific: platform::PlatformSpecific, +} + +impl Window { + /// Converts the window settings into a `WindowBuilder` from `winit`. + pub fn into_builder( + self, + title: &str, + mode: Mode, + primary_monitor: Option<MonitorHandle>, + ) -> WindowBuilder { + let mut window_builder = WindowBuilder::new(); + + let (width, height) = self.size; + + window_builder = window_builder + .with_title(title) + .with_inner_size(winit::dpi::LogicalSize { width, height }) + .with_resizable(self.resizable) + .with_decorations(self.decorations) + .with_transparent(self.transparent) + .with_window_icon(self.icon) + .with_always_on_top(self.always_on_top) + .with_fullscreen(conversion::fullscreen(primary_monitor, mode)); + + if let Some((width, height)) = self.min_size { + window_builder = window_builder + .with_min_inner_size(winit::dpi::LogicalSize { width, height }); + } + + if let Some((width, height)) = self.max_size { + window_builder = window_builder + .with_max_inner_size(winit::dpi::LogicalSize { width, height }); + } + + #[cfg(target_os = "windows")] + { + use winit::platform::windows::WindowBuilderExtWindows; + + if let Some(parent) = self.platform_specific.parent { + window_builder = window_builder.with_parent_window(parent); + } + } + + window_builder + } +} + +impl Default for Window { + fn default() -> Window { + Window { + size: (1024, 768), + min_size: None, + max_size: None, + resizable: true, + decorations: true, + transparent: false, + always_on_top: false, + icon: None, + platform_specific: Default::default(), + } + } +} diff --git a/winit/src/settings/mod.rs b/winit/src/settings/mod.rs deleted file mode 100644 index b2290b46..00000000 --- a/winit/src/settings/mod.rs +++ /dev/null @@ -1,53 +0,0 @@ -//! Configure your application. -#[cfg(target_os = "windows")] -#[path = "windows.rs"] -mod platform; -#[cfg(not(target_os = "windows"))] -#[path = "not_windows.rs"] -mod platform; - -pub use platform::PlatformSpecific; - -/// The settings of an application. -#[derive(Debug, Clone, Copy, PartialEq)] -pub struct Settings { - /// The [`Window`] settings - /// - /// [`Window`]: struct.Window.html - pub window: Window, -} - -impl Default for Settings { - fn default() -> Settings { - Settings { - window: Window::default(), - } - } -} - -/// The window settings of an application. -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub struct Window { - /// The size of the window. - pub size: (u32, u32), - - /// Whether the window should be resizable or not. - pub resizable: bool, - - /// Whether the window should have a border, a title bar, etc. - pub decorations: bool, - - /// Platform specific settings. - pub platform_specific: platform::PlatformSpecific, -} - -impl Default for Window { - fn default() -> Window { - Window { - size: (1024, 768), - resizable: true, - decorations: true, - platform_specific: Default::default(), - } - } -} diff --git a/winit/src/size.rs b/winit/src/size.rs deleted file mode 100644 index 7e3056d4..00000000 --- a/winit/src/size.rs +++ /dev/null @@ -1,30 +0,0 @@ -pub struct Size { - physical: winit::dpi::PhysicalSize<u32>, - logical: winit::dpi::LogicalSize<f64>, - scale_factor: f64, -} - -impl Size { - pub fn new( - physical: winit::dpi::PhysicalSize<u32>, - scale_factor: f64, - ) -> Size { - Size { - logical: physical.to_logical(scale_factor), - physical, - scale_factor, - } - } - - pub fn physical(&self) -> winit::dpi::PhysicalSize<u32> { - self.physical - } - - pub fn logical(&self) -> winit::dpi::LogicalSize<f64> { - self.logical - } - - pub fn scale_factor(&self) -> f64 { - self.scale_factor - } -} |