diff --git a/Cargo.lock b/Cargo.lock index 196dc11..7d8326f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1147,27 +1147,6 @@ dependencies = [ "simd-adler32", ] -[[package]] -name = "femtovg" -version = "0.19.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be5d925785ad33d7b0ae2b445d9f157c3ab42ff3c515fff0b46d53d4a86c43c5" -dependencies = [ - "bitflags 2.10.0", - "bytemuck", - "fnv", - "glow", - "image", - "imgref", - "itertools 0.14.0", - "log", - "rgb", - "slotmap", - "ttf-parser 0.25.1", - "wasm-bindgen", - "web-sys", -] - [[package]] name = "field-offset" version = "0.3.6" @@ -1211,12 +1190,6 @@ version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "98de4bbd547a563b716d8dfa9aad1cb19bfab00f4fa09a6a4ed21dbcf44ce9c4" -[[package]] -name = "fnv" -version = "1.0.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" - [[package]] name = "foldhash" version = "0.1.5" @@ -1659,8 +1632,9 @@ dependencies = [ "glutin", "i-slint-common", "i-slint-core", - "i-slint-renderer-femtovg", + "i-slint-renderer-skia", "input", + "memmap2", "nix", "raw-window-handle", "xkbcommon", @@ -1696,7 +1670,6 @@ dependencies = [ "i-slint-common", "i-slint-core", "i-slint-core-macros", - "i-slint-renderer-femtovg", "i-slint-renderer-skia", "lyon_path", "muda", @@ -1811,29 +1784,6 @@ dependencies = [ "syn", ] -[[package]] -name = "i-slint-renderer-femtovg" -version = "1.14.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03d85d77f899ecb1f05c50c659a6d356fba44463686713f1f925c8be48de6afb" -dependencies = [ - "cfg-if", - "const-field-offset", - "derive_more", - "femtovg", - "glow", - "i-slint-common", - "i-slint-core", - "i-slint-core-macros", - "imgref", - "lyon_path", - "pin-weak", - "rgb", - "ttf-parser 0.25.1", - "wasm-bindgen", - "web-sys", -] - [[package]] name = "i-slint-renderer-skia" version = "1.14.1" @@ -3779,7 +3729,6 @@ dependencies = [ "i-slint-backend-selector", "i-slint-core", "i-slint-core-macros", - "i-slint-renderer-femtovg", "num-traits", "once_cell", "pin-weak", @@ -3925,6 +3874,7 @@ checksum = "aac18da81ebbf05109ab275b157c22a653bb3c12cf884450179942f81bcbf6c3" dependencies = [ "as-raw-xcb-connection", "bytemuck", + "drm", "fastrand", "js-sys", "memmap2", diff --git a/Cargo.toml b/Cargo.toml index 6a99692..c6486c2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,11 +4,14 @@ version = "0.0.1" edition = "2024" [dependencies] -slint = { version = "1.14", default-features = false, features = ["backend-winit", "renderer-femtovg", "compat-1-2"] } +slint = { version = "1.14", default-features = false, features = ["backend-winit", "renderer-winit-skia", "compat-1-2"] } [build-dependencies] slint-build = "1.14" +[features] +default = [] +lite = [] # Lightweight demo with minimal memory footprint [profile.release] opt-level = 'z' # 优化代码体积 diff --git a/build.rs b/build.rs index c984be2..3f110f1 100644 --- a/build.rs +++ b/build.rs @@ -1,3 +1,12 @@ fn main() { - slint_build::compile("ui/demo.slint").unwrap(); + // Use SLINT_DEMO environment variable to choose demo version + // cargo build -> full demo + // cargo build --features lite -> lite demo + let demo_file = if cfg!(feature = "lite") { + "ui/demo-lite.slint" + } else { + "ui/demo.slint" + }; + + slint_build::compile(demo_file).unwrap(); } diff --git a/src/main.rs b/src/main.rs index bcf3138..d2516b2 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,59 +1,72 @@ slint::include_modules!(); +#[cfg(not(feature = "lite"))] use slint::{ComponentHandle, Model, ModelRc, VecModel}; +#[cfg(not(feature = "lite"))] use std::rc::Rc; fn main() -> Result<(), slint::PlatformError> { - let ui = Demo::new()?; + #[cfg(feature = "lite")] + { + // Lite version - minimal memory footprint + let ui = DemoLite::new()?; + ui.run() + } - // Toast management - let toasts: Rc> = Rc::new(VecModel::default()); - ui.set_toasts(ModelRc::from(toasts.clone())); + #[cfg(not(feature = "lite"))] + { + // Full version with all features + let ui = Demo::new()?; - // Handle add-task callback - let ui_weak = ui.as_weak(); - let toasts_clone = toasts.clone(); - ui.on_add_task(move |task_title| { - let ui = ui_weak.unwrap(); + // Toast management + let toasts: Rc> = Rc::new(VecModel::default()); + ui.set_toasts(ModelRc::from(toasts.clone())); - // Show success toast - toasts_clone.push(ToastMessage { - message: format!("Task '{}' added successfully!", task_title).into(), - variant: "success".into(), - show: true, + // Handle add-task callback + let ui_weak = ui.as_weak(); + let toasts_clone = toasts.clone(); + ui.on_add_task(move |task_title| { + let ui = ui_weak.unwrap(); + + // Show success toast + toasts_clone.push(ToastMessage { + message: format!("Task '{}' added successfully!", task_title).into(), + variant: "success".into(), + show: true, + }); + + // Auto-dismiss after 3 seconds + let toasts_clone2 = toasts_clone.clone(); + let index = toasts_clone.row_count() - 1; + slint::Timer::single_shot(std::time::Duration::from_secs(3), move || { + if index < toasts_clone2.row_count() { + toasts_clone2.remove(index); + } + }); + + // Clear input + ui.set_new_task_title("".into()); }); - // Auto-dismiss after 3 seconds - let toasts_clone2 = toasts_clone.clone(); - let index = toasts_clone.row_count() - 1; - slint::Timer::single_shot(std::time::Duration::from_secs(3), move || { - if index < toasts_clone2.row_count() { - toasts_clone2.remove(index); - } + // Handle show-toast callback + let toasts_clone = toasts.clone(); + ui.on_show_toast(move |message, variant| { + toasts_clone.push(ToastMessage { + message: message.clone(), + variant: variant.clone(), + show: true, + }); + + // Auto-dismiss after 3 seconds + let toasts_clone2 = toasts_clone.clone(); + let index = toasts_clone.row_count() - 1; + slint::Timer::single_shot(std::time::Duration::from_secs(3), move || { + if index < toasts_clone2.row_count() { + toasts_clone2.remove(index); + } + }); }); - // Clear input - ui.set_new_task_title("".into()); - }); - - // Handle show-toast callback - let toasts_clone = toasts.clone(); - ui.on_show_toast(move |message, variant| { - toasts_clone.push(ToastMessage { - message: message.clone(), - variant: variant.clone(), - show: true, - }); - - // Auto-dismiss after 3 seconds - let toasts_clone2 = toasts_clone.clone(); - let index = toasts_clone.row_count() - 1; - slint::Timer::single_shot(std::time::Duration::from_secs(3), move || { - if index < toasts_clone2.row_count() { - toasts_clone2.remove(index); - } - }); - }); - - ui.run() + ui.run() + } } diff --git a/ui/components/button.slint b/ui/components/button.slint index 6d919b2..c20ac98 100644 --- a/ui/components/button.slint +++ b/ui/components/button.slint @@ -14,10 +14,10 @@ export component Button { // Callbacks callback clicked(); - // Calculate text color based on variant + // Calculate text color based on variant (shadcn design tokens) private property text-color: { if disabled { Theme.colors.muted-foreground } - else if variant == "destructive" { Theme.colors.destructive-foreground } + else if variant == "destructive" { Theme.colors.destructive } // destructive uses destructive color for text else if variant == "outline" { Theme.colors.foreground } else if variant == "secondary" { Theme.colors.secondary-foreground } else if variant == "ghost" { Theme.colors.foreground } @@ -48,8 +48,9 @@ export component Button { // Main container container := Rectangle { border-radius: SpacingSystem.radius.md; - border-width: variant == "outline" ? 1px : 0px; - border-color: Theme.colors.border; + // Outline variant always has border + border-width: root.variant == "outline" ? 1px : 0px; + border-color: root.variant == "outline" ? Theme.colors.border : Colors.transparent; // Touch interaction area (must be defined before using touch.pressed/has-hover) touch := TouchArea { @@ -61,45 +62,42 @@ export component Button { } } - // Background with state-based colors (shadcn style) - background-rect := Rectangle { - width: 100%; - height: 100%; - border-radius: parent.border-radius; - - // Calculate background color based on variant and state - background: { - if root.disabled { - Theme.colors.muted - } else if root.variant == "destructive" { - if touch.pressed { Theme.colors.destructive.darker(0.1) } - else if touch.has-hover { Theme.colors.destructive.darker(0.05) } - else { Theme.colors.destructive } - } else if root.variant == "outline" { - if touch.pressed { Theme.colors.accent.darker(0.05) } - else if touch.has-hover { Theme.colors.accent } - else { Colors.transparent } - } else if root.variant == "secondary" { - if touch.pressed { Theme.colors.secondary.darker(0.1) } - else if touch.has-hover { Theme.colors.secondary.darker(0.05) } - else { Theme.colors.secondary } - } else if root.variant == "ghost" { - if touch.pressed { Theme.colors.accent.darker(0.05) } - else if touch.has-hover { Theme.colors.accent } - else { Colors.transparent } - } else { - // default variant - if touch.pressed { Theme.colors.primary.darker(0.1) } - else if touch.has-hover { Theme.colors.primary.darker(0.05) } - else { Theme.colors.primary } - } - }; - - // Smooth transitions - animate background { - duration: Animations.durations.fast; - easing: Animations.ease-in-out; + // Calculate background color based on variant and state (shadcn official design) + background: { + if root.disabled { + Theme.colors.muted + } else if root.variant == "destructive" { + // destructive: bg-destructive/10 hover:bg-destructive/20 + if touch.pressed { Theme.colors.destructive.with-alpha(0.25) } + else if touch.has-hover { Theme.colors.destructive.with-alpha(0.2) } + else { Theme.colors.destructive.with-alpha(0.1) } + } else if root.variant == "outline" { + // outline: bg-background hover:bg-muted + if touch.pressed { Theme.colors.muted.darker(0.05) } + else if touch.has-hover { Theme.colors.muted } + else { Theme.colors.background } + } else if root.variant == "secondary" { + // secondary: bg-secondary hover:bg-secondary/80 + if touch.pressed { Theme.colors.secondary.with-alpha(0.7) } + else if touch.has-hover { Theme.colors.secondary.with-alpha(0.8) } + else { Theme.colors.secondary } + } else if root.variant == "ghost" { + // ghost: transparent hover:bg-muted + if touch.pressed { Theme.colors.muted.darker(0.05) } + else if touch.has-hover { Theme.colors.muted } + else { Colors.transparent } + } else { + // default (primary): bg-primary hover:bg-primary/80 + if touch.pressed { Theme.colors.primary.with-alpha(0.7) } + else if touch.has-hover { Theme.colors.primary.with-alpha(0.8) } + else { Theme.colors.primary } } + }; + + // Smooth transitions + animate background { + duration: Animations.durations.fast; + easing: Animations.ease-in-out; } // Content layout diff --git a/ui/demo.slint b/ui/demo.slint index 91ee63e..864a03e 100644 --- a/ui/demo.slint +++ b/ui/demo.slint @@ -15,11 +15,11 @@ export component Demo inherits Window { title: "Slint Shadcn UI - Task Manager Demo"; background: Theme.colors.background; - // Window size settings - reduced for lower memory usage - min-width: 800px; - min-height: 600px; - preferred-width: 1000px; - preferred-height: 700px; + // Window size settings - optimized for lower memory usage + min-width: 600px; + min-height: 400px; + preferred-width: 800px; + preferred-height: 600px; // Application state in-out property new-task-title: ""; diff --git a/ui/theme/colors.slint b/ui/theme/colors.slint index 27b6721..225a6aa 100644 --- a/ui/theme/colors.slint +++ b/ui/theme/colors.slint @@ -1,5 +1,6 @@ // Shadcn-style Color System -// Based on shadcn/ui zinc theme with light/dark mode support +// Based on shadcn/ui official neutral theme with light/dark mode support +// Source: https://github.com/shadcn-ui/ui/blob/main/apps/v4/styles/globals.css // Color palette structure export struct ColorPalette { @@ -24,52 +25,53 @@ export struct ColorPalette { ring: color, } -// Light mode colors - shadcn zinc theme +// Light mode colors - shadcn official neutral theme +// oklch values converted to hex for Slint compatibility export global LightColors { out property palette: { - background: #ffffff, - foreground: #09090b, - card: #ffffff, - card-foreground: #09090b, - popover: #ffffff, - popover-foreground: #09090b, - primary: #18181b, - primary-foreground: #fafafa, - secondary: #f4f4f5, - secondary-foreground: #18181b, - muted: #f4f4f5, - muted-foreground: #71717a, - accent: #f4f4f5, - accent-foreground: #18181b, - destructive: #ef4444, - destructive-foreground: #fafafa, - border: #e4e4e7, - input: #e4e4e7, - ring: #18181b, + background: #ffffff, // oklch(1 0 0) + foreground: #252525, // oklch(0.145 0 0) + card: #ffffff, // oklch(1 0 0) + card-foreground: #252525, // oklch(0.145 0 0) + popover: #ffffff, // oklch(1 0 0) + popover-foreground: #252525, // oklch(0.145 0 0) + primary: #343434, // oklch(0.205 0 0) + primary-foreground: #fbfbfb, // oklch(0.985 0 0) + secondary: #f7f7f7, // oklch(0.97 0 0) + secondary-foreground: #343434, // oklch(0.205 0 0) + muted: #f7f7f7, // oklch(0.97 0 0) + muted-foreground: #8e8e8e, // oklch(0.556 0 0) + accent: #f7f7f7, // oklch(0.97 0 0) + accent-foreground: #343434, // oklch(0.205 0 0) + destructive: #ef4444, // oklch(0.577 0.245 27.325) + destructive-foreground: #f7f7f7, // oklch(0.97 0.01 17) - light red/pink, not white! + border: #ebebeb, // oklch(0.922 0 0) + input: #ebebeb, // oklch(0.922 0 0) + ring: #b4b4b4, // oklch(0.708 0 0) }; } -// Dark mode colors - shadcn zinc theme +// Dark mode colors - shadcn official neutral theme export global DarkColors { out property palette: { - background: #09090b, - foreground: #fafafa, - card: #09090b, - card-foreground: #fafafa, - popover: #09090b, - popover-foreground: #fafafa, - primary: #fafafa, - primary-foreground: #18181b, - secondary: #27272a, - secondary-foreground: #fafafa, - muted: #27272a, - muted-foreground: #a1a1aa, - accent: #27272a, - accent-foreground: #fafafa, - destructive: #7f1d1d, - destructive-foreground: #fafafa, - border: #27272a, - input: #27272a, - ring: #d4d4d8, + background: #252525, // oklch(0.145 0 0) + foreground: #fbfbfb, // oklch(0.985 0 0) + card: #343434, // oklch(0.205 0 0) + card-foreground: #fbfbfb, // oklch(0.985 0 0) + popover: #444444, // oklch(0.269 0 0) + popover-foreground: #fbfbfb, // oklch(0.985 0 0) + primary: #ebebeb, // oklch(0.922 0 0) + primary-foreground: #343434, // oklch(0.205 0 0) + secondary: #444444, // oklch(0.269 0 0) + secondary-foreground: #fbfbfb, // oklch(0.985 0 0) + muted: #444444, // oklch(0.269 0 0) + muted-foreground: #b4b4b4, // oklch(0.708 0 0) + accent: #5e5e5e, // oklch(0.371 0 0) + accent-foreground: #fbfbfb, // oklch(0.985 0 0) + destructive: #dc2626, // oklch(0.704 0.191 22.216) + destructive-foreground: #ef4444, // oklch(0.58 0.22 27) - red color, not white! + border: #1a1a1a, // oklch(1 0 0 / 10%) - 10% white on dark + input: #262626, // oklch(1 0 0 / 15%) - 15% white on dark + ring: #8e8e8e, // oklch(0.556 0 0) }; }