use super::{
confirm::{StatusDecision, ask_write_confirm, confirm_keys, decide_write_status},
picker::{PickKind, pick_file, suggested_file_name},
save_finish::record_last_saved_path,
save_helpers::{
degenerate_marker_header, snapshot_custom_materials, stamp_source_entry_footnote,
},
solve::resolve_solved_then,
};
use crate::{
EditorModel, MainWindow,
bridge::render_thread::RenderContext,
gui::{editor::state::EditorState, show_toast},
};
use slint::ComponentHandle;
use std::{
cell::RefCell,
rc::Rc,
sync::{Arc, Mutex},
};
pub(in crate::gui::editor) fn setup_export_cutting_sheet_callback(
ui: &MainWindow,
state: &Rc<RefCell<EditorState>>,
render_ctx: &Arc<Mutex<RenderContext>>,
) {
let state = Rc::clone(state);
let render_ctx = Arc::clone(render_ctx);
let ui_weak = ui.as_weak();
ui.global::<EditorModel>().on_export_cutting_sheet(move || {
let Some(ui) = ui_weak.upgrade() else {
return;
};
crate::gui::editor::stall_guard::stall_guard("export_cutting_sheet", || {
let (design, base_name) = {
let st = state.borrow();
(st.design.clone(), suggested_file_name(&st))
};
let render_ctx = Arc::clone(&render_ctx);
resolve_solved_then(&ui, Arc::new(design), move |ui, design, solved_result| {
crate::gui::editor::stall_guard::stall_guard("export_cutting_sheet_solved", || {
let solved = match solved_result {
Ok(solved) => solved,
Err(missing) => {
show_toast(
ui,
&format!("Cannot build a cutting sheet: {missing}."),
"error",
);
return;
}
};
let custom_materials = snapshot_custom_materials(&render_ctx);
let base = base_name.trim_end_matches(".asc").to_string();
pick_file(
ui,
PickKind::SaveCuttingSheet {
default_name: format!("{base}_cutting_sheet.html"),
},
move |ui, dest_path| {
let Some(dest_path) = dest_path else {
return;
};
let ui_weak = ui.as_weak();
std::thread::spawn(move || {
let result =
crate::gui::editor::cut_sheet::write_cutting_sheet_html(
&design,
&solved,
&dest_path,
&custom_materials,
);
let _ = ui_weak.upgrade_in_event_loop(move |ui| match result {
Ok(()) => show_toast(
&ui,
&format!("Wrote cutting sheet to {}", dest_path.display()),
"success",
),
Err(message) => show_toast(&ui, &message, "error"),
});
});
},
);
});
});
});
});
}
pub(in crate::gui::editor) fn setup_export_diagram_callback(
ui: &MainWindow,
state: &Rc<RefCell<EditorState>>,
) {
let state = Rc::clone(state);
let ui_weak = ui.as_weak();
ui.global::<EditorModel>().on_export_diagram(move || {
let Some(ui) = ui_weak.upgrade() else {
return;
};
crate::gui::editor::stall_guard::stall_guard("export_diagram", || {
let (design, base_name) = {
let st = state.borrow();
(st.design.clone(), suggested_file_name(&st))
};
resolve_solved_then(&ui, Arc::new(design), move |ui, design, solved_result| {
crate::gui::editor::stall_guard::stall_guard("export_diagram_solved", || {
let solved = match solved_result {
Ok(solved) => solved,
Err(missing) => {
show_toast(
ui,
&format!("Cannot draw this design: {missing}."),
"error",
);
return;
}
};
let base = base_name.trim_end_matches(".asc").to_string();
pick_file(
ui,
PickKind::SaveDiagram {
default_name: format!("{base}_diagram.png"),
},
move |ui, dest_path| {
let Some(dest_path) = dest_path else {
return;
};
let ui_weak = ui.as_weak();
std::thread::spawn(move || {
let result = crate::gui::editor::cut_sheet::write_diagram_png(
&design, &solved, &dest_path,
);
let _ = ui_weak.upgrade_in_event_loop(move |ui| match result {
Ok(()) => show_toast(
&ui,
&format!("Wrote diagram to {}", dest_path.display()),
"success",
),
Err(message) => show_toast(&ui, &message, "error"),
});
});
},
);
});
});
});
});
}
pub(in crate::gui::editor) fn setup_export_asc_callback(
ui: &MainWindow,
state: &Rc<RefCell<EditorState>>,
render_ctx: &Arc<Mutex<RenderContext>>,
) {
let state = Rc::clone(state);
let render_ctx = Arc::clone(render_ctx);
let ui_weak = ui.as_weak();
ui.global::<EditorModel>().on_export_asc(move || {
let Some(ui) = ui_weak.upgrade() else {
return;
};
crate::gui::editor::stall_guard::stall_guard("export_asc", || {
let custom_materials = snapshot_custom_materials(&render_ctx);
let (mut schedule, default_file_name, design, used_placeholder) = {
let st = state.borrow();
let mut schedule = match st.design.to_asc_schedule_with(&custom_materials) {
Ok(schedule) => schedule,
Err(missing) => {
show_toast(&ui, &format!("Cannot export: {missing}."), "error");
return;
}
};
stamp_source_entry_footnote(&mut schedule.footnotes, st.source_entry_id);
(
schedule,
suggested_file_name(&st),
st.design.clone(),
st.used_placeholder,
)
};
if used_placeholder {
indicatrix_formats::asc::mark_reconstructed(
&mut schedule,
"angle-table reconstruction, no attached .asc",
);
}
resolve_solved_then(&ui, Arc::new(design), move |ui, design, solved_result| {
crate::gui::editor::stall_guard::stall_guard("export_asc_status_resolved", || {
match decide_write_status(
&design,
solved_result.as_deref().map_err(String::as_str),
) {
StatusDecision::Fine => finish_export_asc(ui, &schedule, default_file_name),
StatusDecision::NeedsConfirm(message) => {
let heading_message = format!(
"{message}\n\nExport anyway? The written file will note this in \
its own header."
);
ask_write_confirm(
ui,
"This design is not a closed solid",
heading_message,
"Export Anyway",
Some(confirm_keys::NOT_CLOSED_SOLID),
move |ui| {
let mut schedule = schedule;
if let Some(header) =
degenerate_marker_header(&schedule.headers, &message)
{
schedule.headers.insert(0, header);
}
finish_export_asc(ui, &schedule, default_file_name);
},
);
}
}
});
});
});
});
}
fn finish_export_asc(
ui: &MainWindow,
schedule: &indicatrix_formats::asc::AscSchedule,
default_file_name: String,
) {
let text = indicatrix_formats::asc::to_asc_string(schedule);
pick_file(
ui,
PickKind::SaveAsc {
default_name: default_file_name,
},
move |ui, dest_path| {
let Some(dest_path) = dest_path else {
return;
};
let ui_weak = ui.as_weak();
std::thread::spawn(move || {
if let Some(parent) = dest_path.parent() {
let _ = std::fs::create_dir_all(parent);
}
let result = std::fs::write(&dest_path, &text)
.map_err(|e| format!("Failed to write {}: {e}", dest_path.display()));
let _ = ui_weak.upgrade_in_event_loop(move |ui| match result {
Ok(()) => {
record_last_saved_path(&ui, &dest_path);
show_toast(
&ui,
&format!("Exported edited schedule to {}", dest_path.display()),
"success",
);
}
Err(message) => show_toast(&ui, &message, "error"),
});
});
},
);
}