cad-editor/src/app/update/style.rs
Hakan Seven 1738f9a79b feat(cursor): add crosshair options
Add persistent cursor sizing, pick aperture, color, pointer mode, isometric drafting planes, and SNAPANG-aligned grid behavior.\n\nRefs #413
2026-08-08 21:32:44 +03:00

1205 lines
52 KiB
Rust

//! `style` arms and helpers, split out of the original `update.rs` (#mechanical decomposition).
#![allow(unused_imports)]
use super::util::*;
use super::{format_size, VIEWCUBE_HIT_SIZE};
use crate::app::helpers::{
parse_coord, polar_constrain_near, ucs_rotate_vec, ucs_to_wcs, ucs_z_axis,
CoordKind,
};
use crate::app::{Message, OpenCADStudio, POLY_START_DELAY_MS};
use crate::modules::ModuleEvent;
use crate::scene::pick::grip::{find_hit_grip, find_hit_grip_paper, find_hit_grip_rte, GripEdit};
use crate::scene::model::object::GripApply;
use crate::scene::{
self, hover_id, CubeRegion, Scene, VIEWCUBE_DRAW_PX, VIEWCUBE_PAD, VIEWCUBE_PX,
};
use crate::ui::PropertiesPanel;
use acadrust::types::Color as AcadColor;
use acadrust::{EntityType as AcadEntityType, Handle};
use iced::time::Instant;
use iced::{mouse, Point, Task};
impl OpenCADStudio {
pub(in crate::app) fn mlstyle_mut(
&mut self,
tab: usize,
) -> Option<&mut acadrust::objects::MLineStyle> {
use acadrust::objects::ObjectType;
let name = self.mlstyle_selected.clone();
self.tabs[tab]
.scene
.document
.objects
.values_mut()
.find_map(|object| match object {
ObjectType::MLineStyle(style) if style.name == name => Some(style),
_ => None,
})
}
pub(in crate::app) fn load_mlstyle_bufs(&mut self, tab: usize) {
use acadrust::objects::ObjectType;
let name = self.mlstyle_selected.clone();
let Some((description, start_angle, end_angle, fill_color, elements)) = self.tabs[tab]
.scene
.document
.objects
.values()
.find_map(|object| match object {
ObjectType::MLineStyle(style) if style.name == name => Some((
style.description.clone(),
style.start_angle.to_degrees(),
style.end_angle.to_degrees(),
style.fill_color.index().unwrap_or(256),
style
.elements
.iter()
.map(|element| {
[
format!("{}", element.offset),
element.color.index().unwrap_or(256).to_string(),
element.linetype.clone(),
]
})
.collect::<Vec<_>>(),
)),
_ => None,
})
else {
return;
};
self.mln_description = description;
self.mln_start_angle = format!("{start_angle:.4}");
self.mln_end_angle = format!("{end_angle:.4}");
self.mln_fill_color = fill_color.to_string();
self.mln_elements = elements;
}
pub(in crate::app) fn stage_mlstyle_bufs(&mut self) {
let tab = self.active_tab;
let description = self.mln_description.clone();
let start_angle = self.mln_start_angle.trim().parse::<f64>().ok();
let end_angle = self.mln_end_angle.trim().parse::<f64>().ok();
let fill_color = self.mln_fill_color.trim().parse::<i16>().ok();
let elements = self.mln_elements.clone();
if let Some(style) = self.mlstyle_mut(tab) {
style.description = description;
if let Some(value) = start_angle {
style.start_angle = value.to_radians();
}
if let Some(value) = end_angle {
style.end_angle = value.to_radians();
}
if let Some(value) = fill_color {
style.fill_color = acadrust::types::Color::from_index(value);
}
for (element, values) in style.elements.iter_mut().zip(elements) {
if let Ok(value) = values[0].trim().parse::<f64>() {
element.offset = value;
}
if let Ok(value) = values[1].trim().parse::<i16>() {
element.color = acadrust::types::Color::from_index(value);
}
element.linetype = values[2].clone();
}
}
}
pub(in crate::app) fn tablestyle_mut(&mut self, tab: usize) -> Option<&mut acadrust::objects::TableStyle> {
use acadrust::objects::ObjectType;
let name = self.tablestyle_selected.clone();
self.tabs[tab]
.scene
.document
.objects
.values_mut()
.find_map(|o| match o {
ObjectType::TableStyle(s) if s.name == name => Some(s),
_ => None,
})
}
/// Mutable access to a table style's cell style by row (0=Data,1=Header,2=Title).
pub(in crate::app) fn ts_cell_of(
s: &mut acadrust::objects::TableStyle,
row: u8,
) -> Option<&mut acadrust::objects::RowCellStyle> {
match row {
0 => Some(&mut s.data_row_style),
1 => Some(&mut s.header_row_style),
2 => Some(&mut s.title_row_style),
_ => None,
}
}
/// Mutable access to a cell's border by index
/// (0=left 1=right 2=top 3=bottom 4=horizontal-inside 5=vertical-inside).
pub(in crate::app) fn ts_border_of(
c: &mut acadrust::objects::RowCellStyle,
border: u8,
) -> Option<&mut acadrust::objects::TableCellBorder> {
match border {
0 => Some(&mut c.left_border),
1 => Some(&mut c.right_border),
2 => Some(&mut c.top_border),
3 => Some(&mut c.bottom_border),
4 => Some(&mut c.horizontal_inside_border),
5 => Some(&mut c.vertical_inside_border),
_ => None,
}
}
/// Populate margin + per-cell edit buffers from the selected table style.
pub(in crate::app) fn load_tablestyle_bufs(&mut self, tab: usize) {
use acadrust::objects::ObjectType;
let name = self.tablestyle_selected.clone();
let Some(s) = self.tabs[tab]
.scene
.document
.objects
.values()
.find_map(|o| match o {
ObjectType::TableStyle(s) if s.name == name => Some(s),
_ => None,
})
else {
return;
};
self.ts_hmargin = format!("{:.4}", s.horizontal_margin);
self.ts_vmargin = format!("{:.4}", s.vertical_margin);
self.ts_description = s.description.clone();
for (r, c) in [&s.data_row_style, &s.header_row_style, &s.title_row_style]
.into_iter()
.enumerate()
{
self.ts_cell_textstyle[r] = c.text_style_name.clone();
self.ts_cell_height[r] = format!("{:.4}", c.text_height);
self.ts_cell_textcolor[r] = c
.text_color
.index()
.map(|v| v.to_string())
.unwrap_or_default();
self.ts_cell_fillcolor[r] = c
.fill_color
.index()
.map(|v| v.to_string())
.unwrap_or_default();
self.ts_cell_datatype[r] = c.data_type.to_string();
self.ts_cell_unittype[r] = c.unit_type.to_string();
self.ts_cell_format[r] = c.format_string.clone();
let borders = [
&c.left_border,
&c.right_border,
&c.top_border,
&c.bottom_border,
&c.horizontal_inside_border,
&c.vertical_inside_border,
];
for (b, bd) in borders.into_iter().enumerate() {
self.ts_border_lw[r][b] = bd.line_weight.value().to_string();
self.ts_border_color[r][b] =
bd.color.index().map(|v| v.to_string()).unwrap_or_default();
self.ts_border_spacing[r][b] = format!("{:.4}", bd.double_line_spacing);
}
}
}
/// Mutable access to the currently selected multileader style.
pub(in crate::app) fn mleaderstyle_mut(&mut self, tab: usize) -> Option<&mut acadrust::objects::MultiLeaderStyle> {
use acadrust::objects::ObjectType;
let name = self.mleaderstyle_selected.clone();
self.tabs[tab]
.scene
.document
.objects
.values_mut()
.find_map(|o| match o {
ObjectType::MultiLeaderStyle(s) if s.name == name => Some(s),
_ => None,
})
}
/// Populate all edit buffers from the currently selected multileader style.
pub(in crate::app) fn load_mleaderstyle_bufs(&mut self, tab: usize) {
use acadrust::objects::ObjectType;
let name = self.mleaderstyle_selected.clone();
let Some(s) = self.tabs[tab]
.scene
.document
.objects
.values()
.find_map(|o| match o {
ObjectType::MultiLeaderStyle(s) if s.name == name => Some(s),
_ => None,
})
else {
return;
};
self.mls_landing_distance = format!("{:.4}", s.landing_distance);
self.mls_landing_gap = format!("{:.4}", s.landing_gap);
self.mls_arrowhead_size = format!("{:.4}", s.arrowhead_size);
self.mls_text_height = format!("{:.4}", s.text_height);
self.mls_scale_factor = format!("{:.4}", s.scale_factor);
self.mls_break_gap = format!("{:.4}", s.break_gap_size);
self.mls_first_seg_angle = format!("{:.4}", s.first_segment_angle.to_degrees());
self.mls_second_seg_angle = format!("{:.4}", s.second_segment_angle.to_degrees());
self.mls_max_points = s.max_leader_points.to_string();
self.mls_default_text = s.default_text.clone();
self.mls_line_color = s
.line_color
.index()
.map(|c| c.to_string())
.unwrap_or_default();
self.mls_text_color = s
.text_color
.index()
.map(|c| c.to_string())
.unwrap_or_default();
self.mls_description = s.description.clone();
self.mls_align_space = format!("{:.4}", s.align_space);
self.mls_block_color = s
.block_content_color
.index()
.map(|c| c.to_string())
.unwrap_or_default();
self.mls_block_rotation = format!("{:.4}", s.block_content_rotation.to_degrees());
self.mls_block_scale_x = format!("{:.4}", s.block_content_scale_x);
self.mls_block_scale_y = format!("{:.4}", s.block_content_scale_y);
self.mls_block_scale_z = format!("{:.4}", s.block_content_scale_z);
}
/// Populate all edit buffers from the currently selected dim style.
pub(in crate::app) fn load_dimstyle_bufs(&mut self, tab: usize) {
let doc = &self.tabs[tab].scene.document;
let Some(ds) = doc.dim_styles.get(&self.dimstyle_selected) else {
return;
};
self.ds_dimdle = format!("{}", ds.dimdle);
self.ds_dimdli = format!("{}", ds.dimdli);
self.ds_dimgap = format!("{}", ds.dimgap);
self.ds_dimexe = format!("{}", ds.dimexe);
self.ds_dimexo = format!("{}", ds.dimexo);
self.ds_dimsd1 = ds.dimsd1;
self.ds_dimsd2 = ds.dimsd2;
self.ds_dimse1 = ds.dimse1;
self.ds_dimse2 = ds.dimse2;
self.ds_dimasz = format!("{}", ds.dimasz);
self.ds_dimcen = format!("{}", ds.dimcen);
self.ds_dimtsz = format!("{}", ds.dimtsz);
self.ds_dimtxt = format!("{}", ds.dimtxt);
self.ds_dimtxsty = ds.dimtxsty.clone();
self.ds_dimtad = format!("{}", ds.dimtad);
self.ds_dimtih = ds.dimtih;
self.ds_dimtoh = ds.dimtoh;
self.ds_dimscale = format!("{}", ds.dimscale);
self.ds_dimlfac = format!("{}", ds.dimlfac);
self.ds_dimlunit = format!("{}", ds.dimlunit);
self.ds_dimdec = format!("{}", ds.dimdec);
self.ds_dimpost = ds.dimpost.clone();
self.ds_dimtol = ds.dimtol;
self.ds_dimlim = ds.dimlim;
self.ds_dimtp = format!("{}", ds.dimtp);
self.ds_dimtm = format!("{}", ds.dimtm);
self.ds_dimtdec = format!("{}", ds.dimtdec);
self.ds_dimtfac = format!("{}", ds.dimtfac);
self.ds_annotative = ds.annotative;
self.ds_dimclrd = format!("{}", ds.dimclrd);
self.ds_dimlwd = format!("{}", ds.dimlwd);
self.ds_dimclre = format!("{}", ds.dimclre);
self.ds_dimlwe = format!("{}", ds.dimlwe);
self.ds_dimfxl = format!("{}", ds.dimfxl);
self.ds_dimfxlon = ds.dimfxlon;
self.ds_dimsah = ds.dimsah;
self.ds_dimarcsym = format!("{}", ds.dimarcsym);
self.ds_dimjogang = format!("{}", ds.dimjogang.to_degrees());
self.ds_dimclrt = format!("{}", ds.dimclrt);
self.ds_dimjust = format!("{}", ds.dimjust);
self.ds_dimtvp = format!("{}", ds.dimtvp);
self.ds_dimtfill = format!("{}", ds.dimtfill);
self.ds_dimtfillclr = format!("{}", ds.dimtfillclr);
self.ds_dimtxtdirection = ds.dimtxtdirection;
self.ds_dimatfit = format!("{}", ds.dimatfit);
self.ds_dimtix = ds.dimtix;
self.ds_dimsoxd = ds.dimsoxd;
self.ds_dimtmove = format!("{}", ds.dimtmove);
self.ds_dimupt = ds.dimupt;
self.ds_dimtofl = ds.dimtofl;
self.ds_dimfit = format!("{}", ds.dimfit);
self.ds_dimdsep = format!("{}", ds.dimdsep);
self.ds_dimrnd = format!("{}", ds.dimrnd);
self.ds_dimzin = format!("{}", ds.dimzin);
self.ds_dimfrac = format!("{}", ds.dimfrac);
self.ds_dimaunit = format!("{}", ds.dimaunit);
self.ds_dimadec = format!("{}", ds.dimadec);
self.ds_dimunit = format!("{}", ds.dimunit);
self.ds_dimazin = format!("{}", ds.dimazin);
self.ds_dimalt = ds.dimalt;
self.ds_dimaltf = format!("{}", ds.dimaltf);
self.ds_dimaltd = format!("{}", ds.dimaltd);
self.ds_dimaltu = format!("{}", ds.dimaltu);
self.ds_dimalttd = format!("{}", ds.dimalttd);
self.ds_dimaltrnd = format!("{}", ds.dimaltrnd);
self.ds_dimapost = ds.dimapost.clone();
self.ds_dimaltz = format!("{}", ds.dimaltz);
self.ds_dimalttz = format!("{}", ds.dimalttz);
self.ds_dimtolj = format!("{}", ds.dimtolj);
self.ds_dimtzin = format!("{}", ds.dimtzin);
}
/// Write edit buffers back into the selected dim style document entry.
pub(in crate::app) fn apply_dimstyle_bufs(&mut self, tab: usize) {
let doc = &mut self.tabs[tab].scene.document;
let read_only = doc
.dim_styles
.get(&self.dimstyle_selected)
.is_some_and(|style| {
style.xref_reference || style.xref_dependent || !style.xref_handle.is_null()
});
if read_only {
return;
}
let text_style_handle = doc
.text_styles
.get(&self.ds_dimtxsty)
.map(|style| style.handle)
.unwrap_or(acadrust::types::Handle::NULL);
let Some(ds) = doc.dim_styles.get_mut(&self.dimstyle_selected) else {
return;
};
macro_rules! set_f64 {
($field:ident, $buf:expr) => {
if let Ok(v) = $buf.trim().parse::<f64>() {
ds.$field = v;
}
};
}
macro_rules! set_i16 {
($field:ident, $buf:expr) => {
if let Ok(v) = $buf.trim().parse::<i16>() {
ds.$field = v;
}
};
}
set_f64!(dimdle, self.ds_dimdle);
set_f64!(dimdli, self.ds_dimdli);
set_f64!(dimgap, self.ds_dimgap);
set_f64!(dimexe, self.ds_dimexe);
set_f64!(dimexo, self.ds_dimexo);
set_f64!(dimasz, self.ds_dimasz);
set_f64!(dimcen, self.ds_dimcen);
set_f64!(dimtsz, self.ds_dimtsz);
set_f64!(dimtxt, self.ds_dimtxt);
if self.ds_annotative {
ds.dimscale = 0.0;
self.ds_dimscale = "0".to_string();
} else {
set_f64!(dimscale, self.ds_dimscale);
}
set_f64!(dimlfac, self.ds_dimlfac);
set_f64!(dimtp, self.ds_dimtp);
set_f64!(dimtm, self.ds_dimtm);
set_f64!(dimtfac, self.ds_dimtfac);
set_i16!(dimtad, self.ds_dimtad);
set_i16!(dimlunit, self.ds_dimlunit);
set_i16!(dimdec, self.ds_dimdec);
set_i16!(dimtdec, self.ds_dimtdec);
ds.dimsd1 = self.ds_dimsd1;
ds.dimsd2 = self.ds_dimsd2;
ds.dimse1 = self.ds_dimse1;
ds.dimse2 = self.ds_dimse2;
ds.dimtih = self.ds_dimtih;
ds.dimtoh = self.ds_dimtoh;
ds.dimtol = self.ds_dimtol;
ds.dimlim = self.ds_dimlim;
ds.dimpost = self.ds_dimpost.clone();
ds.dimtxsty = self.ds_dimtxsty.clone();
ds.dimtxsty_handle = text_style_handle;
ds.annotative = self.ds_annotative;
set_i16!(dimclrd, self.ds_dimclrd);
set_i16!(dimlwd, self.ds_dimlwd);
set_i16!(dimclre, self.ds_dimclre);
set_i16!(dimlwe, self.ds_dimlwe);
set_f64!(dimfxl, self.ds_dimfxl);
set_i16!(dimarcsym, self.ds_dimarcsym);
set_i16!(dimclrt, self.ds_dimclrt);
set_i16!(dimjust, self.ds_dimjust);
set_f64!(dimtvp, self.ds_dimtvp);
set_i16!(dimtfill, self.ds_dimtfill);
set_i16!(dimtfillclr, self.ds_dimtfillclr);
set_i16!(dimatfit, self.ds_dimatfit);
set_i16!(dimtmove, self.ds_dimtmove);
set_i16!(dimfit, self.ds_dimfit);
set_i16!(dimdsep, self.ds_dimdsep);
set_f64!(dimrnd, self.ds_dimrnd);
set_i16!(dimzin, self.ds_dimzin);
set_i16!(dimfrac, self.ds_dimfrac);
set_i16!(dimaunit, self.ds_dimaunit);
set_i16!(dimadec, self.ds_dimadec);
set_i16!(dimunit, self.ds_dimunit);
set_i16!(dimazin, self.ds_dimazin);
set_f64!(dimaltf, self.ds_dimaltf);
set_i16!(dimaltd, self.ds_dimaltd);
set_i16!(dimaltu, self.ds_dimaltu);
set_i16!(dimalttd, self.ds_dimalttd);
set_f64!(dimaltrnd, self.ds_dimaltrnd);
set_i16!(dimaltz, self.ds_dimaltz);
set_i16!(dimalttz, self.ds_dimalttz);
set_i16!(dimtolj, self.ds_dimtolj);
set_i16!(dimtzin, self.ds_dimtzin);
if let Ok(v) = self.ds_dimjogang.trim().parse::<f64>() {
ds.dimjogang = v.to_radians();
}
ds.dimfxlon = self.ds_dimfxlon;
ds.dimsah = self.ds_dimsah;
ds.dimtxtdirection = self.ds_dimtxtdirection;
ds.dimtix = self.ds_dimtix;
ds.dimsoxd = self.ds_dimsoxd;
ds.dimupt = self.ds_dimupt;
ds.dimtofl = self.ds_dimtofl;
ds.dimalt = self.ds_dimalt;
ds.dimapost = self.ds_dimapost.clone();
// Same reason as a per-dimension edit: every dimension on this style is
// drawn from a block made under the settings that just changed, so the
// pictures are stale. Drop them and let each be drawn again. Without
// this an edit here would move the numbers and leave the drawing alone.
let edited = self.dimstyle_selected.clone();
let stale: Vec<acadrust::Handle> = self.tabs[tab]
.scene
.document
.entities()
.filter_map(|entity| match entity {
acadrust::EntityType::Dimension(dim)
if dim.base().style_name.eq_ignore_ascii_case(&edited) =>
{
Some(entity.common().handle)
}
_ => None,
})
.collect();
for handle in stale {
self.tabs[tab].scene.invalidate_dim_block_recorded(handle);
}
self.command_line
.push_output(crate::tf!("DimStyle '{}' updated.", self.dimstyle_selected).as_ref());
}
/// Update a single string buffer field.
pub(in crate::app) fn apply_ds_edit(&mut self, field: crate::app::DsField, val: String) {
use crate::app::DsField::*;
match field {
Dimdle => self.ds_dimdle = val,
Dimdli => self.ds_dimdli = val,
Dimgap => self.ds_dimgap = val,
Dimexe => self.ds_dimexe = val,
Dimexo => self.ds_dimexo = val,
Dimasz => self.ds_dimasz = val,
Dimcen => self.ds_dimcen = val,
Dimtsz => self.ds_dimtsz = val,
Dimtxt => self.ds_dimtxt = val,
Dimtxsty => self.ds_dimtxsty = val,
Dimtad => self.ds_dimtad = val,
Dimscale => self.ds_dimscale = val,
Dimlfac => self.ds_dimlfac = val,
Dimlunit => self.ds_dimlunit = val,
Dimdec => self.ds_dimdec = val,
Dimpost => self.ds_dimpost = val,
Dimtp => {
let symmetrical = self.ds_dimtol
&& !self.ds_dimlim
&& self.ds_dimtp.trim() == self.ds_dimtm.trim();
self.ds_dimtp = val.clone();
if symmetrical {
self.ds_dimtm = val;
}
}
Dimtm => self.ds_dimtm = val,
Dimtdec => self.ds_dimtdec = val,
Dimtfac => self.ds_dimtfac = val,
Dimclrd => self.ds_dimclrd = val,
Dimlwd => self.ds_dimlwd = val,
Dimclre => self.ds_dimclre = val,
Dimlwe => self.ds_dimlwe = val,
Dimfxl => self.ds_dimfxl = val,
Dimarcsym => self.ds_dimarcsym = val,
Dimjogang => self.ds_dimjogang = val,
Dimclrt => self.ds_dimclrt = val,
Dimjust => self.ds_dimjust = val,
Dimtvp => self.ds_dimtvp = val,
Dimtfill => self.ds_dimtfill = val,
Dimtfillclr => self.ds_dimtfillclr = val,
Dimatfit => self.ds_dimatfit = val,
Dimtmove => self.ds_dimtmove = val,
Dimfit => self.ds_dimfit = val,
Dimdsep => self.ds_dimdsep = val,
Dimrnd => self.ds_dimrnd = val,
Dimzin => self.ds_dimzin = val,
Dimfrac => self.ds_dimfrac = val,
Dimaunit => self.ds_dimaunit = val,
Dimadec => self.ds_dimadec = val,
Dimunit => self.ds_dimunit = val,
Dimazin => self.ds_dimazin = val,
Dimaltf => self.ds_dimaltf = val,
Dimaltd => self.ds_dimaltd = val,
Dimaltu => self.ds_dimaltu = val,
Dimalttd => self.ds_dimalttd = val,
Dimaltrnd => self.ds_dimaltrnd = val,
Dimapost => self.ds_dimapost = val,
Dimaltz => self.ds_dimaltz = val,
Dimalttz => self.ds_dimalttz = val,
Dimtolj => self.ds_dimtolj = val,
Dimtzin => self.ds_dimtzin = val,
// Bool fields — no-op for string edit
_ => {}
}
}
/// Toggle a boolean buffer field.
pub(in crate::app) fn apply_ds_toggle(&mut self, field: crate::app::DsField) {
use crate::app::DsField::*;
match field {
Dimsd1 => self.ds_dimsd1 = !self.ds_dimsd1,
Dimsd2 => self.ds_dimsd2 = !self.ds_dimsd2,
Dimse1 => self.ds_dimse1 = !self.ds_dimse1,
Dimse2 => self.ds_dimse2 = !self.ds_dimse2,
Dimtih => self.ds_dimtih = !self.ds_dimtih,
Dimtoh => self.ds_dimtoh = !self.ds_dimtoh,
Dimtol => self.ds_dimtol = !self.ds_dimtol,
Dimlim => self.ds_dimlim = !self.ds_dimlim,
Annotative => {
self.ds_annotative = !self.ds_annotative;
if self.ds_annotative {
self.ds_dimscale = "0".to_string();
}
}
Dimfxlon => self.ds_dimfxlon = !self.ds_dimfxlon,
Dimsah => self.ds_dimsah = !self.ds_dimsah,
Dimtxtdirection => self.ds_dimtxtdirection = !self.ds_dimtxtdirection,
Dimtix => self.ds_dimtix = !self.ds_dimtix,
Dimsoxd => self.ds_dimsoxd = !self.ds_dimsoxd,
Dimupt => self.ds_dimupt = !self.ds_dimupt,
Dimtofl => self.ds_dimtofl = !self.ds_dimtofl,
Dimalt => self.ds_dimalt = !self.ds_dimalt,
_ => {}
}
}
/// Populate edit buffers from the currently selected text style.
pub(in crate::app) fn load_textstyle_bufs(&mut self, tab: usize) {
let doc = &self.tabs[tab].scene.document;
if let Some(s) = doc.text_styles.get(&self.textstyle_selected) {
self.textstyle_font = s.font_file.clone();
self.textstyle_width = format!("{:.4}", s.width_factor);
self.textstyle_oblique = format!("{:.2}", s.oblique_angle.to_degrees());
self.textstyle_height = format!("{:.4}", s.height);
self.textstyle_bigfont = s.big_font_file.clone();
self.textstyle_ttf = s.true_type_font.clone();
}
}
pub(super) fn on_text_style_dialog_open(&mut self) -> Task<Message> {
let i = self.active_tab;
let cur = self.tabs[i]
.scene
.document
.header
.current_text_style_name
.clone();
let exists = self.tabs[i].scene.document.text_styles.get(&cur).is_some();
self.textstyle_selected = if exists {
cur
} else {
self.tabs[i]
.scene
.document
.text_styles
.iter()
.next()
.map(|s| s.name.clone())
.unwrap_or_else(|| "Standard".to_string())
};
self.load_textstyle_bufs(i);
self.active_modal = Some(crate::app::ModalKind::TextStyle);
self.style_stage_begin();
Task::none()
}
/// Write the editor buffers into the selected text style (staged, no
/// commit), so edits survive switching to another style as well as Apply.
pub(super) fn stage_textstyle_bufs(&mut self) {
let i = self.active_tab;
let name = self.textstyle_selected.clone();
let font = self.textstyle_font.clone();
let width_str = self.textstyle_width.clone();
let oblique_str = self.textstyle_oblique.clone();
let height_str = self.textstyle_height.clone();
let bigfont = self.textstyle_bigfont.clone();
let ttf = self.textstyle_ttf.clone();
if let Some(s) = self.tabs[i].scene.document.text_styles.get_mut(&name) {
if s.xref_dependent {
return;
}
s.font_file = font;
s.big_font_file = bigfont;
s.true_type_font = ttf;
if let Ok(w) = width_str.trim().parse::<f64>() {
s.width_factor = w;
}
if let Ok(a) = oblique_str.trim().parse::<f64>() {
s.oblique_angle = a.to_radians();
}
if let Ok(h) = height_str.trim().parse::<f64>() {
s.height = h.max(0.0);
}
}
}
pub(super) fn on_text_style_apply(&mut self) -> Task<Message> {
self.stage_textstyle_bufs();
self.style_stage_commit();
Task::none()
}
pub(super) fn on_table_style_cell_apply(&mut self, row: u8) -> Task<Message> {
let i = self.active_tab;
let r = row as usize;
if r >= 3 {
return Task::none();
}
let ts = self.ts_cell_textstyle[r].trim().to_string();
let height: Option<f64> = self.ts_cell_height[r].trim().parse().ok();
let tc: Option<i16> = self.ts_cell_textcolor[r].trim().parse().ok();
let fc: Option<i16> = self.ts_cell_fillcolor[r].trim().parse().ok();
let dtype: Option<i32> = self.ts_cell_datatype[r].trim().parse().ok();
let utype: Option<i32> = self.ts_cell_unittype[r].trim().parse().ok();
let fmt = self.ts_cell_format[r].clone();
// Per-border numeric edits for this cell.
let border_vals: [(Option<i16>, Option<i16>, Option<f64>); 6] =
std::array::from_fn(|b| {
(
self.ts_border_lw[r][b].trim().parse().ok(),
self.ts_border_color[r][b].trim().parse().ok(),
self.ts_border_spacing[r][b].trim().parse().ok(),
)
});
if let Some(c) = self
.tablestyle_mut(i)
.and_then(|s| Self::ts_cell_of(s, row))
{
if !ts.is_empty() {
c.text_style_name = ts;
}
if let Some(h) = height {
c.text_height = h;
}
if let Some(v) = tc {
c.text_color = acadrust::types::Color::from_index(v);
}
if let Some(v) = fc {
c.fill_color = acadrust::types::Color::from_index(v);
}
if let Some(v) = dtype {
c.data_type = v;
}
if let Some(v) = utype {
c.unit_type = v;
}
c.format_string = fmt;
for (b, (lw, color, spacing)) in border_vals.into_iter().enumerate() {
if let Some(bd) = Self::ts_border_of(c, b as u8) {
if let Some(v) = lw {
bd.line_weight = acadrust::types::LineWeight::from_value(v);
}
if let Some(v) = color {
bd.color = acadrust::types::Color::from_index(v);
}
if let Some(v) = spacing {
bd.double_line_spacing = v;
}
}
}
}
Task::none()
}
pub(super) fn on_ml_style_dialog_open(&mut self) -> Task<Message> {
use acadrust::objects::ObjectType;
let i = self.active_tab;
let cur = self.tabs[i].scene.document.header.multiline_style.clone();
let exists = self.tabs[i]
.scene
.document
.objects
.values()
.any(|o| matches!(o, ObjectType::MLineStyle(s) if s.name == cur));
self.mlstyle_selected = if exists {
cur
} else {
self.tabs[i]
.scene
.document
.objects
.values()
.find_map(|o| {
if let ObjectType::MLineStyle(s) = o {
Some(s.name.clone())
} else {
None
}
})
.unwrap_or_else(|| "Standard".to_string())
};
self.active_modal = Some(crate::app::ModalKind::MlStyle);
self.load_mlstyle_bufs(i);
self.style_stage_begin();
Task::none()
}
pub(super) fn on_mleader_style_dialog_open(&mut self) -> Task<Message> {
use acadrust::objects::ObjectType;
let i = self.active_tab;
let cur = self.tabs[i].active_mleader_style.clone();
let exists = self.tabs[i]
.scene
.document
.objects
.values()
.any(|o| matches!(o, ObjectType::MultiLeaderStyle(s) if s.name == cur));
self.mleaderstyle_selected = if exists {
cur
} else {
self.tabs[i]
.scene
.document
.objects
.values()
.find_map(|o| {
if let ObjectType::MultiLeaderStyle(s) = o {
Some(s.name.clone())
} else {
None
}
})
.unwrap_or_else(|| "Standard".to_string())
};
self.load_mleaderstyle_bufs(i);
self.active_modal = Some(crate::app::ModalKind::MLeaderStyle);
self.style_stage_begin();
Task::none()
}
pub(super) fn on_mleader_style_dialog_set_current(&mut self) -> Task<Message> {
use acadrust::objects::ObjectType;
let i = self.active_tab;
let name = self.mleaderstyle_selected.clone();
let exists = self.tabs[i]
.scene
.document
.objects
.values()
.any(|o| matches!(o, ObjectType::MultiLeaderStyle(s) if s.name == name));
if exists {
// Staged: header field is the round-trip source of truth
// ($CMLEADERSTYLE); the ribbon/tab mirror it.
self.tabs[i]
.scene
.document
.header
.current_mleader_style_name = name.clone();
self.tabs[i].active_mleader_style = name.clone();
self.ribbon.active_mleader_style = name.clone();
self.command_line
.push_output(crate::tf!("Current multileader style: {}", name).as_ref());
}
Task::none()
}
pub(super) fn on_mleader_style_edit(&mut self, field: &'static str, value: String) -> Task<Message> {
self.mls_color_open = None;
match field {
"landing_distance" => self.mls_landing_distance = value,
"landing_gap" => self.mls_landing_gap = value,
"arrowhead_size" => self.mls_arrowhead_size = value,
"text_height" => self.mls_text_height = value,
"scale_factor" => self.mls_scale_factor = value,
"break_gap" => self.mls_break_gap = value,
"first_seg_angle" => self.mls_first_seg_angle = value,
"second_seg_angle" => self.mls_second_seg_angle = value,
"max_points" => self.mls_max_points = value,
"default_text" => self.mls_default_text = value,
"line_color" => self.mls_line_color = value,
"text_color" => self.mls_text_color = value,
"description" => self.mls_description = value,
"align_space" => self.mls_align_space = value,
"block_color" => self.mls_block_color = value,
"block_rotation" => self.mls_block_rotation = value,
"block_scale_x" => self.mls_block_scale_x = value,
"block_scale_y" => self.mls_block_scale_y = value,
"block_scale_z" => self.mls_block_scale_z = value,
_ => {}
}
Task::none()
}
pub(super) fn on_mleader_style_set_enum(&mut self, field: &'static str, value: String) -> Task<Message> {
use acadrust::objects::{
BlockContentConnectionType, LeaderContentType, LeaderDrawOrderType,
MultiLeaderDrawOrderType, MultiLeaderPathType, TextAlignmentType,
TextAngleType, TextAttachmentDirectionType, TextAttachmentType,
};
// Parse a TextAttachmentType from its debug name.
fn parse_att(v: &str) -> TextAttachmentType {
match v {
"TopOfTopLine" => TextAttachmentType::TopOfTopLine,
"MiddleOfText" => TextAttachmentType::MiddleOfText,
"MiddleOfBottomLine" => TextAttachmentType::MiddleOfBottomLine,
"BottomOfBottomLine" => TextAttachmentType::BottomOfBottomLine,
"BottomLine" => TextAttachmentType::BottomLine,
"BottomOfTopLineUnderlineBottomLine" => {
TextAttachmentType::BottomOfTopLineUnderlineBottomLine
}
"BottomOfTopLineUnderlineTopLine" => {
TextAttachmentType::BottomOfTopLineUnderlineTopLine
}
"BottomOfTopLineUnderlineAll" => {
TextAttachmentType::BottomOfTopLineUnderlineAll
}
"CenterOfText" => TextAttachmentType::CenterOfText,
"CenterOfTextOverline" => TextAttachmentType::CenterOfTextOverline,
_ => TextAttachmentType::MiddleOfTopLine,
}
}
let i = self.active_tab;
if let Some(s) = self.mleaderstyle_mut(i) {
match field {
"path_type" => {
s.path_type = match value.as_str() {
"Invisible" => MultiLeaderPathType::Invisible,
"Spline" => MultiLeaderPathType::Spline,
_ => MultiLeaderPathType::StraightLineSegments,
};
}
"content_type" => {
s.content_type = match value.as_str() {
"None" => LeaderContentType::None,
"Block" => LeaderContentType::Block,
"Tolerance" => LeaderContentType::Tolerance,
_ => LeaderContentType::MText,
};
}
"text_angle_type" => {
s.text_angle_type = match value.as_str() {
"ParallelToLastLeaderLine" => {
TextAngleType::ParallelToLastLeaderLine
}
"Optimized" => TextAngleType::Optimized,
_ => TextAngleType::Horizontal,
};
}
"text_alignment" => {
s.text_alignment = match value.as_str() {
"Center" => TextAlignmentType::Center,
"Right" => TextAlignmentType::Right,
_ => TextAlignmentType::Left,
};
}
"text_left_attachment" => s.text_left_attachment = parse_att(&value),
"text_right_attachment" => s.text_right_attachment = parse_att(&value),
"text_top_attachment" => s.text_top_attachment = parse_att(&value),
"text_bottom_attachment" => s.text_bottom_attachment = parse_att(&value),
"text_attachment_direction" => {
s.text_attachment_direction = match value.as_str() {
"Vertical" => TextAttachmentDirectionType::Vertical,
_ => TextAttachmentDirectionType::Horizontal,
};
}
"block_content_connection" => {
s.block_content_connection = match value.as_str() {
"BasePoint" => BlockContentConnectionType::BasePoint,
_ => BlockContentConnectionType::BlockExtents,
};
}
"leader_draw_order" => {
s.leader_draw_order = match value.as_str() {
"LeaderTailFirst" => LeaderDrawOrderType::LeaderTailFirst,
_ => LeaderDrawOrderType::LeaderHeadFirst,
};
}
"multileader_draw_order" => {
s.multileader_draw_order = match value.as_str() {
"LeaderFirst" => MultiLeaderDrawOrderType::LeaderFirst,
_ => MultiLeaderDrawOrderType::ContentFirst,
};
}
_ => {}
}
}
Task::none()
}
pub(super) fn on_mleader_style_set_handle(&mut self, field: &'static str, value: String) -> Task<Message> {
let i = self.active_tab;
let doc = &self.tabs[i].scene.document;
let handle: Option<acadrust::types::Handle> = if value == "None"
|| value == "ByBlock"
|| value == "Closed filled"
{
None
} else {
match field {
"line_type_handle" => doc
.line_types
.iter()
.find(|lt| lt.name == value)
.map(|lt| lt.handle),
"text_style_handle" => doc
.text_styles
.iter()
.find(|t| t.name == value)
.map(|t| t.handle),
"arrowhead_handle" | "block_content_handle" => doc
.block_records
.iter()
.find(|b| b.name == value)
.map(|b| b.handle),
_ => None,
}
};
if let Some(s) = self.mleaderstyle_mut(i) {
match field {
"line_type_handle" => s.line_type_handle = handle,
"text_style_handle" => s.text_style_handle = handle,
"arrowhead_handle" => s.arrowhead_handle = handle,
"block_content_handle" => s.block_content_handle = handle,
_ => {}
}
}
Task::none()
}
/// Write the editor buffers into the selected multileader style (staged, no
/// commit), so edits survive switching to another style as well as Apply.
pub(super) fn stage_mleaderstyle_bufs(&mut self) {
let i = self.active_tab;
let (ld, lg, asz, th, sf, bg, fsa, ssa, mp, dt, lc, tc) = (
self.mls_landing_distance.parse::<f64>().ok(),
self.mls_landing_gap.parse::<f64>().ok(),
self.mls_arrowhead_size.parse::<f64>().ok(),
self.mls_text_height.parse::<f64>().ok(),
self.mls_scale_factor.parse::<f64>().ok(),
self.mls_break_gap.parse::<f64>().ok(),
self.mls_first_seg_angle.parse::<f64>().ok(),
self.mls_second_seg_angle.parse::<f64>().ok(),
self.mls_max_points.parse::<i32>().ok(),
self.mls_default_text.clone(),
self.mls_line_color.parse::<i16>().ok(),
self.mls_text_color.parse::<i16>().ok(),
);
let desc = self.mls_description.clone();
let align = self.mls_align_space.parse::<f64>().ok();
let bclr = self.mls_block_color.parse::<i16>().ok();
let brot = self.mls_block_rotation.parse::<f64>().ok();
let bsx = self.mls_block_scale_x.parse::<f64>().ok();
let bsy = self.mls_block_scale_y.parse::<f64>().ok();
let bsz = self.mls_block_scale_z.parse::<f64>().ok();
if let Some(s) = self.mleaderstyle_mut(i) {
if let Some(v) = ld {
s.landing_distance = v;
}
if let Some(v) = lg {
s.landing_gap = v;
}
if let Some(v) = asz {
s.arrowhead_size = v;
}
if let Some(v) = th {
s.text_height = v;
}
if let Some(v) = sf {
s.scale_factor = v;
}
if let Some(v) = bg {
s.break_gap_size = v;
}
if let Some(v) = fsa {
s.first_segment_angle = v.to_radians();
}
if let Some(v) = ssa {
s.second_segment_angle = v.to_radians();
}
if let Some(v) = mp {
s.max_leader_points = v;
}
s.default_text = dt;
if let Some(v) = lc {
s.line_color = acadrust::types::Color::from_index(v);
}
if let Some(v) = tc {
s.text_color = acadrust::types::Color::from_index(v);
}
s.description = desc;
if let Some(v) = align {
s.align_space = v;
}
if let Some(v) = bclr {
s.block_content_color = acadrust::types::Color::from_index(v);
}
if let Some(v) = brot {
s.block_content_rotation = v.to_radians();
}
if let Some(v) = bsx {
s.block_content_scale_x = v;
}
if let Some(v) = bsy {
s.block_content_scale_y = v;
}
if let Some(v) = bsz {
s.block_content_scale_z = v;
}
}
}
pub(super) fn on_mleader_style_apply(&mut self) -> Task<Message> {
self.stage_mleaderstyle_bufs();
self.style_stage_commit();
Task::none()
}
pub(super) fn on_dim_style_dialog_open(&mut self) -> Task<Message> {
let i = self.active_tab;
// Pick the document's current dim style or "Standard".
let cur = self.tabs[i]
.scene
.document
.header
.current_dimstyle_name
.clone();
let selected = if self.tabs[i].scene.document.dim_styles.get(&cur).is_some() {
cur
} else {
self.tabs[i]
.scene
.document
.dim_styles
.iter()
.next()
.map(|s| s.name.clone())
.unwrap_or_else(|| "Standard".to_string())
};
self.dimstyle_selected = selected.clone();
self.dimstyle_compare = self.tabs[i]
.scene
.document
.dim_styles
.iter()
.map(|style| style.name.clone())
.find(|name| !name.eq_ignore_ascii_case(&selected))
.unwrap_or_default();
self.load_dimstyle_bufs(i);
self.active_modal = Some(crate::app::ModalKind::DimStyle);
self.style_stage_begin();
Task::none()
}
pub(super) fn on_color_window_pick(&mut self, color: acadrust::types::Color) -> Task<Message> {
let s = crate::ui::color_select::color_to_aci_string(color);
let edit = match self.color_pick_target.take().map(|(target, _)| target) {
Some(crate::app::ColorPickTarget::DimStyle(f)) => Some(Message::DsEdit(f, s)),
Some(crate::app::ColorPickTarget::MLeader(f)) => {
Some(Message::MLeaderStyleEdit { field: f, value: s })
}
Some(crate::app::ColorPickTarget::Table(r, f)) => {
Some(Message::TableStyleCellEdit {
row: r,
field: f,
value: s,
})
}
Some(crate::app::ColorPickTarget::Properties) => {
Some(Message::PropColorChanged(color))
}
Some(crate::app::ColorPickTarget::PropertiesBg) => {
Some(Message::PropBgColorChanged(color))
}
Some(crate::app::ColorPickTarget::MText) => {
Some(Message::MTextColorChanged(color))
}
Some(crate::app::ColorPickTarget::Ribbon) => {
Some(Message::RibbonColorChanged(color))
}
Some(crate::app::ColorPickTarget::Layer(idx)) => {
self.tabs[self.active_tab].layers.selected = Some(idx);
Some(Message::LayerColorSet(color))
}
Some(crate::app::ColorPickTarget::LayerState(idx)) => {
Some(Message::LayerStateEditorLayerColor(idx, color))
}
None => None,
};
if let Some(m) = edit {
self.update(m)
} else {
Task::none()
}
}
pub(super) fn on_ds_set_handle(&mut self, field: &'static str, value: String) -> Task<Message> {
let i = self.active_tab;
let name = self.dimstyle_selected.clone();
let is_lt = matches!(
field,
"dimltex_handle" | "dimltex1_handle" | "dimltex2_handle"
);
let doc = &self.tabs[i].scene.document;
if doc.dim_styles.get(&name).is_some_and(|style| {
style.xref_reference || style.xref_dependent || !style.xref_handle.is_null()
}) {
return Task::none();
}
let handle = if value == "Default" || value == "ByBlock" {
acadrust::types::Handle::NULL
} else if is_lt {
doc.line_types
.iter()
.find(|lt| lt.name == value)
.map(|lt| lt.handle)
.unwrap_or(acadrust::types::Handle::NULL)
} else {
doc.block_records
.iter()
.find(|b| b.name == value)
.map(|b| b.handle)
.unwrap_or(acadrust::types::Handle::NULL)
};
// Staged: persists on Apply.
if let Some(ds) = self.tabs[i].scene.document.dim_styles.get_mut(&name) {
match field {
"dimblk" => ds.dimblk = handle,
"dimblk1" => {
ds.dimblk1 = handle;
ds.dimblk2 = handle;
}
"dimblk2" => ds.dimblk2 = handle,
"dimldrblk" => ds.dimldrblk = handle,
"dimltex_handle" => ds.dimltex_handle = handle,
"dimltex1_handle" => ds.dimltex1_handle = handle,
"dimltex2_handle" => ds.dimltex2_handle = handle,
_ => {}
}
}
Task::none()
}
}