mobile-eda/lib/presentation/widgets/property_panel_widget.dart

683 lines
19 KiB
Dart
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
/// 属性面板组件
///
/// 弹出式属性编辑(元件值、封装、网络名)
/// 实时预览修改效果
/// 输入验证与错误提示
class PropertyPanelWidget extends ConsumerStatefulWidget {
/// 要编辑的属性数据
final PropertyData propertyData;
/// 属性变更回调
final Function(PropertyData)? onPropertyChanged;
/// 实时预览回调
final Function(PropertyData)? onPreview;
/// 面板位置(底部弹出/侧边弹出)
final PropertyPanelPosition position;
const PropertyPanelWidget({
super.key,
required this.propertyData,
this.onPropertyChanged,
this.onPreview,
this.position = PropertyPanelPosition.bottom,
});
@override
ConsumerState<PropertyPanelWidget> createState() => _PropertyPanelWidgetState();
}
class _PropertyPanelWidgetState extends ConsumerState<PropertyPanelWidget> {
late PropertyData _editedData;
final _formKey = GlobalKey<FormState>();
final _refDesignatorController = TextEditingController();
final _valueController = TextEditingController();
final _footprintController = TextEditingController();
final _netNameController = TextEditingController();
// 错误信息
String? _refDesignatorError;
String? _valueError;
String? _footprintError;
// 是否已修改
bool _hasChanges = false;
@override
void initState() {
super.initState();
_editedData = widget.propertyData.copy();
_refDesignatorController.text = _editedData.refDesignator ?? '';
_valueController.text = _editedData.value ?? '';
_footprintController.text = _editedData.footprint ?? '';
_netNameController.text = _editedData.netName ?? '';
}
@override
void dispose() {
_refDesignatorController.dispose();
_valueController.dispose();
_footprintController.dispose();
_netNameController.dispose();
super.dispose();
}
void _validateRefDesignator(String value) {
if (value.isEmpty) {
setState(() => _refDesignatorError = '请输入位号');
return;
}
// 位号格式验证:字母 + 数字 (如 R1, C2, U3)
final regex = RegExp(r'^[A-Z]+\d+$');
if (!regex.hasMatch(value)) {
setState(() => _refDesignatorError = '位号格式错误 (如 R1, C2, U3)');
return;
}
setState(() => _refDesignatorError = null);
}
void _validateValue(String value) {
if (value.isEmpty) {
setState(() => _valueError = null); // 值可以为空
return;
}
// 根据元件类型验证值格式
switch (_editedData.componentType) {
case ComponentType.resistor:
// 电阻值10k, 4.7M, 100R 等
final regex = RegExp(r'^\d+(\.\d+)?[kKmMgGpP]?$');
if (!regex.hasMatch(value)) {
setState(() => _valueError = '电阻值格式错误 (如 10k, 4.7M, 100R)');
return;
}
break;
case ComponentType.capacitor:
// 电容值10u, 100n, 1p 等
final regex = RegExp(r'^\d+(\.\d+)?[uUnNpPmM]?$');
if (!regex.hasMatch(value)) {
setState(() => _valueError = '电容值格式错误 (如 10u, 100n, 1p)');
return;
}
break;
default:
_valueError = null;
break;
}
setState(() => _valueError = null);
}
void _validateFootprint(String value) {
if (value.isEmpty) {
setState(() => _footprintError = null); // 封装可以为空
return;
}
// 封装格式验证:简单验证是否包含字母和数字
if (!RegExp(r'[A-Za-z]').hasMatch(value) || !RegExp(r'\d').hasMatch(value)) {
setState(() => _footprintError = '封装格式错误 (如 0805, SOT23)');
return;
}
setState(() => _footprintError = null);
}
void _onTextChanged() {
setState(() {
_editedData.refDesignator = _refDesignatorController.text;
_editedData.value = _valueController.text;
_editedData.footprint = _footprintController.text;
_editedData.netName = _netNameController.text;
_hasChanges = true;
});
// 实时预览
widget.onPreview?.call(_editedData);
}
void _saveChanges() {
// 验证所有字段
_validateRefDesignator(_refDesignatorController.text);
_validateValue(_valueController.text);
_validateFootprint(_footprintController.text);
// 如果有错误,不保存
if (_refDesignatorError != null || _valueError != null || _footprintError != null) {
return;
}
widget.onPropertyChanged?.call(_editedData);
Navigator.of(context).pop(_editedData);
}
void _cancelChanges() {
Navigator.of(context).pop(null);
}
@override
Widget build(BuildContext context) {
return Container(
decoration: BoxDecoration(
color: Colors.white,
borderRadius: const BorderRadius.vertical(top: Radius.circular(16)),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.2),
blurRadius: 16,
offset: const Offset(0, -4),
),
],
),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
// 顶部标题栏
_buildHeader(),
const Divider(height: 1),
// 属性表单
Flexible(
child: SingleChildScrollView(
padding: const EdgeInsets.all(16),
child: Form(
key: _formKey,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// 元件类型信息
_buildComponentInfo(),
const SizedBox(height: 16),
// 位号编辑
_buildRefDesignatorField(),
const SizedBox(height: 16),
// 值编辑
_buildValueField(),
const SizedBox(height: 16),
// 封装编辑
_buildFootprintField(),
const SizedBox(height: 16),
// 网络名编辑(只读,显示连接的网络)
_buildNetNameField(),
const SizedBox(height: 16),
// 旋转和镜像
_buildRotationMirror(),
],
),
),
),
),
const Divider(height: 1),
// 底部操作按钮
_buildActionButtons(),
],
),
);
}
Widget _buildHeader() {
return Container(
padding: const EdgeInsets.all(16),
child: Row(
children: [
const Icon(Icons.edit_attributes, size: 24),
const SizedBox(width: 12),
const Text(
'属性编辑',
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
),
),
const Spacer(),
if (_hasChanges)
Container(
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
decoration: BoxDecoration(
color: Colors.orange.withOpacity(0.1),
borderRadius: BorderRadius.circular(4),
),
child: Text(
'未保存',
style: TextStyle(
fontSize: 12,
color: Colors.orange[700],
),
),
),
IconButton(
icon: const Icon(Icons.close),
onPressed: _cancelChanges,
),
],
),
);
}
Widget _buildComponentInfo() {
return Container(
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
color: Theme.of(context).primaryColor.withOpacity(0.05),
borderRadius: BorderRadius.circular(8),
),
child: Row(
children: [
Icon(
_getComponentIcon(widget.propertyData.componentType),
color: Theme.of(context).primaryColor,
size: 32,
),
const SizedBox(width: 12),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
_getComponentTypeName(widget.propertyData.componentType),
style: const TextStyle(
fontSize: 16,
fontWeight: FontWeight.bold,
),
),
Text(
widget.propertyData.symbolName ?? '默认符号',
style: TextStyle(
fontSize: 12,
color: Colors.grey[600],
),
),
],
),
],
),
);
}
Widget _buildRefDesignatorField() {
return TextFormField(
controller: _refDesignatorController,
decoration: InputDecoration(
labelText: '位号',
hintText: '如 R1, C2, U3',
prefixIcon: const Icon(Icons.tag),
errorText: _refDesignatorError,
border: const OutlineInputBorder(),
helperText: '元件的唯一标识符',
),
textCapitalization: TextCapitalization.characters,
textInputAction: TextInputAction.next,
onChanged: (value) {
_validateRefDesignator(value);
_onTextChanged();
},
validator: (value) {
if (value == null || value.isEmpty) {
return '请输入位号';
}
return _refDesignatorError;
},
);
}
Widget _buildValueField() {
return TextFormField(
controller: _valueController,
decoration: InputDecoration(
labelText: '',
hintText: _getValueHint(),
prefixIcon: const Icon(Icons.memory),
errorText: _valueError,
border: const OutlineInputBorder(),
helperText: _getValueHelper(),
),
textInputAction: TextInputAction.next,
onChanged: (value) {
_validateValue(value);
_onTextChanged();
},
validator: (value) => _valueError,
);
}
Widget _buildFootprintField() {
return TextFormField(
controller: _footprintController,
decoration: InputDecoration(
labelText: '封装',
hintText: '如 0805, SOT23, SOIC8',
prefixIcon: const Icon(Icons.grid_view),
errorText: _footprintError,
border: const OutlineInputBorder(),
helperText: 'PCB 封装型号',
),
textCapitalization: TextCapitalization.characters,
textInputAction: TextInputAction.next,
onChanged: (value) {
_validateFootprint(value);
_onTextChanged();
},
validator: (value) => _footprintError,
);
}
Widget _buildNetNameField() {
return TextFormField(
controller: _netNameController,
decoration: InputDecoration(
labelText: '网络名',
hintText: '自动或手动命名',
prefixIcon: const Icon(Icons.link),
border: const OutlineInputBorder(),
helperText: '元件引脚连接的网络',
),
readOnly: true,
textInputAction: TextInputAction.done,
);
}
Widget _buildRotationMirror() {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text(
'旋转与镜像',
style: TextStyle(
fontSize: 14,
fontWeight: FontWeight.bold,
),
),
const SizedBox(height: 8),
Row(
children: [
// 旋转按钮
Expanded(
child: _buildRotationButton(0),
),
const SizedBox(width: 8),
Expanded(
child: _buildRotationButton(90),
),
const SizedBox(width: 8),
Expanded(
child: _buildRotationButton(180),
),
const SizedBox(width: 8),
Expanded(
child: _buildRotationButton(270),
),
],
),
const SizedBox(height: 8),
Row(
children: [
// 镜像按钮
Expanded(
child: OutlinedButton.icon(
onPressed: () {
setState(() {
_editedData.mirrorX = !_editedData.mirrorX;
_onTextChanged();
});
},
icon: Icon(
_editedData.mirrorX ? Icons.flip : Icons.flip_outlined,
),
label: const Text('水平'),
style: OutlinedButton.styleFrom(
foregroundColor: _editedData.mirrorX
? Theme.of(context).primaryColor
: Colors.grey[700],
),
),
),
const SizedBox(width: 8),
Expanded(
child: OutlinedButton.icon(
onPressed: () {
setState(() {
_editedData.mirrorY = !_editedData.mirrorY;
_onTextChanged();
});
},
icon: Icon(
Icons.flip,
color: _editedData.mirrorY
? Theme.of(context).primaryColor
: Colors.grey[700],
),
label: const Text('垂直'),
style: OutlinedButton.styleFrom(
foregroundColor: _editedData.mirrorY
? Theme.of(context).primaryColor
: Colors.grey[700],
),
),
),
],
),
],
);
}
Widget _buildRotationButton(int degrees) {
final isSelected = _editedData.rotation == degrees;
return OutlinedButton(
onPressed: () {
setState(() {
_editedData.rotation = degrees;
_onTextChanged();
});
},
style: OutlinedButton.styleFrom(
backgroundColor: isSelected
? Theme.of(context).primaryColor.withOpacity(0.1)
: Colors.transparent,
foregroundColor: isSelected
? Theme.of(context).primaryColor
: Colors.grey[700],
),
child: Transform.rotate(
angle: degrees * 3.14159 / 180,
child: const Icon(Icons.rotate_right, size: 20),
),
);
}
Widget _buildActionButtons() {
return Padding(
padding: const EdgeInsets.all(16),
child: Row(
children: [
Expanded(
child: OutlinedButton(
onPressed: _cancelChanges,
style: OutlinedButton.styleFrom(
padding: const EdgeInsets.symmetric(vertical: 12),
),
child: const Text('取消'),
),
),
const SizedBox(width: 16),
Expanded(
child: ElevatedButton(
onPressed: _hasChanges ? _saveChanges : null,
style: ElevatedButton.styleFrom(
padding: const EdgeInsets.symmetric(vertical: 12),
),
child: const Text('保存'),
),
),
],
),
);
}
String _getComponentTypeName(ComponentType type) {
switch (type) {
case ComponentType.resistor:
return '电阻';
case ComponentType.capacitor:
return '电容';
case ComponentType.inductor:
return '电感';
case ComponentType.diode:
return '二极管';
case ComponentType.transistor:
return '三极管';
case ComponentType.ic:
return '集成电路';
case ComponentType.connector:
return '连接器';
case ComponentType.other:
return '其他';
}
}
IconData _getComponentIcon(ComponentType type) {
switch (type) {
case ComponentType.resistor:
return Icons.rectangle_outlined;
case ComponentType.capacitor:
return Icons.battery_full;
case ComponentType.inductor:
return Icons.waves;
case ComponentType.diode:
return Icons.arrow_right_alt;
case ComponentType.transistor:
return Icons.memory;
case ComponentType.ic:
return Icons.dns;
case ComponentType.connector:
return Icons.usb;
case ComponentType.other:
return Icons.category;
}
}
String _getValueHint() {
switch (widget.propertyData.componentType) {
case ComponentType.resistor:
return '如 10k, 4.7M, 100R';
case ComponentType.capacitor:
return '如 10u, 100n, 1p';
case ComponentType.inductor:
return '如 10uH, 1mH';
default:
return '元件值';
}
}
String _getValueHelper() {
switch (widget.propertyData.componentType) {
case ComponentType.resistor:
return '电阻值 (欧姆)';
case ComponentType.capacitor:
return '电容值 (法拉)';
case ComponentType.inductor:
return '电感值 (亨利)';
default:
return '';
}
}
}
/// 属性数据结构
class PropertyData {
String? refDesignator; // 位号
String? value; // 值
String? footprint; // 封装
String? netName; // 网络名
ComponentType componentType; // 元件类型
String? symbolName; // 符号名称
int rotation; // 旋转角度 (0, 90, 180, 270)
bool mirrorX; // 水平镜像
bool mirrorY; // 垂直镜像
PropertyData({
this.refDesignator,
this.value,
this.footprint,
this.netName,
required this.componentType,
this.symbolName,
this.rotation = 0,
this.mirrorX = false,
this.mirrorY = false,
});
PropertyData copy() {
return PropertyData(
refDesignator: refDesignator,
value: value,
footprint: footprint,
netName: netName,
componentType: componentType,
symbolName: symbolName,
rotation: rotation,
mirrorX: mirrorX,
mirrorY: mirrorY,
);
}
}
/// 元件类型枚举
enum ComponentType {
resistor, // 电阻
capacitor, // 电容
inductor, // 电感
diode, // 二极管
transistor, // 三极管
ic, // 集成电路
connector, // 连接器
other, // 其他
}
/// 面板位置枚举
enum PropertyPanelPosition {
bottom, // 底部弹出
side, // 侧边弹出
floating, // 浮动窗口
}
/// 显示属性面板的辅助函数
Future<PropertyData?> showPropertyPanel(
BuildContext context, {
required PropertyData propertyData,
Function(PropertyData)? onPropertyChanged,
Function(PropertyData)? onPreview,
}) {
return showModalBottomSheet<PropertyData>(
context: context,
isScrollControlled: true,
backgroundColor: Colors.transparent,
builder: (context) => PropertyPanelWidget(
propertyData: propertyData,
onPropertyChanged: onPropertyChanged,
onPreview: onPreview,
),
);
}