This commit is contained in:
2026-01-30 12:56:00 +08:00
parent 91f24cb462
commit dcbbda951e
89 changed files with 13486 additions and 72 deletions

0
ui/app-complete.slint Normal file
View File

243
ui/app-full.slint Normal file
View File

@@ -0,0 +1,243 @@
import { Theme } from "./theme/theme.slint";
import { Button } from "./components/button.slint";
import { StatusIndicator, StatusState } from "./components/status-indicator.slint";
import { HomePage, ProfilesPage, ProxiesPage, LogsPage, ConnectionsPage, SettingsPage } from "./pages-complete.slint";
component NavButton {
in property <string> icon: "";
in property <string> title: "";
in property <bool> active: false;
callback clicked();
private property <bool> hovered: false;
height: 40px;
Rectangle {
background: active ? Theme.colors.primary.transparentize(0.9) :
hovered ? Theme.colors.muted.transparentize(0.5) :
transparent;
border-radius: 8px;
HorizontalLayout {
padding-left: 12px;
padding-right: 12px;
spacing: 12px;
Text {
text: root.icon;
color: active ? Theme.colors.primary : Theme.colors.muted-foreground;
font-size: 18px;
vertical-alignment: center;
width: 20px;
}
Text {
text: root.title;
color: active ? Theme.colors.foreground : Theme.colors.muted-foreground;
font-size: 14px;
font-weight: 500;
vertical-alignment: center;
}
}
TouchArea {
clicked => { root.clicked(); }
moved => { root.hovered = self.has-hover; }
}
}
}
export component App inherits Window {
title: "Clash Manager";
preferred-width: 1200px;
preferred-height: 800px;
background: Theme.colors.background;
in-out property <int> current-page: 0;
in-out property <StatusState> mihomo-status: StatusState.stopped;
in-out property <bool> mihomo-loading: false;
callback toggle-mihomo();
HorizontalLayout {
// Sidebar
Rectangle {
width: 240px;
background: Theme.colors.background;
border-width: 1px;
border-color: Theme.colors.border;
VerticalLayout {
padding: 16px;
spacing: 16px;
// Header
HorizontalLayout {
spacing: 12px;
Rectangle {
width: 32px;
height: 32px;
border-radius: 8px;
background: Theme.colors.primary;
Text {
text: "C";
color: Theme.colors.primary-foreground;
font-size: 18px;
font-weight: 700;
horizontal-alignment: center;
vertical-alignment: center;
}
}
VerticalLayout {
spacing: 2px;
Text {
text: "Clash Manager";
color: Theme.colors.foreground;
font-size: 14px;
font-weight: 600;
}
Text {
text: "v1.0.0";
color: Theme.colors.muted-foreground;
font-size: 12px;
}
}
}
// Navigation
VerticalLayout {
spacing: 4px;
Text {
text: "NAVIGATION";
color: Theme.colors.muted-foreground;
font-size: 10px;
font-weight: 600;
}
NavButton {
icon: "🏠";
title: "Home";
active: root.current-page == 0;
clicked => { root.current-page = 0; }
}
NavButton {
icon: "🌐";
title: "Proxies";
active: root.current-page == 1;
clicked => { root.current-page = 1; }
}
NavButton {
icon: "📋";
title: "Profiles";
active: root.current-page == 2;
clicked => { root.current-page = 2; }
}
NavButton {
icon: "🔗";
title: "Connections";
active: root.current-page == 3;
clicked => { root.current-page = 3; }
}
NavButton {
icon: "📝";
title: "Logs";
active: root.current-page == 4;
clicked => { root.current-page = 4; }
}
NavButton {
icon: "⚙";
title: "Settings";
active: root.current-page == 5;
clicked => { root.current-page = 5; }
}
}
Rectangle {
vertical-stretch: 1;
}
// Status Footer
Rectangle {
background: mihomo-status == StatusState.starting ? #eab30820 :
mihomo-status == StatusState.running ? #22c55e20 :
transparent;
border-radius: 8px;
HorizontalLayout {
padding: 12px;
spacing: 12px;
alignment: space-between;
HorizontalLayout {
spacing: 12px;
StatusIndicator {
state: root.mihomo-status;
}
VerticalLayout {
spacing: 2px;
Text {
text: "Clash";
color: mihomo-status == StatusState.running ? #22c55e :
mihomo-status == StatusState.starting ? #eab308 :
#9ca3af;
font-size: 12px;
font-weight: 600;
}
Text {
text: mihomo-status == StatusState.running ? "Running" :
mihomo-status == StatusState.starting ? "Starting..." :
"Stopped";
color: Theme.colors.muted-foreground;
font-size: 10px;
}
}
}
Button {
width: 32px;
height: 32px;
text: mihomo-loading ? "..." : (mihomo-status == StatusState.running ? "||" : ">");
variant: "ghost";
disabled: mihomo-loading;
clicked => {
root.toggle-mihomo();
}
}
}
}
}
}
// Content Area
Rectangle {
background: Theme.colors.background;
horizontal-stretch: 1;
vertical-stretch: 1;
if root.current-page == 0: HomePage {}
if root.current-page == 1: ProxiesPage {}
if root.current-page == 2: ProfilesPage {}
if root.current-page == 3: ConnectionsPage {}
if root.current-page == 4: LogsPage {}
if root.current-page == 5: SettingsPage {}
}
}
}

366
ui/app-nav-backup.slint Normal file
View File

@@ -0,0 +1,366 @@
import { Theme } from "./theme/theme.slint";
import { Button } from "./components/button.slint";
import { Card } from "./components/card.slint";
import { Switch } from "./components/switch.slint";
import { StatusIndicator, StatusState } from "./components/status-indicator.slint";
import { ScrollView } from "std-widgets.slint";
// Component definitions must come before usage
component NavButton {
in property <string> icon: "";
in property <string> title: "";
in property <bool> active: false;
callback clicked();
private property <bool> hovered: false;
height: 40px;
Rectangle {
background: active ? Theme.colors.primary.transparentize(0.9) :
hovered ? Theme.colors.muted.transparentize(0.5) :
transparent;
border-radius: 8px;
HorizontalLayout {
padding-left: 12px;
padding-right: 12px;
spacing: 12px;
Text {
text: root.icon;
color: active ? Theme.colors.primary : Theme.colors.muted-foreground;
font-size: 18px;
vertical-alignment: center;
width: 20px;
}
Text {
text: root.title;
color: active ? Theme.colors.foreground : Theme.colors.muted-foreground;
font-size: 14px;
font-weight: 500;
vertical-alignment: center;
}
}
TouchArea {
clicked => { root.clicked(); }
moved => { root.hovered = self.has-hover; }
}
}
}
component HomePage {
VerticalLayout {
padding: 24px;
spacing: 16px;
Text {
text: "Home";
font-size: 24px;
font-weight: 700;
color: Theme.colors.foreground;
}
ScrollView {
VerticalLayout {
spacing: 16px;
Card {
VerticalLayout {
spacing: 16px;
Text {
text: "Welcome to Clash Manager";
font-size: 18px;
font-weight: 600;
color: Theme.colors.foreground;
}
Text {
text: "Click the navigation buttons on the left to switch between pages.";
color: Theme.colors.muted-foreground;
font-size: 14px;
}
}
}
Card {
VerticalLayout {
spacing: 12px;
Text {
text: "Quick Settings";
font-size: 16px;
font-weight: 600;
color: Theme.colors.foreground;
}
HorizontalLayout {
spacing: 12px;
alignment: space-between;
Text {
text: "TUN Mode";
color: Theme.colors.foreground;
vertical-alignment: center;
}
Switch {
checked: false;
toggled(checked) => {
debug("TUN mode:", checked);
}
}
}
HorizontalLayout {
spacing: 12px;
alignment: space-between;
Text {
text: "System Proxy";
color: Theme.colors.foreground;
vertical-alignment: center;
}
Switch {
checked: false;
toggled(checked) => {
debug("System proxy:", checked);
}
}
}
}
}
}
}
}
}
component SimplePage {
in property <string> page-title: "Page";
VerticalLayout {
padding: 24px;
spacing: 16px;
Text {
text: root.page-title;
font-size: 24px;
font-weight: 700;
color: Theme.colors.foreground;
}
Card {
VerticalLayout {
spacing: 12px;
Text {
text: root.page-title + " page content";
font-size: 16px;
font-weight: 600;
color: Theme.colors.foreground;
}
Text {
text: "This page is under construction. Full implementation coming soon!";
color: Theme.colors.muted-foreground;
}
}
}
}
}
export component App inherits Window {
title: "Clash Manager";
preferred-width: 1200px;
preferred-height: 800px;
background: Theme.colors.background;
in-out property <int> current-page: 0;
in-out property <StatusState> mihomo-status: StatusState.stopped;
in-out property <bool> mihomo-loading: false;
callback toggle-mihomo();
HorizontalLayout {
// Sidebar
Rectangle {
width: 240px;
background: Theme.colors.background;
border-width: 1px;
border-color: Theme.colors.border;
VerticalLayout {
padding: 16px;
spacing: 16px;
// Header
HorizontalLayout {
spacing: 12px;
Rectangle {
width: 32px;
height: 32px;
border-radius: 8px;
background: Theme.colors.primary;
Text {
text: "C";
color: Theme.colors.primary-foreground;
font-size: 18px;
font-weight: 700;
horizontal-alignment: center;
vertical-alignment: center;
}
}
VerticalLayout {
spacing: 2px;
Text {
text: "Clash Manager";
color: Theme.colors.foreground;
font-size: 14px;
font-weight: 600;
}
Text {
text: "v1.0.0";
color: Theme.colors.muted-foreground;
font-size: 12px;
}
}
}
// Navigation
VerticalLayout {
spacing: 4px;
Text {
text: "NAVIGATION";
color: Theme.colors.muted-foreground;
font-size: 10px;
font-weight: 600;
}
NavButton {
icon: "🏠";
title: "Home";
active: root.current-page == 0;
clicked => { root.current-page = 0; }
}
NavButton {
icon: "🌐";
title: "Proxies";
active: root.current-page == 1;
clicked => { root.current-page = 1; }
}
NavButton {
icon: "📋";
title: "Profiles";
active: root.current-page == 2;
clicked => { root.current-page = 2; }
}
NavButton {
icon: "🔗";
title: "Connections";
active: root.current-page == 3;
clicked => { root.current-page = 3; }
}
NavButton {
icon: "📝";
title: "Logs";
active: root.current-page == 4;
clicked => { root.current-page = 4; }
}
NavButton {
icon: "⚙";
title: "Settings";
active: root.current-page == 5;
clicked => { root.current-page = 5; }
}
}
Rectangle {
vertical-stretch: 1;
}
// Status Footer
Rectangle {
background: mihomo-status == StatusState.starting ? #eab30820 :
mihomo-status == StatusState.running ? #22c55e20 :
transparent;
border-radius: 8px;
HorizontalLayout {
padding: 12px;
spacing: 12px;
alignment: space-between;
HorizontalLayout {
spacing: 12px;
StatusIndicator {
state: root.mihomo-status;
}
VerticalLayout {
spacing: 2px;
Text {
text: "Clash";
color: mihomo-status == StatusState.running ? #22c55e :
mihomo-status == StatusState.starting ? #eab308 :
#9ca3af;
font-size: 12px;
font-weight: 600;
}
Text {
text: mihomo-status == StatusState.running ? "Running" :
mihomo-status == StatusState.starting ? "Starting..." :
"Stopped";
color: Theme.colors.muted-foreground;
font-size: 10px;
}
}
}
Button {
width: 32px;
height: 32px;
text: mihomo-loading ? "..." : (mihomo-status == StatusState.running ? "||" : ">");
variant: "ghost";
disabled: mihomo-loading;
clicked => {
root.toggle-mihomo();
}
}
}
}
}
}
// Content Area
Rectangle {
background: Theme.colors.background;
if root.current-page == 0: HomePage {}
if root.current-page == 1: SimplePage { page-title: "Proxies"; }
if root.current-page == 2: SimplePage { page-title: "Profiles"; }
if root.current-page == 3: SimplePage { page-title: "Connections"; }
if root.current-page == 4: SimplePage { page-title: "Logs"; }
if root.current-page == 5: SimplePage { page-title: "Settings"; }
}
}
}

366
ui/app-nav.slint Normal file
View File

