"手写笔就是一个带压感的手指,没有特殊适配" — 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>
50 lines
2.1 KiB
Dart
50 lines
2.1 KiB
Dart
// lib/editor/input/pressure_curve.dart
|
||
//
|
||
// Configurable pen-pressure response (F5 — the user's repeated "可配置笔" ask).
|
||
// Raw normalized stylus pressure [0,1] is pre-shaped here before it reaches
|
||
// perfect_freehand, giving two user-facing knobs:
|
||
// - [floor]: a minimum output (the plan's marker "min-width floor" — a fixed-
|
||
// pressure marker uses floor≈1.0; a pen uses 0.0).
|
||
// - [gamma]: the response exponent — γ<1 makes light touches register more
|
||
// width (more sensitive), γ>1 requires firmer pressure (less sensitive).
|
||
//
|
||
// Pure value type (widget-free, storage-free) so the full mapping is unit
|
||
// tested; PenConfig / the canvas wire it later (the wiring touches the live
|
||
// draw path and is validated on-device).
|
||
|
||
import 'dart:math' as math;
|
||
|
||
/// Default pressure-response exponent. <1 so light-to-medium pressure registers
|
||
/// more width — the responsive, rnote/OneNote-like feel — instead of the raw
|
||
/// linear mapping that made the pen feel like a pressure-sensitive finger.
|
||
const double kNaturalPressureGamma = 0.7;
|
||
|
||
/// Default minimum shaped pressure: even the lightest touch keeps ~12% of the
|
||
/// dynamic range so thin strokes have body instead of scratchy near-zero width.
|
||
const double kNaturalPressureFloor = 0.12;
|
||
|
||
/// Maps raw normalized pressure to a shaped response in `[floor, 1]`.
|
||
class PressureCurve {
|
||
const PressureCurve({this.floor = 0.0, this.gamma = 1.0})
|
||
: assert(floor >= 0.0 && floor < 1.0),
|
||
assert(gamma > 0.0);
|
||
|
||
/// Minimum output (>=0, <1). 0 = full dynamic range; raise toward 1 for a
|
||
/// fixed-pressure feel (marker).
|
||
final double floor;
|
||
|
||
/// Response exponent (>0). 1 = linear; <1 = more sensitive at light pressure;
|
||
/// >1 = firmer.
|
||
final double gamma;
|
||
|
||
/// Linear, full-range pen response (identity).
|
||
static const PressureCurve linear = PressureCurve();
|
||
|
||
/// Shape [pressure] (clamped to [0,1]) into `[floor, 1]`.
|
||
double apply(double pressure) {
|
||
final p = pressure.isNaN ? 0.0 : pressure.clamp(0.0, 1.0);
|
||
final shaped = gamma == 1.0 ? p : math.pow(p, gamma).toDouble();
|
||
return floor + (1.0 - floor) * shaped;
|
||
}
|
||
}
|