measure_main_view.dart 17 KB

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