mobile-eda/lib/domain/managers/selection_manager.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)';
}