From 9e96b9446544cc1bdd4981d4f373e975f5b8b6f6 Mon Sep 17 00:00:00 2001 From: JiWoong Sul Date: Tue, 30 Dec 2025 19:04:00 +0900 Subject: [PATCH] =?UTF-8?q?feat(ui):=20=EA=B2=8C=EC=9E=84=20=ED=94=8C?= =?UTF-8?q?=EB=A0=88=EC=9D=B4=20=ED=99=94=EB=A9=B4=20=EB=A0=88=ED=8A=B8?= =?UTF-8?q?=EB=A1=9C=20UI=20=EB=B0=8F=20=EB=A1=9C=EC=A7=81=20=EA=B0=9C?= =?UTF-8?q?=EC=84=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 레트로 색상/스타일 전체 적용 - 다이얼로그들 RetroDialog로 통일 - 설정 화면 레트로 테마 적용 --- lib/src/features/game/game_play_screen.dart | 337 ++++++++++++++------ 1 file changed, 247 insertions(+), 90 deletions(-) diff --git a/lib/src/features/game/game_play_screen.dart b/lib/src/features/game/game_play_screen.dart index 783849f..cebc40b 100644 --- a/lib/src/features/game/game_play_screen.dart +++ b/lib/src/features/game/game_play_screen.dart @@ -2,6 +2,7 @@ import 'package:flutter/foundation.dart' show kIsWeb, defaultTargetPlatform, TargetPlatform; import 'package:flutter/material.dart'; import 'package:flutter/scheduler.dart' show SchedulerBinding, SchedulerPhase; +import 'package:flutter/services.dart' show KeyDownEvent, LogicalKeyboardKey; import 'package:askiineverdie/data/game_text_l10n.dart' as game_l10n; import 'package:askiineverdie/data/skill_data.dart'; @@ -846,7 +847,11 @@ class _GamePlayScreenState extends State } } }, - child: Scaffold( + // 웹/데스크톱 키보드 단축키 지원 + child: Focus( + autofocus: true, + onKeyEvent: (node, event) => _handleKeyboardShortcut(event, context), + child: Scaffold( backgroundColor: RetroColors.deepBrown, appBar: AppBar( backgroundColor: RetroColors.darkBrown, @@ -956,10 +961,54 @@ class _GamePlayScreenState extends State ], ), ), + ), ), ); } + /// 키보드 단축키 핸들러 (웹/데스크톱) + /// Space: 일시정지/재개, S: 속도 변경, H: 도움말, Esc: 설정 + KeyEventResult _handleKeyboardShortcut(KeyEvent event, BuildContext context) { + // KeyDown 이벤트만 처리 + if (event is! KeyDownEvent) return KeyEventResult.ignored; + + final key = event.logicalKey; + + // Space: 일시정지/재개 + if (key == LogicalKeyboardKey.space) { + widget.controller.togglePause(); + setState(() {}); + return KeyEventResult.handled; + } + + // S: 속도 변경 + if (key == LogicalKeyboardKey.keyS) { + widget.controller.loop?.cycleSpeed(); + setState(() {}); + return KeyEventResult.handled; + } + + // H 또는 F1: 도움말 + if (key == LogicalKeyboardKey.keyH || key == LogicalKeyboardKey.f1) { + HelpDialog.show(context); + return KeyEventResult.handled; + } + + // Escape: 설정 + if (key == LogicalKeyboardKey.escape) { + _showSettingsScreen(context); + return KeyEventResult.handled; + } + + // I: 통계 + if (key == LogicalKeyboardKey.keyI) { + _showStatisticsDialog(context); + return KeyEventResult.handled; + } + + return KeyEventResult.ignored; + } + /// 좌측 패널: Character Sheet (Traits, Stats, Experience, Spells) Widget _buildCharacterPanel(GameState state) { final l10n = L10n.of(context); @@ -1170,6 +1219,7 @@ class _GamePlayScreenState extends State ); } + /// 레트로 스타일 세그먼트 프로그레스 바 Widget _buildProgressBar( int position, int max, @@ -1177,13 +1227,37 @@ class _GamePlayScreenState extends State String? tooltip, }) { final progress = max > 0 ? (position / max).clamp(0.0, 1.0) : 0.0; + const segmentCount = 20; + final filledSegments = (progress * segmentCount).round(); + final bar = Padding( padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4), - child: LinearProgressIndicator( - value: progress, - backgroundColor: color.withValues(alpha: 0.2), - valueColor: AlwaysStoppedAnimation(color), - minHeight: 12, + child: Container( + height: 12, + decoration: BoxDecoration( + color: color.withValues(alpha: 0.15), + border: Border.all(color: RetroColors.panelBorderOuter, width: 1), + ), + child: Row( + children: List.generate(segmentCount, (index) { + final isFilled = index < filledSegments; + return Expanded( + child: Container( + decoration: BoxDecoration( + color: isFilled ? color : color.withValues(alpha: 0.1), + border: Border( + right: index < segmentCount - 1 + ? BorderSide( + color: RetroColors.panelBorderOuter.withValues(alpha: 0.3), + width: 1, + ) + : BorderSide.none, + ), + ), + ), + ); + }), + ), ), ); @@ -1203,26 +1277,37 @@ class _GamePlayScreenState extends State ]; return Padding( - padding: const EdgeInsets.symmetric(horizontal: 8), + padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 2), child: Column( children: traits.map((t) { - return Row( - children: [ - SizedBox( - width: 50, - child: Text(t.$1, style: const TextStyle(fontSize: 11)), - ), - Expanded( - child: Text( - t.$2, - style: const TextStyle( - fontSize: 11, - fontWeight: FontWeight.bold, + return Padding( + padding: const EdgeInsets.symmetric(vertical: 1), + child: Row( + children: [ + SizedBox( + width: 50, + child: Text( + t.$1.toUpperCase(), + style: const TextStyle( + fontFamily: 'PressStart2P', + fontSize: 6, + color: RetroColors.textDisabled, + ), ), - overflow: TextOverflow.ellipsis, ), - ), - ], + Expanded( + child: Text( + t.$2, + style: const TextStyle( + fontFamily: 'PressStart2P', + fontSize: 7, + color: RetroColors.textLight, + ), + overflow: TextOverflow.ellipsis, + ), + ), + ], + ), ); }).toList(), ), @@ -1237,7 +1322,11 @@ class _GamePlayScreenState extends State return Center( child: Text( L10n.of(context).noSpellsYet, - style: const TextStyle(fontSize: 11), + style: const TextStyle( + fontFamily: 'PressStart2P', + fontSize: 7, + color: RetroColors.textDisabled, + ), ), ); } @@ -1274,7 +1363,11 @@ class _GamePlayScreenState extends State return Center( child: Text( l10n.goldAmount(state.inventory.gold), - style: const TextStyle(fontSize: 11), + style: const TextStyle( + fontFamily: 'PressStart2P', + fontSize: 7, + color: RetroColors.gold, + ), ), ); } @@ -1284,19 +1377,32 @@ class _GamePlayScreenState extends State padding: const EdgeInsets.symmetric(horizontal: 8), itemBuilder: (context, index) { if (index == 0) { - return Row( - children: [ - Expanded( - child: Text(l10n.gold, style: const TextStyle(fontSize: 11)), - ), - Text( - '${state.inventory.gold}', - style: const TextStyle( - fontSize: 11, - fontWeight: FontWeight.bold, + return Padding( + padding: const EdgeInsets.symmetric(vertical: 2), + child: Row( + children: [ + const Icon(Icons.monetization_on, size: 10, color: RetroColors.gold), + const SizedBox(width: 4), + Expanded( + child: Text( + l10n.gold.toUpperCase(), + style: const TextStyle( + fontFamily: 'PressStart2P', + fontSize: 6, + color: RetroColors.gold, + ), + ), ), - ), - ], + Text( + '${state.inventory.gold}', + style: const TextStyle( + fontFamily: 'PressStart2P', + fontSize: 7, + color: RetroColors.gold, + ), + ), + ], + ), ); } final item = state.inventory.items[index - 1]; @@ -1305,20 +1411,31 @@ class _GamePlayScreenState extends State context, item.name, ); - return Row( - children: [ - Expanded( - child: Text( - translatedName, - style: const TextStyle(fontSize: 11), - overflow: TextOverflow.ellipsis, + return Padding( + padding: const EdgeInsets.symmetric(vertical: 1), + child: Row( + children: [ + Expanded( + child: Text( + translatedName, + style: const TextStyle( + fontFamily: 'PressStart2P', + fontSize: 6, + color: RetroColors.textLight, + ), + overflow: TextOverflow.ellipsis, + ), ), - ), - Text( - '${item.count}', - style: const TextStyle(fontSize: 11, fontWeight: FontWeight.bold), - ), - ], + Text( + '${item.count}', + style: const TextStyle( + fontFamily: 'PressStart2P', + fontSize: 6, + color: RetroColors.cream, + ), + ), + ], + ), ); }, ); @@ -1330,7 +1447,14 @@ class _GamePlayScreenState extends State final plotCount = state.progress.plotStageCount; if (plotCount == 0) { return Center( - child: Text(l10n.prologue, style: const TextStyle(fontSize: 11)), + child: Text( + l10n.prologue.toUpperCase(), + style: const TextStyle( + fontFamily: 'PressStart2P', + fontSize: 7, + color: RetroColors.textDisabled, + ), + ), ); } @@ -1339,24 +1463,38 @@ class _GamePlayScreenState extends State padding: const EdgeInsets.symmetric(horizontal: 8), itemBuilder: (context, index) { final isCompleted = index < plotCount - 1; - return Row( - children: [ - Icon( - isCompleted ? Icons.check_box : Icons.check_box_outline_blank, - size: 14, - color: isCompleted ? Colors.green : Colors.grey, - ), - const SizedBox(width: 4), - Text( - index == 0 ? l10n.prologue : l10n.actNumber(_toRoman(index)), - style: TextStyle( - fontSize: 11, - decoration: isCompleted - ? TextDecoration.lineThrough - : TextDecoration.none, + final isCurrent = index == plotCount - 1; + return Padding( + padding: const EdgeInsets.symmetric(vertical: 1), + child: Row( + children: [ + Icon( + isCompleted + ? Icons.check_box + : (isCurrent ? Icons.arrow_right : Icons.check_box_outline_blank), + size: 12, + color: isCompleted + ? RetroColors.expGreen + : (isCurrent ? RetroColors.gold : RetroColors.textDisabled), ), - ), - ], + const SizedBox(width: 4), + Expanded( + child: Text( + index == 0 ? l10n.prologue : l10n.actNumber(_toRoman(index)), + style: TextStyle( + fontFamily: 'PressStart2P', + fontSize: 6, + color: isCompleted + ? RetroColors.textDisabled + : (isCurrent ? RetroColors.gold : RetroColors.textLight), + decoration: isCompleted + ? TextDecoration.lineThrough + : TextDecoration.none, + ), + ), + ), + ], + ), ); }, ); @@ -1368,7 +1506,14 @@ class _GamePlayScreenState extends State if (questHistory.isEmpty) { return Center( - child: Text(l10n.noActiveQuests, style: const TextStyle(fontSize: 11)), + child: Text( + l10n.noActiveQuests.toUpperCase(), + style: const TextStyle( + fontFamily: 'PressStart2P', + fontSize: 7, + color: RetroColors.textDisabled, + ), + ), ); } @@ -1382,32 +1527,44 @@ class _GamePlayScreenState extends State final isCurrentQuest = index == questHistory.length - 1 && !quest.isComplete; - return Row( - children: [ - if (isCurrentQuest) - const Icon(Icons.arrow_right, size: 14) - else + return Padding( + padding: const EdgeInsets.symmetric(vertical: 1), + child: Row( + children: [ Icon( - quest.isComplete - ? Icons.check_box - : Icons.check_box_outline_blank, - size: 14, - color: quest.isComplete ? Colors.green : Colors.grey, + isCurrentQuest + ? Icons.arrow_right + : (quest.isComplete + ? Icons.check_box + : Icons.check_box_outline_blank), + size: 12, + color: isCurrentQuest + ? RetroColors.gold + : (quest.isComplete + ? RetroColors.expGreen + : RetroColors.textDisabled), ), - const SizedBox(width: 4), - Expanded( - child: Text( - quest.caption, - style: TextStyle( - fontSize: 11, - decoration: quest.isComplete - ? TextDecoration.lineThrough - : TextDecoration.none, + const SizedBox(width: 4), + Expanded( + child: Text( + quest.caption, + style: TextStyle( + fontFamily: 'PressStart2P', + fontSize: 6, + color: isCurrentQuest + ? RetroColors.gold + : (quest.isComplete + ? RetroColors.textDisabled + : RetroColors.textLight), + decoration: quest.isComplete + ? TextDecoration.lineThrough + : TextDecoration.none, + ), + overflow: TextOverflow.ellipsis, ), - overflow: TextOverflow.ellipsis, ), - ), - ], + ], + ), ); }, );