mjn
This commit is contained in:
208
lib/calibrador_page.dart
Normal file
208
lib/calibrador_page.dart
Normal file
@@ -0,0 +1,208 @@
|
||||
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<CalibradorPage> createState() => _CalibradorPageState();
|
||||
}
|
||||
|
||||
class _CalibradorPageState extends State<CalibradorPage> {
|
||||
// --- 👇 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<double> 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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user