mirror of
https://github.com/kierankihn/uno-game.git
synced 2025-12-27 02:13:18 +08:00
- Introduced `scale` property across components for responsive design. - Adjusted dimensions and positions dynamically based on scale. - Enhanced flexibility for varying display resolutions.
785 lines
24 KiB
Plaintext
785 lines
24 KiB
Plaintext
import { VerticalBox, HorizontalBox, ScrollView } from "std-widgets.slint";
|
||
import { Button } from "Components.slint";
|
||
|
||
// 卡牌颜色枚举
|
||
export enum CardColor {
|
||
Red,
|
||
Blue,
|
||
Green,
|
||
Yellow
|
||
}
|
||
|
||
// 游戏方向枚举
|
||
export enum GameDirection {
|
||
Clockwise,
|
||
CounterClockwise
|
||
}
|
||
|
||
// 其他玩家信息结构
|
||
export struct OtherPlayer {
|
||
name: string,
|
||
card-count: int,
|
||
has-uno: bool,
|
||
is-current-turn: bool,
|
||
}
|
||
|
||
// 手牌信息结构
|
||
export struct HandCard {
|
||
image-path: image,
|
||
is-selected: bool,
|
||
card-id: int,
|
||
is-wild: bool,
|
||
can-be-played: bool,
|
||
}
|
||
|
||
// 玩家头像组件
|
||
component PlayerAvatar inherits Rectangle {
|
||
in property <float> scale;
|
||
|
||
in property <string> player-name;
|
||
in property <int> card-count;
|
||
in property <bool> has-uno;
|
||
in property <bool> is-current-turn;
|
||
width: 100px * scale;
|
||
height: 130px * scale;
|
||
background: transparent;
|
||
VerticalLayout {
|
||
spacing: 4px * scale;
|
||
alignment: center;
|
||
|
||
// UNO 标志占位区域 (固定高度)
|
||
HorizontalLayout {
|
||
alignment: center;
|
||
Rectangle {
|
||
width: 50px * scale;
|
||
height: 20px * scale;
|
||
background: has-uno ? #FF5722 : transparent;
|
||
border-radius: 4px * scale;
|
||
Text {
|
||
text: "UNO!";
|
||
font-size: 11px * scale;
|
||
font-weight: 700;
|
||
color: has-uno ? #FFFFFF : transparent;
|
||
horizontal-alignment: center;
|
||
vertical-alignment: center;
|
||
}
|
||
}
|
||
}
|
||
|
||
// 头像
|
||
HorizontalLayout {
|
||
alignment: center;
|
||
Rectangle {
|
||
width: 60px * scale;
|
||
height: 60px * scale;
|
||
background: is-current-turn ? #FFD54F : #E0E0E0;
|
||
border-radius: 30px * scale;
|
||
border-width: is-current-turn ? 3px * scale : 0px;
|
||
border-color: #FF9800;
|
||
Text {
|
||
text: "👤";
|
||
font-size: 28px * scale;
|
||
horizontal-alignment: center;
|
||
vertical-alignment: center;
|
||
}
|
||
}
|
||
}
|
||
|
||
// 玩家名称
|
||
Text {
|
||
text: player-name;
|
||
font-size: 12px * scale;
|
||
font-weight: 500;
|
||
color: #333333;
|
||
horizontal-alignment: center;
|
||
}
|
||
|
||
// 剩余手牌数
|
||
Text {
|
||
text: "剩余: " + card-count + " 张";
|
||
font-size: 10px * scale;
|
||
color: #666666;
|
||
horizontal-alignment: center;
|
||
}
|
||
}
|
||
}
|
||
|
||
// 卡牌背面组件 (起牌堆)
|
||
component CardBack inherits Rectangle {
|
||
in property <float> scale;
|
||
|
||
in property <int> card-width: 100;
|
||
in property <int> card-height: 150;
|
||
width: card-width * 1px * scale;
|
||
height: card-height * 1px * scale;
|
||
background: #2C2C2C;
|
||
border-radius: 10px * scale;
|
||
border-width: 2px * scale;
|
||
border-color: #444444;
|
||
Rectangle {
|
||
width: parent.width * 0.75;
|
||
height: parent.height * 0.83;
|
||
background: #1a1a1a;
|
||
border-radius: 8px * scale;
|
||
Text {
|
||
text: "UNO";
|
||
font-size: 20px * scale;
|
||
font-weight: 700;
|
||
color: #888888;
|
||
horizontal-alignment: center;
|
||
vertical-alignment: center;
|
||
}
|
||
}
|
||
}
|
||
|
||
// 弃牌堆卡牌组件
|
||
component DiscardCard inherits Rectangle {
|
||
in property <float> scale;
|
||
|
||
in property <image> card-image;
|
||
in property <int> card-width: 100;
|
||
in property <int> card-height: 150;
|
||
width: card-width * 1px * scale;
|
||
height: card-height * 1px * scale;
|
||
background: transparent;
|
||
border-radius: 10px * scale;
|
||
drop-shadow-blur: 12px * scale;
|
||
drop-shadow-color: #00000040;
|
||
drop-shadow-offset-y: 4px * scale;
|
||
Image {
|
||
width: 100%;
|
||
height: 100%;
|
||
source: card-image;
|
||
image-fit: contain;
|
||
}
|
||
}
|
||
|
||
// 方向环背景组件 - 只绘制环线
|
||
component DirectionRingBackground inherits Rectangle {
|
||
in property <float> scale;
|
||
|
||
in property <CardColor> current-color: CardColor.Red;
|
||
in property <int> ring-width: 500;
|
||
in property <int> ring-height: 280;
|
||
property <color> ring-color: current-color == CardColor.Red ? #F44336 : current-color == CardColor.Blue ? #2196F3 : current-color == CardColor.Green ? #4CAF50 : current-color == CardColor.Yellow ? #FFEB3B : #9E9E9E;
|
||
property <color> ring-color-dim: current-color == CardColor.Red ? #E57373 : current-color == CardColor.Blue ? #64B5F6 : current-color == CardColor.Green ? #81C784 : current-color == CardColor.Yellow ? #FFF176 : #BDBDBD;
|
||
property <float> a: ring-width / 2;
|
||
property <float> b: ring-height / 2;
|
||
width: ring-width * 1px * scale;
|
||
height: ring-height * 1px * scale;
|
||
background: transparent;
|
||
|
||
// 底层光晕环
|
||
Path {
|
||
width: 100%;
|
||
height: 100%;
|
||
stroke: ring-color-dim;
|
||
stroke-width: 8px * scale;
|
||
fill: transparent;
|
||
opacity: 0.3;
|
||
MoveTo {
|
||
x: (ring-width / 2 + a) * scale;
|
||
y: (ring-height / 2) * scale;
|
||
}
|
||
|
||
ArcTo {
|
||
x: (ring-width / 2 - a) * scale;
|
||
y: (ring-height / 2) * scale;
|
||
radius-x: a * scale;
|
||
radius-y: b * scale;
|
||
sweep: true;
|
||
large-arc: true;
|
||
}
|
||
|
||
ArcTo {
|
||
x: (ring-width / 2 + a) * scale;
|
||
y: (ring-height / 2) * scale;
|
||
radius-x: a * scale;
|
||
radius-y: b * scale;
|
||
sweep: true;
|
||
large-arc: true;
|
||
}
|
||
}
|
||
|
||
// 主环
|
||
Path {
|
||
width: 100%;
|
||
height: 100%;
|
||
stroke: ring-color;
|
||
stroke-width: 3px * scale;
|
||
fill: transparent;
|
||
opacity: 0.9;
|
||
MoveTo {
|
||
x: (ring-width / 2 + a) * scale;
|
||
y: (ring-height / 2) * scale;
|
||
}
|
||
|
||
ArcTo {
|
||
x: (ring-width / 2 - a) * scale;
|
||
y: (ring-height / 2) * scale;
|
||
radius-x: a * scale;
|
||
radius-y: b * scale;
|
||
sweep: true;
|
||
large-arc: true;
|
||
}
|
||
|
||
ArcTo {
|
||
x: (ring-width / 2 + a) * scale;
|
||
y: (ring-height / 2) * scale;
|
||
radius-x: a * scale;
|
||
radius-y: b * scale;
|
||
sweep: true;
|
||
large-arc: true;
|
||
}
|
||
}
|
||
}
|
||
|
||
// 方向环光点组件 - 使用内置动画实现平滑移动
|
||
component DirectionRingOrbs inherits Rectangle {
|
||
in property <float> scale;
|
||
|
||
in property <GameDirection> direction: GameDirection.Clockwise;
|
||
in property <CardColor> current-color: CardColor.Red;
|
||
in property <int> ring-width: 500;
|
||
in property <int> ring-height: 280;
|
||
property <color> ring-color: current-color == CardColor.Red ? #F44336 : current-color == CardColor.Blue ? #2196F3 : current-color == CardColor.Green ? #4CAF50 : current-color == CardColor.Yellow ? #FFEB3B : #9E9E9E;
|
||
property <float> a: ring-width / 2;
|
||
property <float> b: ring-height / 2;
|
||
|
||
property <float> base-angle: 0;
|
||
animate base-angle {
|
||
duration: 8s;
|
||
easing: linear;
|
||
iteration-count: -1;
|
||
}
|
||
|
||
// 方向系数:顺时针为1,逆时针为-1
|
||
property <float> dir-sign: direction == GameDirection.Clockwise ? 1 : -1;
|
||
|
||
// 实际角度 = 基础角度 * 方向系数
|
||
property <float> actual-angle: base-angle * dir-sign;
|
||
width: ring-width * 1px * scale;
|
||
height: ring-height * 1px * scale;
|
||
background: transparent;
|
||
|
||
// 光点1
|
||
Rectangle {
|
||
x: (parent.width / 2 - 9px * scale) + cos(actual-angle * 1deg) * a * 1px * scale;
|
||
y: (parent.height / 2 - 9px * scale) + sin(actual-angle * 1deg) * b * 1px * scale;
|
||
width: 18px * scale;
|
||
height: 18px * scale;
|
||
background: ring-color;
|
||
border-radius: 9px * scale;
|
||
drop-shadow-blur: 8px * scale;
|
||
drop-shadow-color: ring-color;
|
||
}
|
||
|
||
// 光点2
|
||
Rectangle {
|
||
x: (parent.width / 2 - 9px * scale) + cos((actual-angle + 90) * 1deg) * a * 1px * scale;
|
||
y: (parent.height / 2 - 9px * scale) + sin((actual-angle + 90) * 1deg) * b * 1px * scale;
|
||
width: 18px * scale;
|
||
height: 18px * scale;
|
||
background: ring-color;
|
||
border-radius: 9px * scale;
|
||
drop-shadow-blur: 8px * scale;
|
||
drop-shadow-color: ring-color;
|
||
}
|
||
|
||
// 光点3
|
||
Rectangle {
|
||
x: (parent.width / 2 - 9px * scale) + cos((actual-angle + 180) * 1deg) * a * 1px * scale;
|
||
y: (parent.height / 2 - 9px * scale) + sin((actual-angle + 180) * 1deg) * b * 1px * scale;
|
||
width: 18px * scale;
|
||
height: 18px * scale;
|
||
background: ring-color;
|
||
border-radius: 9px * scale;
|
||
drop-shadow-blur: 8px * scale;
|
||
drop-shadow-color: ring-color;
|
||
}
|
||
|
||
// 光点4
|
||
Rectangle {
|
||
x: (parent.width / 2 - 9px * scale) + cos((actual-angle + 270) * 1deg) * a * 1px * scale;
|
||
y: (parent.height / 2 - 9px * scale) + sin((actual-angle + 270) * 1deg) * b * 1px * scale;
|
||
width: 18px * scale;
|
||
height: 18px * scale;
|
||
background: ring-color;
|
||
border-radius: 9px * scale;
|
||
drop-shadow-blur: 8px * scale;
|
||
drop-shadow-color: ring-color;
|
||
}
|
||
|
||
// 初始化时启动动画
|
||
init => {
|
||
base-angle = 360;
|
||
}
|
||
}
|
||
|
||
// 手牌卡牌组件
|
||
component HandCardItem inherits Rectangle {
|
||
in property <float> scale;
|
||
|
||
in property <image> card-image;
|
||
in property <bool> is-selected: false;
|
||
callback clicked;
|
||
width: 120px * scale;
|
||
height: 180px * scale;
|
||
y: is-selected ? -25px * scale : 0px;
|
||
background: transparent;
|
||
border-radius: 10px * scale;
|
||
drop-shadow-blur: is-selected ? 16px * scale : 6px * scale;
|
||
drop-shadow-color: is-selected ? #00000040 : #00000025;
|
||
drop-shadow-offset-y: is-selected ? 6px * scale : 3px * scale;
|
||
animate y {
|
||
duration: 150ms;
|
||
easing: ease-out;
|
||
}
|
||
animate drop-shadow-blur { duration: 150ms; }
|
||
touch := TouchArea {
|
||
clicked => {
|
||
root.clicked();
|
||
}
|
||
}
|
||
|
||
Image {
|
||
width: 100%;
|
||
height: 100%;
|
||
source: card-image;
|
||
image-fit: contain;
|
||
}
|
||
}
|
||
|
||
// UNO 圆形按钮组件
|
||
component UnoButton inherits Rectangle {
|
||
in property <float> scale;
|
||
callback clicked;
|
||
width: 90px * scale;
|
||
height: 90px * scale;
|
||
background: touch.pressed ? #D32F2F : #F44336;
|
||
border-radius: 45px * scale;
|
||
drop-shadow-blur: 12px * scale;
|
||
drop-shadow-color: #F4433660;
|
||
drop-shadow-offset-y: 4px * scale;
|
||
touch := TouchArea {
|
||
clicked => {
|
||
root.clicked();
|
||
}
|
||
}
|
||
|
||
Text {
|
||
text: "UNO!";
|
||
font-size: 20px * scale;
|
||
font-weight: 700;
|
||
color: #FFFFFF;
|
||
horizontal-alignment: center;
|
||
vertical-alignment: center;
|
||
}
|
||
}
|
||
|
||
// 颜色选择按钮组件
|
||
component ColorButton inherits Rectangle {
|
||
in property <float> scale;
|
||
in property <color> btn-color;
|
||
in property <string> color-name;
|
||
callback clicked;
|
||
width: 80px * scale;
|
||
height: 80px * scale;
|
||
background: touch.pressed ? btn-color.darker(20%) : btn-color;
|
||
border-radius: 12px * scale;
|
||
border-width: 3px * scale;
|
||
border-color: btn-color.darker(30%);
|
||
drop-shadow-blur: 8px * scale;
|
||
drop-shadow-color: btn-color.with-alpha(0.4);
|
||
drop-shadow-offset-y: 3px * scale;
|
||
touch := TouchArea {
|
||
clicked => {
|
||
root.clicked();
|
||
}
|
||
}
|
||
|
||
Text {
|
||
text: color-name;
|
||
font-size: 14px * scale;
|
||
font-weight: 600;
|
||
color: #FFFFFF;
|
||
horizontal-alignment: center;
|
||
vertical-alignment: center;
|
||
}
|
||
}
|
||
|
||
// 颜色选择弹窗组件
|
||
component ColorPickerDialog inherits Rectangle {
|
||
in property <float> scale;
|
||
in property <bool> show: false;
|
||
callback color-selected(CardColor);
|
||
callback cancel;
|
||
width: 100%;
|
||
height: 100%;
|
||
background: show ? #00000080 : transparent;
|
||
opacity: show ? 1.0 : 0.0;
|
||
visible: show;
|
||
animate opacity {
|
||
duration: 200ms;
|
||
easing: ease-out;
|
||
}
|
||
|
||
// 阻止点击穿透
|
||
TouchArea {
|
||
enabled: show;
|
||
clicked => {
|
||
root.cancel();
|
||
}
|
||
}
|
||
|
||
// 弹窗内容
|
||
Rectangle {
|
||
x: (parent.width - self.width) / 2;
|
||
y: (parent.height - self.height) / 2;
|
||
width: 600px * scale;
|
||
height: 280px * scale;
|
||
background: #FFFFFF;
|
||
border-radius: 20px * scale;
|
||
drop-shadow-blur: 30px * scale;
|
||
drop-shadow-color: #00000040;
|
||
drop-shadow-offset-y: 10px * scale;
|
||
|
||
// 阻止点击弹窗内容时关闭
|
||
TouchArea {
|
||
enabled: show;
|
||
}
|
||
|
||
VerticalLayout {
|
||
padding: 30px * scale;
|
||
spacing: 25px * scale;
|
||
alignment: center;
|
||
|
||
// 标题
|
||
Text {
|
||
text: "选择颜色";
|
||
font-size: 24px * scale;
|
||
font-weight: 700;
|
||
color: #333333;
|
||
horizontal-alignment: center;
|
||
}
|
||
|
||
// 颜色按钮网格
|
||
HorizontalLayout {
|
||
spacing: 20px * scale;
|
||
alignment: center;
|
||
ColorButton {
|
||
scale: root.scale;
|
||
btn-color: #F44336;
|
||
color-name: "红色";
|
||
clicked => {
|
||
root.color-selected(CardColor.Red);
|
||
}
|
||
}
|
||
|
||
ColorButton {
|
||
scale: root.scale;
|
||
btn-color: #2196F3;
|
||
color-name: "蓝色";
|
||
clicked => {
|
||
root.color-selected(CardColor.Blue);
|
||
}
|
||
}
|
||
|
||
ColorButton {
|
||
scale: root.scale;
|
||
btn-color: #4CAF50;
|
||
color-name: "绿色";
|
||
clicked => {
|
||
root.color-selected(CardColor.Green);
|
||
}
|
||
}
|
||
|
||
ColorButton {
|
||
scale: root.scale;
|
||
btn-color: #FFEB3B;
|
||
color-name: "黄色";
|
||
clicked => {
|
||
root.color-selected(CardColor.Yellow);
|
||
}
|
||
}
|
||
}
|
||
|
||
// 取消按钮
|
||
HorizontalLayout {
|
||
alignment: center;
|
||
Button {
|
||
scale: root.scale;
|
||
text: "取消";
|
||
clicked => {
|
||
root.cancel();
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
// 主游戏页面
|
||
export component GamePage inherits Rectangle {
|
||
in property <float> scale;
|
||
// 其他玩家列表
|
||
in property <[OtherPlayer]> other-players;
|
||
|
||
// 当前玩家信息
|
||
in property <string> current-player-name;
|
||
in property <int> current-player-card-count;
|
||
in property <bool> current-player-has-uno;
|
||
in property <bool> is-current-player-turn;
|
||
|
||
// 手牌列表
|
||
in property <[HandCard]> hand-cards;
|
||
|
||
// 当前选中的卡牌索引 (-1 表示未选中)
|
||
property <int> selected-card-index: -1;
|
||
|
||
// 弃牌堆顶牌
|
||
in property <image> discard-top-card;
|
||
|
||
// 游戏状态
|
||
in property <GameDirection> game-direction;
|
||
in property <CardColor> current-color;
|
||
|
||
// 颜色选择弹窗状态
|
||
in-out property <bool> show-color-picker;
|
||
|
||
// 回调
|
||
callback request-play-card(int, CardColor);
|
||
callback request-draw-card;
|
||
callback request-uno;
|
||
width: 100%;
|
||
height: 100%;
|
||
|
||
// 背景渐变
|
||
Rectangle {
|
||
background: @linear-gradient(135deg, #fdfbf7 0%, #f3e7e9 100%);
|
||
}
|
||
|
||
// 主布局
|
||
VerticalLayout {
|
||
padding: 30px * scale;
|
||
spacing: 20px * scale;
|
||
|
||
// ===== 顶部:其他玩家区域 =====
|
||
Rectangle {
|
||
height: 150px * scale;
|
||
background: #FDFBF8;
|
||
border-radius: 16px * scale;
|
||
drop-shadow-blur: 10px * scale;
|
||
drop-shadow-color: #00000010;
|
||
drop-shadow-offset-y: 2px * scale;
|
||
HorizontalLayout {
|
||
padding-top: 10px * scale;
|
||
padding-bottom: 15px * scale;
|
||
padding-left: 15px * scale;
|
||
padding-right: 15px * scale;
|
||
spacing: 40px * scale;
|
||
alignment: center;
|
||
for player[index] in other-players: PlayerAvatar {
|
||
scale: root.scale;
|
||
player-name: player.name;
|
||
card-count: player.card-count;
|
||
has-uno: player.has-uno;
|
||
is-current-turn: player.is-current-turn;
|
||
}
|
||
}
|
||
}
|
||
|
||
// ===== 中间:牌堆区域 =====
|
||
Rectangle {
|
||
vertical-stretch: 1;
|
||
background: transparent;
|
||
|
||
// 中心容器:包含环和两个牌堆
|
||
Rectangle {
|
||
width: 560px * scale;
|
||
height: 320px * scale;
|
||
x: (parent.width - self.width) / 2;
|
||
y: (parent.height - self.height) / 2;
|
||
background: transparent;
|
||
|
||
// 方向环背景 - 在牌堆下面
|
||
DirectionRingBackground {
|
||
scale: root.scale;
|
||
x: (parent.width - self.width) / 2;
|
||
y: (parent.height - self.height) / 2;
|
||
ring-width: 560;
|
||
ring-height: 320;
|
||
current-color: root.current-color;
|
||
}
|
||
|
||
// 起牌堆 - 左侧
|
||
Rectangle {
|
||
x: 80px * scale;
|
||
y: (parent.height - 200px * scale) / 2;
|
||
width: 130px * scale;
|
||
height: 200px * scale;
|
||
CardBack {
|
||
scale: root.scale;
|
||
card-width: 130;
|
||
card-height: 200;
|
||
}
|
||
|
||
TouchArea {
|
||
enabled: root.is-current-player-turn;
|
||
width: parent.width;
|
||
height: parent.height;
|
||
clicked => {
|
||
root.selected-card-index = -1;
|
||
root.request-draw-card();
|
||
}
|
||
}
|
||
}
|
||
|
||
// 弃牌堆 - 右侧
|
||
DiscardCard {
|
||
scale: root.scale;
|
||
x: parent.width - 130px * scale - 80px * scale;
|
||
y: (parent.height - 200px * scale) / 2;
|
||
card-width: 130;
|
||
card-height: 200;
|
||
card-image: root.discard-top-card;
|
||
}
|
||
|
||
// 方向环光点 - 在牌堆上面
|
||
DirectionRingOrbs {
|
||
scale: root.scale;
|
||
x: (parent.width - self.width) / 2;
|
||
y: (parent.height - self.height) / 2;
|
||
ring-width: 560;
|
||
ring-height: 320;
|
||
direction: root.game-direction;
|
||
current-color: root.current-color;
|
||
}
|
||
}
|
||
}
|
||
|
||
// ===== 底部:当前玩家区域 =====
|
||
Rectangle {
|
||
height: 320px * scale;
|
||
background: #FDFBF8;
|
||
border-radius: 16px * scale;
|
||
drop-shadow-blur: 10px * scale;
|
||
drop-shadow-color: #00000010;
|
||
drop-shadow-offset-y: -2px * scale;
|
||
HorizontalLayout {
|
||
padding: 20px * scale;
|
||
padding-left: 40px * scale;
|
||
padding-right: 40px * scale;
|
||
spacing: 30px * scale;
|
||
alignment: space-between;
|
||
|
||
// 当前玩家头像 - 靠左,垂直居中与UNO按钮对齐
|
||
VerticalLayout {
|
||
alignment: center;
|
||
PlayerAvatar {
|
||
scale: root.scale;
|
||
player-name: current-player-name;
|
||
card-count: current-player-card-count;
|
||
has-uno: current-player-has-uno;
|
||
is-current-turn: is-current-player-turn;
|
||
}
|
||
}
|
||
|
||
// 手牌区域
|
||
VerticalLayout {
|
||
horizontal-stretch: 1;
|
||
spacing: 10px * scale;
|
||
alignment: center;
|
||
|
||
// 出牌按钮 (放在手牌上方)
|
||
Rectangle {
|
||
height: 50px * scale;
|
||
background: transparent;
|
||
HorizontalLayout {
|
||
alignment: center;
|
||
padding-bottom: 10px * scale;
|
||
Button {
|
||
scale: root.scale;
|
||
text: "出牌";
|
||
enabled: is-current-player-turn && selected-card-index >= 0 && hand-cards[selected-card-index].can-be-played;
|
||
clicked => {
|
||
if (selected-card-index >= 0) {
|
||
// 检查是否是万能牌
|
||
if (hand-cards[selected-card-index].is-wild) {
|
||
// 显示颜色选择弹窗
|
||
root.show-color-picker = true;
|
||
} else {
|
||
// 非万能牌直接出牌,颜色传 Red
|
||
root.request-play-card(selected-card-index, CardColor.Red);
|
||
selected-card-index = -1;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
// 手牌列表 - 上方留出空间给选中效果
|
||
Rectangle {
|
||
height: 210px * scale;
|
||
background: transparent;
|
||
|
||
// 手牌容器,向下偏移以给选中卡牌留空间,居中显示
|
||
Rectangle {
|
||
x: (parent.width - self.width) / 2;
|
||
y: 25px * scale;
|
||
width: hand-cards-layout.preferred-width;
|
||
height: 180px * scale;
|
||
hand-cards-layout := HorizontalLayout {
|
||
alignment: center;
|
||
spacing: -35px * scale; // 卡牌重叠效果
|
||
|
||
for card[index] in hand-cards: HandCardItem {
|
||
scale: root.scale;
|
||
card-image: card.image-path;
|
||
is-selected: index == selected-card-index;
|
||
clicked => {
|
||
if (is-current-player-turn && card.can-be-played) {
|
||
// 切换选中状态
|
||
if (selected-card-index == index) {
|
||
selected-card-index = -1;
|
||
} else {
|
||
selected-card-index = index;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
// UNO 按钮 - 靠右
|
||
VerticalLayout {
|
||
alignment: center;
|
||
UnoButton {
|
||
scale: root.scale;
|
||
clicked => {
|
||
root.request-uno();
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
// 颜色选择弹窗 (覆盖在最上层)
|
||
ColorPickerDialog {
|
||
scale: root.scale;
|
||
show: root.show-color-picker;
|
||
color-selected(color) => {
|
||
root.show-color-picker = false;
|
||
root.request-play-card(selected-card-index, color);
|
||
selected-card-index = -1;
|
||
}
|
||
cancel => {
|
||
root.show-color-picker = false;
|
||
}
|
||
}
|
||
}
|