Align table properties and editing behavior

This commit is contained in:
ramox81 2026-08-24 18:09:34 +03:00
commit 95279d10c0
8 changed files with 1629 additions and 235 deletions

2
Cargo.lock generated
View file

@ -72,7 +72,7 @@ checksum = "366ffbaa4442f4684d91e2cd7c5ea7c4ed8add41959a31447066e279e432b618"
[[package]]
name = "acadrust"
version = "0.4.1"
source = "git+https://github.com/HakanSeven12/cadcodec.git?rev=94df2c3#94df2c3f87fa051b16ffc3923f80e9247c85c5fd"
source = "git+https://github.com/ramox81/cadcodec.git?rev=1fa0a5e#1fa0a5e22f53dd45c6b0b9d8c63f7fc7730a3a8f"
dependencies = [
"ahash 0.8.12",
"anyhow",

View file

@ -27,7 +27,7 @@ glam = { version = "0.33", features = ["bytemuck"] }
rfd = "0.17"
clap = { version = "4", features = ["derive"] }
env_logger = "0.11"
acadrust = { git = "https://github.com/HakanSeven12/cadcodec.git", rev = "94df2c3", features = ["serde"] }
acadrust = { git = "https://github.com/ramox81/cadcodec.git", rev = "1fa0a5e", features = ["serde"] }
cadkernel = { git = "https://github.com/HakanSeven12/cadkernel.git", rev = "76de3b3", features = ["acis", "offset"] }
dwg-thumbnailer = { path = "crates/dwg-thumbnailer" }
flate2 = "1"
@ -60,6 +60,9 @@ ashpd = { version = "0.13.13", default-features = false, features = ["async-io",
iced_core = { git = "https://github.com/iced-rs/iced.git", rev = "23604ff22ab0aad9e00b9327cb7b8546ed84db39" }
iced_widget = { git = "https://github.com/iced-rs/iced.git", rev = "23604ff22ab0aad9e00b9327cb7b8546ed84db39" }
[patch."https://github.com/HakanSeven12/cadcodec.git"]
acadrust = { git = "https://github.com/ramox81/cadcodec.git", rev = "1fa0a5e" }
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
ocs_plugin_api = { path = "crates/ocs_plugin_api", features = ["host"] }
meshopt = "0.6.2"

View file

@ -144,6 +144,7 @@ impl OpenCADStudio {
};
crate::scene::view::dispatch::set_prop_current_vertex(prop_vertex);
crate::entities::table::set_prop_current_cell(prop_vertex);
crate::entities::table::set_prop_current_cell_active(prop_vertex_indicator_active);
let annotation_scale_handle = self.tabs[i].scene.displayed_annotation_scale_handle();
let new_panel = {
@ -1299,6 +1300,10 @@ impl OpenCADStudio {
}
}
acadrust::EntityType::Table(table) => {
use acadrust::entities::table::{
CellEdgeFlags, CellStylePropertyFlags, CellValueType,
};
let mut names: Vec<String> = doc
.objects
.values()
@ -1313,15 +1318,17 @@ impl OpenCADStudio {
.collect();
names.sort_by_key(|name| name.to_ascii_lowercase());
names.dedup_by(|a, b| a.eq_ignore_ascii_case(b));
let selected = table
let selected_style = table
.table_style_handle
.and_then(|handle| doc.objects.get(&handle))
.and_then(|object| match object {
acadrust::objects::ObjectType::TableStyle(style) => {
Some(style.name.clone())
Some(style)
}
_ => None,
})
});
let selected = selected_style
.map(|style| style.name.clone())
.unwrap_or_else(|| "Standard".to_string());
if !names.iter().any(|name| name.eq_ignore_ascii_case(&selected)) {
names.insert(0, selected.clone());
@ -1334,6 +1341,311 @@ impl OpenCADStudio {
options: names,
},
);
let title_suppressed =
crate::entities::table::resolved_title_suppressed(
table,
selected_style,
);
let header_suppressed =
crate::entities::table::resolved_header_suppressed(
table,
selected_style,
);
let flow_up = crate::entities::table::resolved_flow_up(
table,
selected_style,
);
let (horizontal_margin, vertical_margin) =
crate::entities::table::resolved_table_margins(
table,
selected_style,
);
update_row_toggle(
&mut sections,
"tbl_title_suppressed",
title_suppressed,
);
update_row_toggle(
&mut sections,
"tbl_header_suppressed",
header_suppressed,
);
update_row_text(
&mut sections,
"tbl_flow_direction",
if flow_up { "Up" } else { "Down" }.to_string(),
);
update_row_text(
&mut sections,
"tbl_horizontal_margin",
crate::entities::common::format_length(horizontal_margin),
);
update_row_text(
&mut sections,
"tbl_vertical_margin",
crate::entities::common::format_length(vertical_margin),
);
let columns = table.column_count();
if columns > 0 {
let cell_index = prop_vertex.min(
table.row_count()
.saturating_mul(columns)
.saturating_sub(1),
);
let row_index = cell_index / columns;
let column_index = cell_index % columns;
if let (Some(row), Some(cell)) = (
table.rows.get(row_index),
table.cell(row_index, column_index),
) {
let document_row_style = selected_style.map(|style| {
let kind = match (
title_suppressed,
header_suppressed,
row_index,
) {
(false, _, 0) => 0,
(false, false, 1) | (true, false, 0) => 1,
_ => 2,
};
match kind {
0 => &style.title_row_style,
1 => &style.header_row_style,
_ => &style.data_row_style,
}
});
let local = |property| {
crate::entities::table::style_for_property(
table,
row,
column_index,
cell,
property,
)
};
let alignment = local(CellStylePropertyFlags::ALIGNMENT)
.map(|style| style.alignment)
.or_else(|| {
document_row_style.map(|style| style.alignment as i32)
})
.unwrap_or(5);
let alignment = match alignment {
1 => "Top Left",
2 => "Top Center",
3 => "Top Right",
4 => "Middle Left",
6 => "Middle Right",
7 => "Bottom Left",
8 => "Bottom Center",
9 => "Bottom Right",
_ => "Middle Center",
};
update_row_text(
&mut sections,
"tbl_cell_alignment",
alignment.to_string(),
);
let text_style_name = local(CellStylePropertyFlags::TEXT_STYLE)
.map(|style| style.text_style_name.clone())
.filter(|name| !name.is_empty())
.or_else(|| {
document_row_style
.map(|style| style.text_style_name.clone())
.filter(|name| !name.is_empty())
})
.unwrap_or_else(|| "Standard".to_string());
update_row_text(
&mut sections,
"tbl_cell_text_style",
text_style_name,
);
let text_height = local(CellStylePropertyFlags::TEXT_HEIGHT)
.map(|style| style.text_height)
.or_else(|| {
document_row_style.map(|style| style.text_height)
})
.unwrap_or(0.18);
update_row_text(
&mut sections,
"tbl_cell_text_height",
crate::entities::common::format_length(text_height),
);
let content_color = local(
CellStylePropertyFlags::CONTENT_COLOR,
)
.map(|style| style.content_color)
.or_else(|| {
document_row_style.map(|style| style.text_color)
})
.unwrap_or(acadrust::types::Color::ByBlock);
update_row_color(
&mut sections,
"tbl_cell_content_color",
content_color,
);
let background_style =
local(CellStylePropertyFlags::BACKGROUND_COLOR);
let background_color = background_style
.map(|style| style.background_color)
.or_else(|| {
document_row_style.map(|style| style.fill_color)
})
.unwrap_or(acadrust::types::Color::ByBlock);
update_row_color(
&mut sections,
"tbl_cell_background_color",
background_color,
);
update_row_toggle(
&mut sections,
"tbl_cell_fill",
background_style
.map(|style| style.fill_enabled)
.or_else(|| {
document_row_style
.map(|style| style.fill_enabled)
})
.unwrap_or(false),
);
let format = local(CellStylePropertyFlags::DATA_FORMAT)
.map(|style| style.value_format.clone())
.or_else(|| {
document_row_style
.map(|style| style.format_string.clone())
})
.unwrap_or_default();
update_row_text(
&mut sections,
"tbl_cell_format",
format,
);
let data_type = cell
.contents
.first()
.map(|content| content.value.value_type)
.filter(|kind| *kind != CellValueType::Unknown)
.or_else(|| {
local(CellStylePropertyFlags::DATA_TYPE)
.map(|style| {
CellValueType::from(
style.value_data_type.max(0) as u32,
)
})
})
.or_else(|| {
document_row_style.map(|style| {
CellValueType::from(style.data_type.max(0) as u32)
})
})
.unwrap_or(CellValueType::String);
let data_type = match data_type {
CellValueType::Long => "Integer",
CellValueType::Double => "Decimal",
CellValueType::Date => "Date",
CellValueType::Point2D => "Point 2D",
CellValueType::Point3D => "Point 3D",
CellValueType::Handle => "Handle",
_ => "Text",
};
update_row_text(
&mut sections,
"tbl_cell_data_type",
data_type.to_string(),
);
for (field, property, fallback) in [
(
"tbl_cell_margin_left",
CellStylePropertyFlags::MARGIN_LEFT,
horizontal_margin,
),
(
"tbl_cell_margin_top",
CellStylePropertyFlags::MARGIN_TOP,
vertical_margin,
),
(
"tbl_cell_margin_right",
CellStylePropertyFlags::MARGIN_RIGHT,
horizontal_margin,
),
(
"tbl_cell_margin_bottom",
CellStylePropertyFlags::MARGIN_BOTTOM,
vertical_margin,
),
] {
let value = local(property)
.map(|style| match field {
"tbl_cell_margin_left" => style.margin_left,
"tbl_cell_margin_top" => style.margin_top,
"tbl_cell_margin_right" => style.margin_right,
_ => style.margin_bottom,
})
.unwrap_or(fallback);
update_row_text(
&mut sections,
field,
crate::entities::common::format_length(value),
);
}
let border_visible = |edge: CellEdgeFlags| {
let local_style = [
cell.style.as_ref(),
row.style.as_ref(),
table
.columns
.get(column_index)
.and_then(|column| column.style.as_ref()),
table.base_style.as_ref(),
]
.into_iter()
.flatten()
.find(|style| {
style.applied_border_edges.contains(edge)
});
if let Some(style) = local_style {
return if edge == CellEdgeFlags::TOP {
!style.top_border.invisible
} else if edge == CellEdgeFlags::RIGHT {
!style.right_border.invisible
} else if edge == CellEdgeFlags::BOTTOM {
!style.bottom_border.invisible
} else {
!style.left_border.invisible
};
}
document_row_style
.map(|style| {
if edge == CellEdgeFlags::TOP {
!style.top_border.is_invisible
} else if edge == CellEdgeFlags::RIGHT {
!style.right_border.is_invisible
} else if edge == CellEdgeFlags::BOTTOM {
!style.bottom_border.is_invisible
} else {
!style.left_border.is_invisible
}
})
.unwrap_or(true)
};
for (field, edge) in [
("tbl_cell_border_top", CellEdgeFlags::TOP),
("tbl_cell_border_right", CellEdgeFlags::RIGHT),
("tbl_cell_border_bottom", CellEdgeFlags::BOTTOM),
("tbl_cell_border_left", CellEdgeFlags::LEFT),
] {
update_row_toggle(
&mut sections,
field,
border_visible(edge),
);
}
}
}
}
_ => {}
}
@ -2469,6 +2781,79 @@ fn set_row_value(
}
}
fn update_row_text(
sections: &mut [crate::scene::model::object::PropSection],
field: &str,
value: String,
) {
use crate::scene::model::object::PropValue;
for section in sections.iter_mut() {
let Some(row) = section.props.iter_mut().find(|property| property.field == field) else {
continue;
};
match &mut row.value {
PropValue::ReadOnly(current)
| PropValue::EditText(current)
| PropValue::PlainText(current) => *current = value,
PropValue::ReadOnlyWithTooltip { value: current, .. } => *current = value,
PropValue::Choice { selected, .. } => *selected = value,
_ => {}
}
return;
}
}
fn update_row_toggle(
sections: &mut [crate::scene::model::object::PropSection],
field: &str,
value: bool,
) {
use crate::scene::model::object::PropValue;
for section in sections.iter_mut() {
let Some(row) = section.props.iter_mut().find(|property| property.field == field) else {
continue;
};
match &mut row.value {
PropValue::BoolToggle { value: current, .. } => *current = value,
PropValue::ReadOnly(current) => {
*current = if value { t!("Yes") } else { t!("No") }.into_owned()
}
PropValue::ReadOnlyWithTooltip { value: current, .. } => {
*current = if value { t!("Yes") } else { t!("No") }.into_owned()
}
_ => {}
}
return;
}
}
fn update_row_color(
sections: &mut [crate::scene::model::object::PropSection],
field: &str,
color: acadrust::types::Color,
) {
use crate::scene::model::object::PropValue;
let label = match color {
acadrust::types::Color::None => "None".to_string(),
acadrust::types::Color::ByLayer => "ByLayer".to_string(),
acadrust::types::Color::ByBlock => "ByBlock".to_string(),
acadrust::types::Color::Index(index) => index.to_string(),
acadrust::types::Color::Rgb { r, g, b } => format!("{r},{g},{b}"),
};
for section in sections.iter_mut() {
let Some(row) = section.props.iter_mut().find(|property| property.field == field) else {
continue;
};
match &mut row.value {
PropValue::ColorChoice(current) => *current = color,
PropValue::ReadOnly(current) => *current = label,
PropValue::ReadOnlyWithTooltip { value: current, .. } => *current = label,
_ => {}
}
return;
}
}
/// The lineweight dropdown options (named defaults + the standard millimetre
/// steps), matching the labels `dim_lineweight_label` produces.
pub(crate) fn lineweight_options() -> Vec<String> {

View file

@ -2109,6 +2109,29 @@ pub(super) fn on_tab_close(&mut self, idx: usize) -> Task<Message> {
self.tabs[i].scene.bump_entities(&changes);
}
}
} else if field == "tbl_style_handle" {
let style_handle = self.tabs[i]
.scene
.document
.objects
.iter()
.find_map(|(handle, object)| match object {
acadrust::objects::ObjectType::TableStyle(style)
if style.name.eq_ignore_ascii_case(value.trim()) =>
{
Some(*handle)
}
_ => None,
});
if let Some(style_handle) = style_handle {
for &handle in &handles {
if let Some(acadrust::EntityType::Table(table)) =
self.tabs[i].scene.document.get_entity_mut(handle)
{
table.table_style_handle = Some(style_handle);
}
}
}
} else if field == "transparency" {
for &handle in &handles {
if self.tabs[i].scene.is_layer_locked(handle) {
@ -2248,6 +2271,15 @@ pub(super) fn on_tab_close(&mut self, idx: usize) -> Task<Message> {
}
}
}
if field.starts_with("tbl_") {
for &handle in &handles {
if let Some(acadrust::EntityType::Table(table)) =
self.tabs[i].scene.document.get_entity_mut(handle)
{
table.block_record_handle = None;
}
}
}
self.invalidate_property_targets(i, &handles);
self.tabs[i].dirty = true;
self.tabs[i].properties.edit_choice_open = false;
@ -2555,6 +2587,15 @@ pub(super) fn on_tab_close(&mut self, idx: usize) -> Task<Message> {
}
}
}
if field.starts_with("tbl_") {
for &handle in &handles {
if let Some(acadrust::EntityType::Table(table)) =
self.tabs[i].scene.document.get_entity_mut(handle)
{
table.block_record_handle = None;
}
}
}
self.invalidate_property_targets(i, &handles);
self.tabs[i].dirty = true;
self.refresh_properties();