@@ -0,0 +1,366 @@
import { Theme } from "./theme/theme.slint";
import { Button } from "./components/button.slint";
import { Card } from "./components/card.slint";
import { Switch } from "./components/switch.slint";
import { StatusIndicator, StatusState } from "./components/status-indicator.slint";
import { ScrollView } from "std-widgets.slint";
// Component definitions must come before usage
component NavButton {
in property <string> icon: "";
in property <string> title: "";
in property <bool> active: false;
callback clicked();
private property <bool> hovered: false;
height: 40px;
Rectangle {
background: active ? Theme.colors.primary.transparentize(0.9) :
hovered ? Theme.colors.muted.transparentize(0.5) :
transparent;
border-radius: 8px;
HorizontalLayout {
padding-left: 12px;
padding-right: 12px;
spacing: 12px;
Text {
text: root.icon;
color: active ? Theme.colors.primary : Theme.colors.muted-foreground;
font-size: 18px;
vertical-alignment: center;
width: 20px;
}
Text {
text: root.title;
color: active ? Theme.colors.foreground : Theme.colors.muted-foreground;
font-size: 14px;
font-weight: 500;
vertical-alignment: center;
}
}
TouchArea {
clicked => { root.clicked(); }
moved => { root.hovered = self.has-hover; }
}
}
}
component HomePage {
VerticalLayout {
padding: 24px;
spacing: 16px;
Text {
text: "Home";
font-size: 24px;
font-weight: 700;
color: Theme.colors.foreground;
}
ScrollView {
VerticalLayout {
spacing: 16px;
Card {
VerticalLayout {
spacing: 16px;
Text {
text: "Welcome to Clash Manager";
font-size: 18px;
font-weight: 600;
color: Theme.colors.foreground;
}
Text {
text: "Click the navigation buttons on the left to switch between pages.";
color: Theme.colors.muted-foreground;
font-size: 14px;
}
}
}
Card {
VerticalLayout {
spacing: 12px;
Text {
text: "Quick Settings";
font-size: 16px;
font-weight: 600;
color: Theme.colors.foreground;
}
HorizontalLayout {
spacing: 12px;
alignment: space-between;
Text {
text: "TUN Mode";
color: Theme.colors.foreground;
vertical-alignment: center;
}
Switch {
checked: false;
toggled(checked) => {
debug("TUN mode:", checked);
}
}
}
HorizontalLayout {
spacing: 12px;
alignment: space-between;
Text {
text: "System Proxy";
color: Theme.colors.foreground;
vertical-alignment: center;
}
Switch {
checked: false;
toggled(checked) => {
debug("System proxy:", checked);
}
}
}
}
}
}
}
}
}
component SimplePage {
in property <string> page-title: "Page";
VerticalLayout {
padding: 24px;
spacing: 16px;
Text {
text: root.page-title;
font-size: 24px;
font-weight: 700;
color: Theme.colors.foreground;
}
Card {
VerticalLayout {
spacing: 12px;
Text {
text: root.page-title + " page content";
font-size: 16px;
font-weight: 600;
color: Theme.colors.foreground;
}
Text {
text: "This page is under construction. Full implementation coming soon!";
color: Theme.colors.muted-foreground;
}
}
}
}
}
export component App inherits Window {
title: "Clash Manager";
preferred-width: 1200px;
preferred-height: 800px;
background: Theme.colors.background;
in-out property <int> current-page: 0;
in-out property <StatusState> mihomo-status: StatusState.stopped;
in-out property <bool> mihomo-loading: false;
callback toggle-mihomo();
HorizontalLayout {
// Sidebar
Rectangle {
width: 240px;
background: Theme.colors.background;
border-width: 1px;
border-color: Theme.colors.border;
VerticalLayout {
padding: 16px;
spacing: 16px;
// Header
HorizontalLayout {
spacing: 12px;
Rectangle {
width: 32px;
height: 32px;
border-radius: 8px;
background: Theme.colors.primary;
Text {
text: "C";
color: Theme.colors.primary-foreground;
font-size: 18px;
font-weight: 700;
horizontal-alignment: center;
vertical-alignment: center;
}
}
VerticalLayout {
spacing: 2px;
Text {
text: "Clash Manager";
color: Theme.colors.foreground;
font-size: 14px;
font-weight: 600;
}
Text {
text: "v1.0.0";
color: Theme.colors.muted-foreground;
font-size: 12px;
}
}
}
// Navigation
VerticalLayout {
spacing: 4px;
Text {
text: "NAVIGATION";
color: Theme.colors.muted-foreground;
font-size: 10px;
font-weight: 600;
}
NavButton {
icon: "🏠";
title: "Home";
active: root.current-page == 0;
clicked => { root.current-page = 0; }
}
NavButton {
icon: "🌐";
title: "Proxies";
active: root.current-page == 1;
clicked => { root.current-page = 1; }
}
NavButton {
icon: "📋";
title: "Profiles";
active: root.current-page == 2;
clicked => { root.current-page = 2; }
}
NavButton {
icon: "🔗";
title: "Connections";
active: root.current-page == 3;
clicked => { root.current-page = 3; }
}
NavButton {
icon: "📝";
title: "Logs";
active: root.current-page == 4;
clicked => { root.current-page = 4; }
}
NavButton {
icon: "⚙";
title: "Settings";
active: root.current-page == 5;
clicked => { root.current-page = 5; }
}
}
Rectangle {
vertical-stretch: 1;
}
// Status Footer
Rectangle {
background: mihomo-status == StatusState.starting ? #eab30820 :
mihomo-status == StatusState.running ? #22c55e20 :
transparent;
border-radius: 8px;
HorizontalLayout {
padding: 12px;
spacing: 12px;
alignment: space-between;
HorizontalLayout {
spacing: 12px;
StatusIndicator {
state: root.mihomo-status;
}
VerticalLayout {
spacing: 2px;
Text {
text: "Clash";
color: mihomo-status == StatusState.running ? #22c55e :
mihomo-status == StatusState.starting ? #eab308 :
#9ca3af;
font-size: 12px;
font-weight: 600;
}
Text {
text: mihomo-status == StatusState.running ? "Running" :
mihomo-status == StatusState.starting ? "Starting..." :
"Stopped";
color: Theme.colors.muted-foreground;
font-size: 10px;
}
}
}
Button {
width: 32px;
height: 32px;
text: mihomo-loading ? "..." : (mihomo-status == StatusState.running ? "||" : ">");
variant: "ghost";
disabled: mihomo-loading;
clicked => {
root.toggle-mihomo();
}
}
}
}
}
}
// Content Area
Rectangle {
background: Theme.colors.background;
if root.current-page == 0: HomePage {}
if root.current-page == 1: SimplePage { page-title: "Proxies"; }
if root.current-page == 2: SimplePage { page-title: "Profiles"; }
if root.current-page == 3: SimplePage { page-title: "Connections"; }
if root.current-page == 4: SimplePage { page-title: "Logs"; }
if root.current-page == 5: SimplePage { page-title: "Settings"; }
}
}
}

884
ui/app-simple-old.slint Normal file
View File

@@ -0,0 +1,884 @@
import { Theme, SpacingSystem, Typography } from "./theme/theme.slint";
import { Button } from "./components/button.slint";
import { Card } from "./components/card.slint";
import { Switch } from "./components/switch.slint";
import { Badge } from "./components/badge.slint";
import { StatusIndicator, StatusState } from "./components/status-indicator.slint";
import { Progress } from "./components/progress.slint";
export component App inherits Window {
title: "Clash Manager";
preferred-width: 1200px;
preferred-height: 800px;
background: Theme.colors.background;
in-out property <int> current-page: 0;
in-out property <StatusState> mihomo-status: StatusState.stopped;
in-out property <bool> mihomo-loading: false;
callback toggle-mihomo();
callback navigate(int);
HorizontalLayout {
// Sidebar
sidebar := Rectangle {
width: 240px;
background: Theme.colors.background;
border-width: 1px;
border-color: Theme.colors.border;
VerticalLayout {
padding: 16px;
spacing: 16px;
// Header
HorizontalLayout {
spacing: 12px;
Rectangle {
width: 32px;
height: 32px;
border-radius: 8px;
background: Theme.colors.primary;
Text {
text: "C";
color: Theme.colors.primary-foreground;
font-size: 18px;
font-weight: 700;
horizontal-alignment: center;
vertical-alignment: center;
}
}
VerticalLayout {
spacing: 2px;
Text {
text: "Clash Manager";
color: Theme.colors.foreground;
font-size: 14px;
font-weight: 600;
}
Text {
text: "v1.0.0";
color: Theme.colors.muted-foreground;
font-size: 12px;
}
}
}
// Navigation
VerticalLayout {
spacing: 4px;
Text {
text: "NAVIGATION";
color: Theme.colors.muted-foreground;
font-size: 10px;
font-weight: 600;
}
NavButton {
icon: "🏠";
title: "Home";
active: root.current-page == 0;
clicked => { root.current-page = 0; root.navigate(0); }
}
NavButton {
icon: "🌐";
title: "Proxies";
active: root.current-page == 1;
clicked => { rourrent-page = 1; root.navigate(1); }
}
NavButton {
icon: "📋";
title: "Profiles";
active: root.current-page == 2;
clicked => { root.current-page = 2; root.navigate(2); }
}
NavButton {
icon: "🔗";
title: "Connections";
active: root.current-page == 3;
clicked => { root.current-page = 3; root.navigate(3); }
}
NavButton {
icon: "📝";
title: "Logs";
active: root.current-page == 4;
clicked => { root.current-page = 4; root.navigate(4); }
}
NavButton {
icon: "⚙";
title: "Settings";
active: root.current-page == 5;
clicked => { root.current-page = 5; root.navigate(5); }
}
}
Rectangle {
vertical-stretch: 1;
}
// Status Footer
Rectangle {
background: mihomo-status == StatusState.starting ? #eab30820 :
mihomo-status == StatusState.running ? #22c55e20 :
transparent;
border-radius: 8px;
HorizontalLayout {
padding: 12px;
spacing: 12px;
alignment: space-between;
HorizontalLayout {
spacing: 12px;
StatusIndicator {
state: root.mihomo-status;
}
VerticalLayout {
spacing: 2px;
Text {
text: "Clash";
color: mihomo-status == StatusState.running ? #22c55e :
mihomo-status == StatusState.starting ? #eab308 :
#9ca3af;
font-size: 12px;
font-weight: 600;
}
Text {
text: mihomo-status == StatusState.running ? "Running" :
mihomo-status == StatusState.starting ? "Starting..." :
"Stopped";
color: Theme.colors.muted-foreground;
font-size: 10px;
}
}
}
Button {
width: 32px;
height: 32px;
text: mihomo-loading ? "..." : (mihomo-status == StatusState.running ? "||" : ">");
variant: "ghost";
disabled: mihomo-loading;
clicked => {
root.toggle-mihomo();
}
}
}
}
}
}
// Content Area
content := Rectangle {
background: Theme.colors.background;
// Home Page
if root.current-page == 0: HomePage {
toggle-mihomo => { root.toggle-mihomo(); }
}
// Proxies Page
if root.current-page == 1: ProxiesPage {}
// Profiles Page
if root.current-page == 2: ProfilesPage {}
// Connections Page
if root.current-page == 3: ConnectionsPage {}
// Logs Page
if root.current-page == 4: LogsPage {}
// Settings Page
if root.current-page == 5: SettingsPage {}
}
}
}
component NavButton {
in property <string> icon: "";
in property <string> title: "";
in property <bool> active: false;
callback clicked();
private property <bool> hovered: false;
height: 40px;
container := Rectangle {
background: active ? Theme.colors.primary.transparentize(0.9) :
hovered ? Theme.colors.muted.transparentize(0.5) :
transparent;
border-radius: 8px;
HorizontalLayout {
padding-left: 12px;
padding-right: 12px;
spacing: 12px;
Text {
text: root.icon;
color: active ? Theme.colors.primary : Theme.colors.muted-foregr font-size: 18px;
vertical-alignment: center;
width: 20px;
}
Text {
text: root.title;
color: active ? Theme.colors.foreground : Theme.colors.muted-foreground;
font-size: 14px;
font-weight: 500;
vertical-alignment: center;
}
}
TouchArea {
clicked => { root.clicked(); }
moved => { root.hovered = self.has-hover; }
}
}
}
component HomePage {
toggle-mihomo();
VerticalLayout {
padding: 24px;
spacing: 16px;
Text {
text: "Home";
font-size: 24px;
font-weight: 700;
color: Theme.colors.foreground;
}
ScrollView {
VerticalLayout {
spacing: 16px;
// Profile Card
Card {
VerticalLayout {
spacing: 16px;
HorizontalLayout {
spacing: 12px;
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;
}
}
VerticalLayout {
spacing: 4px;
HorizontalLayout {
spacing: 8px;
Text {
text: "NanoCloud";
color: Theme.colors.foreground;
font-size: 18px;
font-weight: 700;
vertical-alignment: center;
}
}
HorizontalLayout {
spacing: 8px;
Text {
text: "🌐 Free-Japan1-Ver.7";
color: Theme.colors.muted-foreground;
font-size: 12px;
vertical-alignment: center;
}
Badge {
text: "Vmess";
variant: "outline";
}
Badge {
text: "UDP";
variant: "outline";
}
}
}
}
Rectangle {
height: 1px;
background: Theme.colors.border.transparentize(0.6);
}
HorizontalLayout {
spacing: 24px;
VerticalLayout {
spacing: 4px;
Text {
text: "☁ Usage / Total";
color: Theme.colors.muted-foreground;
font-size: 11px;
font-weight: 600;
}
Text {
text: "1.26GB / 100GB";
color: Theme.colors.foreground;
font-size: 13px;
font-weight: 500;
}
n
VerticalLayout {
spacing: 4px;
Text {
text: "✓ Expiry Date";
color: Theme.colors.muted-foreground;
font-size: 11px;
font-weight: 600;
}
Text {
text: "2025-11-11";
color: Theme.colors.foreground;
font-size: 13px;
font-weight: 500;
}
}
VerticalLayout {
spacing: 4px;
Text {
text: "✓ Last Update";
color: Theme.colors.muted-foreground;
font-size: 11px;
font-weight: 600;
}
Text {
text: "2025-10-12 10:05";
color: Theme.colors.foreground;
font-size: 13px;
font-weight: 500;
}
}
}
}
}
// Location Card
Card {
VerticalLayout {
spacing: 16px;
HorizontalLayout {
spacing: 24px;
VerticalLayout {
spacing: 4px;
Text {
text: "🌐 IP Address";
color: Theme.colors.muted-foreground;
font-size: 11px;
font-weight: 600;
}
Text {
text: "47.238.198.100";
color: Theme.colors.foreground;
font-size: 13px;
font-weight: 500;
}
}
VerticalLayout {
spacing: 4px;
Text {
text: "🌐 Location";
color: Theme.colors.muted-foreground;
font-size: 11px;
font-weight: 600;
}
Text {
text: "Japan · Tokyo";
color: Theme.colors.foreground;
font-size: 13px;
font-weight: 500;
}
}
}
}
}
// Settings Card
Card {
VerticalLayout {
spacing: 0;
SettingRow {
label: "TUN Mode";
Switch {
checked: false;
toggled(checked) => { debug("TUN mode:", checked); }
}
}
Rectangle {
height: 1px;
background: Theme.colors.border.transparentize(0.6);
}
SettingRow {
label: "System Proxy";
Switch {
checked: false;
toggled(checked) => { debug("System proxy:", checked); }
}
}
Rectangle {
height: 1px;
background: Themelors.border.transparentize(0.6);
}
SettingRow {
label: "Proxy Port";
Text {
text: "7890";
color: Theme.colors.primary;
font-size: 14px;
vertical-alignment: center;
}
}
}
}
}
}
}
}
component SettingRow {
in property <string> label: "";
height: 48px;
Horout {
spacing: 12px;
alignment: space-between;
HorizontalLayout {
spacing: 8px;
Text {
text: "✓";
color: Theme.colors.muted-foreground;
font-size: 14px;
vertical-alignment: center;
}
Text {
text: root.label;
color: Theme.colors.foreground;
font-size: 14px;
vertical-alignment: center;
}
}
@children
}
}
component ProxiesPage {
Vertica {
padding: 24px;
spacing: 16px;
Text {
text: "Proxies";
font-size: 24px;
font-weight: 700;
color: Theme.colors.foreground;
}
Card {
Text {
text: "Proxy management page - Coming soon";
color: Theme.colors.muted-foreground;
}
}
}
}
component ProfilesPage {
VerticalLayout {
padding: 24px;
spacing: 16px;
Text {
text: "Profiles";
font-size: 24px;
font-weight: 700;
color: Theme.colors.foreground;
}
ScrollView {
GridLayout {
spacing: 16px;
Card {
min-width: 280px;
height: 120px;
VerticalLayout {
spacing: 8px;
HorizontalLayout {
spacing: 8px;
Text {
text: "☁";
font-size: 18px;
vertical-alignment: center;
}
Text {
text: "NanoCloud";
color: Theme.colors.foreground;
font-size: 16px;
font-weight: 600;
vertical-alignment: center;
}
}
Text {
text: "Free tier subscription - Japan servers";
color: Theme.colors.muted-foreground;
font-size: 12px;
}
Progress {
value: 1.26;
}
HorizontalLayout {
alignment: space-between;
Text {
text: "Updated: Just now";
color: Theme.colors.muted-foreground;
font-size: 10px;
}
Text {
text: "1.26 / 100 GB";
color: Theme.colors.foreground;
font-size: 10px;
}
}
}
}
Card {
min-width: 280px;
height: 120px;
VerticalLayout {
spacing: 8px;
HorizontalLayout {
spacing: 8px;
Text {
text: "📄";
font-size: 18px;
vertical-alignment: center;
}
Text {
text: "Local Config";
color: Theme.colors.foreground;
font-size: 16px;
font-weight: 600;
vertical-alignment: center;
}
}
Text {
text: "Custom local configuration file";
color: Theme.colors.muted-foreground;
font-size: 12px;
}
Text {
text: "Local Profile";
color: Theme.colors.muted-foreground;
font-size: 10px;
}
}
}
}
}
}
}
component ConnectionsPage {
VerticalLayout {
padding: 24px;
spacing: 16px;
Text {
text: "Connections";
font-size: 24px;
font-weight: 700;
color: Theme.colors.foreground;
}
ScrollView {
VerticalLayout {
spacing: 8px;
for Card {
height: 60px;
HorizontalLayout {
spacing: 12px;
Rectangle {
width: 40px;
height: 40px;
border-radius: 8px;
background: Theme.colors.primary.transparentize(0.9);
Text {
text: "🔗";
font-size: 18px;
horizontal-alignment: center;
vertical-alignment: center;
}
}
VerticalLayout {
spacing: 4px;
Text {
text: "192.168.1.100:54321 → github.com:443";
color: Theme.colors.foreground;
font-size: 14px;
font-weight: 500;
}
HorizontalLayout {
spacing: 12px;
Text {
text: "HTTPS";
color: Theme.colors.muted-foreground;
font-size: 12px;
}
Text {
text: "↑ 1.2 KB/s";
color: Theme.colors.primary;
font-size: 12px;
}
Text {
text: "↓ 45.6 KB/s";
color: Theme.colors.secondary;
font-size: 12px;
}
}
}
}
}
}
}
}
}
component LogsPage {
VerticalLayout {
padding: 24px;
spacing: 16px;
Text {
text: "Logs";
font-size: 24px;
font-weight: 700;
color: Theme.colors.foreground;
}
Card {
ScrollView {
VerticalLayout {
spacing: 0;
for i in 20: LogRow {
time: "10:23:45";
level: i < 5 ? "info" : (i < 10 ? "debug" : (i < 15 ? "warn" : "error"));
message: "Sample log message " + i;
}
}
}
}
}
}
component LogRow {
in property <string> time: "";
in property <string> level: "";
in property <string> message: "";
private property <color> level-color: level == "error" ? #ef4444 :
level == "warn" ? #eab308 :
level == "info" ? #3b82f6 :
#6b7280;
: 24px;
HorizontalLayout {
padding-left: 8px;
padding-right: 8px;
spacing: 8px;
Text {
text: root.time;
color: Theme.colors.muted-foreground;
font-size: 11px;
font-family: "monospace";
vertical-alignment: center;
width: 80px;
}
Text {
text: root.level;
color: root.level-color;
font-size: 11px;
font-family: "monospace";
font-weight: 700;
vertical-alignment: center;
width: 60px;
}
Text {
text: root.message;
color: Theme.colors.foreground;
font-size: 11px;
font-family: "monospace";
vertical-alignment: center;
}
}
}
component SettingsPage {
VerticalLayout {
padding: 24px;
spacing: 16px;
Text {
text: "Settings";
font-size: 24px;
font-weight: 700;
color: Theme.colors.foreground;
}
ScrollView {
VerticalLayout {
spacing: 16px;
Card {
VerticalLayout {
spacing: 12px;
Text {
text: "App Settings";
font-size: 16px;
font-weight: 600;
color: Theme.colors.foreground;
}
SettingRow {
label: "Auto Start";
Switch {
checked: false;
toggled(checked) debug("Auto start:", checked); }
}
}
SettingRow {
label: "Silent Start";
Switch {
checked: false;
toggled(checked) => { debug("Silent start:", checked); }
}
}
}
}
Card {
VerticalLayout {
spacing: 12px;
Text {
t Settings";
font-size: 16px;
font-weight: 600;
color: Theme.colors.foreground;
}
SettingRow {
label: "Allow LAN";
Switch {
checked: false;
toggled(checked) => { debug("Allow LAN:", checked); }
}
}
SettingRow {
label: "IPv6";
Switch {
checked: false;
toggled(checked) => { debug("IPv6:", checked); }
}
}
}
}
}
}
}
}

