forked from 1b-agent/opencadstudio-upstream
Point clouds: POINTCLOUDMANAGER palette
A dockable Point Cloud Manager lists each point cloud of the drawing (only the selected ones while any is selected) with Regions (disabled, region data is not read), Unassigned Points and Scans, the scans named by their file stems. Each switch shows or hides points: the cloud, its unassigned points, all scans or one scan. The hidden set is the cloud's OCS_POINTCLOUD_VISIBILITY extended data (scan names plus *CLOUD_OFF / *UNASSIGNED_OFF markers, app id registered), written as one undo step and kept on save. The drawn points skip hidden scans by their index ranges, and the hidden set is part of the placed-cloud key. Collapse all / expand all, row highlight and a search box that filters rows (keeping parents of matches). POINTCLOUDMANAGERCLOSE closes it; the pc_manager_toggle automation action flips a switch. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
7761652a9f
commit
ac9a6b07b9
35 files changed
+673
-13
No files matched your search
@@ -3984,6 +3984,14 @@ insert =
|
||||
.pc-rcs = Point Cloud Scan
|
||||
.pc-clouds = Point Clouds
|
||||
.pc-help = Attaches a point cloud scan or project.
|
||||
.pc-manager = Point Cloud Manager
|
||||
.pc-regions-scans = REGIONS AND SCANS
|
||||
.pc-regions = Regions
|
||||
.pc-unassigned = Unassigned Points
|
||||
.pc-scans = Scans
|
||||
.pc-search = Search
|
||||
.pc-collapse-all = Collapse all
|
||||
.pc-expand-all = Expand all
|
||||
model =
|
||||
.create = إنشاء
|
||||
.boolean = عمليات منطقية
|
||||
|
||||
@@ -3971,6 +3971,14 @@ insert =
|
||||
.pc-rcs = Point Cloud Scan
|
||||
.pc-clouds = Point Clouds
|
||||
.pc-help = Attaches a point cloud scan or project.
|
||||
.pc-manager = Point Cloud Manager
|
||||
.pc-regions-scans = REGIONS AND SCANS
|
||||
.pc-regions = Regions
|
||||
.pc-unassigned = Unassigned Points
|
||||
.pc-scans = Scans
|
||||
.pc-search = Search
|
||||
.pc-collapse-all = Collapse all
|
||||
.pc-expand-all = Expand all
|
||||
model =
|
||||
.create = Създаване
|
||||
.boolean = Булеви операции
|
||||
|
||||
@@ -3969,6 +3969,14 @@ insert =
|
||||
.pc-rcs = Point Cloud Scan
|
||||
.pc-clouds = Point Clouds
|
||||
.pc-help = Attaches a point cloud scan or project.
|
||||
.pc-manager = Point Cloud Manager
|
||||
.pc-regions-scans = REGIONS AND SCANS
|
||||
.pc-regions = Regions
|
||||
.pc-unassigned = Unassigned Points
|
||||
.pc-scans = Scans
|
||||
.pc-search = Search
|
||||
.pc-collapse-all = Collapse all
|
||||
.pc-expand-all = Expand all
|
||||
model =
|
||||
.create = Vytvořit
|
||||
.boolean = Booleovské operace
|
||||
|
||||
@@ -3970,6 +3970,14 @@ insert =
|
||||
.pc-rcs = Point Cloud Scan
|
||||
.pc-clouds = Point Clouds
|
||||
.pc-help = Attaches a point cloud scan or project.
|
||||
.pc-manager = Point Cloud Manager
|
||||
.pc-regions-scans = REGIONS AND SCANS
|
||||
.pc-regions = Regions
|
||||
.pc-unassigned = Unassigned Points
|
||||
.pc-scans = Scans
|
||||
.pc-search = Search
|
||||
.pc-collapse-all = Collapse all
|
||||
.pc-expand-all = Expand all
|
||||
model =
|
||||
.create = Erstellen
|
||||
.boolean = Boolesche Operationen
|
||||
|
||||
@@ -3972,6 +3972,14 @@ insert =
|
||||
.pc-rcs = Point Cloud Scan
|
||||
.pc-clouds = Point Clouds
|
||||
.pc-help = Attaches a point cloud scan or project.
|
||||
.pc-manager = Point Cloud Manager
|
||||
.pc-regions-scans = REGIONS AND SCANS
|
||||
.pc-regions = Regions
|
||||
.pc-unassigned = Unassigned Points
|
||||
.pc-scans = Scans
|
||||
.pc-search = Search
|
||||
.pc-collapse-all = Collapse all
|
||||
.pc-expand-all = Expand all
|
||||
model =
|
||||
.create = Δημιουργία
|
||||
.boolean = Δυαδική πράξη
|
||||
|
||||
@@ -3973,6 +3973,14 @@ insert =
|
||||
.pc-rcs = Point Cloud Scan
|
||||
.pc-clouds = Point Clouds
|
||||
.pc-help = Attaches a point cloud scan or project.
|
||||
.pc-manager = Point Cloud Manager
|
||||
.pc-regions-scans = REGIONS AND SCANS
|
||||
.pc-regions = Regions
|
||||
.pc-unassigned = Unassigned Points
|
||||
.pc-scans = Scans
|
||||
.pc-search = Search
|
||||
.pc-collapse-all = Collapse all
|
||||
.pc-expand-all = Expand all
|
||||
model =
|
||||
.create = Create
|
||||
.boolean = Boolean
|
||||
|
||||
@@ -3972,6 +3972,14 @@ insert =
|
||||
.pc-rcs = Point Cloud Scan
|
||||
.pc-clouds = Point Clouds
|
||||
.pc-help = Attaches a point cloud scan or project.
|
||||
.pc-manager = Point Cloud Manager
|
||||
.pc-regions-scans = REGIONS AND SCANS
|
||||
.pc-regions = Regions
|
||||
.pc-unassigned = Unassigned Points
|
||||
.pc-scans = Scans
|
||||
.pc-search = Search
|
||||
.pc-collapse-all = Collapse all
|
||||
.pc-expand-all = Expand all
|
||||
model =
|
||||
.create = Crear
|
||||
.boolean = Booleanas
|
||||
|
||||
@@ -3971,6 +3971,14 @@ insert =
|
||||
.pc-rcs = Point Cloud Scan
|
||||
.pc-clouds = Point Clouds
|
||||
.pc-help = Attaches a point cloud scan or project.
|
||||
.pc-manager = Point Cloud Manager
|
||||
.pc-regions-scans = REGIONS AND SCANS
|
||||
.pc-regions = Regions
|
||||
.pc-unassigned = Unassigned Points
|
||||
.pc-scans = Scans
|
||||
.pc-search = Search
|
||||
.pc-collapse-all = Collapse all
|
||||
.pc-expand-all = Expand all
|
||||
model =
|
||||
.create = Luo
|
||||
.boolean = Boolen operaatiot
|
||||
|
||||
@@ -3972,6 +3972,14 @@ insert =
|
||||
.pc-rcs = Point Cloud Scan
|
||||
.pc-clouds = Point Clouds
|
||||
.pc-help = Attaches a point cloud scan or project.
|
||||
.pc-manager = Point Cloud Manager
|
||||
.pc-regions-scans = REGIONS AND SCANS
|
||||
.pc-regions = Regions
|
||||
.pc-unassigned = Unassigned Points
|
||||
.pc-scans = Scans
|
||||
.pc-search = Search
|
||||
.pc-collapse-all = Collapse all
|
||||
.pc-expand-all = Expand all
|
||||
model =
|
||||
.create = Créer
|
||||
.boolean = Opérations booléennes
|
||||
|
||||
@@ -3960,6 +3960,14 @@ insert =
|
||||
.pc-rcs = Point Cloud Scan
|
||||
.pc-clouds = Point Clouds
|
||||
.pc-help = Attaches a point cloud scan or project.
|
||||
.pc-manager = Point Cloud Manager
|
||||
.pc-regions-scans = REGIONS AND SCANS
|
||||
.pc-regions = Regions
|
||||
.pc-unassigned = Unassigned Points
|
||||
.pc-scans = Scans
|
||||
.pc-search = Search
|
||||
.pc-collapse-all = Collapse all
|
||||
.pc-expand-all = Expand all
|
||||
model =
|
||||
.create = बनाएँ
|
||||
.boolean = बूलियन संक्रियाएँ
|
||||
|
||||
@@ -3971,6 +3971,14 @@ insert =
|
||||
.pc-rcs = Point Cloud Scan
|
||||
.pc-clouds = Point Clouds
|
||||
.pc-help = Attaches a point cloud scan or project.
|
||||
.pc-manager = Point Cloud Manager
|
||||
.pc-regions-scans = REGIONS AND SCANS
|
||||
.pc-regions = Regions
|
||||
.pc-unassigned = Unassigned Points
|
||||
.pc-scans = Scans
|
||||
.pc-search = Search
|
||||
.pc-collapse-all = Collapse all
|
||||
.pc-expand-all = Expand all
|
||||
model =
|
||||
.create = Létrehozás
|
||||
.boolean = Logikai műveletek
|
||||
|
||||
@@ -3971,6 +3971,14 @@ insert =
|
||||
.pc-rcs = Point Cloud Scan
|
||||
.pc-clouds = Point Clouds
|
||||
.pc-help = Attaches a point cloud scan or project.
|
||||
.pc-manager = Point Cloud Manager
|
||||
.pc-regions-scans = REGIONS AND SCANS
|
||||
.pc-regions = Regions
|
||||
.pc-unassigned = Unassigned Points
|
||||
.pc-scans = Scans
|
||||
.pc-search = Search
|
||||
.pc-collapse-all = Collapse all
|
||||
.pc-expand-all = Expand all
|
||||
model =
|
||||
.create = Crea
|
||||
.boolean = Operazioni booleane
|
||||
|
||||
@@ -3960,6 +3960,14 @@ insert =
|
||||
.pc-rcs = Point Cloud Scan
|
||||
.pc-clouds = Point Clouds
|
||||
.pc-help = Attaches a point cloud scan or project.
|
||||
.pc-manager = Point Cloud Manager
|
||||
.pc-regions-scans = REGIONS AND SCANS
|
||||
.pc-regions = Regions
|
||||
.pc-unassigned = Unassigned Points
|
||||
.pc-scans = Scans
|
||||
.pc-search = Search
|
||||
.pc-collapse-all = Collapse all
|
||||
.pc-expand-all = Expand all
|
||||
model =
|
||||
.create = 作成
|
||||
.boolean = ブール演算
|
||||
|
||||
@@ -3971,6 +3971,14 @@ insert =
|
||||
.pc-rcs = Point Cloud Scan
|
||||
.pc-clouds = Point Clouds
|
||||
.pc-help = Attaches a point cloud scan or project.
|
||||
.pc-manager = Point Cloud Manager
|
||||
.pc-regions-scans = REGIONS AND SCANS
|
||||
.pc-regions = Regions
|
||||
.pc-unassigned = Unassigned Points
|
||||
.pc-scans = Scans
|
||||
.pc-search = Search
|
||||
.pc-collapse-all = Collapse all
|
||||
.pc-expand-all = Expand all
|
||||
model =
|
||||
.create = 생성
|
||||
.boolean = 불리언 연산
|
||||
|
||||
@@ -3970,6 +3970,14 @@ insert =
|
||||
.pc-rcs = Point Cloud Scan
|
||||
.pc-clouds = Point Clouds
|
||||
.pc-help = Attaches a point cloud scan or project.
|
||||
.pc-manager = Point Cloud Manager
|
||||
.pc-regions-scans = REGIONS AND SCANS
|
||||
.pc-regions = Regions
|
||||
.pc-unassigned = Unassigned Points
|
||||
.pc-scans = Scans
|
||||
.pc-search = Search
|
||||
.pc-collapse-all = Collapse all
|
||||
.pc-expand-all = Expand all
|
||||
model =
|
||||
.create = Maken
|
||||
.boolean = Booleaanse bewerkingen
|
||||
|
||||
@@ -3971,6 +3971,14 @@ insert =
|
||||
.pc-rcs = Point Cloud Scan
|
||||
.pc-clouds = Point Clouds
|
||||
.pc-help = Attaches a point cloud scan or project.
|
||||
.pc-manager = Point Cloud Manager
|
||||
.pc-regions-scans = REGIONS AND SCANS
|
||||
.pc-regions = Regions
|
||||
.pc-unassigned = Unassigned Points
|
||||
.pc-scans = Scans
|
||||
.pc-search = Search
|
||||
.pc-collapse-all = Collapse all
|
||||
.pc-expand-all = Expand all
|
||||
model =
|
||||
.create = Utwórz
|
||||
.boolean = Operacje boolowskie
|
||||
|
||||
@@ -3972,6 +3972,14 @@ insert =
|
||||
.pc-rcs = Point Cloud Scan
|
||||
.pc-clouds = Point Clouds
|
||||
.pc-help = Attaches a point cloud scan or project.
|
||||
.pc-manager = Point Cloud Manager
|
||||
.pc-regions-scans = REGIONS AND SCANS
|
||||
.pc-regions = Regions
|
||||
.pc-unassigned = Unassigned Points
|
||||
.pc-scans = Scans
|
||||
.pc-search = Search
|
||||
.pc-collapse-all = Collapse all
|
||||
.pc-expand-all = Expand all
|
||||
model =
|
||||
.create = Criar
|
||||
.boolean = Operações booleanas
|
||||
|
||||
@@ -3973,6 +3973,14 @@ insert =
|
||||
.pc-rcs = Point Cloud Scan
|
||||
.pc-clouds = Point Clouds
|
||||
.pc-help = Attaches a point cloud scan or project.
|
||||
.pc-manager = Point Cloud Manager
|
||||
.pc-regions-scans = REGIONS AND SCANS
|
||||
.pc-regions = Regions
|
||||
.pc-unassigned = Unassigned Points
|
||||
.pc-scans = Scans
|
||||
.pc-search = Search
|
||||
.pc-collapse-all = Collapse all
|
||||
.pc-expand-all = Expand all
|
||||
model =
|
||||
.create = Создание
|
||||
.boolean = Булевы операции
|
||||
|
||||
@@ -3932,6 +3932,14 @@ insert =
|
||||
.pc-rcs = Nokta Bulutu Taraması
|
||||
.pc-clouds = Nokta Bulutları
|
||||
.pc-help = Bir nokta bulutu taramasını veya projesini bağlar.
|
||||
.pc-manager = Nokta Bulutu Yöneticisi
|
||||
.pc-regions-scans = BÖLGELER VE TARAMALAR
|
||||
.pc-regions = Bölgeler
|
||||
.pc-unassigned = Atanmamış Noktalar
|
||||
.pc-scans = Taramalar
|
||||
.pc-search = Ara
|
||||
.pc-collapse-all = Tümünü daralt
|
||||
.pc-expand-all = Tümünü genişlet
|
||||
model =
|
||||
.create = Oluştur
|
||||
.boolean = Boole işlemleri
|
||||
|
||||
@@ -3955,6 +3955,14 @@ insert =
|
||||
.pc-rcs = Point Cloud Scan
|
||||
.pc-clouds = Point Clouds
|
||||
.pc-help = Attaches a point cloud scan or project.
|
||||
.pc-manager = Point Cloud Manager
|
||||
.pc-regions-scans = REGIONS AND SCANS
|
||||
.pc-regions = Regions
|
||||
.pc-unassigned = Unassigned Points
|
||||
.pc-scans = Scans
|
||||
.pc-search = Search
|
||||
.pc-collapse-all = Collapse all
|
||||
.pc-expand-all = Expand all
|
||||
model =
|
||||
.create = 创建
|
||||
.boolean = 布尔运算
|
||||
|
||||
@@ -3967,6 +3967,14 @@ insert =
|
||||
.pc-rcs = Point Cloud Scan
|
||||
.pc-clouds = Point Clouds
|
||||
.pc-help = Attaches a point cloud scan or project.
|
||||
.pc-manager = Point Cloud Manager
|
||||
.pc-regions-scans = REGIONS AND SCANS
|
||||
.pc-regions = Regions
|
||||
.pc-unassigned = Unassigned Points
|
||||
.pc-scans = Scans
|
||||
.pc-search = Search
|
||||
.pc-collapse-all = Collapse all
|
||||
.pc-expand-all = Expand all
|
||||
model =
|
||||
.create = 建立
|
||||
.boolean = 布林運算
|
||||
|
||||
@@ -736,6 +736,8 @@ inventory::submit!(crate::command::CommandRegistration {
|
||||
"EXPORTSTEP",
|
||||
"EXPORTSTL",
|
||||
"EXTERNALREFERENCES",
|
||||
"POINTCLOUDMANAGER",
|
||||
"POINTCLOUDMANAGERCLOSE",
|
||||
"EXTRIM",
|
||||
"FILETAB",
|
||||
"FIND",
|
||||
|
||||
@@ -34,7 +34,13 @@ impl OpenCADStudio {
|
||||
common.color.clone()
|
||||
};
|
||||
let color = color.rgb().map_or([255; 3], |(r, g, b)| [r, g, b]);
|
||||
if let Some(placed) = crate::scene::model::point_cloud::placed(document, data, color, None) {
|
||||
if let Some(placed) = crate::scene::model::point_cloud::placed(
|
||||
document,
|
||||
data,
|
||||
color,
|
||||
None,
|
||||
&crate::scene::model::point_cloud::hidden(common),
|
||||
) {
|
||||
clouds.push((common.handle, placed));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -162,6 +162,59 @@ impl OpenCADStudio {
|
||||
self.refresh_properties();
|
||||
}
|
||||
|
||||
/// A Point Cloud Manager click: a switch writes the cloud's visibility
|
||||
/// extended data as one undo step; the rest is palette state.
|
||||
pub(in crate::app) fn update_pc_manager(&mut self, message: crate::ui::window::pc_manager::PcManagerMsg) -> Task<Message> {
|
||||
use crate::scene::model::point_cloud::VISIBILITY_APP;
|
||||
use crate::ui::window::pc_manager::{self, PcManagerMsg};
|
||||
let i = self.active_tab;
|
||||
match message {
|
||||
PcManagerMsg::Toggle(handle, row) => {
|
||||
let Some(cloud) =
|
||||
pc_manager::clouds(&self.tabs[i].scene.document, &[]).into_iter().find(|c| c.handle == handle)
|
||||
else {
|
||||
return Task::none();
|
||||
};
|
||||
if self.tabs[i].scene.is_layer_locked(handle) {
|
||||
return Task::none();
|
||||
}
|
||||
let hidden = pc_manager::toggled(&cloud.hidden, &cloud.scans, &row);
|
||||
self.push_undo_snapshot(i, "POINTCLOUDMANAGER");
|
||||
self.tabs[i].scene.ensure_app_id(VISIBILITY_APP);
|
||||
let app_handle = self.tabs[i].scene.document.app_ids.get(VISIBILITY_APP).map(|a| a.handle.value());
|
||||
if let Some(entity) = self.tabs[i].scene.document.get_entity_mut(handle) {
|
||||
let data = &mut entity.common_mut().extended_data;
|
||||
if let Some(app) = app_handle {
|
||||
data.raw_dwg_eed.retain(|(a, _)| *a != app);
|
||||
}
|
||||
data.remove_record(VISIBILITY_APP);
|
||||
if !hidden.is_empty() {
|
||||
let mut record = codec::xdata::ExtendedDataRecord::new(VISIBILITY_APP);
|
||||
for name in hidden {
|
||||
record.add_value(codec::xdata::XDataValue::String(name));
|
||||
}
|
||||
data.add_record(record);
|
||||
}
|
||||
}
|
||||
self.tabs[i].scene.bump_entities(&[(handle, crate::scene::ChangeKind::Modified)]);
|
||||
self.tabs[i].dirty = true;
|
||||
}
|
||||
PcManagerMsg::Expand(key) => {
|
||||
if !self.pc_manager.collapsed.remove(&key) {
|
||||
self.pc_manager.collapsed.insert(key);
|
||||
}
|
||||
}
|
||||
PcManagerMsg::Select(key) => self.pc_manager.selected = Some(key),
|
||||
PcManagerMsg::Search(text) => self.pc_manager.search = text,
|
||||
PcManagerMsg::CollapseAll => {
|
||||
let clouds = pc_manager::clouds(&self.tabs[i].scene.document, &[]);
|
||||
self.pc_manager.collapsed.extend(pc_manager::folding_keys(&clouds));
|
||||
}
|
||||
PcManagerMsg::ExpandAll => self.pc_manager.collapsed.clear(),
|
||||
}
|
||||
Task::none()
|
||||
}
|
||||
|
||||
/// One undo step that edits every selected PDF underlay.
|
||||
fn edit_selected_underlays(&mut self, i: usize, label: &str, edit: impl Fn(&mut Underlay)) {
|
||||
let handles: Vec<_> = self
|
||||
@@ -319,7 +372,23 @@ impl OpenCADStudio {
|
||||
crop.inverted = !crop.inverted;
|
||||
}
|
||||
}),
|
||||
"_PCUNCROP" => self.edit_selected_point_clouds(i, "POINTCLOUDUNCROP", |data| data.croppings.clear()),
|
||||
// The Point Cloud Manager docks on the right like External
|
||||
// References and opens expanded.
|
||||
"POINTCLOUDMANAGER" => {
|
||||
let id = crate::ui::dock::PanelId::PointCloudManager;
|
||||
self.pc_manager.show = true;
|
||||
if self.dock.location(id).is_none() {
|
||||
self.dock.dock(id, crate::app::config::DockSide::Right, usize::MAX);
|
||||
}
|
||||
self.dock_expanded = Some(id);
|
||||
}
|
||||
"POINTCLOUDMANAGERCLOSE" => {
|
||||
self.pc_manager.show = false;
|
||||
if self.dock_expanded == Some(crate::ui::dock::PanelId::PointCloudManager) {
|
||||
self.dock_expanded = None;
|
||||
}
|
||||
}
|
||||
"_PCUNCROP" =>self.edit_selected_point_clouds(i, "POINTCLOUDUNCROP", |data| data.croppings.clear()),
|
||||
"_PDFULUNCLIP" => {
|
||||
self.edit_selected_underlays(i, "PDFCLIP", |u| {
|
||||
u.clip_boundary_vertices.clear();
|
||||
|
||||
@@ -71,6 +71,7 @@ pub(super) const NAMES: &[&str] = &[
|
||||
"close_modal",
|
||||
"pdf_dialog_ok",
|
||||
"pdf_layer_toggle",
|
||||
"pc_manager_toggle",
|
||||
"pdf_page_select",
|
||||
"ribbon_tab",
|
||||
"ribbon_dropdown",
|
||||
@@ -688,6 +689,19 @@ impl OpenCADStudio {
|
||||
"pdf_layer_toggle" => Message::PdfDialog(
|
||||
crate::ui::window::pdf_dialogs::PdfDialogMsg::LayersToggle(string(req, "value")?.into()),
|
||||
),
|
||||
// Point Cloud Manager: flip a row's switch, "<handle>:<row>" with
|
||||
// row cloud, unassigned, scans or scan:<name>.
|
||||
"pc_manager_toggle" => {
|
||||
use crate::ui::window::pc_manager::{PcManagerMsg, Row};
|
||||
let value = string(req, "value")?;
|
||||
let parsed = value.split_once(':').and_then(|(handle, row)| {
|
||||
Some((codec::Handle::new(u64::from_str_radix(handle, 16).ok()?), Row::parse(row)?))
|
||||
});
|
||||
let Some((handle, row)) = parsed else {
|
||||
return Err(failure("bad_value", "Expected <handle>:<row>"));
|
||||
};
|
||||
Message::PcManager(PcManagerMsg::Toggle(handle, row))
|
||||
}
|
||||
// Attach dialog: choose pages by index ("0,2").
|
||||
"pdf_page_select" => {
|
||||
let pages: Vec<usize> = string(req, "value")?
|
||||
|
||||
@@ -792,6 +792,8 @@ pub(super) struct OpenCADStudio {
|
||||
/// Whether the Browser panel is shown. Off until BROWSER opens it, so
|
||||
/// the default layout is unchanged for existing users.
|
||||
pub(crate) show_browser: bool,
|
||||
/// Point Cloud Manager palette (POINTCLOUDMANAGER).
|
||||
pub(crate) pc_manager: crate::ui::window::pc_manager::PcManager,
|
||||
/// Which viewport background the colour wheel is editing, or `None` when
|
||||
/// it is closed. One slot, because only one wheel can be open at a time.
|
||||
pub(crate) bg_picker: Option<BgTarget>,
|
||||
@@ -3843,6 +3845,8 @@ pub enum Message {
|
||||
PdfImportPick,
|
||||
/// An edit in one of the PDF dialogs.
|
||||
PdfDialog(crate::ui::window::pdf_dialogs::PdfDialogMsg),
|
||||
/// A click or search in the Point Cloud Manager.
|
||||
PcManager(crate::ui::window::pc_manager::PcManagerMsg),
|
||||
PdfImportPickResult(Result<(std::path::PathBuf, std::sync::Arc<Vec<u8>>), String>),
|
||||
/// Result of the PDFATTACH file picker.
|
||||
PdfAttachPickResult(Result<(std::path::PathBuf, std::sync::Arc<Vec<u8>>), String>),
|
||||
@@ -4157,6 +4161,7 @@ impl OpenCADStudio {
|
||||
graph_undo_open: false,
|
||||
show_external_references: false,
|
||||
show_browser: false,
|
||||
pc_manager: Default::default(),
|
||||
bg_picker: None,
|
||||
block_palette: Default::default(),
|
||||
xref_manager: Default::default(),
|
||||
|
||||
@@ -608,6 +608,9 @@ pub(super) fn on_ribbon_tool_click(&mut self, tool_id: String, event: ModuleEven
|
||||
PanelId::NodeGraph => {
|
||||
self.show_node_graph = false;
|
||||
}
|
||||
PanelId::PointCloudManager => {
|
||||
self.pc_manager.show = false;
|
||||
}
|
||||
PanelId::Properties => {
|
||||
self.show_properties = false;
|
||||
self.ribbon.set_properties(false);
|
||||
@@ -712,6 +715,7 @@ pub(super) fn on_ribbon_tool_click(&mut self, tool_id: String, event: ModuleEven
|
||||
PanelId::ExternalReferences => self.show_external_references,
|
||||
PanelId::Browser => self.show_browser,
|
||||
PanelId::NodeGraph => self.show_node_graph,
|
||||
PanelId::PointCloudManager => self.pc_manager.show,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1806,6 +1806,7 @@ impl OpenCADStudio {
|
||||
}
|
||||
|
||||
Message::PdfDialog(message) => self.update_pdf_dialog(message),
|
||||
Message::PcManager(message) => self.update_pc_manager(message),
|
||||
Message::RibbonSelectTab(idx) => {
|
||||
self.ribbon.select(idx);
|
||||
Task::none()
|
||||
|
||||
@@ -1870,6 +1870,7 @@ bg={bg_ms:.1}ms n={view_count}"
|
||||
crate::ui::dock::PanelId::ExternalReferences => self.show_external_references,
|
||||
crate::ui::dock::PanelId::Browser => self.show_browser,
|
||||
crate::ui::dock::PanelId::NodeGraph => self.show_node_graph,
|
||||
crate::ui::dock::PanelId::PointCloudManager => self.pc_manager.show,
|
||||
}
|
||||
};
|
||||
let edge_stack = |side: crate::app::config::DockSide| -> Option<Element<'_, Message>> {
|
||||
@@ -2994,6 +2995,13 @@ impl OpenCADStudio {
|
||||
auto_collapse,
|
||||
),
|
||||
crate::ui::dock::PanelId::NodeGraph => tab.graph.panel(width, auto_collapse),
|
||||
crate::ui::dock::PanelId::PointCloudManager => crate::ui::window::pc_manager::view(
|
||||
&self.pc_manager,
|
||||
&tab.scene.document,
|
||||
&tab.scene.selected_handles_in_order(),
|
||||
width,
|
||||
auto_collapse,
|
||||
),
|
||||
};
|
||||
let divider = dock_divider(id);
|
||||
match side {
|
||||
|
||||
@@ -115,6 +115,14 @@ pub(super) fn message_attribute(source: &str) -> Option<(&'static str, &'static
|
||||
"Point Cloud Scan" => Some(("insert", "pc-rcs")),
|
||||
"Point Clouds" => Some(("insert", "pc-clouds")),
|
||||
"Attaches a point cloud scan or project." => Some(("insert", "pc-help")),
|
||||
"Point Cloud Manager" => Some(("insert", "pc-manager")),
|
||||
"REGIONS AND SCANS" => Some(("insert", "pc-regions-scans")),
|
||||
"Regions" => Some(("insert", "pc-regions")),
|
||||
"Unassigned Points" => Some(("insert", "pc-unassigned")),
|
||||
"Scans" => Some(("insert", "pc-scans")),
|
||||
"Search" => Some(("insert", "pc-search")),
|
||||
"Collapse all" => Some(("insert", "pc-collapse-all")),
|
||||
"Expand all" => Some(("insert", "pc-expand-all")),
|
||||
"Stylization" => Some(("properties", "stylization")),
|
||||
"Color scheme" => Some(("properties", "color-scheme")),
|
||||
"Scan Colors" => Some(("properties", "scan-colors")),
|
||||
|
||||
+7
-1
@@ -8403,7 +8403,13 @@ impl Scene {
|
||||
common.color.clone()
|
||||
};
|
||||
let object_color = color.rgb().map_or([255; 3], |(r, g, b)| [r, g, b]);
|
||||
clouds.extend(point_cloud::placed(&self.document, data, object_color, transform));
|
||||
clouds.extend(point_cloud::placed(
|
||||
&self.document,
|
||||
data,
|
||||
object_color,
|
||||
transform,
|
||||
&point_cloud::hidden(common),
|
||||
));
|
||||
},
|
||||
);
|
||||
if clouds.is_empty() {
|
||||
|
||||
@@ -31,6 +31,8 @@ pub struct CloudPoints {
|
||||
pub has_normals: bool,
|
||||
/// Scans read (one for a scan file).
|
||||
pub scans: usize,
|
||||
/// Each scan's name (its file stem) and its points' index range.
|
||||
pub scan_ranges: Vec<(String, std::ops::Range<usize>)>,
|
||||
/// A project's preview picture (JPEG).
|
||||
pub preview: Option<Vec<u8>>,
|
||||
/// Bounds from the scans' headers, placed like their points: the
|
||||
@@ -70,18 +72,19 @@ fn decode(path: &Path) -> Option<CloudPoints> {
|
||||
.is_some_and(|e| e.eq_ignore_ascii_case("rcp"));
|
||||
// A project lists its scans with their own transforms; a scan file
|
||||
// carries the same transform in its header.
|
||||
let scans: Vec<(rcs::Scan, [[f64; 3]; 3])> = if is_project {
|
||||
let stem = |p: &Path| p.file_stem().map_or_else(String::new, |s| s.to_string_lossy().into_owned());
|
||||
let scans: Vec<(rcs::Scan, [[f64; 3]; 3], String)> = if is_project {
|
||||
rcp::scans(&bytes, path)?
|
||||
.into_iter()
|
||||
.filter_map(|entry| {
|
||||
let scan = rcs::decode(&std::fs::read(&entry.path).ok()?)?;
|
||||
Some((scan, [entry.translation, entry.rotation, entry.scale]))
|
||||
Some((scan, [entry.translation, entry.rotation, entry.scale], stem(&entry.path)))
|
||||
})
|
||||
.collect()
|
||||
} else {
|
||||
let scan = rcs::decode(&bytes)?;
|
||||
let transform = [scan.translation, scan.rotation, scan.scale];
|
||||
vec![(scan, transform)]
|
||||
vec![(scan, transform, stem(path))]
|
||||
};
|
||||
let mut cloud = CloudPoints {
|
||||
id: NEXT_ID.fetch_add(1, std::sync::atomic::Ordering::Relaxed),
|
||||
@@ -89,21 +92,24 @@ fn decode(path: &Path) -> Option<CloudPoints> {
|
||||
colors: Vec::new(),
|
||||
intensity: Vec::new(),
|
||||
normals: Vec::new(),
|
||||
has_rgb: scans.iter().any(|(scan, _)| scan.has_rgb),
|
||||
has_intensity: scans.iter().any(|(scan, _)| scan.has_intensity),
|
||||
has_normals: scans.iter().any(|(scan, _)| scan.has_normals),
|
||||
has_rgb: scans.iter().any(|(scan, ..)| scan.has_rgb),
|
||||
has_intensity: scans.iter().any(|(scan, ..)| scan.has_intensity),
|
||||
has_normals: scans.iter().any(|(scan, ..)| scan.has_normals),
|
||||
scans: scans.len(),
|
||||
scan_ranges: Vec::new(),
|
||||
preview: is_project.then(|| rcp::preview(&bytes)).flatten(),
|
||||
bounds: None,
|
||||
};
|
||||
for (scan, [translation, rotation, scale]) in scans {
|
||||
for (scan, [translation, rotation, scale], name) in scans {
|
||||
let rotate = euler(rotation.map(f64::to_radians));
|
||||
let place = |p: &[f64; 3]| {
|
||||
let s = [p[0] * scale[0], p[1] * scale[1], p[2] * scale[2]];
|
||||
let r = rotate.map(|row| row[0] * s[0] + row[1] * s[1] + row[2] * s[2]);
|
||||
[r[0] + translation[0], r[1] + translation[1], r[2] + translation[2]]
|
||||
};
|
||||
let first = cloud.positions.len();
|
||||
cloud.positions.extend(scan.local.iter().map(place));
|
||||
cloud.scan_ranges.push((name, first..cloud.positions.len()));
|
||||
let [lo, hi] = scan.bounds;
|
||||
for corner in 0..8 {
|
||||
let p = place(&[
|
||||
@@ -249,16 +255,46 @@ pub(crate) fn any_definition(document: &CadDocument) -> bool {
|
||||
})
|
||||
}
|
||||
|
||||
/// What the Point Cloud Manager turned off on a cloud: extended data under
|
||||
/// this application, one string per hidden scan plus the markers below.
|
||||
pub const VISIBILITY_APP: &str = "OCS_POINTCLOUD_VISIBILITY";
|
||||
/// The whole cloud is off.
|
||||
pub const CLOUD_OFF: &str = "*CLOUD_OFF";
|
||||
/// Its unassigned points (every point of a cloud without regions) are off.
|
||||
pub const UNASSIGNED_OFF: &str = "*UNASSIGNED_OFF";
|
||||
|
||||
/// The scans and markers a cloud entity hides.
|
||||
pub(crate) fn hidden(common: &codec::entities::EntityCommon) -> Vec<String> {
|
||||
common
|
||||
.extended_data
|
||||
.records()
|
||||
.iter()
|
||||
.filter(|r| r.application_name.eq_ignore_ascii_case(VISIBILITY_APP))
|
||||
.flat_map(|r| r.values.iter())
|
||||
.filter_map(|v| match v {
|
||||
codec::xdata::XDataValue::String(s) => Some(s.clone()),
|
||||
_ => None,
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// The points `data` shows, placed through its origin and axes and then
|
||||
/// `transform` (a block instance), with its crops applied. `None` when the
|
||||
/// scan file is missing or unreadable.
|
||||
/// `transform` (a block instance), with its crops applied and the `hidden`
|
||||
/// scans left out. `None` when the scan file is missing or unreadable.
|
||||
pub(crate) fn placed(
|
||||
document: &CadDocument,
|
||||
data: &PointCloudExData,
|
||||
object_color: [u8; 3],
|
||||
transform: Option<&Transform>,
|
||||
hidden: &[String],
|
||||
) -> Option<Arc<PlacedCloud>> {
|
||||
let cloud = load(&resolve_source(document, data)?)?;
|
||||
let skipped: Vec<std::ops::Range<usize>> =
|
||||
if hidden.iter().any(|h| h == CLOUD_OFF || h == UNASSIGNED_OFF) {
|
||||
vec![0..cloud.positions.len()]
|
||||
} else {
|
||||
cloud.scan_ranges.iter().filter(|(name, _)| hidden.contains(name)).map(|(_, r)| r.clone()).collect()
|
||||
};
|
||||
// The points shown: POINTCLOUDDENSITY per cent of POINTCLOUDPOINTMAX,
|
||||
// in tenths by POINTCLOUDLOD, every n-th point of a larger cloud.
|
||||
let shown = |name| setting(name).map_or(0, |s| setting_value(document, s));
|
||||
@@ -298,6 +334,7 @@ pub(crate) fn placed(
|
||||
}
|
||||
step.hash(&mut h);
|
||||
style.hash(&mut h);
|
||||
skipped.hash(&mut h);
|
||||
let key = h.finish();
|
||||
|
||||
type Placed = Mutex<FxHashMap<u64, Weak<PlacedCloud>>>;
|
||||
@@ -351,7 +388,7 @@ pub(crate) fn placed(
|
||||
};
|
||||
let mut instances = Vec::with_capacity(cloud.positions.len() / step + 1);
|
||||
for (i, p) in cloud.positions.iter().enumerate().step_by(step) {
|
||||
if !crops.iter().all(|crop| keeps(crop, p)) {
|
||||
if skipped.iter().any(|r| r.contains(&i)) || !crops.iter().all(|crop| keeps(crop, p)) {
|
||||
continue;
|
||||
}
|
||||
let w = world(p);
|
||||
|
||||
@@ -46,6 +46,8 @@ pub enum PanelId {
|
||||
Browser,
|
||||
/// Node library of the node graph overlay.
|
||||
NodeGraph,
|
||||
/// Regions and scans of the drawing's point clouds.
|
||||
PointCloudManager,
|
||||
}
|
||||
|
||||
impl PanelId {
|
||||
@@ -57,6 +59,7 @@ impl PanelId {
|
||||
PanelId::ExternalReferences => "External References",
|
||||
PanelId::Browser => "Browser",
|
||||
PanelId::NodeGraph => "Node Graph",
|
||||
PanelId::PointCloudManager => "Point Cloud Manager",
|
||||
}
|
||||
}
|
||||
|
||||
@@ -68,6 +71,7 @@ impl PanelId {
|
||||
PanelId::ExternalReferences => 460.0,
|
||||
PanelId::Browser => 230.0,
|
||||
PanelId::NodeGraph => 220.0,
|
||||
PanelId::PointCloudManager => 280.0,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -157,6 +161,7 @@ impl DockState {
|
||||
PanelId::ExternalReferences,
|
||||
PanelId::Browser,
|
||||
PanelId::NodeGraph,
|
||||
PanelId::PointCloudManager,
|
||||
] {
|
||||
self.panels.entry(id).or_insert_with(|| DockPanel::for_id(id));
|
||||
}
|
||||
|
||||
@@ -24,6 +24,7 @@ pub mod annotation_data;
|
||||
pub mod alias_editor;
|
||||
pub mod find_replace;
|
||||
pub mod named_parameters;
|
||||
pub mod pc_manager;
|
||||
pub mod pdf_dialogs;
|
||||
pub mod xref_attach;
|
||||
pub mod xref_help;
|
||||
|
||||
@@ -0,0 +1,326 @@
|
||||
//! Point Cloud Manager (POINTCLOUDMANAGER): the regions and scans of the
|
||||
//! drawing's point clouds as a tree, each with a switch that shows or hides
|
||||
//! its points. The switches are the cloud's own visibility extended data, so
|
||||
//! the tree is read straight from the document on every view.
|
||||
|
||||
use crate::app::Message;
|
||||
use crate::scene::model::point_cloud::{self, CLOUD_OFF, UNASSIGNED_OFF};
|
||||
use crate::ui::dock::PanelId;
|
||||
use codec::entities::ExtendedEntityData;
|
||||
use codec::{CadDocument, EntityType, Handle};
|
||||
use iced::widget::{button, checkbox, column, container, mouse_area, row, scrollable, text, text_input, tooltip, Space};
|
||||
use iced::{Background, Border, Element, Fill, Length, Theme};
|
||||
use std::collections::HashSet;
|
||||
|
||||
const CLOUD_ICON: &[u8] = include_bytes!("../../../assets/icons/pc_attach.svg");
|
||||
const REGION_ICON: &[u8] = include_bytes!("../../../assets/icons/region.svg");
|
||||
const UNASSIGNED_ICON: &[u8] = include_bytes!("../../../assets/icons/revcloud.svg");
|
||||
|
||||
/// Palette state that is not in the drawing: shown, folded nodes, the
|
||||
/// highlighted row and the search text.
|
||||
#[derive(Debug, Default)]
|
||||
pub struct PcManager {
|
||||
pub show: bool,
|
||||
pub collapsed: HashSet<String>,
|
||||
pub selected: Option<String>,
|
||||
pub search: String,
|
||||
}
|
||||
|
||||
/// A row with a switch.
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub enum Row {
|
||||
Cloud,
|
||||
Unassigned,
|
||||
Scans,
|
||||
Scan(String),
|
||||
}
|
||||
|
||||
impl Row {
|
||||
/// `cloud`, `unassigned`, `scans` or `scan:<name>` (automation).
|
||||
pub fn parse(text: &str) -> Option<Self> {
|
||||
match text {
|
||||
"cloud" => Some(Self::Cloud),
|
||||
"unassigned" => Some(Self::Unassigned),
|
||||
"scans" => Some(Self::Scans),
|
||||
_ => text.strip_prefix("scan:").map(|name| Self::Scan(name.to_string())),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum PcManagerMsg {
|
||||
Toggle(Handle, Row),
|
||||
Expand(String),
|
||||
Select(String),
|
||||
Search(String),
|
||||
CollapseAll,
|
||||
ExpandAll,
|
||||
}
|
||||
|
||||
/// One cloud of the tree.
|
||||
pub struct Cloud {
|
||||
pub handle: Handle,
|
||||
pub name: String,
|
||||
pub hidden: Vec<String>,
|
||||
pub scans: Vec<String>,
|
||||
}
|
||||
|
||||
/// The drawing's point clouds; only the selected ones when any is selected.
|
||||
pub fn clouds(document: &CadDocument, selected: &[Handle]) -> Vec<Cloud> {
|
||||
let all: Vec<Cloud> = document
|
||||
.entities()
|
||||
.filter_map(|entity| {
|
||||
let EntityType::Extended(extended) = entity else {
|
||||
return None;
|
||||
};
|
||||
let ExtendedEntityData::PointCloudEx(data) = &extended.data else {
|
||||
return None;
|
||||
};
|
||||
let common = entity.common();
|
||||
let scans = point_cloud::resolve_source(document, data)
|
||||
.and_then(|path| point_cloud::load(&path))
|
||||
.map(|cloud| cloud.scan_ranges.iter().map(|(name, _)| name.clone()).collect())
|
||||
.unwrap_or_default();
|
||||
Some(Cloud {
|
||||
handle: common.handle,
|
||||
name: if data.name.is_empty() { format!("{:X}", common.handle.value()) } else { data.name.clone() },
|
||||
hidden: point_cloud::hidden(common),
|
||||
scans,
|
||||
})
|
||||
})
|
||||
.collect();
|
||||
if all.iter().any(|cloud| selected.contains(&cloud.handle)) {
|
||||
all.into_iter().filter(|cloud| selected.contains(&cloud.handle)).collect()
|
||||
} else {
|
||||
all
|
||||
}
|
||||
}
|
||||
|
||||
/// Whether a row's switch is on. A parent is on while any child is.
|
||||
pub fn is_on(hidden: &[String], scans: &[String], row: &Row) -> bool {
|
||||
let off = |name: &str| hidden.iter().any(|h| h == name);
|
||||
let any_scan = scans.is_empty() || scans.iter().any(|s| !off(s));
|
||||
match row {
|
||||
Row::Scan(name) => !off(name),
|
||||
Row::Scans => any_scan,
|
||||
Row::Unassigned => !off(UNASSIGNED_OFF),
|
||||
Row::Cloud => !off(CLOUD_OFF) && (!off(UNASSIGNED_OFF) || any_scan),
|
||||
}
|
||||
}
|
||||
|
||||
/// The hidden list after flipping `row`. Switching the cloud back on shows
|
||||
/// everything under it.
|
||||
pub fn toggled(hidden: &[String], scans: &[String], row: &Row) -> Vec<String> {
|
||||
fn set(out: &mut Vec<String>, name: &str, off: bool) {
|
||||
out.retain(|h| h != name);
|
||||
if off {
|
||||
out.push(name.to_string());
|
||||
}
|
||||
}
|
||||
let on = is_on(hidden, scans, row);
|
||||
let mut out = hidden.to_vec();
|
||||
match row {
|
||||
Row::Cloud if on => set(&mut out, CLOUD_OFF, true),
|
||||
Row::Cloud => out.clear(),
|
||||
Row::Unassigned => set(&mut out, UNASSIGNED_OFF, on),
|
||||
Row::Scans => scans.iter().for_each(|s| set(&mut out, s, on)),
|
||||
Row::Scan(name) => set(&mut out, name, on),
|
||||
}
|
||||
out
|
||||
}
|
||||
|
||||
/// Keys of the rows that fold: each cloud and its Scans.
|
||||
pub fn folding_keys(clouds: &[Cloud]) -> Vec<String> {
|
||||
clouds.iter().flat_map(|c| [key(c.handle), format!("{}/scans", key(c.handle))]).collect()
|
||||
}
|
||||
|
||||
fn key(handle: Handle) -> String {
|
||||
format!("{:X}", handle.value())
|
||||
}
|
||||
|
||||
fn msg(m: PcManagerMsg) -> Message {
|
||||
Message::PcManager(m)
|
||||
}
|
||||
|
||||
/// One tree row: indent, fold arrow, icon, label and switch (`None` for a
|
||||
/// row that has none to give, drawn off and disabled).
|
||||
fn tree_row<'a>(
|
||||
state: &PcManager,
|
||||
key: String,
|
||||
depth: u16,
|
||||
fold: Option<bool>,
|
||||
icon: Element<'a, Message>,
|
||||
label: String,
|
||||
switch: Option<(bool, Message)>,
|
||||
) -> Element<'a, Message> {
|
||||
let arrow: Element<'a, Message> = match fold {
|
||||
Some(open) => button(if open {
|
||||
crate::ui::icons::themed_arrow_down(10.0)
|
||||
} else {
|
||||
crate::ui::icons::themed_arrow_right(10.0)
|
||||
})
|
||||
.on_press(msg(PcManagerMsg::Expand(key.clone())))
|
||||
.style(button::text)
|
||||
.padding(2)
|
||||
.into(),
|
||||
None => Space::new().width(14).into(),
|
||||
};
|
||||
let enabled = switch.is_some();
|
||||
let label = text(label).size(12).width(Fill);
|
||||
let label = if enabled { label } else { label.style(crate::ui::style::common::muted_style) };
|
||||
let switch: Element<'a, Message> = match switch {
|
||||
Some((on, message)) => checkbox(on).on_toggle(move |_| message.clone()).size(14).into(),
|
||||
None => checkbox(false).size(14).into(),
|
||||
};
|
||||
let selected = state.selected.as_deref() == Some(key.as_str());
|
||||
let cells = row![Space::new().width(Length::Fixed(f32::from(depth) * 16.0)), arrow, icon, label, switch]
|
||||
.spacing(5)
|
||||
.align_y(iced::Center);
|
||||
mouse_area(
|
||||
container(cells)
|
||||
.width(Fill)
|
||||
.padding([3, 6])
|
||||
.style(move |theme: &Theme| container::Style {
|
||||
background: selected.then(|| Background::Color(theme.palette().primary.weak.color)),
|
||||
text_color: selected.then(|| theme.palette().primary.weak.text),
|
||||
..Default::default()
|
||||
}),
|
||||
)
|
||||
.on_press(msg(PcManagerMsg::Select(key)))
|
||||
.into()
|
||||
}
|
||||
|
||||
fn fold_button<'a>(icon: &'static [u8], tip: String, message: PcManagerMsg) -> Element<'a, Message> {
|
||||
let b = button(crate::ui::icons::themed_secondary(icon, 12.0))
|
||||
.on_press(msg(message))
|
||||
.style(button::subtle)
|
||||
.padding([2, 4]);
|
||||
tooltip(b, text(tip).size(10), tooltip::Position::Bottom).gap(4).into()
|
||||
}
|
||||
|
||||
pub fn view<'a>(
|
||||
state: &'a PcManager,
|
||||
document: &'a CadDocument,
|
||||
selected: &[Handle],
|
||||
width: f32,
|
||||
auto_collapse: bool,
|
||||
) -> Element<'a, Message> {
|
||||
let title_bar =
|
||||
crate::ui::dock::title_bar(PanelId::PointCloudManager, crate::t!("Point Cloud Manager").into_owned(), auto_collapse);
|
||||
let query = state.search.trim().to_lowercase();
|
||||
let matches = |label: &str| query.is_empty() || label.to_lowercase().contains(&query);
|
||||
// Searching opens every node so the matches show.
|
||||
let open = |key: &str| !query.is_empty() || !state.collapsed.contains(key);
|
||||
let regions = crate::t!("Regions").into_owned();
|
||||
let unassigned = crate::t!("Unassigned Points").into_owned();
|
||||
let scans_label = crate::t!("Scans").into_owned();
|
||||
|
||||
let mut tree = column![].spacing(1);
|
||||
for cloud in clouds(document, selected) {
|
||||
let root = key(cloud.handle);
|
||||
let scans: Vec<&String> = cloud.scans.iter().filter(|s| matches(s)).collect();
|
||||
let show_scans = matches(&scans_label) || !scans.is_empty();
|
||||
let children = matches(®ions) || matches(&unassigned) || show_scans;
|
||||
if !matches(&cloud.name) && !children {
|
||||
continue;
|
||||
}
|
||||
let switch = |row: Row| {
|
||||
let on = is_on(&cloud.hidden, &cloud.scans, &row);
|
||||
Some((on, msg(PcManagerMsg::Toggle(cloud.handle, row))))
|
||||
};
|
||||
let cloud_icon = || crate::ui::icons::semantic(CLOUD_ICON, 14.0);
|
||||
tree = tree.push(tree_row(
|
||||
state,
|
||||
root.clone(),
|
||||
0,
|
||||
Some(open(&root)),
|
||||
cloud_icon(),
|
||||
cloud.name.clone(),
|
||||
switch(Row::Cloud),
|
||||
));
|
||||
if !open(&root) {
|
||||
continue;
|
||||
}
|
||||
if matches(®ions) {
|
||||
// Region data is not read: the row is there, off and disabled.
|
||||
tree = tree.push(tree_row(
|
||||
state,
|
||||
format!("{root}/regions"),
|
||||
1,
|
||||
None,
|
||||
crate::ui::icons::semantic_disabled(REGION_ICON, 14.0),
|
||||
regions.clone(),
|
||||
None,
|
||||
));
|
||||
}
|
||||
if matches(&unassigned) {
|
||||
tree = tree.push(tree_row(
|
||||
state,
|
||||
format!("{root}/unassigned"),
|
||||
1,
|
||||
None,
|
||||
crate::ui::icons::semantic(UNASSIGNED_ICON, 14.0),
|
||||
unassigned.clone(),
|
||||
switch(Row::Unassigned),
|
||||
));
|
||||
}
|
||||
if show_scans {
|
||||
let scans_key = format!("{root}/scans");
|
||||
let scans_open = open(&scans_key);
|
||||
tree = tree.push(tree_row(
|
||||
state,
|
||||
scans_key.clone(),
|
||||
1,
|
||||
Some(scans_open),
|
||||
cloud_icon(),
|
||||
scans_label.clone(),
|
||||
switch(Row::Scans),
|
||||
));
|
||||
if scans_open {
|
||||
for name in scans {
|
||||
tree = tree.push(tree_row(
|
||||
state,
|
||||
format!("{scans_key}/{name}"),
|
||||
2,
|
||||
None,
|
||||
cloud_icon(),
|
||||
name.clone(),
|
||||
switch(Row::Scan(name.clone())),
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let heading = text(crate::t!("REGIONS AND SCANS").into_owned())
|
||||
.size(11)
|
||||
.font(iced::Font {
|
||||
weight: iced::font::Weight::Bold,
|
||||
style: iced::font::Style::Italic,
|
||||
..iced::Font::DEFAULT
|
||||
})
|
||||
.style(|theme: &Theme| text::Style { color: Some(theme.palette().primary.base.color) })
|
||||
.width(Fill);
|
||||
let header = row![
|
||||
heading,
|
||||
fold_button(crate::ui::icons::MINUS, crate::t!("Collapse all").into_owned(), PcManagerMsg::CollapseAll),
|
||||
fold_button(crate::ui::icons::PLUS, crate::t!("Expand all").into_owned(), PcManagerMsg::ExpandAll),
|
||||
]
|
||||
.spacing(2)
|
||||
.align_y(iced::Center);
|
||||
let card = container(column![header, scrollable(tree).height(Fill)].spacing(6))
|
||||
.padding(6)
|
||||
.width(Fill)
|
||||
.height(Fill)
|
||||
.style(|theme: &Theme| container::Style {
|
||||
background: Some(Background::Color(theme.palette().background.weak.color)),
|
||||
border: Border { radius: 4.0.into(), ..Default::default() },
|
||||
..Default::default()
|
||||
});
|
||||
let search = text_input(&crate::t!("Search"), &state.search)
|
||||
.on_input(|v| msg(PcManagerMsg::Search(v)))
|
||||
.padding([4, 8])
|
||||
.size(12);
|
||||
|
||||
crate::ui::dock::frame(column![title_bar, card, search].spacing(6), width)
|
||||
}
|
||||
Reference in new issue
Block a user