fix: restore PDF pen capture and overhaul sticky/pens/pages
All checks were successful
CI / Windows build (push) Successful in 9m55s
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:
@@ -8,6 +8,7 @@ import 'package:flutter/material.dart';
|
||||
|
||||
import '../../l10n/app_localizations.dart';
|
||||
import '../engine/brush.dart';
|
||||
import '../input/pen_slots.dart';
|
||||
import 'editor_tool.dart';
|
||||
|
||||
/// Shared ink color palette for PDF / note / slide / scratch editors.
|
||||
@@ -87,8 +88,8 @@ IconData shapeIcon(ShapeKind kind) => switch (kind) {
|
||||
ShapeKind.arrow => Icons.arrow_outward,
|
||||
};
|
||||
|
||||
/// OneNote-style pen slot: each brush is its own toolbar button with a color
|
||||
/// underline (per-brush remembered color). Prefer this over [BrushPickerButton]
|
||||
/// OneNote-style pen slot: each slot is its own toolbar button with a color
|
||||
/// underline (slot-remembered color). Prefer this over [BrushPickerButton]
|
||||
/// when the UX wants pens visible side-by-side.
|
||||
class PenSlotButton extends StatelessWidget {
|
||||
const PenSlotButton({
|
||||
@@ -98,6 +99,7 @@ class PenSlotButton extends StatelessWidget {
|
||||
required this.color,
|
||||
required this.tooltip,
|
||||
required this.onPressed,
|
||||
this.widthHint,
|
||||
});
|
||||
|
||||
final BrushKind kind;
|
||||
@@ -106,11 +108,18 @@ class PenSlotButton extends StatelessWidget {
|
||||
final String tooltip;
|
||||
final VoidCallback onPressed;
|
||||
|
||||
/// Optional page-width fraction; when set, underline height scales slightly
|
||||
/// so thicker slots read visually thicker. Null keeps the fixed 3px bar.
|
||||
final double? widthHint;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final cs = Theme.of(context).colorScheme;
|
||||
final iconColor =
|
||||
selected ? cs.onSecondaryContainer : cs.onSurfaceVariant;
|
||||
final barHeight = widthHint == null
|
||||
? 3.0
|
||||
: (2.0 + (widthHint! / kThicknessLarge).clamp(0.0, 1.0) * 3.0);
|
||||
return Tooltip(
|
||||
message: tooltip,
|
||||
child: InkWell(
|
||||
@@ -131,7 +140,7 @@ class PenSlotButton extends StatelessWidget {
|
||||
const SizedBox(height: 3),
|
||||
Container(
|
||||
width: 16,
|
||||
height: 3,
|
||||
height: barHeight,
|
||||
decoration: BoxDecoration(
|
||||
color: color,
|
||||
borderRadius: BorderRadius.circular(2),
|
||||
@@ -145,6 +154,119 @@ class PenSlotButton extends StatelessWidget {
|
||||
}
|
||||
}
|
||||
|
||||
/// Compact thickness control: S / M / L presets + a custom slider. Writes the
|
||||
/// chosen page-width fraction via [onChanged] (typically
|
||||
/// [PenSlotsController.setActiveWidth]).
|
||||
class ThicknessPickerButton extends StatelessWidget {
|
||||
const ThicknessPickerButton({
|
||||
super.key,
|
||||
required this.width,
|
||||
required this.onChanged,
|
||||
this.tooltip = 'Thickness',
|
||||
});
|
||||
|
||||
/// Current stroke width (page-width fraction).
|
||||
final double width;
|
||||
|
||||
final ValueChanged<double> onChanged;
|
||||
final String tooltip;
|
||||
|
||||
static String _labelFor(double w) {
|
||||
if ((w - kThicknessSmall).abs() < 0.0003) return 'S';
|
||||
if ((w - kThicknessMedium).abs() < 0.0003) return 'M';
|
||||
if ((w - kThicknessLarge).abs() < 0.0003) return 'L';
|
||||
return '·';
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final cs = Theme.of(context).colorScheme;
|
||||
return PopupMenuButton<double>(
|
||||
tooltip: tooltip,
|
||||
onSelected: onChanged,
|
||||
itemBuilder: (context) => [
|
||||
PopupMenuItem<double>(
|
||||
value: kThicknessSmall,
|
||||
child: Row(
|
||||
children: [
|
||||
const Text('S'),
|
||||
const Spacer(),
|
||||
if ((width - kThicknessSmall).abs() < 0.0003)
|
||||
Icon(Icons.check, size: 18, color: cs.primary),
|
||||
],
|
||||
),
|
||||
),
|
||||
PopupMenuItem<double>(
|
||||
value: kThicknessMedium,
|
||||
child: Row(
|
||||
children: [
|
||||
const Text('M'),
|
||||
const Spacer(),
|
||||
if ((width - kThicknessMedium).abs() < 0.0003)
|
||||
Icon(Icons.check, size: 18, color: cs.primary),
|
||||
],
|
||||
),
|
||||
),
|
||||
PopupMenuItem<double>(
|
||||
value: kThicknessLarge,
|
||||
child: Row(
|
||||
children: [
|
||||
const Text('L'),
|
||||
const Spacer(),
|
||||
if ((width - kThicknessLarge).abs() < 0.0003)
|
||||
Icon(Icons.check, size: 18, color: cs.primary),
|
||||
],
|
||||
),
|
||||
),
|
||||
PopupMenuItem<double>(
|
||||
enabled: false,
|
||||
child: SizedBox(
|
||||
width: 180,
|
||||
child: StatefulBuilder(
|
||||
builder: (context, setLocal) {
|
||||
final v = width.clamp(kPenSlotWidthMin, kPenSlotWidthMax);
|
||||
return Slider(
|
||||
value: v,
|
||||
min: kPenSlotWidthMin,
|
||||
max: kPenSlotWidthMax,
|
||||
onChanged: (next) {
|
||||
onChanged(next);
|
||||
setLocal(() {});
|
||||
},
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
child: AnimatedContainer(
|
||||
duration: const Duration(milliseconds: 150),
|
||||
margin: const EdgeInsets.symmetric(horizontal: 2),
|
||||
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 8),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.transparent,
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Icon(Icons.line_weight, size: 20, color: cs.onSurfaceVariant),
|
||||
const SizedBox(width: 2),
|
||||
Text(
|
||||
_labelFor(width),
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: cs.onSurfaceVariant,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// A dropdown that selects the active PEN brush (fountain / ballpoint / pencil).
|
||||
///
|
||||
/// Highlighter and eraser remain separate tools. Tapping the button opens a
|
||||
|
||||
Reference in New Issue
Block a user