mirror of
https://github.com/kierankihn/uno-game.git
synced 2025-12-27 02:13:18 +08:00
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:
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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 => {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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 => {
|
||||
|
||||
Reference in New Issue
Block a user