use crate::store::ModelStore;
use brep_render::engine_state::EngineState;
pub type EngineFactory = Box<dyn Fn() -> EngineState>;
pub const EMPTY_DOCUMENT: &str = r#"{"expressions":"","configurator":{},"features":[]}"#;
pub struct Document {
pub engine: EngineState,
name: Option<String>,
saved_signature: Option<String>,
dirty_marker: bool,
marker_generation: Option<u64>,
id: u64,
}
impl Document {
pub fn new(engine: EngineState) -> Self {
let saved_signature = Some(engine.history_request_json());
Self {
engine,
name: None,
saved_signature,
dirty_marker: false,
marker_generation: None,
id: next_document_id(),
}
}
pub fn recovered(engine: EngineState, name: Option<String>) -> Self {
Self {
engine,
name,
saved_signature: None,
dirty_marker: true,
marker_generation: None,
id: next_document_id(),
}
}
pub fn name(&self) -> Option<&str> {
self.name.as_deref()
}
pub fn set_name(&mut self, name: Option<String>) {
self.name = name;
}
pub fn title(&self) -> String {
match &self.name {
Some(name) => crate::store::model_display_name(name),
None => "untitled".to_string(),
}
}
pub fn mark_clean(&mut self) {
self.saved_signature = Some(self.engine.history_request_json());
self.dirty_marker = false;
self.marker_generation = Some(self.engine.applied_generation());
}
pub fn is_dirty(&self) -> bool {
match &self.saved_signature {
Some(saved) => *saved != self.engine.history_request_json(),
None => true,
}
}
pub fn refresh_dirty_marker(&mut self) {
let generation = self.engine.applied_generation();
if self.marker_generation == Some(generation) {
return;
}
self.marker_generation = Some(generation);
self.dirty_marker = self.is_dirty();
}
pub fn dirty_marker(&self) -> bool {
self.dirty_marker
}
pub fn id(&self) -> u64 {
self.id
}
}
pub struct Documents {
open: Vec<Document>,
active: usize,
new_engine: EngineFactory,
}
impl Documents {
pub fn new(new_engine: EngineFactory) -> Self {
let first = Document::new(new_engine());
Self {
open: vec![first],
active: 0,
new_engine,
}
}
pub fn spawn_engine(&self) -> EngineState {
let mut engine = (self.new_engine)();
carry_settings(&self.engine().settings_json(), &mut engine, true);
engine
}
pub fn engine(&self) -> &EngineState {
&self.open[self.active].engine
}
pub fn engine_mut(&mut self) -> &mut EngineState {
&mut self.open[self.active].engine
}
pub fn active(&self) -> &Document {
&self.open[self.active]
}
pub fn active_mut(&mut self) -> &mut Document {
&mut self.open[self.active]
}
pub fn active_index(&self) -> usize {
self.active
}
pub fn active_id(&self) -> u64 {
self.open[self.active].id
}
pub fn len(&self) -> usize {
self.open.len()
}
pub fn get(&self, index: usize) -> Option<&Document> {
self.open.get(index)
}
pub fn get_mut(&mut self, index: usize) -> Option<&mut Document> {
self.open.get_mut(index)
}
pub fn iter(&self) -> std::slice::Iter<'_, Document> {
self.open.iter()
}
pub fn iter_mut(&mut self) -> std::slice::IterMut<'_, Document> {
self.open.iter_mut()
}
pub fn index_of(&self, name: &str) -> Option<usize> {
self.open
.iter()
.position(|doc| doc.name.as_deref() == Some(name))
}
pub fn focus_named(&mut self, name: &str) -> bool {
match self.index_of(name) {
Some(index) => {
self.activate(index);
true
}
None => false,
}
}
pub fn open_document(&mut self, doc: Document) -> usize {
self.open.push(doc);
let index = self.open.len() - 1;
self.activate(index);
index
}
pub fn activate(&mut self, index: usize) {
if index >= self.open.len() || index == self.active {
return;
}
let settings = self.open[self.active].engine.settings_json();
self.active = index;
carry_settings(&settings, &mut self.open[index].engine, false);
}
pub fn close(&mut self, index: usize) {
if index >= self.open.len() {
return;
}
let settings = self.open[self.active].engine.settings_json();
let closed_active = index == self.active;
self.open.remove(index);
let refilled = self.open.is_empty();
if refilled {
self.open.push(Document::new((self.new_engine)()));
self.active = 0;
} else if closed_active {
self.active = index.min(self.open.len() - 1);
} else if index < self.active {
self.active -= 1;
}
if closed_active {
let active = self.active;
carry_settings(&settings, &mut self.open[active].engine, refilled);
}
}
pub fn refresh_dirty_markers(&mut self) {
for doc in &mut self.open {
doc.refresh_dirty_marker();
}
}
pub fn session_json(&self) -> String {
let open: Vec<&str> = self.open.iter().filter_map(Document::name).collect();
serde_json::json!({
"open": open,
"active": self.active().name(),
})
.to_string()
}
pub fn restore_session(&mut self, store: &dyn ModelStore, json: &str) -> usize {
let Ok(session) = serde_json::from_str::<serde_json::Value>(json) else {
return 0;
};
let names: Vec<String> = session["open"]
.as_array()
.map(|list| {
list.iter()
.filter_map(|name| name.as_str().map(str::to_string))
.collect()
})
.unwrap_or_default();
let wanted_active = session["active"].as_str().unwrap_or_default().to_string();
let mut restored: Vec<Document> = Vec::new();
let mut active = 0usize;
for name in names {
let Some(contents) = store.read(&name) else {
continue;
};
let mut engine = (self.new_engine)();
if engine.load_model_and_fit(&contents).is_err() {
continue;
}
let mut doc = Document::new(engine);
doc.set_name(Some(name.clone()));
if name == wanted_active {
active = restored.len();
}
restored.push(doc);
}
if restored.is_empty() {
return 0;
}
self.open = restored;
self.active = active;
self.open.len()
}
}
fn carry_settings(settings_json: &str, target: &mut EngineState, with_workbench: bool) {
let Ok(mut settings) = serde_json::from_str::<serde_json::Value>(settings_json) else {
return;
};
if !with_workbench {
if let Some(object) = settings.as_object_mut() {
object.remove("workbench");
}
}
let _ = target.apply_settings_json(&settings.to_string());
}
fn next_document_id() -> u64 {
use std::sync::atomic::{AtomicU64, Ordering};
static NEXT: AtomicU64 = AtomicU64::new(1);
NEXT.fetch_add(1, Ordering::Relaxed)
}