controller.dart 9.4 KB

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