From a4e6557d1b8854cfb3287e010efe81f60c2ce0ef Mon Sep 17 00:00:00 2001 From: me Date: Fri, 30 Jan 2026 20:32:37 +0800 Subject: [PATCH] - --- build.rs | 2 +- src/main.rs | 464 +++++++++++++++- ui/app-complete.slint | 0 ui/app-full.slint | 243 --------- ui/app-nav-backup.slint | 366 ------------- ui/app-nav.slint | 366 ------------- ui/app-simple-old.slint | 884 ------------------------------ ui/app-simple.slint | 5 - ui/app-simple.slint.bak | 884 ------------------------------ ui/app.slint | 452 ++++++++------- ui/components/accordion.slint | 3 +- ui/components/empty.slint | 40 ++ ui/components/switch.slint | 3 +- ui/layouts/container.slint | 11 + ui/layouts/page-layout.slint | 52 ++ ui/pages-complete-backup.slint | 967 --------------------------------- ui/pages-complete.slint | 799 --------------------------- ui/pages/connections.slint | 163 ++---- ui/pages/home.slint | 853 ++++++++++++----------------- ui/pages/logs.slint | 361 ++++++------ ui/pages/profiles.slint | 392 ++++++------- ui/pages/proxies.slint | 467 +++++++++------- ui/pages/settings.slint | 590 +++++++++----------- ui/types.slint | 57 ++ 24 files changed, 2189 insertions(+), 6235 deletions(-) delete mode 100644 ui/app-complete.slint delete mode 100644 ui/app-full.slint delete mode 100644 ui/app-nav-backup.slint delete mode 100644 ui/app-nav.slint delete mode 100644 ui/app-simple-old.slint delete mode 100644 ui/app-simple.slint delete mode 100644 ui/app-simple.slint.bak create mode 100644 ui/components/empty.slint create mode 100644 ui/layouts/container.slint create mode 100644 ui/layouts/page-layout.slint delete mode 100644 ui/pages-complete-backup.slint delete mode 100644 ui/pages-complete.slint create mode 100644 ui/types.slint diff --git a/build.rs b/build.rs index 103ac7e..c19a7b8 100644 --- a/build.rs +++ b/build.rs @@ -1,3 +1,3 @@ fn main() { - slint_build::compile("ui/app-full.slint").unwrap(); + slint_build::compile("ui/app.slint").unwrap(); } diff --git a/src/main.rs b/src/main.rs index ee32dbc..029918a 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,48 +1,454 @@ slint::include_modules!(); -use slint::ComponentHandle; -use std::time::Duration; +use slint::{ComponentHandle, Model, ModelRc, VecModel}; fn main() -> Result<(), slint::PlatformError> { let ui = App::new()?; - // Initialize state - ui.set_mihomo_status(StatusState::Stopped); - ui.set_mihomo_loading(false); + // Initialize with mock data + initialize_mock_data(&ui); - // Setup toggle callback + // Setup callbacks - Data loading let ui_weak = ui.as_weak(); - ui.on_toggle_mihomo(move || { + ui.on_load_profiles(move || { let ui = ui_weak.unwrap(); - let current_status = ui.get_mihomo_status(); + println!("Loading profiles..."); + load_mock_profiles(&ui); + }); - ui.set_mihomo_loading(true); + let ui_weak = ui.as_weak(); + ui.on_load_proxy_groups(move |mode| { + let ui = ui_weak.unwrap(); + println!("Loading proxy groups for mode: {}", mode); + load_mock_proxy_groups(&ui, mode.as_str()); + }); - // Simulate async operation - let ui_weak2 = ui_weak.clone(); - slint::Timer::single_shot(Duration::from_millis(1500), move || { - let ui = ui_weak2.unwrap(); + let ui_weak = ui.as_weak(); + ui.on_load_connections(move || { + let ui = ui_weak.unwrap(); + println!("Loading connections..."); + load_mock_connections(&ui); + }); - match current_status { - StatusState::Stopped => { - ui.set_mihomo_status(StatusState::Running); - println!("Mihomo started"); - } - StatusState::Running => { - ui.set_mihomo_status(StatusState::Stopped); - println!("Mihomo stopped"); - } - _ => {} - } + let ui_weak = ui.as_weak(); + ui.on_load_logs(move |log_type| { + let ui = ui_weak.unwrap(); + println!("Loading logs: {}", log_type); + load_mock_logs(&ui, log_type.as_str()); + }); - ui.set_mihomo_loading(false); - }); + let ui_weak = ui.as_weak(); + ui.on_load_settings(move || { + let ui = ui_weak.unwrap(); + println!("Loading settings..."); + load_mock_settings(&ui); + }); - // Set to starting state - if current_status == StatusState::Stopped { - ui.set_mihomo_status(StatusState::Starting); + // Setup callbacks - User actions + let ui_weak = ui.as_weak(); + ui.on_select_profile(move |profile_id| { + let ui = ui_weak.unwrap(); + println!("Selected profile: {}", profile_id); + + // Update current profile + let profiles = ui.get_profiles(); + for i in 0..profiles.row_count() { + let mut profile = profiles.row_data(i).unwrap(); + profile.selected = profile.id == profile_id; + profiles.set_row_data(i, profile); } + + // Update current profile in UI + if let Some(profile) = profiles.iter().find(|p| p.id == profile_id) { + ui.set_current_profile(profile); + } + + ui.invoke_show_toast("Profile selected".into(), "success".into()); + }); + + let ui_weak = ui.as_weak(); + ui.on_select_proxy(move |group_name, node_name| { + let ui = ui_weak.unwrap(); + println!("Selected proxy: {} -> {}", group_name, node_name); + + // Update proxy groups + let groups = ui.get_proxy_groups(); + for i in 0..groups.row_count() { + let mut group = groups.row_data(i).unwrap(); + if group.name == group_name { + group.now = node_name.clone(); + groups.set_row_data(i, group); + } + } + + ui.invoke_show_toast( + format!("Switched to {}", node_name).into(), + "success".into() + ); + }); + + let ui_weak = ui.as_weak(); + ui.on_test_proxy_delay(move |node_name| { + let ui = ui_weak.unwrap(); + println!("Testing delay for: {}", node_name); + ui.invoke_show_toast( + format!("Testing delay for {}...", node_name).into(), + "default".into() + ); + }); + + let ui_weak = ui.as_weak(); + ui.on_toggle_setting(move |key, value| { + let ui = ui_weak.unwrap(); + println!("Toggle setting: {} = {}", key, value); + + ui.invoke_show_toast( + format!("Setting {} updated", key).into(), + "success".into() + ); + }); + + let ui_weak = ui.as_weak(); + ui.on_update_profile(move |profile_id| { + let ui = ui_weak.unwrap(); + println!("Updating profile: {}", profile_id); + ui.invoke_show_toast("Profile updated".into(), "success".into()); + }); + + let ui_weak = ui.as_weak(); + ui.on_delete_profile(move |profile_id| { + let ui = ui_weak.unwrap(); + println!("Deleting profile: {}", profile_id); + + // Remove profile from list + let profiles = ui.get_profiles(); + let mut new_profiles = Vec::new(); + for i in 0..profiles.row_count() { + let profile = profiles.row_data(i).unwrap(); + if profile.id != profile_id { + new_profiles.push(profile); + } + } + ui.set_profiles(ModelRc::new(VecModel::from(new_profiles))); + + ui.invoke_show_toast("Profile deleted".into(), "success".into()); + }); + + let ui_weak = ui.as_weak(); + ui.on_add_profile(move |_url| { + let ui = ui_weak.unwrap(); + println!("Adding profile"); + ui.invoke_show_toast("Add profile feature coming soon".into(), "default".into()); + }); + + let _ui_weak = ui.as_weak(); + ui.on_show_toast(move |message, toast_type| { + println!("Toast: [{}] {}", toast_type, message); + // Toast handling is done in Slint side }); ui.run() } + +fn initialize_mock_data(ui: &App) { + load_mock_profiles(ui); + load_mock_proxy_groups(ui, "rule"); + load_mock_connections(ui); + load_mock_logs(ui, "mihomo"); + load_mock_settings(ui); +} + +fn load_mock_profiles(ui: &App) { + let profiles = vec![ + Profile { + id: "1".into(), + name: "NanoCloud".into(), + url: "https://example.com/sub1".into(), + r#type: "remote".into(), + updated_at: "2026-01-30 10:05".into(), + usage: "1.26GB / 100GB".into(), + expire_at: "2026-11-11".into(), + selected: true, + }, + Profile { + id: "2".into(), + name: "FastProxy".into(), + url: "https://example.com/sub2".into(), + r#type: "remote".into(), + updated_at: "2026-01-29 15:30".into(), + usage: "5.8GB / 50GB".into(), + expire_at: "2026-12-31".into(), + selected: false, + }, + Profile { + id: "3".into(), + name: "Local Config".into(), + url: "file:///local/config.yaml".into(), + r#type: "local".into(), + updated_at: "2026-01-28 09:00".into(), + usage: "N/A".into(), + expire_at: "N/A".into(), + selected: false, + }, + ]; + + ui.set_profiles(ModelRc::new(VecModel::from(profiles.clone()))); + + // Set current profile + if let Some(current) = profiles.iter().find(|p| p.selected) { + ui.set_current_profile(current.clone()); + } +} + +fn load_mock_proxy_groups(ui: &App, mode: &str) { + let groups = match mode { + "rule" => vec![ + ProxyGroup { + name: "Auto Select".into(), + r#type: "URLTest".into(), + now: "HK-01".into(), + nodes: ModelRc::new(VecModel::from(vec![ + ProxyNode { + name: "HK-01".into(), + r#type: "Vmess".into(), + delay: 45, + udp: true, + }, + ProxyNode { + name: "HK-02".into(), + r#type: "Vmess".into(), + delay: 120, + udp: true, + }, + ProxyNode { + name: "JP-01".into(), + r#type: "Shadowsocks".into(), + delay: 180, + udp: false, + }, + ])), + }, + ProxyGroup { + name: "Proxy".into(), + r#type: "Selector".into(), + now: "SG-01".into(), + nodes: ModelRc::new(VecModel::from(vec![ + ProxyNode { + name: "SG-01".into(), + r#type: "Vmess".into(), + delay: 65, + udp: true, + }, + ProxyNode { + name: "SG-02".into(), + r#type: "Trojan".into(), + delay: 88, + udp: true, + }, + ProxyNode { + name: "US-01".into(), + r#type: "Vmess".into(), + delay: 250, + udp: false, + }, + ])), + }, + ], + "global" => vec![ + ProxyGroup { + name: "Global".into(), + r#type: "Selector".into(), + now: "HK-01".into(), + nodes: ModelRc::new(VecModel::from(vec![ + ProxyNode { + name: "HK-01".into(), + r#type: "Vmess".into(), + delay: 45, + udp: true, + }, + ProxyNode { + name: "JP-01".into(), + r#type: "Shadowsocks".into(), + delay: 180, + udp: false, + }, + ])), + }, + ], + _ => vec![], + }; + + ui.set_proxy_groups(ModelRc::new(VecModel::from(groups))); +} + +fn load_mock_connections(ui: &App) { + let connections = vec![ + Connection { + id: "1".into(), + host: "api.github.com:443".into(), + process: "chrome".into(), + rule: "DOMAIN".into(), + chains: "Proxy".into(), + upload: "1.2KB".into(), + download: "45KB".into(), + time: "10:30:15".into(), + }, + Connection { + id: "2".into(), + host: "www.google.com:443".into(), + process: "firefox".into(), + rule: "DOMAIN-SUFFIX".into(), + chains: "Direct".into(), + upload: "800B".into(), + download: "12KB".into(), + time: "10:29:50".into(), + }, + Connection { + id: "3".into(), + host: "cdn.jsdelivr.net:443".into(), + process: "vscode".into(), + rule: "GEOIP".into(), + chains: "Proxy".into(), + upload: "2.5KB".into(), + download: "128KB".into(), + time: "10:28:30".into(), + }, + ]; + + ui.set_connections(ModelRc::new(VecModel::from(connections))); +} + +fn load_mock_logs(ui: &App, log_type: &str) { + let logs = match log_type { + "mihomo" => vec![ + LogEntry { + time: "10:30:45".into(), + level: "info".into(), + message: "HTTP proxy listening at :7890".into(), + }, + LogEntry { + time: "10:30:46".into(), + level: "info".into(), + message: "SOCKS proxy listening at :7891".into(), + }, + LogEntry { + time: "10:30:50".into(), + level: "warn".into(), + message: "Connection timeout for node HK-03".into(), + }, + LogEntry { + time: "10:31:00".into(), + level: "info".into(), + message: "Profile updated successfully".into(), + }, + LogEntry { + time: "10:31:15".into(), + level: "error".into(), + message: "Failed to connect to node US-02".into(), + }, + ], + "app" => vec![ + LogEntry { + time: "10:30:40".into(), + level: "info".into(), + message: "Application started".into(), + }, + LogEntry { + time: "10:30:42".into(), + level: "info".into(), + message: "Loading configuration from disk".into(), + }, + LogEntry { + time: "10:30:43".into(), + level: "debug".into(), + message: "Initializing UI components".into(), + }, + LogEntry { + time: "10:30:55".into(), + level: "info".into(), + message: "User switched to profile: NanoCloud".into(), + }, + ], + _ => vec![], + }; + + match log_type { + "mihomo" => ui.set_mihomo_logs(ModelRc::new(VecModel::from(logs))), + "app" => ui.set_app_logs(ModelRc::new(VecModel::from(logs))), + _ => {} + } +} + +fn load_mock_settings(ui: &App) { + let app_settings = vec![ + SettingItem { + key: "auto-start".into(), + label: "开机自启".into(), + value: "".into(), + r#type: "switch".into(), + enabled: false, + }, + SettingItem { + key: "silent-start".into(), + label: "静默启动".into(), + value: "".into(), + r#type: "switch".into(), + enabled: true, + }, + SettingItem { + key: "clash-core".into(), + label: "Clash 内核".into(), + value: "".into(), + r#type: "switch".into(), + enabled: true, + }, + ]; + + let clash_settings = vec![ + SettingItem { + key: "tun-mode".into(), + label: "虚拟网卡模式".into(), + value: "".into(), + r#type: "switch".into(), + enabled: false, + }, + SettingItem { + key: "allow-lan".into(), + label: "允许局域网".into(), + value: "".into(), + r#type: "switch".into(), + enabled: true, + }, + SettingItem { + key: "ipv6".into(), + label: "IPv6".into(), + value: "".into(), + r#type: "switch".into(), + enabled: false, + }, + SettingItem { + key: "unified-delay".into(), + label: "统一延迟".into(), + value: "".into(), + r#type: "switch".into(), + enabled: true, + }, + SettingItem { + key: "log-level".into(), + label: "日志等级".into(), + value: "INFO".into(), + r#type: "text".into(), + enabled: true, + }, + SettingItem { + key: "port".into(), + label: "端口设置".into(), + value: "7890".into(), + r#type: "button".into(), + enabled: true, + }, + ]; + + ui.set_app_settings(ModelRc::new(VecModel::from(app_settings))); + ui.set_clash_settings(ModelRc::new(VecModel::from(clash_settings))); +} diff --git a/ui/app-complete.slint b/ui/app-complete.slint deleted file mode 100644 index e69de29..0000000 diff --git a/ui/app-full.slint b/ui/app-full.slint deleted file mode 100644 index 6d44ded..0000000 --- a/ui/app-full.slint +++ /dev/null @@ -1,243 +0,0 @@ -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 icon: ""; - in property title: ""; - in property active: false; - - callback clicked(); - - private property 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 current-page: 0; - in-out property mihomo-status: StatusState.stopped; - in-out property 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 {} - } - } -} diff --git a/ui/app-nav-backup.slint b/ui/app-nav-backup.slint deleted file mode 100644 index 69ad254..0000000 --- a/ui/app-nav-backup.slint +++ /dev/null @@ -1,366 +0,0 @@ -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 icon: ""; - in property title: ""; - in property active: false; - - callback clicked(); - - private property 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 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 current-page: 0; - in-out property mihomo-status: StatusState.stopped; - in-out property 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"; } - } - } -} diff --git a/ui/app-nav.slint b/ui/app-nav.slint deleted file mode 100644 index 69ad254..0000000 --- a/ui/app-nav.slint +++ /dev/null @@ -1,366 +0,0 @@ -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 icon: ""; - in property title: ""; - in property active: false; - - callback clicked(); - - private property 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 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 current-page: 0; - in-out property mihomo-status: StatusState.stopped; - in-out property 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"; } - } - } -} diff --git a/ui/app-simple-old.slint b/ui/app-simple-old.slint deleted file mode 100644 index b4177d3..0000000 --- a/ui/app-simple-old.slint +++ /dev/null @@ -1,884 +0,0 @@ -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 current-page: 0; - in-out property mihomo-status: StatusState.stopped; - in-out property 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 icon: ""; - in property title: ""; - in property active: false; - - callback clicked(); - - private property 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 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 time: ""; - in property level: ""; - in property message: ""; - - private property 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); } - } - } - } - } - } - } - } -} diff --git a/ui/app-simple.slint b/ui/app-simple.slint deleted file mode 100644 index 9055472..0000000 --- a/ui/app-simple.slint +++ /dev/null @@ -1,5 +0,0 @@ -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"; diff --git a/ui/app-simple.slint.bak b/ui/app-simple.slint.bak deleted file mode 100644 index b4177d3..0000000 --- a/ui/app-simple.slint.bak +++ /dev/null @@ -1,884 +0,0 @@ -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 current-page: 0; - in-out property mihomo-status: StatusState.stopped; - in-out property 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 icon: ""; - in property title: ""; - in property active: false; - - callback clicked(); - - private property 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 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 time: ""; - in property level: ""; - in property message: ""; - - private property 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); } - } - } - } - } - } - } - } -} diff --git a/ui/app.slint b/ui/app.slint index 2c4b6b4..edc0deb 100644 --- a/ui/app.slint +++ b/ui/app.slint @@ -1,210 +1,270 @@ -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"; +// Main Application - Proxy Management Tool +import { Theme, Typography, SpacingSystem } from "theme/theme.slint"; +import { Button } from "components/button.slint"; +import { Sidebar, NavItem } from "components/sidebar.slint"; +import { ToastContainer, ToastMessage } from "components/toast.slint"; +import { Profile, ProxyGroup, Connection, LogEntry, SettingItem } from "types.slint"; +import { ConnectionsPage } from "pages/connections.slint"; +import { LogsPage } from "pages/logs.slint"; +import { SettingsPage } from "pages/settings.slint"; +import { ProfilesPage } from "pages/profiles.slint"; +import { HomePage } from "pages/home.slint"; +import { ProxiesPage } from "pages/proxies.slint"; export component App inherits Window { - title: "Clash Manager"; - preferred-width: 1200px; - preferred-height: 800px; + title: "Proxy Manager"; background: Theme.colors.background; - - // Navigation state - in-out property current-page: 0; - - // Mihomo status - in-out property mihomo-status: StatusState.stopped; - in-out property mihomo-loading: false; - - // Home page data - in-out property profile-name: "NanoCloud"; - in-out property profile-node: "Free-Japan1-Ver.7"; - in-out property profile-usage: 1.26; - in-out property profile-total: 100; - in-out property profile-expiry: "2025-11-11"; - in-out property profile-updated: "2025-10-12 10:05"; - in-out property location-ip: "47.238.198.100"; - in-out property location-region: "Japan · Tokyo"; - in-out property tun-mode-enabled: false; - in-out property system-proxy-enabled: false; - in-out property proxy-port: 7890; - - // Profiles page data - in-out property <[ProfileData]> profiles: []; - in-out property selected-profile-id: ""; - - // Proxies page data + + // Window size settings + min-width: 800px; + min-height: 600px; + preferred-width: 1000px; + preferred-height: 700px; + + // ===== Application State ===== + in-out property current-view: "home"; + in-out property sidebar-collapsed: false; + + // ===== Data State ===== + in-out property current-profile; + in-out property <[Profile]> profiles: []; in-out property <[ProxyGroup]> proxy-groups: []; - in-out property proxy-mode: 0; - in-out property selected-proxy: ""; - - // Logs page data + in-out property proxy-mode: "rule"; // "rule" | "global" | "direct" + in-out property <[Connection]> connections: []; in-out property <[LogEntry]> mihomo-logs: []; in-out property <[LogEntry]> app-logs: []; - in-out property log-tab: 0; - - // Connections page data - in-out property <[Connection]> connections: []; - - // Settings page data - in-out property auto-start: false; - in-out property silent-start: false; - in-out property clash-core: false; - in-out property allow-lan: false; - in-out property ipv6: false; - in-out property unified-delay: false; - in-out property log-level-index: 1; - in-out property app-dir: "C:/Program Files/Clash"; - in-out property config-dir: "C:/Users/User/.config/clash"; - in-out property core-dir: "C:/Program Files/Clash/core"; - in-out property 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); + in-out property <[SettingItem]> app-settings: []; + in-out property <[SettingItem]> clash-settings: []; + in-out property <[ToastMessage]> toasts: []; + + // ===== Callbacks (Rust Implementation) ===== + // Data loading + callback load-profiles(); + callback load-proxy-groups(string /* mode: rule/global/direct */); + callback load-connections(); + callback load-logs(string /* type: mihomo/app */); + callback load-settings(); + + // User actions + callback select-profile(string /* profile-id */); + callback select-proxy(string /* group-name */, string /* node-name */); + callback test-proxy-delay(string /* node-name */); + callback toggle-setting(string /* key */, bool /* value */); + callback update-profile(string /* profile-id */); + callback delete-profile(string /* profile-id */); + callback add-profile(string /* url */); + + // Toast notification + callback show-toast(string /* message */, string /* type */); + + // Sidebar navigation items + property <[NavItem]> nav-items: [ + { icon: "🏠", label: "Home", active: current-view == "home" }, + { icon: "🌍", label: "Proxies", active: current-view == "proxies" }, + { icon: "📋", label: "Profiles", active: current-view == "profiles" }, + { icon: "🔗", label: "Connections", active: current-view == "connections" }, + { icon: "📝", label: "Logs", active: current-view == "logs" }, + { icon: "⚙", label: "Settings", active: current-view == "settings" }, + ]; + + // Main layout + VerticalLayout { + // Top bar + Rectangle { + height: 60px; + background: Theme.colors.card; + + // Bottom border + Rectangle { + y: parent.height - 1px; + height: 1px; + background: Theme.colors.border; } - - toggle-mihomo => { - root.toggle-mihomo(); + + HorizontalLayout { + padding: SpacingSystem.spacing.s4; + spacing: SpacingSystem.spacing.s4; + alignment: space-between; + + // Left: Title + HorizontalLayout { + spacing: SpacingSystem.spacing.s3; + alignment: start; + + Text { + text: "🔐 Proxy Manager"; + font-size: Typography.sizes.xl; + font-weight: Typography.weights.bold; + color: Theme.colors.foreground; + vertical-alignment: center; + } + } + + // Right: Theme toggle button + Button { + text: Theme.is-dark-mode ? "☀ Light" : "🌙 Dark"; + variant: "outline"; + size: "sm"; + + clicked => { + Theme.toggle-theme(); + } + } } } - - // 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(); } + + // Main content area + HorizontalLayout { + // Sidebar + Sidebar { + items: nav-items; + collapsed: sidebar-collapsed; + + item-clicked(index) => { + if index == 0 { + current-view = "home"; + root.load-profiles(); + root.load-settings(); + } + else if index == 1 { + current-view = "proxies"; + root.load-proxy-groups(proxy-mode); + } + else if index == 2 { + current-view = "profiles"; + root.load-profiles(); + } + else if index == 3 { + current-view = "connections"; + root.load-connections(); + } + else if index == 4 { + current-view = "logs"; + root.load-logs("mihomo"); + } + else if index == 5 { + current-view = "settings"; + root.load-settings(); + } + } + + toggle-collapsed => { + sidebar-collapsed = !sidebar-collapsed; + } } - - // 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); } + + // Main content area - Pages + Rectangle { + background: Theme.colors.background; + horizontal-stretch: 1; + vertical-stretch: 1; + + // Home page + if current-view == "home": HomePage { + current-profile: root.current-profile; + quick-settings: root.app-settings; + + load-home-data => { + root.load-profiles(); + root.load-settings(); + } + + toggle-setting(key, value) => { + root.toggle-setting(key, value); + } + } + + // Proxies page + if current-view == "proxies": ProxiesPage { + proxy-groups: root.proxy-groups; + proxy-mode: root.proxy-mode; + + load-proxy-groups(mode) => { + root.proxy-mode = mode; + root.load-proxy-groups(mode); + } + + select-proxy(group-name, node-name) => { + root.select-proxy(group-name, node-name); + } + + test-proxy-delay(node-name) => { + root.test-proxy-delay(node-name); + } + } + + // Profiles page + if current-view == "profiles": ProfilesPage { + profiles: root.profiles; + + load-profiles => { + root.load-profiles(); + } + + select-profile(profile-id) => { + root.select-profile(profile-id); + } + + update-profile(profile-id) => { + root.update-profile(profile-id); + } + + delete-profile(profile-id) => { + root.delete-profile(profile-id); + } + + add-profile => { + root.add-profile(""); + } + } + + // Connections page + if current-view == "connections": ConnectionsPage { + connections: root.connections; + + load-connections => { + root.load-connections(); + } + } + + // Logs page + if current-view == "logs": LogsPage { + mihomo-logs: root.mihomo-logs; + app-logs: root.app-logs; + + load-logs(log-type) => { + root.load-logs(log-type); + } + } + + // Settings page + if current-view == "settings": SettingsPage { + app-settings: root.app-settings; + clash-settings: root.clash-settings; + + load-settings => { + root.load-settings(); + } + + toggle-setting(key, value) => { + root.toggle-setting(key, value); + } + } } - - // 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); } + } + } + + // Toast container + Rectangle { + x: parent.width - 390px; + y: 20px; + width: 370px; + + ToastContainer { + toasts: toasts; + + toast-dismissed(index) => { + // Will be handled by Rust } } } diff --git a/ui/components/accordion.slint b/ui/components/accordion.slint index d360179..77e3244 100644 --- a/ui/components/accordion.slint +++ b/ui/components/accordion.slint @@ -1,4 +1,5 @@ -import { Theme, SpacingSystem, Typography, Animations } from "../theme/theme.slint"; +import { Theme, SpacingSystem, Typography } from "../theme/theme.slint"; +import { Animations } from "../utils/animations.slint"; export struct AccordionItemData { title: string, diff --git a/ui/components/empty.slint b/ui/components/empty.slint new file mode 100644 index 0000000..bd87d76 --- /dev/null +++ b/ui/components/empty.slint @@ -0,0 +1,40 @@ +// Empty state component - Shows when no data is available +import { Theme, Typography, SpacingSystem } from "../theme/theme.slint"; + +export component Empty inherits Rectangle { + in property icon: "📭"; + in property title: "No Data"; + in property description: ""; + + background: transparent; + + VerticalLayout { + alignment: center; + spacing: SpacingSystem.spacing.s3; + + // Icon + Text { + text: root.icon; + font-size: 48px; + horizontal-alignment: center; + } + + // Title + Text { + text: root.title; + font-size: Typography.sizes.lg; + font-weight: Typography.weights.semibold; + color: Theme.colors.foreground; + horizontal-alignment: center; + } + + // Description (optional) + if root.description != "": Text { + text: root.description; + font-size: Typography.sizes.sm; + color: Theme.colors.muted-foreground; + horizontal-alignment: center; + wrap: word-wrap; + } + } +} diff --git a/ui/components/switch.slint b/ui/components/switch.slint index 37886fe..81066ed 100644 --- a/ui/components/switch.slint +++ b/ui/components/switch.slint @@ -1,4 +1,5 @@ -import { Theme, SpacingSystem, Animations } from "../theme/theme.slint"; +import { Theme, SpacingSystem } from "../theme/theme.slint"; +import { Animations } from "../utils/animations.slint"; export component Switch { in property checked: false; diff --git a/ui/layouts/container.slint b/ui/layouts/container.slint new file mode 100644 index 0000000..10eb4e1 --- /dev/null +++ b/ui/layouts/container.slint @@ -0,0 +1,11 @@ +// Container component - Card-style container with border and rounded corners +import { Theme, SpacingSystem } from "../theme/theme.slint"; + +export component Container inherits Rectangle { + @children + + background: Theme.colors.card; + border-radius: SpacingSystem.radius.md; + border-width: 1px; + border-color: Theme.colors.border; +} diff --git a/ui/layouts/page-layout.slint b/ui/layouts/page-layout.slint new file mode 100644 index 0000000..d436301 --- /dev/null +++ b/ui/layouts/page-layout.slint @@ -0,0 +1,52 @@ +// Page layout component - Fixed header with scrollable content area +import { Theme, Typography, SpacingSystem } from "../theme/theme.slint"; + +export component PageLayout inherits Rectangle { + in property title; + in property show-actions: false; + + @children + + background: Theme.colors.background; + + VerticalLayout { + // Fixed header + header := Rectangle { + height: 48px; + background: Theme.colors.background; + + HorizontalLayout { + padding-left: SpacingSystem.spacing.s4; + padding-right: SpacingSystem.spacing.s4; + padding-top: SpacingSystem.spacing.s3; + padding-bottom: SpacingSystem.spacing.s3; + spacing: SpacingSystem.spacing.s4; + alignment: space-between; + + Text { + text: root.title; + font-size: Typography.sizes.xl; + font-weight: Typography.weights.bold; + color: Theme.colors.foreground; + vertical-alignment: center; + } + + // Right side actions area (inserted via @children) + if root.show-actions: HorizontalLayout { + spacing: SpacingSystem.spacing.s2; + vertical-alignment: center; + } + } + } + + // Scrollable content area + Flickable { + viewport-height: content-layout.preferred-height + SpacingSystem.spacing.s4 * 2; + + content-layout := VerticalLayout { + padding: SpacingSystem.spacing.s4; + spacing: SpacingSystem.spacing.s4; + } + } + } +} diff --git a/ui/pages-complete-backup.slint b/ui/pages-complete-backup.slint deleted file mode 100644 index f2d3fc5..0000000 --- a/ui/pages-complete-backup.slint +++ /dev/null @@ -1,967 +0,0 @@ -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 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 name: ""; - in property type-icon: ""; - in property description: ""; - in property is-remote: false; - in property usage: 0; - in property total: 100; - in property 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 time: ""; - in property level: ""; - in property message: ""; - - private property 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 source: ""; - in property destination: ""; - in property protocol: ""; - in property upload: ""; - in property download: ""; - in property 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 name: ""; - in property type-text: ""; - in property 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; - } - } - } -} diff --git a/ui/pages-complete.slint b/ui/pages-complete.slint deleted file mode 100644 index cb6e1ab..0000000 --- a/ui/pages-complete.slint +++ /dev/null @@ -1,799 +0,0 @@ -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 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 name: ""; - in property type-icon: ""; - in property description: ""; - in property is-remote: false; - in property usage: 0; - in property total: 100; - in property 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 time: ""; - in property level: ""; - in property message: ""; - private property 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 source: ""; - in property destination: ""; - in property protocol: ""; - in property upload: ""; - in property download: ""; - in property 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 name: ""; - in property type-text: ""; - in property 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"; - } - } - } - } - } - } - } - } -} diff --git a/ui/pages/connections.slint b/ui/pages/connections.slint index 804b959..270be71 100644 --- a/ui/pages/connections.slint +++ b/ui/pages/connections.slint @@ -1,130 +1,57 @@ -import { Theme, SpacingSystem, Typography } from "../theme/theme.slint"; +// Connections Page - Display active connections list +import { Theme, Typography, SpacingSystem } from "../theme/theme.slint"; +import { Item } from "../components/item.slint"; +import { Empty } from "../components/empty.slint"; +import { Connection } from "../types.slint"; -export struct Connection { - source: string, - destination: string, - protocol: string, - upload: string, - download: string, - duration: string, -} +export component ConnectionsPage inherits Rectangle { + in property <[Connection]> connections; + + callback load-connections(); + + background: Theme.colors.background; -export component ConnectionsPage { - in property <[Connection]> connections: []; - VerticalLayout { - padding: SpacingSystem.spacing.s4; - spacing: SpacingSystem.spacing.s4; - - // Header - Text { - text: "Connections"; - color: Theme.colors.foreground; - font-size: Typography.sizes.xl; - font-weight: Typography.weights.bold; - } - - // Connections List - ScrollView { - VerticalLayout { - spacing: SpacingSystem.spacing.s2; - - for connection in root.connections: ConnectionItem { - connection-data: connection; - } - } - } - } -} + // Fixed header + Rectangle { + height: 48px; + background: Theme.colors.background; + + HorizontalLayout { + padding: SpacingSystem.spacing.s4; -component ConnectionItem { - in property connection-data; - - private property hovered: false; - - height: 60px; - - states [ - hovered when root.hovered: { - container.background: Theme.colors.muted.transparentize(0.5); - } - ] - - container := Rectangle { - background: transparent; - border-radius: SpacingSystem.radius.md; - border-width: 1px; - border-color: Theme.colors.border; - - animate background { duration: 150ms; } - - HorizontalLayout { - padding: SpacingSystem.spacing.s3; - spacing: SpacingSystem.spacing.s3; - alignment: space-between; - - // Icon - Rectangle { - width: 40px; - height: 40px; - border-radius: SpacingSystem.radius.md; - background: Theme.colors.primary.transparentize(0.9); - Text { - text: "🔗"; - font-size: Typography.sizes.lg; - horizontal-alignment: center; + text: "Connections"; + font-size: Typography.sizes.xl; + font-weight: Typography.weights.bold; + color: Theme.colors.foreground; vertical-alignment: center; } } - - // Connection Info - VerticalLayout { - spacing: SpacingSystem.spacing.s1; - - Text { - text: root.connection-data.source + " → " + root.connection-data.destination; - color: Theme.colors.foreground; - font-size: Typography.sizes.sm; - font-weight: Typography.weights.medium; - overflow: elide; - } - - HorizontalLayout { - spacing: SpacingSystem.spacing.s3; - - Text { - text: root.connection-data.protocol; - color: Theme.colors.muted-foreground; - font-size: Typography.sizes.xs; - } - - Text { - text: "↑ " + root.connection-data.upload; - color: Theme.colors.primary; - font-size: Typography.sizes.xs; - } - - Text { - text: "↓ " + root.connection-data.download; - color: Theme.colors.secondary; - font-size: Typography.sizes.xs; - } - } - } - - // Duration - Text { - text: root.connection-data.duration; - color: Theme.colmuted-foreground; - font-size: Typography.sizes.xs; - vertical-alignment: center; - } } - - touch := TouchArea { - moved => { - root.hovered = self.has-hover; + + // Scrollable content + Flickable { + viewport-height: content-layout.preferred-height + SpacingSystem.spacing.s4 * 2; + + content-layout := VerticalLayout { + padding: SpacingSystem.spacing.s4; + spacing: SpacingSystem.spacing.s2; + + // Empty state + if connections.length == 0: Empty { + height: 300px; + icon: "🔗"; + title: "No Active Connections"; + description: "Connections will appear here when active"; + } + + // Connection list + for connection in connections: Item { + icon: "✓"; + title: connection.host; + description: "Process: " + connection.process + " | Rule: " + connection.rule + " | ↑ " + connection.upload + " ↓ " + connection.download; + } } } } diff --git a/ui/pages/home.slint b/ui/pages/home.slint index 2811bfe..2e7773c 100644 --- a/ui/pages/home.slint +++ b/ui/pages/home.slint @@ -1,519 +1,380 @@ -import { Theme, SpacingSystem, Typography } from "../theme/theme.slint"; -import { Card } from "../components/card.slint"; +// Home Page - Dashboard with profile info and quick settings +import { Theme, Typography, SpacingSystem } from "../theme/theme.slint"; +import { Container } from "../layouts/container.slint"; import { Badge } from "../components/badge.slint"; import { Button } from "../components/button.slint"; -import { Switch } from "../components/switch.slint"; -import { Label, LabelSize, LabelColor } from "../components/label.slint"; -import { Progress } from "../components/progress.slint"; +import { Profile, SettingItem } from "../types.slint"; + +export component HomePage inherits Rectangle { + in property current-profile; + in property <[SettingItem]> quick-settings; + + callback load-home-data(); + callback toggle-setting(string /* key */, bool /* value */); + + background: Theme.colors.background; -export component HomePage { - callback refresh-profile(); - callback toggle-tun-mode(bool); - callback toggle-system-proxy(bool); - callback open-port-settings(); - - in property profile-name: "NanoCloud"; - in property profile-node: "免费-日本1-Ver.7"; - in property profile-usage: 1.26; - in property profile-total: 100; - in property profile-expiry: "2025-11-11"; - in property profile-updated: "2025-10-12 10:05"; - - in property location-ip: "47.238.198.100"; - in property location-region: "日本 · 东京"; - - in property tun-mode-enabled: false; - in property system-proxy-enabled: false; - in property proxy-port: 7890; - VerticalLayout { - padding: SpacingSystem.spacing.s4; - spacing: SpacingSystem.spacing.s4; - - // Header - Text { - text: "Home"; - color: Theme.colors.foreground; - font-size: Typography.sizes.xl; - font-weight: Typography.weights.bold; + // Fixed header + Rectangle { + height: 48px; + background: Theme.colors.background; + + HorizontalLayout { + padding: SpacingSystem.spacing.s4; + + Text { + text: "Home"; + font-size: Typography.sizes.xl; + font-weight: Typography.weights.bold; + color: Theme.colors.foreground; + vertical-alignment: center; + } + } } - - ScrollView { - VerticalLayout { - spacing: SpacingSystem.spacing.s3; - + + // Scrollable content + Flickable { + viewport-height: content-layout.preferred-height + SpacingSystem.spacing.s4 * 2; + + content-layout := VerticalLayout { + padding: SpacingSystem.spacing.s4; + spacing: SpacingSystem.spacing.s4; + // Profile Card - ProfileCard { - profile-name: root.profile-name; - profile-node: root.profile-node; - usage: root.profile-usage; - total: root.profile-total; - expiry: root.profile-expiry; - updated: root.profile-updated; - - refresh => { - root.refresh-profile(); - } - } - - // Location Card - LocationCard { - ip: root.location-ip; - region: root.location-region; - } - - // Settings Card - gsCard { - tun-enabled: root.tun-mode-enabled; - proxy-enabled: root.system-proxy-enabled; - port: root.proxy-port; - - tun-toggled(enabled) => { - root.toggle-tun-mode(enabled); - } - - proxy-toggled(enabled) => { - root.toggle-system-proxy(enabled); - } - - port-clicked => { - root.open-port-settings(); - } - } - } - } - } -} + Container { + VerticalLayout { + padding: SpacingSystem.spacing.s4; + spacing: SpacingSystem.spacing.s4; -component ProfileCard { - in property profile-name: ""; - in property profile-node: ""; - in property usage: 0; - in property total: 100; - in property expiry: ""; - in property updated: ""; - - callback refresh(); - - Card { - VerticalLayout { - spacing: SpacingSystem.spacing.s4; - - // Header - HorizontalLayout { - spacing: SpacingSystem.spacing.s3; - padding-bottom: SpacingSystem.ss4; - - // Icon - Rectangle { - width: 48px; - height: 48px; - border-radius: 12px; - background: Theme.colors.primary.transparentize(0.9); - - Text { - text: "☁"; - color: Theme.colors.primary; - font-size: 24px; - horizontal-alignment: center; - vertical-alignment: center; + // Header with icon and name + HorizontalLayout { + spacing: SpacingSystem.spacing.s3; + alignment: space-between; + + HorizontalLayout { + spacing: SpacingSystem.spacing.s3; + + // Icon + Rectangle { + width: 48px; + height: 48px; + background: Theme.colors.primary; + border-radius: SpacingSystem.radius.lg; + + Text { + text: "☁"; + font-size: 28px; + horizontal-alignment: center; + vertical-alignment: center; + } + } + + // Profile info + VerticalLayout { + spacing: SpacingSystem.spacing.s1; + + Text { + text: current-profile.name; + font-size: Typography.sizes.lg; + font-weight: Typography.weights.bold; + color: Theme.colors.foreground; + } + + HorizontalLayout { + spacing: SpacingSystem.spacing.s2; + + Text { + text: "🌍"; + font-size: Typography.sizes.xs; + } + + Text { + text: current-profile.type; + font-size: Typography.sizes.xs; + color: Theme.colors.muted-foreground; + } + + Badge { + text: "Vmess"; + variant: "outline"; + } + + Badge { + text: "UDP"; + variant: "outline"; + } + } + } + } + + // Refresh button + Button { + text: "🔄"; + variant: "ghost"; + size: "sm"; + } + } + + // Divider + Rectangle { + height: 1px; + background: Theme.colors.border; + } + + // Stats grid + HorizontalLayout { + spacing: SpacingSystem.spacing.s6; + + // Usage + VerticalLayout { + spacing: SpacingSystem.spacing.s1; + horizontal-stretch: 1; + + HorizontalLayout { + spacing: SpacingSystem.spacing.s2; + + Text { + text: "☁"; + font-size: Typography.sizes.xs; + color: Theme.colors.muted-foreground; + } + + Text { + text: "已使用 / 总量"; + font-size: Typography.sizes.xs; + color: Theme.colors.muted-foreground; + } + } + + Text { + text: current-profile.usage; + font-size: Typography.sizes.sm; + font-weight: Typography.weights.medium; + color: Theme.colors.foreground; + } + } + + // Expire date + VerticalLayout { + spacing: SpacingSystem.spacing.s1; + horizontal-stretch: 1; + + HorizontalLayout { + spacing: SpacingSystem.spacing.s2; + + Text { + text: "✓"; + font-size: Typography.sizes.xs; + color: Theme.colors.muted-foreground; + } + + Text { + text: "到期时间"; + font-size: Typography.sizes.xs; + color: Theme.colors.muted-foreground; + } + } + + Text { + text: current-profile.expire-at; + font-size: Typography.sizes.sm; + font-weight: Typography.weights.medium; + color: Theme.colors.foreground; + } + } + + // Updated date + VerticalLayout { + spacing: SpacingSystem.spacing.s1; + horizontal-stretch: 1; + + HorizontalLayout { + spacing: SpacingSystem.spacing.s2; + + Text { + text: "🕐"; + font-size: Typography.sizes.xs; + color: Theme.colors.muted-foreground; + } + + Text { + text: "更新时间"; + font-size: Typography.sizes.xs; + color: Theme.colors.muted-foreground; + } + } + + Text { + text: current-profile.updated-at; + font-size: Typography.sizes.sm; + font-weight: Typography.weights.medium; + color: Theme.colors.foreground; + } + } + } } } - - // Info - VerticalLayout { - spacing: SpacingSystem.spacing.s1; - - HorizontalLayout { - spacing: SpacingSystem.spacing.s2; - + + // Location Card (placeholder) + Container { + VerticalLayout { + padding: SpacingSystem.spacing.s4; + spacing: SpacingSystem.spacing.s3; + Text { - text: root.profile-name; + text: "位置信息"; + font-size: Typography.sizes.base; + font-weight: Typography.weights.semibold; color: Theme.colors.foreground; - font-size: Typography.sizes.lg; - font-weight: Typography.weights.bold; - vertical-alignment: center; - } - } - - HorizontalLayout { - spacing: SpacingSystem.spacing.s2; - - Text { - text: "🌐"; - font-size: Typography.sizes.xs; - vertical-alignment: center; } - - Text { - text: root.profile-node; - color: Theme.colors.muted-foreground; - font-size: Typography.sizes.xs; - font-weight: Typography.weights.medium; - vertical-alignment: center; - } - - Badge { - text: "Vmess"; - variant: "outline"; - } - - Badge { - text: "UDP"; - variant: "outline"; - } - } - } - - Rectan horizontal-stretch: 1; - } - - // Refresh button - Button { - width: 32px; - height: 32px; - variant: "ghost"; - text: "↻"; - - clicked => { - root.refresh(); - } - } - } - - // Separator - Rectangle { - height: 1px; - background: Theme.colors.border.transparentize(0.6); - } - - // Stats - HorizontalLayout { - spacing: SpacingSystem.spacing.s6; - - VerticalLayout { - spacing: SpacingSystem.spacing.s1; - - HorizontalLayout { - spacing: SpacingSystem.spacing.s1; - - Text { - text: "☁"; - color: Theme.colors.muted-foreground; - font-size: Typography.sizes.xs; - vertical-alignment: center; - } - - Text { - text: "已使用 / 总量"; - color: Theme.colors.muted-foreground; - font-size: Typography.sizes.xs; - font-weight: Typography.weights.semibold; - vertical-alignment: center; - } - } - - Text { - text: root.usage + "GB / " + root.total + "GB"; - color: Theme.colors.foreground; - font-size: Typography.sizes.sm; - font-weight: Typography.weights.medium; - } - } - - VerticalLayout { - spacing: SpacingSystem.spacing.s1; - - HorizontalLayout { - spacing: SpacingSystem.spacing.s1; - - Text { - text: "✓"; - color: Theme.colors.muted-foreground; - font-size: Typography.sizes.xs; - vertical-aliter; - } - - Text { - text: "到期时间"; - color: Theme.colors.muted-foreground; - font-size: Typography.sizes.xs; - font-weight: Typography.weights.semibold; - vertical-alignment: center; - } - } - - Text { - text: root.expiry; - color: Theme.colors.foreground; - font-size: Typography.sizes.sm; - font-weight: Typography.weights.medium; - } - } - - VerticalLayout { - spacing: SpacingSystem.spacing.s1; - - HorizontalLayout { - spacing: SpacingSystem.spacing.s1; - - Text { - text: "✓"; - color: Theme.colors.muted-foreground; - font-size: Typography.sizes.xs; - vertical-: center; - n - Text { - text: "更新时间"; - color: Theme.colors.muted-foreground; - font-size: Typography.sizes.xs; - font-weight: Typography.weights.semibold; - vertical-alignment: center; - } - } - - Text { - text: root.updated; - color: Theme.colors.foreground; - font-size: Typography.sizes.sm; - font-weight: Typography.weights.medium; - } - } - } - } - } -} -component LocationCard { - in property ip: ""; - in property region: ""; - - Card { - VerticalLayout { - spacing: SpacingSystem.spacing.s4; - - // IP and Location - HorizontalLayout { - spacing: SpacingSystem.spacing.s6; - VerticalLayout { - spacing: SpacingSystem.spacing.s1; - - HorizontalLayout { - spacing: SpacingSystem.spacing.s1; - - Text { - text: "🌐"; - font-size: Typography.sizes.xs; - vertical-alignment: center; + HorizontalLayout { + spacing: SpacingSystem.spacing.s3; + + Text { + text: "🌍"; + font-size: 32px; + } + + VerticalLayout { + spacing: SpacingSystem.spacing.s1; + + Text { + text: "当前位置: 日本 东京"; + font-size: Typography.sizes.sm; + color: Theme.colors.foreground; + } + + Text { + text: "IP: 192.168.1.1"; + font-size: Typography.sizes.xs; + color: Theme.colors.muted-foreground; + } + } } - + } + } + + // Quick Settings Card + Container { + VerticalLayout { + padding: SpacingSystem.spacing.s4; + spacing: SpacingSystem.spacing.s3; + Text { - text: "IP"; - color: Theme.colors.muted-foreground; - font-size: Typography.sizes.xs; + text: "快捷设置"; + font-size: Typography.sizes.base; font-weight: Typography.weights.semibold; - vertical-alignment: center; + color: Theme.colors.foreground; + } + + // Quick settings grid + HorizontalLayout { + spacing: SpacingSystem.spacing.s3; + + // System Proxy + Rectangle { + horizontal-stretch: 1; + height: 80px; + background: Theme.colors.muted; + border-radius: SpacingSystem.radius.md; + + VerticalLayout { + padding: SpacingSystem.spacing.s3; + spacing: SpacingSystem.spacing.s2; + alignment: center; + + Text { + text: "🌐"; + font-size: 24px; + horizontal-alignment: center; + } + + Text { + text: "系统代理"; + font-size: Typography.sizes.xs; + color: Theme.colors.foreground; + horizontal-alignment: center; + } + } + + TouchArea { + clicked => { + root.toggle-setting("system-proxy", true); + } + } + } + + // TUN Mode + Rectangle { + horizontal-stretch: 1; + height: 80px; + background: Theme.colors.muted; + border-radius: SpacingSystem.radius.md; + + VerticalLayout { + padding: SpacingSystem.spacing.s3; + spacing: SpacingSystem.spacing.s2; + alignment: center; + + Text { + text: "🔧"; + font-size: 24px; + horizontal-alignment: center; + } + + Text { + text: "TUN 模式"; + font-size: Typography.sizes.xs; + color: Theme.colors.foreground; + horizontal-alignment: center; + } + } + + TouchArea { + clicked => { + root.toggle-setting("tun-mode", true); + } + } + } + + // Allow LAN + Rectangle { + horizontal-stretch: 1; + height: 80px; + background: Theme.colors.muted; + border-radius: SpacingSystem.radius.md; + + VerticalLayout { + padding: SpacingSystem.spacing.s3; + spacing: SpacingSystem.spacing.s2; + alignment: center; + + Text { + text: "📡"; + font-size: 24px; + horizontal-alignment: center; + } + + Text { + text: "允许局域网"; + font-size: Typography.sizes.xs; + color: Theme.colors.foreground; + horizontal-alignment: center; + } + } + + TouchArea { + clicked => { + root.toggle-setting("allow-lan", true); + } + } + } } } - - Text { - text: root.ip; - color: Theme.colors.foreground; - font-size: Typography.sizes.sm; - font-weight: Typography.weights.medium; - } - } - - VerticalLayout { - : SpacingSystem.spacing.s1; - - HorizontalLayout { - spacing: SpacingSystem.spacing.s1; - - Text { - text: "🌐"; - font-size: Typography.sizes.xs; - vertical-alignment: center; - } - - Text { - text: "所属地址"; - color: Theme.colors.muted-foreground; - font-size: Typography.sizes.xs; - font-weight: Typography.weights.semibold; - vertical-alignment: center; - } - } - - Text { - text: root.region; - color: Theme.colors.foreground; - font-size: Typography.sizes.sm; - font-weight: Typography.weights.medium; - } - } - } - - // Latency - VerticalLayout { - spacing: SpacingSystem.spacing.s2; - - HorizontalLayout { - spacing: SpacingSystem.spacing.s1; - - Text { - text: "✓"; - color: Theme.colors.muted-foreground; - font-size: Typography.sizes.xs; - vertical-alignment: center; - } - - Text { - text: "延迟"; - color: Theme.colors.muted-foreground; - font-size: Typography.sizes.xs; - font-weight: Typography.weights.semibold; - vertical-alignment: center; - } - } - - GridLayout { - spacing: SpacingSystem.spacing.s2; - Row { - LatencyItem { service: "Github"; latency: "122ms"; } - LatencyItem { service: "Google"; latency: "45ms"; } - LatencyItem { service: "Youtube"; latency: "67ms"; } - LatencyItem { service: "Twitter"; latency: "89ms"; } - } - Row { - LatencyItem { serviceetflix"; latency: "34ms"; } - LatencyItem { service: "Steam"; latency: "156ms"; } - LatencyItem { service: "Spotify"; latency: "78ms"; } - LatencyItem { service: "Discord"; latency: "92ms"; } - } } } } } } - -component LatencyItem { - in property service: ""; - in property 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 tun-enabled: false; - in property proxy-enabled: false; - in property 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 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 - } -} diff --git a/ui/pages/logs.slint b/ui/pages/logs.slint index 0858ddf..031eb6e 100644 --- a/ui/pages/logs.slint +++ b/ui/pages/logs.slint @@ -1,170 +1,209 @@ -import { Theme, SpacingSystem, Typography } from "../theme/theme.slint"; -import { Tabs, TabItem, TabContent } from "../components/tabs.slint"; +// Logs Page - Display Mihomo and App logs with tabs +import { Theme, Typography, SpacingSystem } from "../theme/theme.slint"; +import { Empty } from "../components/empty.slint"; +import { LogEntry } from "../types.slint"; -export struct LogEntry { - time: string, - level: string, // "debug", "info", "warn", "error" - message: string, -} +export component LogsPage inherits Rectangle { + in property <[LogEntry]> mihomo-logs; + in property <[LogEntry]> app-logs; + in-out property active-tab: "mihomo"; + + callback load-logs(string /* type: mihomo/app */); + + background: Theme.colors.background; -export component LogsPage { - in property <[LogEntry]> mihomo-logs: []; - in property <[LogEntry]> app-logs: []; - in-out property current-tab: 0; - - callback tab-changed(int); - VerticalLayout { - padding: SpacingSystem.spacing.s4; - spacing: SpacingSystem.spacing.s4; - - // Header with Tabs - HorizontalLayout { - spacing: SpacingSystem.spacing.s3; - alignment: space-between; - - Text { - text: "Logs"; - color: Theme.colors.foreground; - font-size: Typography.sizes.xl; - font-weight: Typography.weights.bold; - vertical-alignment: center; - } - - Rectangle { - horizontal-stretch: 1; - } - } - - // Tabs - Tabs { - tabs: [ - { title: "Mihomo Logs" }, - { title: "App Logs" }, - ]; - current-index: root.current-tab; - - tab-changed(index) => { - root.current-tab = index; - root.tab-changed(index); - } - - // Mihomo Logs - TabContent { - index: 0; - current-index: root.current-tab; - - LogView { - logs: root.mihomo-logs; - } - } - - // App Logs - TabContent { - index: 1; - current-index: root.current-tab; - - LogView { - logs: root.app-logs; - } - } - } - } -} + // Fixed header with tabs + Rectangle { + height: 48px; + background: Theme.colors.background; -component LogView { - in property <[LogEntry]> logs: []; - - Rectangle { - background: Theme.colors.background; - border-radius: SpacingSystem.radius.md; - border-width: 1px; - border-color: Theme.colors.border; - - ScrollView { - VerticalLayout { - padding: SpacingSystem.spacing.s2; - spacing: 0; - - for log[index] in root.logs: LogRow { - log-entry: log; - odd: Math.mod(index, 2) == 1; + HorizontalLayout { + padding: SpacingSystem.spacing.s4; + spacing: SpacingSystem.spacing.s4; + alignment: space-between; + + Text { + text: "Logs"; + font-size: Typography.sizes.xl; + font-weight: Typography.weights.bold; + color: Theme.colors.foreground; + vertical-alignment: center; + } + + // Tabs + HorizontalLayout { + spacing: SpacingSystem.spacing.s1; + + Rectangle { + width: 120px; + height: 32px; + background: active-tab == "mihomo" ? Theme.colors.primary : Theme.colors.muted; + border-radius: SpacingSystem.radius.md; + + TouchArea { + clicked => { + active-tab = "mihomo"; + root.load-logs("mihomo"); + } + } + + Text { + text: "Mihomo Logs"; + font-size: Typography.sizes.sm; + font-weight: Typography.weights.medium; + color: active-tab == "mihomo" ? Theme.colors.primary-foreground : Theme.colors.muted-foreground; + horizontal-alignment: center; + vertical-alignment: center; + } + } + + Rectangle { + width: 100px; + height: 32px; + background: active-tab == "app" ? Theme.colors.primary : Theme.colors.muted; + border-radius: SpacingSystem.radius.md; + + TouchArea { + clicked => { + active-tab = "app"; + root.load-logs("app"); + } + } + + Text { + text: "App Logs"; + font-size: Typography.sizes.sm; + font-weight: Typography.weights.medium; + color: active-tab == "app" ? Theme.colors.primary-foreground : Theme.colors.muted-foreground; + horizontal-alignment: center; + vertical-alignment: center; + } + } + } + } + } + + // Scrollable log content + Flickable { + viewport-height: log-content.preferred-height + SpacingSystem.spacing.s4 * 2; + + log-content := VerticalLayout { + padding: SpacingSystem.spacing.s4; + spacing: 0px; + + // Mihomo logs + if active-tab == "mihomo": VerticalLayout { + spacing: 0px; + + if mihomo-logs.length == 0: Empty { + height: 300px; + icon: "📝"; + title: "No Logs"; + description: "Mihomo logs will appear here"; + } + + for log[index] in mihomo-logs: Rectangle { + height: 24px; + background: mod(index, 2) == 0 ? Theme.colors.muted : transparent; + + HorizontalLayout { + padding-left: SpacingSystem.spacing.s2; + padding-right: SpacingSystem.spacing.s2; + spacing: SpacingSystem.spacing.s2; + + // Time + Text { + text: log.time; + font-size: Typography.sizes.xs; + color: Theme.colors.muted-foreground; + font-family: "monospace"; + vertical-alignment: center; + width: 80px; + } + + // Level + Text { + text: log.level; + font-size: Typography.sizes.xs; + font-weight: Typography.weights.bold; + color: log.level == "error" ? #ef4444 : + log.level == "warn" ? #f59e0b : + log.level == "info" ? #3b82f6 : Theme.colors.muted-foreground; + font-family: "monospace"; + vertical-alignment: center; + width: 60px; + } + + // Message + Text { + text: log.message; + font-size: Typography.sizes.xs; + color: Theme.colors.foreground; + font-family: "monospace"; + vertical-alignment: center; + overflow: elide; + } + } + } + } + + // App logs + if active-tab == "app": VerticalLayout { + spacing: 0px; + + if app-logs.length == 0: Empty { + height: 300px; + icon: "📝"; + title: "No Logs"; + description: "App logs will appear here"; + } + + for log[index] in app-logs: Rectangle { + height: 24px; + background: mod(index, 2) == 0 ? Theme.colors.muted : transparent; + + HorizontalLayout { + padding-left: SpacingSystem.spacing.s2; + padding-right: SpacingSystem.spacing.s2; + spacing: SpacingSystem.spacing.s2; + + // Time + Text { + text: log.time; + font-size: Typography.sizes.xs; + color: Theme.colors.muted-foreground; + font-family: "monospace"; + vertical-alignment: center; + width: 80px; + } + + // Level + Text { + text: log.level; + font-size: Typography.sizes.xs; + font-weight: Typography.weights.bold; + color: log.level == "error" ? #ef4444 : + log.level == "warn" ? #f59e0b : + log.level == "info" ? #3b82f6 : Theme.colors.muted-foreground; + font-family: "monospace"; + vertical-alignment: center; + width: 60px; + } + + // Message + Text { + text: log.message; + font-size: Typography.sizes.xs; + color: Theme.colors.foreground; + font-family: "monospace"; + vertical-alignment: center; + overflow: elide; + } + } + } } } } } } - -component LogRow { - in property log-entry; - in property ool> odd: false; - - private property hovered: false; - - private property 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; - } - } - } -} diff --git a/ui/pages/profiles.slint b/ui/pages/profiles.slint index a02d1aa..65aac4a 100644 --- a/ui/pages/profiles.slint +++ b/ui/pages/profiles.slint @@ -1,203 +1,225 @@ -import { Theme, SpacingSystem, Typography } from "../theme/theme.slint"; -import { Card } from "../components/card.slint"; +// Profiles Page - Configuration profiles management +import { Theme, Typography, SpacingSystem } from "../theme/theme.slint"; +import { Card, CardHeader, CardTitle, CardContent, CardFooter } from "../components/card.slint"; import { Button } from "../components/button.slint"; import { Badge } from "../components/badge.slint"; -import { Progress } from "../components/progress.slint"; +import { Empty } from "../components/empty.slint"; +import { Profile } from "../types.slint"; -export struct ProfileData { - id: string, - name: string, - type: string, // "local" or "remote" - description: string, - usage: float, // For remote profiles - total: float, // For remote profiles - updated: string, -} +export component ProfilesPage inherits Rectangle { + in property <[Profile]> profiles; -export component ProfilesPage { - in property <[ProfileData]> profiles: []; - in property selected-profile-id: ""; - + callback load-profiles(); + callback select-profile(string /* profile-id */); + callback update-profile(string /* profile-id */); + callback delete-profile(string /* profile-id */); callback add-profile(); - callback refresh-all(); - callback select-profile(string); - callback edit-profile(string); - callback delete-profile(string); - callback refresh-profile(string); - - VerticalLayout { - padding: SpacingSystem.spacing.s4; - spacing: SpacingSystem.spacing.s4; - - // Header - HorizontalLayout { - spacing: SpacingSystem.spacing.s3; - alignment: space-between; - - Text { - text: "Profiles"; - color: Theme.colors.foreground; - font-size: Typography.sizes.xl; - font-weight: Typography.weights.bold; - vertical-alignment: center; - } - - HorizontalLayout { - spacing: SpacingSystem.spacing.s2; - - Button { - text: "+"; - variant: "ghost"; - - clicked => { - root.add-profile(); - } - } - - Button { - text: "↻"; - variant: "ghost"; - - clicked => { - root.refresh-all(); - } - } - } - } - - // Profile Grid - ScrollView { - GridLayout { - spacing: SpacingSystem.spacing.s3; - - for profile in root.profiles: ProfileItem { - profile-data: profile; - selected: profile.id == root.selected-profile-id; - - clicked => { - root.select-profile(profile.id); - } - - refresh-clicked => { - root.refresh-profile(profile.id); - } - } - } - } - } -} -component ProfileItem { - in property profile-data; - in property selected: false; - - callback clicked(); - callback refresh-clicked(); - - private property hovered: false; - - min-width: 288px; // 18rem - height: 96px; - - states [ - selected when root.selected: { - container.border-color: Theme.colors.primary.transparentize(0.5); - container.background: Theme.colors.accent.transparentize(0.4); - } - ] - - container := Card { - border-width: 2px; - - VerticalLa spacing: SpacingSystem.spacing.s2; - - // Header + background: Theme.colors.background; + + VerticalLayout { + // Fixed header + Rectangle { + height: 48px; + background: Theme.colors.background; + HorizontalLayout { - spacing: SpacingSystem.spacing.s2; + padding: SpacingSystem.spacing.s4; + spacing: SpacingSystem.spacing.s4; alignment: space-between; - + + Text { + text: "Profiles"; + font-size: Typography.sizes.xl; + font-weight: Typography.weights.bold; + color: Theme.colors.foreground; + vertical-alignment: center; + } + + // Action buttons HorizontalLayout { spacing: SpacingSystem.spacing.s2; - - Text { - text: root.profile-data.type == "remote" ? "☁" : "📄"; - font-size: Typography.sizes.lg; - vertical-alignment: center; + + Button { + text: "➕"; + variant: "ghost"; + size: "sm"; + + clicked => { + root.add-profile(); + } } - - { - text: root.profile-data.name; - color: Theme.colors.foreground; - font-size: Typography.sizes.base; - font-weight: Typography.weights.semibold; - vertical-alignment: center; + + Button { + text: "🔄"; + variant: "ghost"; + size: "sm"; + + clicked => { + root.load-profiles(); + } } } - - if root.profile-data.type == "remote": Button { - width: 24px; - height: 24px; - text: "↻"; - variant: "ghost"; - - clicked => { - root.refresh-clicked(); - } - } - } - - // Description - Text { - text: root.profile-data.description; - color: Theme.colors.muted-foreground; - font-size: Typography.sizes.xs; - overflow: elide; - } - - // Progress bar for remote profiles - if root.profile-data.type == "remote": VerticalLayout { - spacing: SpacingSystem.spacing.s1; - - Progress { - value: (root.profile-data.usage / root.profile-data.total) * 100; - } - - HorizontalLayout { - alignment: space-between; - - Text { - text: "Updated: " + root.profile-data.updated; - color: Theme.colors.muted-foreground; - font-size: 10px; - } - - Text { - text: root.profile-data.usage + " / " + root.profile-data.total + " GB"; - color: Theme.colors.oreground; - font-size: 10px; - } - } - } - - // Local profile info - if root.profile-data.type == "local": HorizontalLayout { - alignment: space-between; - - Text { - text: "Local Profile"; - color: Theme.colors.muted-foreground; - font-size: 10px; - } } } - - touch := TouchArea { - clicked => { - root.clicked(); - } - - moved => { - root.hovered = self.has-hover; + + // Scrollable content + Flickable { + viewport-height: content-layout.preferred-height + SpacingSystem.spacing.s4 * 2; + + content-layout := VerticalLayout { + padding: SpacingSystem.spacing.s4; + spacing: SpacingSystem.spacing.s4; + + // Empty state + if profiles.length == 0: Empty { + height: 300px; + icon: "📋"; + title: "No Profiles"; + description: "Click + to add a new profile"; + } + + // Profile list + for profile in profiles: Card { + hoverable: true; + + CardHeader { + HorizontalLayout { + spacing: SpacingSystem.spacing.s2; + alignment: space-between; + + HorizontalLayout { + spacing: SpacingSystem.spacing.s2; + + Text { + text: "☁"; + font-size: Typography.sizes.xl; + vertical-alignment: center; + } + + VerticalLayout { + spacing: SpacingSystem.spacing.s1; + + CardTitle { + text: profile.name; + } + + HorizontalLayout { + spacing: SpacingSystem.spacing.s1; + + Badge { + text: profile.type; + variant: "outline"; + } + + if profile.selected: Badge { + text: "Active"; + variant: "default"; + } + } + } + } + + Button { + text: "🔄"; + variant: "ghost"; + size: "sm"; + + clicked => { + root.update-profile(profile.id); + } + } + } + } + + CardContent { + VerticalLayout { + spacing: SpacingSystem.spacing.s2; + + // Usage + HorizontalLayout { + spacing: SpacingSystem.spacing.s2; + alignment: space-between; + + Text { + text: "Usage:"; + font-size: Typography.sizes.xs; + color: Theme.colors.muted-foreground; + } + + Text { + text: profile.usage; + font-size: Typography.sizes.xs; + color: Theme.colors.foreground; + font-weight: Typography.weights.medium; + } + } + + // Expire date + HorizontalLayout { + spacing: SpacingSystem.spacing.s2; + alignment: space-between; + + Text { + text: "Expires:"; + font-size: Typography.sizes.xs; + color: Theme.colors.muted-foreground; + } + + Text { + text: profile.expire-at; + font-size: Typography.sizes.xs; + color: Theme.colors.foreground; + font-weight: Typography.weights.medium; + } + } + + // Updated date + HorizontalLayout { + spacing: SpacingSystem.spacing.s2; + alignment: space-between; + + Text { + text: "Updated:"; + font-size: Typography.sizes.xs; + color: Theme.colors.muted-foreground; + } + + Text { + text: profile.updated-at; + font-size: Typography.sizes.xs; + color: Theme.colors.muted-foreground; + } + } + } + } + + CardFooter { + HorizontalLayout { + spacing: SpacingSystem.spacing.s2; + + if !profile.selected: Button { + text: "Select"; + variant: "default"; + size: "sm"; + + clicked => { + root.select-profile(profile.id); + } + } + + Button { + text: "Delete"; + variant: "destructive"; + size: "sm"; + + clicked => { + root.delete-profile(profile.id); + } + } + } + } + } } } } diff --git a/ui/pages/proxies.slint b/ui/pages/proxies.slint index 322b0e1..6d5d4b5 100644 --- a/ui/pages/proxies.slint +++ b/ui/pages/proxies.slint @@ -1,90 +1,274 @@ -import { Theme, SpacingSystem, Typography } from "../theme/theme.slint"; -import { Tabs, TabItem, TabContent } from "../components/tabs.slint"; -import { Accordion, AccordionItemData, AccordionContent } from "../components/accordion.slint"; +// Proxies Page - Proxy nodes management with tabs and groups +import { Theme, Typography, SpacingSystem } from "../theme/theme.slint"; import { Item } from "../components/item.slint"; +import { Badge } from "../components/badge.slint"; +import { Button } from "../components/button.slint"; +import { Empty } from "../components/empty.slint"; +import { Container } from "../layouts/container.slint"; +import { ProxyGroup, ProxyNode } from "../types.slint"; -export struct ProxyNode { - name: string, - type: string, - latency: string, -} +export component ProxiesPage inherits Rectangle { + in property <[ProxyGroup]> proxy-groups; + in-out property proxy-mode: "rule"; // "rule" | "global" | "direct" -export struct ProxyGroup { - name: string, - nodes: [ProxyNode], - expanded: bool, -} + callback load-proxy-groups(string /* mode */); + callback select-proxy(string /* group-name */, string /* node-name */); + callback test-proxy-delay(string /* node-name */); + + background: Theme.colors.background; -export component ProxiesPage { - in-out property <[ProxyGroup]> proxy-groups: []; - in-out property current-mode: 0; // 0=Rule, 1=Global, 2=Direct - in property selected-proxy: ""; - - callback mode-changed(int); - callback group-toggled(int, bool); - callback proxy-selected(string); - VerticalLayout { - padding: SpacingSystem.spacing.s4; - spacing: SpacingSystem.spacing.s4; - - // Header with Tabs - HorizontalLayout { - spacing: SpacingSystem.spacing.s3; - alignment: space-between; - - Text { - text: "Proxies"; - color: Theme.colors.foreground; - font-size: Typography.sizes.xl; - font-weight: Typography.weights.bold; - vertical-alignment: center; - } - - Rectangle { - horizontal-stretch: 1; + // Fixed header with tabs + Rectangle { + height: 48px; + background: Theme.colors.background; + + HorizontalLayout { + padding: SpacingSystem.spacing.s4; + spacing: SpacingSystem.spacing.s4; + alignment: space-between; + + Text { + text: "Proxies"; + font-size: Typography.sizes.xl; + font-weight: Typography.weights.bold; + color: Theme.colors.foreground; + vertical-alignment: center; + } + + // Mode tabs + HorizontalLayout { + spacing: SpacingSystem.spacing.s1; + + Rectangle { + width: 80px; + height: 32px; + background: proxy-mode == "rule" ? Theme.colors.primary : Theme.colors.muted; + border-radius: SpacingSystem.radius.md; + + TouchArea { + clicked => { + proxy-mode = "rule"; + root.load-proxy-groups("rule"); + } + } + + Text { + text: "Rule"; + font-size: Typography.sizes.sm; + font-weight: Typography.weights.medium; + color: proxy-mode == "rule" ? Theme.colors.primary-foreground : Theme.colors.muted-foreground; + horizontal-alignment: center; + vertical-alignment: center; + } + } + + Rectangle { + width: 80px; + height: 32px; + background: proxy-mode == "global" ? Theme.colors.primary : Theme.colors.muted; + border-radius: SpacingSystem.radius.md; + + TouchArea { + clicked => { + proxy-mode = "global"; + root.load-proxy-groups("global"); + } + } + + Text { + text: "Global"; + font-size: Typography.sizes.sm; + font-weight: Typography.weights.medium; + color: proxy-mode == "global" ? Theme.colors.primary-foreground : Theme.colors.muted-foreground; + horizontal-alignment: center; + vertical-alignment: center; + } + } + + Rectangle { + width: 80px; + height: 32px; + background: proxy-mode == "direct" ? Theme.colors.primary : Theme.colors.muted; + border-radius: SpacingSystem.radius.md; + + TouchArea { + clicked => { + proxy-mode = "direct"; + root.load-proxy-groups("direct"); + } + } + + Text { + text: "Direct"; + font-size: Typography.sizes.sm; + font-weight: Typography.weights.medium; + color: proxy-mode == "direct" ? Theme.colors.primary-foreground : Theme.colors.muted-foreground; + horizontal-alignment: center; + vertical-alignment: center; + } + } + } } } - - // Tabs - Tabs { - tabs: [ - { title: "Rule" }, - { title: "Global" }, - { title: "Direct" }, - ]; - current-index: root.current-mode; - - tab-changed(index) => { - root.current-mode = index; - root.mode-changed(index); - } - - // Tab Content - TabContent { - index: 0; - current-index: root.current-mode; - - ScrollView { + + // Scrollable content + Flickable { + viewport-height: content-layout.preferred-height + SpacingSystem.spacing.s4 * 2; + + content-layout := VerticalLayout { + padding: SpacingSystem.spacing.s4; + spacing: SpacingSystem.spacing.s4; + + // Empty state + if proxy-groups.length == 0: Empty { + height: 300px; + icon: "🌍"; + title: "No Groups"; + description: "Proxy groups will appear here"; + } + + // Proxy groups + for group in proxy-groups: Container { VerticalLayout { + padding: SpacingSystem.spacing.s4; spacing: SpacingSystem.spacing.s3; - - Accordion { - items: root.proxy-groups.map(|g| { - return { title: g.name, expanded: g.expanded }; - }); - - item-toggled(index, expanded) => { - root.group-toggled(index, expanded); + + // Group header + HorizontalLayout { + spacing: SpacingSystem.spacing.s3; + alignment: space-between; + + VerticalLayout { + spacing: SpacingSystem.spacing.s1; + + Text { + text: group.name; + font-size: Typography.sizes.base; + font-weight: Typography.weights.semibold; + color: Theme.colors.foreground; + } + + Text { + text: "Type: " + group.type + " | Current: " + group.now; + font-size: Typography.sizes.xs; + color: Theme.colors.muted-foreground; + } } - - for group[group-index] in root.proxy-groups: AccordionContent { - ProxyGroupContent { - nodes: group.nodes; - selected-proxy: root.selected-proxy; - - node-selected(name) => { - root.proxy-selected(name); + + Text { + text: group.nodes.length + " nodes"; + font-size: Typography.sizes.xs; + color: Theme.colors.muted-foreground; + vertical-alignment: center; + } + } + + // Divider + Rectangle { + height: 1px; + background: Theme.colors.border; + } + + // Proxy nodes + VerticalLayout { + spacing: SpacingSystem.spacing.s2; + + for node in group.nodes: Rectangle { + height: 60px; + background: node.name == group.now ? Theme.colors.accent : transparent; + border-radius: SpacingSystem.radius.md; + border-width: 1px; + border-color: Theme.colors.border; + + HorizontalLayout { + padding: SpacingSystem.spacing.s3; + spacing: SpacingSystem.spacing.s3; + alignment: space-between; + + // Node info + HorizontalLayout { + spacing: SpacingSystem.spacing.s3; + + // Status indicator + Rectangle { + width: 8px; + height: 8px; + border-radius: 4px; + background: node.delay >= 0 && node.delay < 200 ? #22c55e : + node.delay >= 200 && node.delay < 500 ? #f59e0b : + node.delay >= 500 ? #ef4444 : Theme.colors.muted; + } + + // Node details + VerticalLayout { + spacing: SpacingSystem.spacing.s1; + + Text { + text: node.name; + font-size: Typography.sizes.sm; + font-weight: Typography.weights.medium; + color: Theme.colors.foreground; + } + + HorizontalLayout { + spacing: SpacingSystem.spacing.s2; + + Badge { + text: node.type; + variant: "outline"; + } + + if node.udp: Badge { + text: "UDP"; + variant: "outline"; + } + + if node.delay >= 0: Text { + text: node.delay + "ms"; + font-size: Typography.sizes.xs; + color: Theme.colors.muted-foreground; + } + } + } + } + + // Actions + HorizontalLayout { + spacing: SpacingSystem.spacing.s2; + + Button { + text: "🔍"; + variant: "ghost"; + size: "sm"; + + clicked => { + root.test-proxy-delay(node.name); + } + } + + if node.name != group.now: Button { + text: "Select"; + variant: "outline"; + size: "sm"; + + clicked => { + root.select-proxy(group.name, node.name); + } + } + + if node.name == group.now: Badge { + text: "Active"; + variant: "default"; + } + } + } + + TouchArea { + clicked => { + if node.name != group.now { + root.select-proxy(group.name, node.name); + } } } } @@ -92,135 +276,6 @@ export component ProxiesPage { } } } - - TabContent { - index: 1; - current-index: root.current-mode; - - Text { - text: "Global mode - All traffic goes through selected proxy"; - color: Theme.colors.muted-foreground; - horizontal-alignment: center; - vertical-alignment: center; - } - } - - TabContent { - index: 2; - current-index: root.current-mode; - - Text { - text: "Direct mode - All traffic goes direct"; - color: Theme.colors.muted-foreground; - horizontal-alignment: center; - vertical-alignment: center; - } - } - } - } -} - -component ProxyGroupContent { - in property <[ProxyNode]> nodes: []; - in property 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 node-data; - in property selected: false; - - callback clicked(); - - private property 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; - } } } } diff --git a/ui/pages/settings.slint b/ui/pages/settings.slint index de0dcb7..a878cfb 100644 --- a/ui/pages/settings.slint +++ b/ui/pages/settings.slint @@ -1,336 +1,272 @@ -import { Theme, SpacingSystem, Typography } from "../theme/theme.slint"; -import { Card } from "../components/card.slint"; +// Settings Page - App and Clash settings +import { Theme, Typography, SpacingSystem } from "../theme/theme.slint"; +import { Container } from "../layouts/container.slint"; import { Switch } from "../components/switch.slint"; import { Button } from "../components/button.slint"; -import { Select, SelectOption } from "../components/select.slint"; -import { Separator, Orientation } from "../components/separator.slint"; +import { SettingItem } from "../types.slint"; + +export component SettingsPage inherits Rectangle { + in property <[SettingItem]> app-settings; + in property <[SettingItem]> clash-settings; + + callback toggle-setting(string /* key */, bool /* value */); + callback load-settings(); + + background: Theme.colors.background; -export component SettingsPage { - // App Settings - in property auto-start: false; - in property silent-start: false; - in property clash-core: false; - - // Clash Settings - in property tun-mode: false; - in property allow-lan: false; - in property ipv6: false; - in property unified-delay: false; - in property log-level-index: 1; - in property proxy-port: 7890; - - // Misc - in property app-dir: "C:/Program Files/Clash"; - in property config-dir: "C:/Users/User/.config/clash"; - in property core-dir: "C:/Program Files/Clash/core"; - in property app-version: "1.0.0"; - - callback toggle-auto-start(bool); - callback toggle-silent-start(bool); - callback toggle-clash-core(bool); - callback toggle-tun-mode(bool); - callback toggle-allow-lan(bool); - callback toggle-ipv6(bool); - callback toggle-unified-delay(bool); - callback log-level-changed(int); - callback open-port-settings(); - callback open-tun-config(); - callback open-directory(string); - VerticalLayout { - padding: SpacingSystem.spacing.s4; - spacing: SpacingSystem.spacing.s4; - - // Header - Text { - text: "Settings"; - color: Theme.colors.foreground; - font-size: Typography.sizes.xl; - font-weight: Typography.weights.bold; - } - - ScrollView { - VerticalLayout { - spacing: SpacingSystem.spacing.s3; - - // App Settings Section - Card { - VerticalLayout { - spacing: SpacingSystem.spacing.s3; - - Text { - text: "App Settings"; - color: Theme.colors.foreground; - font-size: Typography.sizes.base; - font-weight: Typography.weights.semibold; - } - - SettingRow { - icon: "🚀"; - label: "Auto Start"; - - Switch { - checked: root.auto-start; - toggled(checked) => { - root.toggle-auto-start(checked); - } - } - } - - Separator { orientation: Orientation.horizontal; } - - SettingRow { - icon: "🔇"; - label: "Silent Start"; - - Switch { - checked: root.silent-start; - toggled(checked) => { - root.toggle-silent-start(checked); - } - } - } - - Separator { orientation: Orientation.horizontal; } - - SettingRow { - icon: "⚙"; - label: "Clash Core"; - - Switch { - checked: root.clash-core; - toggled(checked) => { - root.toggle-clash-core(checked); - } - } - } - } - } - - // Clash Settings Section - Card { - VerticalLayout { - spacing: SpacingSystem.spacing.s3; - - Text { - text: "Clash Settings"; - color: Theme.colors.foreground; - font-size: Typography.sizes.base; - font-weight: Typography.weights.semibold; - } - - SettingRow { - icon: "🌐"; - label: "TUN Mode"; - - HorizontalLayout { - spacing: SpacingSystem.spacing.s2; - - Switch - checked: root.tun-mode; - toggled(checked) => { - root.toggle-tun-mode(checked); - } - } - - Button { - width: 24px; - height: 24px; - text: "⚙"; - variant: "ghost"; - - clicked => { - root.open-tun-config(); - } - } - } - } - - Separator { orientation: Orientation.horizontal; } - - SettingRow { - icon: "📡"; - label: "Allow LAN"; - - Switch { - checked: root.allow-lan; - toggled(checked) => { - root.toggle-allow-lan(checked); - } - } - } - - Separator { orientation: Orientation.horizontal; } - - SettingRow { - icon: "🌍"; - label: "IPv6"; - - Switch { - checked: root.ipv6; - toggled(checked) => { - root.toggle-ipv6(checked); - } - } - } - - Separator { orientation: Orientation.horizontal; } - - SettingRow { - icon: "⏱"; - label: "Unified Delay"; - - Switch { - checked: root.unified-delay; - toggled(checked) => { - root.toggle-unified-delay(checked); - } - } - } - - Separator { orientation: Orientation.horizontal; } - - SettingRow { - icon: "📝"; - label: "Log Level"; - - Select { - options: [ - { label: "Debug", valueg" }, - { label: "Info", value: "info" }, - { label: "Warn", value: "warn" }, - { label: "Error", value: "error" }, - ]; - selected-index: root.log-level-index; - - selected(index, value) => { - root.log-level-changed(index); - } - } - } - - Separator { orientation: Orientation.horizontal; } - - SettingRow { - icon: "🔌"; - label: "Proxy Port"; - - Button { - text: root.proxy-port; - variant: "link"; - - clicked => { - root.open-port-settings(); - } - } - } - } - - // Miscellaneous Section - Card { - VerticalLayout { - spacing: SpacingSystem.spacing.s3; - - Text { - text: "Miscellaneous"; - color: Theme.colors.foreground; - font-size: Typography.sizes.base; - font-weight: Typography.weights.semibold; - } - - SettingRow { - icon: "📁"; - l: "App Directory"; - - Button { - text: "Open"; - variant: "link"; - - clicked => { - root.open-directory(root.app-dir); - } - } - } - - Separator { orientation: Orientation.horizontal; } - - SettingRow { - icon: "📁"; - label: "Config Directory"; - - Button { - text: "Open"; - variant: "link"; - - clicked => { - root.open-directory(root.config-dir); - } - } - } - - Separator { orientation: Orientation.horizontal; } - - SettingRow { - icon: "📁"; - label: "Core Directory"; - - Button { - text: "Open"; - variant: "link"; - - clicked => { - root.open-directory(root.core-dir); - } - } - } - - Separator { orientation: Orientation.horizontal; } - - SettingRow { - icon: "ℹ"; - label: "App Version"; - - Text { - text: root.app-version; - color: Theme.colors.muted-foreground; - font-size: Typography.sizes.sm; - vertical-alignment: center; - } - } - } - } - } - } - } -} + // Fixed header + Rectangle { + height: 48px; + background: Theme.colors.background; -component SettingRow { - in property icon: ""; - in property label: ""; - - height: 40px; - - HorizontalLayout { - spacing: SpacingSystem.spacing.s3; - alignment: space-between; - - HorizontalLayout { - spacing: SpacingSystem.spacing.s2; - - Text { - text: root.icon; - font-size: Typography.sizes.base; - vertical-alignment: center; - } - - Text { - text: root.label; - color: Theme.colors.foreground; - font-size: Typography.sizes.sm; - vertical-alignment: center; + HorizontalLayout { + padding: SpacingSystem.spacing.s4; + + Text { + text: "Settings"; + font-size: Typography.sizes.xl; + font-weight: Typography.weights.bold; + color: Theme.colors.foreground; + vertical-alignment: center; + } + } + } + + // Scrollable content + Flickable { + viewport-height: content-layout.preferred-height + SpacingSystem.spacing.s4 * 2; + + content-layout := VerticalLayout { + padding: SpacingSystem.spacing.s4; + spacing: SpacingSystem.spacing.s4; + + // App Settings Section + Container { + VerticalLayout { + padding: SpacingSystem.spacing.s4; + spacing: SpacingSystem.spacing.s3; + + // Section title + Text { + text: "App 设置"; + font-size: Typography.sizes.base; + font-weight: Typography.weights.semibold; + color: Theme.colors.foreground; + } + + // App settings list + for setting in app-settings: Rectangle { + height: 48px; + + HorizontalLayout { + padding-top: SpacingSystem.spacing.s2; + padding-bottom: SpacingSystem.spacing.s2; + spacing: SpacingSystem.spacing.s3; + alignment: space-between; + + HorizontalLayout { + spacing: SpacingSystem.spacing.s3; + + // Icon + Text { + text: "✓"; + font-size: Typography.sizes.base; + color: Theme.colors.primary; + vertical-alignment: center; + } + + // Label + Text { + text: setting.label; + font-size: Typography.sizes.sm; + color: Theme.colors.foreground; + vertical-alignment: center; + } + } + + // Control based on type + if setting.type == "switch": Switch { + checked: setting.enabled; + + toggled => { + root.toggle-setting(setting.key, self.checked); + } + } + + if setting.type == "button": Button { + text: setting.value; + variant: "link"; + size: "sm"; + } + + if setting.type == "text": Text { + text: setting.value; + font-size: Typography.sizes.sm; + color: Theme.colors.muted-foreground; + vertical-alignment: center; + } + } + } + } + } + + // Clash Settings Section + Container { + VerticalLayout { + padding: SpacingSystem.spacing.s4; + spacing: SpacingSystem.spacing.s3; + + // Section title + Text { + text: "Clash 设置"; + font-size: Typography.sizes.base; + font-weight: Typography.weights.semibold; + color: Theme.colors.foreground; + } + + // Clash settings list + for setting in clash-settings: Rectangle { + height: 48px; + + HorizontalLayout { + padding-top: SpacingSystem.spacing.s2; + padding-bottom: SpacingSystem.spacing.s2; + spacing: SpacingSystem.spacing.s3; + alignment: space-between; + + HorizontalLayout { + spacing: SpacingSystem.spacing.s3; + + // Icon + Text { + text: "⚙"; + font-size: Typography.sizes.base; + color: Theme.colors.primary; + vertical-alignment: center; + } + + // Label + Text { + text: setting.label; + font-size: Typography.sizes.sm; + color: Theme.colors.foreground; + vertical-alignment: center; + } + } + + // Control based on type + if setting.type == "switch": Switch { + checked: setting.enabled; + + toggled => { + root.toggle-setting(setting.key, self.checked); + } + } + + if setting.type == "button": Button { + text: setting.value; + variant: "link"; + size: "sm"; + } + + if setting.type == "text": Text { + text: setting.value; + font-size: Typography.sizes.sm; + color: Theme.colors.muted-foreground; + vertical-alignment: center; + } + } + } + } + } + + // Miscellaneous Section + Container { + VerticalLayout { + padding: SpacingSystem.spacing.s4; + spacing: SpacingSystem.spacing.s3; + + // Section title + Text { + text: "杂项"; + font-size: Typography.sizes.base; + font-weight: Typography.weights.semibold; + color: Theme.colors.foreground; + } + + // Misc items + Rectangle { + height: 48px; + + HorizontalLayout { + padding-top: SpacingSystem.spacing.s2; + padding-bottom: SpacingSystem.spacing.s2; + spacing: SpacingSystem.spacing.s3; + alignment: space-between; + + HorizontalLayout { + spacing: SpacingSystem.spacing.s3; + + Text { + text: "📁"; + font-size: Typography.sizes.base; + vertical-alignment: center; + } + + Text { + text: "App 目录"; + font-size: Typography.sizes.sm; + color: Theme.colors.foreground; + vertical-alignment: center; + } + } + + Button { + text: "打开"; + variant: "link"; + size: "sm"; + } + } + } + + Rectangle { + height: 48px; + + HorizontalLayout { + padding-top: SpacingSystem.spacing.s2; + padding-bottom: SpacingSystem.spacing.s2; + spacing: SpacingSystem.spacing.s3; + alignment: space-between; + + HorizontalLayout { + spacing: SpacingSystem.spacing.s3; + + Text { + text: "📋"; + font-size: Typography.sizes.base; + vertical-alignment: center; + } + + Text { + text: "App 版本"; + font-size: Typography.sizes.sm; + color: Theme.colors.foreground; + vertical-alignment: center; + } + } + + Text { + text: "v1.0.0"; + font-size: Typography.sizes.sm; + color: Theme.colors.muted-foreground; + vertical-alignment: center; + } + } + } + } + } } } - - @children } } diff --git a/ui/types.slint b/ui/types.slint new file mode 100644 index 0000000..53914eb --- /dev/null +++ b/ui/types.slint @@ -0,0 +1,57 @@ +// Data structure definitions for the proxy management application + +// 配置文件信息 +export struct Profile { + id: string, + name: string, + url: string, + type: string, // "remote" | "local" + updated-at: string, + usage: string, // "1.26GB / 100GB" + expire-at: string, + selected: bool, +} + +// 代理节点 +export struct ProxyNode { + name: string, + type: string, // "Vmess", "Shadowsocks", etc. + delay: int, // 延迟 ms,-1 表示未测试 + udp: bool, +} + +// 代理组 +export struct ProxyGroup { + name: string, + type: string, // "Selector", "URLTest", etc. + now: string, // 当前选中的节点 + nodes: [ProxyNode], +} + +// 连接信息 +export struct Connection { + id: string, + host: string, + process: string, + rule: string, + chains: string, + upload: string, + download: string, + time: string, +} + +// 日志条目 +export struct LogEntry { + time: string, + level: string, // "debug", "info", "warn", "error" + message: string, +} + +// 设置项 +export struct SettingItem { + key: string, + label: string, + value: string, + type: string, // "switch", "select", "button", "text" + enabled: bool, +}