feat(ui): 게임 플레이 화면 레트로 UI 및 로직 개선

- 레트로 색상/스타일 전체 적용
- 다이얼로그들 RetroDialog로 통일
- 설정 화면 레트로 테마 적용
This commit is contained in:
JiWoong Sul
2025-12-30 19:04:00 +09:00
parent 27e05fb3c1
commit 9e96b94465

View File

@@ -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<GamePlayScreen>
}
}
},
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<GamePlayScreen>
],
),
),
),
),
);
}
/// 키보드 단축키 핸들러 (웹/데스크톱)
/// 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<GamePlayScreen>
);
}
/// 레트로 스타일 세그먼트 프로그레스 바
Widget _buildProgressBar(
int position,
int max,
@@ -1177,13 +1227,37 @@ class _GamePlayScreenState extends State<GamePlayScreen>
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>(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<GamePlayScreen>
];
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<GamePlayScreen>
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<GamePlayScreen>
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<GamePlayScreen>
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<GamePlayScreen>
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<GamePlayScreen>
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<GamePlayScreen>
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<GamePlayScreen>
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<GamePlayScreen>
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,
),
),
],
],
),
);
},
);