Files
sheetless/lib/features/sheet_viewer/drawing/drawing_controller.dart

192 lines
5.1 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;
if (!currentLine!.isPointTooClose(normalizedPoint)) {
_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();
}
}