fix: restore PDF pen capture and overhaul sticky/pens/pages
All checks were successful
CI / Windows build (push) Successful in 9m55s

Reinstall PenCaptureBinding so stylus ink hits again; keep finger Listener translucent under pinch; page-anchor sticky with drag/resize; OneNote pen slots (brush+width+color); blank-note multi-page; default side button to hold-select-text.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-08-07 02:38:58 +08:00
parent ad9b1b46db
commit 307161f465
16 changed files with 1279 additions and 487 deletions

View File

@@ -19,6 +19,7 @@ import '../engine/brush.dart';
import '../engine/shape_geometry.dart';
import '../input/pen_config.dart';
import '../input/pen_input_service.dart';
import '../input/pen_slots.dart';
import '../input/pressure_curve.dart' show kNaturalPressureGamma;
import '../layout/viewport_fit.dart';
import '../pdf/slide_export.dart';
@@ -58,29 +59,21 @@ class _PenSlideScreenState extends State<PenSlideScreen> {
/// The single active-tool state (shared model across the 3 editors).
EditorToolKind _tool = EditorToolKind.brush;
/// Selected brush for the BRUSH tool. Highlighter tool uses the highlighter
/// brush; local state only (not persisted — TODO(brush-persist-selection)).
BrushKind _penBrush = BrushKind.fountainPen;
/// Selected shape for the SHAPE tool.
ShapeKind _shapeKind = ShapeKind.line;
/// Index of the currently selected committed stroke (SELECT tool), or null.
int? _selectedStroke;
/// rnote-style per-brush color memory (see PenNoteScreen). In-memory only.
final Map<BrushKind, Color> _brushColors = {
BrushKind.fountainPen: Colors.black,
BrushKind.ballpoint: Colors.blue,
BrushKind.pencil: Colors.green,
BrushKind.highlighter: Colors.orange,
};
/// Highlighter keeps its own color (not a pen slot).
Color _highlighterColor = Colors.orange;
BrushKind get _activeColorBrush => _tool == EditorToolKind.highlighter
? BrushKind.highlighter
: _penBrush;
BrushKind get _penBrush =>
_penSlots?.active.brush ?? BrushKind.fountainPen;
Color get _color => _brushColors[_activeColorBrush] ?? Colors.black;
Color get _color => _tool == EditorToolKind.highlighter
? _highlighterColor
: (_penSlots?.active.color ?? Colors.black);
bool _allowFingerDrawing = false;
bool _needsCenter = true;
@@ -88,9 +81,9 @@ class _PenSlideScreenState extends State<PenSlideScreen> {
double? _scrub;
PenConfigController? _penConfig;
PenSlotsController? _penSlots;
final TransformationController _transform = TransformationController();
static const double _penWidthFraction = 0.006;
static const double _highlighterWidthFraction = 0.02;
static const Size _fallbackSlide = Size(1600, 900);
@@ -125,15 +118,23 @@ class _PenSlideScreenState extends State<PenSlideScreen> {
}
Future<void> _initPenConfig() async {
final controller = await PenConfigController.load();
final results = await Future.wait([
PenConfigController.load(),
PenSlotsController.load(),
]);
final config = results[0] as PenConfigController;
final slots = results[1] as PenSlotsController;
if (!mounted) {
controller.dispose();
config.dispose();
slots.dispose();
return;
}
controller.addListener(_onPenConfigChanged);
config.addListener(_onPenConfigChanged);
slots.addListener(_onPenSlotsChanged);
setState(() {
_penConfig = controller;
_allowFingerDrawing = controller.value.fingerDrawing;
_penConfig = config;
_penSlots = slots;
_allowFingerDrawing = config.value.fingerDrawing;
});
}
@@ -141,10 +142,16 @@ class _PenSlideScreenState extends State<PenSlideScreen> {
if (mounted) setState(() {});
}
void _onPenSlotsChanged() {
if (mounted) setState(() {});
}
@override
void dispose() {
_penConfig?.removeListener(_onPenConfigChanged);
_penConfig?.dispose();
_penSlots?.removeListener(_onPenSlotsChanged);
_penSlots?.dispose();
_transform.dispose();
super.dispose();
}
@@ -300,7 +307,7 @@ class _PenSlideScreenState extends State<PenSlideScreen> {
double get _strokeWidth => _tool == EditorToolKind.highlighter
? (_penConfig?.value.highlighterWidth ?? _highlighterWidthFraction)
: (_penConfig?.value.penWidth ?? _penWidthFraction);
: (_penSlots?.active.width ?? 0.006);
// ── SELECT tool: select / move / delete (per-slide, reuses the undo stacks) ──
@@ -465,17 +472,19 @@ class _PenSlideScreenState extends State<PenSlideScreen> {
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
// OneNote-style: each pen is its own slot with remembered color.
for (final b in kPenToolBrushes)
// OneNote-style: each pen slot restores brush + color + thickness.
for (final slot in _penSlots?.slots ?? kDefaultPenSlots())
PenSlotButton(
kind: b,
selected: _tool == EditorToolKind.brush && _penBrush == b,
color: _brushColors[b] ?? Colors.black,
tooltip: brushLabelEn(b),
onPressed: () => setState(() {
_penBrush = b;
_tool = EditorToolKind.brush;
}),
kind: slot.brush,
selected: _tool == EditorToolKind.brush &&
(_penSlots?.activeId ?? 'slot_0') == slot.id,
color: slot.color,
widthHint: slot.width,
tooltip: brushLabelEn(slot.brush),
onPressed: () {
_penSlots?.select(slot.id);
setState(() => _tool = EditorToolKind.brush);
},
),
ToolButton(
icon: Icons.brush_outlined,
@@ -530,6 +539,10 @@ class _PenSlideScreenState extends State<PenSlideScreen> {
),
PaletteDivider(cs: cs),
for (final c in _palette) _colorDot(c, cs),
ThicknessPickerButton(
width: _penSlots?.active.width ?? 0.006,
onChanged: (w) => _penSlots?.setActiveWidth(w),
),
PaletteDivider(cs: cs),
ToolButton(
icon: _allowFingerDrawing ? Icons.touch_app : Icons.do_not_touch,
@@ -558,13 +571,17 @@ class _PenSlideScreenState extends State<PenSlideScreen> {
_tool != EditorToolKind.eraser &&
_tool != EditorToolKind.select;
return GestureDetector(
onTap: () => setState(() {
onTap: () {
if (_tool == EditorToolKind.eraser ||
_tool == EditorToolKind.select) {
_tool = EditorToolKind.brush;
setState(() => _tool = EditorToolKind.brush);
}
_brushColors[_activeColorBrush] = c;
}),
if (_tool == EditorToolKind.highlighter) {
setState(() => _highlighterColor = c);
} else {
_penSlots?.setActiveColor(c);
}
},
child: AnimatedContainer(
duration: const Duration(milliseconds: 150),
margin: const EdgeInsets.symmetric(horizontal: 3),