use egui::{Color32, FontId, Stroke, Vec2}; use std::sync::atomic::{AtomicBool, AtomicI32}; use std::sync::Arc; use parking_lot::Mutex; pub struct AppState { pub running: Arc, pub current_score: Arc, pub status_text: Arc>, pub fetch_count: Arc, pub analysis_count: Arc, pub no_content_count: Arc, } impl AppState { pub fn new() -> Self { Self { running: Arc::new(AtomicBool::new(false)), current_score: Arc::new(AtomicI32::new(50)), status_text: Arc::new(Mutex::new("就绪".to_string())), fetch_count: Arc::new(AtomicI32::new(0)), analysis_count: Arc::new(AtomicI32::new(0)), no_content_count: Arc::new(AtomicI32::new(0)), } } } impl Default for AppState { fn default() -> Self { Self::new() } } pub fn get_score_color(score: i32) -> Color32 { match score { 0..=30 => Color32::from_rgb(21, 101, 192), // 深蓝 31..=38 => Color32::from_rgb(25, 118, 210), // 蓝色 39..=44 => Color32::from_rgb(66, 165, 245), // 浅蓝 45..=55 => Color32::from_rgb(102, 187, 106), // 绿色 56..=64 => Color32::from_rgb(255, 167, 38), // 橙色 65..=69 => Color32::from_rgb(251, 140, 0), // 深橙 70..=100 => Color32::from_rgb(229, 57, 53), // 红色 _ => Color32::GRAY, } } pub fn get_score_label(score: i32, cold: i32, warm: i32) -> &'static str { if score < cold { "看跌" } else if score > warm { "看涨" } else { "中性" } } pub fn get_score_description(score: i32) -> &'static str { match score { 0..=29 => "极度悲观", 30..=38 => "悲观", 39..=44 => "偏悲观", 45..=55 => "中立", 56..=64 => "偏乐观", 65..=69 => "乐观", 70..=100 => "极度乐观", _ => "无法判断", } } pub fn draw_indicator(ui: &mut egui::Ui, score: i32, cold: i32, warm: i32) { let color = get_score_color(score); let label = get_score_label(score, cold, warm); let description = get_score_description(score); let size = Vec2::new(140.0, 160.0); let (rect, _response) = ui.allocate_exact_size(size, egui::Sense::hover()); let painter = ui.painter(); let center = rect.center(); // 外圈背景 - 白色主题 painter.circle_filled(center, 65.0, Color32::from_rgb(240, 240, 245)); painter.circle_stroke(center, 65.0, Stroke::new(3.0, Color32::from_rgb(200, 200, 210))); // 内圈颜色 painter.circle_filled(center, 58.0, color); // 发光效果 for i in 1..=3 { let alpha = (60 / i) as u8; let glow_color = Color32::from_rgba_unmultiplied(color.r(), color.g(), color.b(), alpha); painter.circle_filled(center, 58.0 - (i as f32) * 5.0, glow_color); } // 中心白色背景 painter.circle_filled(center, 45.0, Color32::from_rgb(255, 255, 255)); // 分数文本 - 深色 let score_text = format!("{}", score); painter.text( center - Vec2::new(0.0, 5.0), egui::Align2::CENTER_CENTER, score_text, FontId::proportional(42.0), color, ); // 标签文本 - 深色 painter.text( center + Vec2::new(0.0, 28.0), egui::Align2::CENTER_CENTER, label, FontId::proportional(16.0), Color32::from_rgb(50, 50, 55), ); // 描述文本(在指示灯下方) ui.add_space(10.0); ui.label(egui::RichText::new(description).size(14.0).color(Color32::from_rgb(100, 100, 110))); } pub fn draw_waveform(ui: &mut egui::Ui, data: &[(String, f64)], _width: f32, _height: f32) { let rect = ui.available_rect_before_wrap(); let painter = ui.painter(); // 白色背景 painter.rect_filled(rect, 8.0, Color32::from_rgb(255, 255, 255)); painter.rect_stroke(rect, 8.0, Stroke::new(1.0, Color32::from_rgb(220, 220, 230))); if data.is_empty() { let text = "暂无数据"; painter.text( rect.center(), egui::Align2::CENTER_CENTER, text, FontId::proportional(14.0), Color32::from_rgb(150, 150, 160), ); return; } let values: Vec = data.iter().map(|(_, v)| *v).collect(); let min_val = values.iter().cloned().fold(f64::INFINITY, f64::min); let max_val = values.iter().cloned().fold(f64::NEG_INFINITY, f64::max); let range = if (max_val - min_val).abs() < 0.01 { 1.0 } else { max_val - min_val }; let padding = 15.0; let draw_width = rect.width() - padding * 2.0; let draw_height = rect.height() - padding * 2.0; let step_x = if data.len() > 1 { draw_width / (data.len() - 1) as f32 } else { 0.0 }; // 绘制网格线 for i in 0..=4 { let y = rect.max.y - padding - (i as f32) * (draw_height / 4.0); painter.line_segment( [egui::pos2(rect.min.x + padding, y), egui::pos2(rect.max.x - padding, y)], Stroke::new(1.0, Color32::from_rgba_unmultiplied(200, 200, 210, 100)), ); } // 绘制数据线 for i in 0..data.len().saturating_sub(1) { let x1 = rect.min.x + padding + (i as f32) * step_x; let x2 = rect.min.x + padding + ((i + 1) as f32) * step_x; let y1 = rect.max.y - padding - ((values[i] - min_val) / range * draw_height as f64) as f32; let y2 = rect.max.y - padding - ((values[i + 1] - min_val) / range * draw_height as f64) as f32; painter.line_segment( [egui::pos2(x1, y1), egui::pos2(x2, y2)], Stroke::new(2.0, Color32::from_rgb(0, 123, 255)), ); } // 绘制数据点 for i in 0..data.len() { let x = rect.min.x + padding + (i as f32) * step_x; let y = rect.max.y - padding - ((values[i] - min_val) / range * draw_height as f64) as f32; painter.circle_filled(egui::pos2(x, y), 3.0, Color32::from_rgb(0, 150, 255)); } }