initial commit - v0.1.0
This commit is contained in:
+323
@@ -0,0 +1,323 @@
|
||||
use std::{fs, path::PathBuf, sync::LazyLock};
|
||||
|
||||
use egui::{RichText, ScrollArea};
|
||||
|
||||
mod main_editor;
|
||||
mod object;
|
||||
mod template;
|
||||
use egui_file::DialogType;
|
||||
use object::ObjectInstance;
|
||||
use template::{FieldType, Template};
|
||||
|
||||
static PROJECT_FOLDER: LazyLock<PathBuf> = LazyLock::new(|| {
|
||||
let mut path = std::env::current_dir().unwrap();
|
||||
path.push("project");
|
||||
path
|
||||
});
|
||||
|
||||
fn main() {
|
||||
let app = Interface::new();
|
||||
|
||||
let options = eframe::NativeOptions {
|
||||
viewport: egui::ViewportBuilder::default().with_inner_size([800.0, 600.0]),
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
let _ = eframe::run_native("Code Editor", options, Box::new(|_cc| Ok(Box::new(app))));
|
||||
}
|
||||
|
||||
pub struct Interface {
|
||||
text: String,
|
||||
dialog: Option<egui_file::FileDialog>,
|
||||
right_panel_content: RightPanelContent,
|
||||
editor: main_editor::MainEditor,
|
||||
}
|
||||
|
||||
impl Interface {
|
||||
#[must_use]
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
text: "".to_string(),
|
||||
dialog: None,
|
||||
right_panel_content: RightPanelContent::None,
|
||||
editor: main_editor::MainEditor::new(),
|
||||
}
|
||||
}
|
||||
|
||||
fn show_directory(
|
||||
&mut self,
|
||||
ui: &mut egui::Ui,
|
||||
path: &PathBuf,
|
||||
depth: usize,
|
||||
) -> std::io::Result<()> {
|
||||
if !path.exists() {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let indent = " ".repeat(depth);
|
||||
let entries = fs::read_dir(path)?;
|
||||
let mut dirs = Vec::new();
|
||||
let mut files = Vec::new();
|
||||
|
||||
for entry in entries {
|
||||
let entry = entry?;
|
||||
let path = entry.path();
|
||||
if path.is_dir() {
|
||||
dirs.push(path);
|
||||
} else if let Some(ext) = path.extension() {
|
||||
if ext == "json" {
|
||||
// Only show JSON files
|
||||
files.push(path);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Sort directories and files alphabetically
|
||||
dirs.sort();
|
||||
files.sort();
|
||||
|
||||
// Show directories first
|
||||
for dir in dirs {
|
||||
let dir_name = dir
|
||||
.file_name()
|
||||
.and_then(|n| n.to_str())
|
||||
.unwrap_or("<invalid>")
|
||||
.to_owned()
|
||||
+ "/";
|
||||
|
||||
if egui::CollapsingHeader::new(dir_name.clone())
|
||||
.default_open(depth < 1)
|
||||
.show(ui, |ui| self.show_directory(ui, &dir, depth + 1))
|
||||
.body_returned
|
||||
.is_none()
|
||||
{
|
||||
ui.label(RichText::new(format!(
|
||||
"{indent}❌ Error reading {dir_name}"
|
||||
)));
|
||||
}
|
||||
}
|
||||
|
||||
// Then show files
|
||||
for file in files {
|
||||
if let Some(file_name) = file.file_name().and_then(|n| n.to_str()) {
|
||||
let response = ui.horizontal(|ui| {
|
||||
ui.label(" ".repeat(depth));
|
||||
ui.selectable_label(false, file_name)
|
||||
});
|
||||
|
||||
if response.inner.clicked() {
|
||||
if let Ok(instance) = ObjectInstance::load(file.clone()) {
|
||||
self.right_panel_content =
|
||||
RightPanelContent::instance(Some(file.clone()), Some(instance));
|
||||
} else if let Ok(template) = Template::load(file.clone()) {
|
||||
self.right_panel_content =
|
||||
RightPanelContent::template(Some(file.clone()), Some(template));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for Interface {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
// /home/zxq5/Pictures/logos and pfps/YT profile picture background.png
|
||||
|
||||
impl eframe::App for Interface {
|
||||
fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) {
|
||||
egui_extras::install_image_loaders(ctx);
|
||||
|
||||
{
|
||||
let mut visuals = egui::Visuals::dark();
|
||||
visuals.window_fill = egui::Color32::from_rgb(20, 20, 20);
|
||||
visuals.panel_fill = egui::Color32::from_rgb(20, 20, 20);
|
||||
ctx.set_visuals(visuals);
|
||||
}
|
||||
|
||||
if let Some(dialog) = &mut self.dialog {
|
||||
if dialog.show(ctx).selected() {
|
||||
if let Some(path) = dialog.path() {
|
||||
if dialog.dialog_type() == DialogType::OpenFile {
|
||||
// Handle file dialog for loading templates/instances
|
||||
if let Ok(instance) = ObjectInstance::load(path.to_path_buf()) {
|
||||
// Instance
|
||||
self.right_panel_content = RightPanelContent::instance(
|
||||
Some(path.to_path_buf()),
|
||||
Some(instance),
|
||||
);
|
||||
self.dialog = None;
|
||||
} else if let Ok(template) = Template::load(path.to_path_buf()) {
|
||||
// Template
|
||||
self.right_panel_content = RightPanelContent::template(
|
||||
Some(path.to_path_buf()),
|
||||
Some(template),
|
||||
);
|
||||
self.dialog = None;
|
||||
}
|
||||
} else if dialog.dialog_type() == DialogType::SaveFile {
|
||||
// Handle file dialog for saving templates/instances
|
||||
|
||||
if let RightPanelContent::Template { template, .. } =
|
||||
&mut self.right_panel_content
|
||||
{
|
||||
// set the save location and save
|
||||
template.path = Some(path.to_path_buf());
|
||||
if template.save().is_err() {
|
||||
eprintln!("Failed to save template");
|
||||
} else {
|
||||
self.dialog = None;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Top bar with actions
|
||||
egui::TopBottomPanel::top("top").show(ctx, |ui| {
|
||||
ui.horizontal(|ui| {
|
||||
// create new template
|
||||
if ui.button("New Template").clicked() {
|
||||
self.right_panel_content = RightPanelContent::Template {
|
||||
template: Box::new(Template::default()),
|
||||
new_field_name: String::new(),
|
||||
new_field_type: FieldType::SingleLine,
|
||||
new_field_required: false,
|
||||
new_field_description: String::new(),
|
||||
};
|
||||
}
|
||||
|
||||
// load instance or template from file
|
||||
if ui.button("Load Template/Instance").clicked() {
|
||||
self.dialog = Some(egui_file::FileDialog::open_file(Some(
|
||||
PROJECT_FOLDER.clone(),
|
||||
)));
|
||||
self.dialog.as_mut().unwrap().open();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// Left panel - File browser
|
||||
egui::SidePanel::left("file_browser")
|
||||
.resizable(true)
|
||||
.default_width(250.0)
|
||||
.show(ctx, |ui| {
|
||||
ui.heading("Project Files");
|
||||
ui.separator();
|
||||
|
||||
ScrollArea::vertical().show(ui, |ui| {
|
||||
if let Err(e) = self.show_directory(ui, &PROJECT_FOLDER, 0) {
|
||||
ui.label(
|
||||
RichText::new(format!("Error reading directory: {e}"))
|
||||
.color(egui::Color32::RED),
|
||||
);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// Main content area
|
||||
egui::SidePanel::right("templates").show(ctx, |ui| {
|
||||
let mut new_instance: RightPanelContent = RightPanelContent::None;
|
||||
|
||||
match &mut self.right_panel_content {
|
||||
// an instance of a template
|
||||
RightPanelContent::Instance { instance, path: _ } => {
|
||||
// load template from path
|
||||
|
||||
let mut temp_path = instance.template_path.clone();
|
||||
|
||||
// check if path is relative
|
||||
if !temp_path.is_absolute() {
|
||||
temp_path = PROJECT_FOLDER.join(temp_path);
|
||||
}
|
||||
|
||||
let template = Template::load(temp_path).unwrap();
|
||||
ScrollArea::vertical().show(ui, |ui| {
|
||||
instance.ui(ui, &template);
|
||||
});
|
||||
}
|
||||
|
||||
// an editable template
|
||||
RightPanelContent::Template {
|
||||
template,
|
||||
new_field_name,
|
||||
new_field_type,
|
||||
new_field_required,
|
||||
new_field_description,
|
||||
} => template.ui(
|
||||
ui,
|
||||
&mut new_instance,
|
||||
new_field_name,
|
||||
new_field_type,
|
||||
new_field_required,
|
||||
new_field_description,
|
||||
),
|
||||
|
||||
RightPanelContent::None => {
|
||||
ui.centered_and_justified(|ui| {
|
||||
ui.label("No template loaded to edit.");
|
||||
if ui.button("Back").clicked() {
|
||||
self.right_panel_content = RightPanelContent::None;
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
if let RightPanelContent::None = new_instance {
|
||||
} else {
|
||||
self.right_panel_content = new_instance;
|
||||
}
|
||||
});
|
||||
|
||||
self.editor.ui(ctx);
|
||||
}
|
||||
}
|
||||
|
||||
pub enum RightPanelContent {
|
||||
Template {
|
||||
template: Box<Template>,
|
||||
// fields to edit
|
||||
new_field_name: String,
|
||||
new_field_type: FieldType,
|
||||
new_field_required: bool,
|
||||
new_field_description: String,
|
||||
},
|
||||
Instance {
|
||||
instance: Box<ObjectInstance>,
|
||||
path: Option<PathBuf>,
|
||||
},
|
||||
None,
|
||||
}
|
||||
|
||||
impl RightPanelContent {
|
||||
fn template(path: Option<PathBuf>, template: Option<Template>) -> Self {
|
||||
Self::Template {
|
||||
template: Box::new(template.unwrap_or_default()),
|
||||
new_field_name: String::new(),
|
||||
new_field_type: FieldType::SingleLine,
|
||||
new_field_required: false,
|
||||
new_field_description: String::new(),
|
||||
}
|
||||
}
|
||||
|
||||
fn instance(path: Option<PathBuf>, instance: Option<ObjectInstance>) -> Self {
|
||||
Self::Instance {
|
||||
instance: Box::new(instance.unwrap_or_default()),
|
||||
path,
|
||||
}
|
||||
}
|
||||
|
||||
fn is_saved(&self) -> bool {
|
||||
match self {
|
||||
RightPanelContent::Instance { instance, path: _ } => instance.saved,
|
||||
RightPanelContent::Template { template, .. } => template.path.is_some(),
|
||||
RightPanelContent::None => false,
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user