import 'package:flutter/gestures.dart' show DragStartBehavior; import 'package:flutter/material.dart'; import '../../../fly/virtual_sticks.dart'; import '../../../theme.dart'; import '../hud_kit.dart'; /// What each pad's axes do in [mode], for the captions under the pads. ({String left, String right}) stickCaptions(StickMode mode) => switch (mode) { StickMode.mode1 => (left: 'FWD · YAW', right: 'UP · SIDE'), StickMode.mode2 => (left: 'UP · YAW', right: 'FWD · SIDE'), StickMode.mode3 => (left: 'FWD · SIDE', right: 'UP · YAW'), }; /// One on-screen stick (plan P8): a ring the thumb drags a knob inside, /// springing back to centre on release like a remote's stick. Reports −1…1 /// with y up; the drag starts where the finger lands, not after the slop. class StickPad extends StatefulWidget { const StickPad({super.key, required this.value, required this.onChanged, required this.caption, this.size = 110}); final Offset value; final ValueChanged onChanged; final String caption; final double size; @override State createState() => _StickPadState(); } class _StickPadState extends State { double get _radius => widget.size / 2 - 14; void _to(Offset local) { final Offset centre = Offset(widget.size / 2, widget.size / 2); widget.onChanged(StickMapper.normalise(local - centre, _radius)); } @override Widget build(BuildContext context) { final Offset v = widget.value; final Offset knob = Offset(v.dx * _radius, -v.dy * _radius); final bool active = v != Offset.zero; return Column(mainAxisSize: MainAxisSize.min, children: [ GestureDetector( behavior: HitTestBehavior.opaque, dragStartBehavior: DragStartBehavior.down, onPanStart: (DragStartDetails d) => _to(d.localPosition), onPanUpdate: (DragUpdateDetails d) => _to(d.localPosition), onPanEnd: (_) => widget.onChanged(Offset.zero), onPanCancel: () => widget.onChanged(Offset.zero), child: SizedBox.square( dimension: widget.size, child: Stack(alignment: Alignment.center, children: [ Container( decoration: BoxDecoration( shape: BoxShape.circle, color: Glass.pill, border: Border.all(color: active ? Glass.accent : const Color(0x4DEAF0FA), width: 1.5), ), ), // Cross-hair: the stick's two axes. Container(width: 1, height: widget.size - 28, color: Glass.hairline), Container(width: widget.size - 28, height: 1, color: Glass.hairline), Transform.translate( offset: knob, child: Container( width: 34, height: 34, decoration: BoxDecoration( shape: BoxShape.circle, color: active ? Glass.accent : const Color(0xCC253552), border: Border.all(color: const Color(0x66FFFFFF)), ), ), ), ]), ), ), const SizedBox(height: 2), Text(widget.caption, style: HudText.label.copyWith(fontSize: 8)), ]); } } /// The rail's STICKS button: lit while the on-screen sticks fly the aircraft. class SticksButton extends StatelessWidget { const SticksButton({super.key, required this.open, required this.onTap}); final bool open; final VoidCallback onTap; @override Widget build(BuildContext context) { return GlassPill( key: const ValueKey('rail-sticks'), height: 24, color: open ? Glass.accent : Glass.pillStrong, padding: const EdgeInsets.symmetric(horizontal: 6), onTap: onTap, child: Text('STICKS', style: HudText.label.copyWith(fontSize: 8, color: Glass.ink, fontWeight: FontWeight.w700)), ); } }