337 lines
8.3 KiB
Dart
337 lines
8.3 KiB
Dart
/**
|
|
* 选择管理器模块
|
|
*
|
|
* 负责管理原理图编辑器中的选择状态
|
|
* 支持:单选、框选、批量操作
|
|
*
|
|
* @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<SelectableObject> _selectedObjects = {};
|
|
|
|
/// 选择模式
|
|
SelectionMode _mode = SelectionMode.none;
|
|
|
|
/// 框选矩形区域(如果有)
|
|
Rect? _selectionRect;
|
|
|
|
/// 最后选中的对象(用于 Shift+ 点击添加选择)
|
|
SelectableObject? _lastSelectedObject;
|
|
|
|
/// 选择变化回调
|
|
Function(Set<SelectableObject>)? onSelectionChanged;
|
|
|
|
/// 获取选中的对象集合(不可修改)
|
|
UnmodifiableSetView<SelectableObject> 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<Component> components,
|
|
required List<Net> nets,
|
|
required List<Trace> 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<Component> components,
|
|
required List<Net> nets,
|
|
required List<Trace> 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<ID> getSelectedComponentIds() {
|
|
return _selectedObjects
|
|
.where((obj) => obj.type == SelectableType.component)
|
|
.map((obj) => obj.id)
|
|
.toList();
|
|
}
|
|
|
|
/// 获取选中的网络
|
|
List<ID> getSelectedNetIds() {
|
|
return _selectedObjects
|
|
.where((obj) => obj.type == SelectableType.net)
|
|
.map((obj) => obj.id)
|
|
.toList();
|
|
}
|
|
|
|
/// 获取选中的走线
|
|
List<ID> 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)';
|
|
}
|