Compare commits
1 Commits
waic-hand
...
waic-finge
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9efe360a1d |
29
src/app.rs
29
src/app.rs
@@ -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(
|
||||||
|
|||||||
@@ -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
173
src/ui.rs
@@ -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);
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user