feat: 添加 README,更新 .gitignore,移除 JE-Skin/eskin-finger-sdk
- 添加 README.md 项目文档 - 更新 .gitignore 排除 JE-Skin/、eskin-finger-sdk/ 及构建产物 - 从 git 跟踪中移除 JE-Skin 和 eskin-finger-sdk Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
154
src/app.rs
154
src/app.rs
@@ -5,7 +5,9 @@ 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},
|
||||
render::{
|
||||
BackgroundRenderResources, PRESSURE_CELL_COUNT, PressureFrame, WgpuBackgroundCallback,
|
||||
},
|
||||
ui::{
|
||||
ConfigPanelState, ConnectPanelState, FloatingPanelState, draw_config_panel,
|
||||
draw_connect_panel, draw_scene_panel, draw_stats_panel,
|
||||
@@ -18,7 +20,7 @@ pub struct EskinDesktopApp {
|
||||
connect_panel: FloatingPanelState,
|
||||
connect_state: ConnectPanelState,
|
||||
connection: Arc<ConnectionManager>,
|
||||
pressure_matrix: [f32; PRESSURE_CELL_COUNT],
|
||||
pressure_matrix: PressureFrame,
|
||||
data_log_frame: u64,
|
||||
scene_panel: FloatingPanelState,
|
||||
config_panel: FloatingPanelState,
|
||||
@@ -51,7 +53,7 @@ impl EskinDesktopApp {
|
||||
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],
|
||||
pressure_matrix: [[0.0, 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]),
|
||||
@@ -92,15 +94,115 @@ impl EskinDesktopApp {
|
||||
}
|
||||
}
|
||||
|
||||
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_title_bar(&mut self, ui: &mut egui::Ui, _frame: &mut eframe::Frame) {
|
||||
let title_bar_height = 36.0;
|
||||
let title_bar_rect = ui
|
||||
.allocate_space(egui::vec2(ui.available_width(), title_bar_height))
|
||||
.1;
|
||||
|
||||
// Paint background
|
||||
ui.painter().rect_filled(
|
||||
title_bar_rect,
|
||||
egui::CornerRadius::ZERO,
|
||||
ENGINEERING_DARK.panel_deep,
|
||||
);
|
||||
ui.painter().line_segment(
|
||||
[title_bar_rect.left_bottom(), title_bar_rect.right_bottom()],
|
||||
egui::Stroke::new(1.0, ENGINEERING_DARK.border),
|
||||
);
|
||||
|
||||
// Drag-to-move: double-click to maximize, drag to move
|
||||
let title_bar_response = ui.interact(
|
||||
title_bar_rect,
|
||||
egui::Id::new("title_bar"),
|
||||
egui::Sense::click_and_drag(),
|
||||
);
|
||||
if title_bar_response.drag_started() {
|
||||
ui.ctx().send_viewport_cmd(egui::ViewportCommand::StartDrag);
|
||||
}
|
||||
if title_bar_response.double_clicked() {
|
||||
let is_maximized = ui.input(|i| i.viewport().maximized.unwrap_or(false));
|
||||
ui.ctx()
|
||||
.send_viewport_cmd(egui::ViewportCommand::Maximized(!is_maximized));
|
||||
}
|
||||
|
||||
// macOS traffic light buttons (close / minimize / maximize)
|
||||
let btn_size = 14.0;
|
||||
let btn_spacing = 8.0;
|
||||
let btns_start = title_bar_rect.left_center() + egui::vec2(12.0, 0.0);
|
||||
|
||||
let btn_close_center = btns_start;
|
||||
let btn_min_center = btns_start + egui::vec2(btn_size + btn_spacing, 0.0);
|
||||
let btn_max_center = btns_start + egui::vec2((btn_size + btn_spacing) * 2.0, 0.0);
|
||||
|
||||
// Close (red)
|
||||
let close_rect =
|
||||
egui::Rect::from_center_size(btn_close_center, egui::vec2(btn_size, btn_size));
|
||||
let close_resp = ui.interact(close_rect, egui::Id::new("btn_close"), egui::Sense::click());
|
||||
let close_color = egui::Color32::from_rgb(255, 95, 86);
|
||||
ui.painter()
|
||||
.circle_filled(btn_close_center, btn_size / 2.0, close_color);
|
||||
// Draw × when hovered
|
||||
if close_resp.hovered() {
|
||||
ui.painter().line_segment(
|
||||
[
|
||||
btn_close_center + egui::vec2(-3.0, -3.0),
|
||||
btn_close_center + egui::vec2(3.0, 3.0),
|
||||
],
|
||||
egui::Stroke::new(1.5, egui::Color32::from_rgb(80, 0, 0)),
|
||||
);
|
||||
ui.painter().line_segment(
|
||||
[
|
||||
btn_close_center + egui::vec2(3.0, -3.0),
|
||||
btn_close_center + egui::vec2(-3.0, 3.0),
|
||||
],
|
||||
egui::Stroke::new(1.5, egui::Color32::from_rgb(80, 0, 0)),
|
||||
);
|
||||
}
|
||||
if close_resp.clicked() {
|
||||
ui.ctx().send_viewport_cmd(egui::ViewportCommand::Close);
|
||||
}
|
||||
|
||||
// Minimize (yellow)
|
||||
let min_rect = egui::Rect::from_center_size(btn_min_center, egui::vec2(btn_size, btn_size));
|
||||
let min_resp = ui.interact(min_rect, egui::Id::new("btn_min"), egui::Sense::click());
|
||||
let min_color = egui::Color32::from_rgb(255, 189, 46);
|
||||
ui.painter()
|
||||
.circle_filled(btn_min_center, btn_size / 2.0, min_color);
|
||||
if min_resp.hovered() {
|
||||
ui.painter().line_segment(
|
||||
[
|
||||
btn_min_center + egui::vec2(-3.0, 0.0),
|
||||
btn_min_center + egui::vec2(3.0, 0.0),
|
||||
],
|
||||
egui::Stroke::new(1.5, egui::Color32::from_rgb(120, 80, 0)),
|
||||
);
|
||||
}
|
||||
if min_resp.clicked() {
|
||||
ui.ctx()
|
||||
.send_viewport_cmd(egui::ViewportCommand::Minimized(true));
|
||||
}
|
||||
|
||||
// Maximize (green)
|
||||
let max_rect = egui::Rect::from_center_size(btn_max_center, egui::vec2(btn_size, btn_size));
|
||||
let max_resp = ui.interact(max_rect, egui::Id::new("btn_max"), egui::Sense::click());
|
||||
let max_color = egui::Color32::from_rgb(39, 201, 63);
|
||||
ui.painter()
|
||||
.circle_filled(btn_max_center, btn_size / 2.0, max_color);
|
||||
if max_resp.hovered() {
|
||||
let s = 3.0;
|
||||
ui.painter().rect_stroke(
|
||||
egui::Rect::from_center_size(btn_max_center, egui::vec2(s * 2.0, s * 2.0)),
|
||||
egui::CornerRadius::same(1),
|
||||
egui::Stroke::new(1.5, egui::Color32::from_rgb(0, 80, 10)),
|
||||
egui::StrokeKind::Outside,
|
||||
);
|
||||
}
|
||||
if max_resp.clicked() {
|
||||
let is_maximized = ui.input(|i| i.viewport().maximized.unwrap_or(false));
|
||||
ui.ctx()
|
||||
.send_viewport_cmd(egui::ViewportCommand::Maximized(!is_maximized));
|
||||
}
|
||||
}
|
||||
|
||||
fn draw_floating_panels(&mut self, ctx: &egui::Context) {
|
||||
@@ -133,18 +235,11 @@ fn log_pressure_sample(raw: &[u32], rows: u32, cols: u32) {
|
||||
);
|
||||
}
|
||||
|
||||
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);
|
||||
fn normalize_pressure_sample(raw: &[u32], rows: u32, cols: u32, normalized: &mut PressureFrame) {
|
||||
const RANGE_MIN: f32 = 0.0;
|
||||
const RANGE_MAX: f32 = 7000.0;
|
||||
|
||||
if max_value == 0 {
|
||||
return;
|
||||
}
|
||||
normalized.fill([0.0, 0.0]);
|
||||
|
||||
let src_cols = cols.max(1);
|
||||
let copy_rows = MATRIX_ROWS.min(rows);
|
||||
@@ -155,18 +250,25 @@ fn normalize_pressure_sample(
|
||||
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);
|
||||
let raw_value = *value as f32;
|
||||
let mapped = ((raw_value - RANGE_MIN) / (RANGE_MAX - RANGE_MIN)).clamp(0.0, 1.0);
|
||||
let display_value = if raw_value <= RANGE_MIN + 4.0 {
|
||||
0.0
|
||||
} else {
|
||||
raw_value.round().min(9999.0)
|
||||
};
|
||||
normalized[dst_index] = [mapped, display_value];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl eframe::App for EskinDesktopApp {
|
||||
fn ui(&mut self, ui: &mut egui::Ui, _frame: &mut eframe::Frame) {
|
||||
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_title_bar(ui, frame);
|
||||
self.draw_floating_panels(&ctx);
|
||||
|
||||
// Keep repainting while the wgpu background is a realtime viewport.
|
||||
|
||||
Reference in New Issue
Block a user