feat(text): MTEXT background fill / mask + text frame, editable in Properties

Render the MTEXT background (never drawn before — the flags were only shown
read-only in the panel) and make it editable.

- Text arm emits an opaque fill_tris rect behind the glyphs: flag 0x01 uses
  the entity background colour, 0x02 uses the canvas colour (a wipeout-style
  mask). Box = glyph bounds padded by (background_scale - 1) × text height.
- Text frame (flag 0x10) draws a closed rectangle in the text colour around
  the same box.
- Threaded `bg_color` into `tessellate()` (+ all callers and the
  MultiLeaderTess trait) so the mask can use the drawing-window colour.
- Properties: "Background mask" is a Off / Fill / Mask dropdown, "Background
  color" is its own colour picker (independent of the entity colour — new
  PropBgColor* messages; picking a colour switches to Fill mode), and "Text
  frame" is a checkbox toggle. Edits re-tessellate so the change shows live.

Renders via the face3d fill path, so the fill shows with FILLMODE on (same as
MLEADER background fill); the frame is a stroke and always shows.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
This commit is contained in:
Hakan SevenandClaude Opus 4.8 committed 2026-07-07 17:41:16 +03:00
1 parent 110683f419
commit cd7306f530
10 files changed
+223 -14

No files matched your search

+5
View File
@@ -1504,6 +1504,11 @@ pub enum Message {
PropAttrCommit(String),
/// Toggle the inline color picker dropdown open/closed.
PropColorPickerToggle,
/// Toggle the MTEXT background-colour picker dropdown open/closed.
PropBgColorPickerToggle,
/// User picked an MTEXT background fill colour — sets `background_color`
/// and switches the background mode to Fill so it shows immediately.
PropBgColorChanged(AcadColor),
/// Collapse the inline color picker dropdown. Fired when another
/// properties-panel dropdown (a combo_box) opens, so at most one panel
/// dropdown is open at a time and they can't overlap. (#235)
+2
View File
@@ -396,6 +396,7 @@ impl super::OpenCADStudio {
let mt = ed.build_mtext();
let entity = EntityType::MText(mt.clone());
let anno = self.tabs[i].scene.annotation_scale;
let bg = self.tabs[i].scene.bg_color;
let wires: Vec<WireModel> = tessellate::tessellate(
&self.tabs[i].scene.document,
ed.editing.unwrap_or(Handle::new(1)),
@@ -407,6 +408,7 @@ impl super::OpenCADStudio {
1.0,
anno,
None,
bg,
);
// Per-character boxes share the preview wires' absolute coordinate frame.
let boxes = crate::entities::mtext::glyph_boxes(&mt, &self.tabs[i].scene.document);
+30
View File
@@ -2214,6 +2214,36 @@ impl OpenCADStudio {
Task::none()
}
Message::PropBgColorPickerToggle => {
let i = self.active_tab;
self.tabs[i].properties.bg_color_picker_open =
!self.tabs[i].properties.bg_color_picker_open;
Task::none()
}
Message::PropBgColorChanged(color) => {
let i = self.active_tab;
let handles = self.property_target_handles(i);
if !handles.is_empty() {
self.push_undo_snapshot(i, "CHPROP");
for &handle in &handles {
if let Some(acadrust::EntityType::MText(m)) =
self.tabs[i].scene.document.get_entity_mut(handle)
{
m.background_color = color.clone();
// Picking a colour turns the background on in Fill
// mode (specific colour), preserving the frame bit.
m.background_fill_flags = (m.background_fill_flags & !0x02) | 0x01;
}
}
self.invalidate_property_targets(i, &handles);
self.tabs[i].properties.bg_color_picker_open = false;
self.tabs[i].dirty = true;
self.refresh_properties();
}
Task::none()
}
Message::PropColorPickerClose => {
let i = self.active_tab;
self.tabs[i].properties.color_picker_open = false;