use concinnity_cook::authoring::refs::retarget_references;
use concinnity_cook::authoring::world::find_entry;
use concinnity_core::components::ShaderStage;
use std::path::{Path, PathBuf};
use crate::editor::hook::EditorHook;
use crate::editor::modal::{Action, Button};
use crate::editor::notify;
use crate::editor::panels::shader_edit::{self, ShaderEdit};
use crate::editor::panels::shader_source::{self, Leave, SourceKey, same_file};
use crate::editor::panels::shader_templates;
const SHADER: &str = "Shader";
pub(super) fn button(label: &str, danger: bool, action: Action) -> Button {
Button {
label: label.to_string(),
danger,
action,
}
}
pub(super) fn cancel() -> Button {
button("Cancel", false, Action::Dismiss)
}
impl EditorHook {
fn write_starter(&self, path: &Path, text: &str) -> bool {
let written = path
.parent()
.map_or(Ok(()), std::fs::create_dir_all)
.and_then(|()| std::fs::write(path, text));
if let Err(e) = &written {
self.notifier.error_with(
&format!("Could not write {}: {e}", path.display()),
notify::Action::OpenConsole,
);
}
written.is_ok()
}
fn retarget_shader(&mut self, from: &str, to: Option<&str>) -> usize {
self.entries
.iter_mut()
.map(|e| retarget_references(e, SHADER, from, to))
.sum()
}
pub(in crate::editor::hook) fn confirm_delete_shader(&mut self, i: usize) {
let shaders = self.declared_shaders();
let Some(shader) = shaders.get(i) else {
return;
};
let check = shader_edit::files_check(&shader_edit::files_of(&shaders, i));
let delete = Action::DeleteShader(shader.name.clone());
self.open_modal_with_check(
&shader_edit::delete_message(&shaders, i),
vec![cancel(), button("Delete", true, delete)],
check,
);
}
pub(in crate::editor::hook) fn delete_shader(&mut self, name: &str, files: bool) {
self.edit_shader(ShaderEdit::Delete {
name: name.to_string(),
files,
});
}
pub(in crate::editor::hook) fn remove_vertex_file(&mut self, name: &str) {
self.edit_shader(ShaderEdit::RemoveVertex(name.to_string()));
}
pub(in crate::editor::hook) fn edit_shader(&mut self, edit: ShaderEdit) {
match &self.shaders.source {
Some(src) if edit.closes(&src.key) => self.leave_shader_source(Leave::Edit(edit)),
_ => self.apply_shader_edit(edit),
}
}
pub(in crate::editor::hook) fn apply_shader_edit(&mut self, edit: ShaderEdit) {
match edit {
ShaderEdit::RemoveVertex(name) => self.apply_remove_vertex(&name),
ShaderEdit::Delete { name, files } => self.apply_delete(&name, files),
ShaderEdit::DeleteField { path, files } => self.apply_delete_field(&path, files),
}
}
fn apply_delete(&mut self, name: &str, files: bool) {
let shaders = self.declared_shaders();
let Some(i) = shaders.iter().position(|s| s.name == name) else {
return;
};
let own = match files {
true => shader_edit::files_of(&shaders, i).own,
false => Vec::new(),
};
let Some(idx) = find_entry(&self.entries, name) else {
return;
};
let before = self.entries.len();
self.retarget_shader(name, None);
self.remove_entry_at(idx);
if self.entries.len() == before {
return;
}
let (mut deleted, mut failed) = (Vec::new(), Vec::new());
for path in own {
match std::fs::remove_file(&path) {
Ok(()) => deleted.push(path),
Err(e) => failed.push(format!("{path}: {e}")),
}
}
let message = shader_edit::deleted_message(name, &deleted, &failed);
match failed.is_empty() {
true => self.notifier.success(&message),
false => self
.notifier
.error_with(&message, notify::Action::OpenConsole),
}
}
fn apply_remove_vertex(&mut self, name: &str) {
let Some(idx) = find_entry(&self.entries, name) else {
return;
};
let stage = shader_source::stage_name(ShaderStage::Vertex);
let Some(removed) = self.entries[idx]
.get_mut("args")
.and_then(|a| a.as_object_mut())
.and_then(|a| a.remove(stage))
else {
return;
};
if self.commit() {
let declared = removed.as_str().unwrap_or_default();
self.notifier
.success(&shader_edit::removed_vertex_message(name, declared));
}
}
pub(in crate::editor::hook) fn add_vertex_file(&mut self, i: usize) {
let Some(shader) = self.declared_shaders().into_iter().nth(i) else {
return;
};
let Some(idx) = find_entry(&self.entries, &shader.name) else {
return;
};
if shader.file(ShaderStage::Vertex).is_some() {
return;
}
let (path, declared) = new_shader_file(&shader_edit::vertex_stem(&shader.name));
if !self.entries.is_read_only(idx)
&& !self.write_starter(&path, shader_templates::VERTEX[0].text)
{
return;
}
let stage = shader_source::stage_name(ShaderStage::Vertex);
if let Some(args) = self.entries[idx]
.get_mut("args")
.and_then(|a| a.as_object_mut())
{
args.insert(stage.to_string(), serde_json::Value::String(declared));
}
if !self.commit() {
return;
}
self.notifier
.success(&format!("Added a vertex file to '{}'", shader.name));
self.open_shader_file(SourceKey::shader(&shader.name, ShaderStage::Vertex));
}
pub(in crate::editor::hook) fn follow_shader_source(&mut self) {
let Some(src) = &self.shaders.source else {
return;
};
let SourceKey::Shader { name: open, stage } = src.key.clone() else {
return;
};
let path = src.path.clone();
let shaders = self.declared_shaders();
if shaders.iter().any(|s| s.name == open) {
return;
}
let renamed = shaders
.into_iter()
.find(|s| s.file(stage).is_some_and(|f| same_file(&f.path, &path)));
if let (Some(shader), Some(src)) = (renamed, self.shaders.source.as_mut()) {
src.key = SourceKey::Shader {
name: shader.name,
stage,
};
}
}
}
pub(in crate::editor::hook) fn new_shader_file(stem: &str) -> (PathBuf, String) {
let dir = crate::project::assets_dir().unwrap_or_else(|| PathBuf::from("assets"));
let path = shader_source::starter_path(&dir, stem, Path::exists);
let root = std::env::current_dir().unwrap_or_default();
let declared = shader_source::declared_form(&path, &root);
(path, declared)
}