feat(ui): add dynamic scaling support

- Introduced `scale` property across components for responsive design.
- Adjusted dimensions and positions dynamically based on scale.
- Enhanced flexibility for varying display resolutions.
This commit is contained in:
Kieran Kihn
2025-12-18 11:42:29 +08:00
parent 6276dbdea3
commit 150e29c25c
5 changed files with 240 additions and 184 deletions

View File

@@ -1,17 +1,19 @@
import { LineEdit } from "std-widgets.slint";
export component Button inherits Rectangle {
in property <float> scale;
in property <string> text;
in property <bool> enabled: true;
callback clicked;
width: 100px;
height: 36px;
width: 100px * scale;
height: 36px * scale;
background: !root.enabled ? #555555 // 禁用状态
: touch.pressed ? #444444 // 按下
: #2C2C2C; // 默认
border-radius: 6px;
border-radius: 6px * scale;
touch := TouchArea {
enabled: root.enabled;
@@ -23,41 +25,43 @@ export component Button inherits Rectangle {
Text {
text: root.text;
color: #FFFFFF;
font-size: 14px;
font-size: 14px * scale;
vertical-alignment: center;
horizontal-alignment: center;
}
}
export component LabeledInput inherits VerticalLayout {
in property <float> scale;
in property <InputType> input-type: InputType.text;
in property <string> label-text;
in-out property <string> value <=> input.text;
in property <string> placeholder;
spacing: 6px;
spacing: 6px * scale;
// 标签文本
Text {
text: root.label-text;
font-size: 12px;
font-size: 12px * scale;
font-weight: 500;
color: #555555;
}
// 输入框背景和实体
Rectangle {
height: 36px;
height: 36px * scale;
background: #FFFFFF;
border-radius: 6px;
border-width: 1px;
border-radius: 6px * scale;
border-width: 1px * scale;
border-color: input.has-focus ? #888888 : #E0E0E0; // 聚焦时边框变深
input := LineEdit {
width: 100%;
height: 100%;
font-size: 14px;
font-size: 14px * scale;
placeholder-text: root.placeholder;
input-type: root.input-type;
}

View File

@@ -4,13 +4,15 @@ import {
} from "std-widgets.slint";
import { Button, LabeledInput } from "Components.slint";
export component ConnectPage inherits Window {
export component ConnectPage inherits Rectangle {
in property <float> scale;
in property <bool> is-connecting;
callback request-connect(string, string, string);
width: 1920px;
height: 1080px;
width: 100%;
height: 100%;
// 背景渐变 (从左上角的米色到右下角的淡粉色)
Rectangle {
@@ -19,29 +21,29 @@ export component ConnectPage inherits Window {
// 中心卡片
Rectangle {
width: 600px;
height: 600px; // 根据内容高度调整
width: 600px * scale;
height: 600px * scale; // 根据内容高度调整
background: #FDFBF8; // 略微区别于背景的米白色
border-radius: 20px;
border-radius: 20px * scale;
// 简单的阴影模拟
drop-shadow-blur: 20px;
drop-shadow-blur: 20px * scale;
drop-shadow-color: #00000015;
drop-shadow-offset-y: 4px;
drop-shadow-offset-y: 4px * scale;
VerticalLayout {
padding: 40px;
spacing: 20px;
padding: 40px * scale;
spacing: 20px * scale;
alignment: center;
// 标题区域
VerticalLayout {
spacing: 8px;
spacing: 8px * scale;
alignment: center;
Text {
text: "UNO";
font-size: 96px;
font-size: 96px * scale;
font-weight: 700;
color: #222222;
horizontal-alignment: center;
@@ -49,7 +51,7 @@ export component ConnectPage inherits Window {
Text {
text: "连接至服务器以开始游戏";
font-size: 14px;
font-size: 14px * scale;
color: #666666;
horizontal-alignment: center;
}
@@ -57,32 +59,36 @@ export component ConnectPage inherits Window {
// 占位间隔
Rectangle {
height: 10px;
height: 10px * scale;
}
// 表单区域
address-input := LabeledInput {
scale: root.scale;
label-text: "服务器地址";
value: "localhost";
}
port-input := LabeledInput {
scale: root.scale;
label-text: "服务器端口";
value: "10001";
input-type: InputType.number;
}
name-input := LabeledInput {
scale: root.scale;
label-text: "玩家昵称";
value: "Player";
}
// 底部按钮区域 (增加一点顶部间距)
HorizontalLayout {
padding-top: 10px;
padding-top: 10px * scale;
alignment: center;
Button {
scale: root.scale;
text: is-connecting ? "正在连接..." : "连接";
enabled: !is-connecting;
clicked => {

View File

@@ -34,28 +34,30 @@ export struct HandCard {
// 玩家头像组件
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;
height: 130px;
width: 100px * scale;
height: 130px * scale;
background: transparent;
VerticalLayout {
spacing: 4px;
spacing: 4px * scale;
alignment: center;
// UNO 标志占位区域 (固定高度)
HorizontalLayout {
alignment: center;
Rectangle {
width: 50px;
height: 20px;
width: 50px * scale;
height: 20px * scale;
background: has-uno ? #FF5722 : transparent;
border-radius: 4px;
border-radius: 4px * scale;
Text {
text: "UNO!";
font-size: 11px;
font-size: 11px * scale;
font-weight: 700;
color: has-uno ? #FFFFFF : transparent;
horizontal-alignment: center;
@@ -68,15 +70,15 @@ component PlayerAvatar inherits Rectangle {
HorizontalLayout {
alignment: center;
Rectangle {
width: 60px;
height: 60px;
width: 60px * scale;
height: 60px * scale;
background: is-current-turn ? #FFD54F : #E0E0E0;
border-radius: 30px;
border-width: is-current-turn ? 3px : 0px;
border-radius: 30px * scale;
border-width: is-current-turn ? 3px * scale : 0px;
border-color: #FF9800;
Text {
text: "👤";
font-size: 28px;
font-size: 28px * scale;
horizontal-alignment: center;
vertical-alignment: center;
}
@@ -86,7 +88,7 @@ component PlayerAvatar inherits Rectangle {
// 玩家名称
Text {
text: player-name;
font-size: 12px;
font-size: 12px * scale;
font-weight: 500;
color: #333333;
horizontal-alignment: center;
@@ -95,7 +97,7 @@ component PlayerAvatar inherits Rectangle {
// 剩余手牌数
Text {
text: "剩余: " + card-count + " 张";
font-size: 10px;
font-size: 10px * scale;
color: #666666;
horizontal-alignment: center;
}
@@ -104,22 +106,24 @@ component PlayerAvatar inherits Rectangle {
// 卡牌背面组件 (起牌堆)
component CardBack inherits Rectangle {
in property <float> scale;
in property <int> card-width: 100;
in property <int> card-height: 150;
width: card-width * 1px;
height: card-height * 1px;
width: card-width * 1px * scale;
height: card-height * 1px * scale;
background: #2C2C2C;
border-radius: 10px;
border-width: 2px;
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;
border-radius: 8px * scale;
Text {
text: "UNO";
font-size: 20px;
font-size: 20px * scale;
font-weight: 700;
color: #888888;
horizontal-alignment: center;
@@ -130,16 +134,18 @@ component CardBack inherits Rectangle {
// 弃牌堆卡牌组件
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;
height: card-height * 1px;
width: card-width * 1px * scale;
height: card-height * 1px * scale;
background: transparent;
border-radius: 10px;
drop-shadow-blur: 12px;
border-radius: 10px * scale;
drop-shadow-blur: 12px * scale;
drop-shadow-color: #00000040;
drop-shadow-offset-y: 4px;
drop-shadow-offset-y: 4px * scale;
Image {
width: 100%;
height: 100%;
@@ -150,6 +156,8 @@ component DiscardCard inherits Rectangle {
// 方向环背景组件 - 只绘制环线
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;
@@ -157,8 +165,8 @@ component DirectionRingBackground inherits Rectangle {
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;
height: ring-height * 1px;
width: ring-width * 1px * scale;
height: ring-height * 1px * scale;
background: transparent;
// 底层光晕环
@@ -166,28 +174,28 @@ component DirectionRingBackground inherits Rectangle {
width: 100%;
height: 100%;
stroke: ring-color-dim;
stroke-width: 8px;
stroke-width: 8px * scale;
fill: transparent;
opacity: 0.3;
MoveTo {
x: ring-width / 2 + a;
y: ring-height / 2;
x: (ring-width / 2 + a) * scale;
y: (ring-height / 2) * scale;
}
ArcTo {
x: ring-width / 2 - a;
y: ring-height / 2;
radius-x: a;
radius-y: b;
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;
y: ring-height / 2;
radius-x: a;
radius-y: b;
x: (ring-width / 2 + a) * scale;
y: (ring-height / 2) * scale;
radius-x: a * scale;
radius-y: b * scale;
sweep: true;
large-arc: true;
}
@@ -198,28 +206,28 @@ component DirectionRingBackground inherits Rectangle {
width: 100%;
height: 100%;
stroke: ring-color;
stroke-width: 3px;
stroke-width: 3px * scale;
fill: transparent;
opacity: 0.9;
MoveTo {
x: ring-width / 2 + a;
y: ring-height / 2;
x: (ring-width / 2 + a) * scale;
y: (ring-height / 2) * scale;
}
ArcTo {
x: ring-width / 2 - a;
y: ring-height / 2;
radius-x: a;
radius-y: b;
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;
y: ring-height / 2;
radius-x: a;
radius-y: b;
x: (ring-width / 2 + a) * scale;
y: (ring-height / 2) * scale;
radius-x: a * scale;
radius-y: b * scale;
sweep: true;
large-arc: true;
}
@@ -228,6 +236,8 @@ component DirectionRingBackground inherits Rectangle {
// 方向环光点组件 - 使用内置动画实现平滑移动
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;
@@ -248,55 +258,55 @@ component DirectionRingOrbs inherits Rectangle {
// 实际角度 = 基础角度 * 方向系数
property <float> actual-angle: base-angle * dir-sign;
width: ring-width * 1px;
height: ring-height * 1px;
width: ring-width * 1px * scale;
height: ring-height * 1px * scale;
background: transparent;
// 光点1
Rectangle {
x: parent.width / 2 - 9px + cos(actual-angle * 1deg) * a * 1px;
y: parent.height / 2 - 9px + sin(actual-angle * 1deg) * b * 1px;
width: 18px;
height: 18px;
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;
drop-shadow-blur: 8px;
border-radius: 9px * scale;
drop-shadow-blur: 8px * scale;
drop-shadow-color: ring-color;
}
// 光点2
Rectangle {
x: parent.width / 2 - 9px + cos((actual-angle + 90) * 1deg) * a * 1px;
y: parent.height / 2 - 9px + sin((actual-angle + 90) * 1deg) * b * 1px;
width: 18px;
height: 18px;
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;
drop-shadow-blur: 8px;
border-radius: 9px * scale;
drop-shadow-blur: 8px * scale;
drop-shadow-color: ring-color;
}
// 光点3
Rectangle {
x: parent.width / 2 - 9px + cos((actual-angle + 180) * 1deg) * a * 1px;
y: parent.height / 2 - 9px + sin((actual-angle + 180) * 1deg) * b * 1px;
width: 18px;
height: 18px;
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;
drop-shadow-blur: 8px;
border-radius: 9px * scale;
drop-shadow-blur: 8px * scale;
drop-shadow-color: ring-color;
}
// 光点4
Rectangle {
x: parent.width / 2 - 9px + cos((actual-angle + 270) * 1deg) * a * 1px;
y: parent.height / 2 - 9px + sin((actual-angle + 270) * 1deg) * b * 1px;
width: 18px;
height: 18px;
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;
drop-shadow-blur: 8px;
border-radius: 9px * scale;
drop-shadow-blur: 8px * scale;
drop-shadow-color: ring-color;
}
@@ -308,17 +318,19 @@ component DirectionRingOrbs inherits Rectangle {
// 手牌卡牌组件
component HandCardItem inherits Rectangle {
in property <float> scale;
in property <image> card-image;
in property <bool> is-selected: false;
callback clicked;
width: 120px;
height: 180px;
y: is-selected ? -25px : 0px;
width: 120px * scale;
height: 180px * scale;
y: is-selected ? -25px * scale : 0px;
background: transparent;
border-radius: 10px;
drop-shadow-blur: is-selected ? 16px : 6px;
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 : 3px;
drop-shadow-offset-y: is-selected ? 6px * scale : 3px * scale;
animate y {
duration: 150ms;
easing: ease-out;
@@ -340,14 +352,15 @@ component HandCardItem inherits Rectangle {
// UNO 圆形按钮组件
component UnoButton inherits Rectangle {
in property <float> scale;
callback clicked;
width: 90px;
height: 90px;
width: 90px * scale;
height: 90px * scale;
background: touch.pressed ? #D32F2F : #F44336;
border-radius: 45px;
drop-shadow-blur: 12px;
border-radius: 45px * scale;
drop-shadow-blur: 12px * scale;
drop-shadow-color: #F4433660;
drop-shadow-offset-y: 4px;
drop-shadow-offset-y: 4px * scale;
touch := TouchArea {
clicked => {
root.clicked();
@@ -356,7 +369,7 @@ component UnoButton inherits Rectangle {
Text {
text: "UNO!";
font-size: 20px;
font-size: 20px * scale;
font-weight: 700;
color: #FFFFFF;
horizontal-alignment: center;
@@ -366,18 +379,19 @@ component UnoButton inherits Rectangle {
// 颜色选择按钮组件
component ColorButton inherits Rectangle {
in property <float> scale;
in property <color> btn-color;
in property <string> color-name;
callback clicked;
width: 80px;
height: 80px;
width: 80px * scale;
height: 80px * scale;
background: touch.pressed ? btn-color.darker(20%) : btn-color;
border-radius: 12px;
border-width: 3px;
border-radius: 12px * scale;
border-width: 3px * scale;
border-color: btn-color.darker(30%);
drop-shadow-blur: 8px;
drop-shadow-blur: 8px * scale;
drop-shadow-color: btn-color.with-alpha(0.4);
drop-shadow-offset-y: 3px;
drop-shadow-offset-y: 3px * scale;
touch := TouchArea {
clicked => {
root.clicked();
@@ -386,7 +400,7 @@ component ColorButton inherits Rectangle {
Text {
text: color-name;
font-size: 14px;
font-size: 14px * scale;
font-weight: 600;
color: #FFFFFF;
horizontal-alignment: center;
@@ -396,6 +410,7 @@ component ColorButton inherits Rectangle {
// 颜色选择弹窗组件
component ColorPickerDialog inherits Rectangle {
in property <float> scale;
in property <bool> show: false;
callback color-selected(CardColor);
callback cancel;
@@ -421,13 +436,13 @@ component ColorPickerDialog inherits Rectangle {
Rectangle {
x: (parent.width - self.width) / 2;
y: (parent.height - self.height) / 2;
width: 600px;
height: 280px;
width: 600px * scale;
height: 280px * scale;
background: #FFFFFF;
border-radius: 20px;
drop-shadow-blur: 30px;
border-radius: 20px * scale;
drop-shadow-blur: 30px * scale;
drop-shadow-color: #00000040;
drop-shadow-offset-y: 10px;
drop-shadow-offset-y: 10px * scale;
// 阻止点击弹窗内容时关闭
TouchArea {
@@ -435,14 +450,14 @@ component ColorPickerDialog inherits Rectangle {
}
VerticalLayout {
padding: 30px;
spacing: 25px;
padding: 30px * scale;
spacing: 25px * scale;
alignment: center;
// 标题
Text {
text: "选择颜色";
font-size: 24px;
font-size: 24px * scale;
font-weight: 700;
color: #333333;
horizontal-alignment: center;
@@ -450,9 +465,10 @@ component ColorPickerDialog inherits Rectangle {
// 颜色按钮网格
HorizontalLayout {
spacing: 20px;
spacing: 20px * scale;
alignment: center;
ColorButton {
scale: root.scale;
btn-color: #F44336;
color-name: "红色";
clicked => {
@@ -461,6 +477,7 @@ component ColorPickerDialog inherits Rectangle {
}
ColorButton {
scale: root.scale;
btn-color: #2196F3;
color-name: "蓝色";
clicked => {
@@ -469,6 +486,7 @@ component ColorPickerDialog inherits Rectangle {
}
ColorButton {
scale: root.scale;
btn-color: #4CAF50;
color-name: "绿色";
clicked => {
@@ -477,6 +495,7 @@ component ColorPickerDialog inherits Rectangle {
}
ColorButton {
scale: root.scale;
btn-color: #FFEB3B;
color-name: "黄色";
clicked => {
@@ -489,6 +508,7 @@ component ColorPickerDialog inherits Rectangle {
HorizontalLayout {
alignment: center;
Button {
scale: root.scale;
text: "取消";
clicked => {
root.cancel();
@@ -500,7 +520,8 @@ component ColorPickerDialog inherits Rectangle {
}
// 主游戏页面
export component GamePage inherits Window {
export component GamePage inherits Rectangle {
in property <float> scale;
// 其他玩家列表
in property <[OtherPlayer]> other-players;
@@ -530,8 +551,8 @@ export component GamePage inherits Window {
callback request-play-card(int, CardColor);
callback request-draw-card;
callback request-uno;
width: 1920px;
height: 1080px;
width: 100%;
height: 100%;
// 背景渐变
Rectangle {
@@ -540,25 +561,26 @@ export component GamePage inherits Window {
// 主布局
VerticalLayout {
padding: 30px;
spacing: 20px;
padding: 30px * scale;
spacing: 20px * scale;
// ===== 顶部:其他玩家区域 =====
Rectangle {
height: 150px;
height: 150px * scale;
background: #FDFBF8;
border-radius: 16px;
drop-shadow-blur: 10px;
border-radius: 16px * scale;
drop-shadow-blur: 10px * scale;
drop-shadow-color: #00000010;
drop-shadow-offset-y: 2px;
drop-shadow-offset-y: 2px * scale;
HorizontalLayout {
padding-top: 10px;
padding-bottom: 15px;
padding-left: 15px;
padding-right: 15px;
spacing: 40px;
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;
@@ -574,14 +596,15 @@ export component GamePage inherits Window {
// 中心容器:包含环和两个牌堆
Rectangle {
width: 560px;
height: 320px;
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;
@@ -591,11 +614,12 @@ export component GamePage inherits Window {
// 起牌堆 - 左侧
Rectangle {
x: 80px;
y: (parent.height - 200px) / 2;
width: 130px;
height: 200px;
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;
}
@@ -613,15 +637,17 @@ export component GamePage inherits Window {
// 弃牌堆 - 右侧
DiscardCard {
x: parent.width - 130px - 80px;
y: (parent.height - 200px) / 2;
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 {
DirectionRingOrbs {
scale: root.scale;
x: (parent.width - self.width) / 2;
y: (parent.height - self.height) / 2;
ring-width: 560;
@@ -634,23 +660,24 @@ export component GamePage inherits Window {
// ===== 底部:当前玩家区域 =====
Rectangle {
height: 320px;
height: 320px * scale;
background: #FDFBF8;
border-radius: 16px;
drop-shadow-blur: 10px;
border-radius: 16px * scale;
drop-shadow-blur: 10px * scale;
drop-shadow-color: #00000010;
drop-shadow-offset-y: -2px;
drop-shadow-offset-y: -2px * scale;
HorizontalLayout {
padding: 20px;
padding-left: 40px;
padding-right: 40px;
spacing: 30px;
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;
@@ -661,17 +688,18 @@ export component GamePage inherits Window {
// 手牌区域
VerticalLayout {
horizontal-stretch: 1;
spacing: 10px;
spacing: 10px * scale;
alignment: center;
// 出牌按钮 (放在手牌上方)
Rectangle {
height: 50px;
height: 50px * scale;
background: transparent;
HorizontalLayout {
alignment: center;
padding-bottom: 10px;
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 => {
@@ -693,20 +721,21 @@ export component GamePage inherits Window {
// 手牌列表 - 上方留出空间给选中效果
Rectangle {
height: 210px;
height: 210px * scale;
background: transparent;
// 手牌容器,向下偏移以给选中卡牌留空间,居中显示
Rectangle {
x: (parent.width - self.width) / 2;
y: 25px;
y: 25px * scale;
width: hand-cards-layout.preferred-width;
height: 180px;
height: 180px * scale;
hand-cards-layout := HorizontalLayout {
alignment: center;
spacing: -35px; // 卡牌重叠效果
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 => {
@@ -729,6 +758,7 @@ export component GamePage inherits Window {
VerticalLayout {
alignment: center;
UnoButton {
scale: root.scale;
clicked => {
root.request-uno();
}
@@ -740,6 +770,7 @@ export component GamePage inherits Window {
// 颜色选择弹窗 (覆盖在最上层)
ColorPickerDialog {
scale: root.scale;
show: root.show-color-picker;
color-selected(color) => {
root.show-color-picker = false;

View File

@@ -1,6 +1,12 @@
import { ConnectPage } from "ConnectPage.slint";
import { StartPage } from "StartPage.slint";
import { GamePage, OtherPlayer, HandCard, CardColor, GameDirection } from "GamePage.slint";
import {
GamePage,
OtherPlayer,
HandCard,
CardColor,
GameDirection,
} from "GamePage.slint";
enum PageType {
ConnectPage,
@@ -35,17 +41,23 @@ export component MainWindow inherits Window {
callback request-draw-card;
callback request-uno;
width: 1920px;
height: 1080px;
preferred-width: 1920px;
preferred-height: 1080px;
min-width: 960px;
min-height: 600px;
title: "UNO!";
property <float> scale: min(self.width / 1920px, self.height / 1080px);
if root.active-page == PageType.ConnectPage: connect-page := ConnectPage {
scale: root.scale;
is-connecting: root.is-connecting;
request-connect(server-address, server-port, player-name) => {
root.request-connect(server-address, server-port, player-name);
}
}
if root.active-page == PageType.StartPage: start-page := StartPage {
scale: root.scale;
is-ready: root.is-ready;
is-restart: root.is-restart;
request-start => {
@@ -53,6 +65,7 @@ export component MainWindow inherits Window {
}
}
if root.active-page == PageType.GamePage: game-page := GamePage {
scale: root.scale;
other-players: root.other-players;
current-player-name: root.current-player-name;
current-player-card-count: root.current-player-card-count;

View File

@@ -1,13 +1,14 @@
import {Button} from "Components.slint";
export component StartPage inherits Window {
export component StartPage inherits Rectangle {
in property <float> scale;
in property <bool> is-ready;
in property <bool> is-restart;
callback request-start;
width: 1920px;
height: 1080px;
width: 100%;
height: 100%;
// 背景渐变 (从左上角的米色到右下角的淡粉色)
Rectangle {
@@ -16,29 +17,29 @@ export component StartPage inherits Window {
// 中心卡片
Rectangle {
width: 600px;
height: 600px; // 根据内容高度调整
width: 600px * scale;
height: 600px * scale; // 根据内容高度调整
background: #FDFBF8; // 略微区别于背景的米白色
border-radius: 20px;
border-radius: 20px * scale;
// 简单的阴影模拟
drop-shadow-blur: 20px;
drop-shadow-blur: 20px * scale;
drop-shadow-color: #00000015;
drop-shadow-offset-y: 4px;
drop-shadow-offset-y: 4px * scale;
VerticalLayout {
padding: 40px;
spacing: 20px;
padding: 40px * scale;
spacing: 20px * scale;
alignment: center;
// 标题区域
VerticalLayout {
spacing: 8px;
spacing: 8px * scale;
alignment: center;
Text {
text: "UNO";
font-size: 96px;
font-size: 96px * scale;
font-weight: 700;
color: #222222;
horizontal-alignment: center;
@@ -46,7 +47,7 @@ export component StartPage inherits Window {
Text {
text: is-restart ? "游戏结束,重新准备以重新开始游戏" : "连接成功,所有玩家准备后开始游戏";
font-size: 14px;
font-size: 14px * scale;
color: #666666;
horizontal-alignment: center;
}
@@ -54,15 +55,16 @@ export component StartPage inherits Window {
// 占位间隔
Rectangle {
height: 10px;
height: 10px * scale;
}
// 底部按钮区域 (增加一点顶部间距)
HorizontalLayout {
padding-top: 10px;
padding-top: 10px * scale;
alignment: center;
Button {
scale: root.scale;
enabled: !is-ready;
text: is-ready ? "已准备" : "准备";
clicked => {