This commit is contained in:
me
2026-01-30 20:32:37 +08:00
parent 222449cb1b
commit a4e6557d1b
24 changed files with 2189 additions and 6235 deletions

View File

@@ -1,130 +1,57 @@
import { Theme, SpacingSystem, Typography } from "../theme/theme.slint";
// Connections Page - Display active connections list
import { Theme, Typography, SpacingSystem } from "../theme/theme.slint";
import { Item } from "../components/item.slint";
import { Empty } from "../components/empty.slint";
import { Connection } from "../types.slint";
export struct Connection {
source: string,
destination: string,
protocol: string,
upload: string,
download: string,
duration: string,
}
export component ConnectionsPage inherits Rectangle {
in property <[Connection]> connections;
callback load-connections();
background: Theme.colors.background;
export component ConnectionsPage {
in property <[Connection]> connections: [];
VerticalLayout {
padding: SpacingSystem.spacing.s4;
spacing: SpacingSystem.spacing.s4;
// Header
Text {
text: "Connections";
color: Theme.colors.foreground;
font-size: Typography.sizes.xl;
font-weight: Typography.weights.bold;
}
// Connections List
ScrollView {
VerticalLayout {
spacing: SpacingSystem.spacing.s2;
for connection in root.connections: ConnectionItem {
connection-data: connection;
}
}
}
}
}
// Fixed header
Rectangle {
height: 48px;
background: Theme.colors.background;
HorizontalLayout {
padding: SpacingSystem.spacing.s4;
component ConnectionItem {
in property <Connection> connection-data;
private property <bool> hovered: false;
height: 60px;
states [
hovered when root.hovered: {
container.background: Theme.colors.muted.transparentize(0.5);
}
]
container := Rectangle {
background: transparent;
border-radius: SpacingSystem.radius.md;
border-width: 1px;
border-color: Theme.colors.border;
animate background { duration: 150ms; }
HorizontalLayout {
padding: SpacingSystem.spacing.s3;
spacing: SpacingSystem.spacing.s3;
alignment: space-between;
// Icon
Rectangle {
width: 40px;
height: 40px;
border-radius: SpacingSystem.radius.md;
background: Theme.colors.primary.transparentize(0.9);
Text {
text: "🔗";
font-size: Typography.sizes.lg;
horizontal-alignment: center;
text: "Connections";
font-size: Typography.sizes.xl;
font-weight: Typography.weights.bold;
color: Theme.colors.foreground;
vertical-alignment: center;
}
}
// Connection Info
VerticalLayout {
spacing: SpacingSystem.spacing.s1;
Text {
text: root.connection-data.source + " → " + root.connection-data.destination;
color: Theme.colors.foreground;
font-size: Typography.sizes.sm;
font-weight: Typography.weights.medium;
overflow: elide;
}
HorizontalLayout {
spacing: SpacingSystem.spacing.s3;
Text {
text: root.connection-data.protocol;
color: Theme.colors.muted-foreground;
font-size: Typography.sizes.xs;
}
Text {
text: "↑ " + root.connection-data.upload;
color: Theme.colors.primary;
font-size: Typography.sizes.xs;
}
Text {
text: "↓ " + root.connection-data.download;
color: Theme.colors.secondary;
font-size: Typography.sizes.xs;
}
}
}
// Duration
Text {
text: root.connection-data.duration;
color: Theme.colmuted-foreground;
font-size: Typography.sizes.xs;
vertical-alignment: center;
}
}
touch := TouchArea {
moved => {
root.hovered = self.has-hover;
// Scrollable content
Flickable {
viewport-height: content-layout.preferred-height + SpacingSystem.spacing.s4 * 2;
content-layout := VerticalLayout {
padding: SpacingSystem.spacing.s4;
spacing: SpacingSystem.spacing.s2;
// Empty state
if connections.length == 0: Empty {
height: 300px;
icon: "🔗";
title: "No Active Connections";
description: "Connections will appear here when active";
}
// Connection list
for connection in connections: Item {
icon: "✓";
title: connection.host;
description: "Process: " + connection.process + " | Rule: " + connection.rule + " | ↑ " + connection.upload + " ↓ " + connection.download;
}
}
}
}

View File

@@ -1,519 +1,380 @@
import { Theme, SpacingSystem, Typography } from "../theme/theme.slint";
import { Card } from "../components/card.slint";
// Home Page - Dashboard with profile info and quick settings
import { Theme, Typography, SpacingSystem } from "../theme/theme.slint";
import { Container } from "../layouts/container.slint";
import { Badge } from "../components/badge.slint";
import { Button } from "../components/button.slint";
import { Switch } from "../components/switch.slint";
import { Label, LabelSize, LabelColor } from "../components/label.slint";
import { Progress } from "../components/progress.slint";
import { Profile, SettingItem } from "../types.slint";
export component HomePage inherits Rectangle {
in property <Profile> current-profile;
in property <[SettingItem]> quick-settings;
callback load-home-data();
callback toggle-setting(string /* key */, bool /* value */);
background: Theme.colors.background;
export component HomePage {
callback refresh-profile();
callback toggle-tun-mode(bool);
callback toggle-system-proxy(bool);
callback open-port-settings();
in property <string> profile-name: "NanoCloud";
in property <string> profile-node: "免费-日本1-Ver.7";
in property <float> profile-usage: 1.26;
in property <float> profile-total: 100;
in property <string> profile-expiry: "2025-11-11";
in property <string> profile-updated: "2025-10-12 10:05";
in property <string> location-ip: "47.238.198.100";
in property <string> location-region: "日本 · 东京";
in property <bool> tun-mode-enabled: false;
in property <bool> system-proxy-enabled: false;
in property <int> proxy-port: 7890;
VerticalLayout {
padding: SpacingSystem.spacing.s4;
spacing: SpacingSystem.spacing.s4;
// Header
Text {
text: "Home";
color: Theme.colors.foreground;
font-size: Typography.sizes.xl;
font-weight: Typography.weights.bold;
// Fixed header
Rectangle {
height: 48px;
background: Theme.colors.background;
HorizontalLayout {
padding: SpacingSystem.spacing.s4;
Text {
text: "Home";
font-size: Typography.sizes.xl;
font-weight: Typography.weights.bold;
color: Theme.colors.foreground;
vertical-alignment: center;
}
}
}
ScrollView {
VerticalLayout {
spacing: SpacingSystem.spacing.s3;
// Scrollable content
Flickable {
viewport-height: content-layout.preferred-height + SpacingSystem.spacing.s4 * 2;
content-layout := VerticalLayout {
padding: SpacingSystem.spacing.s4;
spacing: SpacingSystem.spacing.s4;
// Profile Card
ProfileCard {
profile-name: root.profile-name;
profile-node: root.profile-node;
usage: root.profile-usage;
total: root.profile-total;
expiry: root.profile-expiry;
updated: root.profile-updated;
refresh => {
root.refresh-profile();
}
}
// Location Card
LocationCard {
ip: root.location-ip;
region: root.location-region;
}
// Settings Card
gsCard {
tun-enabled: root.tun-mode-enabled;
proxy-enabled: root.system-proxy-enabled;
port: root.proxy-port;
tun-toggled(enabled) => {
root.toggle-tun-mode(enabled);
}
proxy-toggled(enabled) => {
root.toggle-system-proxy(enabled);
}
port-clicked => {
root.open-port-settings();
}
}
}
}
}
}
Container {
VerticalLayout {
padding: SpacingSystem.spacing.s4;
spacing: SpacingSystem.spacing.s4;
component ProfileCard {
in property <string> profile-name: "";
in property <string> profile-node: "";
in property <float> usage: 0;
in property <float> total: 100;
in property <string> expiry: "";
in property <string> updated: "";
callback refresh();
Card {
VerticalLayout {
spacing: SpacingSystem.spacing.s4;
// Header
HorizontalLayout {
spacing: SpacingSystem.spacing.s3;
padding-bottom: SpacingSystem.ss4;
// Icon
Rectangle {
width: 48px;
height: 48px;
border-radius: 12px;
background: Theme.colors.primary.transparentize(0.9);
Text {
text: "☁";
color: Theme.colors.primary;
font-size: 24px;
horizontal-alignment: center;
vertical-alignment: center;
// Header with icon and name
HorizontalLayout {
spacing: SpacingSystem.spacing.s3;
alignment: space-between;
HorizontalLayout {
spacing: SpacingSystem.spacing.s3;
// Icon
Rectangle {
width: 48px;
height: 48px;
background: Theme.colors.primary;
border-radius: SpacingSystem.radius.lg;
Text {
text: "☁";
font-size: 28px;
horizontal-alignment: center;
vertical-alignment: center;
}
}
// Profile info
VerticalLayout {
spacing: SpacingSystem.spacing.s1;
Text {
text: current-profile.name;
font-size: Typography.sizes.lg;
font-weight: Typography.weights.bold;
color: Theme.colors.foreground;
}
HorizontalLayout {
spacing: SpacingSystem.spacing.s2;
Text {
text: "🌍";
font-size: Typography.sizes.xs;
}
Text {
text: current-profile.type;
font-size: Typography.sizes.xs;
color: Theme.colors.muted-foreground;
}
Badge {
text: "Vmess";
variant: "outline";
}
Badge {
text: "UDP";
variant: "outline";
}
}
}
}
// Refresh button
Button {
text: "🔄";
variant: "ghost";
size: "sm";
}
}
// Divider
Rectangle {
height: 1px;
background: Theme.colors.border;
}
// Stats grid
HorizontalLayout {
spacing: SpacingSystem.spacing.s6;
// Usage
VerticalLayout {
spacing: SpacingSystem.spacing.s1;
horizontal-stretch: 1;
HorizontalLayout {
spacing: SpacingSystem.spacing.s2;
Text {
text: "☁";
font-size: Typography.sizes.xs;
color: Theme.colors.muted-foreground;
}
Text {
text: "已使用 / 总量";
font-size: Typography.sizes.xs;
color: Theme.colors.muted-foreground;
}
}
Text {
text: current-profile.usage;
font-size: Typography.sizes.sm;
font-weight: Typography.weights.medium;
color: Theme.colors.foreground;
}
}
// Expire date
VerticalLayout {
spacing: SpacingSystem.spacing.s1;
horizontal-stretch: 1;
HorizontalLayout {
spacing: SpacingSystem.spacing.s2;
Text {
text: "✓";
font-size: Typography.sizes.xs;
color: Theme.colors.muted-foreground;
}
Text {
text: "到期时间";
font-size: Typography.sizes.xs;
color: Theme.colors.muted-foreground;
}
}
Text {
text: current-profile.expire-at;
font-size: Typography.sizes.sm;
font-weight: Typography.weights.medium;
color: Theme.colors.foreground;
}
}
// Updated date
VerticalLayout {
spacing: SpacingSystem.spacing.s1;
horizontal-stretch: 1;
HorizontalLayout {
spacing: SpacingSystem.spacing.s2;
Text {
text: "🕐";
font-size: Typography.sizes.xs;
color: Theme.colors.muted-foreground;
}
Text {
text: "更新时间";
font-size: Typography.sizes.xs;
color: Theme.colors.muted-foreground;
}
}
Text {
text: current-profile.updated-at;
font-size: Typography.sizes.sm;
font-weight: Typography.weights.medium;
color: Theme.colors.foreground;
}
}
}
}
}
// Info
VerticalLayout {
spacing: SpacingSystem.spacing.s1;
HorizontalLayout {
spacing: SpacingSystem.spacing.s2;
// Location Card (placeholder)
Container {
VerticalLayout {
padding: SpacingSystem.spacing.s4;
spacing: SpacingSystem.spacing.s3;
Text {
text: root.profile-name;
text: "位置信息";
font-size: Typography.sizes.base;
font-weight: Typography.weights.semibold;
color: Theme.colors.foreground;
font-size: Typography.sizes.lg;
font-weight: Typography.weights.bold;
vertical-alignment: center;
}
}
HorizontalLayout {
spacing: SpacingSystem.spacing.s2;
Text {
text: "🌐";
font-size: Typography.sizes.xs;
vertical-alignment: center;
}
Text {
text: root.profile-node;
color: Theme.colors.muted-foreground;
font-size: Typography.sizes.xs;
font-weight: Typography.weights.medium;
vertical-alignment: center;
}
Badge {
text: "Vmess";
variant: "outline";
}
Badge {
text: "UDP";
variant: "outline";
}
}
}
Rectan horizontal-stretch: 1;
}
// Refresh button
Button {
width: 32px;
height: 32px;
variant: "ghost";
text: "↻";
clicked => {
root.refresh();
}
}
}
// Separator
Rectangle {
height: 1px;
background: Theme.colors.border.transparentize(0.6);
}
// Stats
HorizontalLayout {
spacing: SpacingSystem.spacing.s6;
VerticalLayout {
spacing: SpacingSystem.spacing.s1;
HorizontalLayout {
spacing: SpacingSystem.spacing.s1;
Text {
text: "☁";
color: Theme.colors.muted-foreground;
font-size: Typography.sizes.xs;
vertical-alignment: center;
}
Text {
text: "已使用 / 总量";
color: Theme.colors.muted-foreground;
font-size: Typography.sizes.xs;
font-weight: Typography.weights.semibold;
vertical-alignment: center;
}
}
Text {
text: root.usage + "GB / " + root.total + "GB";
color: Theme.colors.foreground;
font-size: Typography.sizes.sm;
font-weight: Typography.weights.medium;
}
}
VerticalLayout {
spacing: SpacingSystem.spacing.s1;
HorizontalLayout {
spacing: SpacingSystem.spacing.s1;
Text {
text: "✓";
color: Theme.colors.muted-foreground;
font-size: Typography.sizes.xs;
vertical-aliter;
}
Text {
text: "到期时间";
color: Theme.colors.muted-foreground;
font-size: Typography.sizes.xs;
font-weight: Typography.weights.semibold;
vertical-alignment: center;
}
}
Text {
text: root.expiry;
color: Theme.colors.foreground;
font-size: Typography.sizes.sm;
font-weight: Typography.weights.medium;
}
}
VerticalLayout {
spacing: SpacingSystem.spacing.s1;
HorizontalLayout {
spacing: SpacingSystem.spacing.s1;
Text {
text: "✓";
color: Theme.colors.muted-foreground;
font-size: Typography.sizes.xs;
vertical-: center;
n
Text {
text: "更新时间";
color: Theme.colors.muted-foreground;
font-size: Typography.sizes.xs;
font-weight: Typography.weights.semibold;
vertical-alignment: center;
}
}
Text {
text: root.updated;
color: Theme.colors.foreground;
font-size: Typography.sizes.sm;
font-weight: Typography.weights.medium;
}
}
}
}
}
}
component LocationCard {
in property <string> ip: "";
in property <string> region: "";
Card {
VerticalLayout {
spacing: SpacingSystem.spacing.s4;
// IP and Location
HorizontalLayout {
spacing: SpacingSystem.spacing.s6;
VerticalLayout {
spacing: SpacingSystem.spacing.s1;
HorizontalLayout {
spacing: SpacingSystem.spacing.s1;
Text {
text: "🌐";
font-size: Typography.sizes.xs;
vertical-alignment: center;
HorizontalLayout {
spacing: SpacingSystem.spacing.s3;
Text {
text: "🌍";
font-size: 32px;
}
VerticalLayout {
spacing: SpacingSystem.spacing.s1;
Text {
text: "当前位置: 日本 东京";
font-size: Typography.sizes.sm;
color: Theme.colors.foreground;
}
Text {
text: "IP: 192.168.1.1";
font-size: Typography.sizes.xs;
color: Theme.colors.muted-foreground;
}
}
}
}
}
// Quick Settings Card
Container {
VerticalLayout {
padding: SpacingSystem.spacing.s4;
spacing: SpacingSystem.spacing.s3;
Text {
text: "IP";
color: Theme.colors.muted-foreground;
font-size: Typography.sizes.xs;
text: "快捷设置";
font-size: Typography.sizes.base;
font-weight: Typography.weights.semibold;
vertical-alignment: center;
color: Theme.colors.foreground;
}
// Quick settings grid
HorizontalLayout {
spacing: SpacingSystem.spacing.s3;
// System Proxy
Rectangle {
horizontal-stretch: 1;
height: 80px;
background: Theme.colors.muted;
border-radius: SpacingSystem.radius.md;
VerticalLayout {
padding: SpacingSystem.spacing.s3;
spacing: SpacingSystem.spacing.s2;
alignment: center;
Text {
text: "🌐";
font-size: 24px;
horizontal-alignment: center;
}
Text {
text: "系统代理";
font-size: Typography.sizes.xs;
color: Theme.colors.foreground;
horizontal-alignment: center;
}
}
TouchArea {
clicked => {
root.toggle-setting("system-proxy", true);
}
}
}
// TUN Mode
Rectangle {
horizontal-stretch: 1;
height: 80px;
background: Theme.colors.muted;
border-radius: SpacingSystem.radius.md;
VerticalLayout {
padding: SpacingSystem.spacing.s3;
spacing: SpacingSystem.spacing.s2;
alignment: center;
Text {
text: "🔧";
font-size: 24px;
horizontal-alignment: center;
}
Text {
text: "TUN 模式";
font-size: Typography.sizes.xs;
color: Theme.colors.foreground;
horizontal-alignment: center;
}
}
TouchArea {
clicked => {
root.toggle-setting("tun-mode", true);
}
}
}
// Allow LAN
Rectangle {
horizontal-stretch: 1;
height: 80px;
background: Theme.colors.muted;
border-radius: SpacingSystem.radius.md;
VerticalLayout {
padding: SpacingSystem.spacing.s3;
spacing: SpacingSystem.spacing.s2;
alignment: center;
Text {
text: "📡";
font-size: 24px;
horizontal-alignment: center;
}
Text {
text: "允许局域网";
font-size: Typography.sizes.xs;
color: Theme.colors.foreground;
horizontal-alignment: center;
}
}
TouchArea {
clicked => {
root.toggle-setting("allow-lan", true);
}
}
}
}
}
Text {
text: root.ip;
color: Theme.colors.foreground;
font-size: Typography.sizes.sm;
font-weight: Typography.weights.medium;
}
}
VerticalLayout {
: SpacingSystem.spacing.s1;
HorizontalLayout {
spacing: SpacingSystem.spacing.s1;
Text {
text: "🌐";
font-size: Typography.sizes.xs;
vertical-alignment: center;
}
Text {
text: "所属地址";
color: Theme.colors.muted-foreground;
font-size: Typography.sizes.xs;
font-weight: Typography.weights.semibold;
vertical-alignment: center;
}
}
Text {
text: root.region;
color: Theme.colors.foreground;
font-size: Typography.sizes.sm;
font-weight: Typography.weights.medium;
}
}
}
// Latency
VerticalLayout {
spacing: SpacingSystem.spacing.s2;
HorizontalLayout {
spacing: SpacingSystem.spacing.s1;
Text {
text: "✓";
color: Theme.colors.muted-foreground;
font-size: Typography.sizes.xs;
vertical-alignment: center;
}
Text {
text: "延迟";
color: Theme.colors.muted-foreground;
font-size: Typography.sizes.xs;
font-weight: Typography.weights.semibold;
vertical-alignment: center;
}
}
GridLayout {
spacing: SpacingSystem.spacing.s2;
Row {
LatencyItem { service: "Github"; latency: "122ms"; }
LatencyItem { service: "Google"; latency: "45ms"; }
LatencyItem { service: "Youtube"; latency: "67ms"; }
LatencyItem { service: "Twitter"; latency: "89ms"; }
}
Row {
LatencyItem { serviceetflix"; latency: "34ms"; }
LatencyItem { service: "Steam"; latency: "156ms"; }
LatencyItem { service: "Spotify"; latency: "78ms"; }
LatencyItem { service: "Discord"; latency: "92ms"; }
}
}
}
}
}
}
component LatencyItem {
in property <string> service: "";
in property <string> latency: "";
HorizontalLayout {
spacing: SpacingSystem.spacing.s1;
Text {
text: root.service;
color: Theme.colors.muted-foreg;
font-size: Typography.sizes.xs;
font-weight: Typography.weights.medium;
vertical-alignment: center;
}
Text {
text: root.latency;
color: Theme.colors.foreground;
font-size: Typography.sizes.xs;
font-weight: Typography.weights.semibold;
vertical-alignment: center;
}
}
}
component SettingsCard {
in property <bool> tun-enabled: false;
in property <bool> proxy-enabled: false;
in property <int> port: 7890;
callback tun-toggled(bool);
callback proxy-toggled(bool);
callback port-clicked();
Card {
VerticalLayout {
spacing: 0;
// TUN Mode
SettingItem {
label: "Tun 模式";
Switch {
checked: root.tun-enabled;
toggled(checked) => {
root.tun-toggled(checked);
}
}
}
Rectangle {
height: 1px;
background: Theme.colors.border.transparentize(0.6);
}
// System Proxy
SettingItem {
label: "系统代理";
Switch {
checked: root.proxy-enabled;
toggled(checked) => {
root.proxy-toggled(checked);
}
}
}
Rectangle {
height: 1px;
background: Theme.colors.border.transparentize(0.6);
}
// Proxy Port
SettingItem {
label: "代理端口";
But text: root.port;
variant: "link";
clicked => {
root.port-clicked();
}
}
}
}
}
}
component SettingItem {
in property <string> label: "";
height: 40px;
HorizontalLayout {
padding-left: 0;
padding-right: 0;
spacing: SpacingSystem.spacing.s3;
alignment: space-between;
HorizontalLayout {
spacing: SpacingSystem.spacing.s2;
Text {
text: "✓";
color: Theme.colors.muted-foreground;
font-size: Typography.sizes.sm;
vertical-alignment: center;
}
Text {
text: root.label;
color: Theme.colors.foreground;
font-size: Typography.sizes.sm;
vertical-alignment: center;
}
}
@children
}
}

View File

@@ -1,170 +1,209 @@
import { Theme, SpacingSystem, Typography } from "../theme/theme.slint";
import { Tabs, TabItem, TabContent } from "../components/tabs.slint";
// Logs Page - Display Mihomo and App logs with tabs
import { Theme, Typography, SpacingSystem } from "../theme/theme.slint";
import { Empty } from "../components/empty.slint";
import { LogEntry } from "../types.slint";
export struct LogEntry {
time: string,
level: string, // "debug", "info", "warn", "error"
message: string,
}
export component LogsPage inherits Rectangle {
in property <[LogEntry]> mihomo-logs;
in property <[LogEntry]> app-logs;
in-out property <string> active-tab: "mihomo";
callback load-logs(string /* type: mihomo/app */);
background: Theme.colors.background;
export component LogsPage {
in property <[LogEntry]> mihomo-logs: [];
in property <[LogEntry]> app-logs: [];
in-out property <int> current-tab: 0;
callback tab-changed(int);
VerticalLayout {
padding: SpacingSystem.spacing.s4;
spacing: SpacingSystem.spacing.s4;
// Header with Tabs
HorizontalLayout {
spacing: SpacingSystem.spacing.s3;
alignment: space-between;
Text {
text: "Logs";
color: Theme.colors.foreground;
font-size: Typography.sizes.xl;
font-weight: Typography.weights.bold;
vertical-alignment: center;
}
Rectangle {
horizontal-stretch: 1;
}
}
// Tabs
Tabs {
tabs: [
{ title: "Mihomo Logs" },
{ title: "App Logs" },
];
current-index: root.current-tab;
tab-changed(index) => {
root.current-tab = index;
root.tab-changed(index);
}
// Mihomo Logs
TabContent {
index: 0;
current-index: root.current-tab;
LogView {
logs: root.mihomo-logs;
}
}
// App Logs
TabContent {
index: 1;
current-index: root.current-tab;
LogView {
logs: root.app-logs;
}
}
}
}
}
// Fixed header with tabs
Rectangle {
height: 48px;
background: Theme.colors.background;
component LogView {
in property <[LogEntry]> logs: [];
Rectangle {
background: Theme.colors.background;
border-radius: SpacingSystem.radius.md;
border-width: 1px;
border-color: Theme.colors.border;
ScrollView {
VerticalLayout {
padding: SpacingSystem.spacing.s2;
spacing: 0;
for log[index] in root.logs: LogRow {
log-entry: log;
odd: Math.mod(index, 2) == 1;
HorizontalLayout {
padding: SpacingSystem.spacing.s4;
spacing: SpacingSystem.spacing.s4;
alignment: space-between;
Text {
text: "Logs";
font-size: Typography.sizes.xl;
font-weight: Typography.weights.bold;
color: Theme.colors.foreground;
vertical-alignment: center;
}
// Tabs
HorizontalLayout {
spacing: SpacingSystem.spacing.s1;
Rectangle {
width: 120px;
height: 32px;
background: active-tab == "mihomo" ? Theme.colors.primary : Theme.colors.muted;
border-radius: SpacingSystem.radius.md;
TouchArea {
clicked => {
active-tab = "mihomo";
root.load-logs("mihomo");
}
}
Text {
text: "Mihomo Logs";
font-size: Typography.sizes.sm;
font-weight: Typography.weights.medium;
color: active-tab == "mihomo" ? Theme.colors.primary-foreground : Theme.colors.muted-foreground;
horizontal-alignment: center;
vertical-alignment: center;
}
}
Rectangle {
width: 100px;
height: 32px;
background: active-tab == "app" ? Theme.colors.primary : Theme.colors.muted;
border-radius: SpacingSystem.radius.md;
TouchArea {
clicked => {
active-tab = "app";
root.load-logs("app");
}
}
Text {
text: "App Logs";
font-size: Typography.sizes.sm;
font-weight: Typography.weights.medium;
color: active-tab == "app" ? Theme.colors.primary-foreground : Theme.colors.muted-foreground;
horizontal-alignment: center;
vertical-alignment: center;
}
}
}
}
}
// Scrollable log content
Flickable {
viewport-height: log-content.preferred-height + SpacingSystem.spacing.s4 * 2;
log-content := VerticalLayout {
padding: SpacingSystem.spacing.s4;
spacing: 0px;
// Mihomo logs
if active-tab == "mihomo": VerticalLayout {
spacing: 0px;
if mihomo-logs.length == 0: Empty {
height: 300px;
icon: "📝";
title: "No Logs";
description: "Mihomo logs will appear here";
}
for log[index] in mihomo-logs: Rectangle {
height: 24px;
background: mod(index, 2) == 0 ? Theme.colors.muted : transparent;
HorizontalLayout {
padding-left: SpacingSystem.spacing.s2;
padding-right: SpacingSystem.spacing.s2;
spacing: SpacingSystem.spacing.s2;
// Time
Text {
text: log.time;
font-size: Typography.sizes.xs;
color: Theme.colors.muted-foreground;
font-family: "monospace";
vertical-alignment: center;
width: 80px;
}
// Level
Text {
text: log.level;
font-size: Typography.sizes.xs;
font-weight: Typography.weights.bold;
color: log.level == "error" ? #ef4444 :
log.level == "warn" ? #f59e0b :
log.level == "info" ? #3b82f6 : Theme.colors.muted-foreground;
font-family: "monospace";
vertical-alignment: center;
width: 60px;
}
// Message
Text {
text: log.message;
font-size: Typography.sizes.xs;
color: Theme.colors.foreground;
font-family: "monospace";
vertical-alignment: center;
overflow: elide;
}
}
}
}
// App logs
if active-tab == "app": VerticalLayout {
spacing: 0px;
if app-logs.length == 0: Empty {
height: 300px;
icon: "📝";
title: "No Logs";
description: "App logs will appear here";
}
for log[index] in app-logs: Rectangle {
height: 24px;
background: mod(index, 2) == 0 ? Theme.colors.muted : transparent;
HorizontalLayout {
padding-left: SpacingSystem.spacing.s2;
padding-right: SpacingSystem.spacing.s2;
spacing: SpacingSystem.spacing.s2;
// Time
Text {
text: log.time;
font-size: Typography.sizes.xs;
color: Theme.colors.muted-foreground;
font-family: "monospace";
vertical-alignment: center;
width: 80px;
}
// Level
Text {
text: log.level;
font-size: Typography.sizes.xs;
font-weight: Typography.weights.bold;
color: log.level == "error" ? #ef4444 :
log.level == "warn" ? #f59e0b :
log.level == "info" ? #3b82f6 : Theme.colors.muted-foreground;
font-family: "monospace";
vertical-alignment: center;
width: 60px;
}
// Message
Text {
text: log.message;
font-size: Typography.sizes.xs;
color: Theme.colors.foreground;
font-family: "monospace";
vertical-alignment: center;
overflow: elide;
}
}
}
}
}
}
}
}
component LogRow {
in property <LogEntry> log-entry;
in property ool> odd: false;
private property <bool> hovered: false;
private property <color> level-color: log-entry.level == "error" ? #ef4444 :
log-entry.level == "warn" ? #eab308 :
log-entry.level == "info" ? #3b82f6 :
log-entry.level == "debug" ? #6b7280 :
Theme.colors.muted-foreground;
height: 24px;
states [
hovered when root.hovered: {
container.background: Theme.colors.muted.transparentize(0.5);
}
odd when root.odd && !root.hovered: {
container.background: Theme.colors.muted.transparentize(0.8);
}
]
container := Rectangle {
background: transparent;
border-radius: SpacingSystem.radius.sm;
animate background { duration: 150ms; }
HorizontalLayout {
padding-left: SpacingSystem.spacing.s2;
padding-right: SpacingSystem.spacing.s2;
spacing: SpacingSystem.spacing.s2;
// Time
Text {
text: root.log-entry.time;
color: Theme.colors.muted-foreground;
font-size: 11px;
font-family: "monospace";
vertical-alignment: center;
width: 80px;
}
// Level
Text {
text: root.log-entry.level;
color: root.level-color;
font-size: 11px;
font-family: "monospace";
font-weight: Typography.weights.bold;
vertical-alignment: center;
width: 60px;
}
// Message
Text {
text: root.log-entry.message;
color: Theme.colors.foreground;
font-size: 11px;
font-family: "ospace";
vertical-alignment: center;
overflow: elide;
}
}
touch := TouchArea {
moved => {
root.hovered = self.has-hover;
}
}
}
}

View File

@@ -1,203 +1,225 @@
import { Theme, SpacingSystem, Typography } from "../theme/theme.slint";
import { Card } from "../components/card.slint";
// Profiles Page - Configuration profiles management
import { Theme, Typography, SpacingSystem } from "../theme/theme.slint";
import { Card, CardHeader, CardTitle, CardContent, CardFooter } from "../components/card.slint";
import { Button } from "../components/button.slint";
import { Badge } from "../components/badge.slint";
import { Progress } from "../components/progress.slint";
import { Empty } from "../components/empty.slint";
import { Profile } from "../types.slint";
export struct ProfileData {
id: string,
name: string,
type: string, // "local" or "remote"
description: string,
usage: float, // For remote profiles
total: float, // For remote profiles
updated: string,
}
export component ProfilesPage inherits Rectangle {
in property <[Profile]> profiles;
export component ProfilesPage {
in property <[ProfileData]> profiles: [];
in property <string> selected-profile-id: "";
callback load-profiles();
callback select-profile(string /* profile-id */);
callback update-profile(string /* profile-id */);
callback delete-profile(string /* profile-id */);
callback add-profile();
callback refresh-all();
callback select-profile(string);
callback edit-profile(string);
callback delete-profile(string);
callback refresh-profile(string);
VerticalLayout {
padding: SpacingSystem.spacing.s4;
spacing: SpacingSystem.spacing.s4;
// Header
HorizontalLayout {
spacing: SpacingSystem.spacing.s3;
alignment: space-between;
Text {
text: "Profiles";
color: Theme.colors.foreground;
font-size: Typography.sizes.xl;
font-weight: Typography.weights.bold;
vertical-alignment: center;
}
HorizontalLayout {
spacing: SpacingSystem.spacing.s2;
Button {
text: "+";
variant: "ghost";
clicked => {
root.add-profile();
}
}
Button {
text: "↻";
variant: "ghost";
clicked => {
root.refresh-all();
}
}
}
}
// Profile Grid
ScrollView {
GridLayout {
spacing: SpacingSystem.spacing.s3;
for profile in root.profiles: ProfileItem {
profile-data: profile;
selected: profile.id == root.selected-profile-id;
clicked => {
root.select-profile(profile.id);
}
refresh-clicked => {
root.refresh-profile(profile.id);
}
}
}
}
}
}
component ProfileItem {
in property <ProfileData> profile-data;
in property <bool> selected: false;
callback clicked();
callback refresh-clicked();
private property <bool> hovered: false;
min-width: 288px; // 18rem
height: 96px;
states [
selected when root.selected: {
container.border-color: Theme.colors.primary.transparentize(0.5);
container.background: Theme.colors.accent.transparentize(0.4);
}
]
container := Card {
border-width: 2px;
VerticalLa spacing: SpacingSystem.spacing.s2;
// Header
background: Theme.colors.background;
VerticalLayout {
// Fixed header
Rectangle {
height: 48px;
background: Theme.colors.background;
HorizontalLayout {
spacing: SpacingSystem.spacing.s2;
padding: SpacingSystem.spacing.s4;
spacing: SpacingSystem.spacing.s4;
alignment: space-between;
Text {
text: "Profiles";
font-size: Typography.sizes.xl;
font-weight: Typography.weights.bold;
color: Theme.colors.foreground;
vertical-alignment: center;
}
// Action buttons
HorizontalLayout {
spacing: SpacingSystem.spacing.s2;
Text {
text: root.profile-data.type == "remote" ? "☁" : "📄";
font-size: Typography.sizes.lg;
vertical-alignment: center;
Button {
text: "";
variant: "ghost";
size: "sm";
clicked => {
root.add-profile();
}
}
{
text: root.profile-data.name;
color: Theme.colors.foreground;
font-size: Typography.sizes.base;
font-weight: Typography.weights.semibold;
vertical-alignment: center;
Button {
text: "🔄";
variant: "ghost";
size: "sm";
clicked => {
root.load-profiles();
}
}
}
if root.profile-data.type == "remote": Button {
width: 24px;
height: 24px;
text: "↻";
variant: "ghost";
clicked => {
root.refresh-clicked();
}
}
}
// Description
Text {
text: root.profile-data.description;
color: Theme.colors.muted-foreground;
font-size: Typography.sizes.xs;
overflow: elide;
}
// Progress bar for remote profiles
if root.profile-data.type == "remote": VerticalLayout {
spacing: SpacingSystem.spacing.s1;
Progress {
value: (root.profile-data.usage / root.profile-data.total) * 100;
}
HorizontalLayout {
alignment: space-between;
Text {
text: "Updated: " + root.profile-data.updated;
color: Theme.colors.muted-foreground;
font-size: 10px;
}
Text {
text: root.profile-data.usage + " / " + root.profile-data.total + " GB";
color: Theme.colors.oreground;
font-size: 10px;
}
}
}
// Local profile info
if root.profile-data.type == "local": HorizontalLayout {
alignment: space-between;
Text {
text: "Local Profile";
color: Theme.colors.muted-foreground;
font-size: 10px;
}
}
}
touch := TouchArea {
clicked => {
root.clicked();
}
moved => {
root.hovered = self.has-hover;
// Scrollable content
Flickable {
viewport-height: content-layout.preferred-height + SpacingSystem.spacing.s4 * 2;
content-layout := VerticalLayout {
padding: SpacingSystem.spacing.s4;
spacing: SpacingSystem.spacing.s4;
// Empty state
if profiles.length == 0: Empty {
height: 300px;
icon: "📋";
title: "No Profiles";
description: "Click + to add a new profile";
}
// Profile list
for profile in profiles: Card {
hoverable: true;
CardHeader {
HorizontalLayout {
spacing: SpacingSystem.spacing.s2;
alignment: space-between;
HorizontalLayout {
spacing: SpacingSystem.spacing.s2;
Text {
text: "☁";
font-size: Typography.sizes.xl;
vertical-alignment: center;
}
VerticalLayout {
spacing: SpacingSystem.spacing.s1;
CardTitle {
text: profile.name;
}
HorizontalLayout {
spacing: SpacingSystem.spacing.s1;
Badge {
text: profile.type;
variant: "outline";
}
if profile.selected: Badge {
text: "Active";
variant: "default";
}
}
}
}
Button {
text: "🔄";
variant: "ghost";
size: "sm";
clicked => {
root.update-profile(profile.id);
}
}
}
}
CardContent {
VerticalLayout {
spacing: SpacingSystem.spacing.s2;
// Usage
HorizontalLayout {
spacing: SpacingSystem.spacing.s2;
alignment: space-between;
Text {
text: "Usage:";
font-size: Typography.sizes.xs;
color: Theme.colors.muted-foreground;
}
Text {
text: profile.usage;
font-size: Typography.sizes.xs;
color: Theme.colors.foreground;
font-weight: Typography.weights.medium;
}
}
// Expire date
HorizontalLayout {
spacing: SpacingSystem.spacing.s2;
alignment: space-between;
Text {
text: "Expires:";
font-size: Typography.sizes.xs;
color: Theme.colors.muted-foreground;
}
Text {
text: profile.expire-at;
font-size: Typography.sizes.xs;
color: Theme.colors.foreground;
font-weight: Typography.weights.medium;
}
}
// Updated date
HorizontalLayout {
spacing: SpacingSystem.spacing.s2;
alignment: space-between;
Text {
text: "Updated:";
font-size: Typography.sizes.xs;
color: Theme.colors.muted-foreground;
}
Text {
text: profile.updated-at;
font-size: Typography.sizes.xs;
color: Theme.colors.muted-foreground;
}
}
}
}
CardFooter {
HorizontalLayout {
spacing: SpacingSystem.spacing.s2;
if !profile.selected: Button {
text: "Select";
variant: "default";
size: "sm";
clicked => {
root.select-profile(profile.id);
}
}
Button {
text: "Delete";
variant: "destructive";
size: "sm";
clicked => {
root.delete-profile(profile.id);
}
}
}
}
}
}
}
}

View File

@@ -1,90 +1,274 @@
import { Theme, SpacingSystem, Typography } from "../theme/theme.slint";
import { Tabs, TabItem, TabContent } from "../components/tabs.slint";
import { Accordion, AccordionItemData, AccordionContent } from "../components/accordion.slint";
// Proxies Page - Proxy nodes management with tabs and groups
import { Theme, Typography, SpacingSystem } from "../theme/theme.slint";
import { Item } from "../components/item.slint";
import { Badge } from "../components/badge.slint";
import { Button } from "../components/button.slint";
import { Empty } from "../components/empty.slint";
import { Container } from "../layouts/container.slint";
import { ProxyGroup, ProxyNode } from "../types.slint";
export struct ProxyNode {
name: string,
type: string,
latency: string,
}
export component ProxiesPage inherits Rectangle {
in property <[ProxyGroup]> proxy-groups;
in-out property <string> proxy-mode: "rule"; // "rule" | "global" | "direct"
export struct ProxyGroup {
name: string,
nodes: [ProxyNode],
expanded: bool,
}
callback load-proxy-groups(string /* mode */);
callback select-proxy(string /* group-name */, string /* node-name */);
callback test-proxy-delay(string /* node-name */);
background: Theme.colors.background;
export component ProxiesPage {
in-out property <[ProxyGroup]> proxy-groups: [];
in-out property <int> current-mode: 0; // 0=Rule, 1=Global, 2=Direct
in property <string> selected-proxy: "";
callback mode-changed(int);
callback group-toggled(int, bool);
callback proxy-selected(string);
VerticalLayout {
padding: SpacingSystem.spacing.s4;
spacing: SpacingSystem.spacing.s4;
// Header with Tabs
HorizontalLayout {
spacing: SpacingSystem.spacing.s3;
alignment: space-between;
Text {
text: "Proxies";
color: Theme.colors.foreground;
font-size: Typography.sizes.xl;
font-weight: Typography.weights.bold;
vertical-alignment: center;
}
Rectangle {
horizontal-stretch: 1;
// Fixed header with tabs
Rectangle {
height: 48px;
background: Theme.colors.background;
HorizontalLayout {
padding: SpacingSystem.spacing.s4;
spacing: SpacingSystem.spacing.s4;
alignment: space-between;
Text {
text: "Proxies";
font-size: Typography.sizes.xl;
font-weight: Typography.weights.bold;
color: Theme.colors.foreground;
vertical-alignment: center;
}
// Mode tabs
HorizontalLayout {
spacing: SpacingSystem.spacing.s1;
Rectangle {
width: 80px;
height: 32px;
background: proxy-mode == "rule" ? Theme.colors.primary : Theme.colors.muted;
border-radius: SpacingSystem.radius.md;
TouchArea {
clicked => {
proxy-mode = "rule";
root.load-proxy-groups("rule");
}
}
Text {
text: "Rule";
font-size: Typography.sizes.sm;
font-weight: Typography.weights.medium;
color: proxy-mode == "rule" ? Theme.colors.primary-foreground : Theme.colors.muted-foreground;
horizontal-alignment: center;
vertical-alignment: center;
}
}
Rectangle {
width: 80px;
height: 32px;
background: proxy-mode == "global" ? Theme.colors.primary : Theme.colors.muted;
border-radius: SpacingSystem.radius.md;
TouchArea {
clicked => {
proxy-mode = "global";
root.load-proxy-groups("global");
}
}
Text {
text: "Global";
font-size: Typography.sizes.sm;
font-weight: Typography.weights.medium;
color: proxy-mode == "global" ? Theme.colors.primary-foreground : Theme.colors.muted-foreground;
horizontal-alignment: center;
vertical-alignment: center;
}
}
Rectangle {
width: 80px;
height: 32px;
background: proxy-mode == "direct" ? Theme.colors.primary : Theme.colors.muted;
border-radius: SpacingSystem.radius.md;
TouchArea {
clicked => {
proxy-mode = "direct";
root.load-proxy-groups("direct");
}
}
Text {
text: "Direct";
font-size: Typography.sizes.sm;
font-weight: Typography.weights.medium;
color: proxy-mode == "direct" ? Theme.colors.primary-foreground : Theme.colors.muted-foreground;
horizontal-alignment: center;
vertical-alignment: center;
}
}
}
}
}
// Tabs
Tabs {
tabs: [
{ title: "Rule" },
{ title: "Global" },
{ title: "Direct" },
];
current-index: root.current-mode;
tab-changed(index) => {
root.current-mode = index;
root.mode-changed(index);
}
// Tab Content
TabContent {
index: 0;
current-index: root.current-mode;
ScrollView {
// Scrollable content
Flickable {
viewport-height: content-layout.preferred-height + SpacingSystem.spacing.s4 * 2;
content-layout := VerticalLayout {
padding: SpacingSystem.spacing.s4;
spacing: SpacingSystem.spacing.s4;
// Empty state
if proxy-groups.length == 0: Empty {
height: 300px;
icon: "🌍";
title: "No Groups";
description: "Proxy groups will appear here";
}
// Proxy groups
for group in proxy-groups: Container {
VerticalLayout {
padding: SpacingSystem.spacing.s4;
spacing: SpacingSystem.spacing.s3;
Accordion {
items: root.proxy-groups.map(|g| {
return { title: g.name, expanded: g.expanded };
});
item-toggled(index, expanded) => {
root.group-toggled(index, expanded);
// Group header
HorizontalLayout {
spacing: SpacingSystem.spacing.s3;
alignment: space-between;
VerticalLayout {
spacing: SpacingSystem.spacing.s1;
Text {
text: group.name;
font-size: Typography.sizes.base;
font-weight: Typography.weights.semibold;
color: Theme.colors.foreground;
}
Text {
text: "Type: " + group.type + " | Current: " + group.now;
font-size: Typography.sizes.xs;
color: Theme.colors.muted-foreground;
}
}
for group[group-index] in root.proxy-groups: AccordionContent {
ProxyGroupContent {
nodes: group.nodes;
selected-proxy: root.selected-proxy;
node-selected(name) => {
root.proxy-selected(name);
Text {
text: group.nodes.length + " nodes";
font-size: Typography.sizes.xs;
color: Theme.colors.muted-foreground;
vertical-alignment: center;
}
}
// Divider
Rectangle {
height: 1px;
background: Theme.colors.border;
}
// Proxy nodes
VerticalLayout {
spacing: SpacingSystem.spacing.s2;
for node in group.nodes: Rectangle {
height: 60px;
background: node.name == group.now ? Theme.colors.accent : transparent;
border-radius: SpacingSystem.radius.md;
border-width: 1px;
border-color: Theme.colors.border;
HorizontalLayout {
padding: SpacingSystem.spacing.s3;
spacing: SpacingSystem.spacing.s3;
alignment: space-between;
// Node info
HorizontalLayout {
spacing: SpacingSystem.spacing.s3;
// Status indicator
Rectangle {
width: 8px;
height: 8px;
border-radius: 4px;
background: node.delay >= 0 && node.delay < 200 ? #22c55e :
node.delay >= 200 && node.delay < 500 ? #f59e0b :
node.delay >= 500 ? #ef4444 : Theme.colors.muted;
}
// Node details
VerticalLayout {
spacing: SpacingSystem.spacing.s1;
Text {
text: node.name;
font-size: Typography.sizes.sm;
font-weight: Typography.weights.medium;
color: Theme.colors.foreground;
}
HorizontalLayout {
spacing: SpacingSystem.spacing.s2;
Badge {
text: node.type;
variant: "outline";
}
if node.udp: Badge {
text: "UDP";
variant: "outline";
}
if node.delay >= 0: Text {
text: node.delay + "ms";
font-size: Typography.sizes.xs;
color: Theme.colors.muted-foreground;
}
}
}
}
// Actions
HorizontalLayout {
spacing: SpacingSystem.spacing.s2;
Button {
text: "🔍";
variant: "ghost";
size: "sm";
clicked => {
root.test-proxy-delay(node.name);
}
}
if node.name != group.now: Button {
text: "Select";
variant: "outline";
size: "sm";
clicked => {
root.select-proxy(group.name, node.name);
}
}
if node.name == group.now: Badge {
text: "Active";
variant: "default";
}
}
}
TouchArea {
clicked => {
if node.name != group.now {
root.select-proxy(group.name, node.name);
}
}
}
}
@@ -92,135 +276,6 @@ export component ProxiesPage {
}
}
}
TabContent {
index: 1;
current-index: root.current-mode;
Text {
text: "Global mode - All traffic goes through selected proxy";
color: Theme.colors.muted-foreground;
horizontal-alignment: center;
vertical-alignment: center;
}
}
TabContent {
index: 2;
current-index: root.current-mode;
Text {
text: "Direct mode - All traffic goes direct";
color: Theme.colors.muted-foreground;
horizontal-alignment: center;
vertical-alignment: center;
}
}
}
}
}
component ProxyGroupContent {
in property <[ProxyNode]> nodes: [];
in property <string> selected-proxy: "";
callback node-selected(string);
GridLayout {
spacing: SpacingSystem.spacing.s3;
for node[index] in root.nodes: ProxyNodeItem {
node-data: node;
selected: node.name == root.selected-proxy;
clicked => {
root.node-selected(node.name);
}
}
}
}
component ProxyNodeItem {
in property <ProxyNode> node-data;
in property <bool> selected: false;
callback clicked();
private property <bool> hovered: false;
min-width: 200px;
height: 60px;
states [
selected when root.selected: {
container.border-color: Theme.coloimary;
container.background: Theme.colors.primary.transparentize(0.9);
}
hovered when root.hovered && !root.selected: {
container.background: Theme.colors.muted;
}
]
container := Rectangle {
background: transparent;
border-radius: SpacingSystem.radius.md;
border-width: 1px;
border-color: Theme.colors.border;
animate background { duration: 200ms; }
animate border-color { duration: 200ms; }
HorizontalLayout {
padding: SpacingSystem.spacing.s3;
spacing: SpacingSystem.spacing.s3;
alignment: space-between;
VerticalLayout {
spacing: SpacingSystem.spacing.s1;
HorizontalLayout {
spacing: SpacingSystem.spacing.s2;
Text {
text: "✓";
color: root.selected ? Theme.colors.primary : Theme.colors.muted-foreground;
font-size: Typography.sizes.sm;
vertical-alignment: center;
}
Text {
text: root.node-data.name;
color: Theme.colors.foreground;
font-size: Typography.sizes.sm;
font-weight: Typography.weights.medium;
vertical-alignment: center;
}
}
Text {
text: root.node-data.type;
color: Theme.colors.muted-foreground;
font-size: Typography.sizes.xs;
}
}
Text {
text: root.node-data.latency;
color: Theme.colors.primary;
font-size: Typography.sizes.sm;
font-weight: Typography.weights.semibold;
vertical-alignment: center;
}
}
touch := TouchArea {
clicked => {
root.clicked();
}
moved => {
root.hovered = self.has-hover;
}
}
}
}

View File

@@ -1,336 +1,272 @@
import { Theme, SpacingSystem, Typography } from "../theme/theme.slint";
import { Card } from "../components/card.slint";
// Settings Page - App and Clash settings
import { Theme, Typography, SpacingSystem } from "../theme/theme.slint";
import { Container } from "../layouts/container.slint";
import { Switch } from "../components/switch.slint";
import { Button } from "../components/button.slint";
import { Select, SelectOption } from "../components/select.slint";
import { Separator, Orientation } from "../components/separator.slint";
import { SettingItem } from "../types.slint";
export component SettingsPage inherits Rectangle {
in property <[SettingItem]> app-settings;
in property <[SettingItem]> clash-settings;
callback toggle-setting(string /* key */, bool /* value */);
callback load-settings();
background: Theme.colors.background;
export component SettingsPage {
// App Settings
in property <bool> auto-start: false;
in property <bool> silent-start: false;
in property <bool> clash-core: false;
// Clash Settings
in property <bool> tun-mode: false;
in property <bool> allow-lan: false;
in property <bool> ipv6: false;
in property <bool> unified-delay: false;
in property <int> log-level-index: 1;
in property <int> proxy-port: 7890;
// Misc
in property <string> app-dir: "C:/Program Files/Clash";
in property <string> config-dir: "C:/Users/User/.config/clash";
in property <string> core-dir: "C:/Program Files/Clash/core";
in property <string> app-version: "1.0.0";
callback toggle-auto-start(bool);
callback toggle-silent-start(bool);
callback toggle-clash-core(bool);
callback toggle-tun-mode(bool);
callback toggle-allow-lan(bool);
callback toggle-ipv6(bool);
callback toggle-unified-delay(bool);
callback log-level-changed(int);
callback open-port-settings();
callback open-tun-config();
callback open-directory(string);
VerticalLayout {
padding: SpacingSystem.spacing.s4;
spacing: SpacingSystem.spacing.s4;
// Header
Text {
text: "Settings";
color: Theme.colors.foreground;
font-size: Typography.sizes.xl;
font-weight: Typography.weights.bold;
}
ScrollView {
VerticalLayout {
spacing: SpacingSystem.spacing.s3;
// App Settings Section
Card {
VerticalLayout {
spacing: SpacingSystem.spacing.s3;
Text {
text: "App Settings";
color: Theme.colors.foreground;
font-size: Typography.sizes.base;
font-weight: Typography.weights.semibold;
}
SettingRow {
icon: "🚀";
label: "Auto Start";
Switch {
checked: root.auto-start;
toggled(checked) => {
root.toggle-auto-start(checked);
}
}
}
Separator { orientation: Orientation.horizontal; }
SettingRow {
icon: "🔇";
label: "Silent Start";
Switch {
checked: root.silent-start;
toggled(checked) => {
root.toggle-silent-start(checked);
}
}
}
Separator { orientation: Orientation.horizontal; }
SettingRow {
icon: "⚙";
label: "Clash Core";
Switch {
checked: root.clash-core;
toggled(checked) => {
root.toggle-clash-core(checked);
}
}
}
}
}
// Clash Settings Section
Card {
VerticalLayout {
spacing: SpacingSystem.spacing.s3;
Text {
text: "Clash Settings";
color: Theme.colors.foreground;
font-size: Typography.sizes.base;
font-weight: Typography.weights.semibold;
}
SettingRow {
icon: "🌐";
label: "TUN Mode";
HorizontalLayout {
spacing: SpacingSystem.spacing.s2;
Switch
checked: root.tun-mode;
toggled(checked) => {
root.toggle-tun-mode(checked);
}
}
Button {
width: 24px;
height: 24px;
text: "⚙";
variant: "ghost";
clicked => {
root.open-tun-config();
}
}
}
}
Separator { orientation: Orientation.horizontal; }
SettingRow {
icon: "📡";
label: "Allow LAN";
Switch {
checked: root.allow-lan;
toggled(checked) => {
root.toggle-allow-lan(checked);
}
}
}
Separator { orientation: Orientation.horizontal; }
SettingRow {
icon: "🌍";
label: "IPv6";
Switch {
checked: root.ipv6;
toggled(checked) => {
root.toggle-ipv6(checked);
}
}
}
Separator { orientation: Orientation.horizontal; }
SettingRow {
icon: "⏱";
label: "Unified Delay";
Switch {
checked: root.unified-delay;
toggled(checked) => {
root.toggle-unified-delay(checked);
}
}
}
Separator { orientation: Orientation.horizontal; }
SettingRow {
icon: "📝";
label: "Log Level";
Select {
options: [
{ label: "Debug", valueg" },
{ label: "Info", value: "info" },
{ label: "Warn", value: "warn" },
{ label: "Error", value: "error" },
];
selected-index: root.log-level-index;
selected(index, value) => {
root.log-level-changed(index);
}
}
}
Separator { orientation: Orientation.horizontal; }
SettingRow {
icon: "🔌";
label: "Proxy Port";
Button {
text: root.proxy-port;
variant: "link";
clicked => {
root.open-port-settings();
}
}
}
}
// Miscellaneous Section
Card {
VerticalLayout {
spacing: SpacingSystem.spacing.s3;
Text {
text: "Miscellaneous";
color: Theme.colors.foreground;
font-size: Typography.sizes.base;
font-weight: Typography.weights.semibold;
}
SettingRow {
icon: "📁";
l: "App Directory";
Button {
text: "Open";
variant: "link";
clicked => {
root.open-directory(root.app-dir);
}
}
}
Separator { orientation: Orientation.horizontal; }
SettingRow {
icon: "📁";
label: "Config Directory";
Button {
text: "Open";
variant: "link";
clicked => {
root.open-directory(root.config-dir);
}
}
}
Separator { orientation: Orientation.horizontal; }
SettingRow {
icon: "📁";
label: "Core Directory";
Button {
text: "Open";
variant: "link";
clicked => {
root.open-directory(root.core-dir);
}
}
}
Separator { orientation: Orientation.horizontal; }
SettingRow {
icon: "";
label: "App Version";
Text {
text: root.app-version;
color: Theme.colors.muted-foreground;
font-size: Typography.sizes.sm;
vertical-alignment: center;
}
}
}
}
}
}
}
}
// Fixed header
Rectangle {
height: 48px;
background: Theme.colors.background;
component SettingRow {
in property <string> icon: "";
in property <string> label: "";
height: 40px;
HorizontalLayout {
spacing: SpacingSystem.spacing.s3;
alignment: space-between;
HorizontalLayout {
spacing: SpacingSystem.spacing.s2;
Text {
text: root.icon;
font-size: Typography.sizes.base;
vertical-alignment: center;
}
Text {
text: root.label;
color: Theme.colors.foreground;
font-size: Typography.sizes.sm;
vertical-alignment: center;
HorizontalLayout {
padding: SpacingSystem.spacing.s4;
Text {
text: "Settings";
font-size: Typography.sizes.xl;
font-weight: Typography.weights.bold;
color: Theme.colors.foreground;
vertical-alignment: center;
}
}
}
// Scrollable content
Flickable {
viewport-height: content-layout.preferred-height + SpacingSystem.spacing.s4 * 2;
content-layout := VerticalLayout {
padding: SpacingSystem.spacing.s4;
spacing: SpacingSystem.spacing.s4;
// App Settings Section
Container {
VerticalLayout {
padding: SpacingSystem.spacing.s4;
spacing: SpacingSystem.spacing.s3;
// Section title
Text {
text: "App 设置";
font-size: Typography.sizes.base;
font-weight: Typography.weights.semibold;
color: Theme.colors.foreground;
}
// App settings list
for setting in app-settings: Rectangle {
height: 48px;
HorizontalLayout {
padding-top: SpacingSystem.spacing.s2;
padding-bottom: SpacingSystem.spacing.s2;
spacing: SpacingSystem.spacing.s3;
alignment: space-between;
HorizontalLayout {
spacing: SpacingSystem.spacing.s3;
// Icon
Text {
text: "✓";
font-size: Typography.sizes.base;
color: Theme.colors.primary;
vertical-alignment: center;
}
// Label
Text {
text: setting.label;
font-size: Typography.sizes.sm;
color: Theme.colors.foreground;
vertical-alignment: center;
}
}
// Control based on type
if setting.type == "switch": Switch {
checked: setting.enabled;
toggled => {
root.toggle-setting(setting.key, self.checked);
}
}
if setting.type == "button": Button {
text: setting.value;
variant: "link";
size: "sm";
}
if setting.type == "text": Text {
text: setting.value;
font-size: Typography.sizes.sm;
color: Theme.colors.muted-foreground;
vertical-alignment: center;
}
}
}
}
}
// Clash Settings Section
Container {
VerticalLayout {
padding: SpacingSystem.spacing.s4;
spacing: SpacingSystem.spacing.s3;
// Section title
Text {
text: "Clash 设置";
font-size: Typography.sizes.base;
font-weight: Typography.weights.semibold;
color: Theme.colors.foreground;
}
// Clash settings list
for setting in clash-settings: Rectangle {
height: 48px;
HorizontalLayout {
padding-top: SpacingSystem.spacing.s2;
padding-bottom: SpacingSystem.spacing.s2;
spacing: SpacingSystem.spacing.s3;
alignment: space-between;
HorizontalLayout {
spacing: SpacingSystem.spacing.s3;
// Icon
Text {
text: "⚙";
font-size: Typography.sizes.base;
color: Theme.colors.primary;
vertical-alignment: center;
}
// Label
Text {
text: setting.label;
font-size: Typography.sizes.sm;
color: Theme.colors.foreground;
vertical-alignment: center;
}
}
// Control based on type
if setting.type == "switch": Switch {
checked: setting.enabled;
toggled => {
root.toggle-setting(setting.key, self.checked);
}
}
if setting.type == "button": Button {
text: setting.value;
variant: "link";
size: "sm";
}
if setting.type == "text": Text {
text: setting.value;
font-size: Typography.sizes.sm;
color: Theme.colors.muted-foreground;
vertical-alignment: center;
}
}
}
}
}
// Miscellaneous Section
Container {
VerticalLayout {
padding: SpacingSystem.spacing.s4;
spacing: SpacingSystem.spacing.s3;
// Section title
Text {
text: "杂项";
font-size: Typography.sizes.base;
font-weight: Typography.weights.semibold;
color: Theme.colors.foreground;
}
// Misc items
Rectangle {
height: 48px;
HorizontalLayout {
padding-top: SpacingSystem.spacing.s2;
padding-bottom: SpacingSystem.spacing.s2;
spacing: SpacingSystem.spacing.s3;
alignment: space-between;
HorizontalLayout {
spacing: SpacingSystem.spacing.s3;
Text {
text: "📁";
font-size: Typography.sizes.base;
vertical-alignment: center;
}
Text {
text: "App 目录";
font-size: Typography.sizes.sm;
color: Theme.colors.foreground;
vertical-alignment: center;
}
}
Button {
text: "打开";
variant: "link";
size: "sm";
}
}
}
Rectangle {
height: 48px;
HorizontalLayout {
padding-top: SpacingSystem.spacing.s2;
padding-bottom: SpacingSystem.spacing.s2;
spacing: SpacingSystem.spacing.s3;
alignment: space-between;
HorizontalLayout {
spacing: SpacingSystem.spacing.s3;
Text {
text: "📋";
font-size: Typography.sizes.base;
vertical-alignment: center;
}
Text {
text: "App 版本";
font-size: Typography.sizes.sm;
color: Theme.colors.foreground;
vertical-alignment: center;
}
}
Text {
text: "v1.0.0";
font-size: Typography.sizes.sm;
color: Theme.colors.muted-foreground;
vertical-alignment: center;
}
}
}
}
}
}
}
@children
}
}