forked from 1b-agent/opencadstudio-upstream
feat(blocks): author Block Properties Table from the block editor
Add the BPROPERTIESTABLE command, the Block Authoring palette row and the grid dialog. The row needs an open block edit session with a Linear or Polar parameter column; OK parses cells to finite numbers, re-checks the table against the session it was opened from, writes it in one undo step, and no-op edits close without an undo.
This commit is contained in:
1 parent
2b0c1a6b58
commit
a1de7e4ecd
13 files changed
+865
-4
No files matched your search
@@ -188,12 +188,14 @@ const PARAMETER_SETS: &[(&str, &[u8])] = &[
|
||||
];
|
||||
|
||||
/// Catalog rows remain inert until their authoring commands exist.
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn catalog(
|
||||
tab: Tab,
|
||||
visibility_error: Option<&'static str>,
|
||||
point_error: Option<&'static str>,
|
||||
flip_error: Option<&'static str>,
|
||||
lookup_error: Option<&'static str>,
|
||||
properties_table_error: Option<&'static str>,
|
||||
move_error: Option<&'static str>,
|
||||
scale_error: Option<&'static str>,
|
||||
rotate_error: Option<&'static str>,
|
||||
@@ -222,15 +224,16 @@ fn catalog(
|
||||
let orientation_error = if *label=="Flip" {flip_error} else {point_error};
|
||||
let single=tab==Tab::Parameters&&matches!(*label,"Lookup"|"Basepoint");
|
||||
let lookup_action=tab==Tab::Actions&&*label=="Lookup";
|
||||
let properties_table_action=tab==Tab::Actions&&*label=="Block Properties Table";
|
||||
let geometry_action=tab==Tab::Actions&&matches!(*label,"Move"|"Scale"|"Stretch"|"Polar Stretch"|"Rotate"|"Flip"|"Array");
|
||||
let action_error=match *label {"Scale"=>scale_error,"Stretch"=>stretch_action_error,"Polar Stretch"=>polar_stretch_action_error,"Rotate"=>rotate_error,"Flip"=>flip_action_error,"Array"=>array_action_error,_=>move_error};
|
||||
let enabled = (geometry_action&&action_error.is_none())||(single&&point_error.is_none())||(lookup_action&&lookup_error.is_none())||(orientation && orientation_error.is_none()) || (visibility && visibility_error.is_none()) || ((point || distance) && point_error.is_none());
|
||||
let enabled = (geometry_action&&action_error.is_none())||(single&&point_error.is_none())||(lookup_action&&lookup_error.is_none())||(properties_table_action&&properties_table_error.is_none())||(orientation && orientation_error.is_none()) || (visibility && visibility_error.is_none()) || ((point || distance) && point_error.is_none());
|
||||
let base = if enabled {
|
||||
icons::semantic(icon, 28.0)
|
||||
} else {
|
||||
icons::semantic_disabled(icon, 28.0)
|
||||
};
|
||||
let icon = if tab != Tab::Parameters && *label != "Block Properties Table" {
|
||||
let icon = if tab != Tab::Parameters {
|
||||
stack![
|
||||
base,
|
||||
container(if enabled {
|
||||
@@ -248,8 +251,8 @@ fn catalog(
|
||||
base
|
||||
};
|
||||
let message = enabled.then(|| Message::RibbonToolClick {
|
||||
tool_id: if geometry_action {format!("BACTION_{}",label.to_ascii_uppercase())}else if lookup_action {"BLOOKUPTABLE".into()}else{format!("BPARAMETER_{}", label.to_ascii_uppercase())},
|
||||
event: crate::modules::ModuleEvent::Command(if geometry_action {format!("BACTION {}",label.to_ascii_uppercase())}else if lookup_action {"BLOOKUPTABLE".into()}else{format!("BPARAMETER {}", label.to_ascii_uppercase())}),
|
||||
tool_id: if geometry_action {format!("BACTION_{}",label.to_ascii_uppercase())}else if lookup_action {"BLOOKUPTABLE".into()}else if properties_table_action {"BPROPERTIESTABLE".into()}else{format!("BPARAMETER_{}", label.to_ascii_uppercase())},
|
||||
event: crate::modules::ModuleEvent::Command(if geometry_action {format!("BACTION {}",label.to_ascii_uppercase())}else if lookup_action {"BLOOKUPTABLE".into()}else if properties_table_action {"BPROPERTIESTABLE".into()}else{format!("BPARAMETER {}", label.to_ascii_uppercase())}),
|
||||
});
|
||||
let description = if visibility {
|
||||
crate::t!(visibility_error
|
||||
@@ -272,6 +275,8 @@ fn catalog(
|
||||
crate::t!(action_error.unwrap_or(if *label=="Stretch" {"Select a parameter endpoint, define the stretch frame, select its objects, then place the action marker."}else if *label=="Array" {"Select a parameter endpoint, select the objects to array, specify the column offset, then place the action marker."}else{"Select a parameter endpoint, select the objects it controls, then place the action marker. Use the reference parameter grip in the drawing or Test Block."})).into_owned()
|
||||
} else if lookup_action {
|
||||
crate::t!(lookup_error.unwrap_or("Create or edit property Lookup tables for this block.")).into_owned()
|
||||
} else if properties_table_action {
|
||||
crate::t!(properties_table_error.unwrap_or("Create or edit this block's properties table; rows bind Linear and Polar parameters.")).into_owned()
|
||||
} else {
|
||||
unavailable.to_string()
|
||||
};
|
||||
@@ -290,6 +295,8 @@ fn catalog(
|
||||
container(entry).id(match *label {"Move"=>"authoring-tool-move-action","Scale"=>"authoring-tool-scale-action","Rotate"=>"authoring-tool-rotate-action","Stretch"=>"authoring-tool-stretch-action","Array"=>"authoring-tool-array-action",_=>"authoring-tool-flip-action"}).into()
|
||||
} else if lookup_action {
|
||||
container(entry).id("authoring-tool-lookup-table").into()
|
||||
} else if properties_table_action {
|
||||
container(entry).id("authoring-tool-properties-table").into()
|
||||
} else {
|
||||
entry
|
||||
}
|
||||
@@ -396,6 +403,7 @@ fn constraints() -> Element<'static, Message> {
|
||||
.into()
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn view(
|
||||
selected_tab: Tab,
|
||||
width: f32,
|
||||
@@ -404,6 +412,7 @@ pub fn view(
|
||||
point_error: Option<&'static str>,
|
||||
flip_error: Option<&'static str>,
|
||||
lookup_error: Option<&'static str>,
|
||||
properties_table_error: Option<&'static str>,
|
||||
move_error: Option<&'static str>,
|
||||
scale_error: Option<&'static str>,
|
||||
rotate_error: Option<&'static str>,
|
||||
@@ -466,6 +475,7 @@ pub fn view(
|
||||
point_error,
|
||||
flip_error,
|
||||
lookup_error,
|
||||
properties_table_error,
|
||||
move_error,
|
||||
scale_error,
|
||||
rotate_error,
|
||||
|
||||
@@ -0,0 +1,186 @@
|
||||
use crate::app::Message;
|
||||
use crate::scene::block_properties_table::{Column, TableView};
|
||||
use codec::Handle;
|
||||
use iced::{
|
||||
widget::{button, column, container, pick_list, row, scrollable, text, text_input, Space},
|
||||
Element, Fill,
|
||||
};
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum Action {
|
||||
Property(usize),
|
||||
AddProperty,
|
||||
RemoveProperty(usize),
|
||||
Cell(usize, usize, String),
|
||||
AddRow,
|
||||
DeleteRow(usize),
|
||||
Apply,
|
||||
}
|
||||
|
||||
pub(crate) struct State {
|
||||
pub document: u64,
|
||||
pub definition: Handle,
|
||||
pub epoch: u64,
|
||||
pub original: Option<TableView>,
|
||||
pub columns: Vec<Column>,
|
||||
pub properties: Vec<Column>,
|
||||
pub selected_property: Option<usize>,
|
||||
pub rows: Vec<Vec<String>>,
|
||||
pub error: Option<String>,
|
||||
pub notice: Option<String>,
|
||||
}
|
||||
|
||||
impl State {
|
||||
pub(crate) fn edit(&mut self, action: Action) {
|
||||
self.error = None;
|
||||
self.notice = None;
|
||||
match action {
|
||||
Action::Property(index) if index < self.properties.len() => {
|
||||
self.selected_property = Some(index);
|
||||
return;
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
match action {
|
||||
Action::AddProperty => {
|
||||
let Some(property) = self.selected_property.and_then(|i| self.properties.get(i))
|
||||
else {
|
||||
return;
|
||||
};
|
||||
if self.columns.len() >= 64
|
||||
|| self.columns.iter().any(|c| {
|
||||
c.node_id == property.node_id
|
||||
&& c.connection_name == property.connection_name
|
||||
})
|
||||
{
|
||||
return;
|
||||
}
|
||||
for row in &mut self.rows {
|
||||
row.push("0".into());
|
||||
}
|
||||
self.columns.push(property.clone());
|
||||
self.selected_property = None;
|
||||
}
|
||||
Action::RemoveProperty(index)
|
||||
if self.original.is_none() && index < self.columns.len() =>
|
||||
{
|
||||
self.columns.remove(index);
|
||||
for row in &mut self.rows {
|
||||
if index < row.len() {
|
||||
row.remove(index);
|
||||
}
|
||||
}
|
||||
}
|
||||
Action::Cell(row, col, value)
|
||||
if row < self.rows.len() && col < self.columns.len() && col < self.rows[row].len() =>
|
||||
{
|
||||
self.rows[row][col] = value;
|
||||
}
|
||||
Action::AddRow if !self.columns.is_empty() && self.rows.len() < 1000 => {
|
||||
self.rows.push(vec!["0".into(); self.columns.len()]);
|
||||
}
|
||||
Action::DeleteRow(row) if row < self.rows.len() => {
|
||||
self.rows.remove(row);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn view(state: &State, sizing: crate::ui::modal::ModalSizing) -> Element<'_, Message> {
|
||||
let send = |a| Message::BlockPropertiesTable(a);
|
||||
let cols = state.columns.len();
|
||||
let rows = state.rows.len();
|
||||
let mut headings = row![text(" ").width(28)].spacing(6);
|
||||
for (index, column_binding) in state.columns.iter().enumerate() {
|
||||
let mut header = column![text(column_binding.connection_name.clone()).size(13)];
|
||||
if state.original.is_none() {
|
||||
header = header.push(
|
||||
button(text(crate::t!("Remove").into_owned()).size(11))
|
||||
.on_press(send(Action::RemoveProperty(index))),
|
||||
);
|
||||
}
|
||||
headings = headings.push(container(header.spacing(4)).width(160));
|
||||
}
|
||||
let mut grid = column![headings].spacing(7);
|
||||
for (row_index, row_values) in state.rows.iter().enumerate() {
|
||||
let mut cells = row![text((row_index + 1).to_string()).width(28)].spacing(6);
|
||||
for col_index in 0..cols {
|
||||
cells = cells.push(
|
||||
text_input("", row_values.get(col_index).map_or("", String::as_str))
|
||||
.on_input(move |v| send(Action::Cell(row_index, col_index, v)))
|
||||
.width(160)
|
||||
.style(crate::ui::style::form::field_style),
|
||||
);
|
||||
}
|
||||
cells = cells.push(
|
||||
button(text(crate::t!("Delete").into_owned()))
|
||||
.on_press(send(Action::DeleteRow(row_index))),
|
||||
);
|
||||
grid = grid.push(cells);
|
||||
}
|
||||
let available = state
|
||||
.properties
|
||||
.iter()
|
||||
.enumerate()
|
||||
.filter(|(_, p)| {
|
||||
!state
|
||||
.columns
|
||||
.iter()
|
||||
.any(|c| c.node_id == p.node_id && c.connection_name == p.connection_name)
|
||||
})
|
||||
.map(|(i, _)| i)
|
||||
.collect::<Vec<_>>();
|
||||
container(
|
||||
column![
|
||||
row![
|
||||
pick_list(state.selected_property, available, |i: &usize| state
|
||||
.properties[*i]
|
||||
.connection_name
|
||||
.clone())
|
||||
.on_select(move |i| send(Action::Property(i)))
|
||||
.placeholder(crate::t!("Choose input property").into_owned())
|
||||
.width(Fill),
|
||||
button(text(crate::t!("Add Property").into_owned()))
|
||||
.on_press_maybe(state.selected_property.map(|_| send(Action::AddProperty)))
|
||||
]
|
||||
.spacing(8),
|
||||
text(
|
||||
crate::t!("Bind Linear or Polar parameters. Existing table columns are retained.")
|
||||
.into_owned()
|
||||
)
|
||||
.size(12),
|
||||
scrollable(grid)
|
||||
.direction(scrollable::Direction::Both {
|
||||
vertical: Default::default(),
|
||||
horizontal: Default::default()
|
||||
})
|
||||
.height(Fill)
|
||||
.width(Fill),
|
||||
row![
|
||||
button(text(crate::t!("Add Row").into_owned())).on_press_maybe(
|
||||
(cols > 0 && rows < 1000).then_some(send(Action::AddRow))
|
||||
),
|
||||
Space::new().width(Fill)
|
||||
]
|
||||
.spacing(8),
|
||||
text(state.error.clone().unwrap_or_default())
|
||||
.size(12)
|
||||
.style(text::danger),
|
||||
text(state.notice.clone().unwrap_or_default()).size(12),
|
||||
row![
|
||||
Space::new().width(Fill),
|
||||
button(text(crate::t!("OK").into_owned()))
|
||||
.on_press(send(Action::Apply))
|
||||
.style(button::primary),
|
||||
button(text(crate::t!("Cancel").into_owned())).on_press(Message::CloseModal)
|
||||
]
|
||||
.spacing(8),
|
||||
]
|
||||
.spacing(12),
|
||||
)
|
||||
.padding(16)
|
||||
.width(sizing.width)
|
||||
.height(sizing.height)
|
||||
.into()
|
||||
}
|
||||
@@ -12,6 +12,7 @@ pub mod block_palette;
|
||||
pub mod block_save_as;
|
||||
pub mod block_visibility;
|
||||
pub mod block_lookup;
|
||||
pub mod block_properties_table;
|
||||
pub mod browser;
|
||||
pub mod count_palette;
|
||||
pub mod drafting_settings;
|
||||
|
||||
Reference in new issue
Block a user