From 708148c767d4acfe6eff15503f73bbe4e3b1cf31 Mon Sep 17 00:00:00 2001 From: JiWoong Sul Date: Tue, 30 Dec 2025 18:30:51 +0900 Subject: [PATCH] =?UTF-8?q?feat(ui):=20=EB=A0=88=ED=8A=B8=EB=A1=9C=20UI=20?= =?UTF-8?q?=EC=8B=9C=EC=8A=A4=ED=85=9C=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - PressStart2P 픽셀 폰트 추가 - RetroColors: 레트로 RPG 스타일 색상 팔레트 - RetroPanel: 픽셀 테두리 패널 위젯 - RetroButton: 레트로 스타일 버튼 - RetroProgressBar: 픽셀 스타일 진행 바 - PixelBorderPainter: 커스텀 테두리 페인터 --- assets/fonts/PressStart2P-Regular.ttf | Bin 0 -> 118204 bytes lib/src/shared/retro_colors.dart | 147 ++++++++++ .../shared/widgets/pixel_border_painter.dart | 106 +++++++ lib/src/shared/widgets/retro_button.dart | 150 ++++++++++ lib/src/shared/widgets/retro_panel.dart | 120 ++++++++ .../shared/widgets/retro_progress_bar.dart | 258 ++++++++++++++++++ lib/src/shared/widgets/retro_widgets.dart | 8 + pubspec.yaml | 7 +- 8 files changed, 795 insertions(+), 1 deletion(-) create mode 100644 assets/fonts/PressStart2P-Regular.ttf create mode 100644 lib/src/shared/retro_colors.dart create mode 100644 lib/src/shared/widgets/pixel_border_painter.dart create mode 100644 lib/src/shared/widgets/retro_button.dart create mode 100644 lib/src/shared/widgets/retro_panel.dart create mode 100644 lib/src/shared/widgets/retro_progress_bar.dart create mode 100644 lib/src/shared/widgets/retro_widgets.dart diff --git a/assets/fonts/PressStart2P-Regular.ttf b/assets/fonts/PressStart2P-Regular.ttf new file mode 100644 index 0000000000000000000000000000000000000000..39adf42efa597906e53be689474ac82214112124 GIT binary patch literal 118204 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 Ap8x;= literal 0 HcmV?d00001 diff --git a/lib/src/shared/retro_colors.dart b/lib/src/shared/retro_colors.dart new file mode 100644 index 0000000..66c758c --- /dev/null +++ b/lib/src/shared/retro_colors.dart @@ -0,0 +1,147 @@ +import 'package:flutter/material.dart'; + +/// 레트로 RPG 스타일 색상 팔레트 (8-bit/16-bit 클래식 RPG 느낌) +class RetroColors { + RetroColors._(); + + // ═══════════════════════════════════════════════════════════════════════ + // 메인 UI 컬러 (Main UI Colors) + // ═══════════════════════════════════════════════════════════════════════ + + /// 골드 (테두리, 강조, 타이틀) + static const Color gold = Color(0xFFD4A84B); + + /// 밝은 골드 (호버, 하이라이트) + static const Color goldLight = Color(0xFFE8C97A); + + /// 어두운 골드 (눌림 상태) + static const Color goldDark = Color(0xFFB08A3A); + + /// 갈색 (프레임, 테두리) + static const Color brown = Color(0xFF8B4513); + + /// 크림색 (텍스트 배경, 밝은 패널) + static const Color cream = Color(0xFFF5E6C8); + + /// 다크 브라운 (패널 배경) + static const Color darkBrown = Color(0xFF3D2817); + + /// 매우 어두운 브라운 (딥 배경) + static const Color deepBrown = Color(0xFF2A1A0F); + + // ═══════════════════════════════════════════════════════════════════════ + // 상태 바 컬러 (Status Bar Colors) + // ═══════════════════════════════════════════════════════════════════════ + + /// HP 바 빨간색 + static const Color hpRed = Color(0xFFCC3333); + + /// HP 바 빨간색 (어두운) + static const Color hpRedDark = Color(0xFF8B2222); + + /// MP 바 파란색 + static const Color mpBlue = Color(0xFF3366CC); + + /// MP 바 파란색 (어두운) + static const Color mpBlueDark = Color(0xFF224488); + + /// EXP/성공 초록색 + static const Color expGreen = Color(0xFF33CC66); + + /// EXP/성공 초록색 (어두운) + static const Color expGreenDark = Color(0xFF228844); + + // ═══════════════════════════════════════════════════════════════════════ + // ASCII 애니메이션 컬러 (기존 유지) + // ═══════════════════════════════════════════════════════════════════════ + + /// ASCII 흰색 (캐릭터/몬스터) + static const Color asciiWhite = Color(0xFFFFFFFF); + + /// ASCII 시안 (긍정 효과) + static const Color asciiCyan = Color(0xFF00FFFF); + + /// ASCII 마젠타 (부정 효과) + static const Color asciiMagenta = Color(0xFFFF00FF); + + /// ASCII 노란색 (경고, 중요) + static const Color asciiYellow = Color(0xFFFFFF00); + + /// ASCII 초록색 (성공, 회복) + static const Color asciiGreen = Color(0xFF00FF00); + + /// ASCII 빨간색 (데미지, 위험) + static const Color asciiRed = Color(0xFFFF0000); + + // ═══════════════════════════════════════════════════════════════════════ + // 텍스트 컬러 (Text Colors) + // ═══════════════════════════════════════════════════════════════════════ + + /// 기본 텍스트 (밝은 배경용) + static const Color textDark = Color(0xFF2A1A0F); + + /// 기본 텍스트 (어두운 배경용) + static const Color textLight = Color(0xFFF5E6C8); + + /// 비활성 텍스트 + static const Color textDisabled = Color(0xFF7A6A5A); + + // ═══════════════════════════════════════════════════════════════════════ + // 버튼 컬러 (Button Colors) + // ═══════════════════════════════════════════════════════════════════════ + + /// 주요 버튼 배경 + static const Color buttonPrimary = Color(0xFF5A4A3A); + + /// 주요 버튼 배경 (눌림) + static const Color buttonPrimaryPressed = Color(0xFF3A2A1A); + + /// 보조 버튼 배경 + static const Color buttonSecondary = Color(0xFF4A3A2A); + + /// 보조 버튼 배경 (눌림) + static const Color buttonSecondaryPressed = Color(0xFF2A1A0A); + + // ═══════════════════════════════════════════════════════════════════════ + // 패널 컬러 (Panel Colors) + // ═══════════════════════════════════════════════════════════════════════ + + /// 패널 배경 (기본) + static const Color panelBg = Color(0xFF3D2817); + + /// 패널 배경 (밝은) + static const Color panelBgLight = Color(0xFF5D4837); + + /// 패널 테두리 (외곽, 어두운) + static const Color panelBorderOuter = Color(0xFF1A0F08); + + /// 패널 테두리 (내곽, 밝은) + static const Color panelBorderInner = Color(0xFF8B7355); + + // ═══════════════════════════════════════════════════════════════════════ + // 유틸리티 메서드 (Utility Methods) + // ═══════════════════════════════════════════════════════════════════════ + + /// 레트로 테마용 ColorScheme 생성 + static ColorScheme get colorScheme => ColorScheme.dark( + primary: gold, + onPrimary: textDark, + primaryContainer: goldDark, + onPrimaryContainer: textLight, + secondary: brown, + onSecondary: textLight, + secondaryContainer: darkBrown, + onSecondaryContainer: textLight, + tertiary: cream, + onTertiary: textDark, + tertiaryContainer: panelBgLight, + onTertiaryContainer: textLight, + error: hpRed, + onError: textLight, + surface: deepBrown, + onSurface: textLight, + surfaceContainerHighest: panelBg, + outline: panelBorderOuter, + outlineVariant: panelBorderInner, + ); +} diff --git a/lib/src/shared/widgets/pixel_border_painter.dart b/lib/src/shared/widgets/pixel_border_painter.dart new file mode 100644 index 0000000..ccaeedf --- /dev/null +++ b/lib/src/shared/widgets/pixel_border_painter.dart @@ -0,0 +1,106 @@ +import 'package:flutter/material.dart'; + +import 'package:askiineverdie/src/shared/retro_colors.dart'; + +/// 픽셀 스타일 테두리를 그리는 CustomPainter +/// 8-bit 게임의 UI 프레임 느낌을 재현 +class PixelBorderPainter extends CustomPainter { + const PixelBorderPainter({ + this.borderWidth = 3.0, + this.outerColor = RetroColors.panelBorderOuter, + this.innerColor = RetroColors.panelBorderInner, + this.fillColor, + }); + + /// 테두리 두께 (픽셀 단위로 표현) + final double borderWidth; + + /// 외곽 테두리 색상 (어두운 색) + final Color outerColor; + + /// 내곽 테두리 색상 (밝은 색, 입체감 표현) + final Color innerColor; + + /// 배경 채우기 색상 (null이면 투명) + final Color? fillColor; + + @override + void paint(Canvas canvas, Size size) { + final outerPaint = Paint() + ..color = outerColor + ..style = PaintingStyle.fill; + + final innerPaint = Paint() + ..color = innerColor + ..style = PaintingStyle.fill; + + // 배경 채우기 + if (fillColor != null) { + final bgPaint = Paint() + ..color = fillColor! + ..style = PaintingStyle.fill; + canvas.drawRect( + Rect.fromLTWH(borderWidth, borderWidth, + size.width - borderWidth * 2, size.height - borderWidth * 2), + bgPaint, + ); + } + + // 외곽 테두리 (상단, 좌측 - 어두운 색으로 깊이감) + // 상단 + canvas.drawRect( + Rect.fromLTWH(0, 0, size.width, borderWidth), + outerPaint, + ); + // 좌측 + canvas.drawRect( + Rect.fromLTWH(0, 0, borderWidth, size.height), + outerPaint, + ); + + // 외곽 테두리 (하단, 우측 - 어두운 색) + // 하단 + canvas.drawRect( + Rect.fromLTWH(0, size.height - borderWidth, size.width, borderWidth), + outerPaint, + ); + // 우측 + canvas.drawRect( + Rect.fromLTWH(size.width - borderWidth, 0, borderWidth, size.height), + outerPaint, + ); + + // 내곽 하이라이트 (상단, 좌측 내부 - 밝은 색으로 입체감) + // 상단 내부 + canvas.drawRect( + Rect.fromLTWH(borderWidth, borderWidth, + size.width - borderWidth * 2, borderWidth * 0.5), + innerPaint, + ); + // 좌측 내부 + canvas.drawRect( + Rect.fromLTWH(borderWidth, borderWidth, + borderWidth * 0.5, size.height - borderWidth * 2), + innerPaint, + ); + } + + @override + bool shouldRepaint(covariant PixelBorderPainter oldDelegate) { + return borderWidth != oldDelegate.borderWidth || + outerColor != oldDelegate.outerColor || + innerColor != oldDelegate.innerColor || + fillColor != oldDelegate.fillColor; + } +} + +/// 골드 테두리 스타일 (타이틀, 중요 패널용) +class GoldBorderPainter extends PixelBorderPainter { + const GoldBorderPainter({ + super.borderWidth = 3.0, + super.fillColor, + }) : super( + outerColor: RetroColors.goldDark, + innerColor: RetroColors.goldLight, + ); +} diff --git a/lib/src/shared/widgets/retro_button.dart b/lib/src/shared/widgets/retro_button.dart new file mode 100644 index 0000000..0bc6fcd --- /dev/null +++ b/lib/src/shared/widgets/retro_button.dart @@ -0,0 +1,150 @@ +import 'package:flutter/material.dart'; + +import 'package:askiineverdie/src/shared/retro_colors.dart'; + +/// 레트로 RPG 스타일 버튼 +/// 8-bit 게임의 눌림 효과를 재현 +class RetroButton extends StatefulWidget { + const RetroButton({ + super.key, + required this.child, + this.onPressed, + this.isPrimary = true, + this.padding = const EdgeInsets.symmetric(horizontal: 16, vertical: 12), + }); + + /// 버튼 내부 컨텐츠 + final Widget child; + + /// 클릭 콜백 (null이면 비활성화) + final VoidCallback? onPressed; + + /// Primary 버튼 여부 (색상 차이) + final bool isPrimary; + + /// 내부 패딩 + final EdgeInsets padding; + + @override + State createState() => _RetroButtonState(); +} + +class _RetroButtonState extends State { + bool _isPressed = false; + + bool get _isEnabled => widget.onPressed != null; + + Color get _backgroundColor { + if (!_isEnabled) return RetroColors.buttonSecondary.withValues(alpha: 0.5); + if (_isPressed) { + return widget.isPrimary + ? RetroColors.buttonPrimaryPressed + : RetroColors.buttonSecondaryPressed; + } + return widget.isPrimary + ? RetroColors.buttonPrimary + : RetroColors.buttonSecondary; + } + + Color get _borderTopLeft { + if (_isPressed) return RetroColors.panelBorderOuter; + return RetroColors.panelBorderInner; + } + + Color get _borderBottomRight { + if (_isPressed) return RetroColors.panelBorderInner; + return RetroColors.panelBorderOuter; + } + + @override + Widget build(BuildContext context) { + return GestureDetector( + onTapDown: _isEnabled ? (_) => setState(() => _isPressed = true) : null, + onTapUp: _isEnabled ? (_) => setState(() => _isPressed = false) : null, + onTapCancel: _isEnabled ? () => setState(() => _isPressed = false) : null, + onTap: widget.onPressed, + child: AnimatedContainer( + duration: const Duration(milliseconds: 50), + padding: widget.padding, + decoration: BoxDecoration( + color: _backgroundColor, + border: Border( + top: BorderSide(color: _borderTopLeft, width: 2), + left: BorderSide(color: _borderTopLeft, width: 2), + bottom: BorderSide(color: _borderBottomRight, width: 2), + right: BorderSide(color: _borderBottomRight, width: 2), + ), + ), + transform: _isPressed + ? Matrix4.translationValues(1, 1, 0) + : Matrix4.identity(), + child: DefaultTextStyle( + style: TextStyle( + fontFamily: 'PressStart2P', + fontSize: 10, + color: _isEnabled ? RetroColors.textLight : RetroColors.textDisabled, + ), + child: widget.child, + ), + ), + ); + } +} + +/// 레트로 텍스트 버튼 (간편 생성용) +class RetroTextButton extends StatelessWidget { + const RetroTextButton({ + super.key, + required this.text, + this.onPressed, + this.isPrimary = true, + this.icon, + }); + + final String text; + final VoidCallback? onPressed; + final bool isPrimary; + final IconData? icon; + + @override + Widget build(BuildContext context) { + return RetroButton( + onPressed: onPressed, + isPrimary: isPrimary, + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + mainAxisSize: MainAxisSize.min, + children: [ + if (icon != null) ...[ + Icon(icon, size: 14, color: RetroColors.textLight), + const SizedBox(width: 8), + ], + Text(text.toUpperCase()), + ], + ), + ); + } +} + +/// 레트로 아이콘 버튼 +class RetroIconButton extends StatelessWidget { + const RetroIconButton({ + super.key, + required this.icon, + this.onPressed, + this.size = 32, + }); + + final IconData icon; + final VoidCallback? onPressed; + final double size; + + @override + Widget build(BuildContext context) { + return RetroButton( + onPressed: onPressed, + padding: EdgeInsets.all(size * 0.25), + child: Icon(icon, size: size * 0.5, color: RetroColors.textLight), + ); + } +} diff --git a/lib/src/shared/widgets/retro_panel.dart b/lib/src/shared/widgets/retro_panel.dart new file mode 100644 index 0000000..3196190 --- /dev/null +++ b/lib/src/shared/widgets/retro_panel.dart @@ -0,0 +1,120 @@ +import 'package:flutter/material.dart'; + +import 'package:askiineverdie/src/shared/retro_colors.dart'; +import 'package:askiineverdie/src/shared/widgets/pixel_border_painter.dart'; + +/// 레트로 RPG 스타일 패널 +/// 8-bit 게임의 UI 프레임 느낌을 재현 +class RetroPanel extends StatelessWidget { + const RetroPanel({ + super.key, + required this.child, + this.padding = const EdgeInsets.all(12), + this.backgroundColor = RetroColors.panelBg, + this.borderWidth = 3.0, + this.useGoldBorder = false, + this.title, + }); + + /// 패널 내부 컨텐츠 + final Widget child; + + /// 내부 패딩 + final EdgeInsets padding; + + /// 배경 색상 + final Color backgroundColor; + + /// 테두리 두께 + final double borderWidth; + + /// 골드 테두리 사용 여부 (중요한 패널에 사용) + final bool useGoldBorder; + + /// 패널 타이틀 (상단에 표시) + final String? title; + + @override + Widget build(BuildContext context) { + final painter = useGoldBorder + ? GoldBorderPainter(borderWidth: borderWidth, fillColor: backgroundColor) + : PixelBorderPainter(borderWidth: borderWidth, fillColor: backgroundColor); + + return CustomPaint( + painter: painter, + child: Padding( + padding: EdgeInsets.all(borderWidth).add(padding), + child: title != null + ? Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + mainAxisSize: MainAxisSize.min, + children: [ + _PanelTitle(title: title!, useGoldBorder: useGoldBorder), + const SizedBox(height: 8), + Flexible(child: child), + ], + ) + : child, + ), + ); + } +} + +/// 패널 타이틀 위젯 +class _PanelTitle extends StatelessWidget { + const _PanelTitle({required this.title, required this.useGoldBorder}); + + final String title; + final bool useGoldBorder; + + @override + Widget build(BuildContext context) { + return Container( + padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4), + decoration: BoxDecoration( + color: useGoldBorder + ? RetroColors.goldDark.withValues(alpha: 0.3) + : RetroColors.panelBorderOuter.withValues(alpha: 0.5), + border: Border( + bottom: BorderSide( + color: useGoldBorder ? RetroColors.gold : RetroColors.panelBorderInner, + width: 1, + ), + ), + ), + child: Text( + title.toUpperCase(), + style: TextStyle( + fontFamily: 'PressStart2P', + fontSize: 10, + color: useGoldBorder ? RetroColors.gold : RetroColors.textLight, + letterSpacing: 1, + ), + ), + ); + } +} + +/// 골드 테두리 레트로 패널 (중요한 컨텐츠용) +class RetroGoldPanel extends StatelessWidget { + const RetroGoldPanel({ + super.key, + required this.child, + this.padding = const EdgeInsets.all(12), + this.title, + }); + + final Widget child; + final EdgeInsets padding; + final String? title; + + @override + Widget build(BuildContext context) { + return RetroPanel( + useGoldBorder: true, + padding: padding, + title: title, + child: child, + ); + } +} diff --git a/lib/src/shared/widgets/retro_progress_bar.dart b/lib/src/shared/widgets/retro_progress_bar.dart new file mode 100644 index 0000000..023e817 --- /dev/null +++ b/lib/src/shared/widgets/retro_progress_bar.dart @@ -0,0 +1,258 @@ +import 'package:flutter/material.dart'; + +import 'package:askiineverdie/src/shared/retro_colors.dart'; + +/// 레트로 RPG 스타일 프로그레스 바 +/// 세그먼트 스타일로 8-bit 게임 느낌 재현 +class RetroProgressBar extends StatelessWidget { + const RetroProgressBar({ + super.key, + required this.value, + this.maxValue = 1.0, + this.height = 16, + this.segmentCount = 20, + this.fillColor = RetroColors.expGreen, + this.emptyColor = RetroColors.panelBorderOuter, + this.showSegments = true, + this.label, + this.showPercentage = false, + }); + + /// 현재 값 (0.0 ~ maxValue) + final double value; + + /// 최대 값 + final double maxValue; + + /// 바 높이 + final double height; + + /// 세그먼트 개수 (showSegments가 true일 때) + final int segmentCount; + + /// 채워진 부분 색상 + final Color fillColor; + + /// 빈 부분 색상 + final Color emptyColor; + + /// 세그먼트 표시 여부 + final bool showSegments; + + /// 레이블 (좌측에 표시) + final String? label; + + /// 퍼센트 표시 여부 + final bool showPercentage; + + double get _percentage => maxValue > 0 ? (value / maxValue).clamp(0.0, 1.0) : 0; + + @override + Widget build(BuildContext context) { + return Row( + children: [ + if (label != null) ...[ + Text( + label!, + style: TextStyle( + fontFamily: 'PressStart2P', + fontSize: height * 0.5, + color: RetroColors.textLight, + ), + ), + const SizedBox(width: 8), + ], + Expanded( + child: Container( + height: height, + decoration: BoxDecoration( + color: emptyColor, + border: Border.all(color: RetroColors.panelBorderOuter, width: 2), + ), + child: showSegments + ? _buildSegmentedBar() + : _buildSolidBar(), + ), + ), + if (showPercentage) ...[ + const SizedBox(width: 8), + SizedBox( + width: 48, + child: Text( + '${(_percentage * 100).toInt()}%', + style: TextStyle( + fontFamily: 'PressStart2P', + fontSize: height * 0.45, + color: RetroColors.textLight, + ), + textAlign: TextAlign.right, + ), + ), + ], + ], + ); + } + + Widget _buildSegmentedBar() { + final filledSegments = (_percentage * segmentCount).round(); + return LayoutBuilder( + builder: (context, constraints) { + final segmentWidth = constraints.maxWidth / segmentCount; + return Row( + children: List.generate(segmentCount, (index) { + final isFilled = index < filledSegments; + return Container( + width: segmentWidth, + decoration: BoxDecoration( + color: isFilled ? fillColor : emptyColor, + border: Border( + right: index < segmentCount - 1 + ? BorderSide( + color: RetroColors.panelBorderOuter.withValues(alpha: 0.5), + width: 1, + ) + : BorderSide.none, + ), + ), + ); + }), + ); + }, + ); + } + + Widget _buildSolidBar() { + return FractionallySizedBox( + alignment: Alignment.centerLeft, + widthFactor: _percentage, + child: Container(color: fillColor), + ); + } +} + +/// HP 바 (빨간색) +class RetroHpBar extends StatelessWidget { + const RetroHpBar({ + super.key, + required this.current, + required this.max, + this.height = 16, + this.showLabel = true, + this.showValue = false, + }); + + final int current; + final int max; + final double height; + final bool showLabel; + final bool showValue; + + @override + Widget build(BuildContext context) { + return Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + mainAxisSize: MainAxisSize.min, + children: [ + RetroProgressBar( + value: current.toDouble(), + maxValue: max.toDouble(), + height: height, + fillColor: RetroColors.hpRed, + emptyColor: RetroColors.hpRedDark.withValues(alpha: 0.3), + label: showLabel ? 'HP' : null, + ), + if (showValue) ...[ + const SizedBox(height: 2), + Text( + '$current / $max', + style: TextStyle( + fontFamily: 'PressStart2P', + fontSize: height * 0.4, + color: RetroColors.textLight.withValues(alpha: 0.7), + ), + textAlign: TextAlign.center, + ), + ], + ], + ); + } +} + +/// MP 바 (파란색) +class RetroMpBar extends StatelessWidget { + const RetroMpBar({ + super.key, + required this.current, + required this.max, + this.height = 16, + this.showLabel = true, + this.showValue = false, + }); + + final int current; + final int max; + final double height; + final bool showLabel; + final bool showValue; + + @override + Widget build(BuildContext context) { + return Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + mainAxisSize: MainAxisSize.min, + children: [ + RetroProgressBar( + value: current.toDouble(), + maxValue: max.toDouble(), + height: height, + fillColor: RetroColors.mpBlue, + emptyColor: RetroColors.mpBlueDark.withValues(alpha: 0.3), + label: showLabel ? 'MP' : null, + ), + if (showValue) ...[ + const SizedBox(height: 2), + Text( + '$current / $max', + style: TextStyle( + fontFamily: 'PressStart2P', + fontSize: height * 0.4, + color: RetroColors.textLight.withValues(alpha: 0.7), + ), + textAlign: TextAlign.center, + ), + ], + ], + ); + } +} + +/// EXP 바 (초록색) +class RetroExpBar extends StatelessWidget { + const RetroExpBar({ + super.key, + required this.current, + required this.max, + this.height = 12, + this.showLabel = true, + this.showPercentage = true, + }); + + final int current; + final int max; + final double height; + final bool showLabel; + final bool showPercentage; + + @override + Widget build(BuildContext context) { + return RetroProgressBar( + value: current.toDouble(), + maxValue: max.toDouble(), + height: height, + fillColor: RetroColors.expGreen, + emptyColor: RetroColors.expGreenDark.withValues(alpha: 0.3), + label: showLabel ? 'EXP' : null, + showPercentage: showPercentage, + ); + } +} diff --git a/lib/src/shared/widgets/retro_widgets.dart b/lib/src/shared/widgets/retro_widgets.dart new file mode 100644 index 0000000..4778b42 --- /dev/null +++ b/lib/src/shared/widgets/retro_widgets.dart @@ -0,0 +1,8 @@ +/// 레트로 RPG 스타일 UI 위젯 모음 +/// 8-bit/16-bit 클래식 RPG 느낌의 UI 컴포넌트 +library; + +export 'pixel_border_painter.dart'; +export 'retro_button.dart'; +export 'retro_panel.dart'; +export 'retro_progress_bar.dart'; diff --git a/pubspec.yaml b/pubspec.yaml index 2953737..c4d38b9 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -92,8 +92,13 @@ flutter: # - asset: fonts/TrajanPro_Bold.ttf # weight: 700 # - # 커스텀 모노스페이스 폰트 (Canvas ASCII 렌더링용) + # 커스텀 폰트 fonts: + # 모노스페이스 폰트 (ASCII 렌더링용) - family: JetBrainsMono fonts: - asset: assets/fonts/JetBrainsMono-Regular.ttf + # 픽셀 폰트 (레트로 UI용) + - family: PressStart2P + fonts: + - asset: assets/fonts/PressStart2P-Regular.ttf