Files
AskaEth bc41dfcf7d
CI / fmt (rustfmt --check) (push) Canceled after 0s
CI / clippy (ubuntu-latest) (push) Canceled after 0s
CI / clippy (windows-latest) (push) Canceled after 0s
CI / test (ubuntu-latest) (push) Canceled after 0s
CI / test (windows-latest) (push) Canceled after 0s
CI / build release (ubuntu-latest) (push) Canceled after 0s
CI / build release (windows-latest) (push) Canceled after 0s
Guards / core 零 UI 守门 (push) Canceled after 0s
Guards / 文件行数 ≤400 守门 (push) Canceled after 0s
Guards / 依赖许可与来源(cargo-deny) (push) Canceled after 0s
Unlucky Launcher v0.0.1-alpha
Rust 原生单文件 Minecraft 启动器:CLI 完整可用(install/launch/accounts/java/diagnose/agent),
MCP server 接入外部 Agent 工具,GUI 开发中。
2026-10-31 21:54:10 +08:00

301 lines
9.0 KiB
Plaintext
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// 基础输入件:单行输入(含占位文本)、搜索框、开关、分段控件、复选框。
// 一律自绘(Metro 需要方角 + accent 高亮)。占位文本手绘:内置 TextInput 没有
// placeholder-text(那是 std-widgets 的 LineEdit,外观不可控)。
import { Theme } from "../theme/tokens.slint";
import { Metrics } from "../theme/metrics.slint";
import { AppIcon, IconKind } from "../components/icons.slint";
import { UiText, TextRole } from "../components/label.slint";
/// 单行输入:内置 TextInput + 占位文本层(Focus 态由外部读 `focused`)。
export component PlainInput inherits Rectangle {
in-out property <string> text: "";
in property <string> placeholder: "";
in property <color> text-color: Theme.fg-primary;
in property <length> text-padding: 0px;
out property <bool> focused: input.has-focus;
callback edited(string);
callback accepted(string);
background: transparent;
input := TextInput {
x: root.text-padding;
y: 0;
width: root.width - 2 * root.text-padding;
height: root.height;
color: root.text-color;
font-family: Theme.font-family;
font-size: Theme.font-size-body;
vertical-alignment: center;
single-line: true;
text <=> root.text;
edited => { root.edited(self.text); }
accepted => { root.accepted(self.text); }
}
if root.text == "" && root.placeholder != "" : UiText {
x: root.text-padding;
y: 0;
width: root.width - 2 * root.text-padding;
height: root.height;
role: TextRole.body;
text: root.placeholder;
color: Theme.fg-disabled;
}
}
export component SearchField inherits Rectangle {
in-out property <string> text: "";
in property <string> placeholder: "搜索";
callback edited(string);
callback accepted(string);
height: Metrics.control-height;
background: Theme.bg-input;
border-radius: Metrics.radius-sm;
border-width: 1px;
border-color: field.focused ? Theme.accent : Theme.stroke;
HorizontalLayout {
padding-left: Metrics.gap-2;
padding-right: Metrics.gap-2;
spacing: Metrics.gap-2;
AppIcon { kind: IconKind.search; icon-size: 14px; }
field := PlainInput {
placeholder: root.placeholder;
text <=> root.text;
edited(t) => { root.edited(t); }
accepted(t) => { root.accepted(t); }
}
}
}
export component TextField inherits Rectangle {
in-out property <string> text: "";
in property <string> placeholder: "";
callback edited(string);
height: Metrics.control-height;
background: Theme.bg-input;
border-radius: Metrics.radius-sm;
border-width: 1px;
border-color: field.focused ? Theme.accent : Theme.stroke;
field := PlainInput {
text-padding: Metrics.gap-2;
placeholder: root.placeholder;
text <=> root.text;
edited(t) => { root.edited(t); }
}
}
export component ToggleSwitch inherits Rectangle {
in-out property <bool> checked: false;
callback toggled(bool);
width: 40px;
height: 20px;
border-radius: 10px;
background: checked ? Theme.accent : Theme.stroke-strong;
animate background { duration: Theme.motion-normal; }
Rectangle {
width: 14px;
height: 14px;
y: 3px;
x: root.checked ? root.width - self.width - 3px : 3px;
border-radius: 7px;
background: Theme.fg-on-accent;
animate x { duration: Theme.motion-normal; easing: ease-in-out; }
}
TouchArea {
mouse-cursor: pointer;
clicked => {
root.checked = !root.checked;
root.toggled(root.checked);
}
}
}
/// 分段控件(视图切换、磁贴尺寸、主题三态共用)
export component SegmentedControl inherits Rectangle {
in property <[string]> options;
in-out property <int> selected: 0;
callback changed(int);
height: Metrics.control-height;
background: Theme.bg-canvas;
border-radius: Metrics.radius-sm;
border-width: 1px;
border-color: Theme.stroke;
HorizontalLayout {
padding: 2px;
spacing: 2px;
for o[i] in options : Rectangle {
border-radius: Metrics.radius-sm;
background: i == root.selected
? Theme.accent
: (seg.has-hover ? Theme.bg-hover : transparent);
animate background { duration: Theme.motion-fast; }
UiText {
role: TextRole.caption;
text: o;
horizontal-alignment: center;
color: i == root.selected ? Theme.fg-on-accent : Theme.fg-primary;
}
seg := TouchArea {
mouse-cursor: pointer;
clicked => {
root.selected = i;
root.changed(i);
}
}
}
}
}
export component CheckBox inherits Rectangle { in-out property <bool> checked: false;
in property <string> text: "";
callback toggled(bool);
height: Metrics.touch-target;
background: transparent;
HorizontalLayout {
spacing: Metrics.gap-2;
Rectangle {
width: 16px;
height: 16px;
border-radius: Metrics.radius-sm;
border-width: 1px;
border-color: root.checked ? Theme.accent : Theme.stroke-strong;
background: root.checked ? Theme.accent : Theme.bg-input;
if root.checked : AppIcon {
kind: IconKind.check;
icon-size: 12px;
x: 2px;
y: 2px;
tint: Theme.fg-on-accent;
}
}
UiText { role: TextRole.body; text: root.text; }
TouchArea {
mouse-cursor: pointer;
clicked => {
root.checked = !root.checked;
root.toggled(root.checked);
}
}
}
}
/// 滑条(09 §102/§101 的「强度滑条」:暗化层强度、面板半透明度共用)。
/// 自绘而非 std-widgets 的 Slider:Metro 需要方头轨道 + accent 填充,且要跟主题令牌一起换肤。
export component MetroSlider inherits Rectangle {
in property <float> minimum: 0.0;
in property <float> maximum: 1.0;
in-out property <float> value: 1.0;
callback changed(float);
out property <float> ratio: root.maximum > root.minimum
? Math.max(0.0, Math.min(1.0, (root.value - root.minimum) / (root.maximum - root.minimum)))
: 0.0;
width: 200px;
height: Metrics.touch-target;
background: transparent;
/// 鼠标位置 → 取值(Slint 的 function 不能声明局部变量,所以拆成纯函数;
/// 函数名不用 kebab-case:`value-at(...)` 会被当成减法解析)
pure function valueForX(x: length) -> float {
return root.minimum
+ Math.max(0.0, Math.min(1.0, x / root.width)) * (root.maximum - root.minimum);
}
// 轨道 + 已填充段
Rectangle {
x: 0;
y: (root.height - 4px) / 2;
width: root.width;
height: 4px;
border-radius: 2px;
background: Theme.stroke-strong;
}
Rectangle {
x: 0;
y: (root.height - 4px) / 2;
width: root.width * root.ratio;
height: 4px;
border-radius: 2px;
background: Theme.accent;
}
// 滑块
Rectangle {
x: (root.width - 14px) * root.ratio;
y: (root.height - 14px) / 2;
width: 14px;
height: 14px;
border-radius: 7px;
background: Theme.accent;
border-width: 2px;
border-color: Theme.surface-solid;
}
area := TouchArea {
mouse-cursor: pointer;
moved => {
root.value = root.valueForX(self.mouse-x);
root.changed(root.value);
}
clicked => {
root.value = root.valueForX(self.mouse-x);
root.changed(root.value);
}
}
accessible-role: slider;
}
/// 预设色板(09 §104 自定义强调色:预设色板 + 取色器,取色器由十六进制输入承担)。
export component ColorSwatches inherits Rectangle {
in property <[color]> palette;
in property <color> current;
/// 传**索引**而不是 color:Slint 没有「颜色 → #RRGGBB」的格式化,
/// 预设色板的十六进制串由 Rust 侧同一张表给出(一处真相)。
callback picked(int);
width: root.palette.length * 28px;
height: 28px;
background: transparent;
HorizontalLayout {
spacing: 4px;
for shade[i] in root.palette : Rectangle {
width: 24px;
height: 24px;
border-radius: Metrics.radius-sm;
background: shade;
border-width: shade == root.current ? 2px : 1px;
border-color: shade == root.current ? Theme.fg-primary : Theme.stroke;
swatch-touch := TouchArea {
mouse-cursor: pointer;
clicked => { root.picked(i); }
}
accessible-role: button;
}
}
}