import 'dart:math'; import 'package:bubble/bubble.dart'; import 'package:flutter/material.dart'; import 'package:get/get.dart'; import 'package:trackoffical_app/screen.dart'; import 'package:trackoffical_app/service/game/game_model.dart'; import 'package:trackoffical_app/view/ingame/widget_ruler.dart'; import 'package:trackoffical_app/widget/compass2.dart'; import 'dart:ui' as ui; import '../../model/game_map.dart'; import '../../model/m_control_point.dart'; import '../../service/game/game.dart'; import 'in_game_controller.dart'; const cpRadius = 15.0; class LayerMap extends StatelessWidget { const LayerMap({super.key}); @override Widget build(BuildContext context) { return Stack( children: [ _LayerMapRotateStatic(), _Trajectory(), _LayerMapRuler(), _LayerMapFrontDraw(), _LayerCPNum(), _LayerCPStartBubble(), _LayerCompass(), ], ); } } /// 地图图层,所有元素静态跟随整体旋转 class _LayerMapRotateStatic extends GetView { @override Widget build(BuildContext context) { return Obx(() { final model = controller; // model.mapRotateCenter = Offset(context.width / 2, context.height / 2); var tran = model.mapTransformMatrix.value; return Center( child: Transform( transform: tran, // alignment: Alignment.center, child: SizedBox( height: context.height, width: context.width, child: Stack( children: [ _StaticMap(), _RoutePoints(tran, model.startAt == null), ], )), ), ); }); } } class _RoutePoints extends StatefulWidget { const _RoutePoints(this.tran, this.isOnlyStart); final Matrix4 tran; final bool isOnlyStart; @override State createState() { return _RoutePointsState(); } } class _RoutePointsState extends State<_RoutePoints> with SingleTickerProviderStateMixin { final model = Get.find(); final controller = Get.find(); late AnimationController _animationController; late Animation _animation; @override void initState() { super.initState(); _animationController = AnimationController( vsync: this, duration: const Duration(milliseconds: 500), reverseDuration: const Duration(milliseconds: 500), )..repeat(reverse: true); _animation = IntTween(begin: 255, end: 0).animate(_animationController) ..addListener(() { setState(() { // The state that has changed here is the animation object’s value. }); }); } @override Widget build(BuildContext context) { final radiusPx = controller.mapStatus.mapScaleSrc * cpRadius; final info = controller.mapStatus.gameMapData; final matrix = widget.tran; return Obx(() { var points = controller.controlPointWantSequence; return SizedBox( height: context.height, width: context.width, child: CustomPaint( painter: _RoutePointsPainter( points, info, matrix, radiusPx, _animation.value ?? 255, widget.isOnlyStart, model.nextPlanPointIndex.value, ), ), ); }); } @override void dispose() { _animationController.dispose(); super.dispose(); } } class _RoutePointsPainter extends CustomPainter { final List _points; final GameMap info; final Matrix4 matrix; final service = GameService.to; final double radiusPx; final int nextAlpha; static const nextColor = Colors.red; final bool isOnlyStart; final int? nextPlanIndex; _RoutePointsPainter(this._points, this.info, this.matrix, this.radiusPx, this.nextAlpha, this.isOnlyStart, this.nextPlanIndex); @override void paint(Canvas canvas, Size size) { final paint = Paint(); final strokeWidthColor = radiusPx * 0.15; final strokeWidthWhite = strokeWidthColor * 2; paint.strokeWidth = strokeWidthColor; final picFirstScale = service.mapStatus.picFirstScale; // final textSize = radiusPx * 0.6; final textSize = -22.0 / matrix[0]; // const textSize = 5.0; const isShowText = false; var lastXY = Offset.zero; for (var i = _points.length - 1; i >= 0; i--) { if (isOnlyStart && i != 0) { continue; } final one = _points[i]; if (one.onMap != Offset.zero) { // var xy = service.mapStatus.picOffsetToScreen(one.onMap); var xy = Offset(one.onMap.dx * picFirstScale, one.onMap.dy * picFirstScale); var x = xy.dx; var y = xy.dy; var isNext = false; var isJumped = false; if (nextPlanIndex == null) { isNext = one.isNext; } else { isNext = i == nextPlanIndex; isJumped = !one.isSuccess && i < nextPlanIndex!; } var color = const Color(0xffff00f7); var strokeColor = Colors.white; if (one.isSuccess) { color = Colors.green; } if (isNext) { color = nextColor.withAlpha(nextAlpha); strokeColor = Colors.white.withAlpha(nextAlpha); // paint.color = const Color(0xffff4d00); } if (isJumped) { color = Colors.grey; } paint.style = PaintingStyle.fill; final thisXY = Offset(x, y); // canvas.drawCircle(thisXY, 2, paint); // if (lastXY != Offset.zero) { if (i - 1 >= 0) { final next = _points[i - 1]; lastXY = Offset( next.onMap.dx * picFirstScale, next.onMap.dy * picFirstScale); final dx = thisXY.dx - lastXY.dx; final dy = thisXY.dy - lastXY.dy; final len = sqrt(dx * dx + dy * dy); final dx1 = dx / len * radiusPx; final dy1 = dy / len * radiusPx; var p1 = lastXY + Offset(dx1, dy1); var p2 = thisXY - Offset(dx1, dy1); if (p1.dx.isNaN || p1.dy.isNaN || p2.dx.isNaN || p2.dy.isNaN) { continue; } paint.color = strokeColor; paint.strokeWidth = strokeWidthWhite; canvas.drawLine(p1, p2, paint); paint.color = color; paint.strokeWidth = strokeWidthColor; canvas.drawLine(p1, p2, paint); } lastXY = thisXY; if (one.isStart) { final path = Path(); final widthLeft = radiusPx * 0.9; final widthRight = radiusPx * 1.1; final heightHalf = radiusPx; path.moveTo(x - widthLeft, y - heightHalf); path.lineTo(x + widthRight, y); path.lineTo(x - widthLeft, y + heightHalf); path.close(); paint.style = PaintingStyle.stroke; paint.color = strokeColor; paint.strokeWidth = strokeWidthWhite; canvas.drawPath(path, paint); paint.color = color; paint.strokeWidth = strokeWidthColor; canvas.drawPath(path, paint); } else if (one.isFinish) { paint.style = PaintingStyle.stroke; paint.color = strokeColor; paint.strokeWidth = strokeWidthWhite; canvas.drawCircle(thisXY, radiusPx, paint); canvas.drawCircle(thisXY, radiusPx * 0.7, paint); paint.color = color; paint.strokeWidth = strokeWidthColor; canvas.drawCircle(thisXY, radiusPx, paint); canvas.drawCircle(thisXY, radiusPx * 0.7, paint); } else { paint.style = PaintingStyle.stroke; paint.color = strokeColor; paint.strokeWidth = strokeWidthWhite; canvas.drawCircle(thisXY, radiusPx, paint); paint.color = color; paint.strokeWidth = strokeWidthColor; canvas.drawCircle(thisXY, radiusPx, paint); } if (!one.isStart && !one.isFinish && isShowText) { var textPainter = TextPainter( text: TextSpan( text: one.sn, style: TextStyle( color: paint.color, fontSize: textSize, fontWeight: FontWeight.w700)), textDirection: TextDirection.rtl, textWidthBasis: TextWidthBasis.longestLine, maxLines: 1, )..layout(); textPainter.paint( canvas, Offset(x - textPainter.width / 2, y - textPainter.height - radiusPx)); } } } } @override bool shouldRepaint(_RoutePointsPainter oldDelegate) => false; } class _LayerMapFrontDraw extends GetView { final model = Get.find(); @override Widget build(BuildContext context) { return Obx(() { var points = controller.controlPointWantSequence; final info = controller.mapStatus.gameMapData; final matrix = controller.mapStatus.matrix.value; var showLocationAlpha = 255; if (controller.isOutBoundary || model.isEnableUserLocation) { showLocationAlpha = 255; } if (!model.isEnableUserLocation) { showLocationAlpha = 0; } return SizedBox( height: context.height, width: context.width, child: CustomPaint( painter: _LayerMapFrontDrawPainter( points, info, matrix, controller.positionOnMap ?? Offset.zero, controller.mapStatus.compassAngle, controller.mapStatus.rotateRadian, showLocationAlpha), ), ); }); } } class _LayerMapFrontDrawPainter extends CustomPainter { final List _points; final GameMap info; final Matrix4 matrix; final Offset myPositionOnMap; final service = GameService.to; final double northRadians; final double mapRadians; final int showLocationAlpha; _LayerMapFrontDrawPainter( this._points, this.info, this.matrix, this.myPositionOnMap, this.northRadians, this.mapRadians, this.showLocationAlpha); @override void paint(Canvas canvas, Size size) { final paint = Paint(); drawMyLocation(canvas, paint); // drawRouteNumber(canvas, paint); } @override bool shouldRepaint(_LayerMapFrontDrawPainter oldDelegate) => false; drawMyLocation(Canvas canvas, Paint paint) { var myXY = service.mapStatus.picOffsetToScreen(myPositionOnMap); // final myXY = myPositionOnMap; final color = (const Color(0xFFFF0000)).withAlpha(showLocationAlpha); final color2 = (const Color(0xFFFFA1A1)).withAlpha(showLocationAlpha); paint.color = color; var rect = Rect.fromLTRB(myXY.dx - 12, myXY.dy - 40, myXY.dx + 12, myXY.dy); paint.shader = ui.Gradient.linear(rect.topCenter, rect.bottomCenter, [ const Color(0x00FFFFFF), color2 ], [ 0, 1, ]); final path = Path(); path.moveTo(rect.topLeft.dx, rect.topLeft.dy); path.lineTo(rect.topRight.dx, rect.topRight.dy); path.lineTo(rect.bottomCenter.dx, rect.bottomCenter.dy); canvas.drawPath(path, paint); paint.shader = null; paint.color = Colors.white.withAlpha(showLocationAlpha); canvas.drawCircle(myXY, 8, paint); paint.color = color; canvas.drawCircle(myXY, 5, paint); } drawRouteNumber(Canvas canvas, Paint paint) { var h0 = service.mapStatus.picOffsetToScreen(const Offset(0, 0)); var h1 = service.mapStatus.picOffsetToScreen(const Offset(0, 60)); final dx = h1.dx - h0.dx; final dy = h1.dy - h0.dy; var h = sqrt(dx * dx + dy * dy); for (var one in _points) { if (one.onMap != Offset.zero) { var xy = service.mapStatus.picOffsetToScreen(one.onMap); var x = xy.dx; var y = xy.dy; if (!one.isStart && !one.isFinish) { var textPainter = TextPainter( text: TextSpan( text: one.sn, style: const TextStyle( color: Colors.white, fontSize: 27, fontWeight: FontWeight.w900, letterSpacing: -3)), textDirection: TextDirection.rtl, textWidthBasis: TextWidthBasis.longestLine, maxLines: 1, )..layout(); // textPainter.paint(canvas, Offset(x - textPainter.width / 2 -1 , y - textPainter.height -h + 3)); textPainter = TextPainter( text: TextSpan( text: one.sn, style: const TextStyle( color: Color(0xffff00f7), fontSize: 22, fontWeight: FontWeight.w700)), textDirection: TextDirection.rtl, textWidthBasis: TextWidthBasis.longestLine, maxLines: 1, )..layout(); textPainter.paint(canvas, Offset(x - textPainter.width / 2, y - textPainter.height - h)); } } } } drawRoutes(Canvas canvas, Paint paint) { const radiusPx = 19.0; paint.strokeWidth = 2.18; var lastXY = Offset.zero; for (var one in _points) { if (one.onMap != Offset.zero) { var xy = service.mapStatus.picOffsetToScreen(one.onMap); var x = xy.dx; var y = xy.dy; paint.color = const Color(0xffff00f7); if (one.isSuccess) { paint.color = Colors.green; } if (one.isNext) { paint.color = const Color(0xffffcb00); } paint.style = PaintingStyle.fill; final thisXY = Offset(x, y); // canvas.drawCircle(thisXY, 2, paint); if (lastXY != Offset.zero) { final dx = thisXY.dx - lastXY.dx; final dy = thisXY.dy - lastXY.dy; final len = sqrt(dx * dx + dy * dy); final dx1 = dx / len * radiusPx; final dy1 = dy / len * radiusPx; var p1 = lastXY + Offset(dx1, dy1); var p2 = thisXY - Offset(dx1, dy1); if (p1.dx.isNaN || p1.dy.isNaN || p2.dx.isNaN || p2.dy.isNaN) { continue; } canvas.drawLine(p1, p2, paint); } lastXY = thisXY; if (one.isStart) { paint.style = PaintingStyle.stroke; final path = Path(); const widthLeft = 12; const widthRight = 18; const heightHalf = 17; path.moveTo(x - widthLeft, y - heightHalf); path.lineTo(x + widthRight, y); path.lineTo(x - widthLeft, y + heightHalf); path.close(); canvas.drawPath(path, paint); } else if (one.isFinish) { paint.style = PaintingStyle.stroke; canvas.drawCircle(thisXY, radiusPx, paint); canvas.drawCircle(thisXY, 13.0, paint); } else { paint.style = PaintingStyle.stroke; canvas.drawCircle(thisXY, radiusPx, paint); } if (!one.isStart && !one.isFinish) { var textPainter = TextPainter( text: TextSpan( text: one.sn, style: TextStyle(color: paint.color, fontSize: 14)), textDirection: TextDirection.rtl, textWidthBasis: TextWidthBasis.longestLine, maxLines: 1, )..layout(); textPainter.paint(canvas, Offset(x - textPainter.width / 2, y + 20)); } } } } } class _LayerCPStartBubble extends GetView { final service = GameService.to; @override Widget build(BuildContext context) { return Obx(() { var points = controller.controlPointWantSequence; if (service.startAt != null || points.isEmpty) { return const SizedBox(); } final start = points[0]; var xy = service.mapStatus .picOffsetToScreen(Offset(start.onMap.dx, start.onMap.dy)); var x = xy.dx; var y = xy.dy; var h0 = service.mapStatus.picOffsetToScreen(const Offset(0, 0)); var h1 = service.mapStatus.picOffsetToScreen(const Offset(0, 60)); final dx = h1.dx - h0.dx; final dy = h1.dy - h0.dy; var h = sqrt(dx * dx + dy * dy); return Positioned( top: y, left: x + h + 1.0.wp, child: Bubble( nip: BubbleNip.leftTop, alignment: Alignment.topRight, child: const Text('打开始点后计时', style: TextStyle(color: Colors.red)), ), ); }); } } class _LayerCPNum extends GetView { final model = Get.find(); final service = GameService.to; static const style = TextStyle( color: Color(0xffff00f7), fontSize: 22, fontWeight: FontWeight.w700); final ingame = Get.find(); @override Widget build(BuildContext context) { final radiusPx = controller.mapStatus.mapScaleSrc * cpRadius; return Obx(() { if (service.startAt == null) { return const SizedBox(); } final scale = service.mapStatus.matrix.value[0].abs(); var h0 = service.mapStatus.picOffsetToScreen(const Offset(0, 0)); var h1 = service.mapStatus.picOffsetToScreen(Offset(0, radiusPx)); final dx = h1.dx - h0.dx; final dy = h1.dy - h0.dy; var h = sqrt(dx * dx + dy * dy); final children = []; h = radiusPx*scale*2; h = controller.mapStatus.mapScale.value * cpRadius + 4; var points = controller.controlPointWantSequence; for (var one in points) { if (one.onMap != Offset.zero) { var xy = service.mapStatus.picOffsetToScreen(one.onMap); var x = xy.dx; var y = xy.dy; if (!one.isStart && !one.isFinish) { final height = h + 24; children.add(Positioned( top: y - height, left: x - 40, width: 80, height: height, child: Transform.rotate( angle: model.compassPlantRadian, alignment: Alignment.bottomCenter, child: Container( alignment: Alignment.topCenter, // color: Colors.white, child: Text(one.sn, style: style))), )); } } } return Stack( children: children, ); }); } } class _StaticMap extends GetView { @override Widget build(BuildContext context) { return Obx(() { final data = controller.mapStatus.gameMapData; final pic = controller.mapStatus.mapImageData.value; final geoInfo = data.mapPackage; if (pic.isNotEmpty && geoInfo != null) { // var fit = BoxFit.fitWidth; // if(data.width > data.height){ // fit = BoxFit.fitHeight; // } return Image.memory(controller.mapStatus.gameMapData.pic!, fit: BoxFit.contain); } else { return Container( width: context.width, height: context.height, decoration: const BoxDecoration(color: Colors.white)); } }); } } class _LayerCompass extends GetView { @override Widget build(BuildContext context) { GameModel model = Get.find(); return Obx(() { if (controller.isShowCompass) { final diameter = controller.compassDiameter; final left = (context.width - diameter) / 2; return Positioned( left: left, top: controller.compassCenter.dy - diameter / 2, child: Compass2( compassRadians: model.compassRadiansFused.value, mapNorthRadians: model.compassPlantRadian, nextPointRadians: controller.isShowNextCPRadians.value ? model.compassPlantRadian : null, level: controller.compassLevel.value, showDegrees: model.compassShowDegrees, diameter: diameter, )); } else { return Container(); } }); } } class _LayerMapRuler extends GetView { final service = GameService.to; @override Widget build(BuildContext context) { return Obx(() { if (controller.isShowRuler.value) { final mapScale = service.mapStatus.mapScale.value; if (mapScale == 0) { return const SizedBox(); } var height = context.height / 2; var hideHeight = 0.0; if (controller.isMapRotateAtCompassCenter.value) { height = controller.compassCenter.dy; hideHeight = controller.compassDiameter / 2 + 1; } return Container( alignment: Alignment.topCenter, padding: EdgeInsets.only(top: context.mediaQueryPadding.top), height: height, child: Ruler(hideHeight: hideHeight, mapScale: mapScale)); } else { return const SizedBox(); } }); } } class _Trajectory extends GetView { @override Widget build(BuildContext context) { return Obx(() { return SizedBox( height: context.height, width: context.width, child: CustomPaint( painter: _TrajectoryPainter( controller.trajectoryPoints, controller.isDrawTrajectory), ), ); }); } } class _TrajectoryPainter extends CustomPainter { final bool isShow; final List _points; final painter = Paint() ..strokeWidth = 3 ..color = const Color(0xFF309EF9) ..style = PaintingStyle.stroke ..isAntiAlias = true; final service = GameService.to; _TrajectoryPainter(this._points, this.isShow); @override void paint(Canvas canvas, Size size) { if (_points.length > 1 && isShow) { var alpha = 255; final lineCount = _points.length - 1; final alphaStep = (alpha / lineCount).round(); for (var i = 1; i < _points.length; i++) { final one = _points[i]; final last = _points[i - 1]; final p1 = service.mapStatus.picOffsetToScreen(last); final p2 = service.mapStatus.picOffsetToScreen(one); painter.color = Color.fromARGB(alpha, 255, 0, 0); canvas.drawLine(p1, p2, painter); alpha -= alphaStep; if (alpha < 0) { alpha = 0; } } } } @override bool shouldRepaint(_TrajectoryPainter oldDelegate) => false; }