- Pen vs. finger (#44): the patched winit hands Android pointers with tool type stylus/eraser to the app separately (take_pen_samples, with hover and button state) instead of as touch events. src/pen.rs turns them, like octotablet's events on the desktop, into one PenInput stream, so pen buttons, the eraser end and palm rejection work the same everywhere. Hover exit right before touch-down does not count as leaving; a pen without hover leaves 150 ms after lifting. - Soft keyboard: shown via AndroidApp::show_soft_input when a text field gets focus, hidden when it loses it. The patched winit now fills KeyEvent::text, so typed characters reach egui. - Exports (PDF, PNG, SVG) are written to the app folder and copied to Download/ink-notes through the MediaStore (Android 10+, via JNI). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
4ca21483e5
commit
a8aef30db7
13 files changed
+1187
-559
No files matched your search
Vendored
+35
@@ -77,6 +77,41 @@ impl<T> EventLoopBuilderExtAndroid for EventLoopBuilder<T> {
|
||||
/// depending on the `android_activity` crate, and instead consume the API that
|
||||
/// is re-exported by Winit.
|
||||
///
|
||||
/// ink-notes patch: what a pen sample reports (Issue #44).
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum PenAction {
|
||||
/// Pen comes near the screen, moves above it or leaves it
|
||||
HoverEnter,
|
||||
HoverMove,
|
||||
HoverExit,
|
||||
/// Pen touches the screen, moves on it, is lifted or the gesture is cancelled
|
||||
Down,
|
||||
Move,
|
||||
Up,
|
||||
Cancel,
|
||||
}
|
||||
|
||||
/// ink-notes patch: one sample of a pen (tool type stylus or eraser). Such pointers are not
|
||||
/// reported as `WindowEvent::Touch`; the app collects them with [`take_pen_samples`].
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
pub struct PenSample {
|
||||
pub action: PenAction,
|
||||
/// Position in physical pixels
|
||||
pub x: f32,
|
||||
pub y: f32,
|
||||
/// Usually 0..=1
|
||||
pub pressure: f32,
|
||||
/// Eraser end of the pen (tool type eraser)
|
||||
pub eraser: bool,
|
||||
/// `MotionEvent.getButtonState()`; the pen buttons are 0x20 (primary) and 0x40 (secondary)
|
||||
pub buttons: u32,
|
||||
}
|
||||
|
||||
/// ink-notes patch: the pen samples received since the last call, oldest first.
|
||||
pub fn take_pen_samples() -> Vec<PenSample> {
|
||||
crate::platform_impl::take_pen_samples()
|
||||
}
|
||||
|
||||
/// For compatibility applications should then import the `AndroidApp` type for
|
||||
/// their `android_main(app: AndroidApp)` function like:
|
||||
/// ```rust
|
||||
|
||||
+68
-2
@@ -11,7 +11,7 @@ use std::{
|
||||
time::{Duration, Instant},
|
||||
};
|
||||
|
||||
use android_activity::input::{InputEvent, KeyAction, Keycode, MotionAction};
|
||||
use android_activity::input::{InputEvent, KeyAction, Keycode, MotionAction, ToolType};
|
||||
use android_activity::{
|
||||
AndroidApp, AndroidAppWaker, ConfigurationRef, InputStatus, MainEvent, Rect,
|
||||
};
|
||||
@@ -382,6 +382,7 @@ impl<T: 'static> EventLoop<T> {
|
||||
InputEvent::MotionEvent(motion_event) => {
|
||||
let window_id = window::WindowId(WindowId);
|
||||
let device_id = event::DeviceId(DeviceId(motion_event.device_id()));
|
||||
record_pen_samples(motion_event);
|
||||
|
||||
let phase = match motion_event.action() {
|
||||
MotionAction::Down | MotionAction::PointerDown => {
|
||||
@@ -408,6 +409,10 @@ impl<T: 'static> EventLoop<T> {
|
||||
};
|
||||
|
||||
for pointer in pointers {
|
||||
// ink-notes patch: pens are reported through `take_pen_samples`
|
||||
if is_pen(&pointer) {
|
||||
continue;
|
||||
}
|
||||
let location = PhysicalPosition {
|
||||
x: pointer.x() as _,
|
||||
y: pointer.y() as _,
|
||||
@@ -450,6 +455,15 @@ impl<T: 'static> EventLoop<T> {
|
||||
&mut self.combining_accent,
|
||||
);
|
||||
|
||||
// ink-notes patch: the character typed, so that text fields receive it
|
||||
let text = match (state, &key_char) {
|
||||
(
|
||||
event::ElementState::Pressed,
|
||||
Some(android_activity::input::KeyMapChar::Unicode(c)),
|
||||
) if !c.is_control() => Some(smol_str::SmolStr::from_iter([*c])),
|
||||
_ => None,
|
||||
};
|
||||
|
||||
let event = event::Event::WindowEvent {
|
||||
window_id: window::WindowId(WindowId),
|
||||
event: event::WindowEvent::KeyboardInput {
|
||||
@@ -460,7 +474,7 @@ impl<T: 'static> EventLoop<T> {
|
||||
logical_key: keycodes::to_logical(key_char, keycode),
|
||||
location: keycodes::to_location(keycode),
|
||||
repeat: key.repeat_count() > 0,
|
||||
text: None,
|
||||
text,
|
||||
platform_specific: KeyEventExtra {},
|
||||
},
|
||||
is_synthetic: false,
|
||||
@@ -1124,3 +1138,55 @@ impl VideoMode {
|
||||
self.monitor.clone()
|
||||
}
|
||||
}
|
||||
|
||||
/// ink-notes patch: pen samples waiting for `take_pen_samples` (Issue #44)
|
||||
static PEN_SAMPLES: Mutex<Vec<crate::platform::android::PenSample>> = Mutex::new(Vec::new());
|
||||
|
||||
fn is_pen(pointer: &android_activity::input::Pointer<'_>) -> bool {
|
||||
matches!(pointer.tool_type(), ToolType::Stylus | ToolType::Eraser)
|
||||
}
|
||||
|
||||
fn record_pen_samples(motion_event: &android_activity::input::MotionEvent<'_>) {
|
||||
use crate::platform::android::{PenAction, PenSample};
|
||||
|
||||
let action = match motion_event.action() {
|
||||
MotionAction::Down | MotionAction::PointerDown => PenAction::Down,
|
||||
MotionAction::Up | MotionAction::PointerUp => PenAction::Up,
|
||||
MotionAction::Move => PenAction::Move,
|
||||
MotionAction::Cancel => PenAction::Cancel,
|
||||
MotionAction::HoverEnter => PenAction::HoverEnter,
|
||||
MotionAction::HoverMove => PenAction::HoverMove,
|
||||
MotionAction::HoverExit => PenAction::HoverExit,
|
||||
_ => return,
|
||||
};
|
||||
let buttons = motion_event.button_state().0;
|
||||
let mut record = |pointer: android_activity::input::Pointer<'_>| {
|
||||
if !is_pen(&pointer) {
|
||||
return;
|
||||
}
|
||||
let sample = PenSample {
|
||||
action,
|
||||
x: pointer.x(),
|
||||
y: pointer.y(),
|
||||
pressure: pointer.pressure(),
|
||||
eraser: pointer.tool_type() == ToolType::Eraser,
|
||||
buttons,
|
||||
};
|
||||
let mut samples = PEN_SAMPLES.lock().unwrap_or_else(|e| e.into_inner());
|
||||
// Nobody collects them (app without pen support): don't grow without bound
|
||||
if samples.len() >= 4096 {
|
||||
samples.remove(0);
|
||||
}
|
||||
samples.push(sample);
|
||||
};
|
||||
match action {
|
||||
PenAction::Down | PenAction::Up => {
|
||||
record(motion_event.pointer_at_index(motion_event.pointer_index()))
|
||||
}
|
||||
_ => motion_event.pointers().for_each(record),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn take_pen_samples() -> Vec<crate::platform::android::PenSample> {
|
||||
std::mem::take(&mut *PEN_SAMPLES.lock().unwrap_or_else(|e| e.into_inner()))
|
||||
}
|
||||
Reference in new issue
Block a user