feat(diag): full input logging + read barrel from pointerFlags; focal-jump reject
All checks were successful
CI / Windows build (push) Successful in 12m44s
All checks were successful
CI / Windows build (push) Successful in 12m44s
Buttons (likely fix + ground truth): device diag showed ptr=12577 pen=10056 — WM_POINTER reaches the observer and GetPointerPenInfo succeeds, so the buttons were just read from the wrong field. Native now resolves the barrel from BOTH penFlags(PEN_FLAG_BARREL) AND pointerInfo.pointerFlags(POINTER_FLAG_SECONDBUTTON) — many pens use the latter. It also emits the full raw set (pointerFlags, penFlags, penMask, ButtonChangeType, tilt) plus OR-accumulated flags so a single session reveals exactly which field each button sets. Comprehensive logging (per user request "用好用的log库 / 我手动开启日志再记录"): new DiagnosticLogger emits through dart:developer log(name 'badnote.input') — capturable via `flutter run` / DevTools / `flutter logs` — AND mirrors to a file (path shown in the overlay) for the packaged GUI build that has no console. Manually enabled by the toolbar diagnostic toggle; off by default. PEN lines log on raw-field change; ZOOM lines log every scale frame + rebaselines. Zoom: scale-only glitch rejection didn't stop the jumping, so add focal/position glitch rejection — drop a 2-finger frame whose focal jumps >250px (a touch misread). The full per-frame trace (raw scale, pointerCount, applied change, focal jump, drops) is now logged so the residual cause is unambiguous. InputDiagnostics singleton accumulates the stats; the overlay shows summary + last trace lines + log path + reset. Removed the ad-hoc inline zoom min/max. Dart: analyze clean, 66/66 tests, linux build green. Native compiles on CI. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -12,12 +12,14 @@ import '../../services/database_service.dart';
|
||||
import '../engine/stroke_geometry.dart' show kDefaultPenThinning;
|
||||
import '../engine/stroke_model.dart';
|
||||
import '../engine/undo_stack.dart';
|
||||
import '../input/diagnostic_logger.dart';
|
||||
import '../input/pen_config.dart';
|
||||
import '../input/pen_input_service.dart';
|
||||
import '../persistence/editor_repository.dart';
|
||||
import '../persistence/save_scheduler.dart';
|
||||
import '../ui/pen_settings_page.dart';
|
||||
import '../ui/thumbnail_grid.dart';
|
||||
import 'input_diagnostics.dart';
|
||||
import 'pen_canvas.dart';
|
||||
import 'pen_stroke.dart';
|
||||
|
||||
@@ -95,13 +97,6 @@ class _PenEditorScreenState extends State<PenEditorScreen> {
|
||||
String _penDebug = '';
|
||||
bool _showPenDebug = false;
|
||||
|
||||
/// Live zoom-scale diagnostic (only tracked while the overlay is on), so we
|
||||
/// can capture the value the pinch flash jumps to. `_zoomMin/_zoomMax` record
|
||||
/// the extremes seen since the overlay was last enabled.
|
||||
double _zoomNow = 1.0;
|
||||
double _zoomMin = double.infinity;
|
||||
double _zoomMax = 0.0;
|
||||
|
||||
// Tool state.
|
||||
CanvasTool _tool = CanvasTool.pen;
|
||||
Color _color = Colors.black;
|
||||
@@ -126,23 +121,11 @@ class _PenEditorScreenState extends State<PenEditorScreen> {
|
||||
// Begin listening to the native Windows pen plugin (barrel/eraser/tilt).
|
||||
// No-op on platforms without the plugin (W3).
|
||||
PenInputService.instance.start();
|
||||
_transform.addListener(_onTransformDebug);
|
||||
_initPersistence();
|
||||
_initPenConfig();
|
||||
_open();
|
||||
}
|
||||
|
||||
/// Track the live zoom scale for the diagnostic overlay (no-op when off).
|
||||
void _onTransformDebug() {
|
||||
if (!_showPenDebug) return;
|
||||
final s = _transform.value.getMaxScaleOnAxis();
|
||||
setState(() {
|
||||
_zoomNow = s;
|
||||
if (s < _zoomMin) _zoomMin = s;
|
||||
if (s > _zoomMax) _zoomMax = s;
|
||||
});
|
||||
}
|
||||
|
||||
Future<void> _initPenConfig() async {
|
||||
final controller = await PenConfigController.load();
|
||||
if (!mounted) {
|
||||
@@ -240,10 +223,10 @@ class _PenEditorScreenState extends State<PenEditorScreen> {
|
||||
scheduler.dispose();
|
||||
}
|
||||
_document?.dispose();
|
||||
_transform.removeListener(_onTransformDebug);
|
||||
_transform.dispose();
|
||||
_penConfig?.dispose();
|
||||
PenInputService.instance.stop();
|
||||
DiagnosticLogger.instance.stop();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@@ -453,18 +436,59 @@ class _PenEditorScreenState extends State<PenEditorScreen> {
|
||||
child: Material(
|
||||
color: Theme.of(context).colorScheme.inverseSurface,
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 10, vertical: 6),
|
||||
child: Text(
|
||||
'${_penDebug.isEmpty ? 'hover / draw with the pen…' : _penDebug}'
|
||||
'\nzoom=${_zoomNow.toStringAsFixed(2)} '
|
||||
'min=${_zoomMin.isFinite ? _zoomMin.toStringAsFixed(2) : '-'} '
|
||||
'max=${_zoomMax.toStringAsFixed(2)}',
|
||||
style: TextStyle(
|
||||
fontFamily: 'monospace',
|
||||
fontSize: 12,
|
||||
color: Theme.of(context).colorScheme.onInverseSurface,
|
||||
child: ConstrainedBox(
|
||||
constraints: const BoxConstraints(maxWidth: 380),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 10, vertical: 6),
|
||||
child: ListenableBuilder(
|
||||
listenable: InputDiagnostics.instance,
|
||||
builder: (context, _) {
|
||||
final cs = Theme.of(context).colorScheme;
|
||||
final d = InputDiagnostics.instance;
|
||||
final tail = d.trace.length > 6
|
||||
? d.trace.sublist(d.trace.length - 6)
|
||||
: d.trace;
|
||||
final mono = TextStyle(
|
||||
fontFamily: 'monospace',
|
||||
fontSize: 11,
|
||||
color: cs.onInverseSurface);
|
||||
final monoFaint = mono.copyWith(
|
||||
fontSize: 10,
|
||||
color:
|
||||
cs.onInverseSurface.withValues(alpha: 0.75));
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Text(
|
||||
_penDebug.isEmpty
|
||||
? 'hover / draw with the pen…'
|
||||
: _penDebug,
|
||||
style: mono),
|
||||
const SizedBox(height: 4),
|
||||
Text(d.summary(), style: mono),
|
||||
if (tail.isNotEmpty) ...[
|
||||
const SizedBox(height: 4),
|
||||
Text(tail.join('\n'), style: monoFaint),
|
||||
],
|
||||
const SizedBox(height: 4),
|
||||
Text(
|
||||
'log: ${DiagnosticLogger.instance.path ?? "(developer.log only)"}',
|
||||
style: monoFaint),
|
||||
Align(
|
||||
alignment: Alignment.centerRight,
|
||||
child: TextButton(
|
||||
onPressed: () =>
|
||||
InputDiagnostics.instance.reset(),
|
||||
child: Text('Reset stats',
|
||||
style:
|
||||
TextStyle(color: cs.inversePrimary)),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
@@ -621,15 +645,17 @@ class _PenEditorScreenState extends State<PenEditorScreen> {
|
||||
_ToolButton(
|
||||
icon: Icons.bug_report_outlined,
|
||||
selected: _showPenDebug,
|
||||
tooltip: 'Pen + zoom diagnostic',
|
||||
onPressed: () => setState(() {
|
||||
_showPenDebug = !_showPenDebug;
|
||||
if (_showPenDebug) {
|
||||
_zoomMin = double.infinity;
|
||||
_zoomMax = 0.0;
|
||||
_zoomNow = _transform.value.getMaxScaleOnAxis();
|
||||
tooltip: 'Input diagnostic (writes a log file)',
|
||||
onPressed: () {
|
||||
final on = !_showPenDebug;
|
||||
setState(() => _showPenDebug = on);
|
||||
if (on) {
|
||||
InputDiagnostics.instance.reset();
|
||||
DiagnosticLogger.instance.start();
|
||||
} else {
|
||||
DiagnosticLogger.instance.stop();
|
||||
}
|
||||
}),
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
|
||||
Reference in New Issue
Block a user