use eframe::{egui, egui_wgpu}; use std::sync::Arc; use crate::connection::ConnectionManager; use crate::theme::{ENGINEERING_DARK, apply_fonts, apply_theme}; use crate::{ matrix::{MATRIX_COLS, MATRIX_ROWS}, render::{BackgroundRenderResources, PRESSURE_CELL_COUNT, WgpuBackgroundCallback}, ui::{ ConfigPanelState, ConnectPanelState, FloatingPanelState, draw_config_panel, draw_connect_panel, draw_scene_panel, draw_stats_panel, }, }; const DATA_LOG_EVERY_FRAMES: u64 = 30; pub struct EskinDesktopApp { connect_panel: FloatingPanelState, connect_state: ConnectPanelState, connection: Arc, pressure_matrix: [f32; PRESSURE_CELL_COUNT], data_log_frame: u64, scene_panel: FloatingPanelState, config_panel: FloatingPanelState, config_state: ConfigPanelState, stats_panel: FloatingPanelState, } impl EskinDesktopApp { pub fn new(cc: &eframe::CreationContext<'_>) -> Self { egui_extras::install_image_loaders(&cc.egui_ctx); apply_fonts(&cc.egui_ctx); apply_theme(&cc.egui_ctx, &ENGINEERING_DARK); let wgpu_state = cc .wgpu_render_state .as_ref() .expect("need open eframe wgpu renderer feature"); let mut renderer = wgpu_state.renderer.write(); renderer .callback_resources .insert(BackgroundRenderResources::new( &wgpu_state.device, &wgpu_state.target_format, MATRIX_ROWS, MATRIX_COLS, )); Self { connect_panel: FloatingPanelState::new([0.0, 0.0], [0.0, 0.0]), connect_state: ConnectPanelState::default(), connection: Arc::new(ConnectionManager::new()), pressure_matrix: [0.0; PRESSURE_CELL_COUNT], data_log_frame: 0, scene_panel: FloatingPanelState::new([16.0, 48.0], [16.0, 48.0]), config_panel: FloatingPanelState::new([840.0, 48.0], [128.0, 48.0]), config_state: ConfigPanelState::default(), stats_panel: FloatingPanelState::new([16.0, 520.0], [240.0, 48.0]), } } fn draw_wgpu_background(&mut self, ui: &mut egui::Ui) { self.update_pressure_matrix(); let rect = ui.max_rect(); let width = rect.width().max(1.0); let height = rect.height().max(1.0); ui.painter().add(egui_wgpu::Callback::new_paint_callback( rect, WgpuBackgroundCallback { width, height, pressure: self.pressure_matrix, }, )); } fn update_pressure_matrix(&mut self) { if let Some(sample) = self.connection.take_latest_sample() { normalize_pressure_sample( &sample.matrix, sample.rows, sample.cols, &mut self.pressure_matrix, ); self.data_log_frame += 1; if self.data_log_frame % DATA_LOG_EVERY_FRAMES == 0 { log_pressure_sample(&sample.matrix, sample.rows, sample.cols); } } } fn draw_toolbar(&mut self, ui: &mut egui::Ui) { egui::Panel::top("main_menu").show_inside(ui, |ui| { ui.horizontal(|ui| { ui.checkbox(&mut self.connect_panel.visible, "连接"); ui.checkbox(&mut self.scene_panel.visible, "场景"); ui.checkbox(&mut self.config_panel.visible, "配置"); ui.checkbox(&mut self.stats_panel.visible, "统计"); }); }); } fn draw_floating_panels(&mut self, ctx: &egui::Context) { draw_connect_panel( ctx, &mut self.connect_panel, &mut self.connect_state, &self.connection, ); draw_scene_panel(ctx, &mut self.scene_panel); draw_config_panel(ctx, &mut self.config_panel, &mut self.config_state); draw_stats_panel(ctx, &mut self.stats_panel); } } fn log_pressure_sample(raw: &[u32], rows: u32, cols: u32) { let max = raw.iter().copied().max().unwrap_or(0); let sum: u64 = raw.iter().map(|value| *value as u64).sum(); let non_zero = raw.iter().filter(|value| **value != 0).count(); let preview = raw .iter() .take(12) .map(u32::to_string) .collect::>() .join(", "); println!( "[pressure] {rows}x{cols} cells={} non_zero={non_zero} max={max} sum={sum} first=[{preview}]", raw.len() ); } fn normalize_pressure_sample( raw: &[u32], rows: u32, cols: u32, normalized: &mut [f32; PRESSURE_CELL_COUNT], ) { normalized.fill(0.0); let max_value = raw.iter().copied().max().unwrap_or(0); if max_value == 0 { return; } let src_cols = cols.max(1); let copy_rows = MATRIX_ROWS.min(rows); let copy_cols = MATRIX_COLS.min(cols); for row in 0..copy_rows { for col in 0..copy_cols { let src_index = (row * src_cols + col) as usize; let dst_index = (row * MATRIX_COLS + col) as usize; if let Some(value) = raw.get(src_index) { normalized[dst_index] = (*value as f32 / max_value as f32).clamp(0.0, 1.0); } } } } impl eframe::App for EskinDesktopApp { fn ui(&mut self, ui: &mut egui::Ui, _frame: &mut eframe::Frame) { let ctx = ui.ctx().clone(); self.draw_wgpu_background(ui); self.draw_toolbar(ui); self.draw_floating_panels(&ctx); // Keep repainting while the wgpu background is a realtime viewport. ctx.request_repaint(); } }