feat(bpt): author native table grips and must-match

This commit is contained in:
Karim Jerbi committed 2026-10-18 19:05:17 +01:00
1 parent b6fbac39ff
commit d3e64800f6
4 files changed
+299 -66

No files matched your search

+15 -7
View File
@@ -7,7 +7,9 @@ impl OpenCADStudio {
pub(in crate::app) fn properties_table_error(&self) -> Option<&'static str> { pub(in crate::app) fn properties_table_error(&self) -> Option<&'static str> {
let tab = &self.tabs[self.active_tab]; let tab = &self.tabs[self.active_tab];
let Some(session) = tab.active_block_edit_session() else { let Some(session) = tab.active_block_edit_session() else {
return Some("Open a block in the Block Editor before editing its Block Properties Table."); return Some(
"Open a block in the Block Editor before editing its Block Properties Table.",
);
}; };
let definition = session.br_handle; let definition = session.br_handle;
if table::columns(&tab.scene.document, definition).is_empty() { if table::columns(&tab.scene.document, definition).is_empty() {
@@ -39,11 +41,13 @@ impl OpenCADStudio {
return Task::none(); return Task::none();
} }
}; };
let must_match = original.as_ref().is_some_and(|view| view.must_match);
self.block_properties_table = Some(State { self.block_properties_table = Some(State {
document: tab.id, document: tab.id,
definition, definition,
epoch: tab.scene.geometry_epoch, epoch: tab.scene.geometry_epoch,
original, original,
must_match,
columns, columns,
properties: table::columns(&tab.scene.document, definition), properties: table::columns(&tab.scene.document, definition),
selected_property: None, selected_property: None,
@@ -71,9 +75,8 @@ impl OpenCADStudio {
match value.trim().parse::<f64>() { match value.trim().parse::<f64>() {
Ok(value) if value.is_finite() => parsed.push(value), Ok(value) if value.is_finite() => parsed.push(value),
_ => { _ => {
self.block_properties_table.as_mut().unwrap().error = Some( self.block_properties_table.as_mut().unwrap().error =
crate::t!("Each cell must be a finite number.").into_owned(), Some(crate::t!("Each cell must be a finite number.").into_owned());
);
return Task::none(); return Task::none();
} }
} }
@@ -100,6 +103,7 @@ impl OpenCADStudio {
let unchanged = state.original.as_ref().is_some_and(|view| { let unchanged = state.original.as_ref().is_some_and(|view| {
view.columns == state.columns view.columns == state.columns
&& view.rows.iter().eq(cells.iter()) && view.rows.iter().eq(cells.iter())
&& view.must_match == state.must_match
}); });
if unchanged { if unchanged {
self.block_properties_table = None; self.block_properties_table = None;
@@ -107,9 +111,13 @@ impl OpenCADStudio {
return Task::none(); return Task::none();
} }
let mut working = tab.scene.document.clone(); let mut working = tab.scene.document.clone();
if let Err(error) = if let Err(error) = table::apply(
table::apply(&mut working, state.definition, &state.columns, &cells) &mut working,
{ state.definition,
&state.columns,
&cells,
state.must_match,
) {
self.block_properties_table.as_mut().unwrap().error = self.block_properties_table.as_mut().unwrap().error =
Some(crate::t!(error).into_owned()); Some(crate::t!(error).into_owned());
return Task::none(); return Task::none();
+76 -7
View File
@@ -452,7 +452,9 @@ pub(crate) fn create_with_options(
.get_entity(*h) .get_entity(*h)
.is_none_or(|e| !supported(e) || e.common().owner_handle != definition) .is_none_or(|e| !supported(e) || e.common().owner_handle != definition)
}) { }) {
return Err("This milestone supports lines, polylines, arcs, circles, ellipses, points and text belonging to this block."); return Err(
"This milestone supports lines, polylines, arcs, circles, ellipses, points and text belonging to this block.",
);
} }
let (column_offset, row_offset) = if kind == Kind::Array { let (column_offset, row_offset) = if kind == Kind::Array {
let (col, row) = array_offsets.unwrap_or((1.0, 1.0)); let (col, row) = array_offsets.unwrap_or((1.0, 1.0));
@@ -1034,14 +1036,81 @@ fn evaluate_impl(
if length < 1e-9 { if length < 1e-9 {
return Err("Specify a positive parameter length."); return Err("Specify a positive parameter length.");
} }
position = p.base + axis * length; let mut snapped_length = length;
if !force {
if let Some(table) = super::block_properties_table::table(baseline, definition)?
{
if table.must_match
&& table.columns.iter().any(|column| {
column.handle == p.handle
&& column
.connection_name
.eq_ignore_ascii_case("UpdatedDistance")
})
{
let values = table
.columns
.iter()
.position(|column| {
column.handle == p.handle
&& column
.connection_name
.eq_ignore_ascii_case("UpdatedDistance")
})
.map(|i| {
table
.rows
.iter()
.filter_map(|r| r.get(i).copied())
.collect::<Vec<_>>()
})
.unwrap_or_default();
if let Some(nearest) = values.iter().copied().min_by(|a, b| {
(a - length)
.abs()
.total_cmp(&(b - length).abs())
.then_with(|| a.total_cmp(b))
}) {
snapped_length = nearest;
}
}
}
}
position = p.base + axis * snapped_length;
} }
if p.rotation { if p.rotation {
let delta = position - p.base; let delta = position - p.base;
if delta.length() < 1e-9 { if delta.length() < 1e-9 {
return Err("Specify a rotation direction away from the parameter base."); return Err("Specify a rotation direction away from the parameter base.");
} }
position = p.base + delta.normalize() * p.end.distance(p.base); let mut angle = delta.y.atan2(delta.x);
if !force {
if let Some(table) = super::block_properties_table::table(baseline, definition)?
{
if table.must_match {
if let Some(col) = table.columns.iter().position(|column| {
column.handle == p.handle
&& column.connection_name.eq_ignore_ascii_case("UpdatedAngle")
}) {
if let Some(nearest) = table
.rows
.iter()
.filter_map(|row| row.get(col).copied())
.min_by(|a, b| {
(a - angle)
.abs()
.total_cmp(&(b - angle).abs())
.then_with(|| a.total_cmp(b))
})
{
angle = nearest;
}
}
}
}
}
position =
p.base + DVec3::new(angle.cos(), angle.sin(), 0.0) * p.end.distance(p.base);
} }
Value::Point(position) Value::Point(position)
} }
@@ -1203,7 +1272,7 @@ fn evaluate_impl(
] ]
} => } =>
{ {
return Err("This Move action uses options not supported yet.") return Err("This Move action uses options not supported yet.");
} }
DynamicBlockData::FlipAction(f) DynamicBlockData::FlipAction(f)
if f.connections if f.connections
@@ -1214,7 +1283,7 @@ fn evaluate_impl(
} }
}) => }) =>
{ {
return Err("This Flip action uses options not supported yet.") return Err("This Flip action uses options not supported yet.");
} }
DynamicBlockData::RotateAction(r) DynamicBlockData::RotateAction(r)
if !r.action.dependent if !r.action.dependent
@@ -1230,7 +1299,7 @@ fn evaluate_impl(
name: name.into(), name: name.into(),
}) => }) =>
{ {
return Err("This Rotate action uses options not supported yet.") return Err("This Rotate action uses options not supported yet.");
} }
DynamicBlockData::ScaleAction(s) DynamicBlockData::ScaleAction(s)
if !s.action.dependent if !s.action.dependent
@@ -1246,7 +1315,7 @@ fn evaluate_impl(
name: name.into(), name: name.into(),
}) => }) =>
{ {
return Err("This Scale action uses options not supported yet.") return Err("This Scale action uses options not supported yet.");
} }
_ => {} _ => {}
} }
+195 -46
View File
@@ -1,8 +1,9 @@
//! Native Block Properties Table authoring. //! Native Block Properties Table authoring.
use super::block_authoring::{self as authoring, *}; use super::block_authoring::{self as authoring, *};
use codec::objects::{ use codec::objects::{
BlockElement, BlockEvalValue, BlockOnePointParameter, BlockParameter, BlockPropertiesTable, BlockElement, BlockEvalValue, BlockGrip, BlockGripExpression, BlockOnePointParameter,
BlockPropertiesTableField, BlockPropertiesTableValue, DynamicBlockData, ObjectType, BlockParameter, BlockPropertiesTable, BlockPropertiesTableField, BlockPropertiesTableValue,
DynamicBlockData, ObjectType,
}; };
use codec::{CadDocument, Handle}; use codec::{CadDocument, Handle};
@@ -11,6 +12,7 @@ pub(crate) struct Column {
pub handle: Handle, pub handle: Handle,
pub node_id: i32, pub node_id: i32,
pub connection_name: String, pub connection_name: String,
pub display_name: String,
pub type_code: i64, pub type_code: i64,
} }
@@ -22,11 +24,12 @@ pub(crate) struct TableView {
pub rows: Vec<Vec<f64>>, pub rows: Vec<Vec<f64>>,
pub row_start: usize, pub row_start: usize,
pub trailer_start: usize, pub trailer_start: usize,
pub must_match: bool,
} }
pub(crate) type Grid = (Handle, Vec<Column>, Vec<Vec<String>>); pub(crate) type Grid = (Handle, Vec<Column>, Vec<Vec<String>>);
fn graph_handle(document: &CadDocument, definition: Handle) -> Option<Handle> { pub(crate) fn graph_handle(document: &CadDocument, definition: Handle) -> Option<Handle> {
let xdict = document.extension_dictionary_handle(definition)?; let xdict = document.extension_dictionary_handle(definition)?;
let ObjectType::Dictionary(dictionary) = document.objects.get(&xdict)? else { let ObjectType::Dictionary(dictionary) = document.objects.get(&xdict)? else {
return None; return None;
@@ -59,6 +62,20 @@ pub(crate) fn table(
.find(|parameter| parameter.handle == column.handle) .find(|parameter| parameter.handle == column.handle)
.map(|parameter| parameter.id) .map(|parameter| parameter.id)
.unwrap_or(0); .unwrap_or(0);
if column.node_id != 0 {
column.type_code = i64::from(column.node_id);
}
column.display_name = super::block_lookup::properties(document, definition)
.into_iter()
.find(|property| {
property.column.node_id == column.node_id
&& property
.column
.connection_name
.eq_ignore_ascii_case(&column.connection_name)
})
.map(|property| property.label)
.unwrap_or_else(|| column.connection_name.clone());
} }
Ok(Some(view)) Ok(Some(view))
} }
@@ -90,9 +107,10 @@ fn parse(table_handle: Handle, table: BlockPropertiesTable) -> Result<TableView,
.iter() .iter()
.position(|field| field.code == 91) .position(|field| field.code == 91)
.ok_or("The Block Properties Table has no property columns.")?; .ok_or("The Block Properties Table has no property columns.")?;
let column_count = usize::try_from(integer(&fields[column_count_index]).ok_or( let column_count = usize::try_from(
"The Block Properties Table column count is invalid.", integer(&fields[column_count_index])
)?) .ok_or("The Block Properties Table column count is invalid.")?,
)
.map_err(|_| "The Block Properties Table column count is invalid.")?; .map_err(|_| "The Block Properties Table column count is invalid.")?;
if column_count == 0 || column_count > 64 { if column_count == 0 || column_count > 64 {
return Err("The Block Properties Table column count is unsupported."); return Err("The Block Properties Table column count is unsupported.");
@@ -135,7 +153,8 @@ fn parse(table_handle: Handle, table: BlockPropertiesTable) -> Result<TableView,
columns.push(Column { columns.push(Column {
handle: column_handle, handle: column_handle,
node_id: 0, node_id: 0,
connection_name: name, connection_name: name.clone(),
display_name: name,
type_code: property_id, type_code: property_id,
}); });
index = end + 1; index = end + 1;
@@ -147,9 +166,10 @@ fn parse(table_handle: Handle, table: BlockPropertiesTable) -> Result<TableView,
.find(|(_, field)| field.code == 92) .find(|(_, field)| field.code == 92)
.map(|(i, _)| i) .map(|(i, _)| i)
.ok_or("The Block Properties Table row data is unavailable.")?; .ok_or("The Block Properties Table row data is unavailable.")?;
let row_count = usize::try_from(integer(&fields[row_count_index]).ok_or( let row_count = usize::try_from(
"The Block Properties Table row count is invalid.", integer(&fields[row_count_index])
)?) .ok_or("The Block Properties Table row count is invalid.")?,
)
.map_err(|_| "The Block Properties Table row count is invalid.")?; .map_err(|_| "The Block Properties Table row count is invalid.")?;
if row_count == 0 || row_count > 1000 { if row_count == 0 || row_count > 1000 {
return Err("The Block Properties Table row count is unsupported."); return Err("The Block Properties Table row count is unsupported.");
@@ -195,6 +215,13 @@ fn parse(table_handle: Handle, table: BlockPropertiesTable) -> Result<TableView,
.find(|(_, field)| field.code == 93) .find(|(_, field)| field.code == 93)
.map(|(i, _)| i) .map(|(i, _)| i)
.ok_or("The Block Properties Table trailer is unavailable.")?; .ok_or("The Block Properties Table trailer is unavailable.")?;
let must_match = fields
.get(trailer_start + 1)
.and_then(|f| match f.value {
BlockPropertiesTableValue::Boolean(v) if f.code == 290 => Some(v),
_ => None,
})
.unwrap_or(false);
Ok(TableView { Ok(TableView {
handle: table_handle, handle: table_handle,
table, table,
@@ -202,6 +229,7 @@ fn parse(table_handle: Handle, table: BlockPropertiesTable) -> Result<TableView,
rows, rows,
row_start, row_start,
trailer_start, trailer_start,
must_match,
}) })
} }
@@ -210,29 +238,26 @@ pub(crate) fn columns(document: &CadDocument, definition: Handle) -> Vec<Column>
super::block_lookup::properties(document, definition) super::block_lookup::properties(document, definition)
.into_iter() .into_iter()
.filter_map(|property| { .filter_map(|property| {
let parameter = parameters.iter().find(|parameter| { let parameter = parameters
parameter.id == property.column.node_id .iter()
})?; .find(|parameter| parameter.id == property.column.node_id)?;
let type_code = if property if !property
.column .column
.connection_name .connection_name
.eq_ignore_ascii_case("UpdatedDistance") .eq_ignore_ascii_case("UpdatedDistance")
&& !property
.column
.connection_name
.eq_ignore_ascii_case("UpdatedAngle")
{ {
14
} else if property
.column
.connection_name
.eq_ignore_ascii_case("UpdatedAngle")
{
21
} else {
return None; return None;
}; }
Some(Column { Some(Column {
handle: parameter.handle, handle: parameter.handle,
node_id: parameter.id, node_id: parameter.id,
connection_name: property.column.connection_name, connection_name: property.column.connection_name,
type_code, display_name: property.label,
type_code: i64::from(parameter.id),
}) })
}) })
.collect() .collect()
@@ -258,24 +283,26 @@ pub(crate) fn apply(
definition: Handle, definition: Handle,
columns: &[Column], columns: &[Column],
rows: &[Vec<f64>], rows: &[Vec<f64>],
must_match: bool,
) -> Result<Handle, &'static str> { ) -> Result<Handle, &'static str> {
if columns.is_empty() || columns.len() > 64 || rows.is_empty() || rows.len() > 1000 { if columns.is_empty() || columns.len() > 64 || rows.is_empty() || rows.len() > 1000 {
return Err("Add at least one property and one complete row."); return Err("Add at least one property and one complete row.");
} }
if rows.iter().any(|row| { if rows
row.len() != columns.len() || row.iter().any(|value| !value.is_finite()) .iter()
}) { .any(|row| row.len() != columns.len() || row.iter().any(|value| !value.is_finite()))
{
return Err("Complete each numeric property cell with a finite value."); return Err("Complete each numeric property cell with a finite value.");
} }
let mut working = document.clone(); let mut working = document.clone();
let graph = graph_handle(&working, definition).ok_or("The authoring graph is unavailable.")?; graph_handle(&working, definition).ok_or("The authoring graph is unavailable.")?;
let previous = table(&working, definition)?; let previous = table(&working, definition)?;
let mut new_table_eval = None;
let handle = if let Some(view) = &previous { let handle = if let Some(view) = &previous {
view.handle view.handle
} else { } else {
let (graph, slots) = authoring::prepare(&mut working, definition).map_err(|_| { let (graph, slots) = authoring::prepare(&mut working, definition)
"The block authoring graph cannot accept a Block Properties Table." .map_err(|_| "The block authoring graph cannot accept a Block Properties Table.")?;
})?;
register_classes( register_classes(
&mut working, &mut working,
&[("BLOCKPROPERTIESTABLE", "AcDbBlockPropertiesTable", 1153)], &[("BLOCKPROPERTIESTABLE", "AcDbBlockPropertiesTable", 1153)],
@@ -288,6 +315,7 @@ pub(crate) fn apply(
minor: MINOR, minor: MINOR,
eed_1071: 0, eed_1071: 0,
}; };
new_table_eval = Some(slots.eval);
let parameter = BlockOnePointParameter { let parameter = BlockOnePointParameter {
parameter: BlockParameter { parameter: BlockParameter {
element, element,
@@ -315,7 +343,7 @@ pub(crate) fn apply(
handle handle
}; };
let old_fields = previous.as_ref().map(|view| view.table.fields.as_slice()); let old_fields = previous.as_ref().map(|view| view.table.fields.as_slice());
let new_fields = encode_fields(columns, rows, old_fields)?; let new_fields = encode_fields(columns, rows, old_fields, must_match)?;
let Some(ObjectType::DynamicBlock(object)) = working.objects.get_mut(&handle) else { let Some(ObjectType::DynamicBlock(object)) = working.objects.get_mut(&handle) else {
return Err("The Block Properties Table object is unavailable."); return Err("The Block Properties Table object is unavailable.");
}; };
@@ -323,24 +351,123 @@ pub(crate) fn apply(
return Err("The selected object is not a Block Properties Table."); return Err("The selected object is not a Block Properties Table.");
}; };
table.fields = new_fields; table.fields = new_fields;
if let Some(eval) = new_table_eval {
table.parameter.property_count = eval + 1;
}
let graph = graph_handle(&working, definition).ok_or("The authoring graph is unavailable.")?;
ensure_grip(&mut working, definition, graph, handle)?;
let _ = graph; let _ = graph;
*document = working; *document = working;
Ok(handle) Ok(handle)
} }
fn ensure_grip(
document: &mut CadDocument,
definition: Handle,
graph: Handle,
table_handle: Handle,
) -> Result<(), &'static str> {
let already = document.objects.values().any(|value| matches!(value, ObjectType::DynamicBlock(object) if object.owner == graph && matches!(&object.data, DynamicBlockData::PropertiesTableGrip(_))));
let Some(ObjectType::DynamicBlock(table_obj)) = document.objects.get(&table_handle) else {
return Err("The Block Properties Table object is unavailable.");
};
let DynamicBlockData::PropertiesTable(table_data) = &table_obj.data else {
return Err("The selected object is not a Block Properties Table.");
};
let location = table_data.parameter.definition_point;
let table_eval = table_data.parameter.parameter.element.eval.node_id;
if already {
let grip_eval = document.objects.values().find_map(|value| match value {
ObjectType::DynamicBlock(object) if object.owner == graph => match &object.data {
DynamicBlockData::PropertiesTableGrip(grip) => Some(grip.element.eval.node_id),
_ => None,
},
_ => None,
});
if let Some(grip_eval) = grip_eval {
if let Some(ObjectType::DynamicBlock(object)) = document.objects.get_mut(&table_handle)
{
if let DynamicBlockData::PropertiesTable(table) = &mut object.data {
table.parameter.property_count = grip_eval;
}
}
}
return Ok(());
}
let (graph, slots) = authoring::prepare(document, definition)
.map_err(|_| "The block authoring graph cannot accept a table grip.")?;
authoring::register_classes(
document,
&[
("BLOCKPROPERTIESTABLEGRIP", "AcDbBlockGrip", 55),
("BLOCKGRIPLOCATIONCOMPONENT", "AcDbBlockGripExpr", 20),
],
);
let grip = document.allocate_handle();
let x = document.allocate_handle();
let y = document.allocate_handle();
let grip_data = BlockGrip {
element: BlockElement {
eval: expression(slots.eval, NO_VALUE, BlockEvalValue::None),
name: "Grip".into(),
major: MAJOR,
minor: MINOR,
eed_1071: 0,
},
flags_91: slots.eval + 1,
flags_92: slots.eval + 2,
location,
insert_cycling: false,
insert_cycling_weight: -1,
};
document.objects.insert(
grip,
ObjectType::DynamicBlock(dynamic(
grip,
graph,
"BLOCKPROPERTIESTABLEGRIP",
"AcDbBlockGrip",
DynamicBlockData::PropertiesTableGrip(grip_data),
)),
);
for (handle, eval, axis) in [
(x, slots.eval + 1, "UpdatedX"),
(y, slots.eval + 2, "UpdatedY"),
] {
document.objects.insert(
handle,
ObjectType::DynamicBlock(dynamic(
handle,
graph,
"BLOCKGRIPLOCATIONCOMPONENT",
"AcDbBlockGripExpr",
DynamicBlockData::GripLocationComponent(BlockGripExpression {
eval: expression(eval, 40, BlockEvalValue::Real(0.0)),
grip_type: table_eval,
expression: axis.into(),
}),
)),
);
}
authoring::append_components(document, graph, slots, &[grip, x, y], &[1, 2]);
if let Some(ObjectType::DynamicBlock(object)) = document.objects.get_mut(&table_handle) {
if let DynamicBlockData::PropertiesTable(table) = &mut object.data {
table.parameter.property_count = slots.eval;
}
}
Ok(())
}
fn encode_fields( fn encode_fields(
columns: &[Column], columns: &[Column],
rows: &[Vec<f64>], rows: &[Vec<f64>],
old: Option<&[BlockPropertiesTableField]>, old: Option<&[BlockPropertiesTableField]>,
must_match: bool,
) -> Result<Vec<BlockPropertiesTableField>, &'static str> { ) -> Result<Vec<BlockPropertiesTableField>, &'static str> {
let mut fields = if let Some(fields) = old { let mut fields = if let Some(fields) = old {
fields.to_vec() fields.to_vec()
} else { } else {
vec![ vec![int(90, 2), text(300, "Block Table1"), text(301, "")]
int(90, 2),
text(300, "Block Properties Table"),
text(301, ""),
]
}; };
if old.is_none() { if old.is_none() {
fields.push(int(91, columns.len() as i64)); fields.push(int(91, columns.len() as i64));
@@ -351,33 +478,55 @@ fn encode_fields(
int(171, -1), int(171, -1),
text(300, ""), text(300, ""),
text(301, &column.connection_name), text(301, &column.connection_name),
int(90, column.type_code), int(90, column.node_id as i64),
int(170, -9999), int(170, -9999),
int(170, -9999), int(170, -9999),
boolean(290, false), boolean(290, false),
boolean(291, true), boolean(291, true),
boolean(292, true), boolean(292, true),
boolean(293, column.type_code == 21), boolean(
293,
column.connection_name.eq_ignore_ascii_case("UpdatedAngle"),
),
boolean(294, false), boolean(294, false),
text(302, ""), text(302, ""),
handle_field(340, Handle::NULL), handle_field(340, Handle::NULL),
]); ]);
} }
} else { } else {
let view = parse(Handle::NULL, BlockPropertiesTable { let view = parse(
parameter: Default::default(), Handle::NULL,
fields: fields.clone(), BlockPropertiesTable {
})?; parameter: Default::default(),
fields: fields.clone(),
},
)?;
if view.columns.len() != columns.len() if view.columns.len() != columns.len()
|| view.columns.iter().zip(columns).any(|(old, new)| { || view.columns.iter().zip(columns).any(|(old, new)| {
old.handle != new.handle old.handle != new.handle
|| old.connection_name != new.connection_name || old.connection_name != new.connection_name
|| old.type_code != new.type_code || (old.type_code != new.type_code
&& old.node_id != 0
&& old.node_id != new.node_id)
}) })
{ {
return Err("Changing Block Properties Table columns is not supported for an existing table."); return Err(
"Changing Block Properties Table columns is not supported for an existing table.",
);
} }
fields.truncate(view.row_start - 1); fields.truncate(view.row_start - 1);
for column in columns {
if let Some(position) = fields.iter().enumerate().skip(4).find_map(|(i, f)|
(f.code == 340 && matches!(f.value, BlockPropertiesTableValue::Handle(h) if h == column.handle)).then_some(i)) {
let end = fields.iter().enumerate().skip(position + 1).find(|(_, f)| f.code == 340).map(|(i, _)| i).unwrap_or(fields.len());
if let Some(field) = fields.iter_mut().take(end).skip(position + 1).find(|f| f.code == 90) {
field.value = BlockPropertiesTableValue::Integer(i64::from(column.node_id));
}
if let Some(field) = fields.iter_mut().take(end).skip(position + 1).find(|f| f.code == 293) {
field.value = BlockPropertiesTableValue::Boolean(column.connection_name.eq_ignore_ascii_case("UpdatedAngle"));
}
}
}
} }
fields.push(int(92, rows.len() as i64)); fields.push(int(92, rows.len() as i64));
for (row_index, row) in rows.iter().enumerate() { for (row_index, row) in rows.iter().enumerate() {
@@ -389,7 +538,7 @@ fn encode_fields(
} }
fields.push(int(93, rows.len() as i64)); fields.push(int(93, rows.len() as i64));
fields.extend([ fields.extend([
boolean(290, false), boolean(290, must_match),
boolean(291, true), boolean(291, true),
boolean(292, false), boolean(292, false),
]); ]);
+13 -6
View File
@@ -15,6 +15,7 @@ pub enum Action {
AddRow, AddRow,
DeleteRow(usize), DeleteRow(usize),
Apply, Apply,
MustMatch(bool),
} }
pub(crate) struct State { pub(crate) struct State {
@@ -22,6 +23,7 @@ pub(crate) struct State {
pub definition: Handle, pub definition: Handle,
pub epoch: u64, pub epoch: u64,
pub original: Option<TableView>, pub original: Option<TableView>,
pub must_match: bool,
pub columns: Vec<Column>, pub columns: Vec<Column>,
pub properties: Vec<Column>, pub properties: Vec<Column>,
pub selected_property: Option<usize>, pub selected_property: Option<usize>,
@@ -72,13 +74,16 @@ impl State {
} }
} }
Action::Cell(row, col, value) Action::Cell(row, col, value)
if row < self.rows.len() && col < self.columns.len() && col < self.rows[row].len() => if row < self.rows.len()
&& col < self.columns.len()
&& col < self.rows[row].len() =>
{ {
self.rows[row][col] = value; self.rows[row][col] = value;
} }
Action::AddRow if !self.columns.is_empty() && self.rows.len() < 1000 => { Action::AddRow if !self.columns.is_empty() && self.rows.len() < 1000 => {
self.rows.push(vec!["0".into(); self.columns.len()]); self.rows.push(vec!["0".into(); self.columns.len()]);
} }
Action::MustMatch(value) => self.must_match = value,
Action::DeleteRow(row) if row < self.rows.len() => { Action::DeleteRow(row) if row < self.rows.len() => {
self.rows.remove(row); self.rows.remove(row);
} }
@@ -93,7 +98,7 @@ pub(crate) fn view(state: &State, sizing: crate::ui::modal::ModalSizing) -> Elem
let rows = state.rows.len(); let rows = state.rows.len();
let mut headings = row![text(" ").width(28)].spacing(6); let mut headings = row![text(" ").width(28)].spacing(6);
for (index, column_binding) in state.columns.iter().enumerate() { for (index, column_binding) in state.columns.iter().enumerate() {
let mut header = column![text(column_binding.connection_name.clone()).size(13)]; let mut header = column![text(column_binding.display_name.clone()).size(13)];
if state.original.is_none() { if state.original.is_none() {
header = header.push( header = header.push(
button(text(crate::t!("Remove").into_owned()).size(11)) button(text(crate::t!("Remove").into_owned()).size(11))
@@ -136,7 +141,7 @@ pub(crate) fn view(state: &State, sizing: crate::ui::modal::ModalSizing) -> Elem
row![ row![
pick_list(state.selected_property, available, |i: &usize| state pick_list(state.selected_property, available, |i: &usize| state
.properties[*i] .properties[*i]
.connection_name .display_name
.clone()) .clone())
.on_select(move |i| send(Action::Property(i))) .on_select(move |i| send(Action::Property(i)))
.placeholder(crate::t!("Choose input property").into_owned()) .placeholder(crate::t!("Choose input property").into_owned())
@@ -158,12 +163,14 @@ pub(crate) fn view(state: &State, sizing: crate::ui::modal::ModalSizing) -> Elem
.height(Fill) .height(Fill)
.width(Fill), .width(Fill),
row![ row![
button(text(crate::t!("Add Row").into_owned())).on_press_maybe( button(text(crate::t!("Add Row").into_owned()))
(cols > 0 && rows < 1000).then_some(send(Action::AddRow)) .on_press_maybe((cols > 0 && rows < 1000).then_some(send(Action::AddRow))),
),
Space::new().width(Fill) Space::new().width(Fill)
] ]
.spacing(8), .spacing(8),
iced::widget::checkbox(state.must_match)
.label(crate::t!("Block properties must match a row in the table").into_owned())
.on_toggle(move |value| send(Action::MustMatch(value))),
text(state.error.clone().unwrap_or_default()) text(state.error.clone().unwrap_or_default())
.size(12) .size(12)
.style(text::danger), .style(text::danger),