Skip to content

Commit 5c531db

Browse files
committed
Add results panel UI components: Implement controls for console visibility and UI scaling, display running metrics, progress tracking, and results tables. This enhances user interaction by providing comprehensive feedback during speed tests and improving overall layout and usability.
1 parent 9a2ccfd commit 5c531db

File tree

7 files changed

+482
-0
lines changed

7 files changed

+482
-0
lines changed

src/ui/results_panel/controls.rs

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
use crate::ui::types::ResultsPanelParams;
2+
use eframe::egui;
3+
use egui_phosphor::regular::*;
4+
5+
pub fn render_console_and_scale_controls(
6+
ui: &mut egui::Ui,
7+
params: &mut ResultsPanelParams<'_>,
8+
on_toggle_console: &mut bool,
9+
) {
10+
ui.horizontal(|ui| {
11+
let console_text = if params.console.is_visible() {
12+
format!("{EYE_SLASH} Hide Console")
13+
} else {
14+
format!("{EYE} Show Console")
15+
};
16+
if ui
17+
.add_sized([140.0, 35.0], egui::Button::new(console_text))
18+
.clicked()
19+
{
20+
*on_toggle_console = true;
21+
}
22+
23+
ui.separator();
24+
25+
ui.label(format!("{MAGNIFYING_GLASS} UI Scale"));
26+
let btn_size = egui::vec2(22.0, ui.spacing().interact_size.y);
27+
28+
if ui.add_sized(btn_size, egui::Button::new("-")).clicked() {
29+
*params.ui_scale = (*params.ui_scale - 0.1).max(0.3);
30+
}
31+
32+
if params.ui_scale_text.parse::<f32>().unwrap_or(1.0) != *params.ui_scale {
33+
*params.ui_scale_text = format!("{:.1}", params.ui_scale);
34+
}
35+
let width = 70.0;
36+
if ui
37+
.add_sized(
38+
[width, ui.spacing().interact_size.y],
39+
egui::TextEdit::singleline(params.ui_scale_text),
40+
)
41+
.changed()
42+
&& let Ok(new_scale) = params.ui_scale_text.parse::<f32>()
43+
{
44+
*params.ui_scale = new_scale.clamp(0.3, 3.0);
45+
}
46+
47+
if ui.add_sized(btn_size, egui::Button::new("+")).clicked() {
48+
*params.ui_scale = (*params.ui_scale + 0.1).min(3.0);
49+
}
50+
});
51+
}

src/ui/results_panel/metrics.rs

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
use crate::ui::helpers::get_size_label;
2+
use crate::ui::types::ResultsPanelParams;
3+
use eframe::egui;
4+
use egui_phosphor::regular::*;
5+
6+
pub fn render_running_metrics(ui: &mut egui::Ui, params: &ResultsPanelParams<'_>) {
7+
ui.horizontal(|ui| {
8+
ui.label(
9+
egui::RichText::new(format!(
10+
"{} {:.1} MB/s",
11+
LIGHTNING, params.test_state.current_throughput
12+
))
13+
.size(16.0)
14+
.color(egui::Color32::from_rgb(52, 152, 219)),
15+
);
16+
ui.label("•");
17+
ui.label(
18+
egui::RichText::new(format!(
19+
"{} {} reads/s",
20+
CHART_BAR, params.test_state.current_reads
21+
))
22+
.size(14.0)
23+
.color(egui::Color32::from_rgb(155, 89, 182)),
24+
);
25+
ui.label("•");
26+
ui.label(
27+
egui::RichText::new(format!(
28+
"{} {:.1} μs",
29+
CLOCK, params.test_state.current_latency
30+
))
31+
.size(14.0)
32+
.color(egui::Color32::from_rgb(231, 76, 60)),
33+
);
34+
35+
if let Some(current_size) = params.test_state.current_test_size {
36+
ui.label("•");
37+
ui.label(
38+
egui::RichText::new(format!("{} {}", TARGET, get_size_label(current_size)))
39+
.size(14.0)
40+
.color(egui::Color32::from_rgb(155, 89, 182)),
41+
);
42+
}
43+
});
44+
}

src/ui/results_panel/mod.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
pub mod controls;
2+
pub mod metrics;
3+
pub mod panel;
4+
pub mod plot;
5+
pub mod progress;
6+
pub mod table;
7+
8+
pub use panel::render_results_panel;

