fix(sheetset): read drawings off the UI thread

The wizard's folder scan, Import Layout as Sheet, the template layout
lists and the drawing cache behind Model Views, Place on Sheet, callout /
label blocks and block previews parsed whole DWGs on the UI thread. They
now go through the shared `background_task` worker: a cache miss reads the
drawing and handles the message that needed it again. Block previews lend
the cached drawing to the preview scene instead of cloning it.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
Hakan SevenandClaude Opus 5.5 committed 2026-10-06 09:33:04 +03:00
1 parent 40847d72a1
commit 3a5c1dbb7d
4 files changed
+305 -153

No files matched your search

+255 -139
View File
@@ -4,8 +4,8 @@
use crate::app::{Message, OpenCADStudio};
use crate::ui::window::sheet_set::{
BlockList, Category, FieldId, Form, FormKind, FoundLayout, ImportRow, MenuAction, Placing, PropRow, Properties,
RowKey, SelectBlock, SheetSetMsg, SheetStatus, SsDialog, TemplatePick, ViewAction, Wizard, WizardMsg,
BlockList, Category, FieldId, Form, FormKind, FoundLayout, ImportRead, ImportRow, MenuAction, Placing, PropRow,
Properties, RowKey, SelectBlock, SheetSetMsg, SheetStatus, SsDialog, TemplatePick, ViewAction, Wizard, WizardMsg,
};
use crate::command::{CadCommand, CmdOption, CmdResult};
use codec::entities::Entity as _;
@@ -300,13 +300,40 @@ fn prompts_for_template(db: &SheetSetDatabase, component: &str) -> bool {
false
}
/// A drawing's paper-space layouts and sheet link, for Import (run on a
/// worker thread).
fn read_import(path: &Path) -> ImportRead {
let doc = crate::io::load_file(path).map_err(|e| e.to_string())?;
Ok((paper_layouts(&doc), doc.sheet_set_data()))
}
/// Read the drawings picked for Import off the UI thread.
fn read_imports(paths: Vec<PathBuf>, new_form: bool) -> Task<Message> {
crate::app::update::background_task(
crate::t!("Import Layout as Sheet"),
move || {
paths
.into_iter()
.map(|p| {
let read = read_import(&p);
(p, read)
})
.collect()
},
move |read| Message::SheetSet(SheetSetMsg::ImportRead(new_form, read)),
)
}
/// The layouts of a drawing that can become sheets, each with the sheet set
/// it already belongs to: the drawing's sheet link, or this set.
fn import_rows(path: &Path, db: &SheetSetDatabase) -> Result<Vec<ImportRow>, String> {
let doc = crate::io::load_file(path).map_err(|e| e.to_string())?;
fn import_rows(
path: &Path,
layouts: Vec<(String, String)>,
link: Option<&SheetSetData>,
db: &SheetSetDatabase,
) -> Vec<ImportRow> {
let drawing = path.to_string_lossy().to_string();
let link = doc.sheet_set_data();
Ok(paper_layouts(&doc)
layouts
.into_iter()
.map(|(layout, handle)| {
let mine = db.sheet_for(&drawing, Some(&layout)).is_some_and(|s| {
@@ -315,14 +342,43 @@ fn import_rows(path: &Path, db: &SheetSetDatabase) -> Result<Vec<ImportRow>, Str
// A layout of this set is taken; one the drawing's sheet link
// names for another set is only warned about (still importable).
let warn = !mine
&& link.as_ref().is_some_and(|l| {
&& link.is_some_and(|l| {
l.layout_name.eq_ignore_ascii_case(&layout)
&& !l.sheet_set_file_name.is_empty()
&& db.path.as_deref().is_none_or(|p| ss::path_key(p) != ss::path_key(&l.sheet_set_file_name))
});
ImportRow { drawing: drawing.clone(), layout, handle, on: !mine, taken: mine, warn }
})
.collect())
.collect()
}
/// The layouts of the drawings in a wizard folder (run on a worker thread).
fn scan_folder(path: &Path) -> Vec<FoundLayout> {
let folder = path.to_string_lossy().to_string();
let mut files: Vec<PathBuf> = std::fs::read_dir(path)
.map(|rd| {
rd.filter_map(|e| e.ok().map(|e| e.path()))
.filter(|p| p.extension().is_some_and(|x| x.eq_ignore_ascii_case("dwg")))
.collect()
})
.unwrap_or_default();
files.sort();
let mut found = Vec::new();
for file in files {
let Ok(doc) = crate::io::load_file(&file) else {
continue;
};
for (layout, handle) in paper_layouts(&doc) {
found.push(FoundLayout {
folder: folder.clone(),
file: file.to_string_lossy().to_string(),
layout,
handle,
on: true,
});
}
}
found
}
/// Rename & Renumber: layout and file names follow the options.
@@ -752,7 +808,17 @@ impl OpenCADStudio {
|p| Message::SheetSet(SheetSetMsg::ImportAdd(p)),
)
}
SheetSetMsg::ImportAdd(paths) => self.import_add(paths),
SheetSetMsg::ImportAdd(paths) if !paths.is_empty() => return read_imports(paths, false),
SheetSetMsg::ImportAdd(_) => {}
SheetSetMsg::ImportRead(new_form, read) => self.import_read(new_form, read),
SheetSetMsg::FolderScanned(path, found) => self.folder_scanned(path, found),
SheetSetMsg::DrawingRead(path, read, then) => match read.0 {
Ok((modified, doc)) => {
self.sheet_set.drawings.insert(&path, modified, doc);
return self.on_sheet_set(*then);
}
Err(e) => self.command_line.push_error(&format!("{path}: {e}")),
},
SheetSetMsg::Previous => self.rename_step(false),
SheetSetMsg::Next => self.rename_step(true),
SheetSetMsg::ViewsByCategory(v) => self.sheet_set.by_category = v,
@@ -778,10 +844,14 @@ impl OpenCADStudio {
_ => None,
};
// A whole drawing taken as a block shows its model space.
let preview = target.and_then(|(file, name)| {
let whole = self.sheet_set.drawings.get(&file).is_some_and(|d| d.block_records.get(&name).is_none());
self.block_preview(&file, if whole { "" } else { &name })
});
let mut preview = None;
if let Some((file, name)) = target {
let whole = match self.drawing_or_read(&file, SheetSetMsg::BlockListSelect(k)) {
Ok(doc) => doc.block_records.get(&name).is_none(),
Err(task) => return task,
};
preview = self.block_preview(&file, if whole { "" } else { &name });
}
if let Some(SsDialog::BlockList(b)) = self.sheet_set.dialog.as_mut() {
b.selected = Some(k);
b.preview = preview;
@@ -820,7 +890,7 @@ impl OpenCADStudio {
)
}
SheetSetMsg::SelectBlockPicked(None) => {}
SheetSetMsg::SelectBlockPicked(Some(path)) => self.select_block_file(path),
SheetSetMsg::SelectBlockPicked(Some(path)) => return self.select_block_file(path),
SheetSetMsg::SelectBlockWhole(v) => {
let file = match self.sheet_set.dialog.as_ref() {
Some(SsDialog::SelectBlock(b)) => b.file.clone(),
@@ -847,17 +917,17 @@ impl OpenCADStudio {
}
}
SheetSetMsg::ToggleDrawing(path) => {
let key = ss::path_key(&path);
if !self.sheet_set.open_drawings.remove(&key) {
self.load_model_views(&path);
self.sheet_set.open_drawings.insert(key);
if !self.sheet_set.open_drawings.remove(&ss::path_key(&path)) {
return self.on_sheet_set(SheetSetMsg::SeeViews(path));
}
}
SheetSetMsg::SeeViews(path) => {
self.load_model_views(&path);
if let Err(task) = self.load_model_views(&path) {
return task;
}
self.sheet_set.open_drawings.insert(ss::path_key(&path));
}
SheetSetMsg::PlaceOnSheet(path, view) => self.start_place_view(path, view),
SheetSetMsg::PlaceOnSheet(path, view) => return self.start_place_view(path, view),
SheetSetMsg::ViewMenu(id, action) => {
self.sheet_set.submenu = None;
return self.view_menu(id, action);
@@ -911,10 +981,10 @@ impl OpenCADStudio {
}
}
SheetSetMsg::OpenPicked(None) | SheetSetMsg::ImportPicked(None) | SheetSetMsg::TemplatePicked(None) => {}
SheetSetMsg::ImportPicked(Some(path)) => self.import_layout_form(path),
SheetSetMsg::FolderPicked(field, Some(path)) => self.dialog_folder(field, path),
SheetSetMsg::ImportPicked(Some(path)) => return read_imports(vec![path], true),
SheetSetMsg::FolderPicked(field, Some(path)) => return self.dialog_folder(field, path),
SheetSetMsg::FolderPicked(_, None) => {}
SheetSetMsg::TemplatePicked(Some(path)) => self.properties_template(path),
SheetSetMsg::TemplatePicked(Some(path)) => return self.properties_template(path),
#[cfg(not(target_arch = "wasm32"))]
SheetSetMsg::Browse(field) => {
return Task::perform(
@@ -1116,7 +1186,14 @@ impl OpenCADStudio {
// A subset that prompts for its template asks for it first.
if prompts_for_template(db, &id) {
let file = template.map(|t| t.file_name).unwrap_or_default();
let layouts = crate::io::load_file(Path::new(&file)).map(|d| paper_layouts(&d).into_iter().map(|(n, _)| n).collect()).unwrap_or_default();
let layouts = if Path::new(&file).is_file() {
match self.drawing_or_read(&file, SheetSetMsg::Menu(id.clone(), MenuAction::NewSheet)) {
Ok(doc) => paper_layouts(&doc).into_iter().map(|(n, _)| n).collect(),
Err(task) => return task,
}
} else {
Vec::new()
};
self.sheet_set.dialog = Some(SsDialog::Template(TemplatePick { form, file, layouts, layout: 0 }));
} else {
self.sheet_set.dialog = Some(SsDialog::Form(form));
@@ -1352,46 +1429,42 @@ impl OpenCADStudio {
}
}
fn dialog_folder(&mut self, field: FieldId, path: PathBuf) {
let folder = path.to_string_lossy().to_string();
fn dialog_folder(&mut self, field: FieldId, path: PathBuf) -> Task<Message> {
if field == FieldId::AddFolder {
self.wizard_add_folder(path);
return;
return self.wizard_add_folder(path);
}
self.dialog_input(field, folder);
self.dialog_input(field, path.to_string_lossy().to_string());
Task::none()
}
fn wizard_add_folder(&mut self, path: PathBuf) {
/// The wizard's Browse for existing drawings: the folder's drawings are
/// read off the UI thread.
fn wizard_add_folder(&mut self, path: PathBuf) -> Task<Message> {
let Some(SsDialog::Wizard(w)) = self.sheet_set.dialog.as_ref() else {
return Task::none();
};
if w.folders.contains(&path.to_string_lossy().to_string()) {
return Task::none();
}
crate::app::update::background_task(
crate::t!("Sheet Set Manager"),
move || {
let found = scan_folder(&path);
(path, found)
},
|(path, found)| Message::SheetSet(SheetSetMsg::FolderScanned(path, found)),
)
}
fn folder_scanned(&mut self, path: PathBuf, found: Vec<FoundLayout>) {
let Some(SsDialog::Wizard(w)) = self.sheet_set.dialog.as_mut() else {
return;
};
let folder = path.to_string_lossy().to_string();
if w.folders.contains(&folder) {
return;
if !w.folders.contains(&folder) {
w.layouts.extend(found);
w.folders.push(folder);
}
let mut files: Vec<PathBuf> = std::fs::read_dir(&path)
.map(|rd| {
rd.filter_map(|e| e.ok().map(|e| e.path()))
.filter(|p| p.extension().is_some_and(|x| x.eq_ignore_ascii_case("dwg")))
.collect()
})
.unwrap_or_default();
files.sort();
for file in files {
let Ok(doc) = crate::io::load_file(&file) else {
continue;
};
for (layout, handle) in paper_layouts(&doc) {
w.layouts.push(FoundLayout {
folder: folder.clone(),
file: file.to_string_lossy().to_string(),
layout,
handle,
on: true,
});
}
}
w.folders.push(folder);
}
fn on_wizard(&mut self, m: WizardMsg) {
@@ -1429,18 +1502,42 @@ impl OpenCADStudio {
}
}
fn import_layout_form(&mut self, path: PathBuf) {
let (Some(set), Some(parent)) = (self.sheet_set.current, self.sheet_set_import_parent.take()) else {
/// The drawings picked for Import, read: a new Import dialog for the
/// subset whose menu asked, or the open dialog's rows replaced.
fn import_read(&mut self, new_form: bool, read: Vec<(PathBuf, ImportRead)>) {
let set = match self.sheet_set.dialog.as_ref() {
_ if new_form => self.sheet_set.current,
Some(SsDialog::Form(f)) => Some(f.set),
_ => None,
};
let Some(db) = set.and_then(|k| self.sheet_set.sets.get(k)) else { return };
let mut rows = Vec::new();
let mut errors = Vec::new();
for (path, r) in read {
match r {
Ok((layouts, link)) => rows.extend(import_rows(&path, layouts, link.as_ref(), db)),
Err(e) => errors.push(format!("{}: {e}", path.display())),
}
}
let failed = !errors.is_empty();
for e in errors {
self.command_line.push_error(&e);
}
if !new_form {
// The drawings picked replace the list.
if let Some(SsDialog::Form(f)) = self.sheet_set.dialog.as_mut() {
if !rows.is_empty() {
f.rows = rows;
}
}
return;
}
let (Some(set), Some(parent)) = (set, self.sheet_set_import_parent.take()) else {
return;
};
let Some(db) = self.sheet_set.db() else { return };
let rows = match import_rows(&path, db) {
Ok(rows) => rows,
Err(e) => {
self.command_line.push_error(&format!("{}: {e}", path.display()));
return;
}
};
if failed {
return;
}
self.sheet_set.dialog = Some(SsDialog::Form(Form {
kind: FormKind::ImportLayout,
set,
@@ -1452,25 +1549,6 @@ impl OpenCADStudio {
self.active_modal = Some(crate::app::ModalKind::SheetSet);
}
/// Browse for Drawings of the open Import dialog: add their layouts.
fn import_add(&mut self, paths: Vec<PathBuf>) {
let Some(SsDialog::Form(f)) = self.sheet_set.dialog.as_ref() else { return };
let Some(db) = self.sheet_set.sets.get(f.set) else { return };
let mut added = Vec::new();
for p in paths {
match import_rows(&p, db) {
Ok(rows) => added.extend(rows),
Err(e) => self.command_line.push_error(&format!("{}: {e}", p.display())),
}
}
// The drawings picked replace the list.
if let Some(SsDialog::Form(f)) = self.sheet_set.dialog.as_mut() {
if !added.is_empty() {
f.rows = added;
}
}
}
/// Rename & Renumber: number and title into the set, and the layout and
/// drawing file renamed when they changed.
fn apply_rename(&mut self, f: &Form) -> Result<(), String> {
@@ -1658,16 +1736,38 @@ impl OpenCADStudio {
self.save_current_sheet_set();
}
/// A block preview (`name` empty: the drawing's model space), cached by
/// `file|name`; returns its key.
/// A drawing read and cached, or the task that reads it off the UI thread
/// and then handles `then` again.
fn drawing_or_read(
&mut self,
path: &str,
then: SheetSetMsg,
) -> Result<std::sync::Arc<codec::CadDocument>, Task<Message>> {
if let Some(doc) = self.sheet_set.drawings.get(path) {
return Ok(doc);
}
let path = path.to_string();
Err(crate::app::update::background_task(
crate::t!("Sheet Set Manager"),
{
let path = path.clone();
move || crate::ui::window::sheet_set::read_drawing(&path)
},
move |read| Message::SheetSet(SheetSetMsg::DrawingRead(path, read, Box::new(then))),
))
}
/// A block preview (`name` empty: the drawing's model space) of a cached
/// drawing, cached by `file|name`; returns its key.
fn block_preview(&mut self, file: &str, name: &str) -> Option<String> {
let key = format!("{}|{}", ss::path_key(file), name);
if self.sheet_set.previews.contains_key(&key) {
return Some(key);
}
let doc = self.sheet_set.drawings.get(file)?;
// The cached drawing is lent to a scene for tessellation, not copied.
let (modified, doc) = self.sheet_set.drawings.take(file)?;
let mut scene = crate::scene::Scene::new();
scene.document = (*doc).clone();
scene.document = std::sync::Arc::try_unwrap(doc).unwrap_or_else(|doc| (*doc).clone());
let wires = if name.is_empty() {
let model = scene.document.header.model_space_block_handle;
let entities: Vec<codec::EntityType> = scene
@@ -1682,6 +1782,8 @@ impl OpenCADStudio {
} else {
scene.block_preview_wires(name)
};
let doc = std::mem::take(&mut scene.document);
self.sheet_set.drawings.insert(file, modified, std::sync::Arc::new(doc));
self.sheet_set.previews.insert(key.clone(), wires);
Some(key)
}
@@ -1697,19 +1799,17 @@ impl OpenCADStudio {
Some((db.resolve_file(b), b.prop("Name").unwrap_or("").to_string()))
}
/// Named model views of a drawing (read once, when its row is opened).
fn load_model_views(&mut self, path: &str) {
/// Named model views of a drawing (read once, when its row is opened);
/// `Err` reads the drawing first.
fn load_model_views(&mut self, path: &str) -> Result<(), Task<Message>> {
let key = ss::path_key(path);
if self.sheet_set.model_views.contains_key(&key) {
return;
return Ok(());
}
let views = self
.sheet_set
.drawings
.get(path)
.map(|d| d.views.iter().filter(|v| !v.paper_space).map(|v| v.name.clone()).collect())
.unwrap_or_default();
let doc = self.drawing_or_read(path, SheetSetMsg::SeeViews(path.to_string()))?;
let views = doc.views.iter().filter(|v| !v.paper_space).map(|v| v.name.clone()).collect();
self.sheet_set.model_views.insert(key, views);
Ok(())
}
/// The current set's sheet shown by the active tab at its current layout.
@@ -1748,28 +1848,38 @@ impl OpenCADStudio {
}
/// Place on Sheet: only on a sheet of this set open at its layout.
fn start_place_view(&mut self, drawing: String, view: Option<String>) {
/// The model drawing and the set's view label block drawing are read
/// first.
fn start_place_view(&mut self, drawing: String, view: Option<String>) -> Task<Message> {
if self.tabs[self.active_tab].scene.current_layout.eq_ignore_ascii_case("Model") {
self.command_line
.push_error(crate::t!("Content cannot be placed into model space. Please switch to paper space and try again.").as_ref());
return;
self.command_line.push_error(
crate::t!("Content cannot be placed into model space. Please switch to paper space and try again.")
.as_ref(),
);
return Task::none();
}
let Some((set, sheet)) = self.current_sheet() else {
self.command_line
.push_error(crate::t!("Open a sheet of this sheet set at its layout to place a view on it.").as_ref());
return;
return Task::none();
};
let again = || SheetSetMsg::PlaceOnSheet(drawing.clone(), view.clone());
let label = self.sheet_set.db().and_then(|db| db.sheet_set().named("DefLabelBlk").map(|b| db.resolve_file(b)));
if let Some(label) = label.filter(|l| Path::new(l).is_file()) {
if let Err(task) = self.drawing_or_read(&label, again()) {
return task;
}
}
let size = match self.drawing_or_read(&drawing, again()) {
Ok(doc) => model_view(&doc, view.as_deref()).map(|v| (v.width, v.height)),
Err(task) => return task,
};
let size = self
.sheet_set
.drawings
.get(&drawing)
.and_then(|d| model_view(&d, view.as_deref()))
.map(|v| (v.width, v.height));
let Some(size) = size else {
self.command_line.push_error(crate::tf!("{}: no view to place.", drawing).as_ref());
return;
return Task::none();
};
self.start_placing(Placing::View { set, sheet, drawing, view }, size);
Task::none()
}
/// A sheet view's commands.
@@ -1812,17 +1922,22 @@ impl OpenCADStudio {
self.command_line
.push_error(crate::t!("Open the view's sheet at its layout to place a block on it.").as_ref());
}
ViewAction::Callout(block) => {
let Some(b) = db.find(&block) else { return Task::none() };
let (file, name) = (db.resolve_file(b), b.prop("Name").unwrap_or("").to_string());
self.start_placing(Placing::Block { set, sheet, view: id, file, name }, (0.0, 0.0));
}
ViewAction::Label => {
let Some(b) = db.sheet_set().named("DefLabelBlk") else {
self.command_line.push_error(crate::t!("The sheet set has no label block for views.").as_ref());
ViewAction::Callout(_) | ViewAction::Label => {
let b = match &action {
ViewAction::Callout(block) => db.find(block),
_ => db.sheet_set().named("DefLabelBlk"),
};
let Some(b) = b else {
if action == ViewAction::Label {
self.command_line.push_error(crate::t!("The sheet set has no label block for views.").as_ref());
}
return Task::none();
};
let (file, name) = (db.resolve_file(b), b.prop("Name").unwrap_or("").to_string());
// The block's drawing is read first.
if let Err(task) = self.drawing_or_read(&file, SheetSetMsg::ViewMenu(id.clone(), action)) {
return task;
}
self.start_placing(Placing::Block { set, sheet, view: id, file, name }, (0.0, 0.0));
}
}
@@ -1963,24 +2078,20 @@ impl OpenCADStudio {
}
/// Select Block: read a drawing's named blocks.
fn select_block_file(&mut self, path: PathBuf) {
fn select_block_file(&mut self, path: PathBuf) -> Task<Message> {
let file = path.to_string_lossy().to_string();
let names: Vec<(String, String, bool)> = match self.sheet_set.drawings.get(&file).ok_or_else(|| crate::t!("cannot be read").into_owned()) {
Ok(doc) => {
let mut n: Vec<(String, String, bool)> = doc
.block_records
.iter()
.filter(|b| !b.name.starts_with('*') && !b.is_layout())
.map(|b| (b.name.clone(), format!("{:X}", b.handle.value()), false))
.collect();
n.sort();
n
}
Err(e) => {
self.command_line.push_error(&format!("{}: {e}", path.display()));
return;
}
let doc = match self.drawing_or_read(&file, SheetSetMsg::SelectBlockPicked(Some(path))) {
Ok(doc) => doc,
Err(task) => return task,
};
let mut names: Vec<(String, String, bool)> = doc
.block_records
.iter()
.filter(|b| !b.name.starts_with('*') && !b.is_layout())
.map(|b| (b.name.clone(), format!("{:X}", b.handle.value()), false))
.collect();
names.sort();
drop(doc);
// The whole drawing is the first preview.
let preview = self.block_preview(&file, "");
if let Some(SsDialog::SelectBlock(b)) = self.sheet_set.dialog.as_mut() {
@@ -1989,6 +2100,7 @@ impl OpenCADStudio {
b.preview = preview;
b.error = None;
}
Task::none()
}
/// Model Views: each location folder of the current set and its drawings.
@@ -2057,20 +2169,23 @@ impl OpenCADStudio {
self.sheet_set.status = status;
}
fn properties_template(&mut self, path: PathBuf) {
let layouts = crate::io::load_file(&path).map(|d| paper_layouts(&d)).unwrap_or_default();
fn properties_template(&mut self, path: PathBuf) -> Task<Message> {
let file = path.to_string_lossy().to_string();
let layouts = match self.drawing_or_read(&file, SheetSetMsg::TemplatePicked(Some(path))) {
Ok(doc) => paper_layouts(&doc),
Err(task) => return task,
};
if let Some(SsDialog::Template(t)) = self.sheet_set.dialog.as_mut() {
t.file = path.to_string_lossy().to_string();
t.file = file;
t.layouts = layouts.into_iter().map(|(n, _)| n).collect();
t.layout = 0;
return;
return Task::none();
}
let Some(SsDialog::Properties(p)) = self.sheet_set.dialog.as_mut() else {
return;
return Task::none();
};
match layouts.first() {
Some((name, _)) => {
let file = path.to_string_lossy().to_string();
if let Some(row) = p.rows.iter_mut().find(|r| r.key == RowKey::Template) {
row.value = format!("{name} ({file})");
}
@@ -2079,6 +2194,7 @@ impl OpenCADStudio {
}
None => p.error = Some(crate::t!("The drawing has no layout to use as a sheet template.").into_owned()),
}
Task::none()
}
fn sheet_set_dialog_ok(&mut self) -> Task<Message> {
+1 -1
View File
@@ -40,7 +40,7 @@ fn write_export(path: &std::path::Path, bytes: &[u8]) -> Result<(), String> {
/// reaches the UI thread: it is caught on the worker and re-surfaced as
/// [`Message::BackgroundTaskFailed`] so a crashing export or import reports
/// on the command line instead of killing the application.
pub(super) fn background_task<T, F, M>(context: impl Into<String>, work: F, map: M) -> Task<Message>
pub(in crate::app) fn background_task<T, F, M>(context: impl Into<String>, work: F, map: M) -> Task<Message>
where
T: Send + 'static,
F: FnOnce() -> T + Send + 'static,
+1
View File
@@ -96,6 +96,7 @@ mod blocks_palette;
mod dialog;
mod dynamic;
mod file;
pub(in crate::app) use file::background_task;
mod page_setup_import;
mod style;
pub(in crate::app) mod util;
+48 -13
View File
@@ -91,9 +91,11 @@ pub struct SheetSetManager {
}
/// Drawings read from disk with their modification time; a changed file is
/// read again.
/// read again (off the UI thread, see [`read_drawing`]).
#[derive(Default)]
pub struct DrawingCache(pub std::collections::HashMap<String, (std::time::SystemTime, std::sync::Arc<codec::CadDocument>)>);
pub struct DrawingCache(
pub std::collections::HashMap<String, (std::time::SystemTime, std::sync::Arc<codec::CadDocument>)>,
);
impl std::fmt::Debug for DrawingCache {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
@@ -102,21 +104,47 @@ impl std::fmt::Debug for DrawingCache {
}
impl DrawingCache {
/// The drawing at `path`, read again when its modification time changed.
pub fn get(&mut self, path: &str) -> Option<std::sync::Arc<codec::CadDocument>> {
/// The drawing at `path` when it was read and is unchanged since.
pub fn get(&self, path: &str) -> Option<std::sync::Arc<codec::CadDocument>> {
let modified = std::fs::metadata(path).and_then(|m| m.modified()).ok()?;
let key = ss::path_key(path);
if let Some((t, doc)) = self.0.get(&key) {
if *t == modified {
return Some(doc.clone());
}
}
let doc = std::sync::Arc::new(crate::io::load_file(std::path::Path::new(path)).ok()?);
self.0.insert(key, (modified, doc.clone()));
Some(doc)
let (t, doc) = self.0.get(&ss::path_key(path))?;
(*t == modified).then(|| doc.clone())
}
pub fn insert(&mut self, path: &str, modified: std::time::SystemTime, doc: std::sync::Arc<codec::CadDocument>) {
self.0.insert(ss::path_key(path), (modified, doc));
}
/// Take the drawing at `path` out (to lend it to a scene); put it back
/// with [`DrawingCache::insert`].
pub fn take(&mut self, path: &str) -> Option<(std::time::SystemTime, std::sync::Arc<codec::CadDocument>)> {
self.0.remove(&ss::path_key(path))
}
}
/// A drawing read for the cache (its modification time and document).
#[derive(Clone)]
pub struct ReadDrawing(pub Result<(std::time::SystemTime, std::sync::Arc<codec::CadDocument>), String>);
impl std::fmt::Debug for ReadDrawing {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(if self.0.is_ok() { "ReadDrawing(Ok)" } else { "ReadDrawing(Err)" })
}
}
/// Read the drawing at `path` (run on a worker thread).
pub fn read_drawing(path: &str) -> ReadDrawing {
let modified = std::fs::metadata(path).and_then(|m| m.modified()).map_err(|e| e.to_string());
ReadDrawing(modified.and_then(|t| {
let doc = crate::io::load_file(std::path::Path::new(path)).map_err(|e| e.to_string())?;
Ok((t, std::sync::Arc::new(doc)))
}))
}
/// The paper-space layouts (name, handle) and the sheet link of a drawing
/// picked for Import Layout as Sheet, or why it could not be read.
pub type ImportRead = Result<(Vec<(String, String)>, Option<codec::sheet_set::SheetSetData>), String>;
/// A row menu item: an entry, a separator, or a fly-out submenu (label, key, items).
#[derive(Debug, Clone)]
pub enum Mi {
@@ -193,6 +221,13 @@ pub enum SheetSetMsg {
/// Import: Browse for Drawings and the drawings picked.
BrowseDrawings,
ImportAdd(Vec<std::path::PathBuf>),
/// The drawings picked for import, read: into a new Import dialog
/// (`true`) or replacing the open dialog's rows.
ImportRead(bool, Vec<(std::path::PathBuf, ImportRead)>),
/// A drawing read for the cache, then the message that needed it.
DrawingRead(String, ReadDrawing, Box<SheetSetMsg>),
/// The wizard's folder of existing drawings, scanned for layouts.
FolderScanned(std::path::PathBuf, Vec<FoundLayout>),
/// Rename & Renumber: apply and go to the previous / next sheet.
Previous,
Next,