Files
worldcoder/src/template.rs
T

376 lines
12 KiB
Rust

use core::fmt;
use egui::{RichText, ScrollArea};
use serde::{Deserialize, Serialize};
use crate::{PROJECT_FOLDER, RightPanelContent, error::Error, object::ObjectInstance};
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub enum FieldType {
Image,
SingleLine,
MultiLine,
Date,
Number,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct FieldDefinition {
pub name: String,
pub field_type: FieldType,
pub required: bool,
pub description: Option<String>,
}
#[derive(Serialize, Deserialize)]
pub struct Template {
pub name: String,
pub id: String,
pub description: Option<String>,
pub fields: Vec<FieldDefinition>,
#[serde(skip)]
pub saved: bool,
#[serde(skip)]
pub error: Option<Error>,
}
impl fmt::Debug for Template {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Template")
.field("name", &self.name)
.field("id", &self.id)
.field("description", &self.description)
.field("fields", &self.fields)
.field("saved", &self.saved)
.finish()
}
}
impl Clone for Template {
fn clone(&self) -> Self {
Self {
name: self.name.clone(),
id: self.id.clone(),
description: self.description.clone(),
fields: self.fields.clone(),
saved: self.saved,
error: None,
}
}
}
impl Default for Template {
fn default() -> Self {
Self {
name: "New Template".to_string(),
id: uuid::Uuid::new_v4().to_string(),
description: Some(String::from("Placeholder description")),
fields: Vec::new(),
saved: false,
error: None,
}
}
}
impl Template {
pub fn load(id: &str) -> Result<Self, Box<dyn std::error::Error>> {
let path = PROJECT_FOLDER.join("templates").join(format!("{id}.json"));
let content = std::fs::read_to_string(&path)?;
let mut template: Self = serde_json::from_str(&content)?;
template.saved = true;
Ok(template)
}
pub fn save(&mut self) -> Result<(), Box<dyn std::error::Error>> {
let path = PROJECT_FOLDER
.join("templates")
.join(format!("{}.json", &self.id));
let content = serde_json::to_string_pretty(self)?;
std::fs::write(path, content)?;
self.saved = true;
Ok(())
}
pub fn ui(
&mut self,
ui: &mut egui::Ui,
new_instance: &mut RightPanelContent,
new_field_name: &mut String,
new_field_type: &mut FieldType,
new_field_required: &mut bool,
new_field_description: &mut String,
) {
if let Some(error) = &mut self.error {
error.show(ui);
}
if ui.input(|i| i.key_pressed(egui::Key::S) && i.modifiers.ctrl) {
if let Err(e) = self.save() {
eprintln!("Failed to save: {e}");
}
}
ScrollArea::vertical().show(ui, |ui| {
ui.vertical(|ui| {
ui.group(|ui| {
ui.horizontal(|ui| {
if self.saved {
ui.label(RichText::new("✓ Saved").color(egui::Color32::GREEN));
} else {
ui.label(RichText::new("* Unsaved").color(egui::Color32::YELLOW));
}
ui.label(format!("id: {}", self.id));
});
});
// Save/Cancel buttons
ui.horizontal(|ui| {
if ui.button("Save Template").clicked() {
if let Err(e) = self.save() {
eprintln!("Failed to save: {e}");
}
}
if ui.button("Cancel").clicked() {
// load default state
*self = Self::load(&self.id).unwrap();
}
if ui.button("Create New Instance").clicked() {
if self.saved {
*new_instance = RightPanelContent::Object {
object: Box::new(ObjectInstance::new(self)),
};
} else {
self.error = Some(Error::new(
"You must save the template before creating a new instance!"
.to_string(),
));
}
}
});
self.editor_ui(
ui,
new_field_name,
new_field_type,
new_field_required,
new_field_description,
);
});
});
}
pub fn editor_ui(
&mut self,
ui: &mut egui::Ui,
new_field_name: &mut String,
new_field_type: &mut FieldType,
new_field_required: &mut bool,
new_field_description: &mut String,
) {
egui::Grid::new("template_grid")
.num_columns(2)
.striped(true)
.show(ui, |ui| {
ui.label("Template Name:");
if ui
.add(egui::TextEdit::singleline(&mut self.name).frame(false))
.changed()
{
self.saved = false;
}
ui.end_row();
ui.label("Description:");
if ui
.add(
egui::TextEdit::multiline(self.description.get_or_insert_with(String::new))
.desired_rows(1)
.frame(false),
)
.changed()
{
self.saved = false;
}
ui.end_row();
});
ui.separator();
egui::CollapsingHeader::new("Name").show(ui, |ui: &mut egui::Ui| {
egui::Grid::new("field_grid")
.num_columns(2)
.striped(true)
.show(ui, |ui| {
ui.label("Name:");
ui.label("Name");
ui.end_row();
ui.label("Type:");
ui.label("SingleLine");
ui.end_row();
ui.label("Required");
ui.label("");
ui.end_row();
ui.label("Description:");
ui.label("Object name");
ui.end_row();
});
});
// List of fields
let mut to_remove = None;
for (i, field) in self.fields.iter_mut().enumerate() {
let id = ui.make_persistent_id(format!("field_{i}"));
egui::collapsing_header::CollapsingState::load_with_default_open(ui.ctx(), id, true)
.show_header(ui, |ui| {
if ui.button("").clicked() {
to_remove = Some(i);
}
ui.label(field.name.clone());
})
.body(|ui| {
ui.separator();
egui::Grid::new("field_grid")
.num_columns(2)
.striped(true)
.show(ui, |ui| {
ui.label("Name:");
if ui.text_edit_singleline(&mut field.name).changed() {
self.saved = false;
}
ui.end_row();
ui.label("Type:");
egui::ComboBox::from_id_salt(format!("field_type_{i}"))
.selected_text(format!("{:?}", field.field_type))
.show_ui(ui, |ui| {
for variant in [
FieldType::SingleLine,
FieldType::MultiLine,
FieldType::Number,
FieldType::Date,
FieldType::Image,
] {
if ui
.selectable_value(
&mut field.field_type,
variant.clone(),
format!("{variant:?}"),
)
.changed()
{
self.saved = false;
}
}
});
ui.end_row();
ui.label("Required:");
if ui.checkbox(&mut field.required, "").clicked() {
self.saved = false;
}
ui.end_row();
ui.label("Description:");
if ui
.text_edit_singleline(
field.description.get_or_insert_with(String::new),
)
.changed()
{
self.saved = false;
}
ui.end_row();
ui.separator();
});
});
}
// Remove field if needed
if let Some(index) = to_remove {
self.fields.remove(index);
self.saved = false;
}
// Add new field
ui.separator();
ui.heading("Add New Field");
ui.horizontal(|ui| {
egui::Grid::new("field_grid")
.num_columns(2)
.striped(true)
.show(ui, |ui| {
ui.label("Name:");
ui.text_edit_singleline(new_field_name);
ui.end_row();
ui.label("Type:");
egui::ComboBox::from_id_salt("new_field_type")
.selected_text(format!("{new_field_type:?}"))
.show_ui(ui, |ui| {
for variant in [
FieldType::SingleLine,
FieldType::MultiLine,
FieldType::Number,
FieldType::Date,
FieldType::Image,
] {
ui.selectable_value(
new_field_type,
variant.clone(),
format!("{variant:?}"),
);
}
});
ui.end_row();
ui.label("Required:");
ui.checkbox(new_field_required, "");
ui.end_row();
ui.label("Description:");
ui.text_edit_singleline(new_field_description);
ui.end_row();
if ui.button("Add Field").clicked() && !new_field_name.is_empty() {
self.fields.push(FieldDefinition {
name: new_field_name.clone(),
field_type: new_field_type.clone(),
required: *new_field_required,
description: if new_field_description.is_empty() {
None
} else {
Some(new_field_description.clone())
},
});
self.saved = false;
// Reset new field form
new_field_name.clear();
*new_field_required = false;
new_field_description.clear();
}
});
});
}
}
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct FieldValue {
pub value: String,
#[serde(skip)]
pub modified: bool,
}