forked from 1b-agent/opencadstudio-upstream
Merge pull request #1630 from ramox81/codex/table-edit-overrides
Tables: cell overrides and field backgrounds when drawn from table data. # Conflicts: # Cargo.lock # Cargo.toml
This commit is contained in:
commit
95d5eb73d9
4 files changed
+133
-39
No files matched your search
@@ -134,6 +134,16 @@ pub fn update_fields(
|
|||||||
document.update_fields(&OcsFieldContext(Some(&snapshot)), event, hosts)
|
document.update_fields(&OcsFieldContext(Some(&snapshot)), event, hosts)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Recompute a table's cell formulas into its block after the table changed;
|
||||||
|
/// returns the cell texts that changed.
|
||||||
|
pub fn refresh_table_formulas(document: &mut CadDocument, table: Handle) -> Vec<Handle> {
|
||||||
|
if document.fields.is_empty() {
|
||||||
|
return Vec::new();
|
||||||
|
}
|
||||||
|
let snapshot = document.clone();
|
||||||
|
document.refresh_table_formulas(&OcsFieldContext(Some(&snapshot)), table)
|
||||||
|
}
|
||||||
|
|
||||||
/// The text a table cell shows for its field: a formula is evaluated live
|
/// The text a table cell shows for its field: a formula is evaluated live
|
||||||
/// (it reads other cells), any other field shows its stored value.
|
/// (it reads other cells), any other field shows its stored value.
|
||||||
pub fn cell_text(document: &CadDocument, field: Handle, table: Handle) -> Option<String> {
|
pub fn cell_text(document: &CadDocument, field: Handle, table: Handle) -> Option<String> {
|
||||||
@@ -323,6 +333,12 @@ pub fn set_display(on: bool) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// The gray the reference draws behind a field.
|
/// The gray the reference draws behind a field.
|
||||||
|
/// FIELDDISPLAY box extent around a line of field text, in text heights:
|
||||||
|
/// from a third of the height below the baseline to 1.19 heights above it
|
||||||
|
/// (the reference's box, measured at 2.5 and 10 unit text).
|
||||||
|
pub const BOX_BELOW: f64 = 0.345;
|
||||||
|
pub const BOX_ABOVE: f64 = 1.19;
|
||||||
|
|
||||||
pub const BACKGROUND: [f32; 4] = [203.0 / 255.0, 203.0 / 255.0, 203.0 / 255.0, 1.0];
|
pub const BACKGROUND: [f32; 4] = [203.0 / 255.0, 203.0 / 255.0, 203.0 / 255.0, 1.0];
|
||||||
|
|
||||||
/// Whether `entity` hosts a field: its extension dictionary has ACAD_FIELD.
|
/// Whether `entity` hosts a field: its extension dictionary has ACAD_FIELD.
|
||||||
|
|||||||
+102
-34
@@ -101,12 +101,15 @@ pub(crate) fn style_for_property<'a>(
|
|||||||
cell: &'a codec::entities::table::TableCell,
|
cell: &'a codec::entities::table::TableCell,
|
||||||
property: codec::entities::table::CellStylePropertyFlags,
|
property: codec::entities::table::CellStylePropertyFlags,
|
||||||
) -> Option<&'a codec::entities::table::CellStyle> {
|
) -> Option<&'a codec::entities::table::CellStyle> {
|
||||||
|
// The cell's own overrides, as either layout (DXF or binary) states them.
|
||||||
|
if let Some(style) = cell.style.as_ref().filter(|style| style.sets(property)) {
|
||||||
|
return Some(style);
|
||||||
|
}
|
||||||
let column_style = table
|
let column_style = table
|
||||||
.columns
|
.columns
|
||||||
.get(column)
|
.get(column)
|
||||||
.and_then(|column| column.style.as_ref());
|
.and_then(|column| column.style.as_ref());
|
||||||
for style in [
|
for style in [
|
||||||
cell.style.as_ref(),
|
|
||||||
row.style.as_ref(),
|
row.style.as_ref(),
|
||||||
column_style,
|
column_style,
|
||||||
table.base_style.as_ref(),
|
table.base_style.as_ref(),
|
||||||
@@ -121,6 +124,48 @@ pub(crate) fn style_for_property<'a>(
|
|||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A cell's text height: the content's own format when it overrides it,
|
||||||
|
/// else the cell, row, column or table style that sets it, else the
|
||||||
|
/// content's value.
|
||||||
|
fn cell_text_height(
|
||||||
|
table: &Table,
|
||||||
|
row: &codec::entities::table::TableRow,
|
||||||
|
column: usize,
|
||||||
|
cell: &codec::entities::table::TableCell,
|
||||||
|
content: &codec::entities::table::CellContent,
|
||||||
|
) -> Option<f64> {
|
||||||
|
use codec::entities::table::CellStylePropertyFlags as P;
|
||||||
|
let own = content.sets(P::TEXT_HEIGHT);
|
||||||
|
own.then_some(content.text_height)
|
||||||
|
.or_else(|| style_for_property(table, row, column, cell, P::TEXT_HEIGHT).map(|s| s.text_height))
|
||||||
|
.or(Some(content.text_height))
|
||||||
|
.filter(|h| *h > 1e-6)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A cell's text style: the content's own, else the cell, row, column or
|
||||||
|
/// table style that sets it (by handle, or by name for a cell override).
|
||||||
|
fn cell_text_style(
|
||||||
|
document: &codec::CadDocument,
|
||||||
|
table: &Table,
|
||||||
|
row: &codec::entities::table::TableRow,
|
||||||
|
column: usize,
|
||||||
|
cell: &codec::entities::table::TableCell,
|
||||||
|
content: Option<&codec::entities::table::CellContent>,
|
||||||
|
) -> Option<codec::Handle> {
|
||||||
|
use codec::entities::table::CellStylePropertyFlags as P;
|
||||||
|
content.and_then(|c| c.text_style_handle).or_else(|| {
|
||||||
|
let style = style_for_property(table, row, column, cell, P::TEXT_STYLE)?;
|
||||||
|
style.text_style_handle.or_else(|| {
|
||||||
|
let name = style.text_style_name.trim();
|
||||||
|
document
|
||||||
|
.text_styles
|
||||||
|
.iter()
|
||||||
|
.find(|s| !name.is_empty() && s.name.eq_ignore_ascii_case(name))
|
||||||
|
.map(|s| s.handle)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
fn style_for_border<'a>(
|
fn style_for_border<'a>(
|
||||||
table: &'a Table,
|
table: &'a Table,
|
||||||
row: &'a codec::entities::table::TableRow,
|
row: &'a codec::entities::table::TableRow,
|
||||||
@@ -1250,8 +1295,7 @@ impl RenderConvertible for Table {
|
|||||||
// Resolve text height: content → cell-style → row-style → 0.18.
|
// Resolve text height: content → cell-style → row-style → 0.18.
|
||||||
let content = cell.contents.first();
|
let content = cell.contents.first();
|
||||||
let cell_h = content
|
let cell_h = content
|
||||||
.map(|c| c.text_height)
|
.and_then(|c| cell_text_height(self, row, ci, cell, c))
|
||||||
.filter(|h| *h > 1e-6)
|
|
||||||
.or_else(|| {
|
.or_else(|| {
|
||||||
cell.style
|
cell.style
|
||||||
.as_ref()
|
.as_ref()
|
||||||
@@ -1264,8 +1308,7 @@ impl RenderConvertible for Table {
|
|||||||
let margin = cell_h * 0.5_f32;
|
let margin = cell_h * 0.5_f32;
|
||||||
|
|
||||||
// Resolve text-style handle: content → cell-style → row-style.
|
// Resolve text-style handle: content → cell-style → row-style.
|
||||||
let style_handle = content
|
let style_handle = cell_text_style(document, self, row, ci, cell, content)
|
||||||
.and_then(|c| c.text_style_handle)
|
|
||||||
.or_else(|| cell.style.as_ref().and_then(|s| s.text_style_handle))
|
.or_else(|| cell.style.as_ref().and_then(|s| s.text_style_handle))
|
||||||
.or_else(|| row_style.and_then(|s| s.text_style_handle));
|
.or_else(|| row_style.and_then(|s| s.text_style_handle));
|
||||||
let font_owned = font_for_handle(style_handle).unwrap_or_else(|| "txt".to_string());
|
let font_owned = font_for_handle(style_handle).unwrap_or_else(|| "txt".to_string());
|
||||||
@@ -1485,6 +1528,7 @@ pub fn tessellate_table(
|
|||||||
|
|
||||||
// Accumulators keyed by quantised colour (+ weight for borders).
|
// Accumulators keyed by quantised colour (+ weight for borders).
|
||||||
let mut fills: HashMap<[u8; 4], ([f32; 4], Vec<[f32; 3]>)> = HashMap::default();
|
let mut fills: HashMap<[u8; 4], ([f32; 4], Vec<[f32; 3]>)> = HashMap::default();
|
||||||
|
let mut field_fills: Vec<[f32; 3]> = Vec::new();
|
||||||
// SDF cell text: glyph quads (per-vertex coloured) collected across all
|
// SDF cell text: glyph quads (per-vertex coloured) collected across all
|
||||||
// cells; emitted as one text-carrying wire at the end.
|
// cells; emitted as one text-carrying wire at the end.
|
||||||
let mut text_verts: Vec<crate::scene::pipeline::text_gpu::TextVertex> = Vec::new();
|
let mut text_verts: Vec<crate::scene::pipeline::text_gpu::TextVertex> = Vec::new();
|
||||||
@@ -1761,11 +1805,8 @@ pub fn tessellate_table(
|
|||||||
let fallback_sizes: Vec<_> = value_contents
|
let fallback_sizes: Vec<_> = value_contents
|
||||||
.iter()
|
.iter()
|
||||||
.map(|(_, content, text)| {
|
.map(|(_, content, text)| {
|
||||||
let height = if content.text_height > 1e-6 {
|
let height = cell_text_height(tab, row, ci, cell, content)
|
||||||
content.text_height as f32 * anno_scale
|
.map_or(fallback_text_height, |h| h as f32 * anno_scale);
|
||||||
} else {
|
|
||||||
fallback_text_height
|
|
||||||
};
|
|
||||||
let mut max_chars = 0usize;
|
let mut max_chars = 0usize;
|
||||||
let mut line_count = 0usize;
|
let mut line_count = 0usize;
|
||||||
for line in text.split("\\P") {
|
for line in text.split("\\P") {
|
||||||
@@ -1794,20 +1835,7 @@ pub fn tessellate_table(
|
|||||||
for (slot_index, (content_index, content, text)) in
|
for (slot_index, (content_index, content, text)) in
|
||||||
value_contents.iter().enumerate()
|
value_contents.iter().enumerate()
|
||||||
{
|
{
|
||||||
let text_height_style = style_for_property(
|
let cell_h = cell_text_height(tab, row, ci, cell, content)
|
||||||
tab,
|
|
||||||
row,
|
|
||||||
ci,
|
|
||||||
cell,
|
|
||||||
codec::entities::table::CellStylePropertyFlags::TEXT_HEIGHT,
|
|
||||||
);
|
|
||||||
let cell_h = (content.text_height > 1e-6)
|
|
||||||
.then_some(content.text_height)
|
|
||||||
.or_else(|| {
|
|
||||||
text_height_style
|
|
||||||
.map(|style| style.text_height)
|
|
||||||
.filter(|height| *height > 1e-6)
|
|
||||||
})
|
|
||||||
.or_else(|| {
|
.or_else(|| {
|
||||||
row_style
|
row_style
|
||||||
.map(|style| style.text_height)
|
.map(|style| style.text_height)
|
||||||
@@ -1856,16 +1884,7 @@ pub fn tessellate_table(
|
|||||||
.map(|style| style.margin_bottom as f32 * anno_scale)
|
.map(|style| style.margin_bottom as f32 * anno_scale)
|
||||||
.filter(|margin| *margin > 1e-6)
|
.filter(|margin| *margin > 1e-6)
|
||||||
.unwrap_or_else(|| v_margin.max(cell_h * 0.5));
|
.unwrap_or_else(|| v_margin.max(cell_h * 0.5));
|
||||||
let style_handle = content.text_style_handle.or_else(|| {
|
let style_handle = cell_text_style(document, tab, row, ci, cell, Some(content))
|
||||||
style_for_property(
|
|
||||||
tab,
|
|
||||||
row,
|
|
||||||
ci,
|
|
||||||
cell,
|
|
||||||
codec::entities::table::CellStylePropertyFlags::TEXT_STYLE,
|
|
||||||
)
|
|
||||||
.and_then(|style| style.text_style_handle)
|
|
||||||
})
|
|
||||||
.or_else(|| row_style.and_then(|style| style.text_style_handle));
|
.or_else(|| row_style.and_then(|style| style.text_style_handle));
|
||||||
let font_owned =
|
let font_owned =
|
||||||
font_for_handle(style_handle).unwrap_or_else(|| "txt".to_string());
|
font_for_handle(style_handle).unwrap_or_else(|| "txt".to_string());
|
||||||
@@ -1957,6 +1976,50 @@ pub fn tessellate_table(
|
|||||||
vertical_text: false,
|
vertical_text: false,
|
||||||
want_glyph_boxes: false,
|
want_glyph_boxes: false,
|
||||||
});
|
});
|
||||||
|
// FIELDDISPLAY: a field cell's text on a gray box, one per line,
|
||||||
|
// from the same layout's character cells (screen only).
|
||||||
|
if content.field_handle.is_some() && crate::entities::field::display() {
|
||||||
|
let cells = layout_mtext(&MTextRenderOpts {
|
||||||
|
columns: Default::default(),
|
||||||
|
value: text,
|
||||||
|
insertion: [to.x as f64, to.y as f64, to.z as f64],
|
||||||
|
height: cell_h,
|
||||||
|
rect_w: (col_width - margin_left - margin_right).max(0.0),
|
||||||
|
rotation: rot,
|
||||||
|
style: &resolved,
|
||||||
|
attach_h_anchor,
|
||||||
|
v_anchor,
|
||||||
|
line_spacing_factor: 1.0,
|
||||||
|
exact_line_spacing: false,
|
||||||
|
rectangle_height: 0.0,
|
||||||
|
vertical_text: false,
|
||||||
|
want_glyph_boxes: true,
|
||||||
|
})
|
||||||
|
.glyph_boxes;
|
||||||
|
let (sin_r, cos_r) = (rot as f64).sin_cos();
|
||||||
|
let mut lines: Vec<[f64; 4]> = Vec::new();
|
||||||
|
for b in &cells {
|
||||||
|
let [l, base, r, top] = b.local.map(|v| v as f64);
|
||||||
|
match lines.last_mut() {
|
||||||
|
Some(c) if (c[1] - base).abs() <= 1e-3 * (top - base).max(c[3]) => {
|
||||||
|
c[0] = c[0].min(l);
|
||||||
|
c[2] = c[2].max(r);
|
||||||
|
c[3] = c[3].max(top - base);
|
||||||
|
}
|
||||||
|
_ => lines.push([l, base, r, top - base]),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for [l, base, r, h] in lines.into_iter().filter(|[l, _, r, _]| r > l) {
|
||||||
|
let (b, t) = (base - crate::entities::field::BOX_BELOW * h, base + crate::entities::field::BOX_ABOVE * h);
|
||||||
|
let w = |x: f64, y: f64| [
|
||||||
|
(to.x as f64 + x * cos_r - y * sin_r) as f32,
|
||||||
|
(to.y as f64 + x * sin_r + y * cos_r) as f32,
|
||||||
|
to.z as f32,
|
||||||
|
];
|
||||||
|
let q = [w(l, b), w(r, b), w(r, t), w(l, t)];
|
||||||
|
field_fills.extend([q[0], q[1], q[2], q[0], q[2], q[3]]);
|
||||||
|
}
|
||||||
|
}
|
||||||
let tcol = if selected {
|
let tcol = if selected {
|
||||||
sel_col
|
sel_col
|
||||||
} else if !matches!(
|
} else if !matches!(
|
||||||
@@ -2116,6 +2179,11 @@ pub fn tessellate_table(
|
|||||||
out.push(mk(color, vec![], tris, 1.0));
|
out.push(mk(color, vec![], tris, 1.0));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if !field_fills.is_empty() {
|
||||||
|
let mut background = mk(crate::entities::field::BACKGROUND, vec![], field_fills, 1.0);
|
||||||
|
background.plot_visible = false;
|
||||||
|
out.push(background);
|
||||||
|
}
|
||||||
for (_, (color, lw, pts)) in borders {
|
for (_, (color, lw, pts)) in borders {
|
||||||
if !pts.is_empty() {
|
if !pts.is_empty() {
|
||||||
out.push(mk(color, pts, vec![], lw));
|
out.push(mk(color, pts, vec![], lw));
|
||||||
|
|||||||
@@ -78,11 +78,7 @@ fn oriented_text_corners(
|
|||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
/// FIELDDISPLAY box extent around a line of field text, in text heights:
|
use crate::entities::field::{BOX_ABOVE as FIELD_BOX_ABOVE, BOX_BELOW as FIELD_BOX_BELOW};
|
||||||
/// from a third of the height below the baseline to 1.19 heights above it
|
|
||||||
/// (the reference's box, measured at 2.5 and 10 unit text).
|
|
||||||
const FIELD_BOX_BELOW: f64 = 0.345;
|
|
||||||
const FIELD_BOX_ABOVE: f64 = 1.19;
|
|
||||||
|
|
||||||
/// FIELDDISPLAY boxes: each field's shown text, one box per line it spans,
|
/// FIELDDISPLAY boxes: each field's shown text, one box per line it spans,
|
||||||
/// placed from the text layout itself, in the text's own (rotated) frame —
|
/// placed from the text layout itself, in the text's own (rotated) frame —
|
||||||
|
|||||||
@@ -3695,6 +3695,20 @@ impl Scene {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// An edited table lets go of its block; rebuild it from the cells so
|
||||||
|
// the drawing shows and saves the cell texts and grid it now holds.
|
||||||
|
let mut rebuilt_table_block = false;
|
||||||
|
for (handle, kind) in &changes {
|
||||||
|
if !matches!(kind, ChangeKind::Removed)
|
||||||
|
&& matches!(self.document.get_entity(*handle), Some(EntityType::Table(t)) if t.block_record_handle.is_none())
|
||||||
|
{
|
||||||
|
rebuilt_table_block |= self.document.refresh_table_block(*handle);
|
||||||
|
rebuilt_table_block |= !crate::entities::field::refresh_table_formulas(&mut self.document, *handle).is_empty();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if rebuilt_table_block {
|
||||||
|
self.block_epoch = GEOMETRY_EPOCH.fetch_add(1, Ordering::Relaxed);
|
||||||
|
}
|
||||||
if !changes.is_empty() {
|
if !changes.is_empty() {
|
||||||
self.refresh_dependency_index_for_changes(&changes);
|
self.refresh_dependency_index_for_changes(&changes);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in new issue
Block a user