Refine finger mode UI: app wiring, style helpers, and panel layout

This commit is contained in:
lenn
2026-07-06 10:32:46 +08:00
parent 71d314ac44
commit 9efe360a1d
3 changed files with 169 additions and 41 deletions

View File

@@ -223,19 +223,23 @@ impl EskinDesktopApp {
} }
fn draw_workspace(&mut self, ui: &mut egui::Ui) { fn draw_workspace(&mut self, ui: &mut egui::Ui) {
let screen = ui.max_rect();
let workspace = egui::Rect::from_min_max(
screen.left_top() + egui::vec2(0.0, layout::WORKSPACE_TOP),
screen.right_bottom(),
);
if self.breakout_visible { if self.breakout_visible {
self.draw_split_workspace(ui); self.draw_split_workspace(ui, workspace);
} else { } else {
self.draw_wgpu_background_rect(ui, ui.max_rect()); ui.painter()
.rect_filled(screen, egui::CornerRadius::ZERO, ONE_DARK_PRO.bg);
self.draw_wgpu_background_rect(ui, workspace);
} }
} }
fn draw_split_workspace(&mut self, ui: &mut egui::Ui) { fn draw_split_workspace(&mut self, ui: &mut egui::Ui, content: egui::Rect) {
let screen = ui.max_rect(); let screen = ui.max_rect();
let content = egui::Rect::from_min_max(
screen.left_top() + egui::vec2(0.0, layout::TITLE_BAR_HEIGHT),
screen.right_bottom(),
);
let gutter = 10.0; let gutter = 10.0;
let half_width = (content.width() - gutter).max(1.0) * 0.5; let half_width = (content.width() - gutter).max(1.0) * 0.5;
let left = egui::Rect::from_min_size(content.min, egui::vec2(half_width, content.height())); let left = egui::Rect::from_min_size(content.min, egui::vec2(half_width, content.height()));
@@ -423,12 +427,6 @@ impl EskinDesktopApp {
self.matrix_config_panel.visible = false; self.matrix_config_panel.visible = false;
self.context_menu_open = 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( if let Some(next_mode) = draw_config_panel(
ctx, ctx,
&mut self.config_panel, &mut self.config_panel,
@@ -455,6 +453,11 @@ impl EskinDesktopApp {
} }
} }
if self.breakout_visible {
self.stats_panel.visible = false;
return;
}
if self.config_state.mode == SerialMode::Finger { if self.config_state.mode == SerialMode::Finger {
self.stats_panel.visible = true; self.stats_panel.visible = true;
draw_stats_panel( draw_stats_panel(

View File

@@ -61,6 +61,8 @@ pub const METRICS: DesignMetrics = DesignMetrics {
pub mod layout { pub mod layout {
pub const TITLE_BAR_HEIGHT: f32 = 36.0; pub const TITLE_BAR_HEIGHT: f32 = 36.0;
pub const CONFIG_BAR_HEIGHT: f32 = 48.0;
pub const WORKSPACE_TOP: f32 = TITLE_BAR_HEIGHT + CONFIG_BAR_HEIGHT;
pub const CENTER_PANEL_TOP: f32 = 48.0; pub const CENTER_PANEL_TOP: f32 = 48.0;
pub const LEFT_X: f32 = 24.0; pub const LEFT_X: f32 = 24.0;
pub const RIGHT_X: f32 = 1328.0; pub const RIGHT_X: f32 = 1328.0;

173
src/ui.rs
View File

@@ -157,7 +157,7 @@ fn responsive_panel_width(ctx: &egui::Context, preferred: f32, min_width: f32) -
fn responsive_panel_height(ctx: &egui::Context, preferred: f32, min_height: f32) -> f32 { fn responsive_panel_height(ctx: &egui::Context, preferred: f32, min_height: f32) -> f32 {
let screen = ctx.content_rect(); let screen = ctx.content_rect();
let available = (screen.height() - layout::TITLE_BAR_HEIGHT - PANEL_VIEWPORT_MARGIN * 2.0) let available = (screen.height() - layout::WORKSPACE_TOP - PANEL_VIEWPORT_MARGIN * 2.0)
.max(min_height.min(screen.height())); .max(min_height.min(screen.height()));
let height_cap = (screen.height() * 0.72).max(min_height.min(available)); let height_cap = (screen.height() * 0.72).max(min_height.min(available));
@@ -173,7 +173,7 @@ fn responsive_panel_pos(
panel_width: f32, panel_width: f32,
) -> egui::Pos2 { ) -> egui::Pos2 {
let rect = viewport_panel_rect(ctx); let rect = viewport_panel_rect(ctx);
let min_y = rect.top() + layout::TITLE_BAR_HEIGHT + PANEL_VIEWPORT_MARGIN; let min_y = rect.top() + layout::WORKSPACE_TOP + PANEL_VIEWPORT_MARGIN;
let max_x = (rect.right() - panel_width).max(rect.left()); let max_x = (rect.right() - panel_width).max(rect.left());
let preferred_right_side = preferred.x > rect.center().x; let preferred_right_side = preferred.x > rect.center().x;
let x = if preferred_right_side { let x = if preferred_right_side {
@@ -397,40 +397,163 @@ pub fn draw_config_panel(
) -> Option<SerialMode> { ) -> Option<SerialMode> {
let mut changed_mode = None; let mut changed_mode = None;
let conn_state = connection.state(); let conn_state = connection.state();
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!( config.connected = matches!(
conn_state, conn_state,
ConnectionState::Connected | ConnectionState::Streaming ConnectionState::Connected | ConnectionState::Streaming
); );
panel.visible = true; panel.visible = true;
draw_pinned_panel(ctx, panel, "配置", "config_panel", 560.0, 340.0, |ui| { egui::Area::new(egui::Id::new("config_panel_bar"))
ui.set_min_width(panel_width); .fixed_pos(bar_rect.min)
ui.set_max_width(panel_width); .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) { 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);
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); changed_mode = Some(mode);
} }
// draw_mode_row(ui, config); ui.add_space(12.0);
ui.separator(); draw_config_bar_connection(ui, config, connection, recorder, conn_state);
draw_connection_row(ui, config, connection, recorder, conn_state);
ui.add_space(8.0); ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| {
// Legacy serial parameter grid is intentionally kept commented for future hardware: draw_config_bar_rates(ui, stats, sample_rate_hz, render_rate_hz);
// draw_serial_grid(ui, config);
// ui.add_space(8.0);
draw_mode_body(
ui,
config,
conn_state,
stats,
sample_rate_hz,
render_rate_hz,
);
}); });
});
});
});
changed_mode changed_mode
} }
fn config_bar_frame() -> egui::Frame {
egui::Frame::new()
.fill(ONE_DARK_PRO.panel_deep)
.inner_margin(egui::Margin::symmetric(8, 6))
}
fn draw_config_bar_mode(ui: &mut egui::Ui, config: &mut ConfigPanelState) -> Option<SerialMode> {
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::Hand, "游戏体验") {
changed_to = Some(SerialMode::Hand);
}
});
changed_to
}
fn draw_config_bar_connection(
ui: &mut egui::Ui,
config: &mut ConfigPanelState,
connection: &ConnectionManager,
recorder: &Recorder,
conn_state: ConnectionState,
) {
ui.colored_label(dim_text(), "端口");
egui::ComboBox::from_id_salt("config_bar_ports")
.width(170.0)
.selected_text(if config.port.is_empty() {
"无可用串口".to_owned()
} else {
config.port.clone()
})
.show_ui(ui, |ui| {
for port in &config.available_ports {
ui.selectable_value(&mut config.port, port.clone(), port.as_str());
}
});
if ui
.add(style::icon_button(
egui::RichText::new("").color(ONE_DARK_PRO.text).size(16.0),
egui::vec2(30.0, METRICS.field_height),
))
.on_hover_text("刷新串口")
.clicked()
{
refresh_config_ports(config);
}
ui.add_space(4.0);
ui.colored_label(dim_text(), "波特率");
ui.label(style::value_text(config.baud_rate.to_string()));
ui.add_space(4.0);
ui.colored_label(dim_text(), "状态");
ui.colored_label(
connection_status_color(conn_state),
connection_status_text(conn_state),
);
ui.add_space(6.0);
let is_connected = matches!(
conn_state,
ConnectionState::Connected | ConnectionState::Streaming
);
let button_text = if is_connected { "断开" } else { "连接" };
if ui
.add_sized(
egui::vec2(88.0, METRICS.button_height),
if is_connected {
style::danger_button(button_text)
} else {
style::primary_button(button_text)
},
)
.clicked()
{
if is_connected {
connection.disconnect();
} else if !config.port.is_empty() {
connection.connect(
&config.port,
12,
7,
config.mode.baud_rate(),
config.mode.protocol(),
recorder.clone(),
);
}
}
}
fn draw_config_bar_rates(
ui: &mut egui::Ui,
_stats: SerialIoStats,
sample_rate_hz: f32,
render_rate_hz: f32,
) {
ui.label(style::value_text(format!("{render_rate_hz:.1} Hz")));
ui.colored_label(dim_text(), "画面刷新率");
ui.add_space(14.0);
ui.label(style::value_text(format!("{sample_rate_hz:.1} Hz")));
ui.colored_label(dim_text(), "采样率");
}
pub fn draw_spatial_force_panel( pub fn draw_spatial_force_panel(
ctx: &egui::Context, ctx: &egui::Context,
force: Option<HudSpatialForce>, force: Option<HudSpatialForce>,
@@ -851,7 +974,7 @@ pub fn draw_stats_panel(
let target_pos = egui::pos2( let target_pos = egui::pos2(
viewport.left(), viewport.left(),
(viewport.bottom() - panel_height - PANEL_VIEWPORT_MARGIN * 2.0) (viewport.bottom() - panel_height - PANEL_VIEWPORT_MARGIN * 2.0)
.max(viewport.top() + layout::TITLE_BAR_HEIGHT + PANEL_VIEWPORT_MARGIN), .max(viewport.top() + layout::WORKSPACE_TOP + PANEL_VIEWPORT_MARGIN),
); );
let target_x = target_pos.x; let target_x = target_pos.x;
let hidden_x = if target_x < screen.center().x { let hidden_x = if target_x < screen.center().x {
@@ -924,7 +1047,7 @@ pub fn draw_hand_force_panels(ctx: &egui::Context, visible: bool, histories: &[V
let left_count = 4usize; let left_count = 4usize;
let right_count = HAND_FORCE_PANEL_TITLES.len() - left_count; let right_count = HAND_FORCE_PANEL_TITLES.len() - left_count;
let available_height = let available_height =
(screen.height() - layout::TITLE_BAR_HEIGHT - vertical_margin * 2.0).max(0.0); (screen.height() - layout::WORKSPACE_TOP - vertical_margin * 2.0).max(0.0);
let panel_height = ((available_height - (left_count - 1) as f32 * gap) / left_count as f32) let panel_height = ((available_height - (left_count - 1) as f32 * gap) / left_count as f32)
.clamp( .clamp(
HAND_FORCE_PANEL_MIN_HEIGHT.min(available_height), HAND_FORCE_PANEL_MIN_HEIGHT.min(available_height),
@@ -933,7 +1056,7 @@ pub fn draw_hand_force_panels(ctx: &egui::Context, visible: bool, histories: &[V
let left_height = left_count as f32 * panel_height + (left_count - 1) as f32 * gap; let left_height = left_count as f32 * panel_height + (left_count - 1) as f32 * gap;
let right_height = let right_height =
right_count as f32 * panel_height + (right_count.saturating_sub(1)) as f32 * gap; right_count as f32 * panel_height + (right_count.saturating_sub(1)) as f32 * gap;
let min_top = screen.top() + layout::TITLE_BAR_HEIGHT + vertical_margin; let min_top = screen.top() + layout::WORKSPACE_TOP + vertical_margin;
let left_top = (screen.center().y - left_height * 0.5).max(min_top); 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); let right_top = (screen.center().y - right_height * 0.5).max(min_top);