View file

@ -4651,6 +4651,47 @@ impl OpenCADStudio {
}
}
}
"tbl_title_suppressed" | "tbl_header_suppressed" => {
let next = {
let document = &self.tabs[i].scene.document;
let Some(acadrust::EntityType::Table(table)) =
document.get_entity(handle)
else {
continue;
};
let table_style = table.table_style_handle.and_then(|style_handle| {
document.objects.get(&style_handle).and_then(|object| {
match object {
acadrust::objects::ObjectType::TableStyle(style) => {
Some(style)
}
_ => None,
}
})
});
let current = if field == "tbl_title_suppressed" {
crate::entities::table::resolved_title_suppressed(
table,
table_style,
)
} else {
crate::entities::table::resolved_header_suppressed(
table,
table_style,
)
};
!current
};
if let Some(entity) =
self.tabs[i].scene.document.get_entity_mut(handle)
{
crate::scene::view::dispatch::apply_geom_prop(
entity,
field,
if next { "true" } else { "false" },
);
}
}
_ => {
if let Some(entity) =
self.tabs[i].scene.document.get_entity_mut(handle)
@ -4662,6 +4703,15 @@ impl OpenCADStudio {
}
}
}
if field.starts_with("tbl_") {
for &handle in &handles {
if let Some(acadrust::EntityType::Table(table)) =
self.tabs[i].scene.document.get_entity_mut(handle)
{
table.block_record_handle = None;
}
}
}
self.invalidate_property_targets(i, &handles);
self.tabs[i].dirty = true;
self.refresh_properties();
@ -4889,6 +4939,13 @@ impl OpenCADStudio {
cell_index / columns,
cell_index % columns,
) {
use acadrust::entities::table::CellStateFlags;
if cell.state.intersects(
CellStateFlags::FORMAT_LOCKED
| CellStateFlags::FORMAT_READ_ONLY,
) {
continue;
}
let style = cell.style.get_or_insert_with(Default::default);
if field == "tbl_cell_content_color" {
style.content_color = color;
@ -4903,6 +4960,7 @@ impl OpenCADStudio {
);
}
}
table.block_record_handle = None;
}
self.invalidate_property_targets(i, &handles);
self.tabs[i].properties.open_color_field = None;

