From e9a32376ecdfd3251c6cba56845cd5efe1539e38 Mon Sep 17 00:00:00 2001 From: Ryan Goldstein Date: Mon, 27 Apr 2020 20:19:14 -0400 Subject: [PATCH 01/13] Create the 'input' module to replace 'lifecycle' First, the 'lifecycle' concept is less important than in previous versions. There is just a single 'run' function that takes a Settings instance and a closure, instead of a whole mess of behind-the-scenes work. Most of the 'lifecycle' module (with the exception of a few Window functions) is just about receiving input from the user. The new name makes this more clear. Additionally, there were some ergonomic issues with re-exporting blinds structures rather than wrapping them in new code. Primarily, this required understanding "what is the mint crate" and "why do I care" and "how do I use it." End users of Quicksilver shouldn't have to concern themselves with this, so these parts of the blinds API have been wrapped over. The Settings struct also needed to be decoupled from blinds. There are more settings than just window management (for example, disable searching for 'static/' assets) so there are now Quicksilver-specific Settings. The old module and its members have been left around for convenience, because the alpha frequently requires updating for new features or critical bugfixes. However, they are strongly deprecated and will be subject to removal before beta. --- Cargo.toml | 2 +- README.md | 9 +- examples/00_window.rs | 10 +- examples/01_square.rs | 9 +- examples/02_image.rs | 9 +- examples/03_rgb_triangle.rs | 9 +- examples/04_render_to_texture.rs | 9 +- examples/05_blending.rs | 9 +- examples/06_timers.rs | 9 +- examples/07_text.rs | 9 +- examples/loading_screen.rs | 7 +- src/input.rs | 272 +++++++++++++++++++++++++++++++ src/lib.rs | 19 ++- src/lifecycle.rs | 25 +-- src/run.rs | 139 ++++++++++++++++ 15 files changed, 477 insertions(+), 69 deletions(-) create mode 100644 src/input.rs create mode 100644 src/run.rs diff --git a/Cargo.toml b/Cargo.toml index b8bbc8f3..c8ca5ea9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -30,7 +30,7 @@ ttf = ["elefont/rusttype", "rusttype"] maintenance = { status = "actively-developed" } [dependencies] -blinds = { version = "0.1.0", default-features = false, features = ["gl"] } +blinds = { version = "0.1.4", default-features = false, features = ["gl", "image"] } bytemuck = "1.0" elefont = { version = "0.1.3", features = ["rusttype", "unicode-normalization"], optional = true } gestalt = { version = "0.1", optional = true } diff --git a/README.md b/README.md index 7747607e..06cc68e4 100644 --- a/README.md +++ b/README.md @@ -29,14 +29,13 @@ Then replace `src/main.rs` with the following (the contents of quicksilver's use quicksilver::{ geom::{Rectangle, Vector}, graphics::{Color, Graphics}, - lifecycle::{run, EventStream, Settings, Window}, - Result, + input::{Input, Window}, + Result, Settings, run, }; fn main() { run( Settings { - size: Vector::new(800.0, 600.0).into(), title: "Square Example", ..Settings::default() }, @@ -44,7 +43,7 @@ fn main() { ); } -async fn app(window: Window, mut gfx: Graphics, mut events: EventStream) -> Result<()> { +async fn app(window: Window, mut gfx: Graphics, mut input: Input) -> Result<()> { // Clear the screen to a blank, white color gfx.clear(Color::WHITE); // Paint a blue square with a red outline in the center of our screen @@ -55,7 +54,7 @@ async fn app(window: Window, mut gfx: Graphics, mut events: EventStream) -> Resu // Send the data to be drawn gfx.present(&window)?; loop { - while let Some(_) = events.next_event().await {} + while let Some(_) = input.next_event().await {} } } ``` diff --git a/examples/00_window.rs b/examples/00_window.rs index 8a2f6841..157efe29 100644 --- a/examples/00_window.rs +++ b/examples/00_window.rs @@ -1,11 +1,10 @@ // Example 0: The Window // The simplest example: Do absolutely nothing other than just opening a window -use mint::Vector2; use quicksilver::{ graphics::Graphics, - lifecycle::{run, EventStream, Settings, Window}, - Result, + input::{Input, Window}, + run, Result, Settings, }; // main() serves as our kicking-off point, but it doesn't have our application logic @@ -14,7 +13,6 @@ use quicksilver::{ fn main() { run( Settings { - size: Vector2 { x: 800.0, y: 600.0 }, title: "Window Example", ..Settings::default() }, @@ -23,9 +21,9 @@ fn main() { } // Our actual logic! Not much to see for this example -async fn app(_window: Window, _gfx: Graphics, mut events: EventStream) -> Result<()> { +async fn app(_window: Window, _gfx: Graphics, mut input: Input) -> Result<()> { loop { - while let Some(_) = events.next_event().await { + while let Some(_) = input.next_event().await { // Normally we'd do some processing here } // And then we'd do updates and drawing here diff --git a/examples/01_square.rs b/examples/01_square.rs index 741c94f1..22fec4ea 100644 --- a/examples/01_square.rs +++ b/examples/01_square.rs @@ -3,14 +3,13 @@ use quicksilver::{ geom::{Rectangle, Vector}, graphics::{Color, Graphics}, - lifecycle::{run, EventStream, Settings, Window}, - Result, + input::{Input, Window}, + run, Result, Settings, }; fn main() { run( Settings { - size: Vector::new(800.0, 600.0).into(), title: "Square Example", ..Settings::default() }, @@ -18,7 +17,7 @@ fn main() { ); } -async fn app(window: Window, mut gfx: Graphics, mut events: EventStream) -> Result<()> { +async fn app(window: Window, mut gfx: Graphics, mut input: Input) -> Result<()> { // Clear the screen to a blank, white color gfx.clear(Color::WHITE); // Paint a blue square with a red outline in the center of our screen @@ -29,6 +28,6 @@ async fn app(window: Window, mut gfx: Graphics, mut events: EventStream) -> Resu // Send the data to be drawn gfx.present(&window)?; loop { - while let Some(_) = events.next_event().await {} + while let Some(_) = input.next_event().await {} } } diff --git a/examples/02_image.rs b/examples/02_image.rs index 8ead523c..4b146f56 100644 --- a/examples/02_image.rs +++ b/examples/02_image.rs @@ -3,14 +3,13 @@ use quicksilver::{ geom::{Rectangle, Vector}, graphics::{Color, Graphics, Image}, - lifecycle::{run, EventStream, Settings, Window}, - Result, + input::{Input, Window}, + run, Result, Settings, }; fn main() { run( Settings { - size: Vector::new(800.0, 600.0).into(), title: "Image Example", ..Settings::default() }, @@ -19,7 +18,7 @@ fn main() { } // This time we might return an error, so we use a Result -async fn app(window: Window, mut gfx: Graphics, mut events: EventStream) -> Result<()> { +async fn app(window: Window, mut gfx: Graphics, mut input: Input) -> Result<()> { // Load the image and wait for it to finish // We also use '?' to handle errors like file-not-found let image = Image::load(&gfx, "image.png").await?; @@ -30,6 +29,6 @@ async fn app(window: Window, mut gfx: Graphics, mut events: EventStream) -> Resu gfx.present(&window)?; loop { - while let Some(_) = events.next_event().await {} + while let Some(_) = input.next_event().await {} } } diff --git a/examples/03_rgb_triangle.rs b/examples/03_rgb_triangle.rs index b745b3de..4af94564 100644 --- a/examples/03_rgb_triangle.rs +++ b/examples/03_rgb_triangle.rs @@ -3,14 +3,13 @@ use quicksilver::{ geom::Vector, graphics::{Color, Element, Graphics, Mesh, Vertex}, - lifecycle::{run, EventStream, Settings, Window}, - Result, + input::{Input, Window}, + run, Result, Settings, }; fn main() { run( Settings { - size: Vector::new(800.0, 600.0).into(), title: "RGB Triangle Example", ..Settings::default() }, @@ -18,7 +17,7 @@ fn main() { ); } -async fn app(window: Window, mut gfx: Graphics, mut events: EventStream) -> Result<()> { +async fn app(window: Window, mut gfx: Graphics, mut input: Input) -> Result<()> { // Clear the screen to a blank, black color gfx.clear(Color::BLACK); // Paint a triangle with red, green, and blue vertices, blending the colors for the pixels in-between @@ -54,6 +53,6 @@ async fn app(window: Window, mut gfx: Graphics, mut events: EventStream) -> Resu // Send the data to be drawn gfx.present(&window)?; loop { - while let Some(_) = events.next_event().await {} + while let Some(_) = input.next_event().await {} } } diff --git a/examples/04_render_to_texture.rs b/examples/04_render_to_texture.rs index f1fcd0af..bd831158 100644 --- a/examples/04_render_to_texture.rs +++ b/examples/04_render_to_texture.rs @@ -3,14 +3,13 @@ use quicksilver::{ geom::{Circle, Rectangle, Vector}, graphics::{Color, Graphics, Image, PixelFormat, Surface}, - lifecycle::{run, EventStream, Settings, Window}, - Result, + input::{Input, Window}, + run, Result, Settings, }; fn main() { run( Settings { - size: Vector::new(800.0, 600.0).into(), title: "Square Example", ..Settings::default() }, @@ -18,7 +17,7 @@ fn main() { ); } -async fn app(window: Window, mut gfx: Graphics, mut events: EventStream) -> Result<()> { +async fn app(window: Window, mut gfx: Graphics, mut input: Input) -> Result<()> { gfx.clear(Color::WHITE); // Create a surface, which allows rendering to an image let mut surface = Surface::new( @@ -50,6 +49,6 @@ async fn app(window: Window, mut gfx: Graphics, mut events: EventStream) -> Resu gfx.present(&window)?; loop { - while let Some(_) = events.next_event().await {} + while let Some(_) = input.next_event().await {} } } diff --git a/examples/05_blending.rs b/examples/05_blending.rs index e5a5a241..1c4ec4aa 100644 --- a/examples/05_blending.rs +++ b/examples/05_blending.rs @@ -4,14 +4,13 @@ use quicksilver::{ geom::{Rectangle, Vector}, graphics::blend::{BlendChannel, BlendFactor, BlendFunction, BlendInput, BlendMode}, graphics::{Color, Graphics, Image}, - lifecycle::{run, EventStream, Settings, Window}, - Result, + input::{Input, Window}, + run, Result, Settings, }; fn main() { run( Settings { - size: Vector::new(800.0, 600.0).into(), title: "Blend Example", ..Settings::default() }, @@ -20,7 +19,7 @@ fn main() { } // This time we might return an error, so we use a Result -async fn app(window: Window, mut gfx: Graphics, mut events: EventStream) -> Result<()> { +async fn app(window: Window, mut gfx: Graphics, mut input: Input) -> Result<()> { let image = Image::load(&gfx, "image.png").await?; gfx.clear(Color::WHITE); // Set the blend pipeline @@ -63,6 +62,6 @@ async fn app(window: Window, mut gfx: Graphics, mut events: EventStream) -> Resu gfx.present(&window)?; loop { - while let Some(_) = events.next_event().await {} + while let Some(_) = input.next_event().await {} } } diff --git a/examples/06_timers.rs b/examples/06_timers.rs index 7d132629..4b5d5e31 100644 --- a/examples/06_timers.rs +++ b/examples/06_timers.rs @@ -3,14 +3,13 @@ use quicksilver::{ geom::{Rectangle, Vector}, graphics::{Color, Graphics}, - lifecycle::{run, EventStream, Settings, Window}, - Result, Timer, + input::{Input, Window}, + run, Result, Settings, Timer, }; fn main() { run( Settings { - size: Vector::new(800.0, 600.0).into(), title: "Square Example", ..Settings::default() }, @@ -18,7 +17,7 @@ fn main() { ); } -async fn app(window: Window, mut gfx: Graphics, mut events: EventStream) -> Result<()> { +async fn app(window: Window, mut gfx: Graphics, mut input: Input) -> Result<()> { // Clear the screen to a blank, white color gfx.clear(Color::WHITE); @@ -31,7 +30,7 @@ async fn app(window: Window, mut gfx: Graphics, mut events: EventStream) -> Resu let mut rect = Rectangle::new(Vector::new(0.0, 100.0), Vector::new(100.0, 100.0)); loop { - while let Some(_) = events.next_event().await {} + while let Some(_) = input.next_event().await {} // We use a while loop rather than an if so that we can try to catch up in the event of having a slow down. while update_timer.tick() { diff --git a/examples/07_text.rs b/examples/07_text.rs index d4cff8f7..73a4dc19 100644 --- a/examples/07_text.rs +++ b/examples/07_text.rs @@ -3,14 +3,13 @@ use quicksilver::{ geom::Vector, graphics::{Color, Graphics, VectorFont}, - lifecycle::{run, EventStream, Settings, Window}, - Result, + input::{Input, Window}, + run, Result, Settings, }; fn main() { run( Settings { - size: Vector::new(800.0, 600.0).into(), title: "Font Example", ..Settings::default() }, @@ -18,7 +17,7 @@ fn main() { ); } -async fn app(window: Window, mut gfx: Graphics, mut events: EventStream) -> Result<()> { +async fn app(window: Window, mut gfx: Graphics, mut input: Input) -> Result<()> { // Load the Font, just like loading any other asset let ttf = VectorFont::load("font.ttf").await?; let mut font = ttf.to_renderer(&gfx, 72.0)?; @@ -33,6 +32,6 @@ async fn app(window: Window, mut gfx: Graphics, mut events: EventStream) -> Resu gfx.present(&window)?; loop { - while let Some(_) = events.next_event().await {} + while let Some(_) = input.next_event().await {} } } diff --git a/examples/loading_screen.rs b/examples/loading_screen.rs index b20e91f4..6c73228b 100644 --- a/examples/loading_screen.rs +++ b/examples/loading_screen.rs @@ -11,14 +11,13 @@ use std::time::Duration; use quicksilver::{ geom::{Rectangle, Vector}, graphics::{Color, Graphics}, - lifecycle::{run, EventStream, Settings, Window}, - Result, + input::{Input, Window}, + run, Result, Settings, }; fn main() { run( Settings { - size: Vector::new(800.0, 600.0).into(), title: "Square Example", ..Settings::default() }, @@ -63,7 +62,7 @@ fn draw_loader(window: &Window, gfx: &mut Graphics, progress: usize, total: usiz Ok(()) } -async fn app(window: Window, mut gfx: Graphics, mut events: EventStream) -> Result<()> { +async fn app(window: Window, mut gfx: Graphics, mut events: Input) -> Result<()> { for i in 0..STEPS { draw_loader(&window, &mut gfx, i, STEPS)?; load_something(); diff --git a/src/input.rs b/src/input.rs new file mode 100644 index 00000000..b71ae685 --- /dev/null +++ b/src/input.rs @@ -0,0 +1,272 @@ +//! Read events / input state and manage the window + +use crate::geom::Vector; + +pub use blinds::event::{ + FocusChangedEvent, GamepadAxisEvent, GamepadButtonEvent, GamepadConnectedEvent, + GamepadDisconnectedEvent, KeyboardEvent, ModifiersChangedEvent, PointerEnteredEvent, + PointerInputEvent, PointerLeftEvent, ReceivedCharacterEvent, ScaleFactorChangedEvent, + ScrollDelta, +}; +#[cfg(feature = "event-cache")] +use blinds::event_cache::EventCache; +#[cfg(feature = "event-cache")] +pub use blinds::event_cache::GamepadState; +pub use blinds::{CursorIcon, GamepadAxis, GamepadButton, GamepadId, Key, MouseButton, PointerId}; + +pub struct Input { + source: blinds::EventStream, + #[cfg(feature = "event-cache")] + cache: EventCache, +} + +impl Input { + pub(crate) fn new(source: blinds::EventStream) -> Input { + Input { + source, + #[cfg(feature = "event-cache")] + cache: EventCache::new(), + } + } + + pub async fn next_event(&mut self) -> Option { + while let Some(ev) = self.source.next_event().await { + #[cfg(feature = "event-cache")] + self.cache.process_event(&ev); + // If there is an event, it might not be something Quicksilver can process. + // If it's not, skip this and get the next event + if let Some(ev) = conv(ev) { + return Some(ev); + } + } + + // We didn't have any Some events before we hit a None + None + } +} + +#[cfg(feature = "event-cache")] +impl Input { + /// Check if a given key is down + pub fn key_down(&self, key: Key) -> bool { + self.cache.key(key) + } + + /// The state of the global mouse + /// + /// Under a system with touch input or with multiple cursors, this may report erratic results. + /// The state here is tracked for every pointer event, regardless of pointer ID. + pub fn mouse(&self) -> PointerState { + self.cache.mouse().into() + } + + /// The state of the given pointer + #[allow(clippy::trivially_copy_pass_by_ref)] + pub fn pointer(&self, id: &PointerId) -> Option { + self.cache.pointer(id).map(|p| p.into()) + } + + /// The pointer ID and values that have been tracked + pub fn pointers(&self) -> impl Iterator { + self.cache.pointers().map(|(id, p)| (id, p.into())) + } + + /// The state of the given gamepad + pub fn gamepad(&self, id: &GamepadId) -> Option<&GamepadState> { + self.cache.gamepad(id) + } + + /// The gamepad ID and values that have been tracked + pub fn gamepads(&self) -> impl Iterator { + self.cache.gamepads() + } +} + +pub struct PointerState { + left: bool, + right: bool, + middle: bool, + location: Vector, +} + +impl PointerState { + pub fn left(&self) -> bool { + self.left + } + + pub fn right(&self) -> bool { + self.right + } + + pub fn middle(&self) -> bool { + self.middle + } + + pub fn location(&self) -> Vector { + self.location + } +} + +impl From<&blinds::event_cache::PointerState> for PointerState { + fn from(ps: &blinds::event_cache::PointerState) -> PointerState { + PointerState { + left: ps.left(), + right: ps.right(), + middle: ps.middle(), + location: ps.location().into(), + } + } +} + +#[derive(Clone, Debug)] +#[non_exhaustive] +/// An indicator something has changed or input has been dispatched +pub enum Event { + /// The size of the window has changed, see [`Window::size`] + /// + /// [`Window::size`]: crate::Window::size + Resized(ResizedEvent), + /// The scale factor of the window has changed, see [`Window::scale_factor`] + /// + /// [`Window::scale_factor`]: crate::Window::scale_factor + ScaleFactorChanged(ScaleFactorChangedEvent), + /// The window has gained operating system focus (true), or lost it (false) + FocusChanged(FocusChangedEvent), + /// The user typed a character, used for text input + /// + /// Don't use keyboard events for text! Depending on how the user's operating system and + /// keyboard layout are configured, different keys may produce different Unicode characters. + ReceivedCharacter(ReceivedCharacterEvent), + /// A key has been pressed, released, or held down + /// + /// Operating systems often have key repeat settings that cause duplicate events to be + /// generated for a single press. + KeyboardInput(KeyboardEvent), + /// A pointer entered the window + PointerEntered(PointerEnteredEvent), + /// A pointer has exited the window + PointerLeft(PointerLeftEvent), + /// A pointer has a new position, relative to the window's top-left + PointerMoved(PointerMovedEvent), + /// A button on a pointer, likely a mouse, has produced an input + PointerInput(PointerInputEvent), + /// The mousewheel has scrolled, either in lines or pixels (depending on the input method) + ScrollInput(ScrollDelta), + /// The keyboard modifiers (e.g. shift, alt, ctrl) have changed + ModifiersChanged(ModifiersChangedEvent), + /// A gamepad has been connected + GamepadConnected(GamepadConnectedEvent), + /// A gamepad has been disconnected + GamepadDisconnected(GamepadDisconnectedEvent), + /// A gamepad button has been pressed or released + GamepadButton(GamepadButtonEvent), + /// A gamepad axis has changed its value + GamepadAxis(GamepadAxisEvent), +} + +#[derive(Clone, Debug)] +/// See [`Event::Resized`] +pub struct ResizedEvent { + size: Vector, +} + +impl ResizedEvent { + /// The new size of the window + pub fn size(&self) -> Vector { + self.size + } +} + +#[derive(Clone, Debug)] +/// See [`Event::PointerMoved`] +/// +/// [`Event::PointerMoved`]: crate::event::Event::PointerMoved +pub struct PointerMovedEvent { + id: PointerId, + location: Vector, +} + +impl PointerMovedEvent { + pub fn pointer(&self) -> &PointerId { + &self.id + } + + /// The logical location of the pointer, relative to the top-left of the window + pub fn location(&self) -> Vector { + self.location + } +} + +fn conv(ev: blinds::Event) -> Option { + use Event::*; + Some(match ev { + blinds::Event::Resized(x) => Resized(ResizedEvent { + size: x.logical_size().into(), + }), + blinds::Event::ScaleFactorChanged(x) => ScaleFactorChanged(x), + blinds::Event::FocusChanged(x) => FocusChanged(x), + blinds::Event::ReceivedCharacter(x) => ReceivedCharacter(x), + blinds::Event::KeyboardInput(x) => KeyboardInput(x), + blinds::Event::PointerEntered(x) => PointerEntered(x), + blinds::Event::PointerLeft(x) => PointerLeft(x), + blinds::Event::PointerMoved(x) => PointerMoved(PointerMovedEvent { + id: *x.pointer(), + location: x.location().into(), + }), + blinds::Event::PointerInput(x) => PointerInput(x), + blinds::Event::ScrollInput(x) => ScrollInput(x), + blinds::Event::ModifiersChanged(x) => ModifiersChanged(x), + blinds::Event::GamepadConnected(x) => GamepadConnected(x), + blinds::Event::GamepadDisconnected(x) => GamepadDisconnected(x), + blinds::Event::GamepadButton(x) => GamepadButton(x), + blinds::Event::GamepadAxis(x) => GamepadAxis(x), + _ => return None, + }) +} + +pub struct Window(blinds::Window); + +impl Window { + /// Set the cursor icon to some value, or set it to invisible (None) + pub fn set_cursor_icon(&self, icon: Option) { + self.0.set_cursor_icon(icon); + } + + /// Get the size of the window in logical units + /// + /// On a high-dpi display, this doesn't correspond to physical pixels and must be multiplied by + /// [`scale`] when passing sizes to functions like `glViewport`. + /// + /// [`scale`]: Window::scale_factor + pub fn size(&self) -> Vector { + self.0.size().into() + } + + /// Set the size of the inside of the window in logical units + pub fn set_size(&self, size: Vector) { + self.0.set_size(size.into()); + } + + /// Set the title of the window or browser tab + pub fn set_title(&self, title: &str) { + self.0.set_title(title); + } + + /// Set if the window should be fullscreen or not + /// + /// On desktop, it will instantly become fullscreen (borderless windowed on Windows and Linux, + /// and fullscreen on macOS). On web, it will become fullscreen after the next user + /// interaction, due to browser API restrictions. + pub fn set_fullscreen(&self, fullscreen: bool) { + self.0.set_fullscreen(fullscreen); + } + + /// Draw the current frame to the screen + /// + /// If vsync is enabled, this will block until the frame is completed on desktop. On web, there + /// is no way to control vsync, or to manually control presentation, so this function is a + /// no-op. + pub fn present(&self) { + self.0.present(); + } +} diff --git a/src/lib.rs b/src/lib.rs index a5171e7f..4b44c49f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -26,18 +26,16 @@ //! ```no_run //! // Example 1: The Square //! // Open a window, and draw a colored square in it -//! use mint::Vector2; //! use quicksilver::{ //! geom::{Rectangle, Vector}, //! graphics::{Color, Graphics}, -//! lifecycle::{run, EventStream, Settings, Window}, -//! Result, +//! input::{Input, Window}, +//! Result, Settings, run, //! }; //! //! fn main() { //! run( //! Settings { -//! size: Vector2 { x: 800.0, y: 600.0 }, //! title: "Square Example", //! ..Settings::default() //! }, @@ -45,7 +43,7 @@ //! ); //! } //! -//! async fn app(window: Window, mut gfx: Graphics, mut events: EventStream) -> Result<()> { +//! async fn app(window: Window, mut gfx: Graphics, mut input: Input) -> Result<()> { //! // Clear the screen to a blank, white color //! gfx.clear(Color::WHITE); //! // Paint a blue square with a red outline in the center of our screen @@ -56,7 +54,7 @@ //! // Send the data to be drawn //! gfx.present(&window)?; //! loop { -//! while let Some(_) = events.next_event().await {} +//! while let Some(_) = input.next_event().await {} //! } //! } //! ``` @@ -144,9 +142,9 @@ // Re-export every library that appears in the public API pub use blinds; -pub use golem; #[cfg(feature = "font")] pub use elefont; +pub use golem; pub use log; pub use mint; @@ -154,6 +152,11 @@ mod error; pub mod geom; pub mod graphics; +pub mod input; +#[deprecated( + since = "v0.4.0-alpha0.4", + note = "Please use input module and 'run' function instead" +)] pub mod lifecycle; #[cfg(feature = "saving")] pub mod saving { @@ -162,7 +165,9 @@ pub mod saving { } pub use crate::error::QuicksilverError; +mod run; mod timer; +pub use run::{run, Settings}; pub use timer::Timer; /// Load a file as a [`Future`] diff --git a/src/lifecycle.rs b/src/lifecycle.rs index bb7f5743..74c7a2f4 100644 --- a/src/lifecycle.rs +++ b/src/lifecycle.rs @@ -1,6 +1,7 @@ //! Manage events, input, and the window via the [`blinds`] library //! //! The [`run`] function is the entry point for all applications + use crate::geom::{Rectangle, Transform}; use crate::graphics::Graphics; use std::error::Error; @@ -14,6 +15,10 @@ pub use blinds::{ PointerId, Settings, Window, }; +#[deprecated( + since = "v0.4.0-alpha0.4", + note = "Please use 'run' from the crate root instead" +)] /// The entry point of a Quicksilver application /// /// It provides your application (represented by an async closure or function) with a [`Window`], @@ -25,7 +30,15 @@ where F: 'static + FnOnce(Window, Graphics, EventStream) -> T, { #[cfg(feature = "easy-log")] - set_logger(); + { + #[cfg(target_arch = "wasm32")] + web_logger::custom_init(web_logger::Config { + level: log::Level::Debug, + }); + #[cfg(not(target_arch = "wasm32"))] + simple_logger::init_with_level(log::Level::Debug) + .expect("A logger was already initialized"); + } let size = settings.size; let screen_region = Rectangle::new_sized(size); @@ -55,13 +68,3 @@ where } }); } - -#[cfg(feature = "easy-log")] -fn set_logger() { - #[cfg(target_arch = "wasm32")] - web_logger::custom_init(web_logger::Config { - level: log::Level::Debug, - }); - #[cfg(not(target_arch = "wasm32"))] - simple_logger::init_with_level(log::Level::Debug).expect("A logger was already initialized"); -} diff --git a/src/run.rs b/src/run.rs new file mode 100644 index 00000000..e72465f7 --- /dev/null +++ b/src/run.rs @@ -0,0 +1,139 @@ +use crate::geom::{Rectangle, Transform, Vector}; +use crate::graphics::Graphics; +use crate::input::Input; +use std::error::Error; +use std::future::Future; + +pub struct Settings { + /// The size of the window + pub size: Vector, + /// If the cursor should be visible over the application, or if the cursor should be hidden + pub cursor_icon: Option, + /// If the application should be fullscreen + pub fullscreen: bool, + /// The icon on the window or the favicon on the tab + pub icon_path: Option<&'static str>, + /// How many samples to do for MSAA + /// + /// By default it is None; if it is Some, it should be a non-zero power of two + /// + /// Does nothing on web currently + pub multisampling: Option, + /// Enable or disable vertical sync + /// + /// Does nothing on web + pub vsync: bool, + /// If the window can be resized by the user + /// + /// Does nothing on web + pub resizable: bool, + /// The title of your application + pub title: &'static str, + /// The severity level of logs to show + /// + /// By default, it is set to Warn + pub log_level: log::Level, + /// On desktop, whether to assume the assets are in the 'static/' directory + /// + /// By default, this is on for comfortable parity between stdweb and desktop. If you know you + /// don't need that, feel free to toggle this off + pub use_static_dir: bool, +} + +impl Default for Settings { + fn default() -> Settings { + Settings { + size: Vector::new(1024.0, 768.0), + cursor_icon: Some(blinds::CursorIcon::Default), + fullscreen: false, + icon_path: None, + multisampling: None, + vsync: true, + resizable: false, + title: "", + log_level: log::Level::Warn, + use_static_dir: false, + } + } +} + +/// The entry point of a Quicksilver application +/// +/// It provides your application (represented by an async closure or function) with a [`Window`], +/// [`Graphics`] context, and [`EventStream`]. +pub fn run(settings: Settings, app: F) -> ! +where + E: Into>, + T: 'static + Future>, + F: 'static + FnOnce(crate::lifecycle::Window, Graphics, Input) -> T, +{ + #[cfg(feature = "easy-log")] + set_logger(settings.log_level); + + let size = settings.size; + let screen_region = Rectangle::new_sized(size); + + blinds::run_gl((&settings).into(), move |window, ctx, events| { + #[cfg(not(target_arch = "wasm32"))] + { + if settings.use_static_dir && std::env::set_current_dir("static").is_err() { + log::warn!("Warning: no asset directory found. Please place all your assets inside a directory called 'static' so they can be loaded"); + log::warn!("Execution continuing, but any asset-not-found errors are likely due to the lack of a 'static' directory.") + } + } + + let ctx = golem::Context::from_glow(ctx).unwrap(); + let mut graphics = Graphics::new(ctx).unwrap(); + graphics.set_projection(Transform::orthographic(screen_region)); + + async { + match app(window, graphics, Input::new(events)).await { + Ok(()) => log::info!("Exited successfully"), + Err(err) => { + let err = err.into(); + log::error!("Error: {:?}", err); + panic!("{:?}", err); + } + } + } + }); +} + +#[cfg(feature = "easy-log")] +fn set_logger(level: log::Level) { + #[cfg(target_arch = "wasm32")] + web_logger::custom_init(web_logger::Config { level }); + #[cfg(not(target_arch = "wasm32"))] + simple_logger::init_with_level(level).expect("A logger was already initialized"); +} + +impl From<&Settings> for blinds::Settings { + fn from(settings: &Settings) -> blinds::Settings { + blinds::Settings { + size: settings.size.into(), + cursor_icon: settings.cursor_icon, + fullscreen: settings.fullscreen, + icon_path: settings.icon_path, + multisampling: settings.multisampling, + vsync: settings.vsync, + resizable: settings.resizable, + title: settings.title, + } + } +} + +impl From for Settings { + fn from(settings: blinds::Settings) -> Settings { + Settings { + size: settings.size.into(), + cursor_icon: settings.cursor_icon, + fullscreen: settings.fullscreen, + icon_path: settings.icon_path, + multisampling: settings.multisampling, + vsync: settings.vsync, + resizable: settings.resizable, + title: settings.title, + ..Settings::default() + } + } +} From f8bfc3e923f80eafff09c0af00fe756213aa73ce Mon Sep 17 00:00:00 2001 From: Ryan Goldstein Date: Mon, 27 Apr 2020 20:37:40 -0400 Subject: [PATCH 02/13] Address clippy lints --- src/lib.rs | 2 +- src/lifecycle.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 4b44c49f..bc26ae59 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -154,7 +154,7 @@ pub mod geom; pub mod graphics; pub mod input; #[deprecated( - since = "v0.4.0-alpha0.4", + since = "0.4.0-alpha0.4", note = "Please use input module and 'run' function instead" )] pub mod lifecycle; diff --git a/src/lifecycle.rs b/src/lifecycle.rs index 74c7a2f4..477bbf15 100644 --- a/src/lifecycle.rs +++ b/src/lifecycle.rs @@ -16,7 +16,7 @@ pub use blinds::{ }; #[deprecated( - since = "v0.4.0-alpha0.4", + since = "0.4.0-alpha0.4", note = "Please use 'run' from the crate root instead" )] /// The entry point of a Quicksilver application From 3d3c5f5bccb62e633f6ce36fb1ac7ac9eeaed53c Mon Sep 17 00:00:00 2001 From: Ryan Goldstein Date: Fri, 1 May 2020 21:39:08 -0400 Subject: [PATCH 03/13] Re-organize to move window out of input Also, re-export Graphics and Input at the top level. They are needed in literally every Quicksilver application, because you need to name the types to run 'app.' --- examples/00_window.rs | 6 +--- examples/01_square.rs | 5 ++- examples/02_image.rs | 5 ++- examples/03_rgb_triangle.rs | 5 ++- examples/04_render_to_texture.rs | 5 ++- examples/05_blending.rs | 5 ++- examples/06_timers.rs | 5 ++- examples/07_text.rs | 5 ++- src/graphics.rs | 5 +-- src/input.rs | 47 -------------------------- src/lib.rs | 5 +++ src/run.rs | 4 +-- src/window.rs | 58 ++++++++++++++++++++++++++++++++ 13 files changed, 83 insertions(+), 77 deletions(-) create mode 100644 src/window.rs diff --git a/examples/00_window.rs b/examples/00_window.rs index 157efe29..c61267c4 100644 --- a/examples/00_window.rs +++ b/examples/00_window.rs @@ -1,11 +1,7 @@ // Example 0: The Window // The simplest example: Do absolutely nothing other than just opening a window -use quicksilver::{ - graphics::Graphics, - input::{Input, Window}, - run, Result, Settings, -}; +use quicksilver::{run, Graphics, Input, Result, Settings, Window}; // main() serves as our kicking-off point, but it doesn't have our application logic // Actual logic goes in our app function, which is async diff --git a/examples/01_square.rs b/examples/01_square.rs index 22fec4ea..a9f3ac4c 100644 --- a/examples/01_square.rs +++ b/examples/01_square.rs @@ -2,9 +2,8 @@ // Open a window, and draw a colored square in it use quicksilver::{ geom::{Rectangle, Vector}, - graphics::{Color, Graphics}, - input::{Input, Window}, - run, Result, Settings, + graphics::Color, + run, Graphics, Input, Result, Settings, Window, }; fn main() { diff --git a/examples/02_image.rs b/examples/02_image.rs index 4b146f56..61f86389 100644 --- a/examples/02_image.rs +++ b/examples/02_image.rs @@ -2,9 +2,8 @@ // Draw an image to the screen use quicksilver::{ geom::{Rectangle, Vector}, - graphics::{Color, Graphics, Image}, - input::{Input, Window}, - run, Result, Settings, + graphics::{Color, Image}, + run, Graphics, Input, Result, Settings, Window, }; fn main() { diff --git a/examples/03_rgb_triangle.rs b/examples/03_rgb_triangle.rs index 4af94564..0a6d5770 100644 --- a/examples/03_rgb_triangle.rs +++ b/examples/03_rgb_triangle.rs @@ -2,9 +2,8 @@ // Open a window, and draw the standard GPU triangle use quicksilver::{ geom::Vector, - graphics::{Color, Element, Graphics, Mesh, Vertex}, - input::{Input, Window}, - run, Result, Settings, + graphics::{Color, Element, Mesh, Vertex}, + run, Graphics, Input, Result, Settings, Window, }; fn main() { diff --git a/examples/04_render_to_texture.rs b/examples/04_render_to_texture.rs index bd831158..ecaefd5b 100644 --- a/examples/04_render_to_texture.rs +++ b/examples/04_render_to_texture.rs @@ -2,9 +2,8 @@ // Render some data to an image, and draw that image to the screen use quicksilver::{ geom::{Circle, Rectangle, Vector}, - graphics::{Color, Graphics, Image, PixelFormat, Surface}, - input::{Input, Window}, - run, Result, Settings, + graphics::{Color, Image, PixelFormat, Surface}, + run, Graphics, Input, Result, Settings, Window, }; fn main() { diff --git a/examples/05_blending.rs b/examples/05_blending.rs index 1c4ec4aa..63f22e81 100644 --- a/examples/05_blending.rs +++ b/examples/05_blending.rs @@ -3,9 +3,8 @@ use quicksilver::{ geom::{Rectangle, Vector}, graphics::blend::{BlendChannel, BlendFactor, BlendFunction, BlendInput, BlendMode}, - graphics::{Color, Graphics, Image}, - input::{Input, Window}, - run, Result, Settings, + graphics::{Color, Image}, + run, Graphics, Input, Result, Settings, Window, }; fn main() { diff --git a/examples/06_timers.rs b/examples/06_timers.rs index 4b5d5e31..c15dfbe9 100644 --- a/examples/06_timers.rs +++ b/examples/06_timers.rs @@ -2,9 +2,8 @@ // Use timers to know when to draw and to have a consistent update cycle. use quicksilver::{ geom::{Rectangle, Vector}, - graphics::{Color, Graphics}, - input::{Input, Window}, - run, Result, Settings, Timer, + graphics::Color, + run, Graphics, Input, Result, Settings, Timer, Window, }; fn main() { diff --git a/examples/07_text.rs b/examples/07_text.rs index 73a4dc19..76f889f1 100644 --- a/examples/07_text.rs +++ b/examples/07_text.rs @@ -2,9 +2,8 @@ // Write some text on the screen use quicksilver::{ geom::Vector, - graphics::{Color, Graphics, VectorFont}, - input::{Input, Window}, - run, Result, Settings, + graphics::{Color, VectorFont}, + run, Graphics, Input, Result, Settings, Window, }; fn main() { diff --git a/src/graphics.rs b/src/graphics.rs index 04f91659..d0ba038a 100644 --- a/src/graphics.rs +++ b/src/graphics.rs @@ -29,6 +29,7 @@ pub use self::surface::Surface; pub use self::vertex::{Element, Vertex}; use crate::geom::*; +use crate::Window; use golem::*; use std::iter; use std::mem::size_of; @@ -525,7 +526,7 @@ impl Graphics { } /// Send the draw data to the GPU and paint it to the Window - pub fn present(&mut self, win: &blinds::Window) -> Result<(), QuicksilverError> { + pub fn present(&mut self, win: &Window) -> Result<(), QuicksilverError> { self.flush(None)?; win.present(); @@ -563,7 +564,7 @@ impl Graphics { } /// Set the viewport to cover the window, taking into account DPI - pub fn fit_to_window(&self, window: &blinds::Window) { + pub fn fit_to_window(&self, window: &Window) { let size = window.size(); let scale = window.scale_factor(); let width = size.x * scale; diff --git a/src/input.rs b/src/input.rs index b71ae685..f375c104 100644 --- a/src/input.rs +++ b/src/input.rs @@ -223,50 +223,3 @@ fn conv(ev: blinds::Event) -> Option { _ => return None, }) } - -pub struct Window(blinds::Window); - -impl Window { - /// Set the cursor icon to some value, or set it to invisible (None) - pub fn set_cursor_icon(&self, icon: Option) { - self.0.set_cursor_icon(icon); - } - - /// Get the size of the window in logical units - /// - /// On a high-dpi display, this doesn't correspond to physical pixels and must be multiplied by - /// [`scale`] when passing sizes to functions like `glViewport`. - /// - /// [`scale`]: Window::scale_factor - pub fn size(&self) -> Vector { - self.0.size().into() - } - - /// Set the size of the inside of the window in logical units - pub fn set_size(&self, size: Vector) { - self.0.set_size(size.into()); - } - - /// Set the title of the window or browser tab - pub fn set_title(&self, title: &str) { - self.0.set_title(title); - } - - /// Set if the window should be fullscreen or not - /// - /// On desktop, it will instantly become fullscreen (borderless windowed on Windows and Linux, - /// and fullscreen on macOS). On web, it will become fullscreen after the next user - /// interaction, due to browser API restrictions. - pub fn set_fullscreen(&self, fullscreen: bool) { - self.0.set_fullscreen(fullscreen); - } - - /// Draw the current frame to the screen - /// - /// If vsync is enabled, this will block until the frame is completed on desktop. On web, there - /// is no way to control vsync, or to manually control presentation, so this function is a - /// no-op. - pub fn present(&self) { - self.0.present(); - } -} diff --git a/src/lib.rs b/src/lib.rs index bc26ae59..081e7c62 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -167,8 +167,13 @@ pub use crate::error::QuicksilverError; mod run; mod timer; +mod window; pub use run::{run, Settings}; pub use timer::Timer; +pub use window::Window; + +pub use graphics::Graphics; +pub use input::Input; /// Load a file as a [`Future`] /// diff --git a/src/run.rs b/src/run.rs index e72465f7..04df66f3 100644 --- a/src/run.rs +++ b/src/run.rs @@ -65,7 +65,7 @@ pub fn run(settings: Settings, app: F) -> ! where E: Into>, T: 'static + Future>, - F: 'static + FnOnce(crate::lifecycle::Window, Graphics, Input) -> T, + F: 'static + FnOnce(crate::Window, Graphics, Input) -> T, { #[cfg(feature = "easy-log")] set_logger(settings.log_level); @@ -87,7 +87,7 @@ where graphics.set_projection(Transform::orthographic(screen_region)); async { - match app(window, graphics, Input::new(events)).await { + match app(crate::Window(window), graphics, Input::new(events)).await { Ok(()) => log::info!("Exited successfully"), Err(err) => { let err = err.into(); diff --git a/src/window.rs b/src/window.rs new file mode 100644 index 00000000..bf2fb970 --- /dev/null +++ b/src/window.rs @@ -0,0 +1,58 @@ +use crate::geom::Vector; +use blinds::CursorIcon; + +pub struct Window(pub(crate) blinds::Window); + +impl Window { + /// Set the cursor icon to some value, or set it to invisible (None) + pub fn set_cursor_icon(&self, icon: Option) { + self.0.set_cursor_icon(icon); + } + + /// Get the size of the window in logical units + /// + /// On a high-dpi display, this doesn't correspond to physical pixels and must be multiplied by + /// [`scale`] when passing sizes to functions like `glViewport`. + /// + /// [`scale`]: Window::scale_factor + pub fn size(&self) -> Vector { + self.0.size().into() + } + + /// Set the size of the inside of the window in logical units + pub fn set_size(&self, size: Vector) { + self.0.set_size(size.into()); + } + + /// Set the title of the window or browser tab + pub fn set_title(&self, title: &str) { + self.0.set_title(title); + } + + /// Set if the window should be fullscreen or not + /// + /// On desktop, it will instantly become fullscreen (borderless windowed on Windows and Linux, + /// and fullscreen on macOS). On web, it will become fullscreen after the next user + /// interaction, due to browser API restrictions. + pub fn set_fullscreen(&self, fullscreen: bool) { + self.0.set_fullscreen(fullscreen); + } + + /// The DPI scale factor of the window + /// + /// For a good example of DPI scale factors, see the [winit docs] on the subject + /// + /// [winit docs]: winit::dpi + pub fn scale_factor(&self) -> f32 { + self.0.scale_factor() + } + + /// Draw the current frame to the screen + /// + /// If vsync is enabled, this will block until the frame is completed on desktop. On web, there + /// is no way to control vsync, or to manually control presentation, so this function is a + /// no-op. + pub fn present(&self) { + self.0.present(); + } +} From 0ea965e5fbbf58088a4aac0873344a948dc3511b Mon Sep 17 00:00:00 2001 From: Ryan Goldstein Date: Fri, 1 May 2020 21:40:59 -0400 Subject: [PATCH 04/13] Add an example for keyboard/mouse input --- examples/08_input.rs | 50 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 50 insertions(+) create mode 100644 examples/08_input.rs diff --git a/examples/08_input.rs b/examples/08_input.rs new file mode 100644 index 00000000..5bd868d4 --- /dev/null +++ b/examples/08_input.rs @@ -0,0 +1,50 @@ +// Example 8: Input +// Respond to user keyboard and mouse input onscreen +use quicksilver::{ + geom::{Circle, Rectangle, Vector}, + graphics::Color, + input::Key, + run, Graphics, Input, Result, Settings, Window, +}; + +fn main() { + run( + Settings { + title: "Square Example", + ..Settings::default() + }, + app, + ); +} + +async fn app(window: Window, mut gfx: Graphics, mut input: Input) -> Result<()> { + // Keep track of the position of the square + let mut square_position = Vector::new(300, 300); + loop { + while let Some(_) = input.next_event().await {} + // Check the state of the keys, and move the square accordingly + const SPEED: f32 = 2.0; + if input.key_down(Key::A) { + square_position.x -= SPEED; + } + if input.key_down(Key::D) { + square_position.x += SPEED; + } + if input.key_down(Key::W) { + square_position.y -= SPEED; + } + if input.key_down(Key::S) { + square_position.y += SPEED; + } + + gfx.clear(Color::WHITE); + // Paint a blue square at the given position + gfx.fill_rect( + &Rectangle::new(square_position, Vector::new(64.0, 64.0)), + Color::BLUE, + ); + // Paint a red square at the mouse position + gfx.fill_circle(&Circle::new(input.mouse().location(), 32.0), Color::RED); + gfx.present(&window)?; + } +} From 3f9f6a2e93586fd074c408f43dfb607c007efa50 Mon Sep 17 00:00:00 2001 From: Ryan Goldstein Date: Sun, 3 May 2020 16:45:17 -0400 Subject: [PATCH 05/13] Add more feature markers --- src/input.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/input.rs b/src/input.rs index f375c104..da81d89a 100644 --- a/src/input.rs +++ b/src/input.rs @@ -82,6 +82,7 @@ impl Input { } } +#[cfg(feature = "event-cache")] pub struct PointerState { left: bool, right: bool, @@ -89,6 +90,7 @@ pub struct PointerState { location: Vector, } +#[cfg(feature = "event-cache")] impl PointerState { pub fn left(&self) -> bool { self.left @@ -107,6 +109,7 @@ impl PointerState { } } +#[cfg(feature = "event-cache")] impl From<&blinds::event_cache::PointerState> for PointerState { fn from(ps: &blinds::event_cache::PointerState) -> PointerState { PointerState { From 0ab9565c036260f9ea5290d98c60fd7aca190137 Mon Sep 17 00:00:00 2001 From: Ryan Goldstein Date: Mon, 4 May 2020 22:28:42 -0400 Subject: [PATCH 06/13] Update changelog --- CHANGES.md | 4 +++ src/lib.rs | 5 ---- src/lifecycle.rs | 70 ------------------------------------------------ 3 files changed, 4 insertions(+), 75 deletions(-) delete mode 100644 src/lifecycle.rs diff --git a/CHANGES.md b/CHANGES.md index a35311df..3f674778 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -2,6 +2,10 @@ ## WIP - Fix compile issues with font-related features +- [BREAKING] Replace 'lifecycle' module with 'input' module: + - [BREAKING] Rename `EventStream` to `Input` + - Integrate the input state cache directly into `Input` + - [BREAKING] The `blinds::Window` struct and the `Event` enums are now wrapped with methods that use `quicksilver::geom::Vector` instead of `mint::Vector2` ## v0.4.0-alpha0.3 - Update `golem` to `v0.1.1` to fix non-power-of-2 textures diff --git a/src/lib.rs b/src/lib.rs index 081e7c62..f0efbf37 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -153,11 +153,6 @@ mod error; pub mod geom; pub mod graphics; pub mod input; -#[deprecated( - since = "0.4.0-alpha0.4", - note = "Please use input module and 'run' function instead" -)] -pub mod lifecycle; #[cfg(feature = "saving")] pub mod saving { //! A module to manage cross-platform save data via the [`gestalt`] library diff --git a/src/lifecycle.rs b/src/lifecycle.rs deleted file mode 100644 index 477bbf15..00000000 --- a/src/lifecycle.rs +++ /dev/null @@ -1,70 +0,0 @@ -//! Manage events, input, and the window via the [`blinds`] library -//! -//! The [`run`] function is the entry point for all applications - -use crate::geom::{Rectangle, Transform}; -use crate::graphics::Graphics; -use std::error::Error; -use std::future::Future; - -pub use blinds::event; -#[cfg(feature = "event-cache")] -pub use blinds::{CachedEventStream, EventCache}; -pub use blinds::{ - CursorIcon, Event, EventStream, GamepadAxis, GamepadButton, GamepadId, Key, MouseButton, - PointerId, Settings, Window, -}; - -#[deprecated( - since = "0.4.0-alpha0.4", - note = "Please use 'run' from the crate root instead" -)] -/// The entry point of a Quicksilver application -/// -/// It provides your application (represented by an async closure or function) with a [`Window`], -/// [`Graphics`] context, and [`EventStream`]. -pub fn run(settings: Settings, app: F) -> ! -where - E: Into>, - T: 'static + Future>, - F: 'static + FnOnce(Window, Graphics, EventStream) -> T, -{ - #[cfg(feature = "easy-log")] - { - #[cfg(target_arch = "wasm32")] - web_logger::custom_init(web_logger::Config { - level: log::Level::Debug, - }); - #[cfg(not(target_arch = "wasm32"))] - simple_logger::init_with_level(log::Level::Debug) - .expect("A logger was already initialized"); - } - - let size = settings.size; - let screen_region = Rectangle::new_sized(size); - - blinds::run_gl(settings, move |window, ctx, events| { - #[cfg(not(target_arch = "wasm32"))] - { - if std::env::set_current_dir("static").is_err() { - log::warn!("Warning: no asset directory found. Please place all your assets inside a directory called 'static' so they can be loaded"); - log::warn!("Execution continuing, but any asset-not-found errors are likely due to the lack of a 'static' directory.") - } - } - - let ctx = golem::Context::from_glow(ctx).unwrap(); - let mut graphics = Graphics::new(ctx).unwrap(); - graphics.set_projection(Transform::orthographic(screen_region)); - - async { - match app(window, graphics, events).await { - Ok(()) => log::info!("Exited successfully"), - Err(err) => { - let err = err.into(); - log::error!("Error: {:?}", err); - panic!("{:?}", err); - } - } - } - }); -} From 33932bc9546603866c0a98de654ec9b26a1bb2b6 Mon Sep 17 00:00:00 2001 From: Ryan Goldstein Date: Mon, 4 May 2020 22:47:41 -0400 Subject: [PATCH 07/13] Fix window title --- examples/08_input.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/08_input.rs b/examples/08_input.rs index 5bd868d4..2fcd1fc7 100644 --- a/examples/08_input.rs +++ b/examples/08_input.rs @@ -10,7 +10,7 @@ use quicksilver::{ fn main() { run( Settings { - title: "Square Example", + title: "Input Example", ..Settings::default() }, app, From 830f6621068572e1e6940b17c1da52c093e8729a Mon Sep 17 00:00:00 2001 From: Ryan Goldstein Date: Mon, 4 May 2020 22:47:53 -0400 Subject: [PATCH 08/13] Use static asset directory by default --- src/run.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/run.rs b/src/run.rs index 04df66f3..39d79f8b 100644 --- a/src/run.rs +++ b/src/run.rs @@ -52,7 +52,7 @@ impl Default for Settings { resizable: false, title: "", log_level: log::Level::Warn, - use_static_dir: false, + use_static_dir: true, } } } From 5fae1eac77e436d2f8629b50be1272ad5d84a1eb Mon Sep 17 00:00:00 2001 From: Ryan Goldstein Date: Mon, 4 May 2020 22:49:58 -0400 Subject: [PATCH 09/13] Add an events example --- examples/09_events.rs | 65 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 65 insertions(+) create mode 100644 examples/09_events.rs diff --git a/examples/09_events.rs b/examples/09_events.rs new file mode 100644 index 00000000..2d3066e5 --- /dev/null +++ b/examples/09_events.rs @@ -0,0 +1,65 @@ +// Example 9: Events +// Draw user-typed text to the screen via events +use quicksilver::{ + geom::Vector, + graphics::{Color, VectorFont}, + input::{Event, Key}, + run, Graphics, Input, Result, Settings, Window, +}; + +fn main() { + run( + Settings { + title: "Event Example", + ..Settings::default() + }, + app, + ); +} + +async fn app(window: Window, mut gfx: Graphics, mut input: Input) -> Result<()> { + // We'll need a font to render text and a string to store it + let ttf = VectorFont::load("font.ttf").await?; + let mut font = ttf.to_renderer(&gfx, 36.0)?; + let mut string = String::new(); + // Instead of looping forever, terminate on a given input + let mut running = true; + while running { + while let Some(event) = input.next_event().await { + match event { + Event::KeyboardInput(key) if key.is_down() => { + if key.key() == Key::Escape { + // If the user strikes escape, end the program + running = false; + } else if key.key() == Key::Back { + // If the user strikes Backspace, remove a character from our string + string.pop(); + } + } + Event::ReceivedCharacter(c) => { + // If the user types a printable character, put it into the string + let chr = c.character(); + if !chr.is_control() { + string.push(chr); + } + } + _ => (), + } + } + + // Draw our string to the screen, wrapping at word boundaries + gfx.clear(Color::WHITE); + font.draw_wrapping( + &mut gfx, + &string, + Some(500.0), + Color::BLACK, + Vector::new(100.0, 100.0), + )?; + gfx.present(&window)?; + } + + // Unlike all our earlier examples, our game loop might end early (e.g. before the user closes + // the window.) We have to return Ok(()) because of this + Ok(()) +} From dcdfc2c20b477a54f91d7ce42cdaba50d64af195 Mon Sep 17 00:00:00 2001 From: Ryan Goldstein Date: Tue, 5 May 2020 12:36:16 -0400 Subject: [PATCH 10/13] Fix imports in loading_screen --- examples/loading_screen.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/examples/loading_screen.rs b/examples/loading_screen.rs index 6c73228b..bc783852 100644 --- a/examples/loading_screen.rs +++ b/examples/loading_screen.rs @@ -11,8 +11,7 @@ use std::time::Duration; use quicksilver::{ geom::{Rectangle, Vector}, graphics::{Color, Graphics}, - input::{Input, Window}, - run, Result, Settings, + run, Input, Result, Settings, Window, }; fn main() { From 163b6e27b8a722e3183ec562240a8eb107f1637d Mon Sep 17 00:00:00 2001 From: Ryan Goldstein Date: Tue, 5 May 2020 15:07:33 -0400 Subject: [PATCH 11/13] Add basic docs to window and settings --- src/run.rs | 1 + src/window.rs | 1 + 2 files changed, 2 insertions(+) diff --git a/src/run.rs b/src/run.rs index 39d79f8b..1afc75ca 100644 --- a/src/run.rs +++ b/src/run.rs @@ -4,6 +4,7 @@ use crate::input::Input; use std::error::Error; use std::future::Future; +/// Initial window and behavior options pub struct Settings { /// The size of the window pub size: Vector, diff --git a/src/window.rs b/src/window.rs index bf2fb970..89ad34c1 100644 --- a/src/window.rs +++ b/src/window.rs @@ -1,6 +1,7 @@ use crate::geom::Vector; use blinds::CursorIcon; +/// The window on the user's desktop or in the browser tab pub struct Window(pub(crate) blinds::Window); impl Window { From 3acf2011650d928a7e610e5cc122ab8884d22bbe Mon Sep 17 00:00:00 2001 From: Ryan Goldstein Date: Tue, 5 May 2020 15:25:44 -0400 Subject: [PATCH 12/13] Update docs and fix links --- src/graphics.rs | 6 +++--- src/input.rs | 28 +++++++++++++++++++++++++--- src/lib.rs | 1 + src/run.rs | 8 ++++++-- src/window.rs | 8 ++++++-- 5 files changed, 41 insertions(+), 10 deletions(-) diff --git a/src/graphics.rs b/src/graphics.rs index 43cce528..cd37695e 100644 --- a/src/graphics.rs +++ b/src/graphics.rs @@ -7,7 +7,7 @@ //! //! For loading and drawing images, to the screen, use [`Image`]. //! -//! [`run`]: crate::lifecycle::run +//! [`run`]: crate::run::run use crate::QuicksilverError; @@ -546,8 +546,8 @@ impl Graphics { /// The units given are physical units, not logical units. As such when using [`Window::size`], /// be sure to multiply by [`Window::scale_factor`]. /// - /// [`Window::size`]: crate::lifecycle::Window::size - /// [`Window::scale_factor`]: crate::lifecycle::Window::scale_factor + /// [`Window::size`]: crate::Window::size + /// [`Window::scale_factor`]: crate::Window::scale_factor pub fn set_viewport(&self, x: u32, y: u32, width: u32, height: u32) { self.ctx.set_viewport(x, y, width, height); } diff --git a/src/input.rs b/src/input.rs index da81d89a..284e47d3 100644 --- a/src/input.rs +++ b/src/input.rs @@ -1,4 +1,16 @@ -//! Read events / input state and manage the window +//! Read events / input state +//! +//! The main struct for this module is [`Input`], which is provided by the [`run`] method to your +//! app. [`Input`] allows you to read events from the user, which can range from [`window resizes`] +//! to [`key presses`]. +//! +//! There is also an optional feature (enabled by default) to cache input for user convenience. +//! This allows you to write quick expressions like `if input.key_down(Key::W)` rather than having +//! to write code to handle the event when it comes in. +//! +//! [`run`]: crate::run::run +//! [`window resizes`]: ResizedEvent +//! [`key presses`]: KeyboardEvent use crate::geom::Vector; @@ -10,10 +22,12 @@ pub use blinds::event::{ }; #[cfg(feature = "event-cache")] use blinds::event_cache::EventCache; +/// The button and axis values of a gamepad #[cfg(feature = "event-cache")] pub use blinds::event_cache::GamepadState; -pub use blinds::{CursorIcon, GamepadAxis, GamepadButton, GamepadId, Key, MouseButton, PointerId}; +pub use blinds::{GamepadAxis, GamepadButton, GamepadId, Key, MouseButton, PointerId}; +/// The source of events and input device state pub struct Input { source: blinds::EventStream, #[cfg(feature = "event-cache")] @@ -29,6 +43,13 @@ impl Input { } } + /// Retrieve the next event from the environment, or wait until there is one + /// + /// If an event has occured since this method was last called, it will be return as + /// `Some(event)`. Once all events have been handled, `None` will be returned. At this point you + /// should run any update or drawing logic in your app. When this method is called after it + /// returns `None`, it will yield control back to the environment until your app should run + /// again. pub async fn next_event(&mut self) -> Option { while let Some(ev) = self.source.next_event().await { #[cfg(feature = "event-cache")] @@ -82,6 +103,7 @@ impl Input { } } +/// The buttons and location of a given pointer #[cfg(feature = "event-cache")] pub struct PointerState { left: bool, @@ -183,7 +205,7 @@ impl ResizedEvent { #[derive(Clone, Debug)] /// See [`Event::PointerMoved`] /// -/// [`Event::PointerMoved`]: crate::event::Event::PointerMoved +/// [`Event::PointerMoved`]: crate::input::Event::PointerMoved pub struct PointerMovedEvent { id: PointerId, location: Vector, diff --git a/src/lib.rs b/src/lib.rs index f0efbf37..dac05194 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -166,6 +166,7 @@ mod window; pub use run::{run, Settings}; pub use timer::Timer; pub use window::Window; +pub use blinds::CursorIcon; pub use graphics::Graphics; pub use input::Input; diff --git a/src/run.rs b/src/run.rs index 1afc75ca..330d9c59 100644 --- a/src/run.rs +++ b/src/run.rs @@ -9,7 +9,7 @@ pub struct Settings { /// The size of the window pub size: Vector, /// If the cursor should be visible over the application, or if the cursor should be hidden - pub cursor_icon: Option, + pub cursor_icon: Option, /// If the application should be fullscreen pub fullscreen: bool, /// The icon on the window or the favicon on the tab @@ -61,7 +61,11 @@ impl Default for Settings { /// The entry point of a Quicksilver application /// /// It provides your application (represented by an async closure or function) with a [`Window`], -/// [`Graphics`] context, and [`EventStream`]. +/// [`Graphics`] context, and [`Input`]. +/// +/// [`Graphics`]: crate::Graphics +/// [`Window`]: crate::Window +/// [`Input`]: crate::Input pub fn run(settings: Settings, app: F) -> ! where E: Into>, diff --git a/src/window.rs b/src/window.rs index 89ad34c1..1090052f 100644 --- a/src/window.rs +++ b/src/window.rs @@ -41,9 +41,13 @@ impl Window { /// The DPI scale factor of the window /// - /// For a good example of DPI scale factors, see the [winit docs] on the subject + /// Mostly, this isn't important to you. Some computer screens have more "physical" pixels than + /// "logical" pixels, allowing them to draw sharper-looking images. Quicksilver abstracts this + /// away. However, if you are manually [`setting the viewport`], you need to take this into + /// account. /// - /// [winit docs]: winit::dpi + /// + /// [`setting the viewport`]: crate::Graphics::set_viewport pub fn scale_factor(&self) -> f32 { self.0.scale_factor() } From 1aac7765647699ad17f435f86400494a0c0132f1 Mon Sep 17 00:00:00 2001 From: Ryan Goldstein Date: Tue, 5 May 2020 15:26:29 -0400 Subject: [PATCH 13/13] Fmt --- src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib.rs b/src/lib.rs index dac05194..611aecd5 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -163,10 +163,10 @@ pub use crate::error::QuicksilverError; mod run; mod timer; mod window; +pub use blinds::CursorIcon; pub use run::{run, Settings}; pub use timer::Timer; pub use window::Window; -pub use blinds::CursorIcon; pub use graphics::Graphics; pub use input::Input;