import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'dart:math' as math; class CalibradorPage extends StatefulWidget { const CalibradorPage({super.key}); @override State createState() => _CalibradorPageState(); } class _CalibradorPageState extends State { // --- 👇 VALORES INICIAIS 👇 --- double hoopBaseX = 0.08; double arcRadius = 0.28; double cornerY = 0.40; // ----------------------------------------------------- @override void initState() { super.initState(); SystemChrome.setPreferredOrientations([ DeviceOrientation.landscapeRight, DeviceOrientation.landscapeLeft, ]); } @override void dispose() { SystemChrome.setPreferredOrientations([DeviceOrientation.portraitUp]); super.dispose(); } @override Widget build(BuildContext context) { final double wScreen = MediaQuery.of(context).size.width; final double hScreen = MediaQuery.of(context).size.height; // O MESMO CÁLCULO EXATO DO PLACAR final double sf = math.min(wScreen / 1150, hScreen / 720); return Scaffold( backgroundColor: const Color(0xFF266174), body: SafeArea( top: false, bottom: false, child: Stack( children: [ // 👇 1. O CAMPO COM AS MARGENS EXATAS DO PLACAR 👇 Container( margin: EdgeInsets.only(left: 65 * sf, right: 65 * sf, bottom: 55 * sf), decoration: BoxDecoration( border: Border.all(color: Colors.white, width: 2.5), image: const DecorationImage( image: AssetImage('assets/campo.png'), fit: BoxFit.fill, ), ), child: LayoutBuilder( builder: (context, constraints) { return CustomPaint( painter: LinePainter( hoopBaseX: hoopBaseX, arcRadius: arcRadius, cornerY: cornerY, color: Colors.redAccent, width: constraints.maxWidth, height: constraints.maxHeight, ), ); }, ), ), // 👇 2. TOPO: MOSTRADORES DE VALORES COM FITTEDBOX (Não transborda) 👇 Positioned( top: 0, left: 0, right: 0, child: Container( color: Colors.black87.withOpacity(0.8), padding: EdgeInsets.symmetric(vertical: 5 * sf, horizontal: 15 * sf), child: FittedBox( // Isto impede o ecrã de dar o erro dos 179 pixels! fit: BoxFit.scaleDown, child: Row( mainAxisAlignment: MainAxisAlignment.center, children: [ _buildValueDisplay("Aro X", hoopBaseX, sf), SizedBox(width: 20 * sf), _buildValueDisplay("Raio", arcRadius, sf), SizedBox(width: 20 * sf), _buildValueDisplay("Canto", cornerY, sf), SizedBox(width: 30 * sf), ElevatedButton.icon( onPressed: () => Navigator.pop(context), icon: Icon(Icons.check, size: 18 * sf), label: Text("FECHAR", style: TextStyle(fontSize: 14 * sf, fontWeight: FontWeight.bold)), style: ElevatedButton.styleFrom(backgroundColor: Colors.green), ) ], ), ), ), ), // 👇 3. FUNDO: SLIDERS (Com altura fixa para não dar o erro "hasSize") 👇 Positioned( bottom: 0, left: 0, right: 0, child: Container( color: Colors.black87.withOpacity(0.8), height: 80 * sf, // Altura segura para os sliders child: Row( children: [ Expanded(child: _buildSlider("Pos. do Aro", hoopBaseX, 0.0, 0.25, (val) => setState(() => hoopBaseX = val), sf)), Expanded(child: _buildSlider("Tam. da Curva", arcRadius, 0.1, 0.5, (val) => setState(() => arcRadius = val), sf)), Expanded(child: _buildSlider("Pos. do Canto", cornerY, 0.2, 0.5, (val) => setState(() => cornerY = val), sf)), ], ), ), ), ], ), ), ); } Widget _buildValueDisplay(String label, double value, double sf) { return Row( children: [ Text("$label: ", style: TextStyle(color: Colors.white70, fontSize: 16 * sf)), Text(value.toStringAsFixed(3), style: TextStyle(color: Colors.yellow, fontSize: 20 * sf, fontWeight: FontWeight.bold)), ], ); } Widget _buildSlider(String label, double value, double min, double max, ValueChanged onChanged, double sf) { return Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Text(label, style: TextStyle(color: Colors.white, fontSize: 12 * sf)), SizedBox( height: 40 * sf, // Altura exata para o Slider não crashar child: Slider( value: value, min: min, max: max, activeColor: Colors.yellow, inactiveColor: Colors.white24, onChanged: onChanged, ), ), ], ); } } // ============================================================== // 📐 PINTOR: DESENHA A LINHA MATEMÁTICA NA TELA // ============================================================== class LinePainter extends CustomPainter { final double hoopBaseX; final double arcRadius; final double cornerY; final Color color; final double width; final double height; LinePainter({ required this.hoopBaseX, required this.arcRadius, required this.cornerY, required this.color, required this.width, required this.height, }); @override void paint(Canvas canvas, Size size) { final paint = Paint() ..color = color ..style = PaintingStyle.stroke ..strokeWidth = 4; double aspectRatio = width / height; double hoopY = 0.50 * height; // O cornerY controla a que distância do meio (50%) estão as linhas retas double cornerDistY = cornerY * height; // --- CESTO ESQUERDO --- double hoopLX = hoopBaseX * width; canvas.drawLine(Offset(0, hoopY - cornerDistY), Offset(width * 0.35, hoopY - cornerDistY), paint); // Cima canvas.drawLine(Offset(0, hoopY + cornerDistY), Offset(width * 0.35, hoopY + cornerDistY), paint); // Baixo canvas.drawArc( Rect.fromCenter(center: Offset(hoopLX, hoopY), width: arcRadius * width * 2 / aspectRatio, height: arcRadius * height * 2), -math.pi / 2, math.pi, false, paint, ); // --- CESTO DIREITO --- double hoopRX = (1.0 - hoopBaseX) * width; canvas.drawLine(Offset(width, hoopY - cornerDistY), Offset(width * 0.65, hoopY - cornerDistY), paint); // Cima canvas.drawLine(Offset(width, hoopY + cornerDistY), Offset(width * 0.65, hoopY + cornerDistY), paint); // Baixo canvas.drawArc( Rect.fromCenter(center: Offset(hoopRX, hoopY), width: arcRadius * width * 2 / aspectRatio, height: arcRadius * height * 2), math.pi / 2, math.pi, false, paint, ); } @override bool shouldRepaint(covariant LinePainter oldDelegate) { return oldDelegate.hoopBaseX != hoopBaseX || oldDelegate.arcRadius != arcRadius || oldDelegate.cornerY != cornerY; } }