feat(ui): 게임 플레이 화면 레트로 UI 및 로직 개선
- 레트로 색상/스타일 전체 적용 - 다이얼로그들 RetroDialog로 통일 - 설정 화면 레트로 테마 적용
This commit is contained in:
@@ -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,
|
||||
),
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user