use std::path::PathBuf;
use bevy_egui::egui;
use crate::external_editor;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum CodePreviewTab {
#[default]
Entities,
Enums,
Stubs,
Behaviors,
}
impl CodePreviewTab {
pub fn all() -> &'static [CodePreviewTab] {
&[
CodePreviewTab::Entities,
CodePreviewTab::Enums,
CodePreviewTab::Stubs,
CodePreviewTab::Behaviors,
]
}
pub fn label(&self) -> &'static str {
match self {
CodePreviewTab::Entities => "Entities",
CodePreviewTab::Enums => "Enums",
CodePreviewTab::Stubs => "Stubs",
CodePreviewTab::Behaviors => "Behaviors",
}
}
}
#[derive(Default)]
pub struct CodePreviewDialogState {
pub open: bool,
pub selected_tab: CodePreviewTab,
pub entities_code: String,
pub enums_code: String,
pub stubs_code: String,
pub behaviors_code: String,
pub error: Option<String>,
pub scroll_positions: [f32; 4],
pub output_path: Option<PathBuf>,
pub vscode_path: Option<String>,
pub vscode_available: bool,
}
impl CodePreviewDialogState {
pub fn set_content(
&mut self,
entities: String,
enums: String,
stubs: String,
behaviors: String,
) {
self.entities_code = entities;
self.enums_code = enums;
self.stubs_code = stubs;
self.behaviors_code = behaviors;
self.error = None;
}
pub fn set_error(&mut self, error: String) {
self.error = Some(error);
}
fn current_code(&self) -> &str {
match self.selected_tab {
CodePreviewTab::Entities => &self.entities_code,
CodePreviewTab::Enums => &self.enums_code,
CodePreviewTab::Stubs => &self.stubs_code,
CodePreviewTab::Behaviors => &self.behaviors_code,
}
}
pub fn current_file_path(&self) -> Option<PathBuf> {
self.output_path.as_ref().map(|base| {
let filename = match self.selected_tab {
CodePreviewTab::Entities => "entities.rs",
CodePreviewTab::Enums => "enums.rs",
CodePreviewTab::Stubs => "stubs.rs",
CodePreviewTab::Behaviors => "behaviors.rs",
};
base.join(filename)
})
}
}
pub fn render_code_preview_dialog(ctx: &egui::Context, state: &mut CodePreviewDialogState) -> bool {
let mut close_requested = false;
if !state.open {
return false;
}
egui::Area::new(egui::Id::new("code_preview_modal_overlay"))
.fixed_pos(egui::pos2(0.0, 0.0))
.order(egui::Order::Middle)
.show(ctx, |ui| {
let screen_rect = ctx.input(|i| {
i.raw.screen_rect.unwrap_or(egui::Rect::from_min_size(
egui::Pos2::ZERO,
egui::vec2(1920.0, 1080.0),
))
});
let response = ui.allocate_response(screen_rect.size(), egui::Sense::click_and_drag());
ui.painter()
.rect_filled(screen_rect, 0.0, egui::Color32::from_black_alpha(128));
response.context_menu(|_| {});
});
egui::Window::new("Generated Code Preview")
.collapsible(false)
.resizable(true)
.default_width(700.0)
.default_height(500.0)
.min_width(400.0)
.min_height(300.0)
.anchor(egui::Align2::CENTER_CENTER, [0.0, 0.0])
.order(egui::Order::Foreground)
.show(ctx, |ui| {
if let Some(ref error) = state.error {
ui.colored_label(egui::Color32::RED, format!("Error: {}", error));
ui.separator();
}
ui.horizontal(|ui| {
for tab in CodePreviewTab::all() {
let is_selected = state.selected_tab == *tab;
if ui.selectable_label(is_selected, tab.label()).clicked() {
state.selected_tab = *tab;
}
}
});
ui.separator();
let code = state.current_code().to_string();
let line_count = code.lines().count();
let available_height = ui.available_height() - 30.0;
egui::ScrollArea::vertical()
.id_salt("code_preview_scroll")
.max_height(available_height.max(100.0))
.show(ui, |ui| {
let available_width = ui.available_width();
ui.add(
egui::TextEdit::multiline(&mut code.clone())
.font(egui::TextStyle::Monospace)
.code_editor()
.desired_width(available_width)
.interactive(false),
);
});
ui.separator();
ui.horizontal(|ui| {
ui.label(format!("{} lines", line_count));
ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| {
if ui.button("Close").clicked() {
state.open = false;
close_requested = true;
}
if ui.button("Copy to Clipboard").clicked() {
ctx.copy_text(code.clone());
}
if state.vscode_available {
if let Some(file_path) = state.current_file_path() {
let file_exists = file_path.exists();
ui.add_enabled_ui(file_exists, |ui| {
if ui.button("Open in VS Code").clicked() {
let _ = external_editor::open_in_vscode_with_custom_path(
&file_path,
state.vscode_path.as_deref(),
);
}
});
if !file_exists {
ui.label("(Generate first to open file)");
}
}
}
});
});
});
close_requested
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_code_preview_tab() {
assert_eq!(CodePreviewTab::all().len(), 4);
assert_eq!(CodePreviewTab::Entities.label(), "Entities");
}
#[test]
fn test_code_preview_state() {
let mut state = CodePreviewDialogState::default();
state.set_content(
"entities".to_string(),
"enums".to_string(),
"stubs".to_string(),
"behaviors".to_string(),
);
assert_eq!(state.current_code(), "entities");
state.selected_tab = CodePreviewTab::Enums;
assert_eq!(state.current_code(), "enums");
}
}