vor 0.2.1

Cross-platform performance instrumentation with an in-app egui panel and live system and GPU metrics.
Documentation
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();
    // While actively shift-dragging out a selection, keep the full bar
    // range visible and hittable; the zoom-to-selection only applies
    // once the drag settles. Otherwise the fresh 1-frame selection
    // collapses the layout and the drag can never grow.
    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;

    // 60 / 30 FPS guidelines.
    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),
        );
    }

    // Brush highlight while dragging out a selection over the
    // full-view bars.
    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),
        );
    }

    // Pinned + hover cursors. Hover only renders when no pin is set.
    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)),
        );
    }

    // Shift+drag → range selection (pinning to the slowest frame
    // inside it so the flame chart drills into the spike that
    // motivated it). Plain drag scrubs the pin without selecting, so
    // a stray drag never freezes the panel on a range.
    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);
        // New time window: drop any leftover within-frame zoom.
        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)
}