// ignore_for_file: invalid_use_of_protected_member import 'dart:ui'; import 'package:fis_measure/interfaces/date_types/point.dart'; import 'package:fis_measure/interfaces/enums/items.dart'; import 'package:fis_measure/interfaces/process/items/item.dart'; import 'package:fis_measure/interfaces/process/items/item_metas.dart'; import 'package:fis_measure/interfaces/process/workspace/point_info.dart'; import 'package:fis_measure/process/calcuators/auto_doppler_trace.dart'; import 'package:fis_measure/process/items/item.dart'; import 'package:fis_measure/process/items/item_feature.dart'; import 'package:fis_measure/process/physical_coordinates/doppler.dart'; import 'package:fis_measure/process/primitives/multi_method/dop_trace_disp/cardiac_cycle.dart'; import 'package:fis_measure/process/primitives/multi_method/dop_trace_disp/data.dart'; import 'package:fis_measure/utils/canvas.dart'; import 'package:fis_measure/utils/prompt_box.dart'; import 'package:fis_measure/view/gesture/positioned_touch_cursor.dart'; import 'package:flutter/material.dart'; import 'package:get/get.dart'; class AutoDopplerTrace extends TraceItemAbstract { AutoDopplerTrace(ItemMeta meta, IMeasureItem? parent) : super(meta, parent); late final touchState = Get.find(); @override bool onExecuteMouse(PointInfo args) { if (state == ItemStates.finished) { if (args.pointType == PointInfoType.mouseDown) { state = ItemStates.waiting; } } if (state == ItemStates.waiting) { if (args.pointType == PointInfoType.mouseDown) { handleMouseDownWhileWaiting(args); handleFinish(); } else if (state == ItemStates.running) { if (args.pointType == PointInfoType.mouseUp) return false; } } return true; } void handleFinish() async { await Future.delayed(const Duration(milliseconds: 50)); List currentCardiacCycleList = feature?.refItem.currentCardiacCycleList ?? []; print(currentCardiacCycleList.length); if (currentCardiacCycleList.isNotEmpty) { feature?.refItem.setInnerPoints([ currentCardiacCycleList.first.systoleStart, currentCardiacCycleList.last.diastoleEnd ]); } feature?.refItem.setCurrentCardiacCycleList([]); feature!.isActive = false; doCalculate(); doFeatureFinish(); doFeatureUpdate(); // 若不执行,子测量将无法自动切换 PromptBox.dismiss(); } @override void doFeatureFinish() { super.doFeatureFinish(); } void synchToMainMonitorScreen(PointInfo args) { final point = args.toAreaLogicPoint(); var x = point.x; var y = point.y; } void handleMouseDownWhileWaiting(PointInfo args) { // TODO: 判断是否当前area // 转换为Area逻辑位置 feature = AutoDopplerTraceFeature(this); if (args.hostVisualArea != null) { feature!.hostVisualArea = args.hostVisualArea; } final point = args.toAreaLogicPoint(); feature!.adopt(point); state = ItemStates.running; } void handleTouchDownWhileWaiting(PointInfo args) { print("画结束了"); // TODO: 判断是否当前area // 转换为Area逻辑位置 feature = AutoDopplerTraceFeature(this); if (args.hostVisualArea != null) { feature!.hostVisualArea = args.hostVisualArea; } final point = args.toAreaLogicPoint(); feature!.adopt(point); // state = ItemStates.running; } PointInfo? startPoint; DPoint touchStartPosition = DPoint(0, 0); // 相对位移起始触摸点 bool isFirstPointMove = false; @override bool onExecuteTouch(PointInfo args) { if (state == ItemStates.finished) { if (args.pointType == PointInfoType.touchDown) { state = ItemStates.waiting; } } if (state == ItemStates.waiting) { if (isFirstPointMove) { args.addOffset(0, -0.2); } switch (args.pointType) { case PointInfoType.touchDown: isFirstPointMove = false; startPoint = args; // 设置线段起点 handleTouchDownWhileWaiting(startPoint!); // 通过设置的起点开始一个绘制事件 break; case PointInfoType.touchUp: startPoint = args; // 设置线段起点 state = ItemStates.running; touchState.touchOffset = Offset.zero; break; // 按下立即抬起无事发生 case PointInfoType.touchMove: if (isMoveTargetOutOfRange(args)) return true; isFirstPointMove = true; final pixelSize = application.displaySize; touchState.touchOffset = DPoint(0, -0.2).scale2Size(pixelSize).toOffset(); feature?.innerPoints.first = args; break; default: break; } } else if (state == ItemStates.running) { if (args.pointType == PointInfoType.touchDown) { touchStartPosition = args; final pixelSize = application.displaySize; touchState.touchOffset = startPoint!.scale2Size(pixelSize).toOffset() - args.scale2Size(pixelSize).toOffset(); } if (args.pointType == PointInfoType.touchUp) { touchState.touchOffset = Offset.zero; doFeatureFinish(); } if (args.pointType == PointInfoType.touchMove) { PointInfo newPoint = PointInfo.fromOffset( startPoint!.clone().addVector(args - touchStartPosition).toOffset(), startPoint!.pointType); if (isMoveTargetOutOfRange(newPoint)) return true; feature?.adopt(newPoint); doCalculate(); } } return true; } static AutoDopplerTrace createTrace( ItemMeta meta, [ IMeasureItem? parent, ]) { AutoDopplerTrace trace = AutoDopplerTrace(meta, parent); trace.calculator = AutoDopplerTraceCal(trace); return trace; } } class AutoDopplerTraceFeature extends AutoDopplerTraceItemFeatureAbstract { AutoDopplerTraceFeature(TraceItemAbstract refItem) : super(refItem); final greenPen = Paint() ..color = const Color.fromARGB(255, 0, 255, 0) ..isAntiAlias = true ..strokeWidth = 1 ..style = PaintingStyle.stroke; final dashLinePan = Paint() ..color = const Color.fromARGB(255, 255, 255, 0) ..isAntiAlias = false ..strokeWidth = 1 ..style = PaintingStyle.stroke; DPoint furthestPoint = DPoint(0, 0); List currentCardiacCycleList = []; late Path path; @override void paint(Canvas canvas, Size size) { final double areaTop = hostVisualArea!.displayRegion.top * size.height; final double areaBottom = hostVisualArea!.displayRegion.bottom * size.height; if (innerPoints.isEmpty) return; // if (innerPoints.length == 1) { // // drawVertex(canvas, convert2ViewPoint(size, innerPoints[0]).toOffset()); // // drawId(canvas, size); // // return; // } final points = innerPoints.map((e) => convert2ViewPoint(size, e)).toList(); final startOffset = convert2ViewPoint(size, startPoint); final endOffset = convert2ViewPoint(size, endPoint); double baseLine = (hostVisualArea!.viewport!.physical as DopplerPhysicalCoordinate) .baseLine; double pwHeight = size.height * hostVisualArea!.layoutRegion!.height; double baseLineHeight = areaTop + baseLine * pwHeight; // TraceListData data = TraceListData(); List maxPonints = []; List logicalPoints = []; /// 当前周期数据 List cardiacCycleList = []; List logicalCardiacCycleList = []; List logicalBestCardiacCycleList = []; path = Path(); /// 最大点集 /// below: 1 /// above: 2 if (startOffset.y < baseLineHeight) { // data.setLocation("above"); logicalPoints = List.generate( TraceListData.aboveMaxPonints.length, (index) => TraceListData.aboveMaxPonints[index], ); logicalCardiacCycleList = List.generate( TraceListData.aboveCardiacCycleList.length, (index) => TraceListData.aboveCardiacCycleList[index], ); logicalBestCardiacCycleList = List.generate( TraceListData.aboveOptimumCardiacCycleList.length, (index) => TraceListData.aboveOptimumCardiacCycleList[index], ); } else { logicalPoints = List.generate( TraceListData.belowMaxPonints.length, (index) => TraceListData.belowMaxPonints[index], ); logicalCardiacCycleList = List.generate( TraceListData.belowCardiacCycleList.length, (index) => TraceListData.belowCardiacCycleList[index], ); logicalBestCardiacCycleList = List.generate( TraceListData.belowOptimumCardiacCycleList.length, (index) => TraceListData.belowOptimumCardiacCycleList[index], ); } maxPonints = logicalPoints.map((e) => convert2ViewPoint(size, e)).toList(); for (int i = 0; i < logicalCardiacCycleList.length; i++) { logicalCardiacCycleList[i].index = i; cardiacCycleList.add(logicalCardiacCycleList[i]); } /// 这是正在绘制的 if (points.isNotEmpty) { /// 这边自动测量的最佳周期先默认选择3 后面需要配置 if (logicalBestCardiacCycleList.length > 3) { if (logicalBestCardiacCycleList[2] == null) { return; } DPoint logicalBestCardiacCycleStartDPoint = convert2ViewPoint( size, logicalBestCardiacCycleList[2].bestSystoleStart); DPoint logicalBestCardiacCycleEndDPoint = convert2ViewPoint( size, logicalBestCardiacCycleList[2].bestDiastoleEnd); // 绘制路径 final path = Path() ..moveTo(logicalBestCardiacCycleStartDPoint.x, logicalBestCardiacCycleStartDPoint.y); for (var point in maxPonints) { if (point.x >= logicalBestCardiacCycleStartDPoint.x && point.x <= logicalBestCardiacCycleEndDPoint.x) { path.lineTo(point.x, point.y); } } canvas.drawPath(path, greenPen); /// 根据周期绘制 for (CardiacCycle i in cardiacCycleList) { DPoint diastoleEnd = convert2ViewPoint(size, i.diastoleEnd); DPoint systoleStart = convert2ViewPoint(size, i.systoleStart); DPoint ePeak = convert2ViewPoint(size, i.ePeak ?? DPoint(0, 0)); /// ai提供的周期数据可能不对 if (systoleStart.x <= diastoleEnd.x && systoleStart.x >= logicalBestCardiacCycleStartDPoint.x && diastoleEnd.x <= logicalBestCardiacCycleEndDPoint.x) { canvas.drawDashLine( Offset(systoleStart.x, areaTop), Offset(systoleStart.x, areaBottom), 3, 3, dashLinePan, ); canvas.drawDashLine( Offset(diastoleEnd.x, areaTop), Offset(diastoleEnd.x, areaBottom), 3, 3, dashLinePan, ); if (ePeak.x < endOffset.x) { drawCrossVertex( canvas, ePeak.toOffset(), ); } currentCardiacCycleList.add(i); } } } refItem .setCurrentCardiacCycleList(currentCardiacCycleList.toSet().toList()); canvas.drawPath( path, greenPen, ); } } /// 转成物理坐标 DPoint convert2LogicPoint( Size size, DPoint viewPoint, double baseLine, double width, double height, ) { final x = viewPoint.x / size.width * width; final y = viewPoint.y - (baseLine * size.height) * height; return DPoint(x, y); } } abstract class AutoDopplerTraceItemFeatureAbstract extends MeasureItemFeature { AutoDopplerTraceItemFeatureAbstract(TraceItemAbstract refItem) : super(refItem); @override TraceItemAbstract get refItem => super.refItem as TraceItemAbstract; DPoint get startPoint => innerPoints.first; DPoint get endPoint => innerPoints.last; bool ifRightSide = true; /// 接收新坐标 void adopt(DPoint point) { if (innerPoints.isEmpty) { innerPoints.add(point); } if (point.x > startPoint.x) { handleChangeSide(point); if (point.x < innerPoints.last.x) { clearRight(point.x); } else if (point.x > innerPoints.last.x) { innerPoints.add(point); } } else { handleChangeSide(point); if (point.x > innerPoints.last.x) { clearLeft(point.x); } else if (point.x < innerPoints.last.x) { innerPoints.add(point); } } } void clearRight(double X) { if (innerPoints.isEmpty) return; for (var i = innerPoints.length - 1; i >= 0; i--) { if (innerPoints[i].x >= X) { innerPoints.removeAt(i); } } } void clearLeft(double X) { if (innerPoints.isEmpty) return; for (var i = innerPoints.length - 1; i >= 0; i--) { if (innerPoints[i].x <= X) { innerPoints.removeAt(i); } } } void handleChangeSide(point) { if (ifRightSide) { if (point.x < startPoint.x) { ifRightSide = false; innerPoints.clear(); innerPoints.add(point); } } else { if (point.x > startPoint.x) { ifRightSide = true; innerPoints.clear(); innerPoints.add(point); } } } } abstract class TraceItemAbstract extends MeasureItem { TraceItemAbstract(ItemMeta meta, IMeasureItem? parent) : super(meta, parent); List currentCardiacCycleList = []; void setCurrentCardiacCycleList(List _currentCardiacCycleList) { currentCardiacCycleList = _currentCardiacCycleList; } void setInnerPoints(List points) { feature!.innerPoints.clear(); feature!.innerPoints.addAll(points); } void clearInnerPoints() { feature!.innerPoints.clear(); } }