feat: unified shell, diagnostics pack, native Office, sticky board
All checks were successful
CI / Windows build (push) Successful in 14m22s
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:
@@ -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);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user