measure_main_view.dart 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530
  1. import 'dart:ui';
  2. import 'package:fis_i18n/i18n.dart';
  3. import 'package:fis_jsonrpc/rpc.dart';
  4. import 'package:fis_measure/interfaces/date_types/int_size.dart';
  5. import 'package:fis_measure/interfaces/enums/operate.dart';
  6. import 'package:fis_measure/interfaces/process/calculators/output.dart';
  7. import 'package:fis_measure/interfaces/process/player/play_controller.dart';
  8. import 'package:fis_measure/interfaces/process/standard_line/calibration.dart';
  9. import 'package:fis_measure/interfaces/process/workspace/application.dart';
  10. import 'package:fis_measure/interfaces/process/workspace/measure_3d_view_controller.dart';
  11. import 'package:fis_measure/process/workspace/measure_3d_view_controller.dart';
  12. import 'package:fis_measure/process/workspace/measure_data_controller.dart';
  13. import 'package:fis_measure/process/workspace/measure_handler.dart';
  14. import 'package:fis_measure/process/workspace/third_part/application.dart';
  15. import 'package:fis_measure/process/workspace/third_part/calibration_controller.dart';
  16. import 'package:fis_measure/utils/canvas.dart';
  17. import 'package:fis_measure/values/colors.dart';
  18. import 'package:fis_measure/view/button_group/button_group.dart';
  19. import 'package:fis_measure/view/3d_view/carotid_player.dart';
  20. import 'package:fis_measure/view/gesture/annotation/annotation_gesture.dart';
  21. import 'package:fis_measure/view/measure/capture_image.dart';
  22. import 'package:fis_measure/view/measure/measure_result.dart';
  23. import 'package:fis_measure/view/paint/ai_patint.dart';
  24. import 'package:fis_measure/view/paint/ai_patint_controller.dart';
  25. import 'package:fis_measure/view/paint/ai_patint_result.dart';
  26. import 'package:fis_measure/view/result/results_panel.dart';
  27. import 'package:fis_measure/view/standard_line/calibration_canvas.dart';
  28. import 'package:fis_measure/view/standard_line/calibration_gesture.dart';
  29. import 'package:fis_ui/index.dart';
  30. import 'package:flutter/material.dart';
  31. import 'package:flutter/rendering.dart';
  32. import 'package:get/get.dart';
  33. import 'package:fis_measure/view/canvas/active_canvas.dart';
  34. import 'package:fis_measure/view/canvas/annotation_canvas.dart';
  35. import 'package:fis_measure/view/canvas/records_canvas.dart';
  36. import 'package:fis_measure/view/gesture/mouse_gesture.dart';
  37. import 'package:fis_measure/view/pause/pause_panel.dart';
  38. import 'package:fis_measure/view/player/controller.dart';
  39. import 'package:fis_measure/view/player/player.dart';
  40. class MeasureMainView extends StatefulWidget implements FWidget {
  41. const MeasureMainView({Key? key}) : super(key: key);
  42. @override
  43. State<StatefulWidget> createState() => _MeasureMainViewState();
  44. }
  45. class _MeasureMainViewState extends State<MeasureMainView> {
  46. late final application = Get.find<IApplication>();
  47. late final playerController = Get.find<IPlayerController>();
  48. late final measure3DViewController = Get.find<Measure3DViewController>();
  49. late final measureHandler = Get.find<MeasureHandler>();
  50. /// 测量数据
  51. final measureData = Get.find<MeasureDataController>();
  52. late bool canMeasure = application.canMeasure;
  53. late final aiPatintController = Get.find<AiPatintController>();
  54. late bool enableCarotid2DMeasure = false;
  55. bool isCaptureState = false;
  56. late double calibrationLine = 4;
  57. final List<OutputItem> outputs = [];
  58. /// 是否显示进度条
  59. bool ifShowProgressBar = true;
  60. /// 参考校准线控制器
  61. IStandardLineCalibrationController? standardLineCalibrationController;
  62. final playerKey = GlobalKey();
  63. final _key = GlobalKey();
  64. bool get inAnnotation =>
  65. application.currentOperateType == MeasureOperateType.annotation;
  66. @override
  67. void initState() {
  68. super.initState();
  69. installStandardLine();
  70. VidPlayerController vidPlayerController =
  71. playerController as VidPlayerController;
  72. WidgetsBinding.instance!.addPostFrameCallback((timeStamp) {
  73. onCanMeasureChanged(this, application.canMeasure);
  74. application.canMeasureChanged.addListener(onCanMeasureChanged);
  75. application.operateTypeChanged.addListener(onOperateTypeChanged);
  76. measure3DViewController.updatePlayerMode.addListener(_onModeChanged);
  77. /// [Carotid] ✅在此判断是否为颈动脉2D图像,如果是,则不显示进度条且读取设置播放器单帧缓存
  78. if (measure3DViewController.curMeasureMode == MeasureMode.carotid2DMode) {
  79. vidPlayerController.pause();
  80. vidPlayerController
  81. .set2DMeasureFrame(measure3DViewController.image4Measure!);
  82. setState(() {
  83. enableCarotid2DMeasure = true;
  84. ifShowProgressBar = false;
  85. });
  86. application.clearRecords();
  87. }
  88. });
  89. }
  90. @override
  91. void dispose() {
  92. application.canMeasureChanged.removeListener(onCanMeasureChanged);
  93. application.operateTypeChanged.removeListener(onOperateTypeChanged);
  94. measure3DViewController.updatePlayerMode.removeListener(_onModeChanged);
  95. uninstallStandardLine();
  96. super.dispose();
  97. }
  98. /// 装载参考校准线
  99. void installStandardLine() {
  100. if (application.isThirdPart) {
  101. final standradLine = (application as ThirdPartApplication).standardLine;
  102. standardLineCalibrationController =
  103. StandardLineCalibrationController(application, standradLine);
  104. standardLineCalibrationController!.editStateChanged
  105. .addListener(onStandardLineCalibrationStateChanged);
  106. Get.put<IStandardLineCalibrationController>(
  107. standardLineCalibrationController!);
  108. }
  109. }
  110. /// 卸载参考校准线
  111. void uninstallStandardLine() {
  112. standardLineCalibrationController?.editStateChanged
  113. .removeListener(onStandardLineCalibrationStateChanged);
  114. Get.delete<IStandardLineCalibrationController>();
  115. }
  116. /// 模式改变触发更新
  117. /// [Carotid] 🚧预留消息通知,虽然目前不会触发到,但是为了以后组件不销毁的情况下,切换模式的时候,可以触发
  118. void _onModeChanged(Object s, MeasureMode mode) {
  119. switch (mode) {
  120. case MeasureMode.vidMode:
  121. setState(() {
  122. ifShowProgressBar = true;
  123. enableCarotid2DMeasure = false;
  124. });
  125. break;
  126. case MeasureMode.carotid2DMode:
  127. setState(() {
  128. ifShowProgressBar = false;
  129. enableCarotid2DMeasure = true;
  130. });
  131. application.clearRecords();
  132. break;
  133. case MeasureMode.carotid3DMode:
  134. break;
  135. }
  136. }
  137. /// 保存图片
  138. void capturePng() async {
  139. isCaptureState = true;
  140. setState(() {});
  141. for (var item in application.measureItems) {
  142. if (item.calculator != null) {
  143. // 添加历史测量值
  144. outputs.addAll(item.calculator!.outputs);
  145. if (item.feature?.isActive == true && item.calculator!.output != null) {
  146. // 添加活动测量值
  147. outputs.add(item.calculator!.output!);
  148. }
  149. }
  150. }
  151. MeasureResult measureResult = MeasureResult(
  152. measureApplicationName: application.applicationName,
  153. outputList: outputs,
  154. );
  155. await Future.delayed(const Duration(milliseconds: 10), () async {
  156. final RenderRepaintBoundary? boundary =
  157. _key.currentContext?.findRenderObject() as RenderRepaintBoundary?;
  158. if (boundary != null) {
  159. final image = await boundary.toImage();
  160. final byteData = await image.toByteData(format: ImageByteFormat.png);
  161. final pngBytes = byteData!.buffer.asUint8List();
  162. Get.dialog(
  163. CaptureImage(
  164. bytes: pngBytes,
  165. ),
  166. transitionCurve: Curves.bounceInOut,
  167. );
  168. await measureData.saveImage.call(
  169. pngBytes,
  170. measureData.measureImageData.patientCode ?? '',
  171. measureData.measureImageData.recordCode ?? '',
  172. measureData.measureImageData.remedicalCode ?? '',
  173. measureResult.toDisplay(),
  174. );
  175. isCaptureState = false;
  176. Future.delayed(
  177. const Duration(
  178. milliseconds: 600,
  179. ),
  180. () {
  181. Get.back();
  182. },
  183. );
  184. setState(() {});
  185. }
  186. });
  187. }
  188. void onCanMeasureChanged(Object sender, bool e) {
  189. if (e != canMeasure) {
  190. setState(() {
  191. canMeasure = e;
  192. });
  193. }
  194. }
  195. void onOperateTypeChanged(Object sender, MeasureOperateType e) {
  196. setState(() {});
  197. }
  198. void onStandardLineCalibrationStateChanged(
  199. Object sender, StandardLineCalibrationEditState e) {
  200. setState(() {
  201. if (e == StandardLineCalibrationEditState.drawn) {
  202. Get.dialog(buildCalibrationLine());
  203. }
  204. });
  205. }
  206. FWidget buildCalibrationLine() {
  207. return FSimpleDialog(
  208. title: FText(
  209. i18nBook.measure.guideCalibration.t,
  210. style: const TextStyle(
  211. color: Colors.white,
  212. fontSize: 18,
  213. ),
  214. ),
  215. isDefault: true,
  216. onOk: () {
  217. standardLineCalibrationController!.confirmEdit(calibrationLine);
  218. Get.back();
  219. },
  220. onCancel: () {
  221. standardLineCalibrationController!.cancelEdit();
  222. Get.back();
  223. },
  224. children: [
  225. buildCalibrationLineItem(
  226. i18nBook.measure.length.t,
  227. FTextField(
  228. decoration: InputDecoration(
  229. hintText: i18nBook.common.input.t + i18nBook.measure.length.t,
  230. hintStyle: const TextStyle(
  231. fontSize: 16,
  232. ),
  233. enabledBorder: OutlineInputBorder(
  234. borderSide: BorderSide(
  235. color: Colors.white.withOpacity(0.5),
  236. width: 0.5,
  237. style: BorderStyle.solid,
  238. ),
  239. ),
  240. focusedBorder: const OutlineInputBorder(
  241. borderSide: BorderSide(
  242. color: Colors.blue,
  243. width: 0.5,
  244. style: BorderStyle.solid,
  245. ),
  246. ),
  247. filled: true,
  248. ),
  249. onChanged: (val) => calibrationLine = double.parse(val),
  250. ),
  251. ),
  252. buildCalibrationLineItem(
  253. i18nBook.measure.unit.t,
  254. const FText('cm'),
  255. ),
  256. ],
  257. );
  258. }
  259. FWidget buildCalibrationLineItem(String name, FWidget itemWidget) {
  260. return FContainer(
  261. padding: const EdgeInsets.symmetric(vertical: 15, horizontal: 25),
  262. child: FRow(
  263. children: [
  264. FContainer(
  265. width: 60,
  266. child: FText(name),
  267. ),
  268. FExpanded(
  269. child: itemWidget,
  270. ),
  271. ],
  272. ),
  273. );
  274. }
  275. @override
  276. Widget build(BuildContext context) {
  277. MeasureCanvasExt.setFontFamily(
  278. Theme.of(context).textTheme.button?.fontFamily);
  279. bool canShowAI = [
  280. DiagnosisConclusionEnum.Benign,
  281. DiagnosisConclusionEnum.Malignant,
  282. DiagnosisConclusionEnum.BenignAndMalignant
  283. ].contains(measureData.diagnosisConclusion);
  284. return Container(
  285. color: MeasureColors.Background,
  286. child: RepaintBoundary(
  287. child: Column(
  288. children: [
  289. Expanded(
  290. child: Row(
  291. crossAxisAlignment: CrossAxisAlignment.start,
  292. children: [
  293. Expanded(
  294. child: RepaintBoundary(
  295. key: _key,
  296. child: CustomMultiChildLayout(
  297. delegate: _LayerLayoutDelegate(),
  298. children: [
  299. LayoutId(
  300. id: _LayerLayoutIds.player,
  301. child: enableCarotid2DMeasure
  302. ? CarotidPlayer(
  303. measure3DViewController,
  304. )
  305. : VidPlayer(
  306. playerController as VidPlayerController,
  307. ),
  308. ),
  309. if (canMeasure) ...[
  310. LayoutId(
  311. id: _LayerLayoutIds.recordsCanvas,
  312. child: const MeasureRecordsCanvasPanel(),
  313. ),
  314. LayoutId(
  315. id: _LayerLayoutIds.activeMeasure,
  316. child: const MeasureActiveCanvasPanel(),
  317. ),
  318. LayoutId(
  319. id: _LayerLayoutIds.activeAnnotation,
  320. child: const AnnotationCanvas(),
  321. ),
  322. if (application.isThirdPart)
  323. LayoutId(
  324. id: _LayerLayoutIds.standardLineCalibration,
  325. child: StandardLineCalibrationCanvas(
  326. standardLineCalibrationController!),
  327. ),
  328. LayoutId(
  329. id: _LayerLayoutIds.gesture,
  330. child: _buildGestureLayer(),
  331. ),
  332. LayoutId(
  333. id: _LayerLayoutIds.result,
  334. child: const MeasureResultPanel(),
  335. ),
  336. if (!isCaptureState)
  337. LayoutId(
  338. id: _LayerLayoutIds.buttonGroups,
  339. child: ButtonGroup(
  340. capturePng: () => capturePng(),
  341. ),
  342. )
  343. ],
  344. if (canShowAI) ...[
  345. LayoutId(
  346. id: _LayerLayoutIds.paintAI,
  347. child: AIPaintInfo(
  348. playerController as VidPlayerController,
  349. ),
  350. )
  351. ],
  352. ],
  353. ),
  354. ),
  355. ),
  356. if (canShowAI && !measureHandler.fullScreenState) ...[
  357. Obx(() {
  358. if (aiPatintController.state.isShowAi) {
  359. return SizedBox(
  360. width: 200,
  361. child: AIPaintInfoReslut(
  362. playerController as VidPlayerController,
  363. ),
  364. );
  365. } else {
  366. return const SizedBox();
  367. }
  368. })
  369. ]
  370. ],
  371. ),
  372. ),
  373. ifShowProgressBar ? const MeasurePausePanel() : Container(),
  374. ],
  375. ),
  376. ),
  377. );
  378. }
  379. Widget _buildGestureLayer() {
  380. if (application.isThirdPart) {
  381. if (standardLineCalibrationController!.isEditing) {
  382. return StandardLineCalibrationGesture(
  383. standardLineCalibrationController!);
  384. }
  385. }
  386. return inAnnotation
  387. ? const AnnotationGestureLayer()
  388. : const MeasureMouseGesturePanel();
  389. }
  390. }
  391. class _LayerLayoutDelegate extends MultiChildLayoutDelegate {
  392. Offset? layoutOffset;
  393. Size? layoutSize;
  394. _LayerLayoutDelegate();
  395. @override
  396. void performLayout(Size size) {
  397. if (!hasChild(_LayerLayoutIds.player)) return;
  398. final application = Get.find<IApplication>();
  399. final vidFrame = application.frameData;
  400. final imageSize = IntSize.fill(vidFrame?.width ?? 0, vidFrame?.height ?? 0);
  401. /// 以Contain方式填充布局,计算定位偏移量
  402. calcSize(size, imageSize);
  403. final offset = layoutOffset!;
  404. final renderSize = layoutSize!;
  405. /// 同步图像显示尺寸
  406. application.displaySize = renderSize;
  407. layoutLayer(_LayerLayoutIds.player, offset, renderSize);
  408. /// 其他层按播放器尺寸位置层叠布局
  409. layoutLayer(_LayerLayoutIds.recordsCanvas, offset, renderSize);
  410. layoutLayer(_LayerLayoutIds.activeMeasure, offset, renderSize);
  411. layoutLayer(_LayerLayoutIds.activeAnnotation, offset, renderSize);
  412. layoutLayer(_LayerLayoutIds.standardLineCalibration, offset, renderSize);
  413. layoutLayer(_LayerLayoutIds.gesture, offset, renderSize);
  414. layoutLayer(
  415. _LayerLayoutIds.result,
  416. offset + const Offset(2, 2),
  417. renderSize,
  418. );
  419. layoutLayer(_LayerLayoutIds.pause, offset, renderSize);
  420. layoutLayer(_LayerLayoutIds.paintAI, offset, renderSize);
  421. layoutLayer(_LayerLayoutIds.buttonGroups, offset, renderSize);
  422. }
  423. void layoutLayer(_LayerLayoutIds layoutId, Offset offset, Size size) {
  424. if (hasChild(layoutId)) {
  425. layoutChild(
  426. layoutId,
  427. BoxConstraints.loose(size),
  428. );
  429. positionChild(layoutId, offset);
  430. }
  431. }
  432. void calcSize(Size size, IntSize imageSize) {
  433. final parentWHRatio = size.width / size.height;
  434. final imageWHRatio = imageSize.width / imageSize.height;
  435. if (imageWHRatio < parentWHRatio) {
  436. // 高度撑满
  437. final layoutWidth = size.height * imageWHRatio;
  438. final layoutHeight = size.height;
  439. final offsetX = (size.width - layoutWidth) / 2;
  440. layoutOffset = Offset(offsetX, 0);
  441. layoutSize = Size(layoutWidth, layoutHeight);
  442. } else if (imageWHRatio > parentWHRatio) {
  443. // 宽度撑满
  444. final layoutWidth = size.width;
  445. final layoutHeight = size.width / imageWHRatio;
  446. final offsetY = (size.height - layoutHeight) / 2;
  447. layoutOffset = Offset(0, offsetY);
  448. layoutSize = Size(layoutWidth, layoutHeight);
  449. } else {
  450. layoutOffset = Offset.zero;
  451. layoutSize = size;
  452. }
  453. }
  454. @override
  455. bool shouldRelayout(covariant MultiChildLayoutDelegate oldDelegate) {
  456. return false;
  457. }
  458. }
  459. enum _LayerLayoutIds {
  460. /// 播放器
  461. player,
  462. /// 测量记录画板
  463. recordsCanvas,
  464. /// 活动测量画板
  465. activeMeasure,
  466. /// 活动注释画板
  467. activeAnnotation,
  468. /// 结果面板
  469. result,
  470. /// 手势面板
  471. gesture,
  472. /// 暂停画板 后面需要优化命名
  473. pause,
  474. /// AI画板
  475. paintAI,
  476. /// 按钮组
  477. buttonGroups,
  478. /// 参考校准线画板
  479. standardLineCalibration,
  480. }