cid_app/lib/models.dart

198 lines
6.6 KiB
Dart
Raw Permalink Normal View History

2026-02-27 23:26:13 +01:00
import 'dart:typed_data';
import 'dart:ui' as ui;
import 'package:flutter/material.dart';
import 'dart:math' as math; // Serve per i calcoli della freccia
// --- PAINTER PER IL DISEGNO (A SCHERMO E SU PDF) ---
class PainterV40 extends CustomPainter {
final List<TrattoPenna> tr;
final List<ElementoGrafico> el;
PainterV40(this.tr, this.el);
@override
void paint(Canvas canvas, Size size) {
// Stile della penna (Strada/Linee)
Paint pStrada = Paint()
..color = Colors.black
..strokeWidth = 3.0
..style = PaintingStyle.stroke
..strokeCap = StrokeCap.round;
// 1. DISEGNO TRATTI (PENNA E FRECCE)
for (var t in tr) {
if (t.punti.length > 1) {
Path path = Path()..moveTo(t.punti[0].dx, t.punti[0].dy);
for (var pt in t.punti) path.lineTo(pt.dx, pt.dy);
canvas.drawPath(path, pStrada);
// Se è una freccia, disegna la punta alla fine
if (t.tipo == 'freccia') {
// Prendi gli ultimi due punti per calcolare l'angolazione
_disegnaPunta(canvas, t.punti[t.punti.length - 2], t.punti.last, pStrada);
}
}
}
// 2. DISEGNO ELEMENTI (AUTO E TESTO)
for (var e in el) {
canvas.save();
// Sposta e ruota il canvas nella posizione dell'elemento
canvas.translate(e.posizione.dx, e.posizione.dy);
canvas.rotate(e.rotazione);
if (e.tipo == 'testo') {
_disegnaTesto(canvas, e.label ?? "");
} else {
_disegnaAuto(canvas, e.tipo == 'autoA' ? 'A' : 'B', e.tipo == 'autoA' ? Colors.blue : Colors.orange);
}
canvas.restore();
}
}
// Disegna il rettangolo dell'auto con la lettera
void _disegnaAuto(Canvas canvas, String lettera, Color colore) {
Paint p = Paint()..color = colore;
// Auto centrata (70x40 px)
Rect r = Rect.fromCenter(center: Offset.zero, width: 70, height: 40);
canvas.drawRect(r, p);
// Bordo nero auto
canvas.drawRect(r, Paint()..color = Colors.black..style = PaintingStyle.stroke..strokeWidth = 2);
// Lettera centrata
final tp = TextPainter(
text: TextSpan(text: lettera, style: const TextStyle(color: Colors.white, fontWeight: FontWeight.bold, fontSize: 16)),
textDirection: TextDirection.ltr
)..layout();
tp.paint(canvas, Offset(-tp.width / 2, -tp.height / 2));
}
// Disegna etichette di testo
void _disegnaTesto(Canvas canvas, String txt) {
final tp = TextPainter(
text: TextSpan(text: txt, style: const TextStyle(color: Colors.black, fontSize: 16, fontWeight: FontWeight.bold, backgroundColor: Colors.white70)),
textDirection: TextDirection.ltr
)..layout();
tp.paint(canvas, Offset(-tp.width / 2, -tp.height / 2));
}
// Calcola e disegna la punta della freccia
void _disegnaPunta(Canvas canvas, Offset p1, Offset p2, Paint paint) {
double angle = (p2 - p1).direction;
// Disegna due linee inclinate rispetto alla direzione finale
canvas.drawLine(p2, p2 - Offset.fromDirection(angle - 0.5, 15), paint);
canvas.drawLine(p2, p2 - Offset.fromDirection(angle + 0.5, 15), paint);
}
@override
bool shouldRepaint(covariant CustomPainter oldDelegate) => true;
}
// --- MODELLO DATI: TRATTO PENNA ---
class TrattoPenna {
List<Offset> punti;
String tipo; // 'penna' o 'freccia'
TrattoPenna(this.punti, {this.tipo = 'penna'});
// Metodo utile per la cancellazione (hit test)
bool contiene(Offset p) {
for (var punto in punti) {
if ((p - punto).distance < 20.0) return true;
}
return false;
}
Map<String, dynamic> toMap() => {
'punti': punti.map((p) => {'dx': p.dx, 'dy': p.dy}).toList(),
'tipo': tipo,
};
factory TrattoPenna.fromMap(Map<String, dynamic> map) => TrattoPenna(
(map['punti'] as List).map((p) => Offset(p['dx'], p['dy'])).toList(),
tipo: map['tipo'] ?? 'penna',
);
}
// --- MODELLO DATI: ELEMENTO GRAFICO (AUTO/TESTO) ---
class ElementoGrafico {
Offset posizione;
String tipo; // 'autoA', 'autoB', 'testo'
double rotazione;
String? label;
ElementoGrafico(this.posizione, this.tipo, {this.rotazione = 0, this.label});
Map<String, dynamic> toMap() => {
'pos': {'dx': posizione.dx, 'dy': posizione.dy},
'tipo': tipo,
'rot': rotazione,
'label': label,
};
factory ElementoGrafico.fromMap(Map<String, dynamic> map) => ElementoGrafico(
Offset(map['pos']['dx'], map['pos']['dy']),
map['tipo'],
rotazione: (map['rot'] as num?)?.toDouble() ?? 0,
label: map['label'],
);
bool contiene(Offset p) => (p - posizione).distance < 35;
// --- METODO CRUCIALE PER IL PDF ---
// Genera un'immagine PNG ritagliata e ottimizzata del grafico
static Future<Uint8List?> fondiGraficoDinamica(List<dynamic> trattiRaw, List<dynamic> elementiRaw) async {
// Conversione sicura dei tipi (nel caso arrivino come dynamic da GlobalData)
List<TrattoPenna> tratti = trattiRaw.cast<TrattoPenna>();
List<ElementoGrafico> elementi = elementiRaw.cast<ElementoGrafico>();
if (tratti.isEmpty && elementi.isEmpty) return null;
// 1. Calcolo Bounding Box (i confini del disegno)
double minX = double.infinity, minY = double.infinity;
double maxX = double.negativeInfinity, maxY = double.negativeInfinity;
void checkPoint(Offset p) {
if (p.dx < minX) minX = p.dx;
if (p.dx > maxX) maxX = p.dx;
if (p.dy < minY) minY = p.dy;
if (p.dy > maxY) maxY = p.dy;
}
for (var t in tratti) { for (var p in t.punti) checkPoint(p); }
for (var e in elementi) checkPoint(e.posizione);
// Se non ci sono dimensioni valide, esci
if (minX == double.infinity) return null;
// Aggiungiamo margine (padding) bianco intorno
const double pad = 40.0;
double width = (maxX - minX) + (pad * 2);
double height = (maxY - minY) + (pad * 2);
// 2. Disegno su Canvas off-screen
final recorder = ui.PictureRecorder();
// Crea canvas delle dimensioni esatte
final canvas = ui.Canvas(recorder, Rect.fromLTWH(0, 0, width, height));
// Sfondo Bianco (Copre la griglia del modulo sottostante)
canvas.drawRect(Rect.fromLTWH(0, 0, width, height), Paint()..color = Colors.white);
// Sposta l'origine del canvas per centrare il disegno ed eliminare lo spazio vuoto in alto/sinistra
canvas.translate(-minX + pad, -minY + pad);
// Usa il painter esistente per ridisegnare tutto
final painter = PainterV40(tratti, elementi);
painter.paint(canvas, Size(width, height));
// 3. Conversione in PNG
final picture = recorder.endRecording();
final img = await picture.toImage(width.toInt(), height.toInt());
final pngBytes = await img.toByteData(format: ui.ImageByteFormat.png);
return pngBytes?.buffer.asUint8List();
}
}