Skip to main content

xtap_core_lib/ui/
theme.rs

1/*
2 * Copyright (c) 2026 星TAP实验室
3 * Skill: 通用 UI 布局与主题 (Common UI Layout & Theme)
4 */
5
6use chrono::Timelike;
7use egui::{Color32, Margin, Visuals};
8
9/// UI 布局大师常量
10pub struct UiTheme;
11
12/// 主题模式
13#[derive(Debug, Clone, Copy, PartialEq, serde::Deserialize, serde::Serialize)]
14pub enum ThemeMode {
15    Auto,
16    Light,
17    Dark,
18}
19
20impl UiTheme {
21    // --- 间距与尺寸 ---
22    pub const PADDING: f32 = 20.0;
23    pub const SPACING: f32 = 12.0;
24    pub const ROUNDING: u8 = 12; // 更加圆润
25    pub const BUTTON_HEIGHT: f32 = 36.0;
26    pub const ICON_SIZE: f32 = 18.0;
27
28    // --- 品牌颜色 (星TAP实验室 风格) ---
29    pub const PRIMARY: Color32 = Color32::from_rgb(0, 120, 215);
30    pub const SUCCESS: Color32 = Color32::from_rgb(16, 124, 16);
31    pub const ERROR: Color32 = Color32::from_rgb(209, 52, 56);
32
33    /// 获取标准的外边距
34    pub fn standard_margin() -> Margin {
35        Margin {
36            left: Self::PADDING as i8,
37            right: Self::PADDING as i8,
38            top: Self::PADDING as i8,
39            bottom: Self::PADDING as i8,
40        }
41    }
42
43    /// 应用星TAP实验室推荐的视觉风格
44    pub fn apply_master_visuals(ctx: &egui::Context, mode: ThemeMode) {
45        let is_dark = match mode {
46            ThemeMode::Light => false,
47            ThemeMode::Dark => true,
48            ThemeMode::Auto => {
49                let hour = chrono::Local::now().hour();
50                !(6..18).contains(&hour) // 晚 6 点到早 6 点为深色模式
51            }
52        };
53
54        let mut visuals = if is_dark {
55            let mut v = Visuals::dark();
56            // 深色:深蓝灰质感 (更高级的深色)
57            v.panel_fill = Color32::from_rgb(18, 20, 24);
58            v.widgets.noninteractive.bg_fill = Color32::from_rgb(30, 33, 37);
59            v.widgets.noninteractive.weak_bg_fill = Color32::from_rgb(40, 44, 49);
60            v.extreme_bg_color = Color32::from_rgb(12, 14, 16);
61            v.widgets.noninteractive.fg_stroke.color = Color32::from_gray(200);
62            v.widgets.noninteractive.bg_stroke.color = Color32::from_gray(50); // 统一边框
63
64            // 按钮统一圆角
65            v.widgets.inactive.corner_radius = 12.0.into();
66            v.widgets.hovered.corner_radius = 12.0.into();
67            v.widgets.active.corner_radius = 12.0.into();
68            v
69        } else {
70            let mut v = Visuals::light();
71            // 浅色:纯净乳白/浅灰
72            v.panel_fill = Color32::from_rgb(248, 249, 250);
73            v.widgets.noninteractive.bg_fill = Color32::from_rgb(240, 242, 245);
74            v.widgets.noninteractive.weak_bg_fill = Color32::from_rgb(255, 255, 255);
75            v.extreme_bg_color = Color32::from_rgb(230, 233, 237);
76            v.widgets.noninteractive.fg_stroke.color = Color32::from_rgb(45, 45, 45); // 深灰黑文字
77            v.widgets.noninteractive.bg_stroke.color = Color32::from_gray(200); // 统一边框
78
79            // 按钮统一圆角
80            v.widgets.inactive.corner_radius = 12.0.into();
81            v.widgets.hovered.corner_radius = 12.0.into();
82            v.widgets.active.corner_radius = 12.0.into();
83            v
84        };
85
86        // 统一窗口组件圆角
87        visuals.window_corner_radius = 15.0.into();
88
89        // 强化主色调交互
90        visuals.selection.bg_fill = Self::PRIMARY;
91
92        ctx.set_visuals(visuals);
93    }
94
95    /// [大师技巧] 快速居中排版辅助
96    pub fn center_content<R>(
97        ui: &mut egui::Ui,
98        add_contents: impl FnOnce(&mut egui::Ui) -> R,
99    ) -> R {
100        ui.vertical_centered(|ui| {
101            ui.add_space(ui.available_height() / 3.0);
102            add_contents(ui)
103        })
104        .inner
105    }
106}