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:
lennlouisgeek
2026-05-20 03:15:16 +08:00
parent d2c9fad556
commit 5f1c217853
19 changed files with 628 additions and 116 deletions

View File

@@ -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.