5
ui/app-simple.slint Normal file
View File

@@ -0,0 +1,5 @@
import { Theme, SpacingSystem, Typography } from "./theme/theme.slint";
import { Button } from "./components/button.slint";
import { Card } from "./components/card.slint";
import { Switch } from "./components/switch.slint";
import { StatusIndicator, StatusState } from "./components/status-indicator.slint";

884
ui/app-simple.slint.bak Normal file
View File

@@ -0,0 +1,884 @@
import { Theme, SpacingSystem, Typography } from "./theme/theme.slint";
import { Button } from "./components/button.slint";
import { Card } from "./components/card.slint";
import { Switch } from "./components/switch.slint";
import { Badge } from "./components/badge.slint";
import { StatusIndicator, StatusState } from "./components/status-indicator.slint";
import { Progress } from "./components/progress.slint";
export component App inherits Window {
title: "Clash Manager";
preferred-width: 1200px;
preferred-height: 800px;
background: Theme.colors.background;
in-out property <int> current-page: 0;
in-out property <StatusState> mihomo-status: StatusState.stopped;
in-out property <bool> mihomo-loading: false;
callback toggle-mihomo();
callback navigate(int);
HorizontalLayout {
// Sidebar
sidebar := Rectangle {
width: 240px;
background: Theme.colors.background;
border-width: 1px;
border-color: Theme.colors.border;
VerticalLayout {
padding: 16px;
spacing: 16px;
// Header
HorizontalLayout {
spacing: 12px;
Rectangle {
width: 32px;
height: 32px;
border-radius: 8px;
background: Theme.colors.primary;
Text {
text: "C";
color: Theme.colors.primary-foreground;
font-size: 18px;
font-weight: 700;
horizontal-alignment: center;
vertical-alignment: center;
}
}
VerticalLayout {
spacing: 2px;
Text {
text: "Clash Manager";
color: Theme.colors.foreground;
font-size: 14px;
font-weight: 600;
}
Text {
text: "v1.0.0";
color: Theme.colors.muted-foreground;
font-size: 12px;
}
}
}
// Navigation
VerticalLayout {
spacing: 4px;
Text {
text: "NAVIGATION";
color: Theme.colors.muted-foreground;
font-size: 10px;
font-weight: 600;
}
NavButton {
icon: "🏠";
title: "Home";
active: root.current-page == 0;
clicked => { root.current-page = 0; root.navigate(0); }
}
NavButton {
icon: "🌐";
title: "Proxies";
active: root.current-page == 1;
clicked => { rourrent-page = 1; root.navigate(1); }
}
NavButton {
icon: "📋";
title: "Profiles";
active: root.current-page == 2;
clicked => { root.current-page = 2; root.navigate(2); }
}
NavButton {
icon: "🔗";
title: "Connections";
active: root.current-page == 3;
clicked => { root.current-page = 3; root.navigate(3); }
}
NavButton {
icon: "📝";
title: "Logs";
active: root.current-page == 4;
clicked => { root.current-page = 4; root.navigate(4); }
}
NavButton {
icon: "⚙";
title: "Settings";
active: root.current-page == 5;
clicked => { root.current-page = 5; root.navigate(5); }
}
}
Rectangle {
vertical-stretch: 1;
}
// Status Footer
Rectangle {
background: mihomo-status == StatusState.starting ? #eab30820 :
mihomo-status == StatusState.running ? #22c55e20 :
transparent;
border-radius: 8px;
HorizontalLayout {
padding: 12px;
spacing: 12px;
alignment: space-between;
HorizontalLayout {
spacing: 12px;
StatusIndicator {
state: root.mihomo-status;
}
VerticalLayout {
spacing: 2px;
Text {
text: "Clash";
color: mihomo-status == StatusState.running ? #22c55e :
mihomo-status == StatusState.starting ? #eab308 :
#9ca3af;
font-size: 12px;
font-weight: 600;
}
Text {
text: mihomo-status == StatusState.running ? "Running" :
mihomo-status == StatusState.starting ? "Starting..." :
"Stopped";
color: Theme.colors.muted-foreground;
font-size: 10px;
}
}
}
Button {
width: 32px;
height: 32px;
text: mihomo-loading ? "..." : (mihomo-status == StatusState.running ? "||" : ">");
variant: "ghost";
disabled: mihomo-loading;
clicked => {
root.toggle-mihomo();
}
}
}
}
}
}
// Content Area
content := Rectangle {
background: Theme.colors.background;
// Home Page
if root.current-page == 0: HomePage {
toggle-mihomo => { root.toggle-mihomo(); }
}
// Proxies Page
if root.current-page == 1: ProxiesPage {}
// Profiles Page
if root.current-page == 2: ProfilesPage {}
// Connections Page
if root.current-page == 3: ConnectionsPage {}
// Logs Page
if root.current-page == 4: LogsPage {}
// Settings Page
if root.current-page == 5: SettingsPage {}
}
}
}
component NavButton {
in property <string> icon: "";
in property <string> title: "";
in property <bool> active: false;
callback clicked();
private property <bool> hovered: false;
height: 40px;
container := Rectangle {
background: active ? Theme.colors.primary.transparentize(0.9) :
hovered ? Theme.colors.muted.transparentize(0.5) :
transparent;
border-radius: 8px;
HorizontalLayout {
padding-left: 12px;
padding-right: 12px;
spacing: 12px;
Text {
text: root.icon;
color: active ? Theme.colors.primary : Theme.colors.muted-foregr font-size: 18px;
vertical-alignment: center;
width: 20px;
}
Text {
text: root.title;
color: active ? Theme.colors.foreground : Theme.colors.muted-foreground;
font-size: 14px;
font-weight: 500;
vertical-alignment: center;
}
}
TouchArea {
clicked => { root.clicked(); }
moved => { root.hovered = self.has-hover; }
}
}
}
component HomePage {
toggle-mihomo();
VerticalLayout {
padding: 24px;
spacing: 16px;
Text {
text: "Home";
font-size: 24px;
font-weight: 700;
color: Theme.colors.foreground;
}
ScrollView {
VerticalLayout {
spacing: 16px;
// Profile Card
Card {
VerticalLayout {
spacing: 16px;
HorizontalLayout {
spacing: 12px;
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;
}
}
VerticalLayout {
spacing: 4px;
HorizontalLayout {
spacing: 8px;
Text {
text: "NanoCloud";
color: Theme.colors.foreground;
font-size: 18px;
font-weight: 700;
vertical-alignment: center;
}
}
HorizontalLayout {
spacing: 8px;
Text {
text: "🌐 Free-Japan1-Ver.7";
color: Theme.colors.muted-foreground;
font-size: 12px;
vertical-alignment: center;
}
Badge {
text: "Vmess";
variant: "outline";
}
Badge {
text: "UDP";
variant: "outline";
}
}
}
}
Rectangle {
height: 1px;
background: Theme.colors.border.transparentize(0.6);
}
HorizontalLayout {
spacing: 24px;
VerticalLayout {
spacing: 4px;
Text {
text: "☁ Usage / Total";
color: Theme.colors.muted-foreground;
font-size: 11px;
font-weight: 600;
}
Text {
text: "1.26GB / 100GB";
color: Theme.colors.foreground;
font-size: 13px;
font-weight: 500;
}
n
VerticalLayout {
spacing: 4px;
Text {
text: "✓ Expiry Date";
color: Theme.colors.muted-foreground;
font-size: 11px;
font-weight: 600;
}
Text {
text: "2025-11-11";
color: Theme.colors.foreground;
font-size: 13px;
font-weight: 500;
}
}
VerticalLayout {
spacing: 4px;
Text {
text: "✓ Last Update";
color: Theme.colors.muted-foreground;
font-size: 11px;
font-weight: 600;
}
Text {
text: "2025-10-12 10:05";
color: Theme.colors.foreground;
font-size: 13px;
font-weight: 500;
}
}
}
}
}
// Location Card
Card {
VerticalLayout {
spacing: 16px;
HorizontalLayout {
spacing: 24px;
VerticalLayout {
spacing: 4px;
Text {
text: "🌐 IP Address";
color: Theme.colors.muted-foreground;
font-size: 11px;
font-weight: 600;
}
Text {
text: "47.238.198.100";
color: Theme.colors.foreground;
font-size: 13px;
font-weight: 500;
}
}
VerticalLayout {
spacing: 4px;
Text {
text: "🌐 Location";
color: Theme.colors.muted-foreground;
font-size: 11px;
font-weight: 600;
}
Text {
text: "Japan · Tokyo";
color: Theme.colors.foreground;
font-size: 13px;
font-weight: 500;
}
}
}
}
}
// Settings Card
Card {
VerticalLayout {
spacing: 0;
SettingRow {
label: "TUN Mode";
Switch {
checked: false;
toggled(checked) => { debug("TUN mode:", checked); }
}
}
Rectangle {
height: 1px;
background: Theme.colors.border.transparentize(0.6);
}
SettingRow {
label: "System Proxy";
Switch {
checked: false;
toggled(checked) => { debug("System proxy:", checked); }
}
}
Rectangle {
height: 1px;
background: Themelors.border.transparentize(0.6);
}
SettingRow {
label: "Proxy Port";
Text {
text: "7890";
color: Theme.colors.primary;
font-size: 14px;
vertical-alignment: center;
}
}
}
}
}
}
}
}
component SettingRow {
in property <string> label: "";
height: 48px;
Horout {
spacing: 12px;
alignment: space-between;
HorizontalLayout {
spacing: 8px;
Text {
text: "✓";
color: Theme.colors.muted-foreground;
font-size: 14px;
vertical-alignment: center;
}
Text {
text: root.label;
color: Theme.colors.foreground;
font-size: 14px;
vertical-alignment: center;
}
}
@children
}
}
component ProxiesPage {
Vertica {
padding: 24px;
spacing: 16px;
Text {
text: "Proxies";
font-size: 24px;
font-weight: 700;
color: Theme.colors.foreground;
}
Card {
Text {
text: "Proxy management page - Coming soon";
color: Theme.colors.muted-foreground;
}
}
}
}
component ProfilesPage {
VerticalLayout {
padding: 24px;
spacing: 16px;
Text {
text: "Profiles";
font-size: 24px;
font-weight: 700;
color: Theme.colors.foreground;
}
ScrollView {
GridLayout {
spacing: 16px;
Card {
min-width: 280px;
height: 120px;
VerticalLayout {
spacing: 8px;
HorizontalLayout {
spacing: 8px;
Text {
text: "☁";
font-size: 18px;
vertical-alignment: center;
}
Text {
text: "NanoCloud";
color: Theme.colors.foreground;
font-size: 16px;
font-weight: 600;
vertical-alignment: center;
}
}
Text {
text: "Free tier subscription - Japan servers";
color: Theme.colors.muted-foreground;
font-size: 12px;
}
Progress {
value: 1.26;
}
HorizontalLayout {
alignment: space-between;
Text {
text: "Updated: Just now";
color: Theme.colors.muted-foreground;
font-size: 10px;
}
Text {
text: "1.26 / 100 GB";
color: Theme.colors.foreground;
font-size: 10px;
}
}
}
}
Card {
min-width: 280px;
height: 120px;
VerticalLayout {
spacing: 8px;
HorizontalLayout {
spacing: 8px;
Text {
text: "📄";
font-size: 18px;
vertical-alignment: center;
}
Text {
text: "Local Config";
color: Theme.colors.foreground;
font-size: 16px;
font-weight: 600;
vertical-alignment: center;
}
}
Text {
text: "Custom local configuration file";
color: Theme.colors.muted-foreground;
font-size: 12px;
}
Text {
text: "Local Profile";
color: Theme.colors.muted-foreground;
font-size: 10px;
}
}
}
}
}
}
}
component ConnectionsPage {
VerticalLayout {
padding: 24px;
spacing: 16px;
Text {
text: "Connections";
font-size: 24px;
font-weight: 700;
color: Theme.colors.foreground;
}
ScrollView {
VerticalLayout {
spacing: 8px;
for Card {
height: 60px;
HorizontalLayout {
spacing: 12px;
Rectangle {
width: 40px;
height: 40px;
border-radius: 8px;
background: Theme.colors.primary.transparentize(0.9);
Text {
text: "🔗";
font-size: 18px;
horizontal-alignment: center;
vertical-alignment: center;
}
}
VerticalLayout {
spacing: 4px;
Text {
text: "192.168.1.100:54321 → github.com:443";
color: Theme.colors.foreground;
font-size: 14px;
font-weight: 500;
}
HorizontalLayout {
spacing: 12px;
Text {
text: "HTTPS";
color: Theme.colors.muted-foreground;
font-size: 12px;
}
Text {
text: "↑ 1.2 KB/s";
color: Theme.colors.primary;
font-size: 12px;
}
Text {
text: "↓ 45.6 KB/s";
color: Theme.colors.secondary;
font-size: 12px;
}
}
}
}
}
}
}
}
}
component LogsPage {
VerticalLayout {
padding: 24px;
spacing: 16px;
Text {
text: "Logs";
font-size: 24px;
font-weight: 700;
color: Theme.colors.foreground;
}
Card {
ScrollView {
VerticalLayout {
spacing: 0;
for i in 20: LogRow {
time: "10:23:45";
level: i < 5 ? "info" : (i < 10 ? "debug" : (i < 15 ? "warn" : "error"));
message: "Sample log message " + i;
}
}
}
}
}
}
component LogRow {
in property <string> time: "";
in property <string> level: "";
in property <string> message: "";
private property <color> level-color: level == "error" ? #ef4444 :
level == "warn" ? #eab308 :
level == "info" ? #3b82f6 :
#6b7280;
: 24px;
HorizontalLayout {
padding-left: 8px;
padding-right: 8px;
spacing: 8px;
Text {
text: root.time;
color: Theme.colors.muted-foreground;
font-size: 11px;
font-family: "monospace";
vertical-alignment: center;
width: 80px;
}
Text {
text: root.level;
color: root.level-color;
font-size: 11px;
font-family: "monospace";
font-weight: 700;
vertical-alignment: center;
width: 60px;
}
Text {
text: root.message;
color: Theme.colors.foreground;
font-size: 11px;
font-family: "monospace";
vertical-alignment: center;
}
}
}
component SettingsPage {
VerticalLayout {
padding: 24px;
spacing: 16px;
Text {
text: "Settings";
font-size: 24px;
font-weight: 700;
color: Theme.colors.foreground;
}
ScrollView {
VerticalLayout {
spacing: 16px;
Card {
VerticalLayout {
spacing: 12px;
Text {
text: "App Settings";
font-size: 16px;
font-weight: 600;
color: Theme.colors.foreground;
}
SettingRow {
label: "Auto Start";
Switch {
checked: false;
toggled(checked) debug("Auto start:", checked); }
}
}
SettingRow {
label: "Silent Start";
Switch {
checked: false;
toggled(checked) => { debug("Silent start:", checked); }
}
}
}
}
Card {
VerticalLayout {
spacing: 12px;
Text {
t Settings";
font-size: 16px;
font-weight: 600;
color: Theme.colors.foreground;
}
SettingRow {
label: "Allow LAN";
Switch {
checked: false;
toggled(checked) => { debug("Allow LAN:", checked); }
}
}
SettingRow {
label: "IPv6";
Switch {
checked: false;
toggled(checked) => { debug("IPv6:", checked); }
}
}
}
}
}
}
}
}

