Add pressure visual modes and breakout game
This commit is contained in:
471
src/ui.rs
471
src/ui.rs
@@ -3,9 +3,10 @@ use eframe::egui;
|
||||
use crate::{
|
||||
connection::{ConnectionManager, ConnectionState},
|
||||
recording::Recorder,
|
||||
serial_core::serial::SerialIoStats,
|
||||
style::{
|
||||
self, ACCENT_BLUE, ACCENT_GREEN, ACCENT_ORANGE, ACCENT_RED, METRICS, ONE_DARK_PRO,
|
||||
accent_text, dim_text, group_frame, layout, panel_frame, tag_button,
|
||||
dim_text, group_frame, layout, panel_frame, tag_button,
|
||||
},
|
||||
utils::serial_enum,
|
||||
};
|
||||
@@ -22,6 +23,7 @@ pub struct FloatingPanelState {
|
||||
pub struct ConfigPanelState {
|
||||
pub mode: SerialMode,
|
||||
pub port: String,
|
||||
pub available_ports: Vec<String>,
|
||||
pub baud_rate: u32,
|
||||
pub data_bits: u8,
|
||||
pub stop_bits: u8,
|
||||
@@ -78,17 +80,24 @@ impl FloatingPanelState {
|
||||
|
||||
impl Default for ConfigPanelState {
|
||||
fn default() -> Self {
|
||||
let available_ports = serial_enum().unwrap_or_default();
|
||||
let port = available_ports
|
||||
.first()
|
||||
.cloned()
|
||||
.unwrap_or_else(|| "COM3".to_owned());
|
||||
|
||||
Self {
|
||||
mode: SerialMode::Finger,
|
||||
port: "COM3".to_owned(),
|
||||
baud_rate: 115_200,
|
||||
port,
|
||||
available_ports,
|
||||
baud_rate: 921_600,
|
||||
data_bits: 8,
|
||||
stop_bits: 1,
|
||||
parity: Parity::None,
|
||||
timeout_ms: 1000,
|
||||
module_addr: 1,
|
||||
connected: false,
|
||||
auto_reconnect: true,
|
||||
auto_reconnect: false,
|
||||
manual_tx: "01 03 00 00 00 02".to_owned(),
|
||||
model_path: "model/default.eskin".to_owned(),
|
||||
}
|
||||
@@ -307,8 +316,16 @@ pub fn draw_config_panel(
|
||||
ctx: &egui::Context,
|
||||
panel: &mut FloatingPanelState,
|
||||
config: &mut ConfigPanelState,
|
||||
connection: &ConnectionManager,
|
||||
) -> Option<SerialMode> {
|
||||
let mut changed_mode = None;
|
||||
let conn_state = connection.state();
|
||||
let stats = connection.stats();
|
||||
config.connected = matches!(
|
||||
conn_state,
|
||||
ConnectionState::Connected | ConnectionState::Streaming
|
||||
);
|
||||
|
||||
draw_floating_panel(ctx, panel, "配置", "config_panel", |ui| {
|
||||
ui.set_min_width(560.0);
|
||||
|
||||
@@ -318,11 +335,12 @@ pub fn draw_config_panel(
|
||||
|
||||
// draw_mode_row(ui, config);
|
||||
ui.separator();
|
||||
draw_connection_row(ui, config);
|
||||
draw_connection_row(ui, config, connection, conn_state);
|
||||
ui.add_space(8.0);
|
||||
draw_serial_grid(ui, config);
|
||||
ui.add_space(8.0);
|
||||
draw_mode_body(ui, config);
|
||||
// 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
|
||||
}
|
||||
@@ -341,16 +359,22 @@ fn draw_mode_row(ui: &mut egui::Ui, config: &mut ConfigPanelState) -> Option<Ser
|
||||
}
|
||||
// mode_button(ui, &mut config.mode, SerialMode::Model, "模型");
|
||||
|
||||
ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| {
|
||||
ui.checkbox(&mut config.auto_reconnect, "自动");
|
||||
ui.colored_label(dim_text(), "重连");
|
||||
});
|
||||
// Legacy reconnect controls. Current sensors run fixed 921600 baud without auto-reconnect UI.
|
||||
// ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| {
|
||||
// ui.checkbox(&mut config.auto_reconnect, "自动");
|
||||
// ui.colored_label(dim_text(), "重连");
|
||||
// });
|
||||
});
|
||||
|
||||
changed_to
|
||||
}
|
||||
|
||||
fn draw_connection_row(ui: &mut egui::Ui, config: &mut ConfigPanelState) {
|
||||
fn draw_connection_row(
|
||||
ui: &mut egui::Ui,
|
||||
config: &mut ConfigPanelState,
|
||||
connection: &ConnectionManager,
|
||||
conn_state: ConnectionState,
|
||||
) {
|
||||
ui.horizontal(|ui| {
|
||||
ui.add(
|
||||
egui::Image::new(egui::include_image!("../static/cpu.png"))
|
||||
@@ -360,48 +384,75 @@ fn draw_connection_row(ui: &mut egui::Ui, config: &mut ConfigPanelState) {
|
||||
ui.add_space(METRICS.item_gap);
|
||||
|
||||
ui.vertical(|ui| {
|
||||
ui.label(style::value_text(format!("端口 {}", config.port)));
|
||||
ui.horizontal(|ui| {
|
||||
ui.label(style::field_label("端口"));
|
||||
egui::ComboBox::from_id_salt("config_ports")
|
||||
.width(126.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),
|
||||
METRICS.icon_button,
|
||||
))
|
||||
.on_hover_text("刷新串口")
|
||||
.clicked()
|
||||
{
|
||||
refresh_config_ports(config);
|
||||
}
|
||||
});
|
||||
ui.label(style::value_text(format!("波特率 {}", config.baud_rate)));
|
||||
let status = if config.connected {
|
||||
"已连接"
|
||||
} else {
|
||||
"未连接"
|
||||
};
|
||||
let status_color = if config.connected {
|
||||
style::status_color_ok()
|
||||
} else {
|
||||
style::status_color_error()
|
||||
};
|
||||
ui.colored_label(status_color, status);
|
||||
ui.colored_label(
|
||||
connection_status_color(conn_state),
|
||||
connection_status_text(conn_state),
|
||||
);
|
||||
});
|
||||
|
||||
ui.add_space(22.0);
|
||||
|
||||
let button_text = if config.connected { "断开" } else { "连接" };
|
||||
let is_connected = matches!(
|
||||
conn_state,
|
||||
ConnectionState::Connected | ConnectionState::Streaming
|
||||
);
|
||||
let button_text = if is_connected { "断开" } else { "连接" };
|
||||
if ui
|
||||
.add(if config.connected {
|
||||
.add(if is_connected {
|
||||
style::danger_button(button_text)
|
||||
} else {
|
||||
style::primary_button(button_text)
|
||||
})
|
||||
.clicked()
|
||||
{
|
||||
config.connected = !config.connected;
|
||||
if is_connected {
|
||||
connection.disconnect();
|
||||
} else if !config.port.is_empty() {
|
||||
connection.connect(&config.port, 12, 7);
|
||||
}
|
||||
}
|
||||
|
||||
ui.add_space(18.0);
|
||||
ui.colored_label(
|
||||
if config.auto_reconnect {
|
||||
style::status_color_ok()
|
||||
} else {
|
||||
ONE_DARK_PRO.text_dim
|
||||
},
|
||||
if config.auto_reconnect {
|
||||
"链路保护 开"
|
||||
} else {
|
||||
"链路保护 关"
|
||||
},
|
||||
);
|
||||
// Legacy reconnect/link-protection indicator:
|
||||
// ui.add_space(18.0);
|
||||
// ui.colored_label(
|
||||
// if config.auto_reconnect {
|
||||
// style::status_color_ok()
|
||||
// } else {
|
||||
// ONE_DARK_PRO.text_dim
|
||||
// },
|
||||
// if config.auto_reconnect {
|
||||
// "链路保护 开"
|
||||
// } else {
|
||||
// "链路保护 关"
|
||||
// },
|
||||
// );
|
||||
});
|
||||
}
|
||||
|
||||
@@ -453,41 +504,41 @@ fn draw_serial_grid(ui: &mut egui::Ui, config: &mut ConfigPanelState) {
|
||||
});
|
||||
}
|
||||
|
||||
fn draw_mode_body(ui: &mut egui::Ui, config: &mut ConfigPanelState) {
|
||||
fn draw_mode_body(
|
||||
ui: &mut egui::Ui,
|
||||
config: &mut ConfigPanelState,
|
||||
conn_state: ConnectionState,
|
||||
stats: SerialIoStats,
|
||||
) {
|
||||
group_frame().show(ui, |ui| match config.mode {
|
||||
SerialMode::Finger => {
|
||||
ui.horizontal(|ui| {
|
||||
ui.label(style::field_label("模块地址"));
|
||||
ui.add_sized(
|
||||
egui::vec2(80.0, METRICS.field_height),
|
||||
egui::DragValue::new(&mut config.module_addr).range(1..=247),
|
||||
);
|
||||
ui.add_space(16.0);
|
||||
if ui.add(tag_button("读取信息")).clicked() {}
|
||||
if ui.add(tag_button("探测")).clicked() {}
|
||||
});
|
||||
ui.separator();
|
||||
ui.horizontal(|ui| {
|
||||
ui.colored_label(dim_text(), "状态");
|
||||
ui.label("就绪");
|
||||
ui.colored_label(dim_text(), "接收");
|
||||
ui.label("0 字节");
|
||||
ui.colored_label(dim_text(), "发送");
|
||||
ui.label("0 字节");
|
||||
});
|
||||
// Legacy module-address controls for older devices:
|
||||
// ui.horizontal(|ui| {
|
||||
// ui.label(style::field_label("模块地址"));
|
||||
// ui.add_sized(
|
||||
// egui::vec2(80.0, METRICS.field_height),
|
||||
// egui::DragValue::new(&mut config.module_addr).range(1..=247),
|
||||
// );
|
||||
// ui.add_space(16.0);
|
||||
// if ui.add(tag_button("读取信息")).clicked() {}
|
||||
// if ui.add(tag_button("探测")).clicked() {}
|
||||
// });
|
||||
draw_status_bytes_row(ui, conn_state, stats);
|
||||
}
|
||||
SerialMode::Hand => {
|
||||
ui.horizontal(|ui| {
|
||||
ui.label(style::field_label("发送"));
|
||||
ui.add_sized(
|
||||
egui::vec2(300.0, METRICS.field_height),
|
||||
egui::TextEdit::singleline(&mut config.manual_tx),
|
||||
);
|
||||
if ui.add(tag_button("发送")).clicked() {}
|
||||
if ui.add(tag_button("清空")).clicked() {
|
||||
config.manual_tx.clear();
|
||||
}
|
||||
});
|
||||
// Legacy manual-TX controls for older hand-module debugging:
|
||||
// ui.horizontal(|ui| {
|
||||
// ui.label(style::field_label("发送"));
|
||||
// ui.add_sized(
|
||||
// egui::vec2(300.0, METRICS.field_height),
|
||||
// egui::TextEdit::singleline(&mut config.manual_tx),
|
||||
// );
|
||||
// if ui.add(tag_button("发送")).clicked() {}
|
||||
// if ui.add(tag_button("清空")).clicked() {
|
||||
// config.manual_tx.clear();
|
||||
// }
|
||||
// });
|
||||
draw_status_bytes_row(ui, conn_state, stats);
|
||||
} // SerialMode::Model => {
|
||||
// ui.horizontal(|ui| {
|
||||
// ui.label(style::field_label("模型"));
|
||||
@@ -502,6 +553,45 @@ fn draw_mode_body(ui: &mut egui::Ui, config: &mut ConfigPanelState) {
|
||||
});
|
||||
}
|
||||
|
||||
fn draw_status_bytes_row(ui: &mut egui::Ui, conn_state: ConnectionState, stats: SerialIoStats) {
|
||||
ui.horizontal(|ui| {
|
||||
ui.colored_label(dim_text(), "状态");
|
||||
ui.label(connection_status_text(conn_state));
|
||||
ui.colored_label(dim_text(), "接收");
|
||||
ui.label(format!("{} 字节", stats.rx_bytes));
|
||||
ui.colored_label(dim_text(), "发送");
|
||||
ui.label(format!("{} 字节", stats.tx_bytes));
|
||||
});
|
||||
}
|
||||
|
||||
fn refresh_config_ports(config: &mut ConfigPanelState) {
|
||||
if let Ok(ports) = serial_enum() {
|
||||
if !ports.contains(&config.port) {
|
||||
config.port = ports.first().cloned().unwrap_or_default();
|
||||
}
|
||||
config.available_ports = ports;
|
||||
}
|
||||
}
|
||||
|
||||
fn connection_status_text(state: ConnectionState) -> &'static str {
|
||||
match state {
|
||||
ConnectionState::Disconnected => "未连接",
|
||||
ConnectionState::Connecting => "连接中...",
|
||||
ConnectionState::Connected => "已连接",
|
||||
ConnectionState::Streaming => "采集中",
|
||||
ConnectionState::Error => "连接错误",
|
||||
}
|
||||
}
|
||||
|
||||
fn connection_status_color(state: ConnectionState) -> egui::Color32 {
|
||||
match state {
|
||||
ConnectionState::Disconnected => style::status_color_error(),
|
||||
ConnectionState::Connecting => style::status_color_warn(),
|
||||
ConnectionState::Connected | ConnectionState::Streaming => style::status_color_ok(),
|
||||
ConnectionState::Error => style::status_color_error(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn icon_button<'a>(
|
||||
ui: &mut egui::Ui,
|
||||
icon: IconButtonIcon<'a>,
|
||||
@@ -578,21 +668,236 @@ fn parity_combo(ui: &mut egui::Ui, config: &mut ConfigPanelState) {
|
||||
});
|
||||
}
|
||||
|
||||
pub fn draw_stats_panel(ctx: &egui::Context, panel: &mut FloatingPanelState) {
|
||||
pub fn draw_stats_panel(
|
||||
ctx: &egui::Context,
|
||||
panel: &mut FloatingPanelState,
|
||||
force_history: &[f32],
|
||||
) {
|
||||
draw_floating_panel(ctx, panel, "统计", "stats_panel", |ui| {
|
||||
ui.horizontal(|ui| {
|
||||
ui.colored_label(accent_text(), "0.030");
|
||||
ui.label(style::subtle_text("81m:51s"));
|
||||
});
|
||||
ui.separator();
|
||||
group_frame().show(ui, |ui| {
|
||||
ui.label(style::field_label("帧率 / GPU 信息"));
|
||||
ui.label(style::value_text("边界 589.0us"));
|
||||
ui.label(style::value_text("簇 12.8ms"));
|
||||
});
|
||||
ui.set_min_width(320.0);
|
||||
draw_resultant_force_chart(ui, force_history);
|
||||
});
|
||||
}
|
||||
|
||||
const FORCE_CHART_MAX_N: f32 = 25.6;
|
||||
|
||||
fn draw_resultant_force_chart(ui: &mut egui::Ui, values: &[f32]) {
|
||||
let latest = values.last().copied().unwrap_or(0.0);
|
||||
let max = values.iter().copied().fold(0.0_f32, f32::max);
|
||||
let active_values = values.iter().copied().filter(|value| *value > 0.0);
|
||||
let min = active_values.fold(None, |best: Option<f32>, value| {
|
||||
Some(best.map_or(value, |current| current.min(value)))
|
||||
});
|
||||
|
||||
group_frame().show(ui, |ui| {
|
||||
ui.horizontal(|ui| {
|
||||
ui.colored_label(ACCENT_BLUE, "RF");
|
||||
ui.label(style::panel_title("合力"));
|
||||
ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| {
|
||||
ui.colored_label(ACCENT_GREEN, format!("{latest:.1} N"));
|
||||
});
|
||||
});
|
||||
|
||||
ui.add_space(4.0);
|
||||
ui.horizontal(|ui| {
|
||||
force_metric(ui, "峰值", max);
|
||||
ui.separator();
|
||||
force_metric(ui, "低值", min.unwrap_or(0.0));
|
||||
ui.separator();
|
||||
ui.colored_label(dim_text(), format!("{} 点", values.len()));
|
||||
});
|
||||
|
||||
ui.add_space(6.0);
|
||||
paint_resultant_force_chart(ui, values);
|
||||
});
|
||||
}
|
||||
|
||||
fn force_metric(ui: &mut egui::Ui, label: &'static str, value: f32) {
|
||||
ui.colored_label(dim_text(), label);
|
||||
ui.label(style::value_text(format!("{value:.1} N")));
|
||||
}
|
||||
|
||||
fn paint_resultant_force_chart(ui: &mut egui::Ui, values: &[f32]) {
|
||||
const CHART_HEIGHT: f32 = 138.0;
|
||||
const CHART_POINTS: usize = 42;
|
||||
|
||||
let width = ui.available_width().max(280.0);
|
||||
let (rect, _) = ui.allocate_exact_size(egui::vec2(width, CHART_HEIGHT), egui::Sense::hover());
|
||||
let painter = ui.painter_at(rect);
|
||||
let radius = egui::CornerRadius::same(5);
|
||||
|
||||
painter.rect_filled(rect, radius, egui::Color32::from_rgb(10, 18, 22));
|
||||
painter.rect_stroke(
|
||||
rect,
|
||||
radius,
|
||||
egui::Stroke::new(1.0, color_alpha(ONE_DARK_PRO.accent, 95)),
|
||||
egui::StrokeKind::Outside,
|
||||
);
|
||||
|
||||
let chart_rect = egui::Rect::from_min_max(
|
||||
rect.left_top() + egui::vec2(34.0, 12.0),
|
||||
rect.right_bottom() - egui::vec2(10.0, 24.0),
|
||||
);
|
||||
|
||||
paint_force_grid(&painter, rect, chart_rect);
|
||||
|
||||
if values.is_empty() {
|
||||
painter.text(
|
||||
chart_rect.center(),
|
||||
egui::Align2::CENTER_CENTER,
|
||||
"等待合力数据",
|
||||
egui::FontId::proportional(12.0),
|
||||
ONE_DARK_PRO.text_subtle,
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
ui.ctx().request_repaint();
|
||||
|
||||
let time = ui.ctx().input(|input| input.time) as f32;
|
||||
let enter = ui
|
||||
.ctx()
|
||||
.animate_bool(egui::Id::new("resultant_force_chart_enter"), true);
|
||||
let slide_offset = (1.0 - enter) * -chart_rect.width() * 0.65;
|
||||
let plot_rect = chart_rect.translate(egui::vec2(slide_offset, 0.0));
|
||||
|
||||
let mut points = Vec::with_capacity(values.len());
|
||||
let start_slot = CHART_POINTS.saturating_sub(values.len());
|
||||
for (index, value) in values.iter().enumerate() {
|
||||
let denom = (CHART_POINTS - 1).max(1) as f32;
|
||||
let slot = start_slot + index;
|
||||
let x = plot_rect.left() + (slot as f32 / denom) * plot_rect.width();
|
||||
let normalized = (*value / FORCE_CHART_MAX_N).clamp(0.0, 1.0);
|
||||
let y = plot_rect.bottom() - normalized * plot_rect.height();
|
||||
points.push(egui::pos2(x, y));
|
||||
}
|
||||
|
||||
let plot_painter = painter.with_clip_rect(chart_rect.expand2(egui::vec2(1.0, 1.0)));
|
||||
paint_force_sweep(&plot_painter, chart_rect, time);
|
||||
paint_force_area(&plot_painter, &points, plot_rect);
|
||||
|
||||
if points.len() >= 2 {
|
||||
plot_painter.add(egui::Shape::line(
|
||||
points.clone(),
|
||||
egui::Stroke::new(4.0, color_alpha(ONE_DARK_PRO.accent, 45)),
|
||||
));
|
||||
plot_painter.add(egui::Shape::line(
|
||||
points.clone(),
|
||||
egui::Stroke::new(1.7, ONE_DARK_PRO.accent),
|
||||
));
|
||||
}
|
||||
|
||||
if let Some(last) = points.last().copied() {
|
||||
let pulse = (time * 1.8).fract();
|
||||
plot_painter.circle_stroke(
|
||||
last,
|
||||
5.0 + pulse * 12.0,
|
||||
egui::Stroke::new(
|
||||
1.0,
|
||||
color_alpha(ACCENT_GREEN, ((1.0 - pulse) * 120.0) as u8),
|
||||
),
|
||||
);
|
||||
plot_painter.circle_filled(last, 3.6, ACCENT_GREEN);
|
||||
plot_painter.circle_filled(last, 1.6, egui::Color32::WHITE);
|
||||
}
|
||||
}
|
||||
|
||||
fn paint_force_grid(painter: &egui::Painter, rect: egui::Rect, chart_rect: egui::Rect) {
|
||||
for tick in [25.0, 20.0, 15.0, 10.0, 5.0, 0.0] {
|
||||
let normalized = (tick / FORCE_CHART_MAX_N).clamp(0.0, 1.0);
|
||||
let y = chart_rect.bottom() - normalized * chart_rect.height();
|
||||
painter.line_segment(
|
||||
[
|
||||
egui::pos2(chart_rect.left(), y),
|
||||
egui::pos2(chart_rect.right(), y),
|
||||
],
|
||||
egui::Stroke::new(1.0, color_alpha(ONE_DARK_PRO.border, 78)),
|
||||
);
|
||||
painter.text(
|
||||
egui::pos2(rect.left() + 8.0, y),
|
||||
egui::Align2::LEFT_CENTER,
|
||||
format!("{tick:.0}"),
|
||||
egui::FontId::monospace(9.0),
|
||||
ONE_DARK_PRO.text_dim,
|
||||
);
|
||||
}
|
||||
|
||||
for index in 0..=5 {
|
||||
let x = chart_rect.left() + index as f32 / 5.0 * chart_rect.width();
|
||||
painter.line_segment(
|
||||
[
|
||||
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)),
|
||||
);
|
||||
}
|
||||
|
||||
painter.text(
|
||||
egui::pos2(chart_rect.left(), rect.bottom() - 11.0),
|
||||
egui::Align2::LEFT_CENTER,
|
||||
"-4.2s",
|
||||
egui::FontId::monospace(9.0),
|
||||
ONE_DARK_PRO.text_subtle,
|
||||
);
|
||||
painter.text(
|
||||
egui::pos2(chart_rect.right(), rect.bottom() - 11.0),
|
||||
egui::Align2::RIGHT_CENTER,
|
||||
"now",
|
||||
egui::FontId::monospace(9.0),
|
||||
ONE_DARK_PRO.text_subtle,
|
||||
);
|
||||
}
|
||||
|
||||
fn paint_force_sweep(painter: &egui::Painter, chart_rect: egui::Rect, time: f32) {
|
||||
let sweep = (time * 0.55).fract();
|
||||
let x = chart_rect.left() + sweep * chart_rect.width();
|
||||
let sweep_rect = egui::Rect::from_min_max(
|
||||
egui::pos2(x - 8.0, chart_rect.top()),
|
||||
egui::pos2(x + 8.0, chart_rect.bottom()),
|
||||
);
|
||||
|
||||
painter.rect_filled(
|
||||
sweep_rect,
|
||||
egui::CornerRadius::ZERO,
|
||||
color_alpha(ONE_DARK_PRO.accent, 20),
|
||||
);
|
||||
painter.line_segment(
|
||||
[
|
||||
egui::pos2(x, chart_rect.top()),
|
||||
egui::pos2(x, chart_rect.bottom()),
|
||||
],
|
||||
egui::Stroke::new(1.0, color_alpha(ACCENT_GREEN, 95)),
|
||||
);
|
||||
}
|
||||
|
||||
fn paint_force_area(painter: &egui::Painter, points: &[egui::Pos2], plot_rect: egui::Rect) {
|
||||
if points.len() < 2 {
|
||||
return;
|
||||
}
|
||||
|
||||
for pair in points.windows(2) {
|
||||
let left = pair[0];
|
||||
let right = pair[1];
|
||||
let area = vec![
|
||||
egui::pos2(left.x, plot_rect.bottom()),
|
||||
left,
|
||||
right,
|
||||
egui::pos2(right.x, plot_rect.bottom()),
|
||||
];
|
||||
|
||||
painter.add(egui::Shape::convex_polygon(
|
||||
area,
|
||||
color_alpha(ONE_DARK_PRO.accent, 32),
|
||||
egui::Stroke::NONE,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
fn color_alpha(color: egui::Color32, alpha: u8) -> egui::Color32 {
|
||||
egui::Color32::from_rgba_premultiplied(color.r(), color.g(), color.b(), alpha)
|
||||
}
|
||||
|
||||
fn draw_floating_panel(
|
||||
ctx: &egui::Context,
|
||||
panel: &mut FloatingPanelState,
|
||||
|
||||
Reference in New Issue
Block a user