Custom drawing implementation
This commit is contained in:
189
lib/features/sheet_viewer/drawing/drawing_controller.dart
Normal file
189
lib/features/sheet_viewer/drawing/drawing_controller.dart
Normal file
@@ -0,0 +1,189 @@
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'drawing_line.dart';
|
||||
import 'paint_preset.dart';
|
||||
|
||||
/// Controller for managing drawing state with undo/redo support.
|
||||
///
|
||||
/// Manages a stack of [DrawingLine] objects and provides methods for
|
||||
/// drawing, undoing, redoing, and serializing the drawing state.
|
||||
class DrawingController extends ChangeNotifier {
|
||||
/// All completed lines in the drawing
|
||||
final List<DrawingLine> _lines = [];
|
||||
|
||||
/// Lines that have been undone (for redo functionality)
|
||||
final List<DrawingLine> _undoneLines = [];
|
||||
|
||||
/// The line currently being drawn (null when not drawing)
|
||||
DrawingLine? _currentLine;
|
||||
|
||||
/// Current paint preset being used
|
||||
PaintPreset _currentPreset = PaintPreset.blackPen;
|
||||
|
||||
/// Maximum number of history steps to keep
|
||||
final int maxHistorySteps;
|
||||
|
||||
DrawingController({this.maxHistorySteps = 50});
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Getters
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/// All completed lines (read-only)
|
||||
List<DrawingLine> get lines => List.unmodifiable(_lines);
|
||||
|
||||
/// The line currently being drawn
|
||||
DrawingLine? get currentLine => _currentLine;
|
||||
|
||||
/// Current paint preset
|
||||
PaintPreset get currentPreset => _currentPreset;
|
||||
|
||||
/// Whether undo is available
|
||||
bool get canUndo => _lines.isNotEmpty;
|
||||
|
||||
/// Whether redo is available
|
||||
bool get canRedo => _undoneLines.isNotEmpty;
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Drawing Operations
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/// Starts a new line at the given normalized position.
|
||||
void startLine(Offset normalizedPoint) {
|
||||
_currentLine = DrawingLine(
|
||||
points: [normalizedPoint],
|
||||
color: _currentPreset.color,
|
||||
strokeWidth: _currentPreset.strokeWidth,
|
||||
);
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
/// Adds a point to the current line.
|
||||
void addPoint(Offset normalizedPoint) {
|
||||
if (_currentLine == null) return;
|
||||
|
||||
_currentLine = _currentLine!.addPoint(normalizedPoint);
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
/// Completes the current line and adds it to the history.
|
||||
void endLine() {
|
||||
if (_currentLine == null) return;
|
||||
|
||||
// Only add lines with at least 2 points
|
||||
if (_currentLine!.points.length >= 2) {
|
||||
_lines.add(_currentLine!);
|
||||
// Clear redo stack when new action is performed
|
||||
_undoneLines.clear();
|
||||
_trimHistory();
|
||||
}
|
||||
|
||||
_currentLine = null;
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
/// Trims history to maxHistorySteps to prevent memory growth.
|
||||
void _trimHistory() {
|
||||
while (_lines.length > maxHistorySteps) {
|
||||
_lines.removeAt(0);
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Undo/Redo
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/// Undoes the last line drawn.
|
||||
void undo() {
|
||||
if (!canUndo) return;
|
||||
|
||||
final line = _lines.removeLast();
|
||||
_undoneLines.add(line);
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
/// Redoes the last undone line.
|
||||
void redo() {
|
||||
if (!canRedo) return;
|
||||
|
||||
final line = _undoneLines.removeLast();
|
||||
_lines.add(line);
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
/// Clears all lines from the canvas.
|
||||
void clear() {
|
||||
_lines.clear();
|
||||
_undoneLines.clear();
|
||||
_currentLine = null;
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Paint Preset
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/// Sets the current paint preset.
|
||||
void setPreset(PaintPreset preset) {
|
||||
_currentPreset = preset;
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Serialization
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/// Exports all lines to a JSON-serializable list.
|
||||
List<Map<String, dynamic>> toJsonList() {
|
||||
return _lines.map((line) => line.toJson()).toList();
|
||||
}
|
||||
|
||||
/// Exports all lines to a JSON string.
|
||||
String toJsonString() {
|
||||
return jsonEncode(toJsonList());
|
||||
}
|
||||
|
||||
/// Imports lines from a JSON-serializable list.
|
||||
void fromJsonList(List<Map<String, dynamic>> jsonList) {
|
||||
_lines.clear();
|
||||
_undoneLines.clear();
|
||||
_currentLine = null;
|
||||
|
||||
for (final json in jsonList) {
|
||||
_lines.add(DrawingLine.fromJson(json));
|
||||
}
|
||||
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
/// Imports lines from a JSON string.
|
||||
void fromJsonString(String jsonString) {
|
||||
if (jsonString.isEmpty || jsonString == '[]') {
|
||||
clear();
|
||||
return;
|
||||
}
|
||||
|
||||
final decoded = jsonDecode(jsonString) as List;
|
||||
if (decoded.isEmpty) {
|
||||
clear();
|
||||
return;
|
||||
}
|
||||
fromJsonList(decoded.cast<Map<String, dynamic>>());
|
||||
}
|
||||
|
||||
/// Adds existing lines without clearing (for merging annotations).
|
||||
void addLines(List<DrawingLine> newLines) {
|
||||
_lines.addAll(newLines);
|
||||
_trimHistory();
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_lines.clear();
|
||||
_undoneLines.clear();
|
||||
super.dispose();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user