211
ui/app.slint Normal file
View File

@@ -0,0 +1,211 @@
import { Theme } from "./theme/theme.slint";
import { AppSidebar, NavItem } from "./components/app-sidebar.slint";
import { StatusState } from "./components/status-indicator.slint";
import { HomePage } from "./pages/home.slint";
import { ProfilesPage, ProfileData } from "./pages/profiles.slint";
import { ProxiesPage, ProxyGroup, ProxyNode } from "./pages/proxies.slint";
import { LogsPage, LogEntry } from "./pages/logs.slint";
import { ConnectionsPage, Connection } from "./pages/connections.slint";
import { SettingsPage } from "./pages/settings.slint";
export component App inherits Window {
title: "Clash Manager";
preferred-width: 1200px;
preferred-height: 800px;
background: Theme.colors.background;
// Navigation state
in-out property <int> current-page: 0;
// Mihomo status
in-out property <StatusState> mihomo-status: StatusState.stopped;
in-out property <bool> mihomo-loading: false;
// Home page data
in-out property <string> profile-name: "NanoCloud";
in-out property <string> profile-node: "Free-Japan1-Ver.7";
in-out property <float> profile-usage: 1.26;
in-out property <float> profile-total: 100;
in-out property <string> profile-expiry: "2025-11-11";
in-out property <string> profile-updated: "2025-10-12 10:05";
in-out property <string> location-ip: "47.238.198.100";
in-out property <string> location-region: "Japan · Tokyo";
in-out property <bool> tun-mode-enabled: false;
in-out property <bool> system-proxy-enabled: false;
in-out property <int> proxy-port: 7890;
// Profiles page data
in-out property <[ProfileData]> profiles: [];
in-out property <string> selected-profile-id: "";
// Proxies page data
in-out property <[ProxyGroup]> proxy-groups: [];
in-out property <int> proxy-mode: 0;
in-out property <string> selected-proxy: "";
// Logs page data
in-out property <[LogEntry]> mihomo-logs: [];
in-out property <[LogEntry]> app-logs: [];
in-out property <int> log-tab: 0;
// Connections page data
in-out property <[Connection]> connections: [];
// Settings page data
in-out property <bool> auto-start: false;
in-out property <bool> silent-start: false;
in-out property <bool> clash-core: false;
in-out property <bool> allow-lan: false;
in-out property <bool> ipv6: false;
in-out property <bool> unified-delay: false;
in-out property <int> log-level-index: 1;
in-out property <string> app-dir: "C:/Program Files/Clash";
in-out property <string> config-dir: "C:/Users/User/.config/clash";
in-out property <string> core-dir: "C:/Program Files/Clash/core";
in-out property <string> app-version: "1.0.0";
// Callbacks
callback navigate(int);
callback toggle-mihomo();
callback refresh-profile();
callback toggle-tun-mode(bool);
callback toggle-system-proxy(bool);
callback open-port-settings();
callback add-profile();
callback refresh-all-profiles();
callback select-profile(string);
callback edit-profile(string);
callback delete-profile(string);
callback refresh-single-profile(string);
callback proxy-mode-changed(int);
callback proxy-group-toggled(int, bool);
callback proxy-selected(string);
callback log-tab-changed(int);
callback toggle-auto-start(bool);
callback toggle-silent-start(bool);
callback toggle-clash-core(bool);
callback toggle-allow-lan(bool);
callback toggle-ipv6(bool);
callback toggle-unified-delay(bool);
callback log-level-changed(int);
callback open-tun-config();
callback open-directory(string);
HorizontalLayout {
// Sidebar
AppSidebar {
nav-items: [
{ title: "Home", icon: "🏠" },
{ title: "Proxies", icon: "🌐" },
{ title: "Profiles", icon: "📋" },
{ title: "Connections", icon: "🔗" },
{ title: "Logs", icon: "📝" },
{ title: "Settings", icon: "⚙" },
];
current-page: root.current-page;
mihomo-status: root.mihomo-status;
mihomo-loading: root.mihomo-loading;
navigate(index) => {
root.current-page = index;
root.navigate(index);
}
toggle-mihomo => {
root.toggle-mihomo();
}
}
// Content Area
Rectangle {
background: Theme.colors.background;
// Home Page
if root.current-page == 0: HomePage {
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;
location-ip: root.location-ip;
location-region: root.location-region;
tun-mode-enabled: root.tun-mode-enabled;
system-proxy-enabled: root.system-proxy-enabled;
proxy-port: root.proxy-port;
refresh-profile => { root.refresh-profile(); }
toggle-tun-mode(enabled) => { root.toggle-tun-mode(enabled); }
toggle-system-proxy(enabled) => { root.toggle-system-proxy(enabled); }
open-port-settings => { root.open-port-settings(); }
}
// Proxies Page
if root.current-page == 1: ProxiesPage {
proxy-groups: root.proxy-groups;
current-mode: root.proxy-mode;
selected-proxy: root.selected-proxy;
mode-changed(mode) => { root.proxy-mode-changed(mode); }
group-toggled(index, expanded) => { root.proxy-group-toggled(index, expanded); }
proxy-selected(name) => { root.proxy-selected(name); }
}
// Profiles Page
if root.current-page == 2: ProfilesPage {
profiles: root.profiles;
selected-profile-id: root.selected-profile-id;
adrofile => { root.add-profile(); }
refresh-all => { root.refresh-all-profiles(); }
select-profile(id) => { root.select-profile(id); }
edit-profile(id) => { root.edit-profile(id); }
delete-profile(id) => { root.delete-profile(id); }
refresh-profile(id) => { root.refresh-single-profile(id); }
}
// Connections Page
if root.current-page == 3: ConnectionsPage {
connections: root.connections;
}
// Logs Page
if root.current-page == 4: LogsPage {
mihomo-logs: root.mihomo-logs;
app-logs: root.app-logs;
current-tab: root.log-tab;
tab-changed(index) => { root.log-tab-changed(index); }
}
// Settings Page
if root.current-page == 5: SettingsPage {
auto-start: root.auto-start;
silent-start: root.silent-start;
clash-core: root.clash-core;
tun-mode: root.tun-mode-enabled;
allow-lan: root.allow-lan;
ipv6: root.ipv6;
unified-delay: root.unified-delay;
log-level-index: root.log-level-index;
proxy-port: root.proxy-port;
app-dir: root.app-dir;
config-dir: root.config-dir;
core-dir: root.core-dir;
app-version: root.app-version;
toggle-auto-start(enabled) => { root.toggle-auto-start(enabled); }
toggle-silent-start(enabled) => { root.toggle-silent-start(enabled); }
toggle-clash-core(enabled) => { root.toggle-clash-core(enabled); }
toggle-tun-mode(enabled) => { root.toggle-tun-mode(enabled); }
toggle-allow-lan(enabled) => { root.toggle-allow-lan(enabled); }
toggle-ipv6(enabled) => { root.toggle-ipv6(enabled); }
toggle-unified-delay(enabled) => { root.toggle-unified-delay(enabled); }
log-level-changed(index) => { root.log-level-changed(index); }
open-port-settings => { root.open-port-settings(); }
open-tun-config => { root.open-tun-config(); }
open-directory(dir) => { root.open-directory(dir); }
}
}
}
}

