Unlucky Launcher v0.2.0
CI / runtime smoke (macos arm64, probe) (push) Skipped
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
CI / runtime smoke (ubuntu x64, xvfb) (push) Canceled after 0s
Guards / core 零 UI 守门 (push) Canceled after 0s
Guards / presenter 零 UI 守门 (push) Canceled after 0s
Guards / design 零第三方依赖守门 (push) Canceled after 0s
Guards / unsafe 落点守门(集合必须恰好一个文件) (push) Canceled after 0s
Guards / 文件行数 ≤400 守门 (push) Canceled after 0s
Guards / 依赖许可与来源(cargo-deny) (push) Canceled after 0s

首个 iced + Material 3 Expressive 版本的源码快照。
完整历史与日常提交在 dev 分支;main 只放阶段性快照。
This commit is contained in:
AskaEth committed 2026-11-05 14:22:11 +08:00
commit 8a8e5ca0c0
1079 files changed
+208035

No files matched your search

+204
View File
@@ -0,0 +1,204 @@
// 下载页 · Java 分组(G2 的第二个分类):四家发行版 + 回退链可视化 + 本地运行时。
//
// 回退链是「可观测性」核心:安装失败时用户要能看见 Temurin 跳过 → Oracle 失败 →
// Microsoft 命中的每一步原因(D23),而不是只看到一句「下载失败」。
import { Theme } from "../theme/tokens.slint";
import { Metrics } from "../theme/metrics.slint";
import { UiJavaVendor, UiJavaStep, UiJavaRuntime, JavaStepOutcome } from "../models.slint";
import { PageState } from "../page_state.slint";
import { UiText, TextRole } from "../components/label.slint";
import { Badge } from "../components/badge.slint";
import { AppIcon, IconKind } from "../components/icons.slint";
import { MetroButton, ButtonKind } from "../widgets/button.slint";
import { ScrollView } from "std-widgets.slint";
component ChainStep inherits Rectangle {
in property <UiJavaStep> step;
height: 26px;
background: transparent;
HorizontalLayout {
spacing: Metrics.gap-1;
AppIcon {
icon-size: 12px;
kind: root.step.outcome == JavaStepOutcome.hit ? IconKind.check
: root.step.outcome == JavaStepOutcome.failed ? IconKind.error
: root.step.outcome == JavaStepOutcome.skip ? IconKind.warn
: IconKind.more;
tint: root.step.outcome == JavaStepOutcome.hit ? Theme.state-success
: root.step.outcome == JavaStepOutcome.failed ? Theme.state-danger
: Theme.fg-secondary;
}
UiText {
role: TextRole.caption;
text: root.step.vendor;
width: 72px;
color: root.step.outcome == JavaStepOutcome.hit ? Theme.state-success : Theme.fg-primary;
}
Badge {
text: root.step.outcome_label;
tint: root.step.outcome == JavaStepOutcome.hit ? Theme.state-success
: root.step.outcome == JavaStepOutcome.failed ? Theme.state-danger
: Theme.fg-secondary;
}
UiText {
role: TextRole.caption;
text: root.step.detail;
horizontal-stretch: 1;
overflow: elide;
}
}
}
component VendorRow inherits Rectangle {
in property <UiJavaVendor> data;
callback install();
height: 56px;
background: vendor-touch.has-hover ? Theme.bg-hover : transparent;
HorizontalLayout {
padding-left: Metrics.gap-3;
padding-right: Metrics.gap-3;
spacing: Metrics.gap-2;
UiText {
role: TextRole.bodyStrong;
text: root.data.rank + ". " + root.data.display;
width: 170px;
}
if root.data.installed : Badge { text: "已装"; tint: Theme.state-success; }
UiText {
role: TextRole.caption;
text: "Java " + root.data.majors;
width: 96px;
}
UiText {
role: TextRole.caption;
text: root.data.sources;
horizontal-stretch: 1;
overflow: elide;
}
MetroButton {
kind: ButtonKind.standard;
text: "安装此家";
button-height: 26px;
enabled: !PageState.java-busy;
clicked => { root.install(); }
}
}
vendor-touch := TouchArea {
mouse-cursor: pointer;
clicked => { root.install(); }
}
}
export component JavaPanel inherits Rectangle {
background: Theme.bg-canvas;
VerticalLayout {
// 顶栏:地区 / 优先级来源 / 目标大版本 / 一键按回退链安装
Rectangle {
height: 56px;
background: Theme.bg-canvas;
HorizontalLayout {
padding-left: Metrics.gap-4;
padding-right: Metrics.gap-4;
spacing: Metrics.gap-3;
UiText {
role: TextRole.subtitle;
text: "Java 运行时";
width: 110px;
}
UiText {
role: TextRole.caption;
text: "地区 " + PageState.java-region + " · 优先级 " + PageState.java-priority-source
+ " · 目标 Java " + PageState.java-target-major;
horizontal-stretch: 1;
overflow: elide;
}
MetroButton {
kind: ButtonKind.accent;
text: PageState.java-busy ? "安装中…" : "按回退链安装";
button-height: 28px;
enabled: !PageState.java-busy;
clicked => { PageState.java-install(""); }
}
}
}
UiText {
x: Metrics.gap-4;
role: TextRole.caption;
text: PageState.java-order;
horizontal-stretch: 1;
overflow: elide;
}
if PageState.java-message != "" : Rectangle {
height: 30px;
background: Theme.accent-soft;
UiText {
x: Metrics.gap-4;
role: TextRole.caption;
text: PageState.java-message;
overflow: elide;
}
}
Rectangle { height: 1px; background: Theme.stroke; }
ScrollView {
vertical-stretch: 1;
VerticalLayout {
padding-bottom: Metrics.gap-4;
// 回退链
UiText {
x: Metrics.gap-4;
role: TextRole.bodyStrong;
text: "回退链";
}
for step in PageState.java-chain : ChainStep { step: step; }
UiText {
x: Metrics.gap-4;
role: TextRole.bodyStrong;
text: "四家发行版";
}
for vendor in PageState.java-vendors : VendorRow {
data: vendor;
install => { PageState.java-install(vendor.id); }
}
UiText {
x: Metrics.gap-4;
role: TextRole.bodyStrong;
text: "本地已发现";
}
if PageState.java-runtimes.length == 0 : UiText {
x: Metrics.gap-4;
role: TextRole.caption;
text: "还没有探测到 Java:点上面的「按回退链安装」装一个托管 JDK。";
}
for runtime in PageState.java-runtimes : Rectangle {
height: 24px;
HorizontalLayout {
padding-left: Metrics.gap-4;
spacing: Metrics.gap-2;
UiText { role: TextRole.caption; text: "Java " + runtime.version; width: 120px; }
UiText { role: TextRole.caption; text: runtime.vendor; width: 96px; }
UiText { role: TextRole.caption; text: runtime.origin; width: 80px; }
UiText { role: TextRole.caption; text: runtime.binary; horizontal-stretch: 1; overflow: elide; }
}
}
}
}
}
}
+320
View File
@@ -0,0 +1,320 @@
// G2 下载页(09 §2 P0·G2 / Q24-4 定案):页内独立左侧分类栏(常驻,非 tab)+ 右侧版本列表。
// 远古版入口显式可达(ULL 对 HMCL「隐藏」行为的纠偏),下挂 classic/alpha/beta/unlisted 子类树,
// 进入远古版分类时顶部提示兼容性 + 每行带「远古版」徽标(防误装)。
//
// P3 接线:右侧多一个「Java 运行时」视图(四家发行版 + 回退链),页面底部常驻安装进度条。
import { Theme } from "../theme/tokens.slint";
import { Metrics } from "../theme/metrics.slint";
import { UiVersion, VersionKind, DownloadCategory } from "../models.slint";
import { PageState } from "../page_state.slint";
import { UiText, TextRole } from "../components/label.slint";
import { AppIcon, IconKind } from "../components/icons.slint";
import { Badge } from "../components/badge.slint";
import { MetroButton, ButtonKind } from "../widgets/button.slint";
import { SearchField, CheckBox } from "../widgets/inputs.slint";
import { InstallPanel } from "../components/install-panel.slint";
import { JavaPanel } from "download-java.slint";
import { ListView } from "std-widgets.slint";
component CategoryRow inherits Rectangle {
in property <string> label: "";
in property <bool> active: false;
in property <bool> sub: false;
in property <bool> disabled: false;
callback clicked();
height: 34px;
background: root.active ? Theme.accent-soft : (row-touch.has-hover ? Theme.bg-hover : transparent);
if root.active : Rectangle {
x: 0;
y: 0;
width: 3px;
height: root.height;
background: Theme.accent;
}
UiText {
x: root.sub ? Metrics.gap-5 : Metrics.gap-3;
width: root.width - (root.sub ? Metrics.gap-5 : Metrics.gap-3) - Metrics.gap-2;
height: root.height;
role: TextRole.body;
text: root.label;
color: root.disabled ? Theme.fg-disabled
: root.active ? Theme.accent
: Theme.fg-primary;
}
row-touch := TouchArea {
mouse-cursor: root.disabled ? default : pointer;
clicked => {
if (!root.disabled) {
root.clicked();
}
}
}
}
component VersionRow inherits Rectangle {
in property <UiVersion> data;
callback install();
height: Metrics.row-height + 8px;
background: row-touch.has-hover ? Theme.bg-hover : transparent;
HorizontalLayout {
padding-left: Metrics.gap-4;
padding-right: Metrics.gap-4;
spacing: Metrics.gap-2;
UiText {
role: TextRole.bodyStrong;
text: root.data.id;
width: 160px;
}
Badge {
text: root.data.kind == VersionKind.release ? "正式版"
: root.data.kind == VersionKind.snapshot ? "预览版"
: "远古版";
tint: root.data.ancient ? Theme.state-warn : Theme.fg-secondary;
}
UiText {
role: TextRole.caption;
text: root.data.released;
horizontal-stretch: 1;
}
if root.data.installed : HorizontalLayout {
spacing: Metrics.gap-1;
AppIcon { kind: IconKind.check; icon-size: 12px; tint: Theme.state-success; }
UiText {
role: TextRole.caption;
text: "已装";
color: Theme.state-success;
}
}
MetroButton {
kind: ButtonKind.standard;
text: root.data.installed ? "重装" : "安装";
button-height: 26px;
clicked => { root.install(); }
}
}
row-touch := TouchArea {
mouse-cursor: pointer;
clicked => { root.install(); }
}
}
export component DownloadPage inherits Rectangle {
in property <[UiVersion]> versions;
in-out property <DownloadCategory> category: DownloadCategory.release;
in-out property <bool> installed-only: false;
in-out property <string> query: "";
/// 远古版子类树是否展开(默认展开,显式可达)
in-out property <bool> ancient-expanded: true;
/// 右侧视图:0 = 版本列表,1 = Java 运行时
in-out property <int> view-tab <=> PageState.download-tab;
callback category-changed(DownloadCategory);
callback install(string);
callback installed-only-changed(bool);
callback query-changed(string);
callback toggle-ancient();
/// 远古版兼容性说明(P1 出说明页;当前由 Rust 侧弹 Toast 占位)
callback ancient-help();
background: Theme.bg-canvas;
VerticalLayout {
// 视图切换(版本 / Java)
Rectangle {
height: 40px;
background: Theme.bg-surface;
HorizontalLayout {
padding-left: Metrics.gap-4;
spacing: Metrics.gap-2;
UiText {
role: TextRole.subtitle;
text: "版本获取";
width: 96px;
}
MetroButton {
kind: root.view-tab == 0 ? ButtonKind.accent : ButtonKind.subtle;
text: "版本列表";
button-height: 26px;
clicked => { root.view-tab = 0; }
}
MetroButton {
kind: root.view-tab == 1 ? ButtonKind.accent : ButtonKind.subtle;
text: "Java 运行时";
button-height: 26px;
clicked => { root.view-tab = 1; }
}
Rectangle { horizontal-stretch: 1; background: transparent; }
if PageState.java-busy : UiText {
role: TextRole.caption;
text: "Java 安装中…";
color: Theme.accent;
}
}
}
Rectangle { height: 1px; background: Theme.stroke; }
if root.view-tab == 0 : HorizontalLayout {
vertical-stretch: 1;
// ── 页内分类侧栏(常驻)───────────────────────────────────
Rectangle {
width: 200px;
background: Theme.bg-surface;
border-width: 1px;
border-color: Theme.stroke;
VerticalLayout {
padding-top: Metrics.gap-2;
CategoryRow {
label: "正式版 release";
active: root.category == DownloadCategory.release;
clicked => { root.category-changed(DownloadCategory.release); }
}
CategoryRow {
label: "预览版 snapshot";
active: root.category == DownloadCategory.snapshot;
clicked => { root.category-changed(DownloadCategory.snapshot); }
}
// 远古版:可展开的父节点 + 子类树
CategoryRow {
label: root.ancient-expanded ? "远古版 ▾" : "远古版 ▸";
active: root.category == DownloadCategory.ancientClassic
|| root.category == DownloadCategory.ancientAlpha
|| root.category == DownloadCategory.ancientBeta
|| root.category == DownloadCategory.ancientUnlisted;
clicked => { root.toggle-ancient(); }
}
if root.ancient-expanded : CategoryRow {
label: "classic(rd-*)";
sub: true;
active: root.category == DownloadCategory.ancientClassic;
clicked => { root.category-changed(DownloadCategory.ancientClassic); }
}
if root.ancient-expanded : CategoryRow {
label: "old_alpha(a1.*)";
sub: true;
active: root.category == DownloadCategory.ancientAlpha;
clicked => { root.category-changed(DownloadCategory.ancientAlpha); }
}
if root.ancient-expanded : CategoryRow {
label: "old_beta(b1.*)";
sub: true;
active: root.category == DownloadCategory.ancientBeta;
clicked => { root.category-changed(DownloadCategory.ancientBeta); }
}
if root.ancient-expanded : CategoryRow {
label: "unlisted 汇总";
sub: true;
active: root.category == DownloadCategory.ancientUnlisted;
clicked => { root.category-changed(DownloadCategory.ancientUnlisted); }
}
CategoryRow {
label: "整合包(P1)";
disabled: true;
}
CategoryRow {
label: "Mod(P1)";
disabled: true;
}
Rectangle { vertical-stretch: 1; background: transparent; }
}
}
// ── 右侧版本列表 ──────────────────────────────────────────
VerticalLayout {
vertical-stretch: 1;
Rectangle {
height: Metrics.page-header-height;
background: Theme.bg-canvas;
HorizontalLayout {
padding-left: Metrics.gap-4;
padding-right: Metrics.gap-4;
spacing: Metrics.gap-3;
SearchField {
width: 220px;
placeholder: "筛选版本号";
text <=> root.query;
edited(t) => { root.query-changed(t); }
}
CheckBox {
text: "仅已装";
checked: root.installed-only;
toggled(v) => { root.installed-only-changed(v); }
}
Rectangle { horizontal-stretch: 1; background: transparent; }
}
}
// 远古版兼容性提示条(首次进入显示,显式可达的代价告知)
Rectangle {
height: 44px;
visible: root.category == DownloadCategory.ancientClassic
|| root.category == DownloadCategory.ancientAlpha
|| root.category == DownloadCategory.ancientBeta
|| root.category == DownloadCategory.ancientUnlisted;
background: Theme.state-warn-soft;
HorizontalLayout {
padding-left: Metrics.gap-4;
padding-right: Metrics.gap-4;
spacing: Metrics.gap-2;
AppIcon { kind: IconKind.warn; icon-size: 16px; tint: Theme.state-warn; }
UiText {
role: TextRole.caption;
text: "远古版(classic/alpha/beta)需要旧版 Java 与旧启动参数,装完请先看兼容性说明。";
wrap: word-wrap;
horizontal-stretch: 1;
}
MetroButton {
kind: ButtonKind.subtle;
text: "说明";
button-height: 26px;
clicked => { root.ancient-help(); }
}
}
}
Rectangle { height: 1px; background: Theme.stroke; }
ListView {
vertical-stretch: 1;
for v in root.versions : VersionRow {
data: v;
install => { root.install(v.id); }
}
}
}
}
if root.view-tab == 1 : JavaPanel {
vertical-stretch: 1;
}
// 安装进度(下载页行内安装 / 向导共用同一份进度)
InstallPanel {
data: PageState.install;
cancel => { PageState.install-cancel(); }
}
}
}
+260
View File
@@ -0,0 +1,260 @@
// G1 实例页(09 §2 P0·G1):顶栏(搜索/新建/视图切换/磁贴尺寸)+ 磁贴网格 + 运行中迷你状态条。
// 网格用 GridLayout:列数由 Rust 按磁贴尺寸算好(小 6 / 中 4 / 大 3),row/col 随 UiTile 一起给。
import { Theme } from "../theme/tokens.slint";
import { Metrics } from "../theme/metrics.slint";
import { UiTile, InstanceView, TileSize, RuntimePhase } from "../models.slint";
import { UiText, TextRole } from "../components/label.slint";
import { AppIcon, IconKind } from "../components/icons.slint";
import { MetroButton, ButtonKind } from "../widgets/button.slint";
import { SearchField, SegmentedControl } from "../widgets/inputs.slint";
import { InstanceTile } from "../components/tile.slint";
import { ListView } from "std-widgets.slint";
// 列表视图的一行(与磁贴共用同一份 UiTile 数据)
component InstanceRow inherits Rectangle {
in property <UiTile> data;
callback open();
callback launch();
height: Metrics.row-height + 8px;
background: row-touch.has-hover ? Theme.bg-hover : transparent;
// 行点击区先声明,按钮后声明 → 按钮命中优先
row-touch := TouchArea {
mouse-cursor: pointer;
clicked => { root.open(); }
}
HorizontalLayout {
padding-left: Metrics.gap-3;
padding-right: Metrics.gap-3;
spacing: Metrics.gap-3;
Rectangle {
width: 28px;
height: 28px;
border-radius: Metrics.radius-sm;
background: root.data.hue;
UiText {
text: root.data.initial;
font-size: 13px;
font-weight: 600;
color: Theme.fg-on-accent;
horizontal-alignment: center;
vertical-alignment: center;
}
}
UiText {
role: TextRole.body;
text: root.data.name;
horizontal-stretch: 1;
}
UiText {
role: TextRole.caption;
text: root.data.mc_version;
width: 88px;
}
UiText {
role: TextRole.caption;
text: root.data.loader_label;
width: 88px;
}
UiText {
role: TextRole.caption;
text: root.data.last_played;
width: 120px;
}
MetroButton {
kind: ButtonKind.subtle;
icon: IconKind.play;
icon-only: true;
enabled: root.data.phase == RuntimePhase.idle
|| root.data.phase == RuntimePhase.failed;
clicked => { root.launch(); }
}
}
}
export component InstancesPage inherits Rectangle {
in property <[UiTile]> tiles;
in property <[UiTile]> running;
in property <InstanceView> view: InstanceView.tiles;
in-out property <TileSize> tile-size: TileSize.medium;
in-out property <string> query: "";
callback launch(string);
callback configure(string);
callback stop(string);
callback open(string);
callback new-instance();
callback view-changed(InstanceView);
callback tile-size-changed(TileSize);
callback query-changed(string);
background: Theme.bg-canvas;
VerticalLayout {
// ── 顶栏 ────────────────────────────────────────────────
Rectangle {
height: Metrics.page-header-height;
background: Theme.bg-surface;
HorizontalLayout {
padding-left: Metrics.gap-4;
padding-right: Metrics.gap-4;
spacing: Metrics.gap-2;
UiText {
role: TextRole.subtitle;
text: "实例";
width: 72px;
}
SearchField {
width: 240px;
placeholder: "搜索实例";
text <=> root.query;
edited(t) => { root.query-changed(t); }
}
Rectangle { horizontal-stretch: 1; background: transparent; }
SegmentedControl {
width: 120px;
options: ["磁贴", "列表"];
selected: root.view == InstanceView.tiles ? 0 : 1;
changed(i) => {
root.view-changed(i == 0 ? InstanceView.tiles : InstanceView.list);
}
}
SegmentedControl {
width: 132px;
visible: root.view == InstanceView.tiles;
options: ["小", "中", "大"];
selected: root.tile-size == TileSize.small ? 0
: root.tile-size == TileSize.large ? 2 : 1;
changed(i) => {
root.tile-size-changed(
i == 0 ? TileSize.small : i == 2 ? TileSize.large : TileSize.medium);
}
}
MetroButton {
kind: ButtonKind.accent;
text: "新建实例";
show-icon: true;
icon: IconKind.plus;
clicked => { root.new-instance(); }
}
}
}
Rectangle { height: 1px; background: Theme.stroke; }
// ── 运行中迷你状态条 ──────────────────────────────────────
Rectangle {
height: Metrics.running-strip-height;
visible: root.running.length > 0;
background: Theme.state-info-soft;
HorizontalLayout {
padding-left: Metrics.gap-4;
padding-right: Metrics.gap-4;
spacing: Metrics.gap-2;
Rectangle {
width: 8px;
height: 8px;
border-radius: 4px;
background: Theme.state-success;
}
UiText {
role: TextRole.caption;
text: root.running.length + " 个实例运行中";
color: Theme.state-success;
horizontal-stretch: 1;
}
for r in root.running : UiText {
role: TextRole.caption;
text: r.name;
}
}
}
// ── 磁贴网格 ─────────────────────────────────────────────
body := Flickable {
vertical-stretch: 1;
visible: root.view == InstanceView.tiles && root.tiles.length > 0;
content-width: self.width;
content-height: grid-content.height;
grid-content := Rectangle {
width: body.width;
height: grid.preferred-height;
background: transparent;
grid := GridLayout {
padding: Metrics.gap-4;
spacing: Metrics.gap-3;
for t in root.tiles : InstanceTile {
row: t.row;
col: t.col;
data: t;
compact: root.tile-size == TileSize.small;
open => { root.open(t.id); }
launch => { root.launch(t.id); }
configure => { root.configure(t.id); }
stop => { root.stop(t.id); }
}
}
}
}
// ── 列表视图 ─────────────────────────────────────────────
ListView {
vertical-stretch: 1;
visible: root.view == InstanceView.list && root.tiles.length > 0;
for t in root.tiles : InstanceRow {
data: t;
open => { root.open(t.id); }
launch => { root.launch(t.id); }
}
}
// ── 空态 ────────────────────────────────────────────────
VerticalLayout {
vertical-stretch: 1;
visible: root.tiles.length == 0;
alignment: center;
spacing: Metrics.gap-3;
UiText {
role: TextRole.title;
text: "还没有实例";
horizontal-alignment: center;
color: Theme.fg-secondary;
}
UiText {
role: TextRole.caption;
text: "新建一个实例,或者先去下载页装个版本";
horizontal-alignment: center;
}
HorizontalLayout {
alignment: center;
MetroButton {
kind: ButtonKind.accent;
text: "新建实例";
show-icon: true;
icon: IconKind.plus;
clicked => { root.new-instance(); }
}
}
}
}
}
+289
View File
@@ -0,0 +1,289 @@
// G3 日志页(09 §2 P0·G3):左侧会话列表(✓正常 / ✗崩溃 / ⚠静默失败)+ 右侧日志流 + 崩溃诊断卡片。
// 诊断卡片 = D8 规则引擎结论(命中规则 + 置信度 + 修复动作 + 跳 AI 栏带上下文)。
import { Theme } from "../theme/tokens.slint";
import { Metrics } from "../theme/metrics.slint";
import { UiLogSession, UiLogLine, UiDiagnosis, LogOutcome, LogLevel } from "../models.slint";
import { UiText, TextRole } from "../components/label.slint";
import { AppIcon, IconKind } from "../components/icons.slint";
import { Badge } from "../components/badge.slint";
import { MetroButton, ButtonKind } from "../widgets/button.slint";
import { SearchField } from "../widgets/inputs.slint";
import { PageState } from "../page_state.slint";
import { ListView } from "std-widgets.slint";
component SessionRow inherits Rectangle {
in property <UiLogSession> data;
in property <bool> active: false;
callback clicked();
height: 52px;
background: root.active ? Theme.accent-soft : (row-touch.has-hover ? Theme.bg-hover : transparent);
if root.active : Rectangle {
x: 0;
y: 0;
width: 3px;
height: root.height;
background: Theme.accent;
}
HorizontalLayout {
padding-left: Metrics.gap-3;
padding-right: Metrics.gap-2;
spacing: Metrics.gap-2;
AppIcon {
icon-size: 14px;
kind: root.data.outcome == LogOutcome.ok ? IconKind.check
: root.data.outcome == LogOutcome.crashed ? IconKind.error
: IconKind.warn;
tint: root.data.outcome == LogOutcome.ok ? Theme.state-success
: root.data.outcome == LogOutcome.crashed ? Theme.state-danger
: Theme.state-warn;
}
VerticalLayout {
spacing: 0px;
vertical-stretch: 1;
UiText {
role: TextRole.body;
text: root.data.instance;
horizontal-stretch: 1;
}
UiText {
role: TextRole.caption;
text: root.data.when;
horizontal-stretch: 1;
}
}
UiText {
role: TextRole.caption;
text: root.data.duration;
width: 56px;
horizontal-alignment: right;
}
}
row-touch := TouchArea {
mouse-cursor: pointer;
clicked => { root.clicked(); }
}
}
component LogRow inherits Rectangle {
in property <UiLogLine> line;
height: 18px;
background: root.line.level == LogLevel.error
? Theme.state-danger-soft
: transparent;
UiText {
x: Metrics.gap-2;
y: 0;
width: root.width - 2 * Metrics.gap-2;
height: root.height;
role: TextRole.monoSmall;
text: root.line.text;
color: root.line.level == LogLevel.error ? Theme.state-danger
: root.line.level == LogLevel.warn ? Theme.state-warn
: root.line.level == LogLevel.debug ? Theme.fg-disabled
: Theme.fg-primary;
}
}
export component LogsPage inherits Rectangle {
in property <[UiLogSession]> sessions;
in property <[UiLogLine]> lines;
in property <UiDiagnosis> diagnosis;
in property <bool> has-diagnosis: false;
in property <bool> live: false;
in-out property <string> selected: "";
in-out property <string> query: "";
callback select(string);
callback ask-ai(string);
callback apply-fix();
callback query-changed(string);
/// 重新扫会话索引(手动刷新)
callback refresh();
background: Theme.bg-canvas;
HorizontalLayout {
// ── 左侧会话列表 ─────────────────────────────────────────
Rectangle {
width: 248px;
background: Theme.bg-surface;
VerticalLayout {
Rectangle {
height: 44px;
background: transparent;
HorizontalLayout {
padding-left: Metrics.gap-3;
padding-right: Metrics.gap-2;
spacing: Metrics.gap-2;
UiText {
role: TextRole.bodyStrong;
text: "会话";
horizontal-stretch: 1;
}
if root.live : Rectangle {
width: 6px;
height: 6px;
border-radius: 3px;
background: Theme.state-success;
}
}
}
ListView {
vertical-stretch: 1;
for s in root.sessions : SessionRow {
data: s;
active: root.selected == s.id;
clicked => { root.select(s.id); }
}
}
}
}
Rectangle { width: 1px; background: Theme.stroke; }
// ── 右侧日志流 + 诊断卡片 ────────────────────────────────
VerticalLayout {
vertical-stretch: 1;
// 会话头:标题 + 实时标记 + 筛选 + 刷新(第一行),崩溃日志导入(第二行)
Rectangle {
height: 84px;
background: Theme.bg-surface;
VerticalLayout {
HorizontalLayout {
height: 44px;
padding-left: Metrics.gap-3;
padding-right: Metrics.gap-3;
spacing: Metrics.gap-2;
UiText {
role: TextRole.bodyStrong;
text: root.selected == "" ? "未选择会话" : root.selected;
horizontal-stretch: 1;
overflow: elide;
}
if root.live : Badge { text: "实时"; tint: Theme.state-success; }
SearchField {
width: 140px;
placeholder: "筛选日志行";
text <=> root.query;
edited(t) => { root.query-changed(t); }
}
MetroButton {
kind: ButtonKind.subtle;
text: "刷新";
button-height: 26px;
clicked => { root.refresh(); }
}
}
// 崩溃日志导入分析(hs_err_pid*.log / crash-*.txt / latest.log)
HorizontalLayout {
height: 40px;
padding-left: Metrics.gap-3;
padding-right: Metrics.gap-3;
spacing: Metrics.gap-2;
SearchField {
horizontal-stretch: 1;
placeholder: "崩溃日志路径(如 .minecraft/crash-reports/crash-*.txt)";
text <=> PageState.crash-path;
}
MetroButton {
kind: ButtonKind.standard;
text: "导入分析";
button-height: 26px;
enabled: PageState.crash-path != "";
clicked => { PageState.crash-import(PageState.crash-path); }
}
}
}
}
Rectangle { height: 1px; background: Theme.stroke; }
// 崩溃诊断卡片(D8)
Rectangle {
height: root.has-diagnosis ? 76px : 0px;
visible: root.has-diagnosis;
background: Theme.state-danger-soft;
HorizontalLayout {
padding: Metrics.gap-3;
spacing: Metrics.gap-3;
AppIcon { kind: IconKind.error; icon-size: 20px; tint: Theme.state-danger; }
VerticalLayout {
spacing: 2px;
vertical-stretch: 1;
HorizontalLayout {
spacing: Metrics.gap-2;
UiText {
role: TextRole.bodyStrong;
text: root.diagnosis.title;
horizontal-stretch: 1;
}
Badge {
text: "置信度 " + root.diagnosis.confidence + "%";
tint: Theme.state-danger;
}
}
UiText {
role: TextRole.caption;
text: root.diagnosis.hint;
horizontal-stretch: 1;
}
}
VerticalLayout {
spacing: Metrics.gap-1;
alignment: center;
if root.diagnosis.has_fix : MetroButton {
kind: ButtonKind.standard;
text: "修复建议";
button-height: 26px;
clicked => { root.apply-fix(); }
}
MetroButton {
kind: ButtonKind.subtle;
text: "让 AI 详解";
button-height: 26px;
clicked => { root.ask-ai(root.selected); }
}
}
}
}
// 日志流(行虚拟化)
Rectangle {
vertical-stretch: 1;
background: Theme.bg-canvas;
ListView {
for l in root.lines : LogRow {
line: l;
width: root.width;
}
}
}
}
}
}
+201
View File
@@ -0,0 +1,201 @@
// 设置 → 账户(D9 / G6):三轨账户列表 + 离线添加 + 微软登录(授权码浏览器 / 设备码)+ 删除/切换。
//
// 凭据红线:这里能看到的只有「名字 / 轨道 / 公开 UUID / 登录名 / 创建时间」——
// token 与密码永远不进 UI(Rust 侧的 AccountView 不含这些字段)。
import { Theme } from "../theme/tokens.slint";
import { Metrics } from "../theme/metrics.slint";
import { AccountKind, UiAccount } from "../models.slint";
import { PageState } from "../page_state.slint";
import { UiText, TextRole } from "../components/label.slint";
import { Badge } from "../components/badge.slint";
import { AppIcon, IconKind } from "../components/icons.slint";
import { MetroButton, ButtonKind } from "../widgets/button.slint";
import { SearchField } from "../widgets/inputs.slint";
import { SettingGroup } from "../widgets/setting-item.slint";
component AccountRow inherits Rectangle {
in property <UiAccount> data;
callback activate();
callback remove();
height: 56px;
background: root.data.active ? Theme.accent-soft : transparent;
if root.data.active : Rectangle {
x: 0;
y: 0;
width: 3px;
height: root.height;
background: Theme.accent;
}
HorizontalLayout {
padding-left: Metrics.gap-3;
padding-right: Metrics.gap-3;
spacing: Metrics.gap-2;
AppIcon {
icon-size: 16px;
kind: root.data.kind == AccountKind.offline ? IconKind.instances : IconKind.ai;
tint: root.data.active ? Theme.accent : Theme.fg-secondary;
}
VerticalLayout {
spacing: 0px;
vertical-stretch: 1;
UiText { role: TextRole.bodyStrong; text: root.data.name; horizontal-stretch: 1; }
UiText {
role: TextRole.caption;
text: root.data.profile_id + (root.data.login_name == "" ? "" : " · " + root.data.login_name);
horizontal-stretch: 1;
overflow: elide;
}
}
Badge { text: root.data.kind_label; tint: Theme.fg-secondary; }
UiText { role: TextRole.caption; text: root.data.created; width: 108px; }
if !root.data.active : MetroButton {
kind: ButtonKind.standard;
text: "用这个";
button-height: 24px;
clicked => { root.activate(); }
}
if root.data.active : Badge { text: "启动中"; tint: Theme.accent; }
MetroButton {
kind: ButtonKind.subtle;
text: "删除";
button-height: 24px;
clicked => { root.remove(); }
}
}
}
export component AccountSection inherits Rectangle {
background: transparent;
VerticalLayout {
// 微软登录面板(设备码显示用户码;授权码显示可复制的授权链接;失败显示原因)
if PageState.device.visible : Rectangle {
height: PageState.device.busy ? 96px : 84px;
background: PageState.device.phase_label == "失败" ? Theme.state-danger-soft : Theme.accent-soft;
VerticalLayout {
padding: Metrics.gap-3;
spacing: Metrics.gap-1;
HorizontalLayout {
spacing: Metrics.gap-2;
AppIcon {
icon-size: 16px;
kind: PageState.device.phase_label == "失败" ? IconKind.error : IconKind.ai;
tint: PageState.device.phase_label == "失败" ? Theme.state-danger : Theme.accent;
}
UiText {
role: TextRole.bodyStrong;
text: "微软登录 · " + PageState.device.phase_label;
horizontal-stretch: 1;
}
if PageState.device.busy : MetroButton {
kind: ButtonKind.subtle;
text: "取消";
button-height: 24px;
clicked => { PageState.msa-cancel(); }
}
if !PageState.device.busy : MetroButton {
kind: ButtonKind.subtle;
text: "关闭";
button-height: 24px;
clicked => { PageState.msa-cancel(); }
}
}
if PageState.device.user_code != "" : HorizontalLayout {
spacing: Metrics.gap-3;
UiText {
role: TextRole.title;
text: PageState.device.user_code;
color: Theme.accent;
}
UiText {
role: TextRole.caption;
text: "在浏览器打开 " + PageState.device.verification_uri + " 输入上面的用户码";
horizontal-stretch: 1;
overflow: elide;
}
}
UiText {
role: TextRole.caption;
text: PageState.device.message;
horizontal-stretch: 1;
wrap: word-wrap;
}
if PageState.device.qr_link != "" : UiText {
role: TextRole.monoSmall;
text: PageState.device.qr_link;
horizontal-stretch: 1;
overflow: elide;
}
}
}
// 离线添加表单
SettingGroup { title: "添加离线账户"; }
HorizontalLayout {
padding-left: Metrics.gap-4;
padding-right: Metrics.gap-4;
spacing: Metrics.gap-2;
SearchField {
width: 220px;
text <=> PageState.offline-name;
placeholder: "玩家名(离线 UUID 自动生成)";
}
MetroButton {
kind: ButtonKind.standard;
text: "添加离线";
button-height: 28px;
enabled: PageState.offline-name != "";
clicked => { PageState.account-add-offline(PageState.offline-name); }
}
MetroButton {
kind: ButtonKind.accent;
text: "微软登录";
button-height: 28px;
enabled: !PageState.device.busy;
clicked => { PageState.msa-browser-begin(); }
}
MetroButton {
kind: ButtonKind.subtle;
text: "设备码登录";
button-height: 28px;
enabled: !PageState.device.busy;
clicked => { PageState.msa-begin(); }
}
}
// 账户列表
SettingGroup { title: "账户(" + PageState.accounts.length + ")"; }
if PageState.accounts.length == 0 : Rectangle {
height: 72px;
VerticalLayout {
alignment: center;
spacing: 2px;
UiText {
role: TextRole.caption;
text: "还没有账户:启动时会用「Player」离线占位。";
horizontal-alignment: center;
}
}
}
for account in PageState.accounts : AccountRow {
data: account;
activate => { PageState.account-switch(account.id); }
remove => { PageState.account-remove(account.id); }
}
UiText {
role: TextRole.caption;
x: Metrics.gap-4;
text: "凭据只存在 <数据根>/data/auth/(账号文件与凭据文件分离),界面与 AI 都拿不到 token。";
horizontal-stretch: 1;
wrap: word-wrap;
}
}
}
@@ -0,0 +1,146 @@
// 设置页「外观」分组(09 §2 G6 + §113 D17 定案):主题三态 + 「外观(高级)」P1 四件。
//
// 从 pages/settings.slint 拆出来只为守住 400 行门槛;控件语义与设置键一一对应:
// theme-mode → appearance.theme_mode / bg-* → appearance.bg_image* / panel-alpha →
// appearance.panel_alpha / glass → appearance.glass / accent-color → appearance.accent_color。
// 09 §111 硬约束:高级特性全部默认关闭,开启背景图时这里会给出对比度提示(warn 行)。
import { Theme } from "../theme/tokens.slint";
import { ThemeMode, BgFillKind } from "../models.slint";
import { SettingItem, SettingGroup, ControlKind } from "../widgets/setting-item.slint";
export component AppearanceSection inherits VerticalLayout {
in-out property <ThemeMode> theme-mode: ThemeMode.auto;
in-out property <bool> bg-image-enabled: false;
in-out property <string> bg-image: "";
in-out property <BgFillKind> bg-fill: BgFillKind.cover;
in-out property <float> bg-dim: 0.35;
in-out property <float> panel-alpha: 1.0;
in-out property <bool> glass: false;
in-out property <string> accent-color: "";
in property <bool> accent-invalid: false;
callback theme-changed(ThemeMode);
callback bg-image-enabled-changed(bool);
callback bg-image-changed(string);
callback bg-fill-changed(BgFillKind);
callback bg-dim-changed(float);
callback panel-alpha-changed(float);
callback glass-changed(bool);
callback accent-color-changed(string);
callback accent-swatch-picked(int);
// ── 外观(D17:主题三态 + 09 §101-104 高级四件)──────
SettingGroup { title: "主题"; }
SettingItem {
label: "主题模式";
description: "跟随系统(Auto,随 OS 热切)/ 浅色 / 深色";
kind: ControlKind.segmented;
options: ["跟随系统", "浅色", "深色"];
selected: root.theme-mode == ThemeMode.auto ? 0
: root.theme-mode == ThemeMode.light ? 1 : 2;
changed(i) => {
root.theme-mode = i == 0 ? ThemeMode.auto
: i == 1 ? ThemeMode.light : ThemeMode.dark;
root.theme-changed(root.theme-mode);
}
}
SettingGroup {
title: "高级特性(出厂全部关闭,开启后请确认前景可读)";
}
// ① 自定义窗口背景图(09 §101)
SettingItem {
label: "自定义窗口背景图";
description: "PNG 图片铺满窗口;开启后所有面板半透明处都会透出它";
kind: ControlKind.toggle;
checked: root.bg-image-enabled;
toggled(v) => {
root.bg-image-enabled = v;
root.bg-image-enabled-changed(v);
}
}
SettingItem {
label: "背景图路径";
description: root.bg-image == "" ? "尚未选择(绝对路径,例:D:/wall/p.png)" : root.bg-image;
kind: ControlKind.text;
button-text: "D:/wall/p.png";
value <=> root.bg-image;
edited(t) => { root.bg-image-changed(t); }
}
SettingItem {
label: "填充模式";
description: "铺满(裁切)/ 完整(留底色)/ 平铺";
kind: ControlKind.segmented;
options: ["铺满", "完整", "平铺"];
selected: root.bg-fill == BgFillKind.cover ? 0
: root.bg-fill == BgFillKind.contain ? 1 : 2;
changed(i) => {
root.bg-fill = i == 0 ? BgFillKind.cover
: i == 1 ? BgFillKind.contain : BgFillKind.tile;
root.bg-fill-changed(root.bg-fill);
}
}
SettingItem {
label: "暗化层强度";
// 09 §111:开启背景图时自动给对比度提示——暗化太低 + 面板太透会伤可读性
description: (root.bg-image-enabled && (root.bg-dim < 0.25 || root.panel-alpha < 0.8))
? "⚠ 对比度偏低:建议暗化 ≥ 25% 或面板不透明度 ≥ 80%"
: "越大背景越暗、前景越清晰(0% = 不压暗)";
warn: root.bg-image-enabled && (root.bg-dim < 0.25 || root.panel-alpha < 0.8);
kind: ControlKind.slider;
min-value: 0.0;
max-value: 0.9;
slider-value: root.bg-dim;
slid(v) => {
root.bg-dim = v;
root.bg-dim-changed(v);
}
}
// ② 全局面板半透明度(09 §102)
SettingItem {
label: "全局面板半透明度";
description: "卡片 / 导航 / AI 栏统一透明度(100% = 出厂 Metro 纯色)";
kind: ControlKind.slider;
min-value: 0.4;
max-value: 1.0;
slider-value: root.panel-alpha;
slid(v) => {
root.panel-alpha = v;
root.panel-alpha-changed(v);
}
}
// ③ 玻璃拟态卡片(09 §103)
SettingItem {
label: "玻璃拟态卡片";
description: "卡片底铺背景图的预生成模糊层(需先开启背景图)";
kind: ControlKind.toggle;
checked: root.glass;
toggled(v) => {
root.glass = v;
root.glass-changed(v);
}
}
// ④ 自定义强调色(09 §104)
SettingItem {
label: "预设强调色";
description: root.accent-color == "" ? "当前:出厂 Metro 蓝" : "当前:" + root.accent-color;
kind: ControlKind.swatches;
swatch-colors: [
#0078D4, #0F6CBD, #107C10, #9D5D00,
#C42B1C, #8764B8, #C239B3, #00B7C3,
];
swatch-current: root.accent-color == "" ? #0078D4 : Theme.accent;
swatch-picked(i) => { root.accent-swatch-picked(i); }
}
SettingItem {
label: "强调色(十六进制)";
description: root.accent-invalid
? "⚠ 需要 #RRGGBB 形式,留空恢复出厂蓝"
: "留空 = 出厂 #0078D4;影响顶线 / 选中态 / 按钮 / 徽标";
warn: root.accent-invalid;
kind: ControlKind.text;
button-text: "#0078D4";
value <=> root.accent-color;
edited(t) => { root.accent-color-changed(t); }
}
}
+117
View File
@@ -0,0 +1,117 @@
// 设置页右栏的「schema 分组」渲染器:下载 / Java / 数据 / AI / MCP 五组。
//
// 行来自 Rust 侧的 `SettingGroupView`(core::setting schema 的键),这里只按控件类型画:
// 开关 → toggle;分段 → segmented;文本 → text;只读 → 纯文案;动作 → 按钮。
// 写回一律 `PageState.setting-changed(key, value)` / `setting-action(action)`,
// 由 host 走 `SettingStore::set`(同源 launcher.json)。
import { Theme } from "../theme/tokens.slint";
import { Metrics } from "../theme/metrics.slint";
import { UiSettingGroup, UiSettingRow, SettingControl } from "../models.slint";
import { PageState } from "../page_state.slint";
import { UiText, TextRole } from "../components/label.slint";
import { SettingItem, SettingGroup, ControlKind } from "../widgets/setting-item.slint";
component RowView inherits Rectangle {
in property <UiSettingRow> item;
/// 文本编辑的本地缓冲(编辑中不回写模型,失焦/回车才提交)
in-out property <string> draft;
height: root.item.control == SettingControl.readonly ? 48px
: root.item.description == "" ? 44px : 58px;
background: transparent;
init => { self.draft = root.item.value_text; }
changed item => { self.draft = root.item.value_text; }
if root.item.control == SettingControl.toggle : SettingItem {
label: root.item.label;
description: root.item.description;
kind: ControlKind.toggle;
checked: root.item.flag;
toggled(v) => { PageState.setting-changed(root.item.key, v ? "true" : "false"); }
}
if root.item.control == SettingControl.choice : SettingItem {
label: root.item.label;
description: root.item.description;
kind: ControlKind.segmented;
options: root.item.options;
selected: root.item.selected;
changed(i) => {
if (i >= 0 && i < root.item.options.length) {
PageState.setting-changed(root.item.key, root.item.options[i]);
}
}
}
if root.item.control == SettingControl.text : SettingItem {
label: root.item.label;
description: root.item.description;
kind: ControlKind.text;
value: root.draft;
edited(t) => {
root.draft = t;
PageState.setting-changed(root.item.key, t);
}
}
if root.item.control == SettingControl.action : SettingItem {
label: root.item.label;
description: root.item.description;
kind: ControlKind.button;
button-text: "执行";
clicked => { PageState.setting-action(root.item.action); }
}
if root.item.control == SettingControl.readonly : HorizontalLayout {
padding-left: Metrics.gap-4;
padding-right: Metrics.gap-4;
spacing: Metrics.gap-3;
UiText {
role: TextRole.body;
text: root.item.label;
width: 200px;
}
UiText {
role: TextRole.caption;
text: root.item.description;
horizontal-stretch: 1;
wrap: word-wrap;
}
}
}
export component SettingsGroups inherits Rectangle {
in property <string> tab: "";
background: transparent;
VerticalLayout {
padding-bottom: Metrics.gap-5;
if PageState.setting-groups.length == 0 : Rectangle {
height: 120px;
VerticalLayout {
alignment: center;
spacing: 2px;
UiText {
role: TextRole.caption;
text: "设置还没加载出来(host 未就绪或数据根不可写)";
horizontal-alignment: center;
}
}
}
for group in PageState.setting-groups : Rectangle {
visible: group.id == root.tab;
height: group.rows.length * 58px + 40px;
background: transparent;
VerticalLayout {
SettingGroup { title: group.title; }
for setting-row in group.rows : RowView { item: setting-row; }
}
}
}
}
+280
View File
@@ -0,0 +1,280 @@
// G6 设置页(09 §2 P0·G6):左子导航 + 右表单 + 搜索框(设置项多,防迷路)。
// 「通用 / 外观(D17 三态 + 09 §101-104 高级四件,实现在 settings-appearance.slint)/ 数据 / AI /
// 关于」是真实控件,Java / 账户 / 代理分组仍是入口按钮(等各自 mission 接实现)。
// 关于页内嵌 AboutSlint —— Slint RF 2.0 许可义务(01c §8:关于页放官方部件)。
import { Theme } from "../theme/tokens.slint";
import { Metrics } from "../theme/metrics.slint";
import { SettingSection, TileSize, ThemeMode, BgFillKind } from "../models.slint";
import { UiText, TextRole } from "../components/label.slint";
import { SettingItem, SettingGroup, ControlKind } from "../widgets/setting-item.slint";
import { AppearanceSection } from "settings-appearance.slint";
import { AccountSection } from "settings-account.slint";
import { SettingsGroups } from "settings-groups.slint";
import { PageState } from "../page_state.slint";
import { SearchField } from "../widgets/inputs.slint";
import { AboutSlint, ScrollView } from "std-widgets.slint";
component SectionRow inherits Rectangle {
in property <string> label: "";
in property <SettingSection> section: SettingSection.general;
in property <bool> active: false;
callback clicked();
height: 36px;
background: root.active ? Theme.accent-soft : (row-touch.has-hover ? Theme.bg-hover : transparent);
if root.active : Rectangle {
x: 0;
y: 0;
width: 3px;
height: root.height;
background: Theme.accent;
}
UiText {
x: Metrics.gap-3;
y: 0;
width: root.width - Metrics.gap-3 - Metrics.gap-2;
height: root.height;
role: TextRole.body;
text: root.label;
color: root.active ? Theme.accent : Theme.fg-primary;
}
row-touch := TouchArea {
mouse-cursor: pointer;
clicked => { root.clicked(); }
}
}
export component SettingsPage inherits Rectangle {
in-out property <SettingSection> section: SettingSection.general;
in-out property <string> query: "";
// 外观(D17:主题三态 + 09 §101-104 高级特性四件)
in-out property <ThemeMode> theme-mode: ThemeMode.auto;
in-out property <bool> bg-image-enabled: false;
in-out property <string> bg-image: "";
in-out property <BgFillKind> bg-fill: BgFillKind.cover;
in-out property <float> bg-dim: 0.35;
in-out property <float> panel-alpha: 1.0;
in-out property <bool> glass: false;
in-out property <string> accent-color: "";
in property <bool> accent-invalid: false;
// 通用
in-out property <TileSize> tile-size: TileSize.medium;
in-out property <bool> close-to-tray: true;
// 数据
in-out property <int> data-mode: 0;
in property <string> data-path: "";
// 下载
in-out property <int> download-concurrency: 1;
in property <string> region-label: "未知";
// 账户
in property <string> account-label: "未登录";
// AI
in-out property <int> ai-endpoint: 0;
in-out property <bool> yolo: false;
in-out property <bool> mcp-enabled: false;
// 代理
in-out property <string> proxy: "";
// 关于
in property <string> version: "";
callback section-changed(SettingSection);
callback query-changed(string);
callback theme-changed(ThemeMode);
callback bg-image-enabled-changed(bool);
callback bg-image-changed(string);
callback bg-fill-changed(BgFillKind);
callback bg-dim-changed(float);
callback panel-alpha-changed(float);
callback glass-changed(bool);
callback accent-color-changed(string);
callback accent-swatch-picked(int);
callback tile-size-changed(TileSize);
callback close-to-tray-changed(bool);
callback data-mode-changed(int);
callback download-concurrency-changed(int);
callback ai-endpoint-changed(int);
callback yolo-changed(bool);
callback mcp-changed(bool);
callback proxy-changed(string);
callback migrate-data();
callback login();
callback manage-java();
callback open-audit-log();
callback detect-region();
background: Theme.bg-canvas;
HorizontalLayout {
// ── 左子导航 ──────────────────────────────────────────────
Rectangle {
width: 200px;
background: Theme.bg-surface;
VerticalLayout {
padding-top: Metrics.gap-2;
SectionRow { label: "通用"; section: SettingSection.general; active: root.section == SettingSection.general; clicked => { root.section-changed(SettingSection.general); } }
SectionRow { label: "外观"; section: SettingSection.appearance; active: root.section == SettingSection.appearance; clicked => { root.section-changed(SettingSection.appearance); } }
SectionRow { label: "数据"; section: SettingSection.data; active: root.section == SettingSection.data; clicked => { root.section-changed(SettingSection.data); } }
SectionRow { label: "下载"; section: SettingSection.download; active: root.section == SettingSection.download; clicked => { root.section-changed(SettingSection.download); } }
SectionRow { label: "Java"; section: SettingSection.java; active: root.section == SettingSection.java; clicked => { root.section-changed(SettingSection.java); } }
SectionRow { label: "账户"; section: SettingSection.account; active: root.section == SettingSection.account; clicked => { root.section-changed(SettingSection.account); } }
SectionRow { label: "AI"; section: SettingSection.ai; active: root.section == SettingSection.ai; clicked => { root.section-changed(SettingSection.ai); } }
SectionRow { label: "代理"; section: SettingSection.proxy; active: root.section == SettingSection.proxy; clicked => { root.section-changed(SettingSection.proxy); } }
SectionRow { label: "关于"; section: SettingSection.about; active: root.section == SettingSection.about; clicked => { root.section-changed(SettingSection.about); } }
Rectangle { vertical-stretch: 1; background: transparent; }
}
}
Rectangle { width: 1px; background: Theme.stroke; }
// ── 右表单 ────────────────────────────────────────────────
VerticalLayout {
vertical-stretch: 1;
Rectangle {
height: Metrics.page-header-height;
background: Theme.bg-surface;
HorizontalLayout {
padding-left: Metrics.gap-4;
padding-right: Metrics.gap-4;
spacing: Metrics.gap-3;
UiText {
role: TextRole.subtitle;
text: "设置";
width: 72px;
}
SearchField {
width: 240px;
placeholder: "搜索设置项";
text <=> root.query;
edited(t) => { root.query-changed(t); }
}
Rectangle { horizontal-stretch: 1; background: transparent; }
}
}
Rectangle { height: 1px; background: Theme.stroke; }
ScrollView {
vertical-stretch: 1;
VerticalLayout {
padding-bottom: Metrics.gap-5;
// 外观(D17:主题三态 + 09 §101-104 高级四件;实现在 settings-appearance.slint)
if root.section == SettingSection.appearance : AppearanceSection {
theme-mode: root.theme-mode;
bg-image-enabled: root.bg-image-enabled;
bg-image: root.bg-image;
bg-fill: root.bg-fill;
bg-dim: root.bg-dim;
panel-alpha: root.panel-alpha;
glass: root.glass;
accent-color: root.accent-color;
accent-invalid: root.accent-invalid;
theme-changed(m) => {
root.theme-mode = m;
root.theme-changed(m);
}
bg-image-enabled-changed(v) => {
root.bg-image-enabled = v;
root.bg-image-enabled-changed(v);
}
bg-image-changed(t) => {
root.bg-image = t;
root.bg-image-changed(t);
}
bg-fill-changed(f) => {
root.bg-fill = f;
root.bg-fill-changed(f);
}
bg-dim-changed(v) => {
root.bg-dim = v;
root.bg-dim-changed(v);
}
panel-alpha-changed(v) => {
root.panel-alpha = v;
root.panel-alpha-changed(v);
}
glass-changed(v) => {
root.glass = v;
root.glass-changed(v);
}
accent-color-changed(t) => {
root.accent-color = t;
root.accent-color-changed(t);
}
accent-swatch-picked(i) => { root.accent-swatch-picked(i); }
}
// ── 通用 ────────────────────────────────────────
if root.section == SettingSection.general : SettingGroup { title: "外观"; }
if root.section == SettingSection.general : SettingItem {
label: "磁贴尺寸";
description: "小 6 列 / 中 4 列(默认)/ 大 3 列";
kind: ControlKind.segmented;
options: ["小", "中", "大"];
selected: root.tile-size == TileSize.small ? 0
: root.tile-size == TileSize.large ? 2 : 1;
changed(i) => {
root.tile-size-changed(
i == 0 ? TileSize.small : i == 2 ? TileSize.large : TileSize.medium);
}
}
if root.section == SettingSection.general : SettingItem {
label: "关闭窗口时最小化到托盘";
description: "避免误关正在运行的游戏";
kind: ControlKind.toggle;
checked: root.close-to-tray;
toggled(v) => { root.close-to-tray-changed(v); }
}
// ── 数据 / 下载 / Java / 账户 / AI / MCP ────────────
// 行来自 core::setting schema(`PageState.setting-groups`),写回走
// `SettingStore::set`——与 `ull config set` / AI `set_config` 同一份 launcher.json。
if root.section == SettingSection.data : SettingsGroups { tab: "data"; }
if root.section == SettingSection.download : SettingsGroups { tab: "download"; }
if root.section == SettingSection.java : SettingsGroups { tab: "java"; }
if root.section == SettingSection.ai : SettingsGroups { tab: "ai"; }
if root.section == SettingSection.ai : SettingsGroups { tab: "mcp"; }
if root.section == SettingSection.account : AccountSection { }
// ── 代理 ────────────────────────────────────────
if root.section == SettingSection.proxy : SettingGroup { title: "代理"; }
if root.section == SettingSection.proxy : SettingItem {
label: "HTTP 代理";
description: "留空则使用系统代理";
kind: ControlKind.text;
button-text: "http://127.0.0.1:7890";
value <=> root.proxy;
edited(t) => { root.proxy-changed(t); }
}
// ── 关于 ────────────────────────────────────────
if root.section == SettingSection.about : SettingGroup { title: "关于"; }
if root.section == SettingSection.about : SettingItem {
label: "版本";
description: root.version;
}
if root.section == SettingSection.about : SettingItem {
label: "许可";
description: "Apache-2.0(Slint 走 RF 2.0 轨道)";
}
if root.section == SettingSection.about : Rectangle {
height: 120px;
AboutSlint { }
}
}
}
}
}
}