feat(eraser): configurable size + stroke-eraser mode
Some checks failed
CI / Windows build (push) Has been cancelled
Some checks failed
CI / Windows build (push) Has been cancelled
"优化橡皮擦工具,你优化在哪" — the eraser already did segment erase, but the radius was a hardcoded const with no size control and no whole-stroke mode. Add both, OneNote/Notability-style: - PenConfig: eraserRadius (0.005-0.1, default 0.02) + eraserWholeStroke bool, with copyWith / JSON / setters. - PenCanvas: uses widget.eraserRadius for the live hit area AND the cursor preview (they stay in sync); eraserWholeStroke=true removes the whole stroke on contact, false keeps the segment-split behavior. - pen_editor threads both from PenConfig. - pen-settings: new Eraser section — size slider + "Stroke Eraser" switch. Tests: pen_eraser_mode_widget proves point-eraser keeps the untouched ends while stroke-eraser deletes the whole stroke from the same pass. flutter analyze: 0 issues. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -7,6 +7,10 @@ import 'package:shared_preferences/shared_preferences.dart';
|
||||
import '../engine/stroke_geometry.dart' show kDefaultPenThinning;
|
||||
import 'pressure_curve.dart' show kNaturalPressureGamma;
|
||||
|
||||
/// Default eraser radius as a fraction of page width (the legacy fixed value,
|
||||
/// now the default of the configurable [PenConfig.eraserRadius]).
|
||||
const double kDefaultEraserRadius = 0.02;
|
||||
|
||||
/// Action that can be triggered by a hardware pen button or the eraser end.
|
||||
enum PenButtonAction {
|
||||
none,
|
||||
@@ -29,12 +33,16 @@ class PenConfig {
|
||||
this.penWidth = 0.004,
|
||||
this.highlighterWidth = 0.02,
|
||||
this.pressureSensitivity = kDefaultPenThinning,
|
||||
this.eraserRadius = kDefaultEraserRadius,
|
||||
this.eraserWholeStroke = false,
|
||||
}) : assert(pressureGamma >= 0.3 && pressureGamma <= 3.0,
|
||||
'pressureGamma must be in [0.3, 3.0]'),
|
||||
assert(palmRejectionMs >= 0.0 && palmRejectionMs <= 500.0,
|
||||
'palmRejectionMs must be in [0, 500]'),
|
||||
assert(pressureSensitivity >= 0.0 && pressureSensitivity <= 1.0,
|
||||
'pressureSensitivity must be in [0, 1]');
|
||||
'pressureSensitivity must be in [0, 1]'),
|
||||
assert(eraserRadius >= 0.005 && eraserRadius <= 0.1,
|
||||
'eraserRadius must be in [0.005, 0.1]');
|
||||
|
||||
/// Which action fires when the side barrel button is held.
|
||||
final PenButtonAction sideButton;
|
||||
@@ -67,6 +75,16 @@ class PenConfig {
|
||||
/// export golden are unchanged.
|
||||
final double pressureSensitivity;
|
||||
|
||||
/// Eraser radius as a fraction of page width. Range [0.005, 0.1], default
|
||||
/// [kDefaultEraserRadius]. Controls both the live erase hit area and the
|
||||
/// on-screen eraser cursor.
|
||||
final double eraserRadius;
|
||||
|
||||
/// When true the eraser removes a WHOLE stroke on contact (OneNote-style
|
||||
/// stroke eraser); when false it does a partial / segment erase (the default,
|
||||
/// rnote-style point eraser).
|
||||
final bool eraserWholeStroke;
|
||||
|
||||
PenConfig copyWith({
|
||||
PenButtonAction? sideButton,
|
||||
PenButtonAction? eraserEnd,
|
||||
@@ -76,6 +94,8 @@ class PenConfig {
|
||||
double? penWidth,
|
||||
double? highlighterWidth,
|
||||
double? pressureSensitivity,
|
||||
double? eraserRadius,
|
||||
bool? eraserWholeStroke,
|
||||
}) {
|
||||
return PenConfig(
|
||||
sideButton: sideButton ?? this.sideButton,
|
||||
@@ -86,6 +106,8 @@ class PenConfig {
|
||||
penWidth: penWidth ?? this.penWidth,
|
||||
highlighterWidth: highlighterWidth ?? this.highlighterWidth,
|
||||
pressureSensitivity: pressureSensitivity ?? this.pressureSensitivity,
|
||||
eraserRadius: eraserRadius ?? this.eraserRadius,
|
||||
eraserWholeStroke: eraserWholeStroke ?? this.eraserWholeStroke,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -98,6 +120,8 @@ class PenConfig {
|
||||
'penWidth': penWidth,
|
||||
'highlighterWidth': highlighterWidth,
|
||||
'pressureSensitivity': pressureSensitivity,
|
||||
'eraserRadius': eraserRadius,
|
||||
'eraserWholeStroke': eraserWholeStroke,
|
||||
};
|
||||
|
||||
factory PenConfig.fromJson(Map<String, dynamic> json) {
|
||||
@@ -116,6 +140,9 @@ class PenConfig {
|
||||
highlighterWidth: (json['highlighterWidth'] as num?)?.toDouble() ?? 0.02,
|
||||
pressureSensitivity:
|
||||
(json['pressureSensitivity'] as num?)?.toDouble() ?? kDefaultPenThinning,
|
||||
eraserRadius:
|
||||
(json['eraserRadius'] as num?)?.toDouble() ?? kDefaultEraserRadius,
|
||||
eraserWholeStroke: json['eraserWholeStroke'] as bool? ?? false,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -131,7 +158,9 @@ class PenConfig {
|
||||
fingerDrawing == other.fingerDrawing &&
|
||||
penWidth == other.penWidth &&
|
||||
highlighterWidth == other.highlighterWidth &&
|
||||
pressureSensitivity == other.pressureSensitivity;
|
||||
pressureSensitivity == other.pressureSensitivity &&
|
||||
eraserRadius == other.eraserRadius &&
|
||||
eraserWholeStroke == other.eraserWholeStroke;
|
||||
|
||||
@override
|
||||
int get hashCode => Object.hash(
|
||||
@@ -143,6 +172,8 @@ class PenConfig {
|
||||
penWidth,
|
||||
highlighterWidth,
|
||||
pressureSensitivity,
|
||||
eraserRadius,
|
||||
eraserWholeStroke,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -253,6 +284,18 @@ class PenConfigController extends ChangeNotifier {
|
||||
}
|
||||
|
||||
/// Sets [PenConfig.pressureSensitivity]. Clamped to [0, 1].
|
||||
/// Sets [PenConfig.eraserRadius]. Clamped to [0.005, 0.1].
|
||||
Future<void> setEraserRadius(double radius) async {
|
||||
_value = _value.copyWith(eraserRadius: radius.clamp(0.005, 0.1));
|
||||
await _persist();
|
||||
}
|
||||
|
||||
/// Sets [PenConfig.eraserWholeStroke] (true = OneNote-style stroke eraser).
|
||||
Future<void> setEraserWholeStroke(bool whole) async {
|
||||
_value = _value.copyWith(eraserWholeStroke: whole);
|
||||
await _persist();
|
||||
}
|
||||
|
||||
Future<void> setPressureSensitivity(double sensitivity) async {
|
||||
_value = _value.copyWith(pressureSensitivity: sensitivity.clamp(0.0, 1.0));
|
||||
notifyListeners();
|
||||
|
||||
Reference in New Issue
Block a user