// Rolling ring of recent pen / arbiter events for diagnostic export. class PenEventRecord { PenEventRecord({ required this.at, required this.kind, required this.pointerId, this.pressure, this.tiltX, this.tiltY, this.barrel = false, this.eraser = false, this.inverted = false, this.decision, this.note, }); final DateTime at; final String kind; // down|move|up|hw|arbiter final int pointerId; final double? pressure; final double? tiltX; final double? tiltY; final bool barrel; final bool eraser; final bool inverted; final String? decision; // draw|pan|reject final String? note; Map toJson() => { 'at': at.toIso8601String(), 'kind': kind, 'pointerId': pointerId, if (pressure != null) 'pressure': pressure, if (tiltX != null) 'tiltX': tiltX, if (tiltY != null) 'tiltY': tiltY, 'barrel': barrel, 'eraser': eraser, 'inverted': inverted, if (decision != null) 'decision': decision, if (note != null) 'note': note, }; } class PenEventRing { PenEventRing._(); static final PenEventRing instance = PenEventRing._(); static const int capacity = 2000; final List _events = []; void add(PenEventRecord event) { _events.add(event); if (_events.length > capacity) { _events.removeRange(0, _events.length - capacity); } } void recordPointer({ required String kind, required int pointerId, required String deviceKind, double? pressure, String? decision, String? note, }) { add(PenEventRecord( at: DateTime.now(), kind: kind, pointerId: pointerId, pressure: pressure, decision: decision, note: note ?? deviceKind, )); } void recordHardware({ required bool barrel, required bool eraser, required bool inverted, required double tiltX, required double tiltY, }) { add(PenEventRecord( at: DateTime.now(), kind: 'hw', pointerId: -1, barrel: barrel, eraser: eraser, inverted: inverted, tiltX: tiltX, tiltY: tiltY, )); } void recordArbiter({ required int activeCount, required String deviceKind, required bool draw, required bool fingerDrawing, }) { add(PenEventRecord( at: DateTime.now(), kind: 'arbiter', pointerId: -1, decision: draw ? 'draw' : 'pan', note: 'count=$activeCount kind=$deviceKind finger=$fingerDrawing', )); } List recent({Duration? window}) { if (window == null) return List.unmodifiable(_events); final cut = DateTime.now().subtract(window); return _events.where((e) => e.at.isAfter(cut)).toList(growable: false); } List> toJsonList({Duration? window}) => recent(window: window).map((e) => e.toJson()).toList(growable: false); void clear() => _events.clear(); }