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.minScale = 0.5,
|
||||
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].
|
||||
final Widget pageWidget;
|
||||
|
||||
@@ -249,8 +255,18 @@ class _PenCanvasState extends State<PenCanvas> {
|
||||
|
||||
// --- 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) {
|
||||
if (_isStylus(event.kind)) {
|
||||
_emitPenDebug(event);
|
||||
// Detect eraser (barrel button / inverted) while hovering.
|
||||
_eraserActive = _isEraserSignal(event);
|
||||
}
|
||||
@@ -258,6 +274,7 @@ class _PenCanvasState extends State<PenCanvas> {
|
||||
|
||||
void _onPointerDown(PointerDownEvent event) {
|
||||
if (event.kind == PointerDeviceKind.trackpad) return;
|
||||
if (_isStylus(event.kind)) _emitPenDebug(event);
|
||||
|
||||
_activePointers[event.pointer] = event.kind;
|
||||
|
||||
@@ -282,6 +299,7 @@ class _PenCanvasState extends State<PenCanvas> {
|
||||
}
|
||||
|
||||
void _onPointerMove(PointerMoveEvent event) {
|
||||
if (_isStylus(event.kind)) _emitPenDebug(event);
|
||||
if (event.pointer != _drawPointer) return;
|
||||
if (_activePointers.length >= 2) return; // pinch owns it
|
||||
_extendStroke(event);
|
||||
|
||||
@@ -40,6 +40,15 @@ class _PenEditorScreenState extends State<PenEditorScreen> {
|
||||
/// Live page value while dragging the page slider (null when not dragging).
|
||||
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.
|
||||
CanvasTool _tool = CanvasTool.pen;
|
||||
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
|
||||
: _penWidthFraction,
|
||||
allowFingerDrawing: _allowFingerDrawing,
|
||||
onPenDebug: _showPenDebug
|
||||
? (s) => setState(() => _penDebug = s)
|
||||
: null,
|
||||
onStrokeComplete: _commitStroke,
|
||||
onEraseStroke: _eraseStroke,
|
||||
pageWidget: PdfPageView(
|
||||
@@ -272,6 +312,12 @@ class _PenEditorScreenState extends State<PenEditorScreen> {
|
||||
onPressed: () =>
|
||||
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
|
||||
/// (Windows on-screen keyboard is unreliable) — the slider scrubs pages.
|
||||
/// Floating page control: a COMPACT pill (prev / "n / total" / next). Tapping
|
||||
/// 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() {
|
||||
final doc = _document!;
|
||||
final cs = Theme.of(context).colorScheme;
|
||||
final total = doc.pages.length;
|
||||
final shown = (_scrub ?? (_pageIndex + 1).toDouble()).round();
|
||||
return Material(
|
||||
color: cs.surfaceContainerHigh,
|
||||
elevation: 3,
|
||||
borderRadius: BorderRadius.circular(28),
|
||||
child: ConstrainedBox(
|
||||
constraints: const BoxConstraints(maxWidth: 560),
|
||||
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,
|
||||
),
|
||||
if (total > 1)
|
||||
Flexible(
|
||||
child: Slider(
|
||||
min: 1,
|
||||
max: total.toDouble(),
|
||||
value: (_scrub ?? (_pageIndex + 1).toDouble())
|
||||
.clamp(1, total.toDouble()),
|
||||
label: '$shown',
|
||||
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,
|
||||
),
|
||||
return Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
// Slider — shown only when expanded (not persistent).
|
||||
if (_showSlider && total > 1)
|
||||
Container(
|
||||
margin: const EdgeInsets.only(bottom: 8),
|
||||
constraints: const BoxConstraints(maxWidth: 420),
|
||||
child: Material(
|
||||
color: cs.surfaceContainerHigh,
|
||||
elevation: 3,
|
||||
borderRadius: BorderRadius.circular(28),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12),
|
||||
child: Slider(
|
||||
min: 1,
|
||||
max: total.toDouble(),
|
||||
value: (_scrub ?? (_pageIndex + 1).toDouble())
|
||||
.clamp(1, total.toDouble()),
|
||||
label: '$shown',
|
||||
divisions: total - 1,
|
||||
onChanged: (v) => setState(() => _scrub = v),
|
||||
onChangeEnd: (v) {
|
||||
setState(() => _scrub = null);
|
||||
_goToPage(v.round() - 1);
|
||||
},
|
||||
),
|
||||
),
|
||||
IconButton(
|
||||
tooltip: 'Next page',
|
||||
icon: const Icon(Icons.chevron_right),
|
||||
onPressed: _pageIndex < total - 1
|
||||
? () => _goToPage(_pageIndex + 1)
|
||||
: null,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
// Compact pill — always; fits content (no big frame).
|
||||
Material(
|
||||
color: cs.surfaceContainerHigh,
|
||||
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