use eframe::egui;
use crate::gui::{GuiApp, LogLevel, Status};
pub fn draw(ui: &mut egui::Ui, app: &mut GuiApp) {
ui.vertical(|ui| {
ui.heading("🚚 VRP Solver — Multi-Vehicle Route Planning");
ui.separator();
ui.group(|ui| {
ui.heading("📍 Coordinates CSV (required)");
ui.label("Columns: lat,lon [, label, demand, type]");
if let Some(ref path) = app.vrp_csv_file {
ui.colored_label(egui::Color32::from_rgb(80, 220, 80), path);
} else {
ui.colored_label(egui::Color32::from_rgb(220, 200, 60), "(not set — browse for a CSV)");
}
ui.horizontal(|ui| {
if ui.button("Browse…").clicked() {
if let Some(path) = rfd::FileDialog::new()
.add_filter("CSV", &["csv"])
.pick_file()
{
let path_str = path.display().to_string();
app.vrp_csv_file = Some(path_str.clone());
app.log(LogLevel::Success, format!("Coordinates CSV set: {}", path_str));
}
}
if ui.button("✕ Clear").clicked() {
app.vrp_csv_file = None;
}
});
});
ui.group(|ui| {
ui.heading("🗺️ Road Network (.rmp) — optional");
ui.label("Used for map preview; not required for solving.");
if let Some(ref path) = app.vrp_input_file {
ui.colored_label(egui::Color32::from_rgb(80, 220, 80), path);
} else {
ui.colored_label(egui::Color32::from_rgb(140, 140, 140), "(not set)");
}
ui.horizontal(|ui| {
if ui.button("Browse…").clicked() {
if let Some(path) = rfd::FileDialog::new()
.add_filter("RMP", &["rmp"])
.pick_file()
{
let path_str = path.display().to_string();
app.vrp_input_file = Some(path_str.clone());
app.load_rmp(&path);
app.log(LogLevel::Success, format!("Road network loaded: {}", path_str));
}
}
if ui.button("✕ Clear").clicked() {
app.vrp_input_file = None;
}
});
});
ui.group(|ui| {
ui.heading("Output Directory");
ui.horizontal(|ui| {
ui.text_edit_singleline(&mut app.vrp_output_dir);
if ui.button("Browse…").clicked() {
if let Some(path) = rfd::FileDialog::new().pick_folder() {
app.vrp_output_dir = path.display().to_string();
app.log(LogLevel::Success, format!("Output dir: {}", app.vrp_output_dir));
}
}
});
});
ui.group(|ui| {
ui.heading("Vehicles & Algorithm");
ui.horizontal(|ui| {
ui.add(egui::DragValue::new(&mut app.vrp_vehicles).speed(1).range(1..=100));
ui.label("Vehicles");
});
let algo_options = ["greedy", "savings", "local_search", "simulated_annealing"];
egui::ComboBox::from_id_salt("vrp_algo_select")
.selected_text(app.vrp_algo.clone())
.show_ui(ui, |ui| {
for opt in &algo_options {
ui.selectable_value(&mut app.vrp_algo, opt.to_string(), *opt);
}
});
});
ui.group(|ui| {
ui.heading("Capacity");
let mut cap_val = app.vrp_capacity.unwrap_or(0.0);
ui.horizontal(|ui| {
ui.add(egui::DragValue::new(&mut cap_val).speed(1.0).range(0.0..=10000.0));
ui.label("Vehicle capacity");
if cap_val > 0.0 {
app.vrp_capacity = Some(cap_val);
} else {
app.vrp_capacity = None;
}
});
if app.vrp_capacity.is_none() {
ui.colored_label(egui::Color32::from_rgb(140, 140, 140), "(unlimited)");
}
});
ui.group(|ui| {
super::status_label(ui, &app.vrp_status);
let can_run = app.vrp_csv_file.is_some();
if ui.add_enabled(can_run, egui::Button::new("🚀 Run VRP Solver")).clicked() {
run_vrp(app);
}
});
if !app.map_nodes.is_empty() {
ui.group(|ui| {
ui.heading("Map Preview");
ui.horizontal(|ui| {
ui.label("Edge width:");
ui.add(egui::Slider::new(&mut app.map_edge_width, 0.5..=5.0));
});
super::draw_map_canvas(ui, app);
});
}
});
}
fn run_vrp(app: &mut GuiApp) {
let csv_path = match &app.vrp_csv_file {
Some(p) => p.clone(),
None => {
app.log(LogLevel::Warn, "Load a coordinates CSV first");
return;
}
};
app.vrp_status = Status::Running { progress: 0, message: "Solving VRP…".to_string() };
app.log(LogLevel::Info, format!("Starting VRP with {} vehicles, algo={}", app.vrp_vehicles, app.vrp_algo));
let (stops, _depot_indices) = match crate::core::vrp::utils::parse_csv_stops(&csv_path) {
Ok(result) => result,
Err(e) => {
app.vrp_status = Status::Error(e.clone());
app.log(LogLevel::Error, format!("CSV parse failed: {}", e));
return;
}
};
app.log(LogLevel::Info, format!("Loaded {} stops from CSV ({} depots)", stops.len(), _depot_indices.len()));
let matrix = crate::core::vrp::utils::build_haversine_matrix(&stops, 40.0);
let input = crate::core::vrp::types::VRPSolverInput {
locations: stops,
num_vehicles: app.vrp_vehicles,
vehicle_capacity: app.vrp_capacity.unwrap_or(100.0),
objective: crate::core::vrp::types::VrpObjective::MinDistance,
matrix: Some(matrix),
service_time_secs: None,
use_time_windows: false,
window_open: None,
window_close: None,
};
let rt = tokio::runtime::Builder::new_current_thread().enable_all().build().unwrap();
match rt.block_on(crate::core::vrp::registry::solve_with(&app.vrp_algo, &input)) {
Ok(output) => {
app.vrp_status = Status::Done(format!("{} km, {} stops", output.total_distance_km, output.stops.len()));
app.log(LogLevel::Success, format!(
"VRP complete: {} km total distance, {} stops",
output.total_distance_km, output.stops.len()
));
}
Err(e) => {
app.vrp_status = Status::Error(e.clone());
app.log(LogLevel::Error, format!("VRP failed: {}", e));
}
}
}