src/ui/results_panel/panel.rs

Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
use super::{
2+
controls::render_console_and_scale_controls, metrics::render_running_metrics,
3+
plot::render_plot_column, progress::render_chunk_progress, table::render_results_table,
4+
};
5+
use crate::ui::plot_controls::render_plot_size_controls;
6+
use crate::ui::types::{PlotMetric, ResultsPanelParams};
7+
use eframe::egui;
8+
use egui_phosphor::regular::*;
9+
10+
pub fn render_results_panel(
11+
ui: &mut egui::Ui,
12+
params: &mut ResultsPanelParams<'_>,
13+
on_stop_test: impl FnOnce(),
14+
on_test_again: impl FnOnce(),
15+
on_toggle_console: &mut bool,
16+
) {
17+
ui.vertical_centered(|ui| {
18+
ui.add_space(5.0);
19+
ui.heading(format!("{CHART_LINE_UP} DMA Speed Test Results"));
20+
ui.add_space(10.0);
21+
});
22+
23+
egui::Frame::new()
24+
.fill(ui.visuals().extreme_bg_color)
25+
.corner_radius(6.0)
26+
.inner_margin(10.0)
27+
.show(ui, |ui| {
28+
ui.horizontal(|ui| {
29+
ui.vertical(|ui| {
30+
render_plot_size_controls(
31+
ui,
32+
params.plot_controls.custom_plot_width,
33+
params.plot_controls.custom_plot_height,
34+
params.plot_controls.plot_resize_start_time,
35+
params.plot_controls.plot_resize_direction,
36+
params.plot_controls.plot_resize_last_repeat,
37+
);
38+
39+
ui.add_space(8.0);
40+
render_console_and_scale_controls(ui, params, on_toggle_console);
41+
42+
if params.test_state.is_running {
43+
ui.separator();
44+
render_running_metrics(ui, params);
45+
render_chunk_progress(ui, params);
46+
}
47+
});
48+
49+
ui.with_layout(egui::Layout::right_to_left(egui::Align::TOP), |ui| {
50+
if params.test_state.is_running {
51+
let stop_button = egui::Button::new(
52+
egui::RichText::new(format!("{X_CIRCLE} STOP TEST"))
53+
.color(egui::Color32::BLACK),
54+
)
55+
.fill(egui::Color32::from_rgb(231, 76, 60))
56+
.stroke(egui::Stroke::new(2.0, egui::Color32::from_rgb(192, 57, 43)));
57+
if ui.add_sized([150.0, 40.0], stop_button).clicked() {
58+
on_stop_test();
59+
}
60+
} else if params.test_state.test_end_time.is_some() {
61+
let again_button = egui::Button::new(
62+
egui::RichText::new(format!("{LIGHTNING} TEST AGAIN"))
63+
.color(egui::Color32::BLACK),
64+
)
65+
.fill(egui::Color32::from_rgb(46, 204, 113))
66+
.stroke(egui::Stroke::new(2.0, egui::Color32::from_rgb(39, 174, 96)));
67+
if ui.add_sized([170.0, 40.0], again_button).clicked() {
68+
on_test_again();
69+
*params.show_config = false;
70+
}
71+
72+
ui.add_space(8.0);
73+
74+
let back_button = egui::Button::new(
75+
egui::RichText::new("RETURN TO CONFIG").color(egui::Color32::BLACK),
76+
)
77+
.fill(egui::Color32::from_rgb(241, 196, 15))
78+
.stroke(egui::Stroke::new(
79+
2.0,
80+
egui::Color32::from_rgb(243, 156, 18),
81+
));
82+
if ui.add_sized([190.0, 40.0], back_button).clicked() {
83+
*params.show_config = true;
84+
}
85+
}
86+
});
87+
});
88+
});
89+
90+
ui.add_space(10.0);
91+
92+
ui.columns(3, |columns| {
93+
let width = *params.plot_controls.custom_plot_width;
94+
let height = *params.plot_controls.custom_plot_height;
95+
96+
let specs = [
97+
(
98+
"Throughput (MB/s)",
99+
"throughput_plot",
100+
PlotMetric::Throughput,
101+
"throughput_results",
102+
"Results (MB/s)",
103+
),
104+
(
105+
"Reads per Second",
106+
"reads_plot",
107+
PlotMetric::Reads,
108+
"reads_results",
109+
"Results (reads/s)",
110+
),
111+
(
112+
"Latency (μs)",
113+
"latency_plot",
114+
PlotMetric::Latency,
115+
"latency_results",
116+
"Results (μs)",
117+
),
118+
];
119+
120+
for (column, (heading, plot_id, metric, table_id, title)) in columns.iter_mut().zip(specs) {
121+
column.vertical(|ui| {
122+
render_plot_column(ui, heading, plot_id, metric, params, width, height);
123+
ui.add_space(10.0);
124+
render_results_table(ui, table_id, params.results, metric, title);
125+
});
126+
}
127+
});
128+
}

