/** * 选择管理器模块 * * 负责管理原理图编辑器中的选择状态 * 支持:单选、框选、批量操作 * * @version 0.1.0 * @date 2026-03-07 */ import 'dart:math' as math; import '../models/core_models.dart'; /// 选择状态枚举 enum SelectionMode { none, // 无选择 single, // 单选 multi, // 多选(框选) } /// 可选中的对象类型 enum SelectableType { component, net, trace, via, polygon, } /// 可选中的对象 class SelectableObject { final SelectableType type; final ID id; const SelectableObject({ required this.type, required this.id, }); @override bool operator ==(Object other) { if (identical(this, other)) return true; return other is SelectableObject && other.type == type && other.id == id; } @override int get hashCode => type.hashCode ^ id.hashCode; } /// 选择管理器 class SelectionManager { /// 当前选中的对象集合 final Set _selectedObjects = {}; /// 选择模式 SelectionMode _mode = SelectionMode.none; /// 框选矩形区域(如果有) Rect? _selectionRect; /// 最后选中的对象(用于 Shift+ 点击添加选择) SelectableObject? _lastSelectedObject; /// 选择变化回调 Function(Set)? onSelectionChanged; /// 获取选中的对象集合(不可修改) UnmodifiableSetView get selectedObjects { return UnmodifiableSetView(_selectedObjects); } /// 获取选择模式 SelectionMode get mode => _mode; /// 获取框选矩形 Rect? get selectionRect => _selectionRect; /// 是否有选中的对象 bool get hasSelection => _selectedObjects.isNotEmpty; /// 选中的对象数量 int get selectionCount => _selectedObjects.length; /// 是否只选中了一个对象 bool get isSingleSelection => _selectedObjects.length == 1; /// 是否选中了多个对象 bool get isMultiSelection => _selectedObjects.length > 1; /// 检查对象是否被选中 bool isSelected(SelectableObject object) { return _selectedObjects.contains(object); } /// 检查对象 ID 是否被选中 bool isSelectedById(SelectableType type, ID id) { return _selectedObjects.contains(SelectableObject(type: type, id: id)); } /// 选中单个对象(清除之前的选择) void selectSingle(SelectableObject object) { _selectedObjects.clear(); _selectedObjects.add(object); _mode = SelectionMode.single; _lastSelectedObject = object; _selectionRect = null; _notifyChanged(); } /// 添加对象到选择集(用于 Shift+ 点击) void addToSelection(SelectableObject object) { if (_selectedObjects.add(object)) { _mode = _selectedObjects.length > 1 ? SelectionMode.multi : SelectionMode.single; _lastSelectedObject = object; _notifyChanged(); } } /// 从选择集移除对象 void removeFromSelection(SelectableObject object) { if (_selectedObjects.remove(object)) { _mode = _selectedObjects.isEmpty ? SelectionMode.none : (_selectedObjects.length == 1 ? SelectionMode.single : SelectionMode.multi); _notifyChanged(); } } /// 切换对象选择状态(选中→取消,未选中→选中) void toggleSelection(SelectableObject object) { if (isSelected(object)) { removeFromSelection(object); } else { addToSelection(object); } } /// 开始框选 void startBoxSelection(double x, double y) { _selectionRect = Rect.fromLTWH(x, y, 0, 0); _mode = SelectionMode.multi; } /// 更新框选区域 void updateBoxSelection(double x, double y) { if (_selectionRect == null) return; final left = math.min(_selectionRect!.left, x); final top = math.min(_selectionRect!.top, y); final right = math.max(_selectionRect!.right, x); final bottom = math.max(_selectionRect!.bottom, y); _selectionRect = Rect.fromLTRB(left, top, right, bottom); } /// 完成框选,检测并选中区域内的对象 void endBoxSelection({ required List components, required List nets, required List traces, }) { if (_selectionRect == null) return; _selectedObjects.clear(); // 检测元件 for (final component in components) { if (_isPointInRect(component.position.x, component.position.y)) { _selectedObjects.add(SelectableObject( type: SelectableType.component, id: component.id, )); } } // 检测网络(基于连接点) for (final net in nets) { for (final connection in net.connections) { if (connection.position != null && _isPointInRect(connection.position!.x, connection.position!.y)) { _selectedObjects.add(SelectableObject( type: SelectableType.net, id: net.id, )); break; } } } // 检测走线 for (final trace in traces) { if (_isTraceInRect(trace)) { _selectedObjects.add(SelectableObject( type: SelectableType.trace, id: trace.id, )); } } _mode = _selectedObjects.isEmpty ? SelectionMode.none : SelectionMode.multi; _selectionRect = null; _notifyChanged(); } /// 检查点是否在框选矩形内 bool _isPointInRect(Coordinate x, Coordinate y) { if (_selectionRect == null) return false; return x >= _selectionRect!.left && x <= _selectionRect!.right && y >= _selectionRect!.top && y <= _selectionRect!.bottom; } /// 检查走线是否在框选矩形内(至少有一个点在矩形内) bool _isTraceInRect(Trace trace) { if (_selectionRect == null) return false; for (final point in trace.points) { if (_isPointInRect(point.x, point.y)) { return true; } } return false; } /// 清除所有选择 void clearSelection() { if (_selectedObjects.isNotEmpty) { _selectedObjects.clear(); _mode = SelectionMode.none; _selectionRect = null; _lastSelectedObject = null; _notifyChanged(); } } /// 选中所有对象 void selectAll({ required List components, required List nets, required List traces, }) { _selectedObjects.clear(); for (final component in components) { _selectedObjects.add(SelectableObject( type: SelectableType.component, id: component.id, )); } for (final net in nets) { _selectedObjects.add(SelectableObject( type: SelectableType.net, id: net.id, )); } for (final trace in traces) { _selectedObjects.add(SelectableObject( type: SelectableType.trace, id: trace.id, )); } _mode = _selectedObjects.isEmpty ? SelectionMode.none : SelectionMode.multi; _notifyChanged(); } /// 获取选中的元件 List getSelectedComponentIds() { return _selectedObjects .where((obj) => obj.type == SelectableType.component) .map((obj) => obj.id) .toList(); } /// 获取选中的网络 List getSelectedNetIds() { return _selectedObjects .where((obj) => obj.type == SelectableType.net) .map((obj) => obj.id) .toList(); } /// 获取选中的走线 List getSelectedTraceIds() { return _selectedObjects .where((obj) => obj.type == SelectableType.trace) .map((obj) => obj.id) .toList(); } /// 通知选择变化 void _notifyChanged() { onSelectionChanged?.call(_selectedObjects); } } /// 矩形辅助类 class Rect { final double left; final double top; final double right; final double bottom; const Rect.fromLTRB(this.left, this.top, this.right, this.bottom); factory Rect.fromLTWH(double left, double top, double width, double height) { return Rect.fromLTRB( left, top, left + width, top + height, ); } double get width => right - left; double get height => bottom - top; bool contains(double x, double y) { return x >= left && x <= right && y >= top && y <= bottom; } @override String toString() => 'Rect($left, $top, $right, $bottom)'; }