Add finger mode UI and refine breakout/serial integration

This commit is contained in:
lenn
2026-07-02 15:22:13 +08:00
parent 0a9fea0f0a
commit 71d314ac44
4 changed files with 352 additions and 90 deletions

View File

@@ -13,16 +13,19 @@ use crate::{
},
ui::{
ConfigPanelState, ConnectPanelState, FloatingPanelState, MatrixConfigState,
draw_config_panel, draw_export_panel, draw_hand_force_panels, draw_matrix_config_panel,
draw_stats_panel, panel_restore_item,
draw_config_panel, draw_spatial_force_panel, draw_stats_panel, panel_restore_item,
},
};
use eframe::{egui, egui_wgpu};
use std::sync::Arc;
use std::{
sync::Arc,
time::{Duration, Instant},
};
const SUMMARY_POINTS_PER_SERIES: usize = 42;
const HAND_FORCE_PANEL_COUNT: usize = 7;
const HAND_FORCE_SEGMENT_COUNTS: [usize; HAND_FORCE_PANEL_COUNT] = [84, 84, 84, 84, 84, 70, 44];
const RATE_UPDATE_INTERVAL: Duration = Duration::from_millis(500);
pub struct EskinDesktopApp {
connect_panel: FloatingPanelState,
@@ -44,16 +47,61 @@ pub struct EskinDesktopApp {
hand_signal_histories: [Vec<f32>; HAND_FORCE_PANEL_COUNT],
force_estimator: ForceEstimatorState,
latest_spatial_force: Option<HudSpatialForce>,
spatial_force_panel_force: Option<HudSpatialForce>,
latest_raw_matrix: Vec<u32>,
latest_matrix_rows: u32,
latest_matrix_cols: u32,
breakout_visible: bool,
breakout_game: BreakoutGame,
live_rates: LiveRateMeters,
context_menu_open: bool,
context_menu_pos: egui::Pos2,
active_mode: ActiveMode,
}
struct LiveRateMeters {
window_started_at: Instant,
window_rx_frames: u64,
window_render_frames: u32,
sample_rate_hz: f32,
render_rate_hz: f32,
}
impl LiveRateMeters {
fn new() -> Self {
Self {
window_started_at: Instant::now(),
window_rx_frames: 0,
window_render_frames: 0,
sample_rate_hz: 0.0,
render_rate_hz: 0.0,
}
}
fn tick_render(&mut self, total_rx_frames: u64) {
if total_rx_frames < self.window_rx_frames {
self.window_rx_frames = total_rx_frames;
self.sample_rate_hz = 0.0;
}
self.window_render_frames = self.window_render_frames.saturating_add(1);
let now = Instant::now();
let elapsed = now.duration_since(self.window_started_at);
if elapsed < RATE_UPDATE_INTERVAL {
return;
}
let seconds = elapsed.as_secs_f32().max(0.001);
let rx_delta = total_rx_frames.saturating_sub(self.window_rx_frames);
self.sample_rate_hz = rx_delta as f32 / seconds;
self.render_rate_hz = self.window_render_frames as f32 / seconds;
self.window_started_at = now;
self.window_rx_frames = total_rx_frames;
self.window_render_frames = 0;
}
}
impl EskinDesktopApp {
pub fn new(cc: &eframe::CreationContext<'_>) -> Self {
egui_extras::install_image_loaders(&cc.egui_ctx);
@@ -110,11 +158,13 @@ impl EskinDesktopApp {
hand_signal_histories: std::array::from_fn(|_| Vec::with_capacity(128)),
force_estimator: ForceEstimatorState::new(),
latest_spatial_force: None,
spatial_force_panel_force: None,
latest_raw_matrix: Vec::new(),
latest_matrix_rows: MATRIX_ROWS,
latest_matrix_cols: MATRIX_COLS,
breakout_visible: false,
breakout_game: BreakoutGame::default(),
live_rates: LiveRateMeters::new(),
context_menu_open: false,
context_menu_pos: egui::pos2(24.0, 48.0),
active_mode: ActiveMode::Finger(FingerMode {
@@ -362,13 +412,22 @@ impl EskinDesktopApp {
}
}
fn draw_floating_panels(&mut self, ctx: &egui::Context) {
// draw_scene_panel(ctx, &mut self.scene_panel);
// if self.config_state.mode == SerialMode::Hand && self.stats_panel.visible {
// self.config_panel.visible = false;
// self.export_panel.visible = false;
// self.matrix_config_panel.visible = false;
// }
fn draw_floating_panels(
&mut self,
ctx: &egui::Context,
stats: crate::serial_core::serial::SerialIoStats,
) {
self.scene_panel.visible = false;
self.connect_panel.visible = false;
self.export_panel.visible = false;
self.matrix_config_panel.visible = false;
self.context_menu_open = false;
if self.breakout_visible {
self.config_panel.visible = false;
self.stats_panel.visible = false;
return;
}
if let Some(next_mode) = draw_config_panel(
ctx,
@@ -376,29 +435,43 @@ impl EskinDesktopApp {
&mut self.config_state,
&self.connection,
&self.recorder,
stats,
self.live_rates.sample_rate_hz,
self.live_rates.render_rate_hz,
) {
self.switch_mode(next_mode);
}
match self.config_state.mode {
SerialMode::Finger => {
draw_stats_panel(
ctx,
&mut self.stats_panel,
&self.signal_history,
self.latest_spatial_force,
);
}
SerialMode::Hand => {
draw_hand_force_panels(ctx, self.stats_panel.visible, &self.hand_signal_histories);
if next_mode == SerialMode::Hand {
self.config_state.mode = SerialMode::Finger;
self.connect_state.mode = SerialMode::Finger;
self.config_state.baud_rate = SerialMode::Finger.baud_rate();
self.active_mode = ActiveMode::Finger(FingerMode {
rows: self.matrix_config.rows,
cols: self.matrix_config.cols,
range: 0..7000,
dot: true,
});
self.breakout_visible = true;
} else {
self.switch_mode(next_mode);
}
}
draw_export_panel(
ctx,
&mut self.export_panel,
&self.recorder,
&mut self.export_path,
);
draw_matrix_config_panel(ctx, &mut self.matrix_config_panel, &mut self.matrix_config);
if self.config_state.mode == SerialMode::Finger {
self.stats_panel.visible = true;
draw_stats_panel(
ctx,
&mut self.stats_panel,
&self.signal_history,
self.latest_spatial_force,
);
} else {
self.stats_panel.visible = false;
}
if let Some(force) = self.latest_spatial_force {
self.spatial_force_panel_force = Some(force);
}
draw_spatial_force_panel(ctx, self.spatial_force_panel_force, &self.signal_history);
}
fn draw_panel_context_menu(&mut self, ctx: &egui::Context) {
@@ -499,6 +572,7 @@ impl EskinDesktopApp {
self.hand_pressure.clear();
self.force_estimator.reset();
self.latest_spatial_force = None;
self.spatial_force_panel_force = None;
self.signal_history.clear();
self.hand_signal_histories
.iter_mut()
@@ -663,12 +737,13 @@ fn normalize_pressure_value(value: u32) -> [f32; 2] {
impl eframe::App for EskinDesktopApp {
fn ui(&mut self, ui: &mut egui::Ui, frame: &mut eframe::Frame) {
let ctx = ui.ctx().clone();
let stats = self.connection.stats();
self.live_rates.tick_render(stats.rx_frames);
self.update_pressure_matrix();
self.draw_workspace(ui);
self.draw_title_bar(ui, frame);
self.draw_floating_panels(&ctx);
self.draw_panel_context_menu(&ctx);
self.draw_floating_panels(&ctx, stats);
// Keep repainting while the wgpu background is a realtime viewport.
ctx.request_repaint();