View File

@@ -1,26 +0,0 @@
import { VerticalBox, Button } from "std-widgets.slint";
export component AppWindow inherits Window {
in-out property <string> greeting: "Hello, World!";
width: 400px;
height: 300px;
title: "Slint Hello World";
VerticalBox {
alignment: center;
Text {
text: greeting;
font-size: 24px;
horizontal-alignment: center;
}
Button {
text: "Click Me!";
clicked => {
greeting = "Hello from Slint!";
}
}
}
}

View File

@@ -0,0 +1,116 @@
import { Theme, SpacingSystem, Typography, Animations } from "../theme/theme.slint";
export struct AccordionItemData {
title: string,
expanded: bool,
}
export component Accordion {
in-out property <[AccordionItemData]> items: [];
in property <bool> allow-multiple: false;
callback item-toggled(int, bool);
VerticalLayout {
spacing: 0;
for item[index] in root.items: AccordionItem {
title: item.title;
expanded: item.expanded;
is-last: index == root.items.length - 1;
toggled => {
root.item-toggled(index, !item.expanded);
}
}
}
}
component AccordionItem {
in property <string> title: "";
in property <bool> expanded: false;
in property <bool> is-last: false;
callback toggled();
private property <bool> hovered: false;
VerticalLayout {
spacing: 0;
// Header
header := Rectangle {
height: 48px;
background: root.hovered ? Theme.colors.muted : transparent;
animate background { duration: Animations.durations.fast; }
HorizontalLayout {
padding-left: SpacingSystem.spacing.s4;
padding-right: SpacingSystem.spacing.s4;
spacing: SpacingSystem.spacing.s4;
alignment: space-between;
Text {
text: root.title;
color: Theme.colors.foreground;
font-size: Typography.sizes.sm;
font-weight: Typography.weights.medium;
vertical-alignment: center;
}
chevron := Text {
text: "";
color: Theme.colors.muted-foreground;
font-size: Typography.sizes.lg;
font-weight: Typography.weights.bold;
vertical-alignment: center;
width: 20px;
horizontal-alignment: center;
rotation-angle: root.expanded ? 90deg : 0deg;
rotation-origin-x: self.width / 2;
rotation-origin-y: self.height / 2;
animate rotation-angle { duration: Animations.durations.normal; easing: Animations.ease-out; }
}
}
touch := TouchArea {
clicked => {
root.toggled();
}
moved => {
root.hovered = self.has-hover;
}
}
}
// Content
if root.expanded: content-wrapper := Rectangle {
background: transparent;
VerticalLayout {
padding: SpacingSystem.spacing.s4;
padding-top: 0;
padding-bottom: SpacingSystem.spacing.s4;
@children
}
}
// Border
if !root.is-last: Rectangle {
height: 1px;
background: Theme.colors.border;
}
}
}
// Wrapper for accordion content
export component AccordionContent {
VerticalLayout {
@children
}
}

View File

@@ -0,0 +1,238 @@
import { Theme, SpacingSystem, Typography, Animations } from "../theme/theme.slint";
import { Button } from "./button.slint";
import { StatusIndicator, StatusState } from "./status-indicator.slint";
export struct NavItem {
title: string,
icon: string,
}
export component AppSidebar {
in property <[NavItem]> nav-items: [];
in-out property <int> current-page: 0;
in property <StatusState> mihomo-status: StatusState.stopped;
in property <bool> mihomo-loading: false;
callback navigate(int);
callback toggle-mihomo();
width: 240px;
Rectangle {
background: Theme.colors.background;
border-width: 1px;
border-color: Theme.colors.border;
VerticalLayout {
padding: SpacingSystem.spacing.s4;
spacing: SpacingSystem.spacing.s4;
// Header
HorizontalLayout {
spacing: SpacingSystem.spacing.s3;
Rectangle {
width: 32px;
height: 32px;
border-radius: SpacingSystem.radius.md;
background: Theme.colors.primary;
Text {
text: "C";
color: Theme.colors.primary-foreground;
font-size: Typography.sizes.lg;
font-weight: Typography.weights.bold;
horizontal-alignment: center;
vertical-alignment: center;
}
}
VerticalLayout {
spacing: 2px;
Text {
text: "Clash Manager";
color: Theme.colors.foreground;
font-size: Typography.sizes.sm;
font-weight: Typography.weights.semibold;
}
Text {
text: "v1.0.0";
color: Theme.colors.muted-foreground;
font-size: Typography.sizes.xs;
}
}
}
// Navigation
VerticalLayout {
spacing: SpacingSystem.spacing.s1;
Text {
text: "NAVIGATION";
color: Theme.colors.muted-foreground;
font-size: 10px;
font-weight: Typography.weights.semibold;
}
for item[index] in root.nav-items: NavButton {
icon: item.icon;
title: item.title;
active: index == root.current-page;
clicked => {
root.navigate(index);
}
}
}
Rectangle {
vertical-stretch: 1;
}
// Mihomo Status Footer
MihomoStatusBar {
status: root.mihomo-status;
loading: root.mihomo-loading;
toggle-clicked => {
root.toggle-mihomo();
}
}
}
}
}
export component NavButton {
in property <string> icon: "";
in property <string> title: "";
in property <bool> active: false;
callback clicked();
private property <bool> hovered: false;
height: 40px;
states [
active when root.active: {
container.background: Theme.colors.primary.transparentize(0.9);
icon-text.color: Theme.colors.primary;
title-text.color: Theme.colors.foreground;
}
hovered when root.hovered && !root.active: {
container.background: Theme.colors.muted.transparentize(0.5);
}
]
container := Rectangle {
background: transparent;
border-radius: SpacingSystem.radius.md;
animate background { duration: Animations.durations.fast; }
HorizontalLayout {
padding-left: SpacingSystem.spacing.s3;
padding-right: SpacingSystem.spacing.s3;
spacing: SpacingSystem.spacing.s3;
icon-text := Text {
text: root.icon;
color: Theme.colors.muted-foreground;
font-size: Typography.sizes.lg;
vertical-alignment: center;
width: 20px;
animate color { duration: Animations.durations.fast; }
}
title-text := Text {
text: root.title;
color: Theme.colors.muted-foreground;
font-size: Typography.sizes.sm;
font-weight: Typography.weights.medium;
vertical-alignment: center;
animate color { duration: Animations.durations.fast; }
}
}
touch := TouchArea {
clicked => {
root.clicked();
}
moved => {
root.hovered = self.has-hover;
}
}
}
}
export component MihomoStatusBar {
in property <StatusState> status: StatusState.stopped;
in property <bool> loading: false;
callback toggle-clicked();
private property <string> status-text: status == StatusState.running ? "Running" :
status == StatusState.starting ? "Starting..." :
"Stopped";
private property <color> status-color: status == StatusState.running ? #22c55e :
status == StatusState.starting ? #eab308 :
#9ca3af;
Rectangle {
background: status == StatusState.starting ? #eab30820 :
status == StatusState.running ? #22c55e20 :
transparent;
border-radius: SpacingSystem.radius.md;
animate background { duration: Animations.durations.normal; }
HorizontalLayout {
padding: SpacingSystem.spacing.s3;
spacing: SpacingSystem.spacing.s3;
alignment: space-between;
HorizontalLayout {
spacing: SpacingSystem.spacing.s3;
StatusIndicator {
state: root.status;
}
VerticalLayout {
spacing: 2px;
Text {
text: "Clash";
color: root.status-color;
font-size: Typography.sizes.xs;
font-weight: Typography.weights.semibold;
}
Text {
text: root.status-text;
color: Theme.colors.muted-foreground;
font-size: 10px;
}
}
}
Button {
width: 32px;
height: 32px;
text: root.loading ? "..." : (status == StatusState.running ? "||" : ">");
variant: "ghost";
disabled: root.loading;
clicked => {
root.toggle-clicked();
}
}
}
}
}

44
ui/components/label.slint Normal file
View File

@@ -0,0 +1,44 @@
import { Theme, Typography } from "../theme/theme.slint";
export enum LabelSize {
xs,
sm,
base,
lg,
xl,
}
export enum LabelColor {
foreground,
muted,
primary,
secondary,
destructive,
}
export component Label {
in property <string> text: "";
in property <LabelSize> size: LabelSize.base;
in property <LabelColor> color-variant: LabelColor.foreground;
in property <int> font-weight: Typography.weights.normal;
private property <length> font-size: size == LabelSize.xs ? Typography.sizes.xs :
size == LabelSize.sm ? Typography.sizes.sm :
size == LabelSize.lg ? Typography.sizes.lg :
size == LabelSize.xl ? Typography.sizes.xl :
Typography.sizes.base;
private property <color> text-color: color-variant == LabelColor.muted ? Theme.colors.muted-foreground :
color-variant == LabelColor.primary ? Theme.colors.primary :
color-variant == LabelColor.secondary ? Theme.colors.secondary :
color-variant == LabelColor.destructive ? Theme.colors.destructive :
Theme.colors.foreground;
Text {
text: root.text;
color: root.text-color;
font-size: root.font-size;
font-weight: root.font-weight;
vertical-alignment: center;
}
}

View File

@@ -0,0 +1,36 @@
import { Theme, SpacingSystem, Typography, Animations } from "../theme/theme.slint";
export component Progress {
in property <float> value: 0; // 0-100
in property <bool> show-label: false;
min-height: 8px;
VerticalLayout {
spacing: SpacingSystem.spacing.s1;
// Progress bar
track := Rectangle {
height: 8px;
background: Theme.colors.primary.transparentize(0.8);
border-radius: SpacingSystem.radius.full;
indicator := Rectangle {
width: parent.width * Math.max(0, Math.min(100, root.value)) / 100;
height: parent.height;
background: Theme.colors.primary;
border-radius: SpacingSystem.radius.full;
animate width { duration: Animations.durations.normal; easing: Animations.ease-out; }
}
}
// Optional label
if root.show-label: label := Text {
text: Math.round(root.value) + "%";
color: Theme.colors.muted-foreground;
font-size: Typography.sizes.xs;
horizontal-alignment: right;
}
}
}

174
ui/components/select.slint Normal file
View File

@@ -0,0 +1,174 @@
import { Theme, SpacingSystem, Typography, Animations } from "../theme/theme.slint";
export struct SelectOption {
label: string,
value: string,
}
export component Select {
in property <[SelectOption]> options: [];
in-out property <int> selected-index: -1;
in property <string> placeholder: "Select...";
in property <bool> enabled: true;
callback selected(int, string);
private property <bool> open: false;
private property <bool> hovered: false;
min-width: 120px;
min-height: 36px;
states [
disabled when !root.enabled: {
trigger.opacity: 0.5;
}
]
VerticalLayout {
// Trigger button
trigger := Rectangle {
height: 36px;
background: Theme.colors.background;
border-radius: SpacingSystem.radius.md;
border-width: 1px;
border-color: Theme.colors.input;
drop-shadow-blur: 1px;
drop-shadow-color: #00000010;
drop-shadow-offset-y: 1px;
HorizontalLayout {
padding-left: SpacingSystem.spacing.s3;
padding-right: SpacingSystem.spacing.s3;
spacing: SpacingSystem.spacing.s2;
alignment: space-between;
value-text := Text {
text: root.selected-index >= 0 && root.selected-index < root.options.length
? root.options[root.selected-index].label
: root.placeholder;
color: root.selected-index >= 0
? Theme.colors.foreground
: Theme.colors.muted-foreground;
font-size: Typography.sizes.sm;
vertical-alignment: center;
}
chevron := Text {
text: "";
color: Theme.colors.muted-foreground;
font-size: Typography.sizes.base;
font-weight: Typography.weights.bold;
vertical-alignment: center;
width: 16px;
horizontal-alignment: center;
rotation-angle: root.open ? -90deg : 90deg;
rotation-origin-x: self.width / 2;
rotation-origin-y: self.height / 2;
animate rotation-angle { duration: Animations.durations.fast; easing: Animations.ease-out; }
}
}
touch := TouchArea {
enabled: root.enabled;
clicked => {
root.open = !root.open;
}
moved => {
root.hovered = self.has-hover;
}
}
}
// Dropdown menu
if root.open: dropdown := Rectangle {
y: trigger.height + 4px;
background: Theme.colors.background;
border-radius: SpacingSystem.radius.md;
border-width: 1px;
border-color: Theme.colors.border;
drop-shadow-blur: 8px;
drop-shadow-color: #00000020;
drop-shadow-offset-y: 4px;
VerticalLayout {
padding: SpacingSystem.spacing.s1;
for option[index] in root.options: SelectItem {
label: option.label;
selected: index == root.selected-index;
clicked => {
root.selected-index = index;
root.selected(index, option.value);
root.open = false;
}
}
}
}
}
}
component SelectItem {
in property <string> label: "";
in property <bool> selected: false;
callback clicked();
private property <bool> hovered: false;
height: 32px;
states [
selected when root.selected: {
container.background: Theme.colors.accent;
}
hovered when root.hovered && !root.selected: {
container.background: Theme.colors.accent.transparentize(0.5);
}
]
container := Rectanglen background: transparent;
border-radius: SpacingSystem.radius.sm;
animate background { duration: Animations.durations.fast; }
HorizontalLayout {
padding-left: SpacingSystem.spacing.s2;
padding-right: SpacingSystem.spacing.s2;
spacing: SpacingSystem.spacing.s2;
if root.selected: checkmark := Text {
text: "✓";
color: Theme.colors.primary;
font-size: Typography.sizes.sm;
font-weight: Typography.weights.bold;
vertical-alignment: center;
width: 16px;
}
Text {
text: root.label;
color: Theme.colors.foreground;
font-size: Typography.sizes.sm;
vertical-alignment: center;
}
}
touch := TouchArea {
clicked => {
root.clicked();
}
moved => {
root.hovered = self.has-hover;
}
}
}
}

