style: ui perfection
This commit is contained in:
@@ -9,12 +9,10 @@ import 'package:flutter/material.dart';
|
||||
import '../../../../../core/service/app_sounds.dart';
|
||||
import '../../../domain/entities/game_entities.dart';
|
||||
import '../../bloc/game_bloc.dart';
|
||||
import 'badges.dart';
|
||||
import 'card_codes.dart';
|
||||
import 'card_component.dart';
|
||||
import 'shuffle_animation.dart';
|
||||
import 'table_pieces.dart';
|
||||
import 'turn_timer.dart';
|
||||
|
||||
/// صحنهی میز حکم با انیمیشن. شما همیشه پایین میز (rel=0) هستید.
|
||||
/// کامپوننتهای دست و trick ماندگارند و با افکت حرکت جابهجا میشوند.
|
||||
@@ -38,7 +36,6 @@ class HokmGame extends FlameGame {
|
||||
final Map<String, CardComponent> _trick = {};
|
||||
final List<Component> _backs = [];
|
||||
final List<Component> _info = [];
|
||||
final Map<String, Sprite> _badge = {}; // نشانهای رتبه (پیشبارگذاری)
|
||||
|
||||
// ابعادِ کارتهای روی میز (بر اساس عرض صفحه محاسبه میشود).
|
||||
double _cardW = 60;
|
||||
@@ -72,17 +69,11 @@ class HokmGame extends FlameGame {
|
||||
HokmGame(this.cubit, {this.skin = 'simple'});
|
||||
|
||||
@override
|
||||
Color backgroundColor() => const Color(0xFF2C0A10);
|
||||
Color backgroundColor() => const Color(0xFF0C3A1B);
|
||||
|
||||
@override
|
||||
Future<void> onLoad() async {
|
||||
add(Felt());
|
||||
// پیشبارگذاریِ نشانهای رتبه (اگر فایلی نبود، بینشان رد میشود).
|
||||
for (final t in const ['bronze', 'silver', 'gold', 'diamond', 'king']) {
|
||||
try {
|
||||
_badge[t] = await loadSprite('badge/$t.png');
|
||||
} catch (_) {}
|
||||
}
|
||||
_sub = cubit.stream.listen((ui) {
|
||||
if (ui.state == null) return;
|
||||
final s = ui.state!;
|
||||
@@ -534,8 +525,8 @@ class HokmGame extends FlameGame {
|
||||
_addBacks(_rel(seat), count);
|
||||
}
|
||||
_addTricksWon(s);
|
||||
_addScorePucks(s);
|
||||
_addInfo(s);
|
||||
// امتیاز/نام/نشان/تاج/شمارندهی نوبت اکنون در اوورلیِ Flutter (TableHud) کشیده
|
||||
// میشوند تا متن واضحتر و چیدمان نزدیک به تصویرِ مرجع باشد.
|
||||
}
|
||||
|
||||
// شمارنده ۱: دستهای بردهی این هَند (tricks_won) — دستهکارتِ پشترو + عدد.
|
||||
@@ -561,24 +552,6 @@ class HokmGame extends FlameGame {
|
||||
}
|
||||
}
|
||||
|
||||
// شمارنده ۲: امتیاز بازی (scores = هندهای برده) — دیسکِ هر تیم، ۰ تا ۷.
|
||||
// یکی جلوی شما (پایین)، دیگری جلوی حریف (راست) — نه جلوی یارِ بالا.
|
||||
void _addScorePucks(GameState s) {
|
||||
final radius = size.x * 0.05;
|
||||
for (var team = 0; team < 2; team++) {
|
||||
final score = team < s.scores.length ? s.scores[team] : 0;
|
||||
final mine = team == s.yourSeat % 2;
|
||||
final pos =
|
||||
mine
|
||||
? Vector2(size.x * 0.50, size.y * 0.74)
|
||||
: Vector2(size.x * 0.84, size.y * 0.44);
|
||||
final puck = ScorePuck(score, radius: radius, position: pos)
|
||||
..priority = 22;
|
||||
_info.add(puck);
|
||||
add(puck);
|
||||
}
|
||||
}
|
||||
|
||||
// پشتکارتهای یک حریف بهصورت بادبزنی.
|
||||
void _addBacks(int rel, int count) {
|
||||
if (count <= 0) return;
|
||||
@@ -605,99 +578,6 @@ class HokmGame extends FlameGame {
|
||||
}
|
||||
}
|
||||
|
||||
void _addInfo(GameState s) {
|
||||
if (s.trump != null) {
|
||||
final (sym, col) = _trumpGlyph(s.trump!);
|
||||
_addLabel(
|
||||
'حکم: $sym',
|
||||
Vector2(size.x * 0.04, size.y * 0.04),
|
||||
size.x * 0.05,
|
||||
color: col,
|
||||
anchor: Anchor.topLeft,
|
||||
bold: true,
|
||||
);
|
||||
}
|
||||
final a = s.scores.isNotEmpty ? s.scores[0] : 0;
|
||||
final b = s.scores.length > 1 ? s.scores[1] : 0;
|
||||
_addLabel(
|
||||
'$a - $b',
|
||||
Vector2(size.x / 2, size.y * 0.04),
|
||||
size.x * 0.05,
|
||||
anchor: Anchor.topCenter,
|
||||
);
|
||||
|
||||
for (final p in s.players) {
|
||||
final pos = _seatLabelPos(_rel(p.seat));
|
||||
final isTurn = s.turn == p.seat;
|
||||
_addLabel(
|
||||
'${p.name}${p.bot ? ' (ربات)' : ''}${p.connected ? '' : ' …'}',
|
||||
pos,
|
||||
size.x * 0.035,
|
||||
color: isTurn ? const Color(0xFFE9B949) : Colors.white70,
|
||||
bold: isTurn,
|
||||
);
|
||||
// نشانِ رتبه (راستِ نام) و سکه (چپِ نام). جایگاهِ پایین روی همان خطِ نام،
|
||||
// بقیه کمی زیرِ نام تا خارج از صفحه نرود.
|
||||
if (!p.bot) {
|
||||
final isBottom = pos.y > size.y * 0.75;
|
||||
final y = isBottom ? pos.y : pos.y + size.y * 0.032;
|
||||
// سکه: چپِ نام (متن به سمتِ چپ کشیده میشود).
|
||||
_addLabel(
|
||||
'${p.coins}',
|
||||
Vector2(pos.x - size.x * 0.06, y),
|
||||
size.x * 0.034,
|
||||
color: const Color(0xFFE9B949),
|
||||
anchor: Anchor.centerRight,
|
||||
);
|
||||
// نشانِ رتبه: راستِ نام.
|
||||
final spr = _badge[p.rankTier];
|
||||
if (spr != null) {
|
||||
final b = SpriteComponent(
|
||||
sprite: spr,
|
||||
anchor: Anchor.centerLeft,
|
||||
size: Vector2.all(size.x * 0.06),
|
||||
position: Vector2(pos.x + size.x * 0.07, y),
|
||||
)..priority = 22;
|
||||
_info.add(b);
|
||||
add(b);
|
||||
}
|
||||
}
|
||||
// تاجِ حاکم (روی همهی فازها نشان داده میشود تا حاکم مشخص باشد).
|
||||
// y را داخلِ صفحه نگه میداریم؛ برای بازیکنِ بالا، تاج زیرِ نام مینشیند.
|
||||
if (s.hakem == p.seat) {
|
||||
final above = pos.y > size.y * 0.5; // پایین/کنار ⇒ تاج بالای نام
|
||||
final cy = above
|
||||
? pos.y - size.y * 0.066
|
||||
: pos.y + size.y * 0.05; // بازیکنِ بالا ⇒ تاج پایینِ نام
|
||||
final crown = CrownBadge(size.x * 0.07, Vector2(pos.x, cy))
|
||||
..priority = 25;
|
||||
_info.add(crown);
|
||||
add(crown);
|
||||
}
|
||||
if (isTurn) {
|
||||
final tpos = pos - Vector2(0, size.y * 0.03);
|
||||
final human = !p.bot && p.connected;
|
||||
// حلقهی شمارشِ نوبت برای بازیکنِ انسان. اگر سرور مهلت را نفرستد،
|
||||
// پیشفرضِ ۳۰ ثانیه استفاده میشود تا شمارنده همیشه نمایش داده شود.
|
||||
if (human && (s.phase == 'playing' || s.phase == 'choose_trump')) {
|
||||
final dur = s.turnTimeoutMs > 0 ? s.turnTimeoutMs / 1000.0 : 30.0;
|
||||
final timer = TurnTimer(
|
||||
radius: size.x * 0.045,
|
||||
durationSeconds: dur,
|
||||
position: tpos,
|
||||
)..priority = 20;
|
||||
_info.add(timer);
|
||||
add(timer);
|
||||
}
|
||||
// ستارهی نوبت (مطابقِ تصویرِ مرجع) با ضربان.
|
||||
final star = StarBadge(size.x * 0.015, tpos)..priority = 24;
|
||||
star.add(pulse());
|
||||
_info.add(star);
|
||||
add(star);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ===== کمکیهای ساختِ عنصر (ثبت در فهرستِ بازساختهشونده) =====
|
||||
|
||||
void _addBack(Vector2 pos, Vector2 sz, {required int priority}) {
|
||||
@@ -740,19 +620,6 @@ class HokmGame extends FlameGame {
|
||||
add(t);
|
||||
}
|
||||
|
||||
Vector2 _seatLabelPos(int rel) {
|
||||
switch (rel) {
|
||||
case 1:
|
||||
return Vector2(size.x * 0.07, size.y * 0.30);
|
||||
case 2:
|
||||
return Vector2(size.x / 2, size.y * 0.07);
|
||||
case 3:
|
||||
return Vector2(size.x * 0.93, size.y * 0.30);
|
||||
default:
|
||||
return Vector2(size.x / 2, size.y * 0.975);
|
||||
}
|
||||
}
|
||||
|
||||
(String, Color) _trumpGlyph(String suit) {
|
||||
switch (suit) {
|
||||
case 'hearts':
|
||||
|
||||
@@ -7,49 +7,72 @@ import 'package:flutter/material.dart';
|
||||
const _gold = Color(0xFFE9B949);
|
||||
const _goldDark = Color(0xFFB8860B);
|
||||
|
||||
/// نمدِ سبزِ بیضیشکلِ میز با عمق: لبهی برجسته، گرادیانِ شعاعی و وینیت.
|
||||
/// میز: زمینِ سبزِ نمدی در کلِ صفحه + یک «میزِ» مرکزیِ سرمهای با قابِ طلاییِ
|
||||
/// دولایه (مطابقِ تصویرِ مرجع). کارتهای زمین داخلِ ناحیهی سرمهای انداخته میشوند
|
||||
/// و پشتکارتِ حریفان روی نمدِ سبزِ دورِ آن مینشیند.
|
||||
class Felt extends PositionComponent with HasGameReference {
|
||||
@override
|
||||
void render(Canvas canvas) {
|
||||
final w = game.size.x, h = game.size.y;
|
||||
final center = Offset(w / 2, h / 2);
|
||||
final rect = Rect.fromCenter(center: center, width: w * 0.86, height: h * 0.62);
|
||||
final radius = Radius.circular(h * 0.3);
|
||||
final felt = RRect.fromRectAndRadius(rect, radius);
|
||||
|
||||
// ۱) لبهی چوبیِ بیرونی (ریل) با گرادیان — حسِ برجستگی بدون drawShadowِ هر-فریمی.
|
||||
final rail = RRect.fromRectAndRadius(
|
||||
rect.inflate(w * 0.03), Radius.circular(h * 0.33));
|
||||
// ۱) زمینِ سبزِ نمدی (کلِ صفحه) با وینیتِ شعاعی برای عمق.
|
||||
canvas.drawRect(
|
||||
Rect.fromLTWH(0, 0, w, h),
|
||||
Paint()
|
||||
..shader = RadialGradient(
|
||||
center: Alignment.center,
|
||||
radius: 1.0,
|
||||
colors: const [Color(0xFF1C7A3E), Color(0xFF0C3A1B)],
|
||||
stops: const [0.5, 1.0],
|
||||
).createShader(Rect.fromLTWH(0, 0, w, h)),
|
||||
);
|
||||
|
||||
// ۲) میزِ مرکزیِ سرمهای با قابِ طلایی.
|
||||
final rect = Rect.fromCenter(
|
||||
center: center, width: w * 0.72, height: h * 0.46);
|
||||
final radius = Radius.circular(h * 0.05);
|
||||
final table = RRect.fromRectAndRadius(rect, radius);
|
||||
|
||||
// سایهی نرمِ زیرِ میز (یکبار، نه هر-فریمی سنگین).
|
||||
canvas.drawRRect(
|
||||
rail,
|
||||
table.shift(const Offset(0, 6)),
|
||||
Paint()
|
||||
..color = const Color(0x66000000)
|
||||
..maskFilter = const MaskFilter.blur(BlurStyle.normal, 12),
|
||||
);
|
||||
|
||||
// قابِ طلاییِ بیرونی.
|
||||
canvas.drawRRect(
|
||||
RRect.fromRectAndRadius(rect.inflate(w * 0.012), radius),
|
||||
Paint()
|
||||
..shader = const LinearGradient(
|
||||
begin: Alignment.topCenter,
|
||||
end: Alignment.bottomCenter,
|
||||
colors: [Color(0xFF5A2E12), Color(0xFF2E1608)],
|
||||
).createShader(rail.outerRect),
|
||||
colors: [_gold, _goldDark],
|
||||
).createShader(rect),
|
||||
);
|
||||
|
||||
// ۲) حلقهی طلایی بین ریل و نمد.
|
||||
// سطحِ سرمهایِ میز با گرادیانِ شعاعی (مرکز روشنتر).
|
||||
canvas.drawRRect(
|
||||
RRect.fromRectAndRadius(rect.inflate(w * 0.008), radius),
|
||||
Paint()
|
||||
..style = PaintingStyle.stroke
|
||||
..strokeWidth = w * 0.012
|
||||
..color = _gold,
|
||||
);
|
||||
|
||||
// ۳) نمدِ سبز با گرادیانِ شعاعی (مرکز روشن، لبهها تیره) — خودش حسِ وینیت/عمق میدهد.
|
||||
canvas.drawRRect(
|
||||
felt,
|
||||
table,
|
||||
Paint()
|
||||
..shader = RadialGradient(
|
||||
center: Alignment.center,
|
||||
radius: 0.95,
|
||||
colors: const [Color(0xFF34A03F), Color(0xFF0E3614)],
|
||||
stops: const [0.45, 1.0],
|
||||
radius: 0.9,
|
||||
colors: const [Color(0xFF153A63), Color(0xFF07172E)],
|
||||
stops: const [0.4, 1.0],
|
||||
).createShader(rect),
|
||||
);
|
||||
|
||||
// خطِ طلاییِ نازکِ داخلی (قابِ دولایه).
|
||||
canvas.drawRRect(
|
||||
RRect.fromRectAndRadius(rect.deflate(w * 0.018), Radius.circular(h * 0.04)),
|
||||
Paint()
|
||||
..style = PaintingStyle.stroke
|
||||
..strokeWidth = w * 0.004
|
||||
..color = _gold.withValues(alpha: 0.65),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user