// ignore_for_file: invalid_use_of_protected_member 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/semiauto_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/view/gesture/positioned_touch_cursor.dart'; import 'package:flutter/material.dart'; import 'package:get/get.dart'; class SemiautoTrace extends TraceItemAbstract { SemiautoTrace(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); } } else if (state == ItemStates.running) { if (args.pointType == PointInfoType.mouseUp) return false; feature?.adopt(args); doCalculate(); if (args.pointType == PointInfoType.mouseDown) { List currentCardiacCycleList = feature?.refItem.currentCardiacCycleList ?? []; print(currentCardiacCycleList.length); if (currentCardiacCycleList.isNotEmpty) { feature?.refItem.setInnerPoints([ currentCardiacCycleList.first.systoleStart, currentCardiacCycleList.last.diastoleEnd ]); } feature?.refItem.setCurrentCardiacCycleList([]); doFeatureFinish(); } } return true; } // DPoint convertToAreaPoint(DPoint point) { // /// 像素坐标 // final x = point.x; // final y = point.y; // final application = Get.find(); // Size displaySize = application.displaySize; // return DPoint(x / displaySize.width, y / displaySize.height); // } @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) { print("正在画"); // TODO: 判断是否当前area // 转换为Area逻辑位置 feature = SemiautoTraceFeature(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 = SemiautoTraceFeature(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 SemiautoTrace createTrace( ItemMeta meta, [ IMeasureItem? parent, ]) { SemiautoTrace trace = SemiautoTrace(meta, parent); trace.calculator = SemiautoTraceCal(trace); return trace; } } class SemiautoTraceFeature extends SemiautoTraceItemFeatureAbstract { SemiautoTraceFeature(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; } drawId(canvas, size); 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 = []; 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], ); } else { logicalPoints = List.generate( TraceListData.belowMaxPonints.length, (index) => TraceListData.belowMaxPonints[index], ); logicalCardiacCycleList = List.generate( TraceListData.belowCardiacCycleList.length, (index) => TraceListData.belowCardiacCycleList[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.length > 1) { final firstPoint = points.first; final lastPoint = points.last; DPoint? startDPoint = maxPonints.firstWhereOrNull( (element) => element.x.toInt() == startOffset.x.toInt(), ); var firstIndex = maxPonints.indexWhere( (element) => element.x.toInt() == firstPoint.x.toInt(), ); var lastIndex = maxPonints.indexWhere( (element) => element.x.toInt() == lastPoint.x.toInt(), ); if (firstIndex == -1) { firstIndex = 0; } if (startPoint == null) { return; } path = Path(); path.moveTo(startDPoint!.x, startDPoint.y); List points2 = []; path.moveTo(maxPonints[firstIndex].x, maxPonints[firstIndex].y); for (int i = 0; i < maxPonints.length; i++) { if (lastIndex == -1) { lastIndex = maxPonints.length - 1; } if (firstIndex > lastIndex) { points2 = maxPonints.sublist(lastIndex, firstIndex); } else { points2 = maxPonints.sublist(firstIndex, lastIndex); } } if (firstIndex > lastIndex) { for (var i = points2.length - 1; i > 1; i--) { final point = points2[i]; DPoint endDPoint = maxPonints.firstWhere( (element) => element.x.toInt() == point.x.toInt(), ); path.lineTo(endDPoint.x, endDPoint.y); } } else { for (var i = 1; i < points2.length; i++) { final point = points2[i]; DPoint endDPoint = maxPonints.firstWhere( (element) => element.x.toInt() == point.x.toInt(), ); path.lineTo(endDPoint.x, endDPoint.y); } } /// 根据周期绘制 for (CardiacCycle i in cardiacCycleList) { CardiacCycle? currentCycle = currentCardiacCycleList.firstWhereOrNull( (element) => element.index == i.index, ); 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) { if (startOffset.x <= systoleStart.x && endOffset.x >= diastoleEnd.x) { if (systoleStart.x >= startOffset.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(), ); } } if (currentCycle == null) { currentCardiacCycleList.add(i); } } else { if (currentCycle != null) { currentCardiacCycleList.remove(i); } } } } refItem .setCurrentCardiacCycleList(currentCardiacCycleList.toSet().toList()); canvas.drawPath( path, greenPen, ); } } } abstract class SemiautoTraceItemFeatureAbstract extends MeasureItemFeature { SemiautoTraceItemFeatureAbstract(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; // String currentLocal = "11111"; /// 接收新坐标 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(); } }