use crate::config::FlowConfig;
use crate::types::node::{InternalNode, NodeId};
use crate::types::viewport::Viewport;
use std::collections::HashMap;
const MINIMAP_WIDTH: f32 = 180.0;
const MINIMAP_HEIGHT: f32 = 120.0;
const MINIMAP_MARGIN: f32 = 12.0;
const MINIMAP_PADDING: f32 = 8.0;
pub fn render_minimap<D>(
painter: &egui::Painter,
canvas_rect: egui::Rect,
viewport: &Viewport,
node_lookup: &HashMap<NodeId, InternalNode<D>>,
config: &FlowConfig,
) -> Option<MinimapInfo> {
if !config.show_minimap {
return None;
}
let mm_rect = egui::Rect::from_min_size(
egui::pos2(
canvas_rect.max.x - MINIMAP_WIDTH - MINIMAP_MARGIN,
canvas_rect.max.y - MINIMAP_HEIGHT - MINIMAP_MARGIN,
),
egui::vec2(MINIMAP_WIDTH, MINIMAP_HEIGHT),
);
painter.rect_filled(
mm_rect,
4.0,
egui::Color32::from_rgba_unmultiplied(20, 20, 30, 210),
);
painter.rect_stroke(
mm_rect,
4.0,
egui::Stroke::new(1.0, egui::Color32::from_rgb(80, 80, 100)),
egui::StrokeKind::Middle,
);
let (flow_min, flow_max) = get_flow_bounds(node_lookup);
if flow_min.x > flow_max.x || flow_min.y > flow_max.y {
return Some(MinimapInfo {
mm_rect,
flow_min,
flow_max,
mm_scale: 1.0,
origin: mm_rect.center(),
});
}
let flow_w = (flow_max.x - flow_min.x).max(1.0);
let flow_h = (flow_max.y - flow_min.y).max(1.0);
let inner = mm_rect.shrink(MINIMAP_PADDING);
let scale_x = inner.width() / flow_w;
let scale_y = inner.height() / flow_h;
let mm_scale = scale_x.min(scale_y);
let graph_screen_w = flow_w * mm_scale;
let graph_screen_h = flow_h * mm_scale;
let origin = egui::pos2(
inner.min.x + (inner.width() - graph_screen_w) / 2.0,
inner.min.y + (inner.height() - graph_screen_h) / 2.0,
);
let flow_to_mm = |fp: egui::Pos2| -> egui::Pos2 {
egui::pos2(
origin.x + (fp.x - flow_min.x) * mm_scale,
origin.y + (fp.y - flow_min.y) * mm_scale,
)
};
for node in node_lookup.values() {
if node.node.hidden {
continue;
}
let fp = node.internals.position_absolute;
let fw = node.width();
let fh = node.height();
let mm_min = flow_to_mm(fp);
let mm_max = flow_to_mm(egui::pos2(fp.x + fw, fp.y + fh));
let node_rect = egui::Rect::from_min_max(mm_min, mm_max);
let node_rect = node_rect.intersect(inner);
if node_rect.width() <= 0.0 || node_rect.height() <= 0.0 {
continue;
}
let color = if node.node.selected {
config.node_selected_border_color
} else {
egui::Color32::from_rgb(180, 180, 200)
};
painter.rect_filled(node_rect, 1.0, color);
}
let vp = viewport;
let canvas_flow_min = egui::pos2(
(canvas_rect.min.x - vp.x) / vp.zoom,
(canvas_rect.min.y - vp.y) / vp.zoom,
);
let canvas_flow_max = egui::pos2(
(canvas_rect.max.x - vp.x) / vp.zoom,
(canvas_rect.max.y - vp.y) / vp.zoom,
);
let vp_mm_min = flow_to_mm(canvas_flow_min);
let vp_mm_max = flow_to_mm(canvas_flow_max);
let vp_rect = egui::Rect::from_min_max(vp_mm_min, vp_mm_max);
let vp_rect_clamped = vp_rect.intersect(mm_rect.expand(2.0));
painter.rect_filled(
vp_rect_clamped,
2.0,
egui::Color32::from_rgba_unmultiplied(59, 130, 246, 35),
);
painter.rect_stroke(
vp_rect_clamped,
2.0,
egui::Stroke::new(
1.0,
egui::Color32::from_rgba_unmultiplied(59, 130, 246, 200),
),
egui::StrokeKind::Middle,
);
Some(MinimapInfo {
mm_rect,
flow_min,
flow_max,
mm_scale,
origin,
})
}
#[derive(Clone, Copy, Debug)]
pub struct MinimapInfo {
pub mm_rect: egui::Rect,
pub flow_min: egui::Pos2,
pub flow_max: egui::Pos2,
pub mm_scale: f32,
pub origin: egui::Pos2,
}
impl MinimapInfo {
pub fn screen_to_flow(&self, screen_pos: egui::Pos2) -> Option<egui::Pos2> {
if !self.mm_rect.contains(screen_pos) {
return None;
}
if self.mm_scale <= 0.0 {
return None;
}
let flow_x = (screen_pos.x - self.origin.x) / self.mm_scale + self.flow_min.x;
let flow_y = (screen_pos.y - self.origin.y) / self.mm_scale + self.flow_min.y;
Some(egui::pos2(flow_x, flow_y))
}
}
fn get_flow_bounds<D>(node_lookup: &HashMap<NodeId, InternalNode<D>>) -> (egui::Pos2, egui::Pos2) {
let mut min_x = f32::INFINITY;
let mut min_y = f32::INFINITY;
let mut max_x = f32::NEG_INFINITY;
let mut max_y = f32::NEG_INFINITY;
for node in node_lookup.values() {
if node.node.hidden {
continue;
}
let fp = node.internals.position_absolute;
let fw = node.width();
let fh = node.height();
min_x = min_x.min(fp.x);
min_y = min_y.min(fp.y);
max_x = max_x.max(fp.x + fw);
max_y = max_y.max(fp.y + fh);
}
(egui::pos2(min_x, min_y), egui::pos2(max_x, max_y))
}