feat(pen): apply pressure-response curve for natural feel

"手写笔就是一个带压感的手指,没有特殊适配" — correct. The new editor fed
RAW LINEAR stylus pressure into perfect_freehand, and the pressureGamma
config (a slider in pen-settings) was read ONLY by that slider's UI and
NEVER applied to a stroke. Dead wiring, like the rest.

Wire it for real:
- PenCanvas applies PressureCurve(floor, gamma) at capture, so stored
  pressure carries the feel and live + PDF export replay identically.
- Natural defaults: gamma 0.7 (light touches register more width, rnote/
  OneNote-like) + floor 0.12 (thin strokes keep body, not scratchy).
- pen_editor threads PenConfig.pressureGamma into the canvas — the slider
  now actually changes stroke width.
- pen_config: natural default + one-time migration of the legacy inert
  gamma 1.0, guarded by a marker so a deliberate 1.0 still sticks.

Tests: pen_config_gamma_migration (4) + pressure_curve (6) pass; pen
widget regressions green. flutter analyze: 0 issues.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-23 09:43:56 +08:00
parent 1d70c029b3
commit 299b9546a8
5 changed files with 119 additions and 2 deletions

View File

@@ -17,6 +17,7 @@ import '../engine/undo_stack.dart';
import '../input/diagnostic_logger.dart';
import '../input/pen_config.dart';
import '../input/pen_input_service.dart';
import '../input/pressure_curve.dart' show kNaturalPressureGamma;
import '../layout/viewport_fit.dart';
import '../persistence/editor_repository.dart';
import '../persistence/save_scheduler.dart';
@@ -558,6 +559,11 @@ class _PenEditorScreenState extends State<PenEditorScreen> {
: (_penConfig?.value.penWidth ?? _penWidthFraction),
thinning:
_penConfig?.value.pressureSensitivity ?? kDefaultPenThinning,
// Pressure-response shaping (the rnote-like feel). The pen-settings
// gamma slider now actually drives stroke width; fall back to the
// natural default when no config is loaded yet.
pressureGamma:
_penConfig?.value.pressureGamma ?? kNaturalPressureGamma,
sideButtonAction:
_penConfig?.value.sideButton ?? PenButtonAction.eraser,
eraserEndAction: