forked from 1b-agent/opencadstudio-upstream
Point clouds: scan and region visibility in the cloud record
The manager's switches write the cloud's hidden scans (by the scan identifier its project and scan file carry) and hidden regions (the unassigned points are region 0), as the reference stores them, instead of extended data. A parent switch shows mixed while some of its rows are on. A cloud that shows none of its points draws its extents box. The codec reads and writes both lists. Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
1 parent
ae3868944a
commit
09e9e44c19
11 files changed
+147
-86
No files matched your search
Generated
+1
-1
@@ -4205,7 +4205,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "opencadcodec"
|
||||
version = "0.5.5"
|
||||
source = "git+https://github.com/ramox81/cadcodec.git?rev=380e105#380e1057426ebd621f9967a06df97d54dce4c983"
|
||||
source = "git+https://github.com/ramox81/cadcodec.git?rev=43f83ed#43f83ed5876684b906e01339d8aef617905845c8"
|
||||
dependencies = [
|
||||
"ahash 0.8.12",
|
||||
"anyhow",
|
||||
|
||||
+1
-1
@@ -164,4 +164,4 @@ name = "command_benchmarks"
|
||||
harness = false
|
||||
|
||||
[patch."https://github.com/HakanSeven12/opencadcodec.git"]
|
||||
opencadcodec = { git = "https://github.com/ramox81/cadcodec.git", rev = "380e105" }
|
||||
opencadcodec = { git = "https://github.com/ramox81/cadcodec.git", rev = "43f83ed" }
|
||||
@@ -39,7 +39,7 @@ impl OpenCADStudio {
|
||||
data,
|
||||
color,
|
||||
None,
|
||||
&crate::scene::model::point_cloud::hidden(common),
|
||||
&crate::scene::model::point_cloud::hidden(data),
|
||||
) {
|
||||
clouds.push((common.handle, placed));
|
||||
}
|
||||
|
||||
@@ -162,11 +162,11 @@ 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.
|
||||
/// A Point Cloud Manager click: a switch writes the cloud's hidden scans
|
||||
/// and regions 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};
|
||||
use crate::scene::model::point_cloud::UNASSIGNED_OFF;
|
||||
use crate::ui::window::pc_manager::{self, PcManagerMsg, Row};
|
||||
let i = self.active_tab;
|
||||
match message {
|
||||
PcManagerMsg::Toggle(handle, row) => {
|
||||
@@ -178,22 +178,19 @@ impl OpenCADStudio {
|
||||
if self.tabs[i].scene.is_layer_locked(handle) {
|
||||
return Task::none();
|
||||
}
|
||||
// A scan named by its file name (automation) is the scan with that name.
|
||||
let row = match row {
|
||||
Row::Scan(key) => Row::Scan(
|
||||
cloud.scans.iter().find(|(name, _)| *name == key).map_or(key, |(_, id)| id.clone()),
|
||||
),
|
||||
row => row,
|
||||
};
|
||||
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);
|
||||
if let Some(codec::EntityType::Extended(extended)) = self.tabs[i].scene.document.get_entity_mut(handle) {
|
||||
if let codec::entities::ExtendedEntityData::PointCloudEx(data) = &mut extended.data {
|
||||
data.hidden_regions = if hidden.iter().any(|h| h == UNASSIGNED_OFF) { vec![0] } else { Vec::new() };
|
||||
data.hidden_scans = hidden.into_iter().filter(|h| h != UNASSIGNED_OFF).collect();
|
||||
}
|
||||
}
|
||||
self.tabs[i].scene.bump_entities(&[(handle, crate::scene::ChangeKind::Modified)]);
|
||||
|
||||
@@ -146,8 +146,8 @@ impl OpenCADStudio {
|
||||
name,
|
||||
show_intensity: false,
|
||||
show_cropping: true,
|
||||
unknown_bl0: 0,
|
||||
unknown_bl1: 0,
|
||||
hidden_scans: Vec::new(),
|
||||
hidden_regions: Vec::new(),
|
||||
stylization_type: 1,
|
||||
intensity_color_scheme: String::new(),
|
||||
current_color_scheme: String::new(),
|
||||
|
||||
@@ -570,7 +570,8 @@ fn point_cloud_wires(
|
||||
let mut shown = true;
|
||||
let (mut pick_tris, mut pick_tris_low) = (Vec::new(), Vec::new());
|
||||
if let codec::entities::ExtendedEntityData::PointCloudEx(data) = &extended.data {
|
||||
shown = crate::scene::model::point_cloud::resolve_source(document, data).is_none();
|
||||
// Its box shows too with every point hidden (the reference does).
|
||||
shown = crate::scene::model::point_cloud::shows_no_points(document, data);
|
||||
let c = codec::entities::point_cloud_ex_corners(data).map(|c| [c.x, c.y, c.z]);
|
||||
let mut tris = Vec::with_capacity(36);
|
||||
for [a, b, d, e] in [[0, 1, 3, 2], [4, 5, 7, 6], [0, 1, 4, 5], [2, 3, 6, 7], [0, 2, 4, 6], [1, 3, 5, 7]] {
|
||||
|
||||
+1
-1
@@ -8408,7 +8408,7 @@ impl Scene {
|
||||
data,
|
||||
object_color,
|
||||
transform,
|
||||
&point_cloud::hidden(common),
|
||||
&point_cloud::hidden(data),
|
||||
));
|
||||
},
|
||||
);
|
||||
|
||||
@@ -32,7 +32,7 @@ pub struct CloudPoints {
|
||||
/// 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>)>,
|
||||
pub scan_ranges: Vec<ScanRange>,
|
||||
/// A project's preview picture (JPEG).
|
||||
pub preview: Option<Vec<u8>>,
|
||||
/// Bounds from the scans' headers, placed like their points: the
|
||||
@@ -73,18 +73,21 @@ fn decode(path: &Path) -> Option<CloudPoints> {
|
||||
// A project lists its scans with their own transforms; a scan file
|
||||
// carries the same transform in its header.
|
||||
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 {
|
||||
let scans: Vec<(rcs::Scan, [[f64; 3]; 3], String, 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], stem(&entry.path)))
|
||||
// The project's identifier, else the scan file's own.
|
||||
let id = if entry.id.is_empty() { scan.id.clone() } else { entry.id.clone() };
|
||||
Some((scan, [entry.translation, entry.rotation, entry.scale], stem(&entry.path), id))
|
||||
})
|
||||
.collect()
|
||||
} else {
|
||||
let scan = rcs::decode(&bytes)?;
|
||||
let transform = [scan.translation, scan.rotation, scan.scale];
|
||||
vec![(scan, transform, stem(path))]
|
||||
let id = scan.id.clone();
|
||||
vec![(scan, transform, stem(path), id)]
|
||||
};
|
||||
let mut cloud = CloudPoints {
|
||||
id: NEXT_ID.fetch_add(1, std::sync::atomic::Ordering::Relaxed),
|
||||
@@ -100,7 +103,7 @@ fn decode(path: &Path) -> Option<CloudPoints> {
|
||||
preview: is_project.then(|| rcp::preview(&bytes)).flatten(),
|
||||
bounds: None,
|
||||
};
|
||||
for (scan, [translation, rotation, scale], name) in scans {
|
||||
for (scan, [translation, rotation, scale], name, id) 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]];
|
||||
@@ -109,7 +112,8 @@ fn decode(path: &Path) -> Option<CloudPoints> {
|
||||
};
|
||||
let first = cloud.positions.len();
|
||||
cloud.positions.extend(scan.local.iter().map(place));
|
||||
cloud.scan_ranges.push((name, first..cloud.positions.len()));
|
||||
let id = if id.is_empty() { name.clone() } else { id };
|
||||
cloud.scan_ranges.push(ScanRange { name, id, range: first..cloud.positions.len() });
|
||||
let [lo, hi] = scan.bounds;
|
||||
for corner in 0..8 {
|
||||
let p = place(&[
|
||||
@@ -257,27 +261,37 @@ 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";
|
||||
/// One scan's points in a cloud: its name (the file's), its identifier and
|
||||
/// its index range.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct ScanRange {
|
||||
pub name: String,
|
||||
pub id: String,
|
||||
pub range: std::ops::Range<usize>,
|
||||
}
|
||||
|
||||
/// 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()
|
||||
/// Stands in a hidden list for the cloud's unassigned points (region 0),
|
||||
/// which are every point of a cloud without regions.
|
||||
pub const UNASSIGNED_OFF: &str = "*UNASSIGNED";
|
||||
|
||||
/// What a cloud turns off: the identifiers of its hidden scans, and
|
||||
/// [`UNASSIGNED_OFF`] when its unassigned points are hidden.
|
||||
pub(crate) fn hidden(data: &PointCloudExData) -> Vec<String> {
|
||||
let mut hidden = data.hidden_scans.clone();
|
||||
if data.hidden_regions.contains(&0) {
|
||||
hidden.push(UNASSIGNED_OFF.to_string());
|
||||
}
|
||||
hidden
|
||||
}
|
||||
|
||||
/// Whether a cloud draws none of its points: its file is not found, or its
|
||||
/// unassigned points or every scan are turned off.
|
||||
pub(crate) fn shows_no_points(document: &CadDocument, data: &PointCloudExData) -> bool {
|
||||
let Some(cloud) = resolve_source(document, data).and_then(|path| load(&path)) else {
|
||||
return true;
|
||||
};
|
||||
data.hidden_regions.contains(&0)
|
||||
|| (!cloud.scan_ranges.is_empty() && cloud.scan_ranges.iter().all(|s| data.hidden_scans.contains(&s.id)))
|
||||
}
|
||||
|
||||
/// The points `data` shows, placed through its origin and axes and then
|
||||
@@ -292,10 +306,10 @@ pub(crate) fn placed(
|
||||
) -> 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) {
|
||||
if hidden.iter().any(|h| h == UNASSIGNED_OFF) {
|
||||
vec![0..cloud.positions.len()]
|
||||
} else {
|
||||
cloud.scan_ranges.iter().filter(|(name, _)| hidden.contains(name)).map(|(_, r)| r.clone()).collect()
|
||||
cloud.scan_ranges.iter().filter(|s| hidden.contains(&s.id)).map(|s| s.range.clone()).collect()
|
||||
};
|
||||
// The points shown: POINTCLOUDDENSITY per cent of POINTCLOUDPOINTMAX,
|
||||
// in tenths by POINTCLOUDLOD, every n-th point of a larger cloud.
|
||||
|
||||
@@ -9,6 +9,8 @@ use std::path::{Path, PathBuf};
|
||||
/// One visible scan of the project.
|
||||
pub struct ScanEntry {
|
||||
pub path: PathBuf,
|
||||
/// The scan's identifier ("{…}").
|
||||
pub id: String,
|
||||
pub translation: [f64; 3],
|
||||
pub rotation: [f64; 3],
|
||||
pub scale: [f64; 3],
|
||||
@@ -90,6 +92,7 @@ fn entry(node: roxmltree::Node, folder: &Path) -> Option<ScanEntry> {
|
||||
})?;
|
||||
Some(ScanEntry {
|
||||
path,
|
||||
id: node.attribute("Id").unwrap_or_default().to_string(),
|
||||
translation: xyz(node, "Translation", 0.0),
|
||||
rotation: xyz(node, "Rotation", 0.0),
|
||||
scale: xyz(node, "Scale", 1.0),
|
||||
|
||||
@@ -32,6 +32,9 @@ pub struct Scan {
|
||||
pub scale: [f64; 3],
|
||||
/// The scan's bounds as its header records them (local frame).
|
||||
pub bounds: [[f64; 3]; 2],
|
||||
/// The scan's identifier ("{…}"), as its project and a cloud's hidden
|
||||
/// scans name it.
|
||||
pub id: String,
|
||||
}
|
||||
|
||||
const NODE_RECORD: usize = 368;
|
||||
@@ -77,6 +80,16 @@ fn normal(code: u16) -> [f32; 3] {
|
||||
n.map(|v| (v / length) as f32)
|
||||
}
|
||||
|
||||
/// The identifier the header carries after its flags, NUL-terminated.
|
||||
fn header_id(b: &[u8]) -> String {
|
||||
let tail = b.get(0xE3..b.len().min(0xE3 + 64)).unwrap_or_default();
|
||||
let text = &tail[..tail.iter().position(|c| *c == 0).unwrap_or(tail.len())];
|
||||
match std::str::from_utf8(text) {
|
||||
Ok(id) if id.starts_with('{') && id.ends_with('}') => id.to_string(),
|
||||
_ => String::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn decode(b: &[u8]) -> Option<Scan> {
|
||||
if b.get(0..5)? != b"ADOCT" {
|
||||
return None;
|
||||
@@ -119,6 +132,7 @@ pub fn decode(b: &[u8]) -> Option<Scan> {
|
||||
rotation,
|
||||
scale,
|
||||
bounds,
|
||||
id: header_id(b),
|
||||
};
|
||||
for node in 0..nodes {
|
||||
let record = directory + 8 + node * NODE_RECORD;
|
||||
|
||||
+65
-33
@@ -1,10 +1,10 @@
|
||||
//! 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
|
||||
//! its points. The switches are the cloud's own hidden scans and regions, 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::scene::model::point_cloud::{self, UNASSIGNED_OFF};
|
||||
use crate::ui::dock::PanelId;
|
||||
use codec::entities::ExtendedEntityData;
|
||||
use codec::{CadDocument, EntityType, Handle};
|
||||
@@ -32,11 +32,21 @@ pub enum Row {
|
||||
Cloud,
|
||||
Unassigned,
|
||||
Scans,
|
||||
/// A scan, by identifier.
|
||||
Scan(String),
|
||||
}
|
||||
|
||||
/// A switch's state: a parent is mixed while some children are on.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum Switch {
|
||||
On,
|
||||
Off,
|
||||
Mixed,
|
||||
}
|
||||
|
||||
impl Row {
|
||||
/// `cloud`, `unassigned`, `scans` or `scan:<name>` (automation).
|
||||
/// `cloud`, `unassigned`, `scans` or `scan:<name or identifier>`
|
||||
/// (automation).
|
||||
pub fn parse(text: &str) -> Option<Self> {
|
||||
match text {
|
||||
"cloud" => Some(Self::Cloud),
|
||||
@@ -62,7 +72,8 @@ pub struct Cloud {
|
||||
pub handle: Handle,
|
||||
pub name: String,
|
||||
pub hidden: Vec<String>,
|
||||
pub scans: Vec<String>,
|
||||
/// Each scan's name and identifier.
|
||||
pub scans: Vec<(String, String)>,
|
||||
}
|
||||
|
||||
/// The drawing's point clouds; only the selected ones when any is selected.
|
||||
@@ -79,12 +90,12 @@ pub fn clouds(document: &CadDocument, selected: &[Handle]) -> Vec<Cloud> {
|
||||
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())
|
||||
.map(|cloud| cloud.scan_ranges.iter().map(|s| (s.name.clone(), s.id.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),
|
||||
hidden: point_cloud::hidden(data),
|
||||
scans,
|
||||
})
|
||||
})
|
||||
@@ -96,35 +107,48 @@ pub fn clouds(document: &CadDocument, selected: &[Handle]) -> Vec<Cloud> {
|
||||
}
|
||||
}
|
||||
|
||||
/// 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));
|
||||
/// A row's switch. A parent is on with all its children on, off with all
|
||||
/// off, mixed otherwise (the reference shows the cloud mixed with its only
|
||||
/// scan off and its unassigned points on).
|
||||
pub fn switch_state(hidden: &[String], scans: &[(String, String)], row: &Row) -> Switch {
|
||||
let on = |id: &str| !hidden.iter().any(|h| h == id);
|
||||
let of = |states: &[bool]| match (states.iter().all(|s| *s), states.iter().any(|s| *s)) {
|
||||
(true, _) => Switch::On,
|
||||
(false, false) => Switch::Off,
|
||||
_ => Switch::Mixed,
|
||||
};
|
||||
let scan_states: Vec<bool> = scans.iter().map(|(_, id)| on(id)).collect();
|
||||
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),
|
||||
Row::Scan(id) => of(&[on(id)]),
|
||||
Row::Scans => of(&scan_states),
|
||||
Row::Unassigned => of(&[on(UNASSIGNED_OFF)]),
|
||||
Row::Cloud => {
|
||||
let mut all = scan_states;
|
||||
all.push(on(UNASSIGNED_OFF));
|
||||
of(&all)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// 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);
|
||||
/// The hidden list after clicking `row`: an on switch turns its rows off,
|
||||
/// an off or mixed one turns them all on.
|
||||
pub fn toggled(hidden: &[String], scans: &[(String, String)], row: &Row) -> Vec<String> {
|
||||
fn set(out: &mut Vec<String>, id: &str, off: bool) {
|
||||
out.retain(|h| h != id);
|
||||
if off {
|
||||
out.push(name.to_string());
|
||||
out.push(id.to_string());
|
||||
}
|
||||
}
|
||||
let on = is_on(hidden, scans, row);
|
||||
let off = switch_state(hidden, scans, row) == Switch::On;
|
||||
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),
|
||||
Row::Cloud => {
|
||||
scans.iter().for_each(|(_, id)| set(&mut out, id, off));
|
||||
set(&mut out, UNASSIGNED_OFF, off);
|
||||
}
|
||||
Row::Unassigned => set(&mut out, UNASSIGNED_OFF, off),
|
||||
Row::Scans => scans.iter().for_each(|(_, id)| set(&mut out, id, off)),
|
||||
Row::Scan(id) => set(&mut out, id, off),
|
||||
}
|
||||
out
|
||||
}
|
||||
@@ -151,7 +175,7 @@ fn tree_row<'a>(
|
||||
fold: Option<bool>,
|
||||
icon: Element<'a, Message>,
|
||||
label: String,
|
||||
switch: Option<(bool, Message)>,
|
||||
switch: Option<(Switch, Message)>,
|
||||
) -> Element<'a, Message> {
|
||||
let arrow: Element<'a, Message> = match fold {
|
||||
Some(open) => button(if open {
|
||||
@@ -169,7 +193,15 @@ fn tree_row<'a>(
|
||||
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(),
|
||||
// Mixed: a dash in the box, as the reference's indeterminate state.
|
||||
Some((Switch::Mixed, message)) => button(crate::ui::icons::themed_primary(crate::ui::icons::MINUS, 10.0))
|
||||
.on_press(message)
|
||||
.style(button::secondary)
|
||||
.padding(1)
|
||||
.width(Length::Fixed(16.0))
|
||||
.height(Length::Fixed(16.0))
|
||||
.into(),
|
||||
Some((on, message)) => checkbox(on == Switch::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());
|
||||
@@ -218,14 +250,14 @@ pub fn view<'a>(
|
||||
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 scans: Vec<&(String, String)> = cloud.scans.iter().filter(|(name, _)| matches(name)).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);
|
||||
let on = switch_state(&cloud.hidden, &cloud.scans, &row);
|
||||
Some((on, msg(PcManagerMsg::Toggle(cloud.handle, row))))
|
||||
};
|
||||
let cloud_icon = || crate::ui::icons::semantic(CLOUD_ICON, 14.0);
|
||||
@@ -277,15 +309,15 @@ pub fn view<'a>(
|
||||
switch(Row::Scans),
|
||||
));
|
||||
if scans_open {
|
||||
for name in scans {
|
||||
for (name, id) in scans {
|
||||
tree = tree.push(tree_row(
|
||||
state,
|
||||
format!("{scans_key}/{name}"),
|
||||
format!("{scans_key}/{id}"),
|
||||
2,
|
||||
None,
|
||||
cloud_icon(),
|
||||
name.clone(),
|
||||
switch(Row::Scan(name.clone())),
|
||||
switch(Row::Scan(id.clone())),
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user