View File

@@ -0,0 +1,20 @@
import { Theme } from "../theme/theme.slint";
export enum Orientation {
horizontal,
vertical,
}
export component Separator {
in property <Orientation> orientation: Orientation.horizontal;
if orientation == Orientation.horizontal: Rectangle {
height: 1px;
background: Theme.colors.border;
}
if orientation == Orientation.vertical: Rectangle {
width: 1px;
background: Theme.colors.border;
}
}

View File

@@ -0,0 +1,57 @@
import { Theme, Animations } from "../theme/theme.slint";
export enum StatusState {
stopped,
starting,
running,
}
export component StatusIndicator {
in property <StatusState> state: StatusState.stopped;
private property <color> dot-color: state == StatusState.running ? #22c55e :
state == StatusState.starting ? #eab308 :
#9ca3af;
private property <bool> should-animate: state == StatusState.running || state == StatusState.starting;
width: 10px;
height: 10px;
// Breathing animation ring
if root.should-animate: ping := Rectangle {
width: parent.width;
height: parent.height;
border-radius: 5px;
background: root.dot-color.transparentize(0.25);
// Animate scale and opacity for breathing effect
states [
pulse: {
ping.width: parent.width * 2;
ping.height: parent.height * 2;
ping.x: -parent.width / 2;
ping.y: -parent.height / 2;
ping.opacity: 0;
in {
animate width, height, x, y, opacity {
duration: 1500ms;
easing: cubic-bezier(0, 0, 0.2, 1);
iteration-count: -1;
}
}
}
]
}
// Main dot
dot := Rectangle {
width: parent.width;
height: parent.height;
border-radius: 5px;
background: root.dot-color;
animate background { duration: Animations.durations.normal; }
}
}

View File

@@ -0,0 +1,77 @@
import { Theme, SpacingSystem, Animations } from "../theme/theme.slint";
export component Switch {
in property <bool> checked: false;
in property <bool> enabled: true;
callback toggled(bool);
private property <bool> pressed: false;
private property <bool> hovered: false;
min-width: 32px;
min-height: 18px;
states [
disabled when !root.enabled: {
track.opacity: 0.5;
thumb.opacity: 0.5;
}
checked when root.checked: {
track.background: Theme.colors.primary;
thumb.x: root.width - thumb.width - 2px;
}
unchecked when !root.checked: {
track.background: Theme.colors.input;
thumb.x: 2px;
}
]
track := Rectangle {
width: 32px;
height: 18px;
border-radius: 9px;
background: Theme.colors.input;
border-width: 1px;
border-color: transparent;
animate background { duration: Animations.durations.fast; easing: Animations.ease-out; }
thumb := Rectangle {
width: 14px;
height: 14px;
y: (parent.height - self.height) / 2;
x: 2px;
border-radius: 7px;
background: Theme.colors.background;
drop-shadow-blur: 2px;
drop-shadow-color: #00000040;
drop-shadow-offset-y: 1px;
animate x { duration: Animations.durations.fast; easing: Animations.ease-out; }
}
}
touch := TouchArea {
enabled: root.enabled;
clicked => {
if (root.enabled) {
root.toggled(!root.checked);
}
}
pointer-event(event) => {
if (event.kind == PointerEventKind.down) {
root.pressed = true;
} else if (event.kind == PointerEventKind.up || event.kind == PointerEventKind.cancel) {
root.pressed = false;
}
}
moved => {
root.hovered = self.has-hover;
}
}
}

125
ui/components/tabs.slint Normal file
View File

@@ -0,0 +1,125 @@
import { Theme, SpacingSystem, Typography, Animations } from "../theme/theme.slint";
export struct TabItem {
title: string,
}
export component Tabs {
in property <[TabItem]> tabs: [];
in-out property <int> current-index: 0;
callback tab-changed(int);
min-height: 200px;
VerticalLayout {
spacing: SpacingSystem.spacing.s2;
// Tab List
tab-list := HorizontalLayout {
height: 36px;
spacing: 0;
Rectangle {
background: Theme.colors.muted;
border-radius: SpacingSystem.radius.lg;
padding: 3px;
HorizontalLayout {
spacing: 0;
padding: 0;
for tab[index] in root.tabs: TabTrigger {
text: tab.title;
active: index == root.current-index;
clicked => {
root.current-index = index;
root.tab-changed(index);
}
}
}
}
}
// Tab Content Area
content-area := Rectangle {
// Content will be provided by @children in parent component
@children
}
}
}
component TabTrigger {
in property <string> text: "";
in property <bool> active: false;
callback clicked();
private property <bool> hovered: false;
min-width: 60px;
height: 30px;
states [
active when root.active: {
container.background: Theme.colors.background;
label.color: Theme.colors.foreground;
container.border-color: Theme.colors.input;
}
inactive when !root.active: {
container.background: transparent;
label.color: Theme.colors.muted-foreground;
container.border-color: transparent;
}
hovered when root.hovered && !root.active: {
label.color: Theme.colors.foreground;
}
]
container := Rectangle {
border-radius: SpacingSystem.radius.md;
background: transparent;
border-width: 1px;
border-color: transparent;
drop-shadow-blur: root.active ? 2px : 0;
drop-shadow-color: root.active ? #00000010 : transparent;
drop-shadow-offset-y: root.active ? 1px : 0;
animate background { duration: Animations.durations.fast; }
animate border-color { duration: Animations.durations.fast; }
label := Text {
text: root.text;
color: Theme.colors.muted-foreground;
font-size: Typography.sizes.sm;
font-weight: Typography.weights.medium;
horizontal-alignment: center;
vertical-alignment: center;
animate color { duration: Animations.durations.fast; }
}
}
touch := TouchArea {
clicked => {
root.clicked();
}
moved => {
root.hovered = self.has-hover;
}
}
}
// TabContent component for wrapping content
export component TabContent {
in property <int> index: 0;
in property <int> current-index: 0;
visible: root.index == root.current-index;
VerticalLayout {
@children
}
}

View File

@@ -0,0 +1,42 @@
import { Theme, SpacingSystem, Typography, Animations } from "../theme/theme.slint";
export component Tooltip {
in property <string> text: "";
in property <bool> show: false;
if root.show: popup := Rectangle {
y: -self.height - 8px;
x: (parent.width - self.width) / 2;
width: tooltip-text.width + SpacingSystem.spacing.s3 * 2;
height: tooltip-text.height + SpacingSystem.spacing.s2 * 2;
background: Theme.colors.foreground;
border-radius: SpacingSystem.radius.sm;
drop-shadow-blur: 8px;
drop-shadow-color: #00000030;
drop-shadow-offset-y: 2px;
opacity: root.show ? 1 : 0;
animate opacity { duration: Animations.durations.fast; }
tooltip-text := Text {
text: root.text;
color: Theme.colors.background;
font-size: Typography.sizes.xs;
horizontal-alignment: center;
vertical-alignment: center;
x: SpacingSystem.spacing.s3;
y: SpacingSystem.spacing.s2;
}
// Arrow
arrow := Path {
y: parent.height;
x: (parent.width - 8px) / 2;
width: 8px;
height: 4px;
fill: Theme.colors.foreground;
commands: "M 0 0 L 4 4 L 8 0 Z";
}
}
}

View File

