190 lines
5.0 KiB
Dart
190 lines
5.0 KiB
Dart
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();
|
|
}
|
|
}
|