View file

@ -4412,25 +4412,23 @@ impl OpenCADStudio {
let mut down = horizontal
.cross(normal)
.normalize_or(glam::DVec3::NEG_Y);
let flows_up = table
.table_style_handle
.and_then(|style_handle| {
let table_style = table.table_style_handle.and_then(|style_handle| {
self.tabs[i]
.scene
.document
.objects
.get(&style_handle)
.and_then(|object| match object {
acadrust::objects::ObjectType::TableStyle(style) => {
Some(style)
}
_ => None,
})
.is_some_and(|object| {
matches!(
object,
acadrust::objects::ObjectType::TableStyle(style)
if matches!(
style.flow_direction,
acadrust::objects::TableFlowDirection::Up
)
)
});
let flows_up = crate::entities::table::resolved_flow_up(
table,
table_style,
);
if flows_up {
down = -down;
}
@ -4461,10 +4459,19 @@ impl OpenCADStudio {
Some(*offset)
})
.position(|end| y <= end)?;
let locked = table.cell(row, column).is_some_and(|cell| {
use acadrust::entities::table::CellStateFlags;
cell.state.intersects(
CellStateFlags::CONTENT_LOCKED
| CellStateFlags::CONTENT_READ_ONLY,
)
});
Some((
(!locked).then(|| {
crate::modules::annotate::table_cmd::TableCellEditCommand::new(
handle, table, row, column,
),
)
}),
row * table.column_count() + column,
))
}
@ -4475,9 +4482,11 @@ impl OpenCADStudio {
self.tabs[i].properties.prop_vertex_indicator_active = true;
crate::entities::table::set_prop_current_cell(cell_index);
self.refresh_properties();
if let Some(command) = command {
self.command_line
.push_info(&crate::command::CadCommand::prompt(&command));
self.tabs[i].active_cmd = Some(Box::new(command));
}
return Task::none();
}
// Any text-bearing entity opens its in-place editor

File diff suppressed because it is too large Load diff

View file

@ -520,6 +520,7 @@ impl CadCommand for TableCellEditCommand {
return Some(CmdResult::Cancel);
}
cell.set_text(text);
self.table.block_record_handle = None;
Some(CmdResult::ReplaceMany(
vec![(self.handle, vec![EntityType::Table(self.table.clone())])],
Vec::new(),