src/ui/results_panel/plot.rs

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
use crate::ui::constants::*;
2+
use crate::ui::helpers::{color_for_size, get_size_label};
3+
use crate::ui::types::{PlotMetric, ResultsPanelParams, TestResults};
4+
use eframe::egui;
5+
use egui_plot::{Corner, Legend, Line, Plot, PlotPoints};
6+
7+
pub fn render_plot_column(
8+
ui: &mut egui::Ui,
9+
heading: &str,
10+
plot_id: &'static str,
11+
metric: PlotMetric,
12+
params: &ResultsPanelParams<'_>,
13+
width: f32,
14+
height: f32,
15+
) {
16+
ui.heading(heading);
17+
ui.add_space(6.0);
18+
render_plot(
19+
ui,
20+
plot_id,
21+
params.results,
22+
metric,
23+
params.duration,
24+
width,
25+
height,
26+
);
27+
}
28+
29+
fn base_plot(id: &'static str, duration: u64, width: f32, height: f32) -> Plot<'static> {
30+
Plot::new(id)
31+
.height(height)
32+
.width(width)
33+
.include_x(0.0)
34+
.include_x(duration as f64)
35+
.show_grid(PLOT_SHOW_GRID)
36+
.allow_drag(PLOT_ALLOW_DRAG)
37+
.allow_zoom(PLOT_ALLOW_ZOOM)
38+
.allow_scroll(PLOT_ALLOW_SCROLL)
39+
.allow_boxed_zoom(PLOT_ALLOW_BOXED_ZOOM)
40+
.allow_double_click_reset(PLOT_ALLOW_DOUBLE_CLICK_RESET)
41+
}
42+
43+
fn y_range_for(results: &TestResults, metric: PlotMetric) -> (f64, f64) {
44+
let mut min_v: Option<f64> = None;
45+
let mut max_v: Option<f64> = None;
46+
if let Ok(results) = results.lock() {
47+
for (_, (throughput_points, reads_points, latency_points)) in results.iter() {
48+
let pts: &Vec<(f64, f64)> = match metric {
49+
PlotMetric::Throughput => throughput_points,
50+
PlotMetric::Reads => reads_points,
51+
PlotMetric::Latency => latency_points,
52+
};
53+
for &(_, y) in pts.iter() {
54+
min_v = Some(min_v.map_or(y, |m| m.min(y)));
55+
max_v = Some(max_v.map_or(y, |m| m.max(y)));
56+
}
57+
}
58+
}
59+
match (min_v, max_v) {
60+
(Some(miny), Some(maxy)) if maxy > miny => {
61+
let range = maxy - miny;
62+
(miny - 0.1 * range, maxy + 0.1 * range)
63+
}
64+
_ => (0.0, 1.0),
65+
}
66+
}
67+
68+
fn render_plot(
69+
ui: &mut egui::Ui,
70+
plot_id: &'static str,
71+
results: &TestResults,
72+
metric: PlotMetric,
73+
duration: u64,
74+
width: f32,
75+
height: f32,
76+
) {
77+
let (y_min, y_max) = y_range_for(results, metric);
78+
base_plot(plot_id, duration, width, height)
79+
.include_y(y_min)
80+
.include_y(y_max)
81+
.legend(Legend::default().position(Corner::RightTop))
82+
.show(ui, |plot_ui| {
83+
if let Ok(results) = results.lock() {
84+
let mut sorted_results: Vec<_> = results.iter().collect();
85+
sorted_results.sort_by(|a, b| b.0.cmp(&a.0));
86+
for (read_size, (throughput_points, reads_points, latency_points)) in sorted_results
87+
{
88+
let points = match metric {
89+
PlotMetric::Throughput => throughput_points,
90+
PlotMetric::Reads => reads_points,
91+
PlotMetric::Latency => latency_points,
92+
};
93+
if !points.is_empty() {
94+
let color = color_for_size(*read_size);
95+
let plot_points: PlotPoints<'_> =
96+
points.iter().map(|&(x, y)| [x, y]).collect();
97+
plot_ui.line(
98+
Line::new(get_size_label(*read_size), plot_points)
99+
.color(color)
100+
.width(2.0),
101+
);
102+
}
103+
}
104+
}
105+
});
106+
}

0 commit comments

Comments
 (0)