@@ -0,0 +1,967 @@
import { Theme, SpacingSystem, Typography } from "./theme/theme.slint";
import { Button } from "./components/button.slint";
import { Card } from "./components/card.slint";
import { Switch } from "./components/switch.slint";
import { Badge } from "./components/badge.slint";
import { Progress } from "./components/progress.slint";
import { ScrollView } from "std-widgets.slint";
// Home Page - Redesigned with better UI
export component HomePage {
VerticalLayout {
padding: 24px;
spacing: 20px;
Text {
text: "Home";
font-size: 28px;
font-weight: 700;
color: Theme.colors.foreground;
}
ScrollView {
VerticalLayout {
spacing: 20px;
// Profile Card
Card {
VerticalLayout {
spacing: 16px;
HorizontalLayout {
spacing: 16px;
Rectangle {
width: 56px;
height: 56px;
border-radius: 12px;
background: Theme.colors.primary.transparentize(0.9);
Text {
text: "☁";
color: Theme.colors.primary;
font-size: 28px;
horizontal-alignment: center;
vertical-alignment: center;
}
}
VerticalLayout {
spacing: 6px;
HorizontalLayout {
spacing: 10px;
Text {
text: "NanoCloud";
color: Theme.colors.foreground;
font-size: 20px;
font-weight: 700;
vertical-alignment: center;
}
}
HorizontalLayout {
spacing: 8px;
Text {
text: "🌐 Free-Japan1-Ver.7";
color: Theme.colors.muted-foreground;
font-size: 13px;
vertical-alignment: center;
}
Badge {
text: "Vmess";
variant: "outline";
}
Badge {
text: "UDP";
variant: "secondary";
}
}
}
Rectangle {
horizontal-stretch: 1;
}
Button {
text: "↻";
variant: "ghost";
width: 40px;
height: 40px;
}
}
Rectangle {
height: 1px;
background: Theme.colors.border;
}
GridLayout {
spacing: 20px;
Row {
VerticalLayout {
spacing: 6px;
Text {
text: "Usage / Total";
color: Theme.colors.muted-foreground;
font-size: 12px;
font-weight: 600;
}
Text {
text: "1.26GB / 100GB";
color: Theme.colors.foreground;
font-size: 16px;
font-weight: 600;
}
Progress {
value: 1.26;
}
}
VerticalLayout {
spacing: 6px;
Text {
text: "Expiry Date";
color: Theme.colors.muted-foreground;
font-size: 12px;
font-weight: 600;
}
Text {
text: "2025-11-11";
color: Theme.colors.foreground;
font-size: 16px;
font-weight: 600;
}
}
VerticalLayout {
spacing: 6px;
Text {
text: "Last Update";
color: Theme.colors.muted-foreground;
font-size: 12px;
font-weight: 600;
}
Text {
text: "2025-10-12";
color: Theme.colors.foreground;
font-size: 16px;
font-weight: 600;
}
}
}
}
}
}
// Location Card
Card {
VerticalLayout {
spacing: 16px;
Text {
text: "Location Info";
font-size: 16px;
font-weight: 600;
color: Theme.colors.foreground;
}
HorizontalLayout {
spacing: 32px;
VerticalLayout {
spacing: 6px;
Text {
text: "IP Address";
color: Theme.colors.muted-foreground;
font-size: 12px;
font-weight: 600;
}
Text {
text: "47.238.198.100";
color: Theme.colors.foreground;
font-size: 14px;
font-weight: 500;
}
}
VerticalLayout {
spacing: 6px;
Text {
text: "Location";
color: Theme.colors.muted-foreground;
font-size: 12px;
font-weight: 600;
}
Text {
text: "Japan · Tokyo";
color: Theme.colors.foreground;
font-size: 14px;
font-weight: 500;
}
}
}
}
}
// Quick Settings Card
Card {
VerticalLayout {
spacing: 0;
Text {
text: "Quick Settings";
font-size: 16px;
font-weight: 600;
color: Theme.colors.foreground;
padding-bottom: 12px;
}
SettingRow {
label: "TUN Mode";
Switch {
checked: false;
}
}
Rectangle {
height: 1px;
background: Theme.colors.border;
}
SettingRow {
label: "System Proxy";
Switch {
checked: false;
}
}
Rectangle {
height: 1px;
background: Theme.colors.border;
}
SettingRow {
label: "Proxy Port";
Text {
text: "7890";
color: Theme.colors.primary;
font-size: 14px;
font-weight: 600;
vertical-alignment: center;
}
}
}
}
}
}
}
}
component SettingRow {
in property <string> label: "";
height: 52px;
HorizontalLayout {
spacing: 12px;
alignment: space-between;
Text {
text: root.label;
color: Theme.colors.foreground;
font-size: 14px;
font-weight: 500;
vertical-alignment: center;
}
@children
}
}
// Profiles Page
export component ProfilesPage {
VerticalLayout {
padding: 24px;
spacing: 20px;
HorizontalLayout {
spacing: 12px;
alignment: space-between;
Text {
text: "Profiles";
font-size: 28px;
font-weight: 700;
color: Theme.colors.foreground;
vertical-alignment: center;
}
HorizontalLayout {
spacing: 8px;
Button {
text: "+ Add";
variant: "default";
}
Button {
text: "↻";
variant: "ghost";
}
}
}
ScrollView {
GridLayout {
spacing: 16px;
Row {
ProfileCard {
name: "NanoCloud";
type-icon: "☁";
description: "Free tier subscription - Japan servers";
is-remote: true;
usage: 1.26;
total: 100.0;
updated: "Just now";
}
ProfileCard {
name: "Local Config";
type-icon: "📄";
description: "Custom local configuration file";
is-remote: false;
updated: "2025-01-15";
}
ProfileCard {
name: "Premium VPN";
type-icon: "☁";
description: "Premium subscription with global servers";
is-remote: true;
usage: 45.8;
total: 500.0;
updated: "2 hours ago";
}
}
}
}
}
}
component ProfileCard {
in property <string> name: "";
in property <string> type-icon: "";
in property <string> description: "";
in property <bool> is-remote: false;
in property <float> usage: 0;
in property <float> total: 100;
in property <string> updated: "";
min-width: 300px;
height: 140px;
Card {
VerticalLayout {
spacing: 10px;
HorizontalLayout {
spacing: 10px;
Text {
text: root.type-icon;
font-size: 20px;
vertical-alignment: center;
}
Text {
text: root.name;
color: Theme.colors.foreground;
font-size: 16px;
font-weight: 600;
vertical-alignment: center;
}
Rectangle {
horizontal-stretch: 1;
}
if root.is-remote: Button {
text: "↻";
variant: "ghost";
width: 28px;
height: 28px;
}
}
Text {
text: root.description;
color: Theme.colors.muted-foreground;
font-size: 13px;
overflow: elide;
}
if root.is-remote: VerticalLayout {
spacing: 6px;
Progress {
value: (root.usage / root.total) * 100;
}
HorizontalLayout {
alignment: space-between;
Text {
text: "Updated: " + root.updated;
color: Theme.colors.muted-foreground;
font-size: 11px;
}
Text {
text: root.usage + " / " + root.total + " GB";
color: Theme.colors.foreground;
font-size: 11px;
font-weight: 600;
}
}
}
if !root.is-remote: Text {
text: "Local Profile";
color: Theme.colors.muted-foreground;
font-size: 11px;
}
}
}
}
// Logs Page
export component LogsPage {
VerticalLayout {
padding: 24px;
spacing: 20px;
Text {
text: "Logs";
font-size: 28px;
font-weight: 700;
color: Theme.colors.foreground;
}
Card {
height: 500px;
ScrollView {
VerticalLayout {
spacing: 0;
LogRow {
time: "10:23:45";
level: "info";
message: "Mihomo core started successfully";
}
LogRow {
time: "10:23:46";
level: "info";
message: "HTTP proxy listening on 127.0.0.1:7890";
}
LogRow {
time: "10:23:47";
level: "debug";
message: "Loading configuration from config.yaml";
}
LogRow {
time: "10:23:48";
level: "info";
message: "Profile loaded: NanoCloud";
}
LogRow {
time: "10:23:50";
level: "warn";
message: "DNS resolution timeout for example.com";
}
LogRow {
time: "10:23:51";
level: "error";
message: "Failed to connect to proxy server";
}
LogRow {
time: "10:23:52";
level: "info";
message: "Retrying connection...";
}
LogRow {
time: "10:23:53";
level: "info";
message: "Connection established successfully";
}
}
}
}
}
}
component LogRow {
in property <string> time: "";
in property <string> level: "";
in property <string> message: "";
private property <color> level-color: level == "error" ? #ef4444 :
level == "warn" ? #eab308 :
level == "info" ? #3b82f6 :
#6b7280;
height: 28px;
HorizontalLayout {
padding-left: 12px;
padding-right: 12px;
spacing: 12px;
Text {
text: root.time;
color: Theme.colors.muted-foreground;
font-size: 12px;
font-family: "monospace";
vertical-alignment: center;
width: 80px;
}
Text {
text: root.level;
color: root.level-color;
font-size: 12px;
font-family: "monospace";
font-weight: 700;
vertical-alignment: center;
width: 70px;
}
Text {
text: root.message;
color: Theme.colors.foreground;
font-size: 12px;
font-family: "monospace";
vertical-alignment: center;
}
}
}
// Connections Page
export component ConnectionsPage {
VerticalLayout {
padding: 24px;
spacing: 20px;
Text {
text: "Connections";
font-size: 28px;
font-weight: 700;
color: Theme.colors.foreground;
}
ScrollView {
VerticalLayout {
spacing: 12px;
ConnectionItem {
source: "192.168.1.100:54321";
destination: "github.com:443";
protocol: "HTTPS";
upload: "1.2 KB/s";
download: "45.6 KB/s";
duration: "00:02:15";
}
ConnectionItem {
source: "192.168.1.100:54322";
destination: "google.com:443";
protocol: "HTTPS";
upload: "0.5 KB/s";
download: "12.3 KB/s";
duration: "00:01:30";
}
ConnectionItem {
source: "192.168.1.100:54323";
destination: "youtube.com:443";
protocol: "HTTPS";
upload: "2.1 KB/s";
download: "256.7 KB/s";
duration: "00:05:42";
}
}
}
}
}
component ConnectionItem {
in property <string> source: "";
in property <string> destination: "";
in property <string> protocol: "";
in property <string> upload: "";
in property <string> download: "";
in property <string> duration: "";
height: 72px;
Card {
HorizontalLayout {
spacing: 16px;
Rectangle {
width: 48px;
height: 48px;
border-radius: 10px;
background: Theme.colors.primary.transparentize(0.9);
Text {
text: "🔗";
font-size: 20px;
horizontal-alignment: center;
vertical-alignment: center;
}
}
VerticalLayout {
spacing: 6px;
Text {
text: root.source + " → " + root.destination;
color: Theme.colors.foreground;
font-size: 14px;
font-weight: 500;
overflow: elide;
}
HorizontalLayout {
spacing: 16px;
Text {
text: root.protocol;
color: Theme.colors.muted-foreground;
font-size: 12px;
}
Text {
text: "↑ " + root.upload;
color: Theme.colors.primary;
font-size: 12px;
font-weight: 600;
}
Text {
text: "↓ " + root.download;
color: #22c55e;
font-size: 12px;
font-weight: 600;
}
Text {
text: root.duration;
color: Theme.colors.muted-foreground;
font-size: 12px;
}
}
}
}
}
}
// Settings Page
export component SettingsPage {
VerticalLayout {
padding: 24px;
spacing: 20px;
Text {
text: "Settings";
font-size: 28px;
font-weight: 700;
color: Theme.colors.foreground;
}
ScrollView {
VerticalLayout {
spacing: 16px;
Card {
VerticalLayout {
spacing: 0;
Text {
text: "App Settings";
font-size: 16px;
font-weight: 600;
color: Theme.colors.foreground;
padding-bottom: 12px;
}
SettingRow {
label: "Auto Start";
Switch {
checked: false;
}
}
Rectangle {
height: 1px;
background: Theme.colors.border;
}
SettingRow {
label: "Silent Start";
Switch {
checked: false;
}
}
Rectangle {
height: 1px;
background: Theme.colors.border;
}
SettingRow {
label: "Clash Core";
Switch {
checked: true;
}
}
}
}
Card {
VerticalLayout {
spacing: 0;
Text {
text: "Clash Settings";
font-size: 16px;
font-weight: 600;
color: Theme.colors.foreground;
padding-bottom: 12px;
}
SettingRow {
label: "Allow LAN";
Switch {
checked: false;
}
}
Rectangle {
height: 1px;
background: Theme.colors.border;
}
SettingRow {
label: "IPv6";
Switch {
checked: false;
}
}
Rectangle {
height: 1px;
background: Theme.colors.border;
}
SettingRow {
label: "Unified Delay";
Switch {
checked: true;
}
}
}
}
Card {
VerticalLayout {
spacing: 12px;
Text {
text: "About";
font-size: 16px;
font-weight: 600;
color: Theme.colors.foreground;
}
HorizontalLayout {
spacing: 12px;
alignment: space-between;
Text {
text: "App Version";
color: Theme.colors.muted-foreground;
font-size: 14px;
}
Text {
text: "1.0.0";
color: Theme.colors.foreground;
font-size: 14px;
font-weight: 600;
}
}
}
}
}
}
}
}
// Proxies Page
export component ProxiesPage {
VerticalLayout {
padding: 24px;
spacing: 20px;
Text {
text: "Proxies";
font-size: 28px;
font-weight: 700;
color: Theme.colors.foreground;
}
ScrollView {
VerticalLayout {
spacing: 16px;
Card {
VerticalLayout {
spacing: 12px;
Text {
text: "Hong Kong";
font-size: 16px;
font-weight: 600;
color: Theme.colors.foreground;
}
GridLayout {
spacing: 12px;
Row {
ProxyNode {
name: "HK-01";
type-text: "Vmess";
latency: "45ms";
}
ProxyNode {
name: "HK-02";
type-text: "Vmess";
latency: "52ms";
}
ProxyNode {
name: "HK-03";
type-text: "Trojan";
latency: "38ms";
}
}
}
}
}
Card {
VerticalLayout {
spacing: 12px;
Text {
text: "Japan";
font-size: 16px;
font-weight: 600;
color: Theme.colors.foreground;
}
GridLayout {
spacing: 12px;
Row {
ProxyNode {
name: "JP-Tokyo-01";
type-text: "Vmess";
latency: "122ms";
}
ProxyNode {
name: "JP-Tokyo-02";
type-text: "SS";
latency: "115ms";
}
ProxyNode {
name: "JP-Osaka-01";
type-text: "Vmess";
latency: "135ms";
}
}
}
}
}
}
}
}
}
component ProxyNode {
in property <string> name: "";
in property <string> type-text: "";
in property <string> latency: "";
min-width: 200px;
height: 70px;
Card {
HorizontalLayout {
spacing: 12px;
alignment: space-between;
VerticalLayout {
spacing: 6px;
Text {
text: root.name;
color: Theme.colors.foreground;
font-size: 14px;
font-weight: 600;
}
Text {
text: root.type-text;
color: Theme.colors.muted-foreground;
font-size: 12px;
}
}
Text {
text: root.latency;
color: Theme.colors.primary;
font-size: 14px;
font-weight: 600;
vertical-alignment: center;
}
}
}
}

799
ui/pages-complete.slint Normal file
View File

