- PressStart2P 픽셀 폰트 추가 - RetroColors: 레트로 RPG 스타일 색상 팔레트 - RetroPanel: 픽셀 테두리 패널 위젯 - RetroButton: 레트로 스타일 버튼 - RetroProgressBar: 픽셀 스타일 진행 바 - PixelBorderPainter: 커스텀 테두리 페인터
259 lines
6.5 KiB
Dart
259 lines
6.5 KiB
Dart
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,
|
|
);
|
|
}
|
|
}
|