fplanner/src/app.rs

285 lines
10 KiB
Rust

pub mod components;
pub mod draw;
mod structure;
use serde_lexpr::{from_str, print, to_string_custom};
use chrono::prelude::Utc;
use chrono_humanize::HumanTime;
use fake::{Fake, Faker};
use std::collections::HashMap;
use std::path::PathBuf;
use uuid::Uuid;
/// We derive Deserialize/Serialize so we can persist app state on shutdown.
#[derive(serde::Deserialize, serde::Serialize)]
#[serde(default)] // if we add new fields, give them default values when deserializing old state
pub struct PlannerApp {
// Example stuff:
label: String,
#[serde(skip)]
update_view: bool,
view_layers: Vec<structure::Layer>,
new_index: usize,
new_label: String,
save_path: Option<PathBuf>,
current_project: structure::Project,
}
impl Default for PlannerApp {
fn default() -> Self {
Self {
// Example stuff:
label: "Helllllo World!".to_owned(),
view_layers: vec![],
update_view: true,
new_index: 0,
new_label: String::default(),
save_path: None,
current_project: structure::Project::default(),
}
}
}
impl PlannerApp {
/// Called once before the first frame.
pub fn new(cc: &eframe::CreationContext<'_>) -> Self {
// This is also where you can customize the look and feel of egui using
// `cc.egui_ctx.set_visuals` and `cc.egui_ctx.set_fonts`.
// Load previous app state (if any).
// Note that you must enable the `persistence` feature for this to work.
if let Some(storage) = cc.storage {
return eframe::get_value(storage, eframe::APP_KEY).unwrap_or_default();
}
Default::default()
}
}
impl eframe::App for PlannerApp {
/// Called by the frame work to save state before shutdown.
fn save(&mut self, storage: &mut dyn eframe::Storage) {
eframe::set_value(storage, eframe::APP_KEY, self);
}
/// Called each time the UI needs repainting, which may be many times per second.
/// Put your widgets into a `SidePanel`, `TopPanel`, `CentralPanel`, `Window` or `Area`.
fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) {
let Self {
label,
view_layers,
update_view,
new_index,
new_label,
save_path,
current_project,
} = self;
// Examples of how to create different panels and windows.
// Pick whichever suits you.
// Tip: a good default choice is to just keep the `CentralPanel`.
// For inspiration and more examples, go to https://emilk.github.io/egui
#[cfg(not(target_arch = "wasm32"))] // no File->Quit on web pages!
egui::TopBottomPanel::top("top_panel").show(ctx, |ui| {
// The top panel is often a good place for a menu bar:
egui::menu::bar(ui, |ui| {
ui.menu_button("File", |ui| {
if ui.button("Load").clicked() {
if let Some(path) = rfd::FileDialog::new()
.add_filter("plan", &["splan"])
.set_title("Load Plan")
.pick_file()
{
let read = std::fs::read_to_string(path.display().to_string()).unwrap();
println!("{}", &read);
let sexpr: structure::Project = from_str(&read).unwrap();
println!("{:?}", &sexpr);
*current_project = sexpr;
}
}
if ui.button("Save As").clicked() {
let options = print::Options::default();
let sexpr = to_string_custom(
&current_project,
options
.with_keyword_syntax(print::KeywordSyntax::ColonPrefix)
.with_nil_syntax(print::NilSyntax::Token)
.with_bool_syntax(print::BoolSyntax::Token)
.with_vector_syntax(print::VectorSyntax::Octothorpe),
)
.unwrap();
if let Some(path) = rfd::FileDialog::new()
.add_filter("plan", &["splan"])
.set_file_name("untitled.splan")
.set_title("Save Plan")
.save_file()
{
println!("{}", &path.display().to_string());
std::fs::write(path.display().to_string(), sexpr).unwrap();
*save_path = Some(path.to_owned());
}
}
if ui.button("Quit").clicked() {
_frame.close();
}
});
});
});
egui::SidePanel::right("right_panel").show(ctx, |ui| {
ui.heading("Components");
for i in view_layers.clone() {
if i.visible {
for (id, comp) in &i.components {
ui.label(format!("{}: {}", &id.to_string(), &comp.label));
}
}
}
});
egui::SidePanel::left("side_panel").show(ctx, |ui| {
ui.heading("Project");
ui.horizontal(|ui| {
ui.label("Project Name: ");
ui.text_edit_singleline(&mut current_project.name);
});
ui.collapsing("Metadata", |ui| {
ui.label(current_project.id.to_string());
ui.label(format!(
"Created {}",
HumanTime::from(current_project.created)
));
match current_project.modified {
Some(d) => {
ui.label(format!("Modified {}", HumanTime::from(d)));
}
None => {}
}
if ui.button("Modify").clicked() {
current_project.modified = Some(Utc::now());
}
ui.separator();
ui.collapsing("License", |ui| {
ui.vertical(|ui| {
ui.radio_value(
&mut current_project.license,
structure::License::MIT,
"MIT",
);
ui.radio_value(
&mut current_project.license,
structure::License::GPL,
"GPL",
);
ui.radio_value(
&mut current_project.license,
structure::License::AGPL,
"AGPL",
);
ui.radio_value(
&mut current_project.license,
structure::License::CNPL,
"CNPLv7+",
);
});
});
});
ui.collapsing("Layers", |ui| {
ui.label("New Layer:");
ui.horizontal(|ui| {
ui.label("Label: ");
ui.text_edit_singleline(new_label);
});
ui.add(egui::Slider::new(new_index, 0..=10).text("Z-Index"));
if ui.button("Create").clicked() {
let id = Uuid::new_v4();
let new_layer = structure::Layer {
zindex: new_index.clone(),
label: new_label.clone(),
visible: true,
components: HashMap::from([(
id.clone(),
components::Component {
id: id.clone(),
label: Faker.fake::<String>(),
description: Faker.fake::<String>(),
c_type: components::ComponentType::Door,
material: components::Material::Metal,
shapes: vec![],
},
)]),
};
current_project
.layers
.insert(new_index.clone(), new_layer.clone());
current_project.modified = Some(Utc::now());
*update_view = true;
}
if *update_view {
*view_layers = vec![];
for (index, layer) in &current_project.layers {
view_layers.push(layer.clone());
}
*update_view = false;
}
ui.separator();
for x in view_layers {
ui.checkbox(&mut x.visible, format!("{}: {}", &x.zindex, &x.label));
}
});
ui.with_layout(egui::Layout::bottom_up(egui::Align::LEFT), |ui| {
ui.horizontal(|ui| {
ui.spacing_mut().item_spacing.x = 0.0;
ui.label("powered by ");
ui.hyperlink_to("egui", "https://github.com/emilk/egui");
ui.label(" and ");
ui.hyperlink_to(
"eframe",
"https://github.com/emilk/egui/tree/master/crates/eframe",
);
ui.label(".");
});
});
});
egui::CentralPanel::default().show(ctx, |ui| {
// The central panel the region left after adding TopPanel's and SidePanel's
ui.heading("eframe template");
ui.hyperlink("https://github.com/emilk/eframe_template");
ui.add(egui::github_link_file!(
"https://github.com/emilk/eframe_template/blob/master/",
"Source code."
));
egui::warn_if_debug_build(ui);
});
if false {
egui::Window::new("Window").show(ctx, |ui| {
ui.label("Windows can be moved by dragging them.");
ui.label("They are automatically sized based on contents.");
ui.label("You can turn on resizing and scrolling if you like.");
ui.label("You would normally choose either panels OR windows.");
});
}
}
}