forked from 1b-agent/opencadstudio-upstream
Sheet sets: fly-out submenus, block previews, window-aspect named views
- Row menus open Set category / Place Callout Block as fly-out submenus (hover or right press; leftwards when the palette is docked right). - List of Blocks and Select Block show the Blocks palette's preview. - VIEW SAVE keeps the window's width/height ratio. - Placing view content in model space is refused with the reference message. - Drawings read for views, placement and blocks are cached by modification time. Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
1 parent
0d0fbafaa8
commit
a3c79be278
27 files changed
+296
-62
No files matched your search
@@ -4407,6 +4407,8 @@ view =
|
||||
.ss-new-sheet-location = New sheet location
|
||||
.ss-remove-subset = Remove Subset
|
||||
.ss-rename-subset = Rename Subset...
|
||||
.ss-content-model-space = Content cannot be placed into model space. Please switch to paper space and try again.
|
||||
.ss-cannot-read = cannot be read
|
||||
.ss-rename-renumber-view = Rename & Renumber View
|
||||
.ss-list-of-blocks = List of Blocks
|
||||
.ss-select-block = Select Block
|
||||
|
||||
@@ -4394,6 +4394,8 @@ view =
|
||||
.ss-new-sheet-location = New sheet location
|
||||
.ss-remove-subset = Remove Subset
|
||||
.ss-rename-subset = Rename Subset...
|
||||
.ss-content-model-space = Content cannot be placed into model space. Please switch to paper space and try again.
|
||||
.ss-cannot-read = cannot be read
|
||||
.ss-rename-renumber-view = Rename & Renumber View
|
||||
.ss-list-of-blocks = List of Blocks
|
||||
.ss-select-block = Select Block
|
||||
|
||||
@@ -4392,6 +4392,8 @@ view =
|
||||
.ss-new-sheet-location = New sheet location
|
||||
.ss-remove-subset = Remove Subset
|
||||
.ss-rename-subset = Rename Subset...
|
||||
.ss-content-model-space = Content cannot be placed into model space. Please switch to paper space and try again.
|
||||
.ss-cannot-read = cannot be read
|
||||
.ss-rename-renumber-view = Rename & Renumber View
|
||||
.ss-list-of-blocks = List of Blocks
|
||||
.ss-select-block = Select Block
|
||||
|
||||
@@ -4393,6 +4393,8 @@ view =
|
||||
.ss-new-sheet-location = New sheet location
|
||||
.ss-remove-subset = Remove Subset
|
||||
.ss-rename-subset = Rename Subset...
|
||||
.ss-content-model-space = Content cannot be placed into model space. Please switch to paper space and try again.
|
||||
.ss-cannot-read = cannot be read
|
||||
.ss-rename-renumber-view = Rename & Renumber View
|
||||
.ss-list-of-blocks = List of Blocks
|
||||
.ss-select-block = Select Block
|
||||
|
||||
@@ -4395,6 +4395,8 @@ view =
|
||||
.ss-new-sheet-location = New sheet location
|
||||
.ss-remove-subset = Remove Subset
|
||||
.ss-rename-subset = Rename Subset...
|
||||
.ss-content-model-space = Content cannot be placed into model space. Please switch to paper space and try again.
|
||||
.ss-cannot-read = cannot be read
|
||||
.ss-rename-renumber-view = Rename & Renumber View
|
||||
.ss-list-of-blocks = List of Blocks
|
||||
.ss-select-block = Select Block
|
||||
|
||||
@@ -4396,6 +4396,8 @@ view =
|
||||
.ss-new-sheet-location = New sheet location
|
||||
.ss-remove-subset = Remove Subset
|
||||
.ss-rename-subset = Rename Subset...
|
||||
.ss-content-model-space = Content cannot be placed into model space. Please switch to paper space and try again.
|
||||
.ss-cannot-read = cannot be read
|
||||
.ss-rename-renumber-view = Rename & Renumber View
|
||||
.ss-list-of-blocks = List of Blocks
|
||||
.ss-select-block = Select Block
|
||||
|
||||
@@ -4395,6 +4395,8 @@ view =
|
||||
.ss-new-sheet-location = New sheet location
|
||||
.ss-remove-subset = Remove Subset
|
||||
.ss-rename-subset = Rename Subset...
|
||||
.ss-content-model-space = Content cannot be placed into model space. Please switch to paper space and try again.
|
||||
.ss-cannot-read = cannot be read
|
||||
.ss-rename-renumber-view = Rename & Renumber View
|
||||
.ss-list-of-blocks = List of Blocks
|
||||
.ss-select-block = Select Block
|
||||
|
||||
@@ -4394,6 +4394,8 @@ view =
|
||||
.ss-new-sheet-location = New sheet location
|
||||
.ss-remove-subset = Remove Subset
|
||||
.ss-rename-subset = Rename Subset...
|
||||
.ss-content-model-space = Content cannot be placed into model space. Please switch to paper space and try again.
|
||||
.ss-cannot-read = cannot be read
|
||||
.ss-rename-renumber-view = Rename & Renumber View
|
||||
.ss-list-of-blocks = List of Blocks
|
||||
.ss-select-block = Select Block
|
||||
|
||||
@@ -4395,6 +4395,8 @@ view =
|
||||
.ss-new-sheet-location = New sheet location
|
||||
.ss-remove-subset = Remove Subset
|
||||
.ss-rename-subset = Rename Subset...
|
||||
.ss-content-model-space = Content cannot be placed into model space. Please switch to paper space and try again.
|
||||
.ss-cannot-read = cannot be read
|
||||
.ss-rename-renumber-view = Rename & Renumber View
|
||||
.ss-list-of-blocks = List of Blocks
|
||||
.ss-select-block = Select Block
|
||||
|
||||
@@ -4383,6 +4383,8 @@ view =
|
||||
.ss-new-sheet-location = New sheet location
|
||||
.ss-remove-subset = Remove Subset
|
||||
.ss-rename-subset = Rename Subset...
|
||||
.ss-content-model-space = Content cannot be placed into model space. Please switch to paper space and try again.
|
||||
.ss-cannot-read = cannot be read
|
||||
.ss-rename-renumber-view = Rename & Renumber View
|
||||
.ss-list-of-blocks = List of Blocks
|
||||
.ss-select-block = Select Block
|
||||
|
||||
@@ -4394,6 +4394,8 @@ view =
|
||||
.ss-new-sheet-location = New sheet location
|
||||
.ss-remove-subset = Remove Subset
|
||||
.ss-rename-subset = Rename Subset...
|
||||
.ss-content-model-space = Content cannot be placed into model space. Please switch to paper space and try again.
|
||||
.ss-cannot-read = cannot be read
|
||||
.ss-rename-renumber-view = Rename & Renumber View
|
||||
.ss-list-of-blocks = List of Blocks
|
||||
.ss-select-block = Select Block
|
||||
|
||||
@@ -4394,6 +4394,8 @@ view =
|
||||
.ss-new-sheet-location = New sheet location
|
||||
.ss-remove-subset = Remove Subset
|
||||
.ss-rename-subset = Rename Subset...
|
||||
.ss-content-model-space = Content cannot be placed into model space. Please switch to paper space and try again.
|
||||
.ss-cannot-read = cannot be read
|
||||
.ss-rename-renumber-view = Rename & Renumber View
|
||||
.ss-list-of-blocks = List of Blocks
|
||||
.ss-select-block = Select Block
|
||||
|
||||
@@ -4383,6 +4383,8 @@ view =
|
||||
.ss-new-sheet-location = New sheet location
|
||||
.ss-remove-subset = Remove Subset
|
||||
.ss-rename-subset = Rename Subset...
|
||||
.ss-content-model-space = Content cannot be placed into model space. Please switch to paper space and try again.
|
||||
.ss-cannot-read = cannot be read
|
||||
.ss-rename-renumber-view = Rename & Renumber View
|
||||
.ss-list-of-blocks = List of Blocks
|
||||
.ss-select-block = Select Block
|
||||
|
||||
@@ -4394,6 +4394,8 @@ view =
|
||||
.ss-new-sheet-location = New sheet location
|
||||
.ss-remove-subset = Remove Subset
|
||||
.ss-rename-subset = Rename Subset...
|
||||
.ss-content-model-space = Content cannot be placed into model space. Please switch to paper space and try again.
|
||||
.ss-cannot-read = cannot be read
|
||||
.ss-rename-renumber-view = Rename & Renumber View
|
||||
.ss-list-of-blocks = List of Blocks
|
||||
.ss-select-block = Select Block
|
||||
|
||||
@@ -4393,6 +4393,8 @@ view =
|
||||
.ss-new-sheet-location = New sheet location
|
||||
.ss-remove-subset = Remove Subset
|
||||
.ss-rename-subset = Rename Subset...
|
||||
.ss-content-model-space = Content cannot be placed into model space. Please switch to paper space and try again.
|
||||
.ss-cannot-read = cannot be read
|
||||
.ss-rename-renumber-view = Rename & Renumber View
|
||||
.ss-list-of-blocks = List of Blocks
|
||||
.ss-select-block = Select Block
|
||||
|
||||
@@ -4394,6 +4394,8 @@ view =
|
||||
.ss-new-sheet-location = New sheet location
|
||||
.ss-remove-subset = Remove Subset
|
||||
.ss-rename-subset = Rename Subset...
|
||||
.ss-content-model-space = Content cannot be placed into model space. Please switch to paper space and try again.
|
||||
.ss-cannot-read = cannot be read
|
||||
.ss-rename-renumber-view = Rename & Renumber View
|
||||
.ss-list-of-blocks = List of Blocks
|
||||
.ss-select-block = Select Block
|
||||
|
||||
@@ -4395,6 +4395,8 @@ view =
|
||||
.ss-new-sheet-location = New sheet location
|
||||
.ss-remove-subset = Remove Subset
|
||||
.ss-rename-subset = Rename Subset...
|
||||
.ss-content-model-space = Content cannot be placed into model space. Please switch to paper space and try again.
|
||||
.ss-cannot-read = cannot be read
|
||||
.ss-rename-renumber-view = Rename & Renumber View
|
||||
.ss-list-of-blocks = List of Blocks
|
||||
.ss-select-block = Select Block
|
||||
|
||||
@@ -4396,6 +4396,8 @@ view =
|
||||
.ss-new-sheet-location = New sheet location
|
||||
.ss-remove-subset = Remove Subset
|
||||
.ss-rename-subset = Rename Subset...
|
||||
.ss-content-model-space = Content cannot be placed into model space. Please switch to paper space and try again.
|
||||
.ss-cannot-read = cannot be read
|
||||
.ss-rename-renumber-view = Rename & Renumber View
|
||||
.ss-list-of-blocks = List of Blocks
|
||||
.ss-select-block = Select Block
|
||||
|
||||
@@ -4355,6 +4355,8 @@ view =
|
||||
.ss-new-sheet-location = Yeni sayfa konumu
|
||||
.ss-remove-subset = Alt Kümeyi Kaldır
|
||||
.ss-rename-subset = Alt Kümeyi Yeniden Adlandır...
|
||||
.ss-content-model-space = İçerik model uzayına yerleştirilemez. Kağıt uzayına geçip yeniden deneyin.
|
||||
.ss-cannot-read = okunamıyor
|
||||
.ss-rename-renumber-view = Görünümü Yeniden Adlandır ve Numaralandır
|
||||
.ss-list-of-blocks = Blok Listesi
|
||||
.ss-select-block = Blok Seç
|
||||
|
||||
@@ -4378,6 +4378,8 @@ view =
|
||||
.ss-new-sheet-location = New sheet location
|
||||
.ss-remove-subset = Remove Subset
|
||||
.ss-rename-subset = Rename Subset...
|
||||
.ss-content-model-space = Content cannot be placed into model space. Please switch to paper space and try again.
|
||||
.ss-cannot-read = cannot be read
|
||||
.ss-rename-renumber-view = Rename & Renumber View
|
||||
.ss-list-of-blocks = List of Blocks
|
||||
.ss-select-block = Select Block
|
||||
|
||||
@@ -4390,6 +4390,8 @@ view =
|
||||
.ss-new-sheet-location = New sheet location
|
||||
.ss-remove-subset = Remove Subset
|
||||
.ss-rename-subset = Rename Subset...
|
||||
.ss-content-model-space = Content cannot be placed into model space. Please switch to paper space and try again.
|
||||
.ss-cannot-read = cannot be read
|
||||
.ss-rename-renumber-view = Rename & Renumber View
|
||||
.ss-list-of-blocks = List of Blocks
|
||||
.ss-select-block = Select Block
|
||||
|
||||
@@ -705,13 +705,24 @@ impl OpenCADStudio {
|
||||
SheetSetMsg::AddBlocks => {
|
||||
if let Some(SsDialog::Category(c)) = self.sheet_set.dialog.take() {
|
||||
let blocks = c.blocks.iter().map(|(id, label, _)| (id.clone(), label.clone())).collect();
|
||||
self.sheet_set.dialog = Some(SsDialog::BlockList(BlockList { category: Box::new(c), blocks, selected: None }));
|
||||
self.sheet_set.dialog = Some(SsDialog::BlockList(BlockList { category: Box::new(c), blocks, selected: None, preview: None }));
|
||||
}
|
||||
}
|
||||
SheetSetMsg::BlocksPicked(_) | SheetSetMsg::LocationPicked(None) => {}
|
||||
SheetSetMsg::Submenu(key) => self.sheet_set.submenu = key,
|
||||
SheetSetMsg::BlockListSelect(k) => {
|
||||
let target = match self.sheet_set.dialog.as_ref() {
|
||||
Some(SsDialog::BlockList(b)) => b.blocks.get(k).and_then(|(id, _)| self.listed_block(b.category.set, id)),
|
||||
_ => 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 })
|
||||
});
|
||||
if let Some(SsDialog::BlockList(b)) = self.sheet_set.dialog.as_mut() {
|
||||
b.selected = Some(k);
|
||||
b.preview = preview;
|
||||
}
|
||||
}
|
||||
SheetSetMsg::BlockListDelete => {
|
||||
@@ -728,6 +739,7 @@ impl OpenCADStudio {
|
||||
file: String::new(),
|
||||
whole: true,
|
||||
names: Vec::new(),
|
||||
preview: None,
|
||||
error: None,
|
||||
}));
|
||||
}
|
||||
@@ -748,15 +760,28 @@ impl OpenCADStudio {
|
||||
SheetSetMsg::SelectBlockPicked(None) => {}
|
||||
SheetSetMsg::SelectBlockPicked(Some(path)) => self.select_block_file(path),
|
||||
SheetSetMsg::SelectBlockWhole(v) => {
|
||||
let file = match self.sheet_set.dialog.as_ref() {
|
||||
Some(SsDialog::SelectBlock(b)) => b.file.clone(),
|
||||
_ => String::new(),
|
||||
};
|
||||
let preview = if v && !file.is_empty() { self.block_preview(&file, "") } else { None };
|
||||
if let Some(SsDialog::SelectBlock(b)) = self.sheet_set.dialog.as_mut() {
|
||||
b.whole = v;
|
||||
b.preview = preview;
|
||||
}
|
||||
}
|
||||
SheetSetMsg::SelectBlockCheck(k, v) => {
|
||||
let picked = match self.sheet_set.dialog.as_ref() {
|
||||
Some(SsDialog::SelectBlock(b)) => b.names.get(k).map(|n| (b.file.clone(), n.0.clone())),
|
||||
_ => None,
|
||||
};
|
||||
// The block just checked is previewed.
|
||||
let preview = picked.and_then(|(file, name)| self.block_preview(&file, &name));
|
||||
if let Some(SsDialog::SelectBlock(b)) = self.sheet_set.dialog.as_mut() {
|
||||
if let Some(n) = b.names.get_mut(k) {
|
||||
n.2 = v;
|
||||
}
|
||||
b.preview = preview;
|
||||
}
|
||||
}
|
||||
SheetSetMsg::ToggleDrawing(path) => {
|
||||
@@ -771,7 +796,10 @@ impl OpenCADStudio {
|
||||
self.sheet_set.open_drawings.insert(ss::path_key(&path));
|
||||
}
|
||||
SheetSetMsg::PlaceOnSheet(path, view) => self.start_place_view(path, view),
|
||||
SheetSetMsg::ViewMenu(id, action) => return self.view_menu(id, action),
|
||||
SheetSetMsg::ViewMenu(id, action) => {
|
||||
self.sheet_set.submenu = None;
|
||||
return self.view_menu(id, action);
|
||||
}
|
||||
SheetSetMsg::AddLocation => {
|
||||
return Task::perform(
|
||||
async {
|
||||
@@ -1540,13 +1568,55 @@ impl OpenCADStudio {
|
||||
self.save_current_sheet_set();
|
||||
}
|
||||
|
||||
/// A block preview (`name` empty: the drawing's model space), 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)?;
|
||||
let mut scene = crate::scene::Scene::new();
|
||||
scene.document = (*doc).clone();
|
||||
let wires = if name.is_empty() {
|
||||
let model = scene.document.header.model_space_block_handle;
|
||||
let entities: Vec<codec::EntityType> = scene
|
||||
.document
|
||||
.entities()
|
||||
.filter(|e| e.common().owner_handle == model)
|
||||
.filter(|e| !matches!(e, codec::EntityType::Block(_) | codec::EntityType::BlockEnd(_)))
|
||||
.take(20_000)
|
||||
.cloned()
|
||||
.collect();
|
||||
scene.wires_for_entities(&entities)
|
||||
} else {
|
||||
scene.block_preview_wires(name)
|
||||
};
|
||||
self.sheet_set.previews.insert(key.clone(), wires);
|
||||
Some(key)
|
||||
}
|
||||
|
||||
/// The file and block name behind a List of Blocks id.
|
||||
fn listed_block(&self, set: usize, id: &str) -> Option<(String, String)> {
|
||||
if let Some(spec) = id.strip_prefix("new:") {
|
||||
let mut p = spec.splitn(3, '|');
|
||||
return Some((p.next()?.to_string(), p.next()?.to_string()));
|
||||
}
|
||||
let db = self.sheet_set.sets.get(set)?;
|
||||
let b = db.find(id)?;
|
||||
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) {
|
||||
let key = ss::path_key(path);
|
||||
if self.sheet_set.model_views.contains_key(&key) {
|
||||
return;
|
||||
}
|
||||
let views = crate::io::load_file(Path::new(path))
|
||||
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();
|
||||
self.sheet_set.model_views.insert(key, views);
|
||||
@@ -1589,13 +1659,20 @@ 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>) {
|
||||
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;
|
||||
}
|
||||
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;
|
||||
};
|
||||
let size = crate::io::load_file(Path::new(&drawing))
|
||||
.ok()
|
||||
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 {
|
||||
@@ -1637,6 +1714,10 @@ impl OpenCADStudio {
|
||||
}
|
||||
self.save_current_sheet_set();
|
||||
}
|
||||
ViewAction::Callout(_) | ViewAction::Label 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());
|
||||
}
|
||||
ViewAction::Callout(_) | ViewAction::Label if self.current_sheet().map(|s| s.1) != Some(sheet.clone()) => {
|
||||
self.command_line
|
||||
.push_error(crate::t!("Open the view's sheet at its layout to place a block on it.").as_ref());
|
||||
@@ -1662,7 +1743,7 @@ impl OpenCADStudio {
|
||||
/// insert it at `at` with its attributes; their `?View.` / `?Sheet.`
|
||||
/// fields become the view's and sheet's navigation fields.
|
||||
fn insert_set_block(&mut self, i: usize, file: &str, name: &str, at: codec::types::Vector3, view: &str, sheet: &str, set: usize) {
|
||||
let Ok(source) = crate::io::load_file(Path::new(file)) else {
|
||||
let Some(source) = self.sheet_set.drawings.get(file) else {
|
||||
self.command_line.push_error(crate::tf!("{}: cannot be read.", file).as_ref());
|
||||
return;
|
||||
};
|
||||
@@ -1719,7 +1800,7 @@ impl OpenCADStudio {
|
||||
self.insert_set_block(i, &file, &name, Vector3::new(x, y, 0.0), &view, &sheet, set);
|
||||
}
|
||||
Placing::View { set, sheet, drawing, view } => {
|
||||
let Some(mv) = crate::io::load_file(Path::new(&drawing)).ok().and_then(|d| model_view(&d, view.as_deref())) else {
|
||||
let Some(mv) = self.sheet_set.drawings.get(&drawing).and_then(|d| model_view(&d, view.as_deref())) else {
|
||||
return;
|
||||
};
|
||||
let title = view.clone().unwrap_or_else(|| {
|
||||
@@ -1791,7 +1872,8 @@ impl OpenCADStudio {
|
||||
|
||||
/// Select Block: read a drawing's named blocks.
|
||||
fn select_block_file(&mut self, path: PathBuf) {
|
||||
let names: Vec<(String, String, bool)> = match crate::io::load_file(&path) {
|
||||
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
|
||||
@@ -1807,9 +1889,12 @@ impl OpenCADStudio {
|
||||
return;
|
||||
}
|
||||
};
|
||||
// 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() {
|
||||
b.file = path.to_string_lossy().to_string();
|
||||
b.file = file;
|
||||
b.names = names;
|
||||
b.preview = preview;
|
||||
b.error = None;
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -3041,7 +3041,7 @@ impl OpenCADStudio {
|
||||
auto_collapse,
|
||||
),
|
||||
crate::ui::dock::PanelId::SheetSetManager => {
|
||||
crate::ui::window::sheet_set::view(&self.sheet_set, width, auto_collapse)
|
||||
crate::ui::window::sheet_set::view(&self.sheet_set, width, auto_collapse, side == crate::app::config::DockSide::Right)
|
||||
}
|
||||
};
|
||||
let divider = dock_divider(id);
|
||||
|
||||
@@ -50,6 +50,8 @@ pub(super) fn message_attribute(source: &str) -> Option<(&'static str, &'static
|
||||
"New sheet location" => Some(("view", "ss-new-sheet-location")),
|
||||
"Remove Subset" => Some(("view", "ss-remove-subset")),
|
||||
"Rename Subset..." => Some(("view", "ss-rename-subset")),
|
||||
"Content cannot be placed into model space. Please switch to paper space and try again." => Some(("view", "ss-content-model-space")),
|
||||
"cannot be read" => Some(("view", "ss-cannot-read")),
|
||||
"Rename & Renumber View" => Some(("view", "ss-rename-renumber-view")),
|
||||
"List of Blocks" => Some(("view", "ss-list-of-blocks")),
|
||||
"Select Block" => Some(("view", "ss-select-block")),
|
||||
|
||||
@@ -227,7 +227,8 @@ impl Scene {
|
||||
let cam = self.camera.borrow();
|
||||
let eye_dir = cam.rotation * glam::Vec3::Z;
|
||||
let height = cam.ortho_size() * 2.0;
|
||||
let width = height; // caller can adjust; rough square
|
||||
// The window's shape: the last rendered aspect ratio.
|
||||
let width = height * self.active_camera_aspect();
|
||||
let mut view = codec::tables::View::new(name);
|
||||
// The centre is in view (DCS) coordinates, relative to the target.
|
||||
view.center = Vector3 { x: 0.0, y: 0.0, z: 0.0 };
|
||||
|
||||
@@ -378,6 +378,11 @@ impl<'a> Program<Message> for BlockPreviewCanvas<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
/// A block preview (the palette's canvas) for other dialogs.
|
||||
pub(crate) fn preview_canvas<'a>(wires: &'a [WireModel], height: f32) -> Element<'a, Message> {
|
||||
canvas(BlockPreviewCanvas { wires }).width(Fill).height(Length::Fixed(height)).into()
|
||||
}
|
||||
|
||||
fn pm(m: BlockPaletteMsg) -> Message {
|
||||
Message::BlockPalette(m)
|
||||
}
|
||||
|
||||
+150
-51
@@ -82,6 +82,47 @@ pub struct SheetSetManager {
|
||||
pub placing: Option<Placing>,
|
||||
/// A named view to show once the sheet drawing being opened is up.
|
||||
pub pending_view: Option<String>,
|
||||
/// The fly-out submenu open in a row menu (its key).
|
||||
pub submenu: Option<String>,
|
||||
/// Block previews (`file|name`; no name: the whole drawing).
|
||||
pub previews: std::collections::HashMap<String, Vec<crate::scene::model::wire_model::WireModel>>,
|
||||
/// Drawings read for Model Views, placement and blocks, by path key.
|
||||
pub drawings: DrawingCache,
|
||||
}
|
||||
|
||||
/// Drawings read from disk with their modification time; a changed file is
|
||||
/// read again.
|
||||
#[derive(Default)]
|
||||
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 {
|
||||
write!(f, "DrawingCache({} drawings)", self.0.len())
|
||||
}
|
||||
}
|
||||
|
||||
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>> {
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
/// A row menu item: an entry, a separator, or a fly-out submenu (label, key, items).
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum Mi {
|
||||
Entry(String, Option<Message>),
|
||||
Sep,
|
||||
Sub(String, String, Vec<(String, Option<Message>)>),
|
||||
}
|
||||
|
||||
/// What a sheet set point pick places on the current sheet.
|
||||
@@ -176,6 +217,8 @@ pub enum SheetSetMsg {
|
||||
ViewMenu(String, ViewAction),
|
||||
/// List of Blocks / Select Block.
|
||||
BlockListSelect(usize),
|
||||
/// A row menu's fly-out submenu opened (hovered) or closed.
|
||||
Submenu(Option<String>),
|
||||
BlockListAdd,
|
||||
BlockListDelete,
|
||||
SelectBlockBrowse,
|
||||
@@ -407,6 +450,8 @@ pub struct BlockList {
|
||||
pub category: Box<Category>,
|
||||
pub blocks: Vec<(String, String)>,
|
||||
pub selected: Option<usize>,
|
||||
/// Preview of the selected block (`file|name`).
|
||||
pub preview: Option<String>,
|
||||
}
|
||||
|
||||
/// Select Block: a drawing, taken whole or by its named blocks.
|
||||
@@ -417,6 +462,8 @@ pub struct SelectBlock {
|
||||
pub whole: bool,
|
||||
/// Block name, handle, checked.
|
||||
pub names: Vec<(String, String, bool)>,
|
||||
/// Preview shown (`file|name`).
|
||||
pub preview: Option<String>,
|
||||
pub error: Option<String>,
|
||||
}
|
||||
|
||||
@@ -564,7 +611,8 @@ fn plain_row<'a>(
|
||||
label: String,
|
||||
fold: Option<(bool, Message)>,
|
||||
double: Option<Message>,
|
||||
menu: Vec<(String, Option<Message>)>,
|
||||
menu: Vec<Mi>,
|
||||
open_sub: Option<(String, bool)>,
|
||||
) -> Element<'a, Message> {
|
||||
let arrow: Element<'a, Message> = match fold {
|
||||
Some((open, m)) => button(if open { crate::ui::icons::themed_arrow_down(10.0) } else { crate::ui::icons::themed_arrow_right(10.0) })
|
||||
@@ -589,27 +637,72 @@ fn plain_row<'a>(
|
||||
if menu.is_empty() {
|
||||
return area.into();
|
||||
}
|
||||
iced_aw::ContextMenu::new(area, move || {
|
||||
let items: Vec<Element<'static, Message>> = menu
|
||||
.iter()
|
||||
.map(|(label, m)| if label.is_empty() { menu_separator() } else { menu_entry(label.clone(), m.clone()) })
|
||||
.collect();
|
||||
container(iced::widget::Column::with_children(items).spacing(1))
|
||||
.padding(4)
|
||||
.width(Length::Fixed(240.0))
|
||||
.style(|theme: &Theme| container::Style {
|
||||
background: Some(Background::Color(theme.palette().background.weak.color)),
|
||||
border: Border { color: theme.palette().background.neutral.color, width: 1.0, radius: 4.0.into() },
|
||||
..Default::default()
|
||||
})
|
||||
.into()
|
||||
})
|
||||
.into()
|
||||
iced_aw::ContextMenu::new(area, move || menu_panel(&menu, open_sub.as_ref())).into()
|
||||
}
|
||||
|
||||
/// Height of a menu entry and of a separator (with the column spacing).
|
||||
const MENU_ENTRY_H: f32 = 27.0;
|
||||
const MENU_SEP_H: f32 = 2.0;
|
||||
|
||||
fn menu_box<'b>(items: Vec<Element<'b, Message>>) -> Element<'b, Message> {
|
||||
container(iced::widget::Column::with_children(items).spacing(1))
|
||||
.padding(4)
|
||||
.width(Length::Fixed(240.0))
|
||||
.style(|theme: &Theme| container::Style {
|
||||
background: Some(Background::Color(theme.palette().background.weak.color)),
|
||||
border: Border { color: theme.palette().background.neutral.color, width: 1.0, radius: 4.0.into() },
|
||||
..Default::default()
|
||||
})
|
||||
.into()
|
||||
}
|
||||
|
||||
/// A row menu; hovering a submenu entry opens its items beside it (`open`:
|
||||
/// key and whether the fly-out goes on the left).
|
||||
fn menu_panel(menu: &[Mi], open: Option<&(String, bool)>) -> Element<'static, Message> {
|
||||
let mut items: Vec<Element<'static, Message>> = Vec::new();
|
||||
let mut flyout: Option<(f32, Element<'static, Message>)> = None;
|
||||
let mut y = 0.0;
|
||||
for item in menu {
|
||||
match item {
|
||||
Mi::Sep => {
|
||||
items.push(menu_separator());
|
||||
y += MENU_SEP_H;
|
||||
}
|
||||
Mi::Entry(label, m) => {
|
||||
items.push(mouse_area(menu_entry(label.clone(), m.clone())).on_enter(msg(SheetSetMsg::Submenu(None))).into());
|
||||
y += MENU_ENTRY_H;
|
||||
}
|
||||
Mi::Sub(label, key, sub) => {
|
||||
let open_here = open.is_some_and(|(k, _)| k == key);
|
||||
// Hover (or a right press) opens the fly-out; a click closes the menu.
|
||||
let entry = menu_entry(format!("{label} ▸"), Some(msg(SheetSetMsg::Submenu(None))));
|
||||
let open_msg = msg(SheetSetMsg::Submenu(Some(key.clone())));
|
||||
items.push(mouse_area(entry).on_enter(open_msg.clone()).on_right_press(open_msg).into());
|
||||
if open_here {
|
||||
let subs = sub.iter().map(|(l, m)| menu_entry(l.clone(), m.clone())).collect();
|
||||
flyout = Some((y, menu_box(subs)));
|
||||
}
|
||||
y += MENU_ENTRY_H;
|
||||
}
|
||||
}
|
||||
}
|
||||
let main = menu_box(items);
|
||||
match flyout {
|
||||
Some((offset, sub)) => {
|
||||
let sub = column![Space::new().height(Length::Fixed(offset)), sub];
|
||||
// A menu opened from a right-docked palette sits left of the cursor
|
||||
// (the overlay flips it at the window edge); its fly-out opens to
|
||||
// the left so the menu itself does not move.
|
||||
// ponytail: side from the dock, not the menu's actual position.
|
||||
if open.is_some_and(|(_, left)| *left) { row![sub, main] } else { row![main, sub] }.spacing(2).into()
|
||||
}
|
||||
None => main,
|
||||
}
|
||||
}
|
||||
|
||||
/// A menu item: translated label and message (an empty label is a separator).
|
||||
fn mi(label: &str, m: Option<Message>) -> (String, Option<Message>) {
|
||||
(if label.is_empty() { String::new() } else { t!(label).into_owned() }, m)
|
||||
fn mi(label: &str, m: Option<Message>) -> Mi {
|
||||
if label.is_empty() { Mi::Sep } else { Mi::Entry(t!(label).into_owned(), m) }
|
||||
}
|
||||
|
||||
fn tree_row<'a>(
|
||||
@@ -689,32 +782,32 @@ fn push_tree<'a>(
|
||||
/// A sheet view row and its menu: Display, Rename & Renumber, Set category
|
||||
/// (the categories as sub-items), Place Callout Block (the set's callout
|
||||
/// blocks as sub-items) and Place View Label Block.
|
||||
fn view_row<'a>(db: &SheetSetDatabase, view: &SsElement, depth: u16) -> Element<'a, Message> {
|
||||
fn view_row<'a>(state: &SheetSetManager, db: &SheetSetDatabase, view: &SsElement, depth: u16, left: bool) -> Element<'a, Message> {
|
||||
let id = view.id().to_string();
|
||||
let act = |a: ViewAction| Some(msg(SheetSetMsg::ViewMenu(id.clone(), a)));
|
||||
let mut menu = vec![mi("Display", act(ViewAction::Display)), mi("Rename & Renumber...", act(ViewAction::Rename)), mi("", None)];
|
||||
menu.push((format!("{} ▸", t!("Set category")), None));
|
||||
for c in db.view_categories() {
|
||||
menu.push((format!(" {}", c.prop("Name").unwrap_or("")), act(ViewAction::Category(c.id().to_string()))));
|
||||
}
|
||||
menu.push((format!("{} ▸", t!("Place Callout Block")), None));
|
||||
for b in db.callout_blocks() {
|
||||
menu.push((format!(" {}", b.prop("Name").unwrap_or("")), act(ViewAction::Callout(b.id().to_string()))));
|
||||
}
|
||||
menu.push(mi("Place View Label Block", act(ViewAction::Label)));
|
||||
plain_row(depth, crate::ui::icons::DOC, ss::number_and_title(view), None, act(ViewAction::Display), menu)
|
||||
let categories = db.view_categories().iter().map(|c| (c.prop("Name").unwrap_or("").to_string(), act(ViewAction::Category(c.id().to_string())))).collect();
|
||||
let callouts = db.callout_blocks().iter().map(|b| (b.prop("Name").unwrap_or("").to_string(), act(ViewAction::Callout(b.id().to_string())))).collect();
|
||||
let menu = vec![
|
||||
mi("Display", act(ViewAction::Display)),
|
||||
mi("Rename & Renumber...", act(ViewAction::Rename)),
|
||||
Mi::Sep,
|
||||
Mi::Sub(t!("Set category").into_owned(), format!("{id}|category"), categories),
|
||||
Mi::Sub(t!("Place Callout Block").into_owned(), format!("{id}|callout"), callouts),
|
||||
mi("Place View Label Block", act(ViewAction::Label)),
|
||||
];
|
||||
plain_row(depth, crate::ui::icons::DOC, ss::number_and_title(view), None, act(ViewAction::Display), menu, state.submenu.clone().map(|k| (k, left)))
|
||||
}
|
||||
|
||||
/// The Sheet Views page: the set, then its view categories (or sheets) and their views.
|
||||
fn sheet_views_rows<'a>(state: &SheetSetManager, db: &SheetSetDatabase) -> Vec<Element<'a, Message>> {
|
||||
fn sheet_views_rows<'a>(state: &SheetSetManager, db: &SheetSetDatabase, left: bool) -> Vec<Element<'a, Message>> {
|
||||
let new_category = || mi("New View Category...", Some(msg(SheetSetMsg::NewCategory)));
|
||||
let mut rows = vec![plain_row(0, SET_ICON, db.name().to_string(), None, None, vec![new_category()])];
|
||||
let mut rows = vec![plain_row(0, SET_ICON, db.name().to_string(), None, None, vec![new_category()], None)];
|
||||
let views: Vec<&SsElement> = db.sheets().into_iter().flat_map(|s| db.sheet_views(s)).collect();
|
||||
if state.by_category {
|
||||
let named = db.view_categories();
|
||||
// Views of the unnamed (default) category sit directly under the set.
|
||||
for v in views.iter().filter(|v| !SheetSetDatabase::view_category_of(v).is_some_and(|c| named.iter().any(|n| n.id() == c))) {
|
||||
rows.push(view_row(db, v, 1));
|
||||
rows.push(view_row(state, db, v, 1, left));
|
||||
}
|
||||
for c in named {
|
||||
let id = c.id().to_string();
|
||||
@@ -729,9 +822,10 @@ fn sheet_views_rows<'a>(state: &SheetSetManager, db: &SheetSetDatabase) -> Vec<E
|
||||
mi("Properties...", Some(msg(SheetSetMsg::CategoryProperties(id.clone())))),
|
||||
mi("Remove", Some(msg(SheetSetMsg::CategoryRemove(id.clone())))),
|
||||
],
|
||||
None,
|
||||
));
|
||||
for v in views.iter().filter(|v| SheetSetDatabase::view_category_of(v) == Some(id.as_str())) {
|
||||
rows.push(view_row(db, v, 2));
|
||||
rows.push(view_row(state, db, v, 2, left));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
@@ -740,9 +834,9 @@ fn sheet_views_rows<'a>(state: &SheetSetManager, db: &SheetSetDatabase) -> Vec<E
|
||||
if views.is_empty() {
|
||||
continue;
|
||||
}
|
||||
rows.push(plain_row(1, crate::ui::icons::DOC, ss::number_and_title(sheet), None, None, vec![]));
|
||||
rows.push(plain_row(1, crate::ui::icons::DOC, ss::number_and_title(sheet), None, None, vec![], None));
|
||||
for v in views {
|
||||
rows.push(view_row(db, v, 2));
|
||||
rows.push(view_row(state, db, v, 2, left));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -767,6 +861,7 @@ fn model_views_rows<'a>(state: &SheetSetManager) -> Vec<Element<'a, Message>> {
|
||||
mi("Add New Location...", Some(msg(SheetSetMsg::AddLocation))),
|
||||
mi("Remove Location", Some(msg(SheetSetMsg::RemoveLocation(id.clone())))),
|
||||
],
|
||||
None,
|
||||
));
|
||||
for file in files {
|
||||
let path = std::path::Path::new(folder).join(file).to_string_lossy().to_string();
|
||||
@@ -784,6 +879,7 @@ fn model_views_rows<'a>(state: &SheetSetManager) -> Vec<Element<'a, Message>> {
|
||||
mi("Place on Sheet", Some(msg(SheetSetMsg::PlaceOnSheet(path.clone(), None)))),
|
||||
mi("See Model Space Views", Some(msg(SheetSetMsg::SeeViews(path.clone())))),
|
||||
],
|
||||
None,
|
||||
));
|
||||
if open {
|
||||
for v in state.model_views.get(&key).cloned().unwrap_or_default() {
|
||||
@@ -798,6 +894,7 @@ fn model_views_rows<'a>(state: &SheetSetManager) -> Vec<Element<'a, Message>> {
|
||||
mi("", None),
|
||||
mi("Place on Sheet", Some(msg(SheetSetMsg::PlaceOnSheet(path.clone(), Some(v))))),
|
||||
],
|
||||
None,
|
||||
));
|
||||
}
|
||||
}
|
||||
@@ -822,7 +919,8 @@ fn heading(label: &str) -> Element<'static, Message> {
|
||||
.into()
|
||||
}
|
||||
|
||||
pub fn view<'a>(state: &'a SheetSetManager, width: f32, auto_collapse: bool) -> Element<'a, Message> {
|
||||
/// `dock_right`: the palette is docked on the right (fly-outs open leftwards).
|
||||
pub fn view<'a>(state: &'a SheetSetManager, width: f32, auto_collapse: bool, dock_right: bool) -> Element<'a, Message> {
|
||||
let title_bar =
|
||||
crate::ui::dock::title_bar(PanelId::SheetSetManager, t!("Sheet Set Manager").into_owned(), auto_collapse);
|
||||
let mut picks: Vec<Pick> =
|
||||
@@ -888,7 +986,7 @@ pub fn view<'a>(state: &'a SheetSetManager, width: f32, auto_collapse: bool) ->
|
||||
]
|
||||
.spacing(4)
|
||||
.align_y(iced::Center);
|
||||
column![header, scrollable(iced::widget::Column::with_children(sheet_views_rows(state, db)).spacing(1)).height(Fill)]
|
||||
column![header, scrollable(iced::widget::Column::with_children(sheet_views_rows(state, db, dock_right)).spacing(1)).height(Fill)]
|
||||
.spacing(6)
|
||||
.into()
|
||||
}
|
||||
@@ -1537,15 +1635,16 @@ fn category_view<'a>(c: &'a Category) -> Element<'a, Message> {
|
||||
col.push(ok_cancel("OK")).into()
|
||||
}
|
||||
|
||||
fn preview_box<'a>() -> Element<'a, Message> {
|
||||
container(text(t!("preview").into_owned()).size(11).style(muted_style))
|
||||
.center_x(Fill)
|
||||
.center_y(Fill)
|
||||
.style(card_style)
|
||||
.into()
|
||||
/// A block preview (the Blocks palette's), or the empty preview box.
|
||||
fn preview_box<'a>(previews: &'a std::collections::HashMap<String, Vec<crate::scene::model::wire_model::WireModel>>, key: Option<&String>, height: f32) -> Element<'a, Message> {
|
||||
let inner: Element<'a, Message> = match key.and_then(|k| previews.get(k)) {
|
||||
Some(w) if !w.is_empty() => crate::ui::window::block_palette::preview_canvas(w, height - 8.0),
|
||||
_ => text(t!("preview").into_owned()).size(11).style(muted_style).into(),
|
||||
};
|
||||
container(inner).center_x(Fill).center_y(Length::Fixed(height)).padding(4).style(card_style).into()
|
||||
}
|
||||
|
||||
fn block_list_view<'a>(b: &'a BlockList) -> Element<'a, Message> {
|
||||
fn block_list_view<'a>(b: &'a BlockList, state: &'a SheetSetManager) -> Element<'a, Message> {
|
||||
let rows = b.blocks.iter().enumerate().map(|(i, (_, label))| {
|
||||
button(text(label.clone()).size(12))
|
||||
.on_press(msg(SheetSetMsg::BlockListSelect(i)))
|
||||
@@ -1565,7 +1664,7 @@ fn block_list_view<'a>(b: &'a BlockList) -> Element<'a, Message> {
|
||||
.style(button_style(false))
|
||||
.padding([5, 12])
|
||||
.width(Fill),
|
||||
preview_box(),
|
||||
preview_box(&state.previews, b.preview.as_ref(), 84.0),
|
||||
]
|
||||
.spacing(6)
|
||||
.width(Length::Fixed(130.0));
|
||||
@@ -1593,7 +1692,7 @@ fn block_list_view<'a>(b: &'a BlockList) -> Element<'a, Message> {
|
||||
.into()
|
||||
}
|
||||
|
||||
fn select_block_view<'a>(b: &'a SelectBlock) -> Element<'a, Message> {
|
||||
fn select_block_view<'a>(b: &'a SelectBlock, state: &'a SheetSetManager) -> Element<'a, Message> {
|
||||
let names = b.names.iter().enumerate().map(|(i, (name, _, on))| {
|
||||
let c = checkbox(*on).label(name.clone()).text_size(12).size(14);
|
||||
if b.whole { c.into() } else { c.on_toggle(move |v| msg(SheetSetMsg::SelectBlockCheck(i, v))).into() }
|
||||
@@ -1615,7 +1714,7 @@ fn select_block_view<'a>(b: &'a SelectBlock) -> Element<'a, Message> {
|
||||
.height(Length::Fixed(110.0))
|
||||
.width(Fill)
|
||||
.style(card_style),
|
||||
container(preview_box()).width(Length::Fixed(130.0)).height(Length::Fixed(110.0)),
|
||||
container(preview_box(&state.previews, b.preview.as_ref(), 110.0)).width(Length::Fixed(130.0)),
|
||||
]
|
||||
.spacing(10),
|
||||
]
|
||||
@@ -1658,8 +1757,8 @@ pub fn dialog_view<'a>(state: &'a SheetSetManager, sizing: crate::ui::modal::Mod
|
||||
Some(SsDialog::Confirm(_, _, q)) => confirm_view(q),
|
||||
Some(SsDialog::Template(t)) => template_view(t),
|
||||
Some(SsDialog::Category(c)) => category_view(c),
|
||||
Some(SsDialog::BlockList(b)) => block_list_view(b),
|
||||
Some(SsDialog::SelectBlock(b)) => select_block_view(b),
|
||||
Some(SsDialog::BlockList(b)) => block_list_view(b, state),
|
||||
Some(SsDialog::SelectBlock(b)) => select_block_view(b, state),
|
||||
None => Space::new().into(),
|
||||
};
|
||||
container(body).padding([10, 12]).width(sizing.width).into()
|
||||
|
||||
Reference in new issue
Block a user