Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitignore
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/
*~
#*#
Expand Down
5 changes: 5 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@ smol_str = "0.3"
tracing = { version = "0.1.40", default-features = false }

[dev-dependencies]
font8x8 = "0.3.1"
Copy link
Member

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?

Copy link
Author

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

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"] }
Expand Down Expand Up @@ -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
Copy link
Member

Choose a reason for hiding this comment

The 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"
Expand Down
17 changes: 17 additions & 0 deletions examples/tester.html
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>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: Missing trailing newline

313 changes: 313 additions & 0 deletions examples/tester.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,313 @@
//! Basic winit interactivity example.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: Maybe call the example something like interactivity.rs or drawing_app.rs?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, you can group stuff in a folder, like examples/drawing_app/main.rs + examples/drawing_app/index.html


//! 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
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: I prefer shadowing in these situations (use y instead of _y).

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
Copy link
Member

Choose a reason for hiding this comment

The 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)]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: Is this still required? I thought that wasm-bindgen supported fn main nowadays?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I had trouble getting it to work properly with a normal fn main, don't know why.

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(())
}
Loading
Loading