diff --git a/src/app.rs b/src/app.rs index ba0511e..c5abde0 100644 --- a/src/app.rs +++ b/src/app.rs @@ -3,7 +3,7 @@ use crate::connection::ConnectionManager; use crate::force::{ForceEstimatorState, HudSpatialForce}; use crate::recording::Recorder; use crate::render::{ActiveMode, FingerMode, HandGatewayMode}; -use crate::style::{ONE_DARK_PRO, apply_fonts, apply_theme, layout}; +use crate::style::{self, ONE_DARK_PRO, apply_fonts, apply_theme, dim_text, layout}; use crate::ui::SerialMode; use crate::{ matrix::{MATRIX_COLS, MATRIX_ROWS}, @@ -13,8 +13,8 @@ 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_export_panel, draw_hand_force_panels, draw_stats_panel, + panel_restore_item, }, }; use eframe::{egui, egui_wgpu}; @@ -159,15 +159,15 @@ impl EskinDesktopApp { let color = egui::Color32::from_rgb(255, 196, 54); let glow = egui::Color32::from_rgba_unmultiplied(255, 196, 54, 58); - painter.line_segment([center, end], egui::Stroke::new(9.0, glow)); - painter.line_segment([center, end], egui::Stroke::new(2.4, color)); + painter.line_segment([center, end], egui::Stroke::new(9.0_f32, glow)); + painter.line_segment([center, end], egui::Stroke::new(2.4_f32, color)); painter.line_segment( [end, end - direction * 14.0 + side * 7.0], - egui::Stroke::new(2.4, color), + egui::Stroke::new(2.4_f32, color), ); painter.line_segment( [end, end - direction * 14.0 - side * 7.0], - egui::Stroke::new(2.4, color), + egui::Stroke::new(2.4_f32, color), ); painter.circle_filled(center, 4.2, color); } @@ -265,7 +265,7 @@ impl EskinDesktopApp { ); ui.painter().line_segment( [title_bar_rect.left_bottom(), title_bar_rect.right_bottom()], - egui::Stroke::new(1.0, ONE_DARK_PRO.border), + egui::Stroke::new(1.0_f32, ONE_DARK_PRO.border), ); // Drag-to-move: double-click to maximize, drag to move @@ -306,14 +306,14 @@ impl EskinDesktopApp { 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)), + egui::Stroke::new(1.5_f32, 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)), + egui::Stroke::new(1.5_f32, egui::Color32::from_rgb(80, 0, 0)), ); } if close_resp.clicked() { @@ -332,7 +332,7 @@ impl EskinDesktopApp { 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)), + egui::Stroke::new(1.5_f32, egui::Color32::from_rgb(120, 80, 0)), ); } if min_resp.clicked() { @@ -351,7 +351,7 @@ impl EskinDesktopApp { 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::Stroke::new(1.5_f32, egui::Color32::from_rgb(0, 80, 10)), egui::StrokeKind::Outside, ); } @@ -362,7 +362,11 @@ impl EskinDesktopApp { } } - fn draw_floating_panels(&mut self, ctx: &egui::Context) { + fn draw_floating_panels( + &mut self, + ctx: &egui::Context, + stats: crate::serial_core::serial::SerialIoStats, + ) { // draw_scene_panel(ctx, &mut self.scene_panel); // if self.config_state.mode == SerialMode::Hand && self.stats_panel.visible { // self.config_panel.visible = false; @@ -376,6 +380,9 @@ impl EskinDesktopApp { &mut self.config_state, &self.connection, &self.recorder, + stats, + 100.0, + 100.0, ) { self.switch_mode(next_mode); } @@ -398,7 +405,7 @@ impl EskinDesktopApp { &self.recorder, &mut self.export_path, ); - draw_matrix_config_panel(ctx, &mut self.matrix_config_panel, &mut self.matrix_config); + // draw_matrix_config_panel(ctx, &mut self.matrix_config_panel, &mut self.matrix_config); } fn draw_panel_context_menu(&mut self, ctx: &egui::Context) { @@ -590,7 +597,7 @@ fn paint_split_viewport_shell( painter.rect_stroke( rect, egui::CornerRadius::same(8), - egui::Stroke::new(1.0, ONE_DARK_PRO.border_soft), + egui::Stroke::new(1.0_f32, ONE_DARK_PRO.border_soft), egui::StrokeKind::Outside, ); painter.line_segment( @@ -598,7 +605,7 @@ fn paint_split_viewport_shell( rect.left_top() + egui::vec2(12.0, 34.0), rect.right_top() + egui::vec2(-12.0, 34.0), ], - egui::Stroke::new(1.0, ONE_DARK_PRO.border_soft), + egui::Stroke::new(1.0_f32, ONE_DARK_PRO.border_soft), ); painter.text( rect.left_top() + egui::vec2(14.0, 16.0), @@ -660,14 +667,50 @@ fn normalize_pressure_value(value: u32) -> [f32; 2] { [mapped, display_value] } +fn draw_config_bar_mode(ui: &mut egui::Ui, config: &mut ConfigPanelState) -> Option { + let mut changed_to = None; + + ui.horizontal(|ui| { + ui.colored_label(dim_text(), "单面指尖"); + if mode_button(ui, &mut config.mode, SerialMode::Finger, "单面指尖") { + changed_to = Some(SerialMode::Finger); + } + // if mode_button(ui, &mut config.mode, SerialMode::Finger3D, "3D指尖") { + // changed_to = Some(SerialMode::Finger3D); + // } + if mode_button(ui, &mut config.mode, SerialMode::Hand, "展示手掌") { + changed_to = Some(SerialMode::Hand); + } + }); + + changed_to +} + +fn mode_button( + ui: &mut egui::Ui, + mode: &mut SerialMode, + value: SerialMode, + label: &'static str, +) -> bool { + let clicked = ui.add(style::mode_button(label, *mode == value)).clicked(); + + if clicked && *mode != value { + *mode = value; + true + } else { + false + } +} + 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.update_pressure_matrix(); self.draw_workspace(ui); self.draw_title_bar(ui, frame); - self.draw_floating_panels(&ctx); + self.draw_floating_panels(&ctx, stats); self.draw_panel_context_menu(&ctx); // Keep repainting while the wgpu background is a realtime viewport. diff --git a/src/breakout.rs b/src/breakout.rs index 882bda8..9f396db 100644 --- a/src/breakout.rs +++ b/src/breakout.rs @@ -249,7 +249,11 @@ impl BreakoutGame { } fn launch_ball(&mut self) { - let direction = if self.level % 2 == 0 { -0.24 } else { 0.24 }; + let direction = if self.level.is_multiple_of(2) { + -0.24 + } else { + 0.24 + }; self.ball_pos = egui::pos2(self.paddle_x, PADDLE_Y - PADDLE_H - BALL_RADIUS); self.ball_vel = egui::vec2(direction, -1.0).normalized() * BALL_SPEED; } @@ -372,7 +376,7 @@ impl BreakoutGame { painter.rect_stroke( rect, egui::CornerRadius::same(6), - egui::Stroke::new(1.0, color_alpha(ONE_DARK_PRO.accent, 110)), + egui::Stroke::new(1.0_f32, color_alpha(ONE_DARK_PRO.accent, 110)), egui::StrokeKind::Outside, ); @@ -469,7 +473,7 @@ fn circle_hits_rect(center: egui::Pos2, radius: f32, rect: egui::Rect) -> bool { fn status_chip(ui: &mut egui::Ui, label: &'static str, value: impl ToString, color: egui::Color32) { egui::Frame::new() .fill(color_alpha(ONE_DARK_PRO.panel_deep, 190)) - .stroke(egui::Stroke::new(1.0, color_alpha(color, 92))) + .stroke(egui::Stroke::new(1.0_f32, color_alpha(color, 92))) .corner_radius(egui::CornerRadius::same(4)) .inner_margin(egui::Margin::symmetric(8, 4)) .show(ui, |ui| { @@ -514,7 +518,7 @@ fn paint_arena_grid(painter: &egui::Painter, rect: egui::Rect) { painter.rect_stroke( rect, egui::CornerRadius::same(6), - egui::Stroke::new(1.0, color_alpha(ONE_DARK_PRO.accent, 70)), + egui::Stroke::new(1.0_f32, color_alpha(ONE_DARK_PRO.accent, 70)), egui::StrokeKind::Inside, ); } @@ -531,7 +535,7 @@ fn paint_brick(painter: &egui::Painter, arena: egui::Rect, brick: &Brick, index: painter.rect_stroke( rect.expand(4.0 * brick.flash), egui::CornerRadius::same(4), - egui::Stroke::new(1.4, color_alpha(ONE_DARK_PRO.accent_hot, alpha)), + egui::Stroke::new(1.4_f32, color_alpha(ONE_DARK_PRO.accent_hot, alpha)), egui::StrokeKind::Outside, ); } @@ -564,7 +568,7 @@ fn paint_control_meter(painter: &egui::Painter, arena: egui::Rect, control: Brea painter.rect_stroke( meter, egui::CornerRadius::same(4), - egui::Stroke::new(1.0, color_alpha(ONE_DARK_PRO.border, 120)), + egui::Stroke::new(1.0_f32, color_alpha(ONE_DARK_PRO.border, 120)), egui::StrokeKind::Outside, ); let center_x = meter.center().x; @@ -573,7 +577,7 @@ fn paint_control_meter(painter: &egui::Painter, arena: egui::Rect, control: Brea egui::pos2(center_x, meter.top()), egui::pos2(center_x, meter.bottom()), ], - egui::Stroke::new(1.0, ONE_DARK_PRO.border_soft), + egui::Stroke::new(1.0_f32, ONE_DARK_PRO.border_soft), ); let marker_x = center_x + control.axis.clamp(-1.0, 1.0) * meter.width() * 0.45; painter.circle_filled( diff --git a/src/connection.rs b/src/connection.rs index fc600e1..f659616 100644 --- a/src/connection.rs +++ b/src/connection.rs @@ -179,6 +179,7 @@ impl Default for ConnectionManager { } /// The blocking device loop that runs on a background thread. +#[allow(clippy::too_many_arguments)] fn run_device_loop( port_name: &str, rows: u32, diff --git a/src/recording.rs b/src/recording.rs index 2a5d9f5..78d8b84 100644 --- a/src/recording.rs +++ b/src/recording.rs @@ -145,10 +145,10 @@ impl Recorder { r.state == RecordingState::Recording || r.state == RecordingState::Paused, "nothing to stop" ); - if r.state == RecordingState::Paused { - if let Some(ps) = r.pause_start.take() { - r.paused_duration_ms += ps.elapsed().as_millis() as u64; - } + if r.state == RecordingState::Paused + && let Some(ps) = r.pause_start.take() + { + r.paused_duration_ms += ps.elapsed().as_millis() as u64; } r.state = RecordingState::Idle; Ok(()) @@ -317,15 +317,14 @@ impl Recorder { } // Set the start instant so duration_ms() reports the imported span - if !r.frames.is_empty() { - if let Some(last) = r.frames.last() { - // Pretend the recording happened `last.timestamp_ms` ago - // so that elapsed_ms() would return that value. - // We store a "fake" start by noting the offset. - r.start = - Some(Instant::now() - std::time::Duration::from_millis(last.timestamp_ms)); - r.paused_duration_ms = 0; - } + if !r.frames.is_empty() + && let Some(last) = r.frames.last() + { + // Pretend the recording happened `last.timestamp_ms` ago + // so that elapsed_ms() would return that value. + // We store a "fake" start by noting the offset. + r.start = Some(Instant::now() - std::time::Duration::from_millis(last.timestamp_ms)); + r.paused_duration_ms = 0; } Ok(()) diff --git a/src/resources.rs b/src/resources.rs index 8b16e0c..76a167a 100644 --- a/src/resources.rs +++ b/src/resources.rs @@ -457,6 +457,7 @@ fn create_color_material( )) } +#[allow(clippy::too_many_arguments)] fn create_material( device: &wgpu::Device, texture_bind_group_layout: &wgpu::BindGroupLayout, @@ -702,6 +703,7 @@ fn append_gltf_node_meshes( Ok(()) } +#[allow(clippy::too_many_arguments)] fn append_gltf_primitive_mesh( mesh_name: &str, primitive_index: usize, @@ -969,7 +971,7 @@ fn rgba_from_chunks( read_component: fn(&[u8]) -> u8, ) -> anyhow::Result> { let pixel_width = channels * component_width; - if pixel_width == 0 || pixels.len() % pixel_width != 0 { + if pixel_width == 0 || !pixels.len().is_multiple_of(pixel_width) { bail!("invalid glTF image byte length for {channels} channels"); } diff --git a/src/serial_core/codecs/hand_gateway.rs b/src/serial_core/codecs/hand_gateway.rs index 7edb93a..8dabe55 100644 --- a/src/serial_core/codecs/hand_gateway.rs +++ b/src/serial_core/codecs/hand_gateway.rs @@ -146,7 +146,7 @@ impl HandGatewayCodec { } pub fn parse_node_payload(data: &[u8]) -> Result, CodecError> { - if data.len() % 2 != 0 { + if !data.len().is_multiple_of(2) { return Err(CodecError::InvalidLength); } diff --git a/src/serial_core/codecs/tactile_a.rs b/src/serial_core/codecs/tactile_a.rs index 5f02a94..11ad808 100644 --- a/src/serial_core/codecs/tactile_a.rs +++ b/src/serial_core/codecs/tactile_a.rs @@ -31,7 +31,7 @@ impl TactileACodec { } pub fn parse_data_frame(data: &[u8]) -> Result, CodecError> { - if data.len() % 2 != 0 { + if !data.len().is_multiple_of(2) { return Err(CodecError::InvalidLength); } @@ -132,7 +132,7 @@ impl Codec for TactileACodec { let need_check_data = self.buffer[0..14 + except_data_len].to_vec(); let payload = self.buffer[14..14 + except_data_len].to_vec(); let crc8_itu_alg = crc::Crc::::new(&crc::CRC_8_I_432_1); - let checksum = crc8_itu_alg.checksum(&need_check_data.as_slice()); + let checksum = crc8_itu_alg.checksum(need_check_data.as_slice()); if self.buffer[frame_length - 1] != checksum { log::debug!( "checksum mismatch: expected {:02X}, got {:02X}, frame_len={}", diff --git a/src/serial_core/multi_dim_force.rs b/src/serial_core/multi_dim_force.rs index e0530e6..463dcb2 100644 --- a/src/serial_core/multi_dim_force.rs +++ b/src/serial_core/multi_dim_force.rs @@ -87,7 +87,7 @@ impl PztProcessor { return 0.0; } - if n % 2 == 0 { + if n.is_multiple_of(2) { (sorted[n / 2 - 1] + sorted[n / 2]) / 2.0 } else { sorted[n / 2] diff --git a/src/serial_core/serial.rs b/src/serial_core/serial.rs index aa837e4..bc55360 100644 --- a/src/serial_core/serial.rs +++ b/src/serial_core/serial.rs @@ -24,6 +24,7 @@ pub enum SerialProtocol { /// Runs the serial polling loop on the calling (background) thread. /// Sends decoded pressure matrix data (Vec) to the output channel. +#[allow(clippy::too_many_arguments)] pub fn run_serial_loop( port: &mut dyn ReadWrite, rows: usize, @@ -73,11 +74,11 @@ fn run_tactile_a_loop( } // Send poll request - if let Ok(req_bytes) = codec.encode(&req_frame) { - if port.write_all(&req_bytes).is_ok() { - io_stats.tx_bytes += req_bytes.len() as u64; - publish_stats(stats_tx, io_stats); - } + if let Ok(req_bytes) = codec.encode(&req_frame) + && port.write_all(&req_bytes).is_ok() + { + io_stats.tx_bytes += req_bytes.len() as u64; + publish_stats(stats_tx, io_stats); } // Read response with poll interval @@ -93,15 +94,15 @@ fn run_tactile_a_loop( publish_stats(stats_tx, io_stats); if let Ok(frames) = codec.decode(&buffer[..n], session_started_at) { for frame in frames { - if let TactileAFrame::Rep(rep) = frame { - if let Ok(vals) = TactileACodec::parse_data_frame(&rep.payload) { - if let Some(recorder) = recorder { - let pressures: Vec = - vals.iter().map(|v| (*v).max(0) as u32).collect(); - recorder.add_frame(&pressures); - } - let _ = sample_tx.try_send(vals); + if let TactileAFrame::Rep(rep) = frame + && let Ok(vals) = TactileACodec::parse_data_frame(&rep.payload) + { + if let Some(recorder) = recorder { + let pressures: Vec = + vals.iter().map(|v| (*v).max(0) as u32).collect(); + recorder.add_frame(&pressures); } + let _ = sample_tx.try_send(vals); } } } diff --git a/src/style.rs b/src/style.rs index 16bd2a2..a28560c 100644 --- a/src/style.rs +++ b/src/style.rs @@ -61,6 +61,7 @@ pub const METRICS: DesignMetrics = DesignMetrics { pub mod layout { pub const TITLE_BAR_HEIGHT: f32 = 36.0; + pub const CONFIG_BAR_HEIGHT: f32 = 48.0; pub const CENTER_PANEL_TOP: f32 = 48.0; pub const LEFT_X: f32 = 24.0; pub const RIGHT_X: f32 = 1328.0; @@ -76,7 +77,7 @@ pub fn apply_theme(ctx: &egui::Context, theme: &AppTheme) { visuals.override_text_color = Some(theme.text); visuals.panel_fill = theme.bg; visuals.window_fill = theme.panel; - visuals.window_stroke = egui::Stroke::new(1.0, theme.border); + visuals.window_stroke = egui::Stroke::new(1.0_f32, theme.border); visuals.extreme_bg_color = theme.panel_deep; visuals.faint_bg_color = theme.panel_strong; visuals.code_bg_color = theme.panel_deep; @@ -84,23 +85,23 @@ pub fn apply_theme(ctx: &egui::Context, theme: &AppTheme) { visuals.error_fg_color = ACCENT_RED; visuals.widgets.noninteractive.bg_fill = theme.panel_strong; - visuals.widgets.noninteractive.bg_stroke = egui::Stroke::new(1.0, theme.border_soft); - visuals.widgets.noninteractive.fg_stroke = egui::Stroke::new(1.0, theme.text); + visuals.widgets.noninteractive.bg_stroke = egui::Stroke::new(1.0_f32, theme.border_soft); + visuals.widgets.noninteractive.fg_stroke = egui::Stroke::new(1.0_f32, theme.text); visuals.widgets.inactive.bg_fill = theme.panel_strong; - visuals.widgets.inactive.bg_stroke = egui::Stroke::new(1.0, theme.border_soft); - visuals.widgets.inactive.fg_stroke = egui::Stroke::new(1.0, theme.text); + visuals.widgets.inactive.bg_stroke = egui::Stroke::new(1.0_f32, theme.border_soft); + visuals.widgets.inactive.fg_stroke = egui::Stroke::new(1.0_f32, theme.text); visuals.widgets.hovered.bg_fill = egui::Color32::from_rgb(44, 57, 70); - visuals.widgets.hovered.bg_stroke = egui::Stroke::new(1.0, theme.accent); - visuals.widgets.hovered.fg_stroke = egui::Stroke::new(1.0, egui::Color32::WHITE); + visuals.widgets.hovered.bg_stroke = egui::Stroke::new(1.0_f32, theme.accent); + visuals.widgets.hovered.fg_stroke = egui::Stroke::new(1.0_f32, egui::Color32::WHITE); visuals.widgets.active.bg_fill = theme.accent; - visuals.widgets.active.bg_stroke = egui::Stroke::new(1.0, theme.accent_hot); - visuals.widgets.active.fg_stroke = egui::Stroke::new(1.0, egui::Color32::WHITE); + visuals.widgets.active.bg_stroke = egui::Stroke::new(1.0_f32, theme.accent_hot); + visuals.widgets.active.fg_stroke = egui::Stroke::new(1.0_f32, egui::Color32::WHITE); visuals.widgets.open.bg_fill = egui::Color32::from_rgb(39, 50, 62); - visuals.widgets.open.bg_stroke = egui::Stroke::new(1.0, theme.accent); - visuals.widgets.open.fg_stroke = egui::Stroke::new(1.0, theme.text); + visuals.widgets.open.bg_stroke = egui::Stroke::new(1.0_f32, theme.accent); + visuals.widgets.open.fg_stroke = egui::Stroke::new(1.0_f32, theme.text); visuals.selection.bg_fill = egui::Color32::from_rgb(35, 123, 140); - visuals.selection.stroke = egui::Stroke::new(1.0, egui::Color32::WHITE); + visuals.selection.stroke = egui::Stroke::new(1.0_f32, egui::Color32::WHITE); visuals.hyperlink_color = theme.accent_hot; ctx.set_visuals(visuals); @@ -126,8 +127,8 @@ pub fn apply_fonts(ctx: &egui::Context) { let mut fonts = egui::FontDefinitions::default(); fonts.font_data.insert( - "Hack-Bold".to_owned(), - egui::FontData::from_static(include_bytes!("../static/Hack-Bold.ttf")).into(), + "MapleMono-NF-CN-Bold".to_owned(), + egui::FontData::from_static(include_bytes!("../static/MapleMono-NF-CN-Bold.ttf")).into(), ); let has_yahei = std::fs::read(r"C:\Windows\Fonts\msyh.ttc") @@ -144,7 +145,7 @@ pub fn apply_fonts(ctx: &egui::Context) { .families .entry(egui::FontFamily::Proportional) .or_default() - .insert(0, "Hack-Bold".to_owned()); + .insert(0, "MapleMono-NF-CN-Bold".to_owned()); if has_yahei { fonts .families @@ -157,7 +158,7 @@ pub fn apply_fonts(ctx: &egui::Context) { .families .entry(egui::FontFamily::Monospace) .or_default() - .insert(0, "Hack-Bold".to_owned()); + .insert(0, "MapleMono-NF-CN-Bold".to_owned()); if has_yahei { fonts .families @@ -173,7 +174,7 @@ pub fn panel_frame(ctx: &egui::Context) -> egui::Frame { let style = ctx.global_style(); egui::Frame::window(&style) .fill(ONE_DARK_PRO.panel) - .stroke(egui::Stroke::new(1.0, ONE_DARK_PRO.border)) + .stroke(egui::Stroke::new(1.0_f32, ONE_DARK_PRO.border)) .corner_radius(egui::CornerRadius::same(ONE_DARK_PRO.radius)) .inner_margin(egui::Margin::same(METRICS.panel_padding)) .shadow(egui::epaint::Shadow { @@ -197,7 +198,7 @@ pub fn center_panel_frame() -> egui::Frame { pub fn group_frame() -> egui::Frame { egui::Frame::new() .fill(ONE_DARK_PRO.panel_deep) - .stroke(egui::Stroke::new(1.0, ONE_DARK_PRO.border_soft)) + .stroke(egui::Stroke::new(1.0_f32, ONE_DARK_PRO.border_soft)) .corner_radius(egui::CornerRadius::same(4)) .inner_margin(egui::Margin::symmetric( METRICS.group_padding_x, @@ -208,7 +209,7 @@ pub fn group_frame() -> egui::Frame { pub fn tag_button(label: impl Into) -> egui::Button<'static> { egui::Button::new(label) .fill(ONE_DARK_PRO.panel_strong) - .stroke(egui::Stroke::new(1.0, ONE_DARK_PRO.border)) + .stroke(egui::Stroke::new(1.0_f32, ONE_DARK_PRO.border)) .corner_radius(egui::CornerRadius::same(4)) .min_size(egui::vec2(0.0, METRICS.button_height)) } @@ -221,7 +222,7 @@ pub fn rich_tag_button( egui::Button::new(text) .fill(ONE_DARK_PRO.panel_strong) - .stroke(egui::Stroke::new(1.0, ONE_DARK_PRO.border)) + .stroke(egui::Stroke::new(1.0_f32, ONE_DARK_PRO.border)) .corner_radius(egui::CornerRadius::same(4)) .min_size(egui::vec2(0.0, METRICS.button_height)) } @@ -229,7 +230,7 @@ pub fn rich_tag_button( pub fn primary_button(label: impl Into) -> egui::Button<'static> { egui::Button::new(label) .fill(ONE_DARK_PRO.accent) - .stroke(egui::Stroke::new(1.0, ONE_DARK_PRO.accent_hot)) + .stroke(egui::Stroke::new(1.0_f32, ONE_DARK_PRO.accent_hot)) .corner_radius(egui::CornerRadius::same(4)) .min_size(egui::vec2(112.0, METRICS.button_height)) } @@ -238,7 +239,7 @@ pub fn danger_button(label: impl Into) -> egui::Button<'static egui::Button::new(label) .fill(ACCENT_RED) .stroke(egui::Stroke::new( - 1.0, + 1.0_f32, egui::Color32::from_rgb(255, 138, 126), )) .corner_radius(egui::CornerRadius::same(4)) @@ -251,7 +252,7 @@ pub fn accent_button( ) -> egui::Button<'static> { egui::Button::new(label) .fill(fill) - .stroke(egui::Stroke::new(1.0, ONE_DARK_PRO.border)) + .stroke(egui::Stroke::new(1.0_f32, ONE_DARK_PRO.border)) .corner_radius(egui::CornerRadius::same(4)) .min_size(egui::vec2(0.0, METRICS.button_height)) } @@ -270,7 +271,7 @@ pub fn mode_button(label: &'static str, selected: bool) -> egui::Button<'static> egui::Button::new(egui::RichText::new(label).color(egui::Color32::WHITE)) .fill(fill) - .stroke(egui::Stroke::new(1.0, stroke)) + .stroke(egui::Stroke::new(1.0_f32, stroke)) .corner_radius(egui::CornerRadius::same(4)) .min_size(egui::vec2(96.0, METRICS.button_height)) } @@ -278,7 +279,7 @@ pub fn mode_button(label: &'static str, selected: bool) -> egui::Button<'static> pub fn icon_button<'a>(icon: impl Into, size: egui::Vec2) -> egui::Button<'a> { egui::Button::new(icon) .fill(ONE_DARK_PRO.panel_strong) - .stroke(egui::Stroke::new(1.0, ONE_DARK_PRO.border)) + .stroke(egui::Stroke::new(1.0_f32, ONE_DARK_PRO.border)) .corner_radius(egui::CornerRadius::same(4)) .min_size(size) } diff --git a/src/texture.rs b/src/texture.rs index b4abd72..936a45e 100644 --- a/src/texture.rs +++ b/src/texture.rs @@ -187,6 +187,7 @@ impl Texture { ) } + #[allow(clippy::too_many_arguments)] pub fn from_rgba8_with_sampler( device: &wgpu::Device, queue: &wgpu::Queue, diff --git a/src/theme.rs b/src/theme.rs index dc0be4b..6349ac4 100644 --- a/src/theme.rs +++ b/src/theme.rs @@ -83,53 +83,6 @@ pub fn apply_theme(ctx: &egui::Context, theme: &AppTheme) { ctx.set_global_style(style); } -pub fn apply_fonts(ctx: &egui::Context) { - let mut fonts = egui::FontDefinitions::default(); - - fonts.font_data.insert( - "Hack-Bold".to_owned(), - egui::FontData::from_static(include_bytes!("../static/Hack-Bold.ttf")).into(), - ); - - let has_yahei = std::fs::read(r"C:\Windows\Fonts\msyh.ttc") - .or_else(|_| std::fs::read(r"C:\Windows\Fonts\msyhbd.ttc")) - .map(|font_data| { - fonts.font_data.insert( - "Microsoft-YaHei".to_owned(), - egui::FontData::from_owned(font_data).into(), - ); - }) - .is_ok(); - - fonts - .families - .entry(egui::FontFamily::Proportional) - .or_default() - .insert(0, "Hack-Bold".to_owned()); - if has_yahei { - fonts - .families - .entry(egui::FontFamily::Proportional) - .or_default() - .push("Microsoft-YaHei".to_owned()); - } - - fonts - .families - .entry(egui::FontFamily::Monospace) - .or_default() - .insert(0, "Hack-Bold".to_owned()); - if has_yahei { - fonts - .families - .entry(egui::FontFamily::Monospace) - .or_default() - .push("Microsoft-YaHei".to_owned()); - } - - ctx.set_fonts(fonts); -} - pub fn panel_frame(ctx: &egui::Context) -> egui::Frame { let style = ctx.global_style(); egui::Frame::window(&style) diff --git a/src/ui.rs b/src/ui.rs index eb38e24..432e334 100644 --- a/src/ui.rs +++ b/src/ui.rs @@ -7,7 +7,9 @@ use crate::{ serial_core::serial::{SerialIoStats, SerialProtocol}, style::{ self, ACCENT_BLUE, ACCENT_GREEN, ACCENT_ORANGE, ACCENT_RED, METRICS, ONE_DARK_PRO, - dim_text, group_frame, layout, panel_frame, rich_tag_button, tag_button, + dim_text, group_frame, + layout::{self}, + panel_frame, rich_tag_button, tag_button, }, utils::serial_enum, }; @@ -51,6 +53,7 @@ pub struct ConnectPanelState { #[derive(Clone, Copy, PartialEq, Eq)] pub enum SerialMode { Finger, + // Finger3D, Hand, } @@ -101,7 +104,7 @@ impl Default for ConfigPanelState { let port = available_ports .first() .cloned() - .unwrap_or_else(|| "COM3".to_owned()); + .unwrap_or_else(|| "无可用串口".to_owned()); Self { mode: SerialMode::Finger, @@ -241,8 +244,8 @@ pub fn draw_connect_panel( ui.vertical(|ui| { draw_connect_port_row(ui, config); - ui.add_space(6.0); - draw_connect_matrix_row(ui, config); + // ui.add_space(8.0); + // draw_connect_matrix_row(ui, config); }); }); }); @@ -297,13 +300,12 @@ fn draw_connect_port_row(ui: &mut egui::Ui, config: &mut ConnectPanelState) { )) .on_hover_text("刷新串口") .clicked() + && let Ok(ports) = serial_enum() { - if let Ok(ports) = serial_enum() { - if !ports.contains(&config.selected_port) { - config.selected_port = ports.first().cloned().unwrap_or_default(); - } - config.port = ports; + if !ports.contains(&config.selected_port) { + config.selected_port = ports.first().cloned().unwrap_or_default(); } + config.port = ports; } ui.add_space(METRICS.item_gap); @@ -385,43 +387,94 @@ fn draw_connect_action_row( }); } +#[allow(clippy::too_many_arguments)] pub fn draw_config_panel( ctx: &egui::Context, panel: &mut FloatingPanelState, config: &mut ConfigPanelState, connection: &ConnectionManager, recorder: &Recorder, + _state: SerialIoStats, + _sample_rate_hz: f32, + _render_rate_hz: f32, ) -> Option { let mut changed_mode = None; let conn_state = connection.state(); - let stats = connection.stats(); - let panel_width = responsive_panel_width(ctx, 560.0, 340.0); + let screen = ctx.content_rect(); + let bar_rect = egui::Rect::from_min_size( + screen.left_top() + egui::vec2(0.0, layout::TITLE_BAR_HEIGHT), + egui::vec2(screen.width(), layout::CONFIG_BAR_HEIGHT), + ); config.connected = matches!( conn_state, ConnectionState::Connected | ConnectionState::Streaming ); + panel.visible = true; - draw_floating_panel(ctx, panel, "配置", "config_panel", 560.0, 340.0, |ui| { - ui.set_min_width(panel_width); - ui.set_max_width(panel_width); + egui::Area::new(egui::Id::new("config_panel_bar")) + .fixed_pos(bar_rect.min) + .order(egui::Order::Foreground) + .show(ctx, |ui| { + ui.set_min_size(bar_rect.size()); + ui.set_max_size(bar_rect.size()); - if let Some(mode) = draw_mode_row(ui, config) { - changed_mode = Some(mode); - } + config_bar_frame().show(ui, |ui| { + ui.set_min_width(bar_rect.width()); + ui.set_max_width(bar_rect.width()); + ui.spacing_mut().item_spacing = egui::vec2(METRICS.item_gap, 6.0); - // draw_mode_row(ui, config); - ui.separator(); - draw_connection_row(ui, config, connection, recorder, conn_state); - ui.add_space(8.0); - // Legacy serial parameter grid is intentionally kept commented for future hardware: - // draw_serial_grid(ui, config); - // ui.add_space(8.0); - draw_mode_body(ui, config, conn_state, stats); - }); + ui.horizontal(|ui| { + ui.label(style::panel_title("配置面板")); + ui.add_space(12.0); + + if let Some(mode) = draw_config_bar_mode(ui, config) { + changed_mode = Some(mode); + } + + ui.add_space(12.0); + draw_config_bar_connection(ui, config, connection, recorder, conn_state); + + // ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| { + // draw_conf + // }) + }) + }) + }); + + // let stats = connection.stats(); + // let panel_width = responsive_panel_width(ctx, 560.0, 340.0); + // config.connected = matches!( + // conn_state, + // ConnectionState::Connected | ConnectionState::Streaming + // ); + + // draw_floating_panel(ctx, panel, "配置", "config_panel", 560.0, 340.0, |ui| { + // ui.set_min_width(panel_width); + // ui.set_max_width(panel_width); + + // if let Some(mode) = draw_mode_row(ui, config) { + // changed_mode = Some(mode); + // } + + // // draw_mode_row(ui, config); + // ui.separator(); + // draw_connection_row(ui, config, connection, recorder, conn_state); + // ui.add_space(8.0); + // // Legacy serial parameter grid is intentionally kept commented for future hardware: + // // draw_serial_grid(ui, config); + // // ui.add_space(8.0); + // draw_mode_body(ui, config, conn_state, stats); + // }); changed_mode } -fn draw_mode_row(ui: &mut egui::Ui, config: &mut ConfigPanelState) -> Option { +fn config_bar_frame() -> egui::Frame { + egui::Frame::new() + .fill(ONE_DARK_PRO.panel_deep) + .inner_margin(egui::Margin::symmetric(8, 8)) +} + +fn draw_config_bar_mode(ui: &mut egui::Ui, config: &mut ConfigPanelState) -> Option { let mut changed_to = None; ui.horizontal_wrapped(|ui| { ui.colored_label(dim_text(), "模式"); @@ -445,6 +498,86 @@ fn draw_mode_row(ui: &mut egui::Ui, config: &mut ConfigPanelState) -> Option( ), } .fill(ONE_DARK_PRO.panel_strong) - .stroke(egui::Stroke::new(1.0, ONE_DARK_PRO.border)) + .stroke(egui::Stroke::new(1.0_f32, ONE_DARK_PRO.border)) .corner_radius(egui::CornerRadius::same(ONE_DARK_PRO.radius)) .min_size(size); @@ -885,9 +1018,8 @@ pub fn draw_hand_force_panels(ctx: &egui::Context, visible: bool, histories: &[V let left_top = (screen.center().y - left_height * 0.5).max(min_top); let right_top = (screen.center().y - right_height * 0.5).max(min_top); - for index in 0..HAND_FORCE_PANEL_TITLES.len() { + for (index, &(code, title)) in HAND_FORCE_PANEL_TITLES.iter().enumerate() { let history = histories.get(index).map(Vec::as_slice).unwrap_or(&[]); - let (code, title) = HAND_FORCE_PANEL_TITLES[index]; let (target_x, target_y) = if index < left_count { (left_x, left_top + index as f32 * (panel_height + gap)) } else { @@ -911,6 +1043,7 @@ pub fn draw_hand_force_panels(ctx: &egui::Context, visible: bool, histories: &[V } } +#[allow(clippy::too_many_arguments)] fn draw_force_chart_area( ctx: &egui::Context, id: egui::Id, @@ -987,7 +1120,7 @@ fn paint_resultant_force_chart(ui: &mut egui::Ui, values: &[f32], chart_height: painter.rect_stroke( rect, radius, - egui::Stroke::new(1.0, color_alpha(ONE_DARK_PRO.accent, 95)), + egui::Stroke::new(1.0_f32, color_alpha(ONE_DARK_PRO.accent, 95)), egui::StrokeKind::Outside, ); @@ -1036,11 +1169,11 @@ fn paint_resultant_force_chart(ui: &mut egui::Ui, values: &[f32], chart_height: if points.len() >= 2 { plot_painter.add(egui::Shape::line( points.clone(), - egui::Stroke::new(4.0, color_alpha(ONE_DARK_PRO.accent, 45)), + egui::Stroke::new(4.0_f32, color_alpha(ONE_DARK_PRO.accent, 45)), )); plot_painter.add(egui::Shape::line( points.clone(), - egui::Stroke::new(1.7, ONE_DARK_PRO.accent), + egui::Stroke::new(1.7_f32, ONE_DARK_PRO.accent), )); } @@ -1050,7 +1183,7 @@ fn paint_resultant_force_chart(ui: &mut egui::Ui, values: &[f32], chart_height: last, 5.0 + pulse * 12.0, egui::Stroke::new( - 1.0, + 1.0_f32, color_alpha(ACCENT_GREEN, ((1.0 - pulse) * 120.0) as u8), ), ); @@ -1074,7 +1207,7 @@ fn paint_force_grid( egui::pos2(chart_rect.left(), y), egui::pos2(chart_rect.right(), y), ], - egui::Stroke::new(1.0, color_alpha(ONE_DARK_PRO.border, 78)), + egui::Stroke::new(1.0_f32, color_alpha(ONE_DARK_PRO.border, 78)), ); painter.text( egui::pos2(rect.left() + 8.0, y), @@ -1092,7 +1225,7 @@ fn paint_force_grid( egui::pos2(x, chart_rect.top()), egui::pos2(x, chart_rect.bottom()), ], - egui::Stroke::new(1.0, color_alpha(ONE_DARK_PRO.border_soft, 42)), + egui::Stroke::new(1.0_f32, color_alpha(ONE_DARK_PRO.border_soft, 42)), ); } @@ -1201,24 +1334,22 @@ fn draw_floating_panel( window_rect = Some(response.response.rect); } - if hide_requested { - if let Some(rect) = window_rect { - let screen = ctx.content_rect(); - let tag_size = egui::vec2(86.0, 22.0); + if hide_requested && let Some(rect) = window_rect { + let screen = ctx.content_rect(); + let tag_size = egui::vec2(86.0, 22.0); - let distance_to_left = rect.left(); - let distance_to_right = screen.right() - rect.right(); + let distance_to_left = rect.left(); + let distance_to_right = screen.right() - rect.right(); - let x = if distance_to_left <= distance_to_right { - screen.left() - } else { - screen.right() - tag_size.x - }; + let x = if distance_to_left <= distance_to_right { + screen.left() + } else { + screen.right() - tag_size.x + }; - let y = rect.top().clamp(screen.top(), screen.bottom() - tag_size.y); + let y = rect.top().clamp(screen.top(), screen.bottom() - tag_size.y); - panel.tag_pos = egui::pos2(x, y); - } + panel.tag_pos = egui::pos2(x, y); } panel.visible = open && !hide_requested; } @@ -1401,11 +1532,11 @@ fn paint_integrated_handle( ui.painter().add(egui::Shape::convex_polygon( points.clone(), fill, - egui::Stroke::new(1.0, ONE_DARK_PRO.border), + egui::Stroke::new(1.0_f32, ONE_DARK_PRO.border), )); ui.painter().line_segment( [points[2], points[3]], - egui::Stroke::new(1.0, ONE_DARK_PRO.border_soft), + egui::Stroke::new(1.0_f32, ONE_DARK_PRO.border_soft), ); } @@ -1426,14 +1557,14 @@ fn paint_integrated_handle( arrow_center + egui::vec2(-6.0, -2.0 * arrow), arrow_center + egui::vec2(0.0, 4.0 * arrow), ], - egui::Stroke::new(1.7, arrow_color), + egui::Stroke::new(1.7_f32, arrow_color), ); ui.painter().line_segment( [ arrow_center + egui::vec2(6.0, -2.0 * arrow), arrow_center + egui::vec2(0.0, 4.0 * arrow), ], - egui::Stroke::new(1.7, arrow_color), + egui::Stroke::new(1.7_f32, arrow_color), ); if !label.is_empty() { @@ -1459,8 +1590,8 @@ fn paint_integrated_center_panel( handle_on_bottom: bool, ) { let painter = ui.painter(); - let stroke = egui::Stroke::new(1.0, ONE_DARK_PRO.border); - let accent_stroke = egui::Stroke::new(1.2, ONE_DARK_PRO.border_soft); + let stroke = egui::Stroke::new(1.0_f32, ONE_DARK_PRO.border); + let accent_stroke = egui::Stroke::new(1.2_f32, ONE_DARK_PRO.border_soft); let top = rect.top(); let left = rect.left(); let right = rect.right(); @@ -1605,7 +1736,7 @@ pub fn draw_signal_chart(ui: &mut egui::Ui, values: &[f32], label: &str, color: }) .collect(); - painter.add(egui::Shape::line(points, egui::Stroke::new(1.5, color))); + painter.add(egui::Shape::line(points, egui::Stroke::new(1.5_f32, color))); }); } @@ -1666,10 +1797,8 @@ pub fn draw_recording_toolbar(ui: &mut egui::Ui, recorder: &Recorder, export_pat ui.add_space(4.0); // Pause/Resume - if is_recording { - if ui.add(tag_button("⏸ 暂停")).clicked() { - let _ = recorder.pause_recording(); - } + if is_recording && ui.add(tag_button("⏸ 暂停")).clicked() { + let _ = recorder.pause_recording(); } ui.add_space(8.0); diff --git a/src/utils.rs b/src/utils.rs index 2069439..6949f97 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -1,4 +1,3 @@ -use anyhow; use serialport::available_ports; pub fn serial_enum() -> anyhow::Result> { diff --git a/static/MapleMono-NF-CN-Bold.ttf b/static/MapleMono-NF-CN-Bold.ttf new file mode 100644 index 0000000..cf9e08b Binary files /dev/null and b/static/MapleMono-NF-CN-Bold.ttf differ diff --git a/static/MapleMono-NF-CN-Regular.ttf b/static/MapleMono-NF-CN-Regular.ttf new file mode 100644 index 0000000..c112ba8 Binary files /dev/null and b/static/MapleMono-NF-CN-Regular.ttf differ