Compare commits

..
3 Commits
Author SHA1 Message Date
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
19 changed files with 4450 additions and 4284 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.34.2"
dependencies = [
"ab_glyph",
"arboard",
+1 -1
View File
@@ -1,6 +1,6 @@
[package]
name = "ink-notes"
version = "0.34.1"
version = "0.34.2"
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
+1 -1
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.
+1 -1
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.
@@ -84,6 +84,11 @@
</branding>
<content_rating type="oars-1.1" />
<releases>
<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>
+5
View File
@@ -84,6 +84,11 @@
</branding>
<content_rating type="oars-1.1" />
<releases>
<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>
+1
View File
@@ -3,6 +3,7 @@
Notebook app for pen, touch and mouse on Linux (tested on Zorin OS 18 / Ubuntu 24.04).
### Highlights:
- **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
+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(