feat: better ux

This commit is contained in:
2026-06-15 17:44:39 +03:30
parent 068930c7bd
commit 323031adbe
2 changed files with 135 additions and 51 deletions
+78 -5
View File
@@ -1,4 +1,5 @@
import 'package:flame/components.dart';
import 'package:flame/effects.dart';
import 'package:flame/events.dart';
import 'package:flutter/material.dart';
@@ -7,17 +8,23 @@ const double kCardRatio = 726 / 500;
/// یک کارت روی میز؛ اگر تصویر `assets/images/cards/<code>.png` موجود باشد از آن
/// استفاده می‌کند، وگرنه نسخه‌ی برداری می‌کشد. برای پشت کارت `back.jpg`.
class CardComponent extends PositionComponent with TapCallbacks, HasGameReference {
/// کارت‌های قابل‌بازی هم با tap و هم با کشیدن (drag) به سمت زمین بازی می‌شوند.
class CardComponent extends PositionComponent
with TapCallbacks, DragCallbacks, HasGameReference {
final String code; // مثل "AS"؛ برای پشت کارت خالی
final bool faceUp;
VoidCallback? onTap; // با تغییر نوبت/مجازبودن به‌روز می‌شود
VoidCallback? onPlay; // در صورت مجاز بودن، بازیِ این کارت
bool dimmed; // کارت غیرمجاز/غیرفعال
Vector2? home; // موقعیت اصلی در دست (برای برگشت پس از کشیدنِ ناقص)
Rect? dropZone; // ناحیه‌ی وسط میز؛ رهاکردن کارت در آن یعنی بازی
Sprite? _sprite;
bool _dragging = false;
bool _overZone = false; // کارت روی ناحیه‌ی انداختن است (برای هایلایت)
CardComponent({
required this.code,
required this.faceUp,
this.onTap,
this.onPlay,
this.dimmed = false,
super.position,
super.size,
@@ -26,6 +33,8 @@ class CardComponent extends PositionComponent with TapCallbacks, HasGameReferenc
super.anchor = Anchor.center,
});
bool get _playable => onPlay != null && !dimmed;
@override
Future<void> onLoad() async {
try {
@@ -36,8 +45,60 @@ class CardComponent extends PositionComponent with TapCallbacks, HasGameReferenc
}
@override
void onTapDown(TapDownEvent event) {
if (onTap != null && !dimmed) onTap!.call();
void onTapUp(TapUpEvent event) {
if (_playable) onPlay!.call();
}
@override
void onDragStart(DragStartEvent event) {
super.onDragStart(event);
if (!_playable) return;
_dragging = true;
priority = 1000; // روی همه‌ی کارت‌ها
scale = Vector2.all(1.12); // بزرگ‌نمایی هنگام کشیدن (فیدبک)
}
@override
void onDragUpdate(DragUpdateEvent event) {
if (!_dragging) return;
position += event.localDelta;
_overZone = _inDropZone();
}
@override
void onDragEnd(DragEndEvent event) {
super.onDragEnd(event);
if (!_dragging) return;
_dragging = false;
scale = Vector2.all(1);
// فقط اگر داخل ناحیه‌ی وسط میز رها شد ⇒ بازی؛ وگرنه برگشت به جای مرتبِ خود.
if (_inDropZone()) {
onPlay?.call();
} else {
_returnHome();
}
_overZone = false;
}
@override
void onDragCancel(DragCancelEvent event) {
super.onDragCancel(event);
if (!_dragging) return;
_dragging = false;
_overZone = false;
scale = Vector2.all(1);
_returnHome();
}
bool _inDropZone() {
final z = dropZone;
if (z != null) return z.contains(position.toOffset());
return position.y < game.size.y * 0.72; // fallback
}
void _returnHome() {
if (home == null) return;
add(MoveToEffect(home!, EffectController(duration: 0.2, curve: Curves.easeOut)));
}
@override
@@ -67,6 +128,18 @@ class CardComponent extends PositionComponent with TapCallbacks, HasGameReferenc
..strokeWidth = 1.5
..color = const Color(0xFF1A1A1A),
);
// هایلایت طلایی وقتی کارت روی ناحیه‌ی انداختن است (رهاکنی، بازی می‌شود).
if (_dragging && _overZone) {
canvas.drawRRect(
rrect,
Paint()
..style = PaintingStyle.stroke
..strokeWidth = size.x * 0.06
..color = const Color(0xFFE9B949)
..maskFilter = const MaskFilter.blur(BlurStyle.normal, 6),
);
}
}
void _drawVectorBack(Canvas canvas, RRect rrect) {