fix: 修复 P0 Bug - 保存/元件添加/画布绘制
- ✅ 实现保存功能 (SaveService) - 本地 Tile 格式保存/加载 - 云同步接口预留 - ✅ 实现元件添加功能 (ComponentLibraryService) - 常用电阻/电容/LED/555 定时器 - 从模板创建元件实例 - ✅ 修复画布元件绘制 (FixedSchematicCanvasPainter) - 完整绘制所有元件 - 支持图形/引脚/位号渲染 - 选中/悬停高亮效果 预计工时:4-6 小时 影响范围:核心编辑功能 测试状态:待测试
This commit is contained in:
parent
1e9334229d
commit
019fa39e1a
823
mobile-eda/lib/presentation/components/p0_bug_fixes.dart
Normal file
823
mobile-eda/lib/presentation/components/p0_bug_fixes.dart
Normal file
@ -0,0 +1,823 @@
|
||||
/**
|
||||
* 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;
|
||||
*/
|
||||
Loading…
x
Reference in New Issue
Block a user