feat: unified shell, diagnostics pack, native Office, sticky board
All checks were successful
CI / Windows build (push) Successful in 14m22s

Make Surface remote debugging and classroom workflows viable: always-on
structured logs with one-click zip export, a single AppShell chrome,
OOXML PPTX/DOCX annotation without LibreOffice, and a first-class sticky
board. Also drop spike/legacy ink widgets and tighten pen feel
(predictor, PenInfoHistory, page-tile layer).

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-08-05 17:55:27 +08:00
parent 3cabc7e074
commit d346cc2670
49 changed files with 2883 additions and 2515 deletions

View File

@@ -25,12 +25,11 @@ const double kDefaultPenThinning = 0.85;
/// perfect_freehand input-smoothing parameters, shared (single source of truth)
/// by the on-screen painter and the export path so the two can never diverge
/// (guarded by the screen==export parity test). [kPenStreamline] lowers the
/// per-point lag from freehand's 0.5 default to 0.32: at 0.5 a quick flick lags
/// so far behind the pen that a short fast stroke collapsed toward its start and
/// rendered as a dot ("写字识别成单击") and the pen felt sluggish; 0.32 tracks the
/// real path closely (crisper, lower-latency feel) while still damping digitizer
/// jitter. [kPenSmoothing] keeps freehand's 0.5 corner rounding.
const double kPenStreamline = 0.32;
/// per-point lag from freehand's 0.5 default to 0.28: paired with
/// [StrokePredictor] lookahead this tracks the Surface Pen more tightly while
/// still damping digitizer jitter. [kPenSmoothing] keeps freehand's 0.5 corner
/// rounding.
const double kPenStreamline = 0.28;
const double kPenSmoothing = 0.5;
/// THE single perfect_freehand outline recipe — the raw outline points for a

View File

@@ -0,0 +1,56 @@
// Lightweight stroke prediction — extrapolates the next point from recent
// velocity so the live stroke tip leads the digitizer slightly (lower perceived
// latency). Not a full ink-stroke-modeler; intentionally small and testable.
import 'dart:ui';
class PredictedPoint {
const PredictedPoint(this.offset, this.pressure);
final Offset offset;
final double pressure;
}
class StrokePredictor {
StrokePredictor({this.lookaheadMs = 12});
/// How far ahead to project, in milliseconds of recent velocity.
final double lookaheadMs;
Offset? _prev;
double? _prevPressure;
DateTime? _prevAt;
Offset _velocity = Offset.zero;
void reset() {
_prev = null;
_prevPressure = null;
_prevAt = null;
_velocity = Offset.zero;
}
/// Feed a real sample; returns an optional predicted tip ahead of [point].
PredictedPoint? observe(Offset point, double pressure, {DateTime? at}) {
final now = at ?? DateTime.now();
if (_prev != null && _prevAt != null) {
final dtMs = now.difference(_prevAt!).inMicroseconds / 1000.0;
if (dtMs > 0.5 && dtMs < 80) {
final raw = (point - _prev!) * (1000.0 / dtMs);
// EMA blend to avoid jerky predictions.
_velocity = Offset(
_velocity.dx * 0.55 + raw.dx * 0.45,
_velocity.dy * 0.55 + raw.dy * 0.45,
);
}
}
_prev = point;
_prevPressure = pressure;
_prevAt = now;
if (_velocity.distance < 40) return null; // idle / slow — no predict
final tip = point + _velocity * (lookaheadMs / 1000.0);
return PredictedPoint(tip, pressure);
}
/// Last known pressure (for predicted tip).
double get lastPressure => _prevPressure ?? 0.5;
}