controller.dart 9.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377
  1. import 'dart:async';
  2. import 'dart:math' as math;
  3. import 'package:flutter/material.dart';
  4. import 'package:tetris/gamer/gamer.dart';
  5. import 'package:tetris/generated/l10n.dart';
  6. class GameController extends StatelessWidget {
  7. const GameController({super.key});
  8. @override
  9. Widget build(BuildContext context) {
  10. return const SizedBox(
  11. height: 200,
  12. child: Row(
  13. children: <Widget>[
  14. Expanded(child: LeftController()),
  15. Expanded(child: DirectionController()),
  16. ],
  17. ),
  18. );
  19. }
  20. }
  21. const Size directionButtonSize = Size(48, 48);
  22. const Size systemButtonSize = Size(28, 28);
  23. const double directionSpace = 16;
  24. const double _iconSize = 16;
  25. class DirectionController extends StatelessWidget {
  26. const DirectionController({super.key});
  27. @override
  28. Widget build(BuildContext context) {
  29. return Stack(
  30. alignment: Alignment.center,
  31. children: <Widget>[
  32. SizedBox.fromSize(size: directionButtonSize * 2.8),
  33. Transform.rotate(
  34. angle: math.pi / 4,
  35. child: Column(
  36. mainAxisSize: MainAxisSize.min,
  37. children: <Widget>[
  38. Row(
  39. mainAxisSize: MainAxisSize.min,
  40. children: <Widget>[
  41. Transform.scale(
  42. scale: 1.5,
  43. child: Transform.rotate(
  44. angle: -math.pi / 4,
  45. child: const Icon(
  46. Icons.arrow_drop_up,
  47. size: _iconSize,
  48. )),
  49. ),
  50. Transform.scale(
  51. scale: 1.5,
  52. child: Transform.rotate(
  53. angle: -math.pi / 4,
  54. child: const Icon(
  55. Icons.arrow_right,
  56. size: _iconSize,
  57. )),
  58. ),
  59. ],
  60. ),
  61. Row(
  62. mainAxisSize: MainAxisSize.min,
  63. children: <Widget>[
  64. Transform.scale(
  65. scale: 1.5,
  66. child: Transform.rotate(
  67. angle: -math.pi / 4,
  68. child: const Icon(
  69. Icons.arrow_left,
  70. size: _iconSize,
  71. )),
  72. ),
  73. Transform.scale(
  74. scale: 1.5,
  75. child: Transform.rotate(
  76. angle: -math.pi / 4,
  77. child: const Icon(
  78. Icons.arrow_drop_down,
  79. size: _iconSize,
  80. )),
  81. ),
  82. ],
  83. ),
  84. ],
  85. ),
  86. ),
  87. Transform.rotate(
  88. angle: math.pi / 4,
  89. child: Column(
  90. mainAxisSize: MainAxisSize.min,
  91. children: <Widget>[
  92. const SizedBox(height: directionSpace),
  93. Row(
  94. mainAxisSize: MainAxisSize.min,
  95. children: <Widget>[
  96. _Button(
  97. enableLongPress: false,
  98. size: directionButtonSize,
  99. onTap: () {
  100. Game.of(context).rotate();
  101. }),
  102. const SizedBox(width: directionSpace),
  103. _Button(
  104. size: directionButtonSize,
  105. onTap: () {
  106. Game.of(context).right();
  107. }),
  108. ],
  109. ),
  110. const SizedBox(height: directionSpace),
  111. Row(
  112. mainAxisSize: MainAxisSize.min,
  113. children: <Widget>[
  114. _Button(
  115. size: directionButtonSize,
  116. onTap: () {
  117. Game.of(context).left();
  118. }),
  119. const SizedBox(width: directionSpace),
  120. _Button(
  121. size: directionButtonSize,
  122. onTap: () {
  123. Game.of(context).down();
  124. },
  125. ),
  126. ],
  127. ),
  128. const SizedBox(height: directionSpace),
  129. ],
  130. ),
  131. ),
  132. ],
  133. );
  134. }
  135. }
  136. class SystemButtonGroup extends StatelessWidget {
  137. static const _systemButtonColor = Color(0xFF2dc421);
  138. const SystemButtonGroup({super.key});
  139. @override
  140. Widget build(BuildContext context) {
  141. return Row(
  142. mainAxisAlignment: MainAxisAlignment.spaceEvenly,
  143. children: <Widget>[
  144. _Description(
  145. text: S.of(context).sounds,
  146. child: _Button(
  147. size: systemButtonSize,
  148. color: _systemButtonColor,
  149. enableLongPress: false,
  150. onTap: () {
  151. Game.of(context).soundSwitch();
  152. }),
  153. ),
  154. _Description(
  155. text: S.of(context).pause_resume,
  156. child: _Button(
  157. size: systemButtonSize,
  158. color: _systemButtonColor,
  159. enableLongPress: false,
  160. onTap: () {
  161. Game.of(context).pauseOrResume();
  162. }),
  163. ),
  164. _Description(
  165. text: S.of(context).reset,
  166. child: _Button(
  167. size: systemButtonSize,
  168. enableLongPress: false,
  169. color: Colors.red,
  170. onTap: () {
  171. Game.of(context).reset();
  172. }),
  173. )
  174. ],
  175. );
  176. }
  177. }
  178. class DropButton extends StatelessWidget {
  179. const DropButton({super.key});
  180. @override
  181. Widget build(BuildContext context) {
  182. return _Description(
  183. text: 'drop',
  184. child: _Button(
  185. enableLongPress: false,
  186. size: const Size(90, 90),
  187. onTap: () {
  188. Game.of(context).drop();
  189. }),
  190. );
  191. }
  192. }
  193. class LeftController extends StatelessWidget {
  194. const LeftController({super.key});
  195. @override
  196. Widget build(BuildContext context) {
  197. return const Column(
  198. mainAxisSize: MainAxisSize.min,
  199. children: <Widget>[
  200. SystemButtonGroup(),
  201. Expanded(
  202. child: Center(
  203. child: DropButton(),
  204. ),
  205. )
  206. ],
  207. );
  208. }
  209. }
  210. class _Button extends StatefulWidget {
  211. final Size size;
  212. final Widget? icon;
  213. final VoidCallback onTap;
  214. ///the color of button
  215. final Color color;
  216. final bool enableLongPress;
  217. const _Button({
  218. Key? key,
  219. required this.size,
  220. required this.onTap,
  221. this.icon,
  222. this.color = Colors.blue,
  223. this.enableLongPress = true,
  224. }) : super(key: key);
  225. @override
  226. _ButtonState createState() {
  227. return _ButtonState();
  228. }
  229. }
  230. ///show a hint text for child widget
  231. class _Description extends StatelessWidget {
  232. final String text;
  233. final Widget child;
  234. final AxisDirection direction;
  235. const _Description({
  236. Key? key,
  237. required this.text,
  238. this.direction = AxisDirection.down,
  239. required this.child,
  240. }) : super(key: key);
  241. @override
  242. Widget build(BuildContext context) {
  243. Widget widget;
  244. switch (direction) {
  245. case AxisDirection.right:
  246. widget = Row(
  247. mainAxisSize: MainAxisSize.min,
  248. children: <Widget>[child, const SizedBox(width: 8), Text(text)]);
  249. break;
  250. case AxisDirection.left:
  251. widget = Row(
  252. mainAxisSize: MainAxisSize.min,
  253. children: <Widget>[Text(text), const SizedBox(width: 8), child],
  254. );
  255. break;
  256. case AxisDirection.up:
  257. widget = Column(
  258. mainAxisSize: MainAxisSize.min,
  259. children: <Widget>[Text(text), const SizedBox(height: 8), child],
  260. );
  261. break;
  262. case AxisDirection.down:
  263. widget = Column(
  264. mainAxisSize: MainAxisSize.min,
  265. children: <Widget>[child, const SizedBox(height: 8), Text(text)],
  266. );
  267. break;
  268. }
  269. return DefaultTextStyle(
  270. style: const TextStyle(fontSize: 12, color: Colors.black),
  271. child: widget,
  272. );
  273. }
  274. }
  275. class _ButtonState extends State<_Button> {
  276. Timer? _timer;
  277. bool _tapEnded = false;
  278. late Color _color;
  279. @override
  280. void didUpdateWidget(_Button oldWidget) {
  281. super.didUpdateWidget(oldWidget);
  282. _color = widget.color;
  283. }
  284. @override
  285. void initState() {
  286. super.initState();
  287. _color = widget.color;
  288. }
  289. @override
  290. Widget build(BuildContext context) {
  291. return Material(
  292. color: _color,
  293. elevation: 2,
  294. shape: const CircleBorder(),
  295. child: GestureDetector(
  296. behavior: HitTestBehavior.opaque,
  297. onTapDown: (_) async {
  298. setState(() {
  299. _color = widget.color.withOpacity(0.5);
  300. });
  301. if (_timer != null) {
  302. return;
  303. }
  304. _tapEnded = false;
  305. widget.onTap();
  306. if (!widget.enableLongPress) {
  307. return;
  308. }
  309. await Future.delayed(const Duration(milliseconds: 300));
  310. if (_tapEnded) {
  311. return;
  312. }
  313. _timer = Timer.periodic(const Duration(milliseconds: 60), (t) {
  314. if (!_tapEnded) {
  315. widget.onTap();
  316. } else {
  317. t.cancel();
  318. _timer = null;
  319. }
  320. });
  321. },
  322. onTapCancel: () {
  323. _tapEnded = true;
  324. _timer?.cancel();
  325. _timer = null;
  326. setState(() {
  327. _color = widget.color;
  328. });
  329. },
  330. onTapUp: (_) {
  331. _tapEnded = true;
  332. _timer?.cancel();
  333. _timer = null;
  334. setState(() {
  335. _color = widget.color;
  336. });
  337. },
  338. child: SizedBox.fromSize(
  339. size: widget.size,
  340. ),
  341. ),
  342. );
  343. }
  344. }