Add playable core UI: board painter, drag-and-drop, HUD, result overlay
CustomPainter board with gems/ghost/clear-flash, finger-lifted drag with snap preview, combo text effect, HUD chips, phase overlays with rescue stubs, demo stage. E2E widget test drives a real drag gesture. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,95 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import '../../state/game_session_notifier.dart';
|
||||
import 'board_painter.dart';
|
||||
|
||||
/// The board with clear-flash and combo-text effects. Pure display: drag
|
||||
/// orchestration lives in the game screen.
|
||||
class BoardWidget extends StatefulWidget {
|
||||
const BoardWidget({super.key, required this.view, required this.ghost});
|
||||
|
||||
final GameViewState view;
|
||||
final GhostSpec? ghost;
|
||||
|
||||
@override
|
||||
State<BoardWidget> createState() => _BoardWidgetState();
|
||||
}
|
||||
|
||||
class _BoardWidgetState extends State<BoardWidget>
|
||||
with SingleTickerProviderStateMixin {
|
||||
late final AnimationController _flash = AnimationController(
|
||||
vsync: this,
|
||||
duration: const Duration(milliseconds: 350),
|
||||
);
|
||||
|
||||
List<int> _flashRows = const [];
|
||||
List<int> _flashCols = const [];
|
||||
int _comboStreak = 0;
|
||||
|
||||
@override
|
||||
void didUpdateWidget(BoardWidget old) {
|
||||
super.didUpdateWidget(old);
|
||||
final placement = widget.view.lastPlacement;
|
||||
if (widget.view.fxTick != old.view.fxTick &&
|
||||
placement != null &&
|
||||
placement.linesCleared > 0) {
|
||||
_flashRows = placement.clearedRows;
|
||||
_flashCols = placement.clearedCols;
|
||||
_comboStreak = placement.comboStreak;
|
||||
_flash.forward(from: 0);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_flash.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return AspectRatio(
|
||||
aspectRatio: 1,
|
||||
child: AnimatedBuilder(
|
||||
animation: _flash,
|
||||
builder: (context, _) {
|
||||
final fading = _flash.isAnimating ? 1 - _flash.value : 0.0;
|
||||
return Stack(
|
||||
fit: StackFit.expand,
|
||||
children: [
|
||||
CustomPaint(
|
||||
painter: BoardPainter(
|
||||
grid: widget.view.grid,
|
||||
ghost: widget.ghost,
|
||||
flashProgress: fading,
|
||||
flashRows: _flashRows,
|
||||
flashCols: _flashCols,
|
||||
),
|
||||
),
|
||||
if (_flash.isAnimating && _comboStreak >= 2)
|
||||
Center(
|
||||
child: Transform.scale(
|
||||
scale: 0.8 + 0.6 * _flash.value,
|
||||
child: Opacity(
|
||||
opacity: 1 - _flash.value,
|
||||
child: Text(
|
||||
'COMBO x$_comboStreak',
|
||||
style: TextStyle(
|
||||
fontSize: 36,
|
||||
fontWeight: FontWeight.w900,
|
||||
color: Colors.amber.shade300,
|
||||
shadows: const [
|
||||
Shadow(blurRadius: 12, color: Colors.black54),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user