Compare commits

...
5 Commits
Author SHA1 Message Date
haraldandClaude Opus 5.5 b5d9d72e79 chore(release): bump version to 0.35.0
Build & Publish Release / Build Packages & Publish to Gitea (push) Successful in 7m17s
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-27 08:52:15 +01:00
haraldandClaude Opus 5.5 955eaa4095 perf: simplify stroke outlines before PDF export
Each stroke mesh has about 16 points per pen sample, and every one of them
went into the PDF as a polygon point: a full handwritten page (1650 strokes)
took 2.9 s to export, and PNG export and copying a selection as an image
took as long, because both go through the PDF. The outline is now simplified
with a closed Douglas-Peucker that measures against segments, at most 0.01 mm
from the drawn outline. The same page now takes 0.44 s (PNG 0.55 s), the PDF
shrinks from 10.7 MB to 1.4 MB, and the rendered PNG differs only in edge
anti-aliasing (max 21/255). INK_NOTES_EXACT_OUTLINES=1 exports every point
as before.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-27 08:45:17 +01:00
haraldandClaude Opus 5.5 330c29193f chore(release): bump version to 0.34.2
Build & Publish Release / Build Packages & Publish to Gitea (push) Successful in 7m23s
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-26 14:10:23 +01:00
haraldandClaude Opus 5.5 dd515787ec fix: measure the swipe speed with the compositor's touch event times (Issue #28)
Pia's log shows the lift arriving 200-300 ms after the last move on every
swipe, so the release looked like a resting finger and nothing glided. The
vendored winit now keeps the Wayland timestamp of each touch event
(take_touch_time), and the fling measures with those times instead of when
the app got around to handling the events. The diagnostics log both gaps and
any event handling that blocks the app for more than 40 ms.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-26 14:08:13 +01:00
haraldandClaude Opus 5.5 b11fb2d762 refactor: move the event loop into the library as app::run (prep for Issue #42)
src/main.rs is now only the desktop entry point; the app itself lives in
src/app.rs, so other entry points such as android_main can start it. The code
is unchanged apart from crate:: paths; the source scans in the i18n and UI
tests and the docs point to app.rs.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-25 14:02:43 +01:00
21 changed files with 4622 additions and 4301 deletions

No files matched your search

Generated
+1 -1
View File
@@ -1393,7 +1393,7 @@ dependencies = [
[[package]]
name = "ink-notes"
version = "0.34.1"
version = "0.35.0"
dependencies = [
"ab_glyph",
"arboard",
+1 -1
View File
@@ -1,6 +1,6 @@
[package]
name = "ink-notes"
version = "0.34.1"
version = "0.35.0"
edition = "2021"
description = "Notizbuch-App für Stift, Touch und Maus unter Linux"
repository = "https://gitea.pansi.eu/harald/ink-notes"
+3 -2
View File
@@ -152,8 +152,9 @@ Die CI prüft zusätzlich `cargo fmt --check` und `cargo clippy --all-targets --
```text
src/
├── main.rs # Event-Loop (winit), Eingaben von Maus, Touch und Stift, Ablaufsteuerung
├── lib.rs # Bibliotheks-Exporte (für Tests)
├── main.rs # Einstieg für den Desktop, startet app::run
├── app.rs # Event-Loop (winit), Eingaben von Maus, Touch und Stift, Ablaufsteuerung
├── lib.rs # Bibliotheks-Exporte (App und Module, auch für Tests)
├── canvas.rs # wgpu-Rendering: Papier, Muster, Bilder, Tinte, Nachbarseiten
├── shader.wgsl # Vertex- und Fragment-Shader
├── viewport.rs # Kamera (Zoom, Verschieben), Seitenstapel der durchgehenden Ansicht
+3 -2
View File
@@ -154,8 +154,9 @@ New code comments and commit messages are written in English; older code and his
```text
src/
├── main.rs # Event loop (winit), mouse, touch and pen input, control flow
├── lib.rs # Library exports (for tests)
├── main.rs # Desktop entry point, starts app::run
├── app.rs # Event loop (winit), mouse, touch and pen input, control flow
├── lib.rs # Library exports (app and modules, also for tests)
├── canvas.rs # wgpu rendering: paper, patterns, images, ink, neighboring pages
├── shader.wgsl # Vertex and fragment shaders
├── viewport.rs # Camera (zoom, pan), page stack of the continuous view, swipe momentum
+2 -2
View File
@@ -15,7 +15,7 @@ ink-notes ist ein einzelnes Programm mit einem Event-Loop (`winit`) im Haupt-Thr
| Haupt-Thread | Eingaben, Datenmodell, Rendering (`wgpu`), Oberfläche (`egui`) |
| Autosave | Seiten und Manifest auf die Festplatte schreiben (`autosave.rs`) |
| PDF-Rendering | Importierte PDF-Seiten zu Texturen rendern (`pdf_doc.rs`, `hayro`) |
| Export | PDF/PNG/SVG-Export, damit die Oberfläche nicht hängt (`main.rs`) |
| Export | PDF/PNG/SVG-Export, damit die Oberfläche nicht hängt (`app.rs`) |
Stifteingaben kommen über `octotablet` (Tablet-Protokoll von Wayland bzw. X11), Maus und Touch über `winit`. Die Oberfläche (Werkzeugleiste, Menüs, Dialoge, Textbearbeitung) ist mit `egui` gebaut und wird im selben Render-Durchgang über das Blatt gezeichnet.
@@ -195,7 +195,7 @@ Jede Seite hat einen eigenen Verlauf (`history.rs`): Hinzufügen, Entfernen, Ers
2. **PDF** (nur im Notfall): importierte PDF-Seite als Bild, falls sie sich nicht als Vektor übernehmen lässt.
3. **Images:** eingefügte Bilder (höchstens 300 dpi).
4. **Text:** Textfelder als echter Text mit eingebetteter Schrift (durchsuchbar, kopierbar). Jedes Feld liegt in einem eigenen Koordinatensystem, das eine Transformationsmatrix auf seine Ecken abbildet.
5. **Ink:** Striche als gefüllte Polygone, Formen als Pfade. Halbtransparente Striche (Textmarker) bekommen einen Grafikzustand mit Deckkraft (`ca`/`CA`); printpdf kann solche Zustände nicht anlegen, deshalb trägt `add_alpha_states` sie nach dem Speichern mit `lopdf` in die Seitenressourcen ein. Jeder Strich ist ein einziger Pfad und deckt sich daher nicht selbst doppelt.
5. **Ink:** Striche als gefüllte Polygone, Formen als Pfade. Halbtransparente Striche (Textmarker) bekommen einen Grafikzustand mit Deckkraft (`ca`/`CA`); printpdf kann solche Zustände nicht anlegen, deshalb trägt `add_alpha_states` sie nach dem Speichern mit `lopdf` in die Seitenressourcen ein. Jeder Strich ist ein einziger Pfad und deckt sich daher nicht selbst doppelt. Der Umriss eines Strichs wird vor dem Export vereinfacht (`geometry::simplify_closed_outline`, höchstens 0,01 mm vom gezeichneten entfernt): Das Netz hat etwa 16 Punkte je Stiftpunkt, und mit allen dauerte der Export einer vollen Seite Sekunden. `INK_NOTES_EXACT_OUTLINES=1` exportiert wie bisher jeden Punkt.
Importierte PDF-Seiten werden anschließend als Form-XObject unter die Seite gelegt (`pdf_merge.rs`), bleiben also Vektorgrafik. Seitentitel werden zu Lesezeichen.
+2 -2
View File
@@ -15,7 +15,7 @@ ink-notes is a single program with an event loop (`winit`) on the main thread, p
| Main thread | Input, data model, rendering (`wgpu`), interface (`egui`) |
| Autosave | Writes pages and manifest to disk (`autosave.rs`) |
| PDF rendering | Renders imported PDF pages to textures (`pdf_doc.rs`, `hayro`) |
| Export | PDF/PNG/SVG export, so the interface does not freeze (`main.rs`) |
| Export | PDF/PNG/SVG export, so the interface does not freeze (`app.rs`) |
Pen input arrives via `octotablet` (the Wayland or X11 tablet protocol), mouse and touch via `winit`. The interface (toolbar, menus, dialogs, text editing) is built with `egui` and drawn over the page in the same render pass.
@@ -195,7 +195,7 @@ Each page has its own history (`history.rs`): adding, removing, replacing and mo
2. **PDF** (fallback only): imported PDF page as an image, if it cannot be taken over as vectors.
3. **Images:** inserted images (at most 300 dpi).
4. **Text:** text boxes as real text with embedded font (searchable, copyable). Each box lives in its own coordinate system that a transformation matrix maps onto its corners.
5. **Ink:** strokes as filled polygons, shapes as paths. Semi-transparent strokes (highlighter) get a graphics state with opacity (`ca`/`CA`); printpdf cannot create such states, so `add_alpha_states` adds them to the page resources with `lopdf` after saving. Each stroke is a single path and therefore does not cover itself twice.
5. **Ink:** strokes as filled polygons, shapes as paths. Semi-transparent strokes (highlighter) get a graphics state with opacity (`ca`/`CA`); printpdf cannot create such states, so `add_alpha_states` adds them to the page resources with `lopdf` after saving. Each stroke is a single path and therefore does not cover itself twice. The outline of a stroke is simplified before export (`geometry::simplify_closed_outline`, at most 0.01 mm away from the drawn one): the mesh has about 16 points per pen sample, and with all of them a full page took seconds to export. `INK_NOTES_EXACT_OUTLINES=1` exports every point as before.
Imported PDF pages are then placed under the page as a form XObject (`pdf_merge.rs`), so they stay vector graphics. Page titles become bookmarks.
+10
View File
@@ -84,6 +84,16 @@
</branding>
<content_rating type="oars-1.1" />
<releases>
<release version="0.35.0" date="2026-10-27">
<description>
<p>PDF and PNG export and copying a selection as an image are about six times faster, and exported PDFs are much smaller, because stroke outlines are simplified invisibly before export.</p>
</description>
</release>
<release version="0.34.2" date="2026-10-26">
<description>
<p>The swipe speed for gliding is measured with the touch screen's own event times, so a swipe still glides when the app handles the lift a little late.</p>
</description>
</release>
<release version="0.34.1" date="2026-10-25">
<description>
<p>A finger swipe is measured reliably again for gliding, also when the view touches the edge of the notebook. The fling diagnostics log more details.</p>
+10
View File
@@ -84,6 +84,16 @@
</branding>
<content_rating type="oars-1.1" />
<releases>
<release version="0.35.0" date="2026-10-27">
<description>
<p>PDF and PNG export and copying a selection as an image are about six times faster, and exported PDFs are much smaller, because stroke outlines are simplified invisibly before export.</p>
</description>
</release>
<release version="0.34.2" date="2026-10-26">
<description>
<p>The swipe speed for gliding is measured with the touch screen's own event times, so a swipe still glides when the app handles the lift a little late.</p>
</description>
</release>
<release version="0.34.1" date="2026-10-25">
<description>
<p>A finger swipe is measured reliably again for gliding, also when the view touches the edge of the notebook. The fling diagnostics log more details.</p>
+2
View File
@@ -3,6 +3,8 @@
Notebook app for pen, touch and mouse on Linux (tested on Zorin OS 18 / Ubuntu 24.04).
### Highlights:
- **Faster export**: PDF and PNG export and copying a selection as an image are about six times faster and PDFs are up to eight times smaller, because stroke outlines are simplified before export (at most 0.01 mm off; `INK_NOTES_EXACT_OUTLINES=1` exports every point as before)
- **Fling timing**: The swipe speed for gliding is measured with the touch screen's own event times (Wayland), so a fast swipe glides even when the lift is handled late; the fling diagnostics also report slow event handling
- **Fling fix**: A finger swipe is no longer lost when the view touches the edge of the notebook, so the page glides on after a fast swipe; the fling diagnostics (`INK_NOTES_DEBUG_FLING=1`) log moves, distance and timing
- **Feedback round**: The eraser size is set in a menu like the pens (four presets and a slider); corners of snapped triangles and polygons stay pointed; slides (landscape PDFs) get flat tiles in the page overview without an empty strip
- **Palm rejection & more feedback**: A hand resting on the screen while the pen is near is ignored (also on the title bar, no more accidental paste menu or window moving); the eraser has four sizes (button below the eraser modes, remembered); the page overview shows imported PDF pages; snapped triangles, rectangles and polygons have the same width on every side; a selection made with the pen's select button can be moved with the bare pen
+4352
View File
File diff suppressed because it is too large. Load diff
+30 -15
View File
@@ -1222,21 +1222,25 @@ fn render_stroke_polygon(layer: &printpdf::PdfLayerReference, stroke: &Stroke) {
// Geschlossener Pfad um den Ribbon-Rand:
// Left[0] -> Left[1] -> ... -> Left[n-1] -> Right[n-1] -> Right[n-2] -> ... -> Right[0]
let mut ring = Vec::with_capacity(n * 2);
// 1. Linke Hüllkurve vorwärts
for i in 0..n {
let v = stroke.vertices[i * 2];
let (x, y) = page_to_pdf_mm(v.position[0], v.position[1]);
ring.push((Point::new(Mm(x), Mm(y)), false));
}
// 2. Rechte Hüllkurve rückwärts
for i in (0..n).rev() {
let v = stroke.vertices[i * 2 + 1];
let (x, y) = page_to_pdf_mm(v.position[0], v.position[1]);
ring.push((Point::new(Mm(x), Mm(y)), false));
}
let outline: Vec<glam::Vec2> = (0..n)
.map(|i| stroke.vertices[i * 2].position)
.chain((0..n).rev().map(|i| stroke.vertices[i * 2 + 1].position))
.map(glam::Vec2::from)
.collect();
// The mesh has about 16 points per pen sample, far more than the eye (or a printer) can
// tell apart; fewer points make the PDF, PNG export and copying as an image much faster
let outline = if exact_outlines() {
outline
} else {
crate::geometry::simplify_closed_outline(&outline, OUTLINE_TOLERANCE)
};
let ring: Vec<(Point, bool)> = outline
.iter()
.map(|p| {
let (x, y) = page_to_pdf_mm(p.x, p.y);
(Point::new(Mm(x), Mm(y)), false)
})
.collect();
let fill_color = Color::Rgb(Rgb::new(
stroke.color[0],
@@ -1254,6 +1258,17 @@ fn render_stroke_polygon(layer: &printpdf::PdfLayerReference, stroke: &Stroke) {
layer.add_polygon(poly);
}
/// Largest deviation of an exported stroke outline from the drawn one, in page units
/// (0.01 mm: a tenth of a pixel at 200 dpi).
pub const OUTLINE_TOLERANCE: f32 = 0.1;
/// `INK_NOTES_EXACT_OUTLINES=1` exports every point of the stroke outlines, as before this change
/// (fallback in case the simplified outlines ever look wrong).
fn exact_outlines() -> bool {
static EXACT: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
*EXACT.get_or_init(|| std::env::var_os("INK_NOTES_EXACT_OUTLINES").is_some_and(|v| v != "0"))
}
/// Strategie B: Kubische Bézier-Kurven aus den Abtastpunkten via printpdf::Line.
fn render_stroke_bezier(layer: &printpdf::PdfLayerReference, stroke: &Stroke) {
if stroke.points.is_empty() {
+129
View File
@@ -352,6 +352,63 @@ fn rdp_recursive(points: &[Vec2], epsilon: f32) -> Vec<Vec2> {
}
}
/// Simplifies a closed outline (the last point connects back to the first) so that no point
/// of the original lies farther than `tolerance` from the result. Unlike
/// [`ramer_douglas_peucker`], it measures the distance to the segment, not to the infinite
/// line, so stroke tips that turn back are kept. Works without recursion, so long strokes
/// cannot overflow the stack.
pub fn simplify_closed_outline(points: &[Vec2], tolerance: f32) -> Vec<Vec2> {
if points.len() <= 3 {
return points.to_vec();
}
// Two chains between the first point and the point farthest from it: the start of an
// outline often lies on top of its end (round cap), which would leave no chord to measure
let far = (1..points.len())
.max_by(|&i, &j| {
points[i]
.distance_squared(points[0])
.total_cmp(&points[j].distance_squared(points[0]))
})
.unwrap_or(1);
let mut keep = vec![false; points.len()];
keep[0] = true;
keep[far] = true;
let mut stack = vec![(0, far), (far, points.len())];
while let Some((start, end)) = stack.pop() {
// `end == points.len()` closes the ring back to the first point
let (a, b) = (points[start], points[end % points.len()]);
let mut worst = (0.0_f32, start);
for (i, &p) in points.iter().enumerate().take(end).skip(start + 1) {
let d = point_to_segment_dist(p, a, b);
if d > worst.0 {
worst = (d, i);
}
}
if worst.0 > tolerance {
keep[worst.1] = true;
stack.push((start, worst.1));
stack.push((worst.1, end));
}
}
points
.iter()
.zip(keep)
.filter_map(|(&p, keep)| keep.then_some(p))
.collect()
}
/// Abstand eines Punktes `p` zur Strecke von `a` nach `b`.
#[inline]
pub fn point_to_segment_dist(p: Vec2, a: Vec2, b: Vec2) -> f32 {
let ab = b - a;
let len_sq = ab.length_squared();
if len_sq < 1e-12 {
return p.distance(a);
}
let t = ((p - a).dot(ab) / len_sq).clamp(0.0, 1.0);
p.distance(a + ab * t)
}
/// Abstand eines Punktes `p` zur Geraden durch `a` und `b`.
#[inline]
pub fn point_to_line_dist(p: Vec2, a: Vec2, b: Vec2) -> f32 {
@@ -2327,6 +2384,78 @@ pub fn shape_to_stroke(
mod tests {
use super::*;
/// Largest distance of any point of `original` from the closed outline `simplified`.
fn outline_deviation(original: &[Vec2], simplified: &[Vec2]) -> f32 {
original
.iter()
.map(|&p| {
(0..simplified.len())
.map(|i| {
point_to_segment_dist(
p,
simplified[i],
simplified[(i + 1) % simplified.len()],
)
})
.fold(f32::INFINITY, f32::min)
})
.fold(0.0, f32::max)
}
#[test]
fn test_simplified_stroke_outline_stays_within_tolerance() {
// A handwritten loop like a letter, as the pen delivers it
let samples: Vec<StylusSample> = (0..30)
.map(|i| {
let t = i as f32 / 29.0 * std::f32::consts::TAU * 1.3;
StylusSample {
position: Vec2::new(500.0 + 12.0 * t.cos() + t * 4.0, 800.0 + 20.0 * t.sin()),
pressure: 0.4 + 0.3 * (t * 0.7).sin().abs(),
timestamp: i as f64 * 0.008,
}
})
.collect();
let vertices = crate::storage::reconstruct_stroke_vertices(
&samples,
[0.0, 0.0, 0.0, 1.0],
5.0,
Default::default(),
true,
);
let n = vertices.len() / 2;
let outline: Vec<Vec2> = (0..n)
.map(|i| vertices[i * 2].position)
.chain((0..n).rev().map(|i| vertices[i * 2 + 1].position))
.map(Vec2::from)
.collect();
let tolerance = 0.1;
let simplified = simplify_closed_outline(&outline, tolerance);
let deviation = outline_deviation(&outline, &simplified);
assert!(deviation <= tolerance + 1e-4, "{deviation}");
assert!(
simplified.len() * 4 < outline.len(),
"{} -> {} points",
outline.len(),
simplified.len()
);
}
#[test]
fn test_simplified_outline_keeps_tips_that_turn_back() {
// Thin hairpin: out along y = 0 to x = 20, back along y = 0.05. Measured against the
// infinite line, the tip would vanish.
let mut outline: Vec<Vec2> = (0..=20).map(|x| Vec2::new(x as f32, 0.0)).collect();
outline.extend((0..20).rev().map(|x| Vec2::new(x as f32, 0.05)));
let simplified = simplify_closed_outline(&outline, 0.1);
let reach = simplified.iter().map(|p| p.x).fold(f32::MIN, f32::max);
assert_eq!(reach, 20.0, "{simplified:?}");
assert!(outline_deviation(&outline, &simplified) <= 0.1);
assert!(simplified.len() <= 4, "{simplified:?}");
// Tiny outlines stay as they are
let dot = [Vec2::ZERO, Vec2::X, Vec2::Y];
assert_eq!(simplify_closed_outline(&dot, 0.1), dot.to_vec());
}
#[test]
fn test_detect_line() {
// Leicht zittrige diagonale Punktfolge
+1 -1
View File
@@ -156,7 +156,7 @@ mod tests {
("file_picker.rs", include_str!("file_picker.rs")),
("geometry.rs", include_str!("geometry.rs")),
("library.rs", include_str!("library.rs")),
("main.rs", include_str!("main.rs")),
("app.rs", include_str!("app.rs")),
("page_export.rs", include_str!("page_export.rs")),
("pdf_doc.rs", include_str!("pdf_doc.rs")),
("pdf_merge.rs", include_str!("pdf_merge.rs")),
+1
View File
@@ -1,5 +1,6 @@
//! ink-notes: Latenzoptimierte Inking-App für Linux.
pub mod app;
pub mod assets;
pub mod autosave;
pub mod background;
+3 -4272
View File
File diff suppressed because it is too large. Load diff
+1 -1
View File
@@ -2385,7 +2385,7 @@ mod tests {
create_new_notebook(temp_dir.path(), "Neue Seiten").unwrap();
let mut autosave = AutosaveController::new(dir.clone(), Duration::from_secs(60));
// Wie Taste N in main.rs: neue Seite anhängen, Strich darauf, speichern
// Wie Taste N in app.rs: neue Seite anhängen, Strich darauf, speichern
notebook.next_page();
let idx = notebook.current_page_index;
notebook.pages[idx] = page_with_one_stroke(notebook.pages[idx].id, PageBackground::Lined);
+1 -1
View File
@@ -6133,7 +6133,7 @@ mod tests {
const UI_SOURCES: [(&str, &str); 4] = [
("ui.rs", include_str!("ui.rs")),
("selection.rs", include_str!("selection.rs")),
("main.rs", include_str!("main.rs")),
("app.rs", include_str!("app.rs")),
("file_picker.rs", include_str!("file_picker.rs")),
];
+27
View File
@@ -389,6 +389,14 @@ impl Fling {
velocity
}
/// The glide continues from `now` (after [`Self::release`] with the time the finger was
/// lifted, which may lie a little in the past).
pub fn restart_clock(&mut self, now: std::time::Instant) {
if self.active {
self.last = Some(now);
}
}
/// Hält an (neue Berührung, Mausklick, Mausrad).
pub fn stop(&mut self) {
*self = Self::default();
@@ -430,6 +438,25 @@ mod tests {
PageStack::new(vec![A4; count], current)
}
#[test]
fn test_fling_measures_with_event_times_handled_late() {
use std::time::{Duration, Instant};
let t0 = Instant::now();
let ms = |n: u64| t0 + Duration::from_millis(n);
let mut fling = Fling::default();
// The finger moved until 100 ms and was lifted at 105 ms; the app only got to the
// lift 300 ms later, so the glide starts from there
for i in 0..=10 {
fling.track(Vec2::new(0.0, -30.0), ms(i * 10));
}
let velocity = fling.release(ms(105));
assert!(velocity.y < -2000.0, "{velocity:?}");
assert!(fling.is_active());
fling.restart_clock(ms(405));
let first = fling.step(ms(421)).unwrap();
assert!(first.y < -20.0 && first.y > -60.0, "{first:?}");
}
#[test]
fn test_fling_glides_and_slows_down() {
use std::time::{Duration, Instant};
+4
View File
@@ -14,6 +14,10 @@ docs of the original crate were removed.
- `src/platform/wayland.rs`: `ignore_decoration_touches_until` lets ink-notes ignore touches on
the title bar while the pen is near the screen, so a resting palm does not move the window
(Issue #39).
- `src/platform/wayland.rs` and the touch handler: `take_touch_time` returns the compositor
timestamp of each touch event (Started, Moved, Ended), so ink-notes measures the swipe speed
for gliding with the real event times and not with the time it got around to handling them
(Issue #28).
- `Cargo.toml`: `[lints.rust] warnings = "allow"`, because a path dependency is not built with
capped lints like a crates.io dependency.
+30
View File
@@ -28,6 +28,36 @@ pub(crate) fn decoration_touches_ignored() -> bool {
.is_some_and(|until| std::time::Instant::now() < until)
}
/// ink-notes patch: compositor timestamps (ms) of the touch events not yet taken, in event order.
static TOUCH_TIMES: std::sync::Mutex<
std::collections::VecDeque<(u64, crate::event::TouchPhase, u32)>,
> = std::sync::Mutex::new(std::collections::VecDeque::new());
/// ink-notes patch: remembers the compositor time of a touch event that is about to be sent.
pub(crate) fn push_touch_time(id: u64, phase: crate::event::TouchPhase, time: u32) {
let mut times = TOUCH_TIMES.lock().unwrap_or_else(|e| e.into_inner());
// Nobody takes them (e.g. an app that never asks): keep only the latest
if times.len() >= 256 {
times.pop_front();
}
times.push_back((id, phase, time));
}
/// ink-notes patch: compositor time in milliseconds (arbitrary origin, wraps around) of the
/// touch event with this id and phase (`Started`, `Moved` or `Ended`; `Cancelled` has none),
/// for the event currently being handled. Call it once for every such `WindowEvent::Touch`.
/// `None` on X11 or if the time is unknown.
pub fn take_touch_time(id: u64, phase: crate::event::TouchPhase) -> Option<u32> {
let mut times = TOUCH_TIMES.lock().unwrap_or_else(|e| e.into_inner());
// Older entries that were never taken are skipped
while let Some((i, p, time)) = times.pop_front() {
if i == id && p == phase {
return Some(time);
}
}
None
}
/// Additional methods on [`EventLoopWindowTarget`] that are specific to Wayland.
pub trait EventLoopWindowTargetExtWayland {
/// True if the [`EventLoopWindowTarget`] uses Wayland.
@@ -83,6 +83,9 @@ impl TouchHandler for WinitState {
.touch_map
.insert(id, TouchPoint { surface, location, frame_of: None });
// ink-notes patch: keep the event time for `take_touch_time`
crate::platform::wayland::push_touch_time(id as u64, TouchPhase::Started, time);
self.events_sink.push_window_event(
WindowEvent::Touch(Touch {
device_id: crate::event::DeviceId(crate::platform_impl::DeviceId::Wayland(
@@ -138,6 +141,8 @@ impl TouchHandler for WinitState {
None => return,
};
// ink-notes patch: keep the event time for `take_touch_time`
crate::platform::wayland::push_touch_time(id as u64, TouchPhase::Ended, time);
self.events_sink.push_window_event(
WindowEvent::Touch(Touch {
device_id: crate::event::DeviceId(crate::platform_impl::DeviceId::Wayland(
@@ -157,7 +162,7 @@ impl TouchHandler for WinitState {
_: &Connection,
_: &QueueHandle<Self>,
touch: &WlTouch,
_: u32,
time: u32,
id: i32,
position: (f64, f64),
) {
@@ -192,6 +197,9 @@ impl TouchHandler for WinitState {
touch_point.location = LogicalPosition::<f64>::from(position);
// ink-notes patch: keep the event time for `take_touch_time`
crate::platform::wayland::push_touch_time(id as u64, TouchPhase::Moved, time);
self.events_sink.push_window_event(
WindowEvent::Touch(Touch {
device_id: crate::event::DeviceId(crate::platform_impl::DeviceId::Wayland(