fix(canvas): compact page pill + pen diagnostic
All checks were successful
CI / Windows build (push) Successful in 10m45s
All checks were successful
CI / Windows build (push) Successful in 10m45s
Page slider is no longer persistent: a compact prev/'n/total'/next pill; tapping the label reveals the slider (collapses again), so it stops blocking the page. Add a pen-pressure diagnostic toggle (bug icon) that shows the live kind/pressure/min/max Windows delivers — to pin down why pressure reads flat on the Surface Pen.
This commit is contained in:
@@ -42,8 +42,14 @@ class PenCanvas extends StatefulWidget {
|
|||||||
this.allowFingerDrawing = false,
|
this.allowFingerDrawing = false,
|
||||||
this.minScale = 0.5,
|
this.minScale = 0.5,
|
||||||
this.maxScale = 8.0,
|
this.maxScale = 8.0,
|
||||||
|
this.onPenDebug,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
/// Debug hook: called with a readout of the latest pen event
|
||||||
|
/// (kind / pressure / pressureMin / pressureMax) so we can see what Windows
|
||||||
|
/// actually delivers. Null in release UI.
|
||||||
|
final void Function(String readout)? onPenDebug;
|
||||||
|
|
||||||
/// The rendered PDF page bitmap, already sized to [pageSize].
|
/// The rendered PDF page bitmap, already sized to [pageSize].
|
||||||
final Widget pageWidget;
|
final Widget pageWidget;
|
||||||
|
|
||||||
@@ -249,8 +255,18 @@ class _PenCanvasState extends State<PenCanvas> {
|
|||||||
|
|
||||||
// --- Listener callbacks ---------------------------------------------------
|
// --- Listener callbacks ---------------------------------------------------
|
||||||
|
|
||||||
|
void _emitPenDebug(PointerEvent event) {
|
||||||
|
final cb = widget.onPenDebug;
|
||||||
|
if (cb == null) return;
|
||||||
|
cb('${event.kind.name} p=${event.pressure.toStringAsFixed(3)} '
|
||||||
|
'min=${event.pressureMin.toStringAsFixed(2)} '
|
||||||
|
'max=${event.pressureMax.toStringAsFixed(2)} '
|
||||||
|
'tilt=${event.tilt.toStringAsFixed(2)}');
|
||||||
|
}
|
||||||
|
|
||||||
void _onPointerHover(PointerHoverEvent event) {
|
void _onPointerHover(PointerHoverEvent event) {
|
||||||
if (_isStylus(event.kind)) {
|
if (_isStylus(event.kind)) {
|
||||||
|
_emitPenDebug(event);
|
||||||
// Detect eraser (barrel button / inverted) while hovering.
|
// Detect eraser (barrel button / inverted) while hovering.
|
||||||
_eraserActive = _isEraserSignal(event);
|
_eraserActive = _isEraserSignal(event);
|
||||||
}
|
}
|
||||||
@@ -258,6 +274,7 @@ class _PenCanvasState extends State<PenCanvas> {
|
|||||||
|
|
||||||
void _onPointerDown(PointerDownEvent event) {
|
void _onPointerDown(PointerDownEvent event) {
|
||||||
if (event.kind == PointerDeviceKind.trackpad) return;
|
if (event.kind == PointerDeviceKind.trackpad) return;
|
||||||
|
if (_isStylus(event.kind)) _emitPenDebug(event);
|
||||||
|
|
||||||
_activePointers[event.pointer] = event.kind;
|
_activePointers[event.pointer] = event.kind;
|
||||||
|
|
||||||
@@ -282,6 +299,7 @@ class _PenCanvasState extends State<PenCanvas> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void _onPointerMove(PointerMoveEvent event) {
|
void _onPointerMove(PointerMoveEvent event) {
|
||||||
|
if (_isStylus(event.kind)) _emitPenDebug(event);
|
||||||
if (event.pointer != _drawPointer) return;
|
if (event.pointer != _drawPointer) return;
|
||||||
if (_activePointers.length >= 2) return; // pinch owns it
|
if (_activePointers.length >= 2) return; // pinch owns it
|
||||||
_extendStroke(event);
|
_extendStroke(event);
|
||||||
|
|||||||
@@ -40,6 +40,15 @@ class _PenEditorScreenState extends State<PenEditorScreen> {
|
|||||||
/// Live page value while dragging the page slider (null when not dragging).
|
/// Live page value while dragging the page slider (null when not dragging).
|
||||||
double? _scrub;
|
double? _scrub;
|
||||||
|
|
||||||
|
/// Whether the page-jump slider is expanded (NOT persistent — toggled by
|
||||||
|
/// tapping the page label; collapses after a jump).
|
||||||
|
bool _showSlider = false;
|
||||||
|
|
||||||
|
/// Latest pen-event debug readout (kind/pressure/min/max) — shown only when
|
||||||
|
/// the diagnostic toggle is on, to inspect what Windows delivers.
|
||||||
|
String _penDebug = '';
|
||||||
|
bool _showPenDebug = false;
|
||||||
|
|
||||||
// Tool state.
|
// Tool state.
|
||||||
CanvasTool _tool = CanvasTool.pen;
|
CanvasTool _tool = CanvasTool.pen;
|
||||||
Color _color = Colors.black;
|
Color _color = Colors.black;
|
||||||
@@ -164,6 +173,34 @@ class _PenEditorScreenState extends State<PenEditorScreen> {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
// Pen diagnostic readout (top-right) — shows what Windows delivers.
|
||||||
|
if (_showPenDebug)
|
||||||
|
SafeArea(
|
||||||
|
child: Align(
|
||||||
|
alignment: Alignment.topRight,
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.all(8),
|
||||||
|
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,
|
||||||
|
style: TextStyle(
|
||||||
|
fontFamily: 'monospace',
|
||||||
|
fontSize: 12,
|
||||||
|
color: Theme.of(context).colorScheme.onInverseSurface,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
@@ -213,6 +250,9 @@ class _PenEditorScreenState extends State<PenEditorScreen> {
|
|||||||
? _highlighterWidthFraction
|
? _highlighterWidthFraction
|
||||||
: _penWidthFraction,
|
: _penWidthFraction,
|
||||||
allowFingerDrawing: _allowFingerDrawing,
|
allowFingerDrawing: _allowFingerDrawing,
|
||||||
|
onPenDebug: _showPenDebug
|
||||||
|
? (s) => setState(() => _penDebug = s)
|
||||||
|
: null,
|
||||||
onStrokeComplete: _commitStroke,
|
onStrokeComplete: _commitStroke,
|
||||||
onEraseStroke: _eraseStroke,
|
onEraseStroke: _eraseStroke,
|
||||||
pageWidget: PdfPageView(
|
pageWidget: PdfPageView(
|
||||||
@@ -272,6 +312,12 @@ class _PenEditorScreenState extends State<PenEditorScreen> {
|
|||||||
onPressed: () =>
|
onPressed: () =>
|
||||||
setState(() => _allowFingerDrawing = !_allowFingerDrawing),
|
setState(() => _allowFingerDrawing = !_allowFingerDrawing),
|
||||||
),
|
),
|
||||||
|
_ToolButton(
|
||||||
|
icon: Icons.bug_report_outlined,
|
||||||
|
selected: _showPenDebug,
|
||||||
|
tooltip: 'Pen pressure diagnostic',
|
||||||
|
onPressed: () => setState(() => _showPenDebug = !_showPenDebug),
|
||||||
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -299,67 +345,85 @@ class _PenEditorScreenState extends State<PenEditorScreen> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Floating page-control pill: prev / drag-slider / next. No keyboard input
|
/// Floating page control: a COMPACT pill (prev / "n / total" / next). Tapping
|
||||||
/// (Windows on-screen keyboard is unreliable) — the slider scrubs pages.
|
/// the label reveals a drag-slider — which is NOT persistent (collapses again
|
||||||
|
/// on tap) so it doesn't block the page. No keyboard input (Windows IME is
|
||||||
|
/// unreliable).
|
||||||
Widget _buildPagePill() {
|
Widget _buildPagePill() {
|
||||||
final doc = _document!;
|
final doc = _document!;
|
||||||
final cs = Theme.of(context).colorScheme;
|
final cs = Theme.of(context).colorScheme;
|
||||||
final total = doc.pages.length;
|
final total = doc.pages.length;
|
||||||
final shown = (_scrub ?? (_pageIndex + 1).toDouble()).round();
|
final shown = (_scrub ?? (_pageIndex + 1).toDouble()).round();
|
||||||
return Material(
|
return Column(
|
||||||
color: cs.surfaceContainerHigh,
|
mainAxisSize: MainAxisSize.min,
|
||||||
elevation: 3,
|
children: [
|
||||||
borderRadius: BorderRadius.circular(28),
|
// Slider — shown only when expanded (not persistent).
|
||||||
child: ConstrainedBox(
|
if (_showSlider && total > 1)
|
||||||
constraints: const BoxConstraints(maxWidth: 560),
|
Container(
|
||||||
child: Padding(
|
margin: const EdgeInsets.only(bottom: 8),
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 4, vertical: 2),
|
constraints: const BoxConstraints(maxWidth: 420),
|
||||||
child: Row(
|
child: Material(
|
||||||
mainAxisSize: MainAxisSize.min,
|
color: cs.surfaceContainerHigh,
|
||||||
children: [
|
elevation: 3,
|
||||||
IconButton(
|
borderRadius: BorderRadius.circular(28),
|
||||||
tooltip: 'Previous page',
|
child: Padding(
|
||||||
icon: const Icon(Icons.chevron_left),
|
padding: const EdgeInsets.symmetric(horizontal: 12),
|
||||||
onPressed:
|
child: Slider(
|
||||||
_pageIndex > 0 ? () => _goToPage(_pageIndex - 1) : null,
|
min: 1,
|
||||||
),
|
max: total.toDouble(),
|
||||||
if (total > 1)
|
value: (_scrub ?? (_pageIndex + 1).toDouble())
|
||||||
Flexible(
|
.clamp(1, total.toDouble()),
|
||||||
child: Slider(
|
label: '$shown',
|
||||||
min: 1,
|
divisions: total - 1,
|
||||||
max: total.toDouble(),
|
onChanged: (v) => setState(() => _scrub = v),
|
||||||
value: (_scrub ?? (_pageIndex + 1).toDouble())
|
onChangeEnd: (v) {
|
||||||
.clamp(1, total.toDouble()),
|
setState(() => _scrub = null);
|
||||||
label: '$shown',
|
_goToPage(v.round() - 1);
|
||||||
divisions: total - 1,
|
},
|
||||||
onChanged: (v) => setState(() => _scrub = v),
|
|
||||||
onChangeEnd: (v) {
|
|
||||||
setState(() => _scrub = null);
|
|
||||||
_goToPage(v.round() - 1);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Padding(
|
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 6),
|
|
||||||
child: Text(
|
|
||||||
'$shown / $total',
|
|
||||||
style: TextStyle(
|
|
||||||
color: cs.onSurface,
|
|
||||||
fontWeight: FontWeight.w600,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
IconButton(
|
),
|
||||||
tooltip: 'Next page',
|
),
|
||||||
icon: const Icon(Icons.chevron_right),
|
// Compact pill — always; fits content (no big frame).
|
||||||
onPressed: _pageIndex < total - 1
|
Material(
|
||||||
? () => _goToPage(_pageIndex + 1)
|
color: cs.surfaceContainerHigh,
|
||||||
: null,
|
elevation: 3,
|
||||||
),
|
borderRadius: BorderRadius.circular(28),
|
||||||
],
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 4, vertical: 2),
|
||||||
|
child: Row(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
IconButton(
|
||||||
|
tooltip: 'Previous page',
|
||||||
|
icon: const Icon(Icons.chevron_left),
|
||||||
|
onPressed:
|
||||||
|
_pageIndex > 0 ? () => _goToPage(_pageIndex - 1) : null,
|
||||||
|
),
|
||||||
|
TextButton(
|
||||||
|
onPressed: total > 1
|
||||||
|
? () => setState(() => _showSlider = !_showSlider)
|
||||||
|
: null,
|
||||||
|
child: Text(
|
||||||
|
'$shown / $total',
|
||||||
|
style: TextStyle(
|
||||||
|
color: cs.onSurface,
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
IconButton(
|
||||||
|
tooltip: 'Next page',
|
||||||
|
icon: const Icon(Icons.chevron_right),
|
||||||
|
onPressed: _pageIndex < total - 1
|
||||||
|
? () => _goToPage(_pageIndex + 1)
|
||||||
|
: null,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user