-
Notifications
You must be signed in to change notification settings - Fork 1.1k
Make "fill" module used by examples more useful + add an input tester example #4219
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
8a0cafa
e3aa3e7
bbe81af
d2d26e3
c0308c4
abe8185
7d5a5a3
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,6 +1,8 @@ | ||
| Cargo.lock | ||
| target/ | ||
| rls/ | ||
| # pkg: Often used for temporary wasm debug builds | ||
| pkg/ | ||
| .vscode/ | ||
| *~ | ||
| #*# | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -85,6 +85,7 @@ smol_str = "0.3" | |
| tracing = { version = "0.1.40", default-features = false } | ||
|
|
||
| [dev-dependencies] | ||
| font8x8 = "0.3.1" | ||
| image = { version = "0.25.0", default-features = false, features = ["png"] } | ||
| tracing = { version = "0.1.40", default-features = false, features = ["log"] } | ||
| tracing-subscriber = { version = "0.3.18", features = ["env-filter"] } | ||
|
|
@@ -312,6 +313,10 @@ xkbcommon-dl = "0.4.2" | |
| orbclient = { version = "0.3.47", default-features = false } | ||
| redox_syscall = "0.5.7" | ||
|
|
||
| # We want the web-time crate on non-web clients too, for examples (hence dev-dependencies) | ||
| [target.'cfg(not(target_family = "wasm"))'.dev-dependencies] | ||
| web-time = "1" | ||
|
Comment on lines
+317
to
+318
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nit: Slight preference for doing: #[cfg(target_family = "wasm")]
use web_time::Instant;
#[cfg(not(target_family = "wasm"))]
use std::time::Instant;In example code too. |
||
|
|
||
| # Web | ||
| [target.'cfg(target_family = "wasm")'.dependencies] | ||
| js-sys = "0.3.70" | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,17 @@ | ||
| <!DOCTYPE html> | ||
| <html> | ||
| <head> | ||
| <meta charset="utf-8"> | ||
| <meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no"> | ||
| <title>Winit Tablet Tester</title> | ||
| <style> | ||
| canvas { width: 360px; height: 250px; } * { margin: 0; padding: 0; } | ||
| </style> | ||
| <script type="module"> | ||
| import start from '../pkg/tester.js'; | ||
| start(); | ||
| </script> | ||
| </head> | ||
| <body> | ||
| </body> | ||
| </html> | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nit: Missing trailing newline |
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,313 @@ | ||
| //! Basic winit interactivity example. | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nit: Maybe call the example something like There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Also, you can group stuff in a folder, like |
||
|
|
||
| //! Web compilation and testing example: | ||
| //! cargo build --release --target wasm32-unknown-unknown --example tester && \ | ||
| //! wasm-bindgen target/wasm32-unknown-unknown/release/examples/tester.wasm \ | ||
| //! --out-dir pkg --target web | ||
| //! python3 -m http.server | ||
| //! Then navigate to http://localhost:8000/examples/tester.html | ||
|
|
||
| use std::error::Error; | ||
|
|
||
| use font8x8::legacy::BASIC_LEGACY; | ||
| use winit::application::ApplicationHandler; | ||
| use winit::event::{ButtonSource, MouseButton, PointerSource, WindowEvent}; | ||
| use winit::event_loop::{ActiveEventLoop, EventLoop}; | ||
| #[cfg(web_platform)] | ||
| use winit::platform::web::WindowAttributesExtWeb; | ||
| use winit::window::{Window, WindowAttributes, WindowId}; | ||
|
|
||
| fn draw_char(frame: &mut [u32], width: usize, x: u32, y: u32, ch: char, fg: u32, bg: u32) { | ||
| let x: usize = x.try_into().unwrap(); | ||
| let y: usize = y.try_into().unwrap(); | ||
| let glyph = BASIC_LEGACY.get(ch as usize).unwrap_or(&BASIC_LEGACY[' ' as usize]); | ||
| for (row, byte) in glyph.iter().enumerate() { | ||
| let ypart = (y + row) * width; | ||
| for col in 0..8 { | ||
| let i = ypart + (x + col); | ||
| if i < frame.len() && byte & (1 << col) != 0 { | ||
| frame[i] = fg; | ||
| } else { | ||
| frame[i] = bg; | ||
| } | ||
| } | ||
| } | ||
| } | ||
|
|
||
| fn draw_text( | ||
| frame: &mut [u32], | ||
| width: usize, | ||
| mut x: u32, | ||
| mut y: u32, | ||
| text: &str, | ||
| fg: u32, | ||
| bg: u32, | ||
| ) -> (u32, u32) { | ||
| let x_init = x; | ||
| let mut max_x = x; | ||
| let mut max_y = y; | ||
| for ch in text.chars() { | ||
| if ch == '\n' { | ||
| x = x_init; | ||
| y += 8; | ||
| max_y = max_y.max(y); | ||
| } else { | ||
| draw_char(frame, width, x, y, ch, fg, bg); | ||
| x += 8; | ||
| max_x = max_x.max(x); | ||
| } | ||
| } | ||
| (max_x + 8, max_y + 8) | ||
| } | ||
|
|
||
| #[path = "util/fill.rs"] | ||
| mod fill; | ||
| #[path = "util/tracing.rs"] | ||
| mod tracing; | ||
|
|
||
| #[derive(Default, Debug)] | ||
| struct App { | ||
| window: Option<Box<dyn Window>>, | ||
| old_posx: f32, | ||
| old_posy: f32, | ||
| posx: f32, | ||
| posy: f32, | ||
| has_pressure: Option<f32>, | ||
| drawing: bool, | ||
| last_draw: Vec<[u32; 4]>, | ||
| } | ||
|
|
||
| impl ApplicationHandler for App { | ||
| fn can_create_surfaces(&mut self, event_loop: &dyn ActiveEventLoop) { | ||
| #[cfg(not(web_platform))] | ||
| let window_attributes = WindowAttributes::default(); | ||
| #[cfg(web_platform)] | ||
| let window_attributes = WindowAttributes::default().with_append(true); | ||
| self.window = match event_loop.create_window(window_attributes) { | ||
| Ok(window) => Some(window), | ||
| Err(err) => { | ||
| eprintln!("error creating window: {err}"); | ||
| event_loop.exit(); | ||
| return; | ||
| }, | ||
| }; | ||
|
|
||
| let window = self.window.as_ref().unwrap(); | ||
| window.pre_present_notify(); | ||
| fill::fill_window_with_color(&**window, 0xff181818); | ||
| window.request_redraw(); | ||
| } | ||
|
|
||
| fn window_event(&mut self, event_loop: &dyn ActiveEventLoop, _: WindowId, event: WindowEvent) { | ||
| match event { | ||
| WindowEvent::CloseRequested => { | ||
| println!("Close was requested; stopping"); | ||
| event_loop.exit(); | ||
| }, | ||
| WindowEvent::SurfaceResized(_) => { | ||
| let window = self.window.as_ref().expect("resize event without a window"); | ||
| window.pre_present_notify(); | ||
| fill::fill_window_with_color(&**window, 0xff181818); | ||
| window.request_redraw(); | ||
| }, | ||
| WindowEvent::PointerButton { primary, position, state, button, .. } => { | ||
| let window = self.window.as_ref().expect("resize event without a window"); | ||
| let scale = window.scale_factor(); | ||
| match (primary, button) { | ||
| (true, ButtonSource::Mouse(MouseButton::Left)) => { | ||
| self.posx = position.to_logical::<f32>(scale).x; | ||
| self.posy = position.to_logical::<f32>(scale).y; | ||
| self.old_posx = position.to_logical::<f32>(scale).x; | ||
| self.old_posy = position.to_logical::<f32>(scale).y; | ||
| self.drawing = state == winit::event::ElementState::Pressed; | ||
| self.has_pressure = None; | ||
| }, | ||
| (true, ButtonSource::Touch { force, .. }) => { | ||
| self.posx = position.to_logical::<f32>(scale).x; | ||
| self.posy = position.to_logical::<f32>(scale).y; | ||
| self.old_posx = position.to_logical::<f32>(scale).x; | ||
| self.old_posy = position.to_logical::<f32>(scale).y; | ||
| self.drawing = state == winit::event::ElementState::Pressed; | ||
| self.has_pressure = force.map(|x| x.normalized() as f32); | ||
| }, | ||
| _ => { | ||
| let window = self.window.as_ref().unwrap(); | ||
| window.pre_present_notify(); | ||
| fill::fill_window_with_color(&**window, 0xff181818); | ||
| window.request_redraw(); | ||
| }, | ||
| } | ||
| }, | ||
| WindowEvent::PointerMoved { position, source, .. } => { | ||
| let window = self.window.as_ref().expect("resize event without a window"); | ||
| let scale = window.scale_factor(); | ||
| if self.drawing { | ||
| self.posx = position.to_logical::<f32>(scale).x; | ||
| self.posy = position.to_logical::<f32>(scale).y; | ||
| } | ||
| if let PointerSource::Touch { force, .. } = source { | ||
| if self.has_pressure.is_some() { | ||
| self.has_pressure = force.map(|x| x.normalized() as f32); | ||
| } | ||
| } | ||
| }, | ||
| WindowEvent::RedrawRequested => { | ||
| // Redraw the application. | ||
| // | ||
| // It's preferable for applications that do not render continuously to render in | ||
| // this event rather than in AboutToWait, since rendering in here allows | ||
| // the program to gracefully handle redraws requested by the OS. | ||
|
|
||
| let window = self.window.as_ref().expect("redraw request without a window"); | ||
|
|
||
| // Notify that you're about to draw. | ||
| window.pre_present_notify(); | ||
|
|
||
| let mut rects = vec![]; | ||
| let rects_ref = &mut rects; | ||
| // Draw. | ||
| fill::fill_window_with_fn(&**window, |frame, stride, scale, frame_w, frame_h| { | ||
| // Clear the top left 50x50 rect, we'll put an animation there. | ||
| for y in 0..((50.0 * scale) as usize).min(frame_h as usize) { | ||
| for x in 0..((50.0 * scale) as usize).min(frame_w as usize) { | ||
| frame[y * stride + x] = 0xff181818; | ||
| } | ||
| } | ||
|
|
||
| let extent = draw_text( | ||
| frame, | ||
| stride, | ||
| (20.0 * scale) as u32, | ||
| (50.0 * scale) as u32, | ||
| &format!( | ||
| "Input tester.\nLeft click to draw, right click to clear.\nx: {}\ny: \ | ||
| {}\npressure: {:?}", | ||
| self.posx, self.posy, self.has_pressure | ||
| ), | ||
| 0xffffffff, | ||
| 0xff181818, | ||
| ); | ||
| let rect1 = [20, 50, extent.0 - 20, extent.1 - 50]; | ||
|
|
||
| let mut draw_line = | ||
| |xpos: f32, ypos: f32, xoff: f32, yoff: f32, thickness: u32| { | ||
| if xoff == 0.0 && yoff == 0.0 { | ||
| return; | ||
| } | ||
| let len = (xoff * xoff + yoff * yoff).sqrt(); | ||
| let norm = 1.0 / xoff.abs().max(yoff.abs()); | ||
| let xo_small = xoff * norm; | ||
| let yo_small = yoff * norm; | ||
| let spacing = (thickness as f32 * 0.25).max(1.0); | ||
| let antinorm = | ||
| 1.0 / (xo_small * xo_small + yo_small * yo_small).sqrt() / spacing; | ||
| for i in 0..=(len * antinorm) as usize { | ||
| let i = i as f32; | ||
|
|
||
| for _y in -(thickness as i32) / 2..(thickness as i32 + 1) / 2 { | ||
| for _x in -(thickness as i32) / 2..(thickness as i32 + 1) / 2 { | ||
|
Comment on lines
+207
to
+208
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nit: I prefer shadowing in these situations (use |
||
| let x = (xpos + _x as f32 + xo_small * i * spacing) | ||
| * scale as f32; | ||
| let y = (ypos + _y as f32 + yo_small * i * spacing) | ||
| * scale as f32; | ||
|
|
||
| let xpart = x.clamp(0.0, frame_w as f32 - 1.0) as usize; | ||
| let ypart = | ||
| y.clamp(0.0, frame_h as f32 - 1.0) as usize * stride; | ||
| frame[ypart + xpart] = 0xffffffff; | ||
| } | ||
| } | ||
| } | ||
| }; | ||
|
|
||
| // Animation. | ||
| let x = 25.0; | ||
| let y = 25.0; | ||
|
|
||
| static START: std::sync::OnceLock<web_time::Instant> = | ||
| std::sync::OnceLock::new(); | ||
| let start = START.get_or_init(web_time::Instant::now); | ||
| let time = web_time::Instant::now().duration_since(*start).as_secs_f32(); | ||
|
|
||
| let xo = (time).sin() * 25.0; | ||
| let yo = (time).cos() * 25.0; | ||
| draw_line(x, y, xo, yo, 1); | ||
| draw_line(x, y, -xo, -yo, 1); | ||
|
|
||
| // Any new lines to draw from input? | ||
| let diameter = self.has_pressure.map(|x| x * 10.0).unwrap_or(0.0).ceil(); | ||
| if self.drawing { | ||
| draw_line( | ||
| self.old_posx, | ||
| self.old_posy, | ||
| self.posx - self.old_posx, | ||
| self.posy - self.old_posy, | ||
| diameter as u32 + 1, | ||
| ); | ||
| } | ||
| let radius = (diameter * 0.5).ceil(); | ||
|
|
||
| let x = (self.posx.min(self.old_posx) - radius).max(0.0); | ||
| let y = (self.posy.min(self.old_posy) - radius).max(0.0); | ||
| let w = | ||
| (self.posx.max(self.old_posx) - x + 1.0 + radius).min(frame_w as f32 - x); | ||
| let h = | ||
| (self.posy.max(self.old_posy) - y + 1.0 + radius).min(frame_h as f32 - y); | ||
|
|
||
| *rects_ref = | ||
| vec![[0, 0, 50, 50], rect1, [x as u32, y as u32, w as u32, h as u32]]; | ||
| let mut damaged = rects_ref.clone(); | ||
| damaged.extend_from_slice(&self.last_draw); | ||
| damaged | ||
| }); | ||
|
|
||
| self.old_posx = self.posx; | ||
| self.old_posy = self.posy; | ||
|
|
||
| self.last_draw = rects; | ||
|
|
||
| // For contiguous redraw loop you can request a redraw from here. | ||
| window.request_redraw(); | ||
| // Don't run hundreds of thousands of times per second even if the platform asks. | ||
| // (Can't sleep on web, so gated off there. Browsers won't ask for too much.) | ||
| #[cfg(not(web_platform))] | ||
| { | ||
| std::thread::sleep(std::time::Duration::from_millis(1)); | ||
| } | ||
|
Comment on lines
+271
to
+276
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The fact that you currently need this kind of stuff is exactly why this example is so useful! |
||
| }, | ||
| _ => (), | ||
| } | ||
| } | ||
| } | ||
|
|
||
| fn main() -> Result<(), Box<dyn Error>> { | ||
| #[cfg(web_platform)] | ||
| console_error_panic_hook::set_once(); | ||
|
|
||
| tracing::init(); | ||
|
|
||
| let event_loop = EventLoop::new()?; | ||
|
|
||
| // For alternative loop run options see `pump_events` and `run_on_demand` examples. | ||
| event_loop.run_app(App::default())?; | ||
|
|
||
| Ok(()) | ||
| } | ||
|
|
||
| #[cfg(web_platform)] | ||
| use wasm_bindgen::prelude::wasm_bindgen; | ||
| #[cfg(web_platform)] | ||
| #[wasm_bindgen(start)] | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nit: Is this still required? I thought that wasm-bindgen supported There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I had trouble getting it to work properly with a normal |
||
| pub fn start() -> Result<(), wasm_bindgen::JsValue> { | ||
| #[cfg(web_platform)] | ||
| console_error_panic_hook::set_once(); | ||
|
|
||
| tracing::init(); | ||
|
|
||
| let event_loop = EventLoop::new().unwrap(); | ||
|
|
||
| // For alternative loop run options see `pump_events` and `run_on_demand` examples. | ||
| event_loop.run_app(App::default()).unwrap(); | ||
|
|
||
| Ok(()) | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: Seems like this hasn't been updated in 4 years. Maybe there's something a bit more maintained we can use?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I considered it, but because it's "just" a font, I figured it probably wasn't that important that it's unmaintained 🤔 Maybe the decision between different debugging/embedded bitmap font crates could better be made based off of license or looks?
Actually, I think I found a potential snafu: the font data font8x8 is based on says it's public domain, but the maintainer/author seems to located in Germany, and there isn't a workaround license like CC0 to make up for the fact that "speech act" public domain dedications don't work under German law? This is almost certainly not a problem, but it COULD be one, hypothetically. Maybe I'll open an issue on it. https://github.com/dhepper/font8x8