feat(ui): 게임 플레이 화면 레트로 UI 및 로직 개선
- 레트로 색상/스타일 전체 적용 - 다이얼로그들 RetroDialog로 통일 - 설정 화면 레트로 테마 적용
This commit is contained in:
@@ -2,6 +2,7 @@ import 'package:flutter/foundation.dart'
|
|||||||
show kIsWeb, defaultTargetPlatform, TargetPlatform;
|
show kIsWeb, defaultTargetPlatform, TargetPlatform;
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/scheduler.dart' show SchedulerBinding, SchedulerPhase;
|
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/game_text_l10n.dart' as game_l10n;
|
||||||
import 'package:askiineverdie/data/skill_data.dart';
|
import 'package:askiineverdie/data/skill_data.dart';
|
||||||
@@ -846,6 +847,10 @@ class _GamePlayScreenState extends State<GamePlayScreen>
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
// 웹/데스크톱 키보드 단축키 지원
|
||||||
|
child: Focus(
|
||||||
|
autofocus: true,
|
||||||
|
onKeyEvent: (node, event) => _handleKeyboardShortcut(event, context),
|
||||||
child: Scaffold(
|
child: Scaffold(
|
||||||
backgroundColor: RetroColors.deepBrown,
|
backgroundColor: RetroColors.deepBrown,
|
||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
@@ -957,9 +962,53 @@ 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)
|
/// 좌측 패널: Character Sheet (Traits, Stats, Experience, Spells)
|
||||||
Widget _buildCharacterPanel(GameState state) {
|
Widget _buildCharacterPanel(GameState state) {
|
||||||
final l10n = L10n.of(context);
|
final l10n = L10n.of(context);
|
||||||
@@ -1170,6 +1219,7 @@ class _GamePlayScreenState extends State<GamePlayScreen>
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// 레트로 스타일 세그먼트 프로그레스 바
|
||||||
Widget _buildProgressBar(
|
Widget _buildProgressBar(
|
||||||
int position,
|
int position,
|
||||||
int max,
|
int max,
|
||||||
@@ -1177,13 +1227,37 @@ class _GamePlayScreenState extends State<GamePlayScreen>
|
|||||||
String? tooltip,
|
String? tooltip,
|
||||||
}) {
|
}) {
|
||||||
final progress = max > 0 ? (position / max).clamp(0.0, 1.0) : 0.0;
|
final progress = max > 0 ? (position / max).clamp(0.0, 1.0) : 0.0;
|
||||||
|
const segmentCount = 20;
|
||||||
|
final filledSegments = (progress * segmentCount).round();
|
||||||
|
|
||||||
final bar = Padding(
|
final bar = Padding(
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
|
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
|
||||||
child: LinearProgressIndicator(
|
child: Container(
|
||||||
value: progress,
|
height: 12,
|
||||||
backgroundColor: color.withValues(alpha: 0.2),
|
decoration: BoxDecoration(
|
||||||
valueColor: AlwaysStoppedAnimation<Color>(color),
|
color: color.withValues(alpha: 0.15),
|
||||||
minHeight: 12,
|
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(
|
return Padding(
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 8),
|
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 2),
|
||||||
child: Column(
|
child: Column(
|
||||||
children: traits.map((t) {
|
children: traits.map((t) {
|
||||||
return Row(
|
return Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(vertical: 1),
|
||||||
|
child: Row(
|
||||||
children: [
|
children: [
|
||||||
SizedBox(
|
SizedBox(
|
||||||
width: 50,
|
width: 50,
|
||||||
child: Text(t.$1, style: const TextStyle(fontSize: 11)),
|
child: Text(
|
||||||
|
t.$1.toUpperCase(),
|
||||||
|
style: const TextStyle(
|
||||||
|
fontFamily: 'PressStart2P',
|
||||||
|
fontSize: 6,
|
||||||
|
color: RetroColors.textDisabled,
|
||||||
|
),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
Expanded(
|
Expanded(
|
||||||
child: Text(
|
child: Text(
|
||||||
t.$2,
|
t.$2,
|
||||||
style: const TextStyle(
|
style: const TextStyle(
|
||||||
fontSize: 11,
|
fontFamily: 'PressStart2P',
|
||||||
fontWeight: FontWeight.bold,
|
fontSize: 7,
|
||||||
|
color: RetroColors.textLight,
|
||||||
),
|
),
|
||||||
overflow: TextOverflow.ellipsis,
|
overflow: TextOverflow.ellipsis,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}).toList(),
|
}).toList(),
|
||||||
),
|
),
|
||||||
@@ -1237,7 +1322,11 @@ class _GamePlayScreenState extends State<GamePlayScreen>
|
|||||||
return Center(
|
return Center(
|
||||||
child: Text(
|
child: Text(
|
||||||
L10n.of(context).noSpellsYet,
|
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(
|
return Center(
|
||||||
child: Text(
|
child: Text(
|
||||||
l10n.goldAmount(state.inventory.gold),
|
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),
|
padding: const EdgeInsets.symmetric(horizontal: 8),
|
||||||
itemBuilder: (context, index) {
|
itemBuilder: (context, index) {
|
||||||
if (index == 0) {
|
if (index == 0) {
|
||||||
return Row(
|
return Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(vertical: 2),
|
||||||
|
child: Row(
|
||||||
children: [
|
children: [
|
||||||
|
const Icon(Icons.monetization_on, size: 10, color: RetroColors.gold),
|
||||||
|
const SizedBox(width: 4),
|
||||||
Expanded(
|
Expanded(
|
||||||
child: Text(l10n.gold, style: const TextStyle(fontSize: 11)),
|
child: Text(
|
||||||
|
l10n.gold.toUpperCase(),
|
||||||
|
style: const TextStyle(
|
||||||
|
fontFamily: 'PressStart2P',
|
||||||
|
fontSize: 6,
|
||||||
|
color: RetroColors.gold,
|
||||||
|
),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
Text(
|
Text(
|
||||||
'${state.inventory.gold}',
|
'${state.inventory.gold}',
|
||||||
style: const TextStyle(
|
style: const TextStyle(
|
||||||
fontSize: 11,
|
fontFamily: 'PressStart2P',
|
||||||
fontWeight: FontWeight.bold,
|
fontSize: 7,
|
||||||
|
color: RetroColors.gold,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
final item = state.inventory.items[index - 1];
|
final item = state.inventory.items[index - 1];
|
||||||
@@ -1305,20 +1411,31 @@ class _GamePlayScreenState extends State<GamePlayScreen>
|
|||||||
context,
|
context,
|
||||||
item.name,
|
item.name,
|
||||||
);
|
);
|
||||||
return Row(
|
return Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(vertical: 1),
|
||||||
|
child: Row(
|
||||||
children: [
|
children: [
|
||||||
Expanded(
|
Expanded(
|
||||||
child: Text(
|
child: Text(
|
||||||
translatedName,
|
translatedName,
|
||||||
style: const TextStyle(fontSize: 11),
|
style: const TextStyle(
|
||||||
|
fontFamily: 'PressStart2P',
|
||||||
|
fontSize: 6,
|
||||||
|
color: RetroColors.textLight,
|
||||||
|
),
|
||||||
overflow: TextOverflow.ellipsis,
|
overflow: TextOverflow.ellipsis,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
Text(
|
Text(
|
||||||
'${item.count}',
|
'${item.count}',
|
||||||
style: const TextStyle(fontSize: 11, fontWeight: FontWeight.bold),
|
style: const TextStyle(
|
||||||
|
fontFamily: 'PressStart2P',
|
||||||
|
fontSize: 6,
|
||||||
|
color: RetroColors.cream,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
@@ -1330,7 +1447,14 @@ class _GamePlayScreenState extends State<GamePlayScreen>
|
|||||||
final plotCount = state.progress.plotStageCount;
|
final plotCount = state.progress.plotStageCount;
|
||||||
if (plotCount == 0) {
|
if (plotCount == 0) {
|
||||||
return Center(
|
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),
|
padding: const EdgeInsets.symmetric(horizontal: 8),
|
||||||
itemBuilder: (context, index) {
|
itemBuilder: (context, index) {
|
||||||
final isCompleted = index < plotCount - 1;
|
final isCompleted = index < plotCount - 1;
|
||||||
return Row(
|
final isCurrent = index == plotCount - 1;
|
||||||
|
return Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(vertical: 1),
|
||||||
|
child: Row(
|
||||||
children: [
|
children: [
|
||||||
Icon(
|
Icon(
|
||||||
isCompleted ? Icons.check_box : Icons.check_box_outline_blank,
|
isCompleted
|
||||||
size: 14,
|
? Icons.check_box
|
||||||
color: isCompleted ? Colors.green : Colors.grey,
|
: (isCurrent ? Icons.arrow_right : Icons.check_box_outline_blank),
|
||||||
|
size: 12,
|
||||||
|
color: isCompleted
|
||||||
|
? RetroColors.expGreen
|
||||||
|
: (isCurrent ? RetroColors.gold : RetroColors.textDisabled),
|
||||||
),
|
),
|
||||||
const SizedBox(width: 4),
|
const SizedBox(width: 4),
|
||||||
Text(
|
Expanded(
|
||||||
|
child: Text(
|
||||||
index == 0 ? l10n.prologue : l10n.actNumber(_toRoman(index)),
|
index == 0 ? l10n.prologue : l10n.actNumber(_toRoman(index)),
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: 11,
|
fontFamily: 'PressStart2P',
|
||||||
|
fontSize: 6,
|
||||||
|
color: isCompleted
|
||||||
|
? RetroColors.textDisabled
|
||||||
|
: (isCurrent ? RetroColors.gold : RetroColors.textLight),
|
||||||
decoration: isCompleted
|
decoration: isCompleted
|
||||||
? TextDecoration.lineThrough
|
? TextDecoration.lineThrough
|
||||||
: TextDecoration.none,
|
: TextDecoration.none,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
),
|
||||||
],
|
],
|
||||||
|
),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
@@ -1368,7 +1506,14 @@ class _GamePlayScreenState extends State<GamePlayScreen>
|
|||||||
|
|
||||||
if (questHistory.isEmpty) {
|
if (questHistory.isEmpty) {
|
||||||
return Center(
|
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,24 +1527,35 @@ class _GamePlayScreenState extends State<GamePlayScreen>
|
|||||||
final isCurrentQuest =
|
final isCurrentQuest =
|
||||||
index == questHistory.length - 1 && !quest.isComplete;
|
index == questHistory.length - 1 && !quest.isComplete;
|
||||||
|
|
||||||
return Row(
|
return Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(vertical: 1),
|
||||||
|
child: Row(
|
||||||
children: [
|
children: [
|
||||||
if (isCurrentQuest)
|
|
||||||
const Icon(Icons.arrow_right, size: 14)
|
|
||||||
else
|
|
||||||
Icon(
|
Icon(
|
||||||
quest.isComplete
|
isCurrentQuest
|
||||||
|
? Icons.arrow_right
|
||||||
|
: (quest.isComplete
|
||||||
? Icons.check_box
|
? Icons.check_box
|
||||||
: Icons.check_box_outline_blank,
|
: Icons.check_box_outline_blank),
|
||||||
size: 14,
|
size: 12,
|
||||||
color: quest.isComplete ? Colors.green : Colors.grey,
|
color: isCurrentQuest
|
||||||
|
? RetroColors.gold
|
||||||
|
: (quest.isComplete
|
||||||
|
? RetroColors.expGreen
|
||||||
|
: RetroColors.textDisabled),
|
||||||
),
|
),
|
||||||
const SizedBox(width: 4),
|
const SizedBox(width: 4),
|
||||||
Expanded(
|
Expanded(
|
||||||
child: Text(
|
child: Text(
|
||||||
quest.caption,
|
quest.caption,
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: 11,
|
fontFamily: 'PressStart2P',
|
||||||
|
fontSize: 6,
|
||||||
|
color: isCurrentQuest
|
||||||
|
? RetroColors.gold
|
||||||
|
: (quest.isComplete
|
||||||
|
? RetroColors.textDisabled
|
||||||
|
: RetroColors.textLight),
|
||||||
decoration: quest.isComplete
|
decoration: quest.isComplete
|
||||||
? TextDecoration.lineThrough
|
? TextDecoration.lineThrough
|
||||||
: TextDecoration.none,
|
: TextDecoration.none,
|
||||||
@@ -1408,6 +1564,7 @@ class _GamePlayScreenState extends State<GamePlayScreen>
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|||||||
Reference in New Issue
Block a user