@@ -0,0 +1,799 @@
import { Theme, SpacingSystem, Typography } from "./theme/theme.slint";
import { Button } from "./components/button.slint";
import { Card } from "./components/card.slint";
import { Switch } from "./components/switch.slint";
import { Badge } from "./components/badge.slint";
import { Progress } from "./components/progress.slint";
import { ScrollView } from "std-widgets.slint";
// Helper components - must be defined before they are used
component SettingRow {
in property <string> label: "";
height: 52px;
HorizontalLayout {
spacing: 12px;
alignment: space-between;
Text {
text: root.label;
color: Theme.colors.foreground;
font-size: 14px;
font-weight: 500;
vertical-alignment: center;
}
@children
}
}
component ProfileCard {
in property <string> name: "";
in property <string> type-icon: "";
in property <string> description: "";
in property <bool> is-remote: false;
in property <float> usage: 0;
in property <float> total: 100;
in property <string> updated: "";
min-width: 300px;
height: 140px;
Card {
VerticalLayout {
spacing: 10px;
HorizontalLayout {
spacing: 10px;
Text {
text: root.type-icon;
font-size: 20px;
vertical-alignment: center;
}
Text {
text: root.name;
color: Theme.colors.foreground;
font-size: 16px;
font-weight: 600;
vertical-alignment: center;
}
Rectangle { horizontal-stretch: 1; }
if root.is-remote: Button {
text: "↻";
variant: "ghost";
width: 28px;
height: 28px;
}
}
Text {
text: root.description;
color: Theme.colors.muted-foreground;
font-size: 13px;
overflow: elide;
}
if root.is-remote: VerticalLayout {
spacing: 6px;
Progress {
value: (root.usage / root.total) * 100;
}
HorizontalLayout {
alignment: space-between;
Text {
text: "Updated: " + root.updated;
color: Theme.colors.muted-foreground;
font-size: 11px;
}
Text {
text: root.usage + " / " + root.total + " GB";
color: Theme.colors.foreground;
font-size: 11px;
font-weight: 600;
}
}
}
if !root.is-remote: Text {
text: "Local Profile";
color: Theme.colors.muted-foreground;
font-size: 11px;
}
}
}
}
component LogRow {
in property <string> time: "";
in property <string> level: "";
in property <string> message: "";
private property <color> level-color: level == "error" ? #ef4444 :
level == "warn" ? #eab308 :
level == "info" ? #3b82f6 : #6b7280;
height: 28px;
HorizontalLayout {
padding-left: 12px;
padding-right: 12px;
spacing: 12px;
Text {
text: root.time;
color: Theme.colors.muted-foreground;
font-size: 12px;
font-family: "monospace";
vertical-alignment: center;
width: 80px;
}
Text {
text: root.level;
color: root.level-color;
font-size: 12px;
font-family: "monospace";
font-weight: 700;
vertical-alignment: center;
width: 70px;
}
Text {
text: root.message;
color: Theme.colors.foreground;
font-size: 12px;
font-family: "monospace";
vertical-alignment: center;
}
}
}
component ConnectionItem {
in property <string> source: "";
in property <string> destination: "";
in property <string> protocol: "";
in property <string> upload: "";
in property <string> download: "";
in property <string> duration: "";
height: 72px;
Card {
HorizontalLayout {
spacing: 16px;
Rectangle {
width: 48px;
height: 48px;
border-radius: 10px;
background: Theme.colors.primary.transparentize(0.9);
Text {
text: "🔗";
font-size: 20px;
horizontal-alignment: center;
vertical-alignment: center;
}
}
VerticalLayout {
spacing: 6px;
Text {
text: root.source + " → " + root.destination;
color: Theme.colors.foreground;
font-size: 14px;
font-weight: 500;
overflow: elide;
}
HorizontalLayout {
spacing: 16px;
Text {
text: root.protocol;
color: Theme.colors.muted-foreground;
font-size: 12px;
}
Text {
text: "↑ " + root.upload;
color: Theme.colors.primary;
font-size: 12px;
font-weight: 600;
}
Text {
text: "↓ " + root.download;
color: #22c55e;
font-size: 12px;
font-weight: 600;
}
Text {
text: root.duration;
color: Theme.colors.muted-foreground;
font-size: 12px;
}
}
}
}
}
}
component ProxyNode {
in property <string> name: "";
in property <string> type-text: "";
in property <string> latency: "";
min-width: 200px;
height: 70px;
Card {
HorizontalLayout {
spacing: 12px;
alignment: space-between;
VerticalLayout {
spacing: 6px;
Text {
text: root.name;
color: Theme.colors.foreground;
font-size: 14px;
font-weight: 600;
}
Text {
text: root.type-text;
color: Theme.colors.muted-foreground;
font-size: 12px;
}
}
Text {
text: root.latency;
color: Theme.colors.primary;
font-size: 14px;
font-weight: 600;
vertical-alignment: center;
}
}
}
}
// Exported page components
export component HomePage {
VerticalLayout {
padding: 24px;
spacing: 20px;
Text {
text: "Home";
font-size: 28px;
font-weight: 700;
color: Theme.colors.foreground;
}
ScrollView {
VerticalLayout {
spacing: 20px;
Card {
VerticalLayout {
spacing: 16px;
HorizontalLayout {
spacing: 16px;
Rectangle {
width: 56px;
height: 56px;
border-radius: 12px;
background: Theme.colors.primary.transparentize(0.9);
Text {
text: "☁";
color: Theme.colors.primary;
font-size: 28px;
horizontal-alignment: center;
vertical-alignment: center;
}
}
VerticalLayout {
spacing: 6px;
HorizontalLayout {
spacing: 10px;
Text {
text: "NanoCloud";
color: Theme.colors.foreground;
font-size: 20px;
font-weight: 700;
vertical-alignment: center;
}
}
HorizontalLayout {
spacing: 8px;
Text {
text: "🌐 Free-Japan1-Ver.7";
color: Theme.colors.muted-foreground;
font-size: 13px;
vertical-alignment: center;
}
Badge {
text: "Vmess";
variant: "outline";
}
Badge {
text: "UDP";
variant: "secondary";
}
}
}
Rectangle { horizontal-stretch: 1; }
Button {
text: "↻";
variant: "ghost";
width: 40px;
height: 40px;
}
}
Rectangle {
height: 1px;
background: Theme.colors.border;
}
GridLayout {
spacing: 20px;
Row {
VerticalLayout {
spacing: 6px;
Text {
text: "Usage / Total";
color: Theme.colors.muted-foreground;
font-size: 12px;
font-weight: 600;
}
Text {
text: "1.26GB / 100GB";
color: Theme.colors.foreground;
font-size: 16px;
font-weight: 600;
}
Progress { value: 1.26; }
}
VerticalLayout {
spacing: 6px;
Text {
text: "Expiry Date";
color: Theme.colors.muted-foreground;
font-size: 12px;
font-weight: 600;
}
Text {
text: "2025-11-11";
color: Theme.colors.foreground;
font-size: 16px;
font-weight: 600;
}
}
VerticalLayout {
spacing: 6px;
Text {
text: "Last Update";
color: Theme.colors.muted-foreground;
font-size: 12px;
font-weight: 600;
}
Text {
text: "2025-10-12";
color: Theme.colors.foreground;
font-size: 16px;
font-weight: 600;
}
}
}
}
}
}
Card {
VerticalLayout {
spacing: 16px;
Text {
text: "Location Info";
font-size: 16px;
font-weight: 600;
color: Theme.colors.foreground;
}
HorizontalLayout {
spacing: 32px;
VerticalLayout {
spacing: 6px;
Text {
text: "IP Address";
color: Theme.colors.muted-foreground;
font-size: 12px;
font-weight: 600;
}
Text {
text: "47.238.198.100";
color: Theme.colors.foreground;
font-size: 14px;
font-weight: 500;
}
}
VerticalLayout {
spacing: 6px;
Text {
text: "Location";
color: Theme.colors.muted-foreground;
font-size: 12px;
font-weight: 600;
}
Text {
text: "Japan · Tokyo";
color: Theme.colors.foreground;
font-size: 14px;
font-weight: 500;
}
}
}
}
}
Card {
VerticalLayout {
spacing: 0;
Text {
text: "Quick Settings";
font-size: 16px;
font-weight: 600;
color: Theme.colors.foreground;
padding-bottom: 12px;
}
SettingRow {
label: "TUN Mode";
Switch { checked: false; }
}
Rectangle {
height: 1px;
background: Theme.colors.border;
}
SettingRow {
label: "System Proxy";
Switch { checked: false; }
}
Rectangle {
height: 1px;
background: Theme.colors.border;
}
SettingRow {
label: "Proxy Port";
Text {
text: "7890";
color: Theme.colors.primary;
font-size: 14px;
font-weight: 600;
vertical-alignment: center;
}
}
}
}
}
}
}
}
export component ProfilesPage {
VerticalLayout {
padding: 24px;
spacing: 20px;
HorizontalLayout {
spacing: 12px;
alignment: space-between;
Text {
text: "Profiles";
font-size: 28px;
font-weight: 700;
color: Theme.colors.foreground;
vertical-alignment: center;
}
HorizontalLayout {
spacing: 8px;
Button {
text: "+ Add";
variant: "default";
}
Button {
text: "↻";
variant: "ghost";
}
}
}
ScrollView {
GridLayout {
spacing: 16px;
Row {
ProfileCard {
name: "NanoCloud";
type-icon: "☁";
description: "Free tier subscription - Japan servers";
is-remote: true;
usage: 1.26;
total: 100.0;
updated: "Just now";
}
ProfileCard {
name: "Local Config";
type-icon: "📄";
description: "Custom local configuration file";
is-remote: false;
updated: "2025-01-15";
}
ProfileCard {
name: "Premium VPN";
type-icon: "☁";
description: "Premium subscription with global servers";
is-remote: true;
usage: 45.8;
total: 500.0;
updated: "2 hours ago";
}
}
}
}
}
}
export component LogsPage {
VerticalLayout {
padding: 24px;
spacing: 20px;
Text {
text: "Logs";
font-size: 28px;
font-weight: 700;
color: Theme.colors.foreground;
}
Card {
height: 500px;
ScrollView {
VerticalLayout {
spacing: 0;
LogRow {
time: "10:23:45";
level: "info";
message: "Mihomo core started successfully";
}
LogRow {
time: "10:23:46";
level: "info";
message: "HTTP proxy listening on 127.0.0.1:7890";
}
LogRow {
time: "10:23:47";
level: "debug";
message: "Loading configuration from config.yaml";
}
LogRow {
time: "10:23:48";
level: "info";
message: "Profile loaded: NanoCloud";
}
LogRow {
time: "10:23:50";
level: "warn";
message: "DNS resolution timeout for example.com";
}
LogRow {
time: "10:23:51";
level: "error";
message: "Failed to connect to proxy server";
}
LogRow {
time: "10:23:52";
level: "info";
message: "Retrying connection...";
}
LogRow {
time: "10:23:53";
level: "info";
message: "Connection established successfully";
}
}
}
}
}
}
export component ConnectionsPage {
VerticalLayout {
padding: 24px;
spacing: 20px;
Text {
text: "Connections";
font-size: 28px;
font-weight: 700;
color: Theme.colors.foreground;
}
ScrollView {
VerticalLayout {
spacing: 12px;
ConnectionItem {
source: "192.168.1.100:54321";
destination: "github.com:443";
protocol: "HTTPS";
upload: "1.2 KB/s";
download: "45.6 KB/s";
duration: "00:02:15";
}
ConnectionItem {
source: "192.168.1.100:54322";
destination: "google.com:443";
protocol: "HTTPS";
upload: "0.5 KB/s";
download: "12.3 KB/s";
duration: "00:01:30";
}
ConnectionItem {
source: "192.168.1.100:54323";
destination: "youtube.com:443";
protocol: "HTTPS";
upload: "2.1 KB/s";
download: "256.7 KB/s";
duration: "00:05:42";
}
}
}
}
}
export component SettingsPage {
VerticalLayout {
padding: 24px;
spacing: 20px;
Text {
text: "Settings";
font-size: 28px;
font-weight: 700;
color: Theme.colors.foreground;
}
ScrollView {
VerticalLayout {
spacing: 16px;
Card {
VerticalLayout {
spacing: 0;
Text {
text: "App Settings";
font-size: 16px;
font-weight: 600;
color: Theme.colors.foreground;
padding-bottom: 12px;
}
SettingRow {
label: "Auto Start";
Switch { checked: false; }
}
Rectangle {
height: 1px;
background: Theme.colors.border;
}
SettingRow {
label: "Silent Start";
Switch { checked: false; }
}
Rectangle {
height: 1px;
background: Theme.colors.border;
}
SettingRow {
label: "Clash Core";
Switch { checked: true; }
}
}
}
Card {
VerticalLayout {
spacing: 0;
Text {
text: "Clash Settings";
font-size: 16px;
font-weight: 600;
color: Theme.colors.foreground;
padding-bottom: 12px;
}
SettingRow {
label: "Allow LAN";
Switch { checked: false; }
}
Rectangle {
height: 1px;
background: Theme.colors.border;
}
SettingRow {
label: "IPv6";
Switch { checked: false; }
}
Rectangle {
height: 1px;
background: Theme.colors.border;
}
SettingRow {
label: "Unified Delay";
Switch { checked: true; }
}
}
}
Card {
VerticalLayout {
spacing: 12px;
Text {
text: "About";
font-size: 16px;
font-weight: 600;
color: Theme.colors.foreground;
}
HorizontalLayout {
spacing: 12px;
alignment: space-between;
Text {
text: "App Version";
color: Theme.colors.muted-foreground;
font-size: 14px;
}
Text {
text: "1.0.0";
color: Theme.colors.foreground;
font-size: 14px;
font-weight: 600;
}
}
}
}
}
}
}
}
export component ProxiesPage {
VerticalLayout {
padding: 24px;
spacing: 20px;
Text {
text: "Proxies";
font-size: 28px;
font-weight: 700;
color: Theme.colors.foreground;
}
ScrollView {
VerticalLayout {
spacing: 16px;
Card {
VerticalLayout {
spacing: 12px;
Text {
text: "Hong Kong";
font-size: 16px;
font-weight: 600;
color: Theme.colors.foreground;
}
GridLayout {
spacing: 12px;
Row {
ProxyNode {
name: "HK-01";
type-text: "Vmess";
latency: "45ms";
}
ProxyNode {
name: "HK-02";
type-text: "Vmess";
latency: "52ms";
}
ProxyNode {
name: "HK-03";
type-text: "Trojan";
latency: "38ms";
}
}
}
}
}
Card {
VerticalLayout {
spacing: 12px;
Text {
text: "Japan";
font-size: 16px;
font-weight: 600;
color: Theme.colors.foreground;
}
GridLayout {
spacing: 12px;
Row {
ProxyNode {
name: "JP-Tokyo-01";
type-text: "Vmess";
latency: "122ms";
}
ProxyNode {
name: "JP-Tokyo-02";
type-text: "SS";
latency: "115ms";
}
ProxyNode {
name: "JP-Osaka-01";
type-text: "Vmess";
latency: "135ms";
}
}
}
}
}
}
}
}
}

131
ui/pages/connections.slint Normal file
View File

@@ -0,0 +1,131 @@
import { Theme, SpacingSystem, Typography } from "../theme/theme.slint";
export struct Connection {
source: string,
destination: string,
protocol: string,
upload: string,
download: string,
duration: string,
}
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;
}
}
}
}
}
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;
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;
}
}
}
}

519
ui/pages/home.slint Normal file
View File

@@ -0,0 +1,519 @@
import { Theme, SpacingSystem, Typography } from "../theme/theme.slint";
import { Card } from "../components/card.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";
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;
}
ScrollView {
VerticalLayout {
spacing: SpacingSystem.spacing.s3;
// 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();
}
}
}
}
}
}
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;
}
}
// Info
VerticalLayout {
spacing: SpacingSystem.spacing.s1;
HorizontalLayout {
spacing: SpacingSystem.spacing.s2;
Text {
text: root.profile-name;
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;
}
Text {
text: "IP";
color: Theme.colors.muted-foreground;
font-size: Typography.sizes.xs;
font-weight: Typography.weights.semibold;
vertical-alignment: center;
}
}
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
}
}

170
ui/pages/logs.slint Normal file
View File

@@ -0,0 +1,170 @@
import { Theme, SpacingSystem, Typography } from "../theme/theme.slint";
import { Tabs, TabItem, TabContent } from "../components/tabs.slint";
export struct LogEntry {
time: string,
level: string, // "debug", "info", "warn", "error"
message: string,
}
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;
}
}
}
}
}
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;
}
}
}
}
}
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;
}
}
}
}

204
ui/pages/profiles.slint Normal file
View File

@@ -0,0 +1,204 @@
import { Theme, SpacingSystem, Typography } from "../theme/theme.slint";
import { Card } from "../components/card.slint";
import { Button } from "../components/button.slint";
import { Badge } from "../components/badge.slint";
import { Progress } from "../components/progress.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 {
in property <[ProfileData]> profiles: [];
in property <string> selected-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
HorizontalLayout {
spacing: SpacingSystem.spacing.s2;
alignment: space-between;
HorizontalLayout {
spacing: SpacingSystem.spacing.s2;
Text {
text: root.profile-data.type == "remote" ? "☁" : "📄";
font-size: Typography.sizes.lg;
vertical-alignment: center;
}
{
text: root.profile-data.name;
color: Theme.colors.foreground;
font-size: Typography.sizes.base;
font-weight: Typography.weights.semibold;
vertical-alignment: center;
}
}
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;
}
}
}
}

226
ui/pages/proxies.slint Normal file
View File

@@ -0,0 +1,226 @@
import { Theme, SpacingSystem, Typography } from "../theme/theme.slint";
import { Tabs, TabItem, TabContent } from "../components/tabs.slint";
import { Accordion, AccordionItemData, AccordionContent } from "../components/accordion.slint";
import { Item } from "../components/item.slint";
export struct ProxyNode {
name: string,
type: string,
latency: string,
}
export struct ProxyGroup {
name: string,
nodes: [ProxyNode],
expanded: bool,
}
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;
}
}
// 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 {
VerticalLayout {
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);
}
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);
}
}
}
}
}
}
}
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;
}
}
}
}

336
ui/pages/settings.slint Normal file
View File

@@ -0,0 +1,336 @@
import { Theme, SpacingSystem, Typography } from "../theme/theme.slint";
import { Card } from "../components/card.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";
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;
}
}
}
}
}
}
}
}
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;
}
}
@children
}
}

View File

@@ -4,6 +4,7 @@
import { ColorPalette, LightColors, DarkColors } from "colors.slint";
import { Typography } from "typography.slint";
import { SpacingSystem } from "spacing.slint";
import { Animations } from "../utils/animations.slint";
// Global theme manager (singleton)
export global Theme {
@@ -23,4 +24,4 @@ export global Theme {
}
// Re-export for convenience
export { Typography, SpacingSystem }
export { Typography, SpacingSystem, Animations }