use egui::{Color32, Rect, Sense, Stroke, Vec2, pos2};
use crate::viz::color::health_color;
use crate::viz::layout::PlotLayout;
use crate::viz::state::{PanelConfig, PanelState};
const BARS_H: f32 = 80.0;
pub(super) fn bars_row(ui: &mut egui::Ui, state: &mut PanelState) {
let PanelConfig {
history_capacity,
bar_good_threshold,
bar_warn_threshold,
} = state.config.clone();
let (rect, resp) = ui.allocate_exact_size(
Vec2::new(ui.available_width(), BARS_H),
Sense::click_and_drag(),
);
let painter = ui.painter_at(rect);
painter.rect_filled(rect, 2.0, Color32::from_gray(18));
if state.system.is_empty() {
return;
}
let n = state.system.len();
let shift = ui.input(|i| i.modifiers.shift);
let selecting = shift && resp.dragged();
let layout = PlotLayout::new(
rect,
n,
if selecting { None } else { state.selection },
history_capacity,
);
let bar_w = (layout.slot_w - 1.0).max(1.0);
let y_max = (bar_warn_threshold * 1.5) as f32;
let y_good = rect.bottom() - rect.height() * (bar_good_threshold as f32 / y_max);
painter.line_segment(
[pos2(rect.left(), y_good), pos2(rect.right(), y_good)],
Stroke::new(1.0, Color32::from_rgb(46, 204, 113)),
);
let y_warn = rect.bottom() - rect.height() * (bar_warn_threshold as f32 / y_max);
painter.line_segment(
[pos2(rect.left(), y_warn), pos2(rect.right(), y_warn)],
Stroke::new(1.0, Color32::from_rgb(231, 76, 60)),
);
for i in layout.range.clone() {
let frame_ms = state.system[i].frame_ns as f64 / 1e6;
let x = layout.base_x + (i - layout.range.start) as f32 * layout.slot_w;
let h = (frame_ms as f32 / y_max).clamp(0.02, 1.0) * rect.height();
let bar_rect =
Rect::from_min_max(pos2(x, rect.bottom() - h), pos2(x + bar_w, rect.bottom()));
painter.rect_filled(
bar_rect,
0.0,
health_color(frame_ms, bar_good_threshold, bar_warn_threshold),
);
}
if selecting && let Some((lo, hi)) = state.selection {
let x0 = layout.base_x + lo.saturating_sub(layout.range.start) as f32 * layout.slot_w;
let x1 = layout.base_x + (hi + 1).saturating_sub(layout.range.start) as f32 * layout.slot_w;
painter.rect_filled(
Rect::from_min_max(pos2(x0, rect.top()), pos2(x1, rect.bottom())),
0.0,
Color32::from_white_alpha(28),
);
}
if let Some(idx) = state.pinned
&& let Some(x) = layout.cursor_x(idx)
{
painter.line_segment(
[pos2(x, rect.top()), pos2(x, rect.bottom())],
Stroke::new(1.5, Color32::WHITE),
);
} else if let Some(pos) = resp.hover_pos()
&& let Some(idx) = layout.hover_to_idx(pos.x)
&& let Some(x) = layout.cursor_x(idx)
{
painter.line_segment(
[pos2(x, rect.top()), pos2(x, rect.bottom())],
Stroke::new(1.0, Color32::from_gray(180)),
);
}
if shift
&& resp.drag_started()
&& let Some(pos) = resp.interact_pointer_pos()
&& let Some(idx) = layout.hover_to_idx(pos.x)
{
state.selection = Some((idx, idx));
state.pinned = Some(idx);
state.reset_flame_zoom();
}
if shift
&& resp.dragged()
&& let Some(pos) = resp.interact_pointer_pos()
&& let Some(idx) = layout.hover_to_idx(pos.x)
&& let Some((anchor, _)) = state.selection
{
let (lo, hi) = if anchor <= idx {
(anchor, idx)
} else {
(idx, anchor)
};
state.selection = Some((lo, hi));
state.pinned = slowest_in_range(state, lo, hi).or(Some(idx));
}
if (resp.clicked() || (!shift && resp.dragged()))
&& let Some(pos) = resp.interact_pointer_pos()
&& let Some(idx) = layout.hover_to_idx(pos.x)
{
state.selection = None;
state.pinned = Some(idx);
state.reset_flame_zoom();
}
}
fn slowest_in_range(state: &PanelState, lo: usize, hi: usize) -> Option<usize> {
state
.system
.iter()
.enumerate()
.skip(lo)
.take(hi - lo + 1)
.max_by_key(|(_, s)| s.frame_ns)
.map(|(i, _)| i)
}