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,11 +25,13 @@ import '../engine/brush.dart';
import '../engine/stroke_eraser.dart';
import '../engine/stroke_geometry.dart' show kDefaultPenThinning;
import '../engine/stroke_model.dart';
import '../engine/stroke_predictor.dart';
import '../engine/stroke_store.dart';
import '../input/input_arbiter.dart' as arbiter;
import '../input/pen_config.dart';
import '../input/pressure_curve.dart';
import '../input/pen_input_service.dart';
import '../../diagnostics/pen_event_ring.dart';
import '../engine/shape_geometry.dart';
import '../render/ink_picture_cache.dart';
import '../render/live_ink_painter.dart' as render;
@@ -210,6 +212,9 @@ class _PenCanvasState extends State<PenCanvas> {
/// In-progress stroke points (normalized).
final List<PenPoint> _livePoints = [];
final StrokePredictor _predictor = StrokePredictor();
/// Count of real (non-predicted) points in [_livePoints].
int _realPointCount = 0;
/// Live stroke snapshot handed to the LiveInkPainter; null when idle.
PenStroke? _liveStroke;
@@ -407,12 +412,21 @@ class _PenCanvasState extends State<PenCanvas> {
/// Decide whether the gesture currently forming should DRAW. Delegates to the
/// pure [arbiter.shouldDraw] (unit-tested truth table) so the live canvas and
/// the tests can never disagree on the rule.
bool _shouldDraw(PointerDeviceKind kind) => arbiter.shouldDraw(
activePointerCount: _activePointers.length,
kind: kind,
fingerDrawingEnabled: _fingerDrawingEnabled,
hwPanActive: _hwPanActive,
);
bool _shouldDraw(PointerDeviceKind kind) {
final draw = arbiter.shouldDraw(
activePointerCount: _activePointers.length,
kind: kind,
fingerDrawingEnabled: _fingerDrawingEnabled,
hwPanActive: _hwPanActive,
);
PenEventRing.instance.recordArbiter(
activeCount: _activePointers.length,
deviceKind: kind.name,
draw: draw,
fingerDrawing: _fingerDrawingEnabled,
);
return draw;
}
// --- Coordinate mapping ---------------------------------------------------
@@ -437,6 +451,8 @@ class _PenCanvasState extends State<PenCanvas> {
void _startStroke(PointerDownEvent event) {
_drawPointer = event.pointer;
_livePoints.clear();
_realPointCount = 0;
_predictor.reset();
_shapeStart = null;
_selectLast = null;
_selectDragging = false;
@@ -469,7 +485,10 @@ class _PenCanvasState extends State<PenCanvas> {
return;
}
if (p != null) _livePoints.add(p);
if (p != null) {
_livePoints.add(p);
_realPointCount = _livePoints.length;
}
_updateLiveStroke();
}
@@ -507,7 +526,21 @@ class _PenCanvasState extends State<PenCanvas> {
return;
}
// Drop previous predicted tip before appending the real sample.
if (_livePoints.length > _realPointCount) {
_livePoints.removeRange(_realPointCount, _livePoints.length);
}
_livePoints.add(p);
_realPointCount = _livePoints.length;
final pred = _predictor.observe(Offset(p.x, p.y), p.pressure ?? 0.5);
if (pred != null) {
_livePoints.add(PenPoint(
pred.offset.dx.clamp(0.0, 1.0),
pred.offset.dy.clamp(0.0, 1.0),
pred.pressure,
tilt: p.tilt,
));
}
_updateLiveStroke();
}
@@ -533,6 +566,10 @@ class _PenCanvasState extends State<PenCanvas> {
} else if (tool == CanvasTool.select) {
// Nothing to commit on release: selection + moves were applied live.
} else if (!wasEraser && _livePoints.isNotEmpty) {
// Never commit predicted tips — only real digitizer samples.
if (_livePoints.length > _realPointCount) {
_livePoints.removeRange(_realPointCount, _livePoints.length);
}
widget.onStrokeComplete(
PenStroke(
points: List.of(_livePoints),
@@ -548,6 +585,8 @@ class _PenCanvasState extends State<PenCanvas> {
_selectLast = null;
_selectDragging = false;
_livePoints.clear();
_realPointCount = 0;
_predictor.reset();
_eraserCursor.value = null; // hide the preview when the pen lifts
setState(() => _liveStroke = null);
}