824 lines
23 KiB
Dart
824 lines
23 KiB
Dart
/**
|
|
* P0 Bug 修复补丁
|
|
*
|
|
* 修复三个 P0 阻塞问题:
|
|
* 1. 保存功能未实现
|
|
* 2. 元件添加功能未实现
|
|
* 3. 画布元件绘制未完成
|
|
*
|
|
* @version 1.0.0
|
|
* @date 2026-03-07
|
|
*/
|
|
|
|
import 'package:flutter/material.dart';
|
|
import 'dart:convert';
|
|
import 'dart:io';
|
|
import 'package:path_provider/path_provider.dart';
|
|
import '../../data/models/core_models.dart';
|
|
import '../../data/format/tile_format.dart';
|
|
import '../../domain/managers/selection_manager.dart';
|
|
|
|
// ============================================================================
|
|
// Bug #1: 保存功能实现
|
|
// ============================================================================
|
|
|
|
/// 保存服务 - 实现设计文件的保存/加载
|
|
class SaveService {
|
|
static final SaveService _instance = SaveService._internal();
|
|
factory SaveService() => _instance;
|
|
SaveService._internal();
|
|
|
|
/// 保存到本地文件
|
|
Future<bool> saveDesign(Design design, String filename) async {
|
|
try {
|
|
// 获取应用文档目录
|
|
final directory = await getApplicationDocumentsDirectory();
|
|
final filePath = '${directory.path}/designs/$filename';
|
|
|
|
// 确保目录存在
|
|
final dir = Directory('${directory.path}/designs');
|
|
if (!await dir.exists()) {
|
|
await dir.create(recursive: true);
|
|
}
|
|
|
|
// 使用 Tile 格式序列化
|
|
final tileData = TileFormatExporter.export(design);
|
|
final jsonStr = jsonEncode(tileData);
|
|
|
|
// 写入文件
|
|
final file = File(filePath);
|
|
await file.writeAsString(jsonStr);
|
|
|
|
debugPrint('✅ 设计已保存:$filePath');
|
|
return true;
|
|
} catch (e) {
|
|
debugPrint('❌ 保存失败:$e');
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/// 从本地文件加载
|
|
Future<Design?> loadDesign(String filename) async {
|
|
try {
|
|
final directory = await getApplicationDocumentsDirectory();
|
|
final filePath = '${directory.path}/designs/$filename';
|
|
|
|
final file = File(filePath);
|
|
if (!await file.exists()) {
|
|
debugPrint('❌ 文件不存在:$filePath');
|
|
return null;
|
|
}
|
|
|
|
final jsonStr = await file.readAsString();
|
|
final tileData = jsonDecode(jsonStr);
|
|
|
|
// 使用 Tile 格式反序列化
|
|
final design = TileFormatImporter.import(tileData);
|
|
|
|
debugPrint('✅ 设计已加载:$filename');
|
|
return design;
|
|
} catch (e) {
|
|
debugPrint('❌ 加载失败:$e');
|
|
return null;
|
|
}
|
|
}
|
|
|
|
/// 保存到云同步
|
|
Future<bool> saveToCloud(Design design, String projectId) async {
|
|
// TODO: 集成后端云同步 API
|
|
// POST /api/v1/projects/:id/sync
|
|
debugPrint('☁️ 云同步:$projectId');
|
|
return true;
|
|
}
|
|
|
|
/// 获取已保存的设计列表
|
|
Future<List<String>> getSavedDesigns() async {
|
|
try {
|
|
final directory = await getApplicationDocumentsDirectory();
|
|
final dir = Directory('${directory.path}/designs');
|
|
|
|
if (!await dir.exists()) {
|
|
return [];
|
|
}
|
|
|
|
final files = await dir.list().toList();
|
|
return files
|
|
.where((f) => f.path.endsWith('.tile'))
|
|
.map((f) => f.path.split('/').last)
|
|
.toList();
|
|
} catch (e) {
|
|
debugPrint('❌ 获取列表失败:$e');
|
|
return [];
|
|
}
|
|
}
|
|
}
|
|
|
|
// ============================================================================
|
|
// Bug #2: 元件添加功能实现
|
|
// ============================================================================
|
|
|
|
/// 元件库服务 - 提供常用元件
|
|
class ComponentLibraryService {
|
|
static final ComponentLibraryService _instance = ComponentLibraryService._internal();
|
|
factory ComponentLibraryService() => _instance;
|
|
ComponentLibraryService._internal();
|
|
|
|
/// 获取常用元件列表
|
|
List<ComponentTemplate> getCommonComponents() {
|
|
return [
|
|
ComponentTemplate(
|
|
id: 'resistor',
|
|
name: '电阻',
|
|
category: 'passive',
|
|
symbol: 'R',
|
|
pinCount: 2,
|
|
pins: [
|
|
PinDefinition(pinId: '1', x: -10, y: 0, direction: PinDirection.left),
|
|
PinDefinition(pinId: '2', x: 10, y: 0, direction: PinDirection.right),
|
|
],
|
|
graphics: [
|
|
GraphicElement(
|
|
type: GraphicType.line,
|
|
points: [Offset(-8, 0), Offset(-4, 0)],
|
|
style: LineStyle.solid,
|
|
width: 1,
|
|
color: Colors.black,
|
|
),
|
|
GraphicElement(
|
|
type: GraphicType.zigzag,
|
|
points: [
|
|
Offset(-4, 0), Offset(-2, -3), Offset(0, 3),
|
|
Offset(2, -3), Offset(4, 0), Offset(8, 0),
|
|
],
|
|
style: LineStyle.solid,
|
|
width: 1,
|
|
color: Colors.black,
|
|
),
|
|
],
|
|
),
|
|
ComponentTemplate(
|
|
id: 'capacitor',
|
|
name: '电容',
|
|
category: 'passive',
|
|
symbol: 'C',
|
|
pinCount: 2,
|
|
pins: [
|
|
PinDefinition(pinId: '1', x: -10, y: 0, direction: PinDirection.left),
|
|
PinDefinition(pinId: '2', x: 10, y: 0, direction: PinDirection.right),
|
|
],
|
|
graphics: [
|
|
GraphicElement(
|
|
type: GraphicType.line,
|
|
points: [Offset(-10, 0), Offset(-5, 0)],
|
|
style: LineStyle.solid,
|
|
width: 1,
|
|
color: Colors.black,
|
|
),
|
|
GraphicElement(
|
|
type: GraphicType.line,
|
|
points: [Offset(-5, -5), Offset(-5, 5)],
|
|
style: LineStyle.solid,
|
|
width: 2,
|
|
color: Colors.black,
|
|
),
|
|
GraphicElement(
|
|
type: GraphicType.line,
|
|
points: [Offset(5, -5), Offset(5, 5)],
|
|
style: LineStyle.solid,
|
|
width: 2,
|
|
color: Colors.black,
|
|
),
|
|
GraphicElement(
|
|
type: GraphicType.line,
|
|
points: [Offset(5, 0), Offset(10, 0)],
|
|
style: LineStyle.solid,
|
|
width: 1,
|
|
color: Colors.black,
|
|
),
|
|
],
|
|
),
|
|
ComponentTemplate(
|
|
id: 'led',
|
|
name: 'LED',
|
|
category: 'active',
|
|
symbol: 'D',
|
|
pinCount: 2,
|
|
pins: [
|
|
PinDefinition(pinId: 'A', x: -10, y: 0, direction: PinDirection.left),
|
|
PinDefinition(pinId: 'K', x: 10, y: 0, direction: PinDirection.right),
|
|
],
|
|
graphics: [
|
|
GraphicElement(
|
|
type: GraphicType.triangle,
|
|
points: [Offset(-5, -5), Offset(-5, 5), Offset(5, 0)],
|
|
style: LineStyle.solid,
|
|
width: 1,
|
|
color: Colors.black,
|
|
filled: true,
|
|
fillColor: Colors.yellow,
|
|
),
|
|
GraphicElement(
|
|
type: GraphicType.line,
|
|
points: [Offset(5, -5), Offset(5, 5)],
|
|
style: LineStyle.solid,
|
|
width: 2,
|
|
color: Colors.black,
|
|
),
|
|
// 光线箭头
|
|
GraphicElement(
|
|
type: GraphicType.line,
|
|
points: [Offset(8, -8), Offset(12, -12)],
|
|
style: LineStyle.solid,
|
|
width: 1,
|
|
color: Colors.orange,
|
|
arrowHead: true,
|
|
),
|
|
GraphicElement(
|
|
type: GraphicType.line,
|
|
points: [Offset(10, -6), Offset(14, -10)],
|
|
style: LineStyle.solid,
|
|
width: 1,
|
|
color: Colors.orange,
|
|
arrowHead: true,
|
|
),
|
|
],
|
|
),
|
|
ComponentTemplate(
|
|
id: 'ic_555',
|
|
name: '555 定时器',
|
|
category: 'ic',
|
|
symbol: 'U',
|
|
pinCount: 8,
|
|
pins: [
|
|
PinDefinition(pinId: '1', x: -15, y: 15, direction: PinDirection.left),
|
|
PinDefinition(pinId: '2', x: -15, y: 10, direction: PinDirection.left),
|
|
PinDefinition(pinId: '3', x: -15, y: 5, direction: PinDirection.left),
|
|
PinDefinition(pinId: '4', x: -15, y: 0, direction: PinDirection.left),
|
|
PinDefinition(pinId: '5', x: 15, y: -15, direction: PinDirection.right),
|
|
PinDefinition(pinId: '6', x: 15, y: -10, direction: PinDirection.right),
|
|
PinDefinition(pinId: '7', x: 15, y: -5, direction: PinDirection.right),
|
|
PinDefinition(pinId: '8', x: 15, y: 0, direction: PinDirection.right),
|
|
],
|
|
graphics: [
|
|
GraphicElement(
|
|
type: GraphicType.rectangle,
|
|
points: [Offset(-15, -20), Offset(15, 20)],
|
|
style: LineStyle.solid,
|
|
width: 2,
|
|
color: Colors.black,
|
|
),
|
|
// 引脚 1 标记
|
|
GraphicElement(
|
|
type: GraphicType.circle,
|
|
points: [Offset(-10, 15)],
|
|
radius: 2,
|
|
style: LineStyle.solid,
|
|
width: 1,
|
|
color: Colors.black,
|
|
filled: true,
|
|
fillColor: Colors.black,
|
|
),
|
|
],
|
|
),
|
|
];
|
|
}
|
|
|
|
/// 从模板创建元件实例
|
|
Component createComponentFromTemplate(
|
|
ComponentTemplate template, {
|
|
required double x,
|
|
required double y,
|
|
String? reference,
|
|
String? value,
|
|
}) {
|
|
return Component(
|
|
id: 'C${DateTime.now().millisecondsSinceEpoch}',
|
|
templateId: template.id,
|
|
position: Position2D(x: x, y: y),
|
|
rotation: 0,
|
|
mirror: false,
|
|
layerId: 'top',
|
|
properties: {
|
|
'reference': reference ?? '${template.symbol}?',
|
|
'value': value ?? '',
|
|
},
|
|
pins: template.pins.map((p) => Pin(
|
|
pinId: p.pinId,
|
|
x: p.x,
|
|
y: p.y,
|
|
direction: p.direction,
|
|
)).toList(),
|
|
graphics: template.graphics,
|
|
);
|
|
}
|
|
}
|
|
|
|
/// 元件模板数据模型
|
|
class ComponentTemplate {
|
|
final String id;
|
|
final String name;
|
|
final String category;
|
|
final String symbol;
|
|
final int pinCount;
|
|
final List<PinDefinition> pins;
|
|
final List<GraphicElement> graphics;
|
|
|
|
ComponentTemplate({
|
|
required this.id,
|
|
required this.name,
|
|
required this.category,
|
|
required this.symbol,
|
|
required this.pinCount,
|
|
required this.pins,
|
|
required this.graphics,
|
|
});
|
|
}
|
|
|
|
class PinDefinition {
|
|
final String pinId;
|
|
final double x, y;
|
|
final PinDirection direction;
|
|
|
|
PinDefinition({
|
|
required this.pinId,
|
|
required this.x,
|
|
required this.y,
|
|
required this.direction,
|
|
});
|
|
}
|
|
|
|
// ============================================================================
|
|
// Bug #3: 画布元件绘制实现
|
|
// ============================================================================
|
|
|
|
/// 优化的画布绘制器 - 修复元件绘制问题
|
|
class FixedSchematicCanvasPainter extends CustomPainter {
|
|
final Design design;
|
|
final double zoomLevel;
|
|
final Offset offset;
|
|
final SelectionManager selectionManager;
|
|
final WiringState? wiringState;
|
|
final Rect? selectionRect;
|
|
final PinReference? hoveredPin;
|
|
final Component? hoveredComponent;
|
|
final double gridSize;
|
|
|
|
FixedSchematicCanvasPainter({
|
|
required this.design,
|
|
required this.zoomLevel,
|
|
required this.offset,
|
|
required this.selectionManager,
|
|
this.wiringState,
|
|
this.selectionRect,
|
|
this.hoveredPin,
|
|
this.hoveredComponent,
|
|
required this.gridSize,
|
|
});
|
|
|
|
@override
|
|
void paint(Canvas canvas, Size size) {
|
|
// 1. 绘制背景
|
|
_drawBackground(canvas, size);
|
|
|
|
// 2. 绘制网格
|
|
_drawGrid(canvas, size);
|
|
|
|
// 3. 绘制所有元件 ✅ 修复:之前这里没实现
|
|
_drawAllComponents(canvas);
|
|
|
|
// 4. 绘制网络
|
|
_drawNets(canvas);
|
|
|
|
// 5. 绘制选中框
|
|
if (selectionRect != null) {
|
|
_drawSelectionRect(canvas);
|
|
}
|
|
|
|
// 6. 绘制连线中的线
|
|
if (wiringState != null) {
|
|
_drawWiringLine(canvas);
|
|
}
|
|
|
|
// 7. 绘制悬停高亮
|
|
if (hoveredComponent != null) {
|
|
_drawHoverHighlight(canvas, hoveredComponent!);
|
|
}
|
|
if (hoveredPin != null) {
|
|
_drawPinHighlight(canvas, hoveredPin!);
|
|
}
|
|
}
|
|
|
|
void _drawBackground(Canvas canvas, Size size) {
|
|
canvas.drawColor(const Color(0xFFFAFAFA), BlendMode.src);
|
|
}
|
|
|
|
void _drawGrid(Canvas canvas, Size size) {
|
|
final paint = Paint()
|
|
..color = const Color(0xFFE0E0E0)
|
|
..strokeWidth = 0.5;
|
|
|
|
final visibleRect = _getVisibleRect(size);
|
|
final startX = (visibleRect.left / gridSize).floor() * gridSize;
|
|
final startY = (visibleRect.top / gridSize).floor() * gridSize;
|
|
|
|
// 绘制垂直线
|
|
for (double x = startX; x < visibleRect.right; x += gridSize) {
|
|
final screenX = _worldToScreenX(x);
|
|
canvas.drawLine(
|
|
Offset(screenX, _worldToScreenY(visibleRect.top)),
|
|
Offset(screenX, _worldToScreenY(visibleRect.bottom)),
|
|
paint,
|
|
);
|
|
}
|
|
|
|
// 绘制水平线
|
|
for (double y = startY; y < visibleRect.bottom; y += gridSize) {
|
|
final screenY = _worldToScreenY(y);
|
|
canvas.drawLine(
|
|
Offset(_worldToScreenX(visibleRect.left), screenY),
|
|
Offset(_worldToScreenX(visibleRect.right), screenY),
|
|
paint,
|
|
);
|
|
}
|
|
}
|
|
|
|
void _drawAllComponents(Canvas canvas) {
|
|
// ✅ 修复:遍历所有元件并绘制
|
|
for (final component in design.components.values) {
|
|
_drawComponent(canvas, component);
|
|
}
|
|
}
|
|
|
|
void _drawComponent(Canvas canvas, Component component) {
|
|
final isSelected = selectionManager.isSelected(
|
|
SelectableComponent(type: SelectableType.component, id: component.id),
|
|
);
|
|
|
|
// 应用变换
|
|
canvas.save();
|
|
|
|
// 平移到元件位置
|
|
final screenPos = _worldToScreen(component.position.x, component.position.y);
|
|
canvas.translate(screenPos.dx, screenPos.dy);
|
|
|
|
// 应用旋转
|
|
if (component.rotation != 0) {
|
|
canvas.rotate(component.rotation * 3.14159 / 180);
|
|
}
|
|
|
|
// 应用镜像
|
|
if (component.mirror) {
|
|
canvas.scale(-1, 1);
|
|
}
|
|
|
|
// 绘制元件图形
|
|
for (final graphic in component.graphics) {
|
|
_drawGraphicElement(canvas, graphic);
|
|
}
|
|
|
|
// 绘制引脚
|
|
for (final pin in component.pins) {
|
|
_drawPin(canvas, pin);
|
|
}
|
|
|
|
// 绘制位号
|
|
if (component.properties.containsKey('reference')) {
|
|
_drawReference(canvas, component.properties['reference']!);
|
|
}
|
|
|
|
// 绘制选中效果
|
|
if (isSelected) {
|
|
_drawSelectionHighlight(canvas, component);
|
|
}
|
|
|
|
canvas.restore();
|
|
}
|
|
|
|
void _drawGraphicElement(Canvas canvas, GraphicElement graphic) {
|
|
final paint = Paint()
|
|
..color = graphic.color ?? Colors.black
|
|
..strokeWidth = graphic.width ?? 1.0
|
|
..style = PaintingStyle.stroke;
|
|
|
|
switch (graphic.type) {
|
|
case GraphicType.line:
|
|
if (graphic.points.length >= 2) {
|
|
final path = Path();
|
|
path.moveTo(graphic.points[0].dx, graphic.points[0].dy);
|
|
for (int i = 1; i < graphic.points.length; i++) {
|
|
path.lineTo(graphic.points[i].dx, graphic.points[i].dy);
|
|
}
|
|
canvas.drawPath(path, paint);
|
|
}
|
|
break;
|
|
|
|
case GraphicType.rectangle:
|
|
if (graphic.points.length >= 2) {
|
|
final rect = Rect.fromPoints(
|
|
graphic.points[0],
|
|
graphic.points[1],
|
|
);
|
|
if (graphic.filled ?? false) {
|
|
paint.style = PaintingStyle.fill;
|
|
paint.color = graphic.fillColor ?? graphic.color ?? Colors.black;
|
|
}
|
|
canvas.drawRect(rect, paint);
|
|
}
|
|
break;
|
|
|
|
case GraphicType.circle:
|
|
if (graphic.points.isNotEmpty) {
|
|
final center = graphic.points[0];
|
|
final radius = graphic.radius ?? 5.0;
|
|
if (graphic.filled ?? false) {
|
|
paint.style = PaintingStyle.fill;
|
|
}
|
|
canvas.drawCircle(center, radius, paint);
|
|
}
|
|
break;
|
|
|
|
case GraphicType.triangle:
|
|
if (graphic.points.length >= 3) {
|
|
final path = Path();
|
|
path.moveTo(graphic.points[0].dx, graphic.points[0].dy);
|
|
for (int i = 1; i < graphic.points.length; i++) {
|
|
path.lineTo(graphic.points[i].dx, graphic.points[i].dy);
|
|
}
|
|
path.close();
|
|
if (graphic.filled ?? false) {
|
|
paint.style = PaintingStyle.fill;
|
|
paint.color = graphic.fillColor ?? graphic.color ?? Colors.black;
|
|
}
|
|
canvas.drawPath(path, paint);
|
|
}
|
|
break;
|
|
|
|
case GraphicType.text:
|
|
if (graphic.text != null && graphic.points.isNotEmpty) {
|
|
final textPainter = TextPainter(
|
|
text: TextSpan(
|
|
text: graphic.text,
|
|
style: TextStyle(
|
|
color: graphic.color ?? Colors.black,
|
|
fontSize: 12,
|
|
),
|
|
),
|
|
textDirection: TextDirection.ltr,
|
|
);
|
|
textPainter.layout();
|
|
textPainter.paint(canvas, graphic.points[0]);
|
|
}
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
void _drawPin(Canvas canvas, Pin pin) {
|
|
final paint = Paint()
|
|
..color = Colors.black
|
|
..strokeWidth = 1.5
|
|
..style = PaintingStyle.stroke;
|
|
|
|
final screenX = pin.x * zoomLevel;
|
|
final screenY = pin.y * zoomLevel;
|
|
|
|
// 绘制引脚点
|
|
canvas.drawCircle(Offset(screenX, screenY), 3.0, paint);
|
|
|
|
// 绘制引脚编号
|
|
if (pin.pinId.isNotEmpty) {
|
|
final textPainter = TextPainter(
|
|
text: TextSpan(
|
|
text: pin.pinId,
|
|
style: const TextStyle(
|
|
color: Colors.black,
|
|
fontSize: 10,
|
|
),
|
|
),
|
|
textDirection: TextDirection.ltr,
|
|
);
|
|
textPainter.layout();
|
|
textPainter.paint(canvas, Offset(screenX + 5, screenY - 5));
|
|
}
|
|
}
|
|
|
|
void _drawReference(Canvas canvas, String reference) {
|
|
final textPainter = TextPainter(
|
|
text: TextSpan(
|
|
text: reference,
|
|
style: const TextStyle(
|
|
color: Colors.blue,
|
|
fontSize: 14,
|
|
fontWeight: FontWeight.bold,
|
|
),
|
|
),
|
|
textDirection: TextDirection.ltr,
|
|
);
|
|
textPainter.layout();
|
|
textPainter.paint(canvas, const Offset(15, -20));
|
|
}
|
|
|
|
void _drawSelectionHighlight(Canvas canvas, Component component) {
|
|
final paint = Paint()
|
|
..color = Colors.blue.withOpacity(0.3)
|
|
..style = PaintingStyle.fill;
|
|
|
|
// 简单绘制包围盒
|
|
canvas.drawRect(
|
|
Rect.fromLTWH(-20, -25, 40, 50),
|
|
paint,
|
|
);
|
|
|
|
final borderPaint = Paint()
|
|
..color = Colors.blue
|
|
..strokeWidth = 2.0
|
|
..style = PaintingStyle.stroke;
|
|
|
|
canvas.drawRect(
|
|
Rect.fromLTWH(-20, -25, 40, 50),
|
|
borderPaint,
|
|
);
|
|
}
|
|
|
|
void _drawSelectionRect(Canvas canvas) {
|
|
if (selectionRect == null) return;
|
|
|
|
final paint = Paint()
|
|
..color = Colors.blue.withOpacity(0.2)
|
|
..style = PaintingStyle.fill;
|
|
|
|
final borderPaint = Paint()
|
|
..color = Colors.blue
|
|
..strokeWidth = 1.0
|
|
..style = PaintingStyle.stroke;
|
|
|
|
final rect = Rect.fromPoints(
|
|
Offset(_worldToScreenX(selectionRect!.left), _worldToScreenY(selectionRect!.top)),
|
|
Offset(_worldToScreenX(selectionRect!.right), _worldToScreenY(selectionRect!.bottom)),
|
|
);
|
|
|
|
canvas.drawRect(rect, paint);
|
|
canvas.drawRect(rect, borderPaint);
|
|
}
|
|
|
|
void _drawWiringLine(Canvas canvas) {
|
|
if (wiringState == null) return;
|
|
|
|
final paint = Paint()
|
|
..color = Colors.green
|
|
..strokeWidth = 2.0;
|
|
|
|
final startScreen = _worldToScreen(
|
|
wiringState!.startPoint.position.x,
|
|
wiringState!.startPoint.position.y,
|
|
);
|
|
|
|
final currentScreen = _worldToScreen(
|
|
wiringState!.currentPoint.dx,
|
|
wiringState!.currentPoint.dy,
|
|
);
|
|
|
|
canvas.drawLine(startScreen, currentScreen, paint);
|
|
|
|
// 绘制起点高亮
|
|
canvas.drawCircle(startScreen, 5.0, paint);
|
|
}
|
|
|
|
void _drawHoverHighlight(Canvas canvas, Component component) {
|
|
final paint = Paint()
|
|
..color = Colors.orange.withOpacity(0.3)
|
|
..style = PaintingStyle.fill;
|
|
|
|
canvas.drawRect(
|
|
Rect.fromLTWH(
|
|
_worldToScreenX(component.position.x - 20),
|
|
_worldToScreenY(component.position.y - 25),
|
|
40 * zoomLevel,
|
|
50 * zoomLevel,
|
|
),
|
|
paint,
|
|
);
|
|
}
|
|
|
|
void _drawPinHighlight(Canvas canvas, PinReference pinRef) {
|
|
final paint = Paint()
|
|
..color = Colors.green
|
|
..strokeWidth = 2.0;
|
|
|
|
final screenPos = _worldToScreen(
|
|
pinRef.component.position.x + pinRef.pin.x,
|
|
pinRef.component.position.y + pinRef.pin.y,
|
|
);
|
|
|
|
canvas.drawCircle(screenPos, 6.0, paint);
|
|
}
|
|
|
|
void _drawNets(Canvas canvas) {
|
|
for (final net in design.nets.values) {
|
|
_drawNet(canvas, net);
|
|
}
|
|
}
|
|
|
|
void _drawNet(Canvas canvas, Net net) {
|
|
final paint = Paint()
|
|
..color = Colors.purple
|
|
..strokeWidth = 1.5;
|
|
|
|
if (net.traces.isNotEmpty) {
|
|
final path = Path();
|
|
final firstPoint = _worldToScreen(net.traces[0].start.x, net.traces[0].start.y);
|
|
path.moveTo(firstPoint.dx, firstPoint.dy);
|
|
|
|
for (final trace in net.traces) {
|
|
final point = _worldToScreen(trace.end.x, trace.end.y);
|
|
path.lineTo(point.dx, point.dy);
|
|
}
|
|
|
|
canvas.drawPath(path, paint);
|
|
}
|
|
}
|
|
|
|
Offset _worldToScreen(double worldX, double worldY) {
|
|
return Offset(
|
|
(worldX * zoomLevel) + offset.dx,
|
|
(worldY * zoomLevel) + offset.dy,
|
|
);
|
|
}
|
|
|
|
double _worldToScreenX(double worldX) => (worldX * zoomLevel) + offset.dx;
|
|
double _worldToScreenY(double worldY) => (worldY * zoomLevel) + offset.dy;
|
|
|
|
Rect _getVisibleRect(Size size) {
|
|
return Rect.fromLTWH(
|
|
(-offset.dx) / zoomLevel,
|
|
(-offset.dy) / zoomLevel,
|
|
size.width / zoomLevel,
|
|
size.height / zoomLevel,
|
|
);
|
|
}
|
|
|
|
@override
|
|
bool shouldRepaint(FixedSchematicCanvasPainter oldDelegate) {
|
|
return oldDelegate.zoomLevel != zoomLevel ||
|
|
oldDelegate.offset != offset ||
|
|
oldDelegate.selectionManager != selectionManager ||
|
|
oldDelegate.wiringState != wiringState ||
|
|
oldDelegate.selectionRect != selectionRect ||
|
|
oldDelegate.hoveredPin != hoveredPin ||
|
|
oldDelegate.hoveredComponent != hoveredComponent;
|
|
}
|
|
}
|
|
|
|
// ============================================================================
|
|
// 使用示例
|
|
// ============================================================================
|
|
|
|
/*
|
|
在 EditableCanvas 中替换 painter:
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return GestureDetector(
|
|
// ... 手势处理 ...
|
|
child: Container(
|
|
color: const Color(0xFFFAFAFA),
|
|
child: CustomPaint(
|
|
size: Size.infinite,
|
|
painter: FixedSchematicCanvasPainter( // ✅ 使用修复后的绘制器
|
|
design: widget.design,
|
|
zoomLevel: _zoomLevel,
|
|
offset: _offset,
|
|
selectionManager: widget.selectionManager,
|
|
wiringState: _wiringState,
|
|
selectionRect: _selectionRect,
|
|
hoveredPin: _hoveredPin,
|
|
hoveredComponent: _hoveredComponent,
|
|
gridSize: _gridSize,
|
|
),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
// 保存功能调用
|
|
final saveService = SaveService();
|
|
await saveService.saveDesign(design, 'my_circuit.tile');
|
|
|
|
// 加载功能调用
|
|
final design = await saveService.loadDesign('my_circuit.tile');
|
|
|
|
// 添加元件
|
|
final library = ComponentLibraryService();
|
|
final template = library.getCommonComponents().first; // 电阻
|
|
final component = library.createComponentFromTemplate(
|
|
template,
|
|
x: 100,
|
|
y: 100,
|
|
reference: 'R1',
|
|
value: '10k',
|
|
);
|
|
design.components[component.id] = component;
|
|
*/
|