Files
OneCAD-Draft/src/ui/style/dimstyle.rs
T
NgoQuocViet2001 074e589ce8 fix(dimension): render DIMAUNIT 4 as a surveyor's bearing
DIMAUNIT 4 is Surveyor's units, but format_angular_value matched only
0-3 and let 4 fall through the catch-all to decimal degrees. An angular
dimension in a drawing authored with surveyor units therefore read as
plain degrees, while AUNITS 4 in the same drawing writes a bearing for
the coordinate readout — the two disagreed on one angle.

Format the bearing from the dimension's own DMS path so it carries
DIMADEC, DIMAZIN and DIMDSEP, quantise before picking the quadrant so a
value just short of a cardinal does not quote a quarter turn, and offer
the option in the dimension-style dialog.
2026-09-03 15:23:42 +07:00

1240 lines
45 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
//! Dimension Style Manager window — fills the entire OS window.
use crate::app::{ColorPickTarget, DsField, Message};
use iced::widget::{
button, canvas, checkbox, column, container, row, scrollable, text, text_input, Space,
};
use iced::{mouse, Background, Border, Element, Length, Point, Rectangle, Theme};
use crate::ui::style::common::muted_style;
use crate::t;
use std::borrow::Cow;
use std::fmt;
#[derive(Clone, Debug, PartialEq, Eq)]
struct DimEnumChoice {
code: String,
label: String,
}
impl fmt::Display for DimEnumChoice {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(&self.label)
}
}
/// All DimStyle field values needed by the view.
pub struct DimStyleValues<'a> {
pub dimdle: &'a str,
pub dimdli: &'a str,
pub dimgap: &'a str,
pub dimexe: &'a str,
pub dimexo: &'a str,
pub dimsd1: bool,
pub dimsd2: bool,
pub dimse1: bool,
pub dimse2: bool,
pub dimasz: &'a str,
pub dimcen: &'a str,
pub dimtsz: &'a str,
pub dimtxt: &'a str,
pub dimtxsty: &'a str,
pub dimtad: &'a str,
pub dimtih: bool,
pub dimtoh: bool,
pub dimscale: &'a str,
pub dimlfac: &'a str,
pub dimlunit: &'a str,
pub dimdec: &'a str,
pub dimpost: &'a str,
pub dimtol: bool,
pub dimlim: bool,
pub dimtp: &'a str,
pub dimtm: &'a str,
pub dimtdec: &'a str,
pub dimtfac: &'a str,
pub annotative: bool,
pub dimclrd: &'a str,
pub dimlwd: &'a str,
pub dimclre: &'a str,
pub dimlwe: &'a str,
pub dimfxl: &'a str,
pub dimfxlon: bool,
pub dimsah: bool,
pub dimarcsym: &'a str,
pub dimjogang: &'a str,
pub dimclrt: &'a str,
pub dimjust: &'a str,
pub dimtvp: &'a str,
pub dimtfill: &'a str,
pub dimtfillclr: &'a str,
pub dimtxtdirection: bool,
pub dimatfit: &'a str,
pub dimtix: bool,
pub dimsoxd: bool,
pub dimtmove: &'a str,
pub dimupt: bool,
pub dimtofl: bool,
pub dimdsep: &'a str,
pub dimrnd: &'a str,
pub dimzin: &'a str,
pub dimfrac: &'a str,
pub dimaunit: &'a str,
pub dimadec: &'a str,
pub dimazin: &'a str,
pub dimalt: bool,
pub dimaltf: &'a str,
pub dimaltd: &'a str,
pub dimaltu: &'a str,
pub dimalttd: &'a str,
pub dimaltrnd: &'a str,
pub dimapost: &'a str,
pub dimaltz: &'a str,
pub dimalttz: &'a str,
pub dimtolj: &'a str,
pub dimtzin: &'a str,
// Resolved selected names for the block/linetype Handle fields.
pub dimblk_name: String,
pub dimblk1_name: String,
pub dimblk2_name: String,
pub dimldrblk_name: String,
pub dimltex_name: String,
pub dimltex1_name: String,
pub dimltex2_name: String,
// Dropdown option lists shared by the arrowhead / linetype fields.
pub block_opts: Vec<String>,
pub lt_opts: Vec<String>,
pub text_style_opts: Vec<String>,
pub text_style_fixed_height: Option<f64>,
pub compare_name: String,
pub compare_opts: Vec<String>,
pub comparison_sections: Vec<String>,
pub read_only: bool,
pub in_use: bool,
/// Colour field whose expanded palette is currently open.
pub color_open: Option<DsField>,
}
fn tab_btn_style(active: bool) -> impl Fn(&Theme, button::Status) -> button::Style {
move |theme: &Theme, st| {
let palette = theme.palette();
let pair = match (active, st) {
(true, _) => palette.primary.strong,
(false, button::Status::Hovered | button::Status::Pressed) => {
palette.background.strong
}
_ => palette.background.weak,
};
button::Style {
background: Some(Background::Color(pair.color)),
text_color: pair.text,
border: Border {
color: palette.background.neutral.color,
width: 1.0,
radius: 3.0.into(),
},
..Default::default()
}
}
}
fn field_style(theme: &Theme, status: text_input::Status) -> text_input::Style {
let palette = theme.palette();
let border = match status {
text_input::Status::Focused { .. } => palette.primary.base.color,
_ => palette.background.neutral.color,
};
text_input::Style {
background: Background::Color(palette.background.base.color),
border: Border {
color: border,
width: 1.0,
radius: 3.0.into(),
},
icon: palette.background.base.text,
placeholder: palette.background.base.text.scale_alpha(0.48),
value: palette.background.base.text,
selection: palette.primary.base.color.scale_alpha(0.5),
}
}
fn primary_style(theme: &Theme) -> iced::widget::text::Style {
iced::widget::text::Style {
color: Some(theme.palette().primary.base.color),
}
}
struct DimensionPreview {
ext1: bool,
ext2: bool,
dim1: bool,
dim2: bool,
tick: bool,
arrow_size: f32,
text_above: bool,
basic: bool,
text: String,
}
impl canvas::Program<Message> for DimensionPreview {
type State = ();
fn draw(
&self,
_state: &(),
renderer: &iced::Renderer,
theme: &Theme,
bounds: Rectangle,
_cursor: mouse::Cursor,
) -> Vec<canvas::Geometry> {
let mut frame = canvas::Frame::new(renderer, bounds.size());
let palette = theme.palette();
let ink = palette.background.base.text.scale_alpha(0.88);
let guide = palette.primary.base.color;
let stroke = canvas::Stroke::default().with_color(ink).with_width(1.2);
let x1 = 52.0;
let x2 = (bounds.width - 52.0).max(x1 + 40.0);
let y = bounds.height * 0.58;
let object_y = bounds.height - 18.0;
let line = |a: Point, b: Point| canvas::Path::line(a, b);
frame.stroke(
&line(Point::new(x1 - 18.0, object_y), Point::new(x2 + 18.0, object_y)),
canvas::Stroke::default().with_color(ink.scale_alpha(0.38)).with_width(1.0),
);
if self.ext1 {
frame.stroke(&line(Point::new(x1, object_y), Point::new(x1, 22.0)), stroke.clone());
}
if self.ext2 {
frame.stroke(&line(Point::new(x2, object_y), Point::new(x2, 22.0)), stroke.clone());
}
let text_half = (self.text.chars().count() as f32 * 3.4 + 8.0).min((x2 - x1) * 0.34);
if self.dim1 {
frame.stroke(&line(Point::new(x1, y), Point::new((bounds.width * 0.5 - text_half).max(x1), y)), stroke.clone());
}
if self.dim2 {
frame.stroke(&line(Point::new((bounds.width * 0.5 + text_half).min(x2), y), Point::new(x2, y)), stroke.clone());
}
let size = self.arrow_size.clamp(5.0, 14.0);
for (tip_x, sign) in [(x1, 1.0_f32), (x2, -1.0_f32)] {
if self.tick {
frame.stroke(
&line(
Point::new(tip_x - size * 0.55, y + size * 0.75),
Point::new(tip_x + size * 0.55, y - size * 0.75),
),
canvas::Stroke::default().with_color(guide).with_width(1.6),
);
} else {
let arrow = canvas::Path::new(|path| {
path.move_to(Point::new(tip_x, y));
path.line_to(Point::new(tip_x + sign * size, y - size * 0.42));
path.line_to(Point::new(tip_x + sign * size, y + size * 0.42));
path.close();
});
frame.fill(&arrow, guide);
}
}
let text_y = if self.text_above { y - 16.0 } else { y };
if self.basic {
let frame_width = (self.text.chars().count() as f32 * 7.0 + 12.0)
.min((x2 - x1) * 0.8);
frame.stroke(
&canvas::Path::rectangle(
Point::new(bounds.width * 0.5 - frame_width * 0.5, text_y - 9.0),
iced::Size::new(frame_width, 18.0),
),
canvas::Stroke::default().with_color(ink).with_width(1.0),
);
}
frame.fill_text(canvas::Text {
content: self.text.clone(),
position: Point::new(bounds.width * 0.5, text_y),
color: ink,
size: iced::Pixels(12.0),
align_x: iced::advanced::text::Alignment::Center,
align_y: iced::alignment::Vertical::Center,
shaping: iced::advanced::text::Shaping::Advanced,
..Default::default()
});
vec![frame.into_geometry()]
}
}
fn hdivider<'a>(width: iced::Length) -> Element<'a, Message> {
container(Space::new().width(width).height(1))
.width(width)
.height(1)
.style(|theme: &Theme| container::Style {
background: Some(Background::Color(
theme.palette().background.neutral.color
)),
..Default::default()
})
.into()
}
pub fn view_window<'a>(
styles: Vec<String>,
selected: &'a str,
current: &'a str,
tab: u8,
vals: DimStyleValues<'a>,
rename_active: Option<&'a str>,
rename_buf: &'a str,
sizing: crate::ui::modal::ModalSizing,
) -> Element<'a, Message> {
// ── Tab bar ───────────────────────────────────────────────────────────
let tabs = row![
button(text(t!("Lines")).size(11))
.on_press(Message::DimStyleDialogTab(0))
.style(tab_btn_style(tab == 0))
.padding([4, 10]),
button(text(t!("Symbols and Arrows")).size(11))
.on_press(Message::DimStyleDialogTab(1))
.style(tab_btn_style(tab == 1))
.padding([4, 10]),
button(text(t!("Text")).size(11))
.on_press(Message::DimStyleDialogTab(2))
.style(tab_btn_style(tab == 2))
.padding([4, 10]),
button(text(t!("Fit")).size(11))
.on_press(Message::DimStyleDialogTab(3))
.style(tab_btn_style(tab == 3))
.padding([4, 10]),
button(text(t!("Primary Units")).size(11))
.on_press(Message::DimStyleDialogTab(4))
.style(tab_btn_style(tab == 4))
.padding([4, 10]),
button(text(t!("Alternate Units")).size(11))
.on_press(Message::DimStyleDialogTab(5))
.style(tab_btn_style(tab == 5))
.padding([4, 10]),
button(text(t!("Tolerances")).size(11))
.on_press(Message::DimStyleDialogTab(6))
.style(tab_btn_style(tab == 6))
.padding([4, 10]),
]
.spacing(2);
let lbl = |s: Cow<'static, str>| text(s).size(11).style(muted_style).width(180);
let mk_field = |fld: DsField, val: &'a str| -> Element<'a, Message> {
if vals.read_only {
crate::ui::read_only::field(val, 11.0, Length::Fixed(100.0))
} else {
text_input("", val)
.on_input(move |s| Message::DsEdit(fld.clone(), s))
.style(field_style)
.size(11)
.width(100)
.into()
}
};
let mk_field_enabled = |fld: DsField, val: &'a str, enabled: bool| -> Element<'a, Message> {
if enabled && !vals.read_only {
text_input("", val)
.on_input(move |value| Message::DsEdit(fld.clone(), value))
.style(field_style)
.size(11)
.width(100)
.into()
} else {
crate::ui::read_only::field(val, 11.0, Length::Fixed(100.0))
}
};
let chk = |label: Cow<'static, str>, val: bool, fld: DsField| -> Element<'a, Message> {
let item = checkbox(val).label(label).size(14).text_size(11);
if vals.read_only {
item.into()
} else {
item.on_toggle(move |_| Message::DsToggle(fld.clone())).into()
}
};
// Enum dropdown: maps the stored integer code to a named option and back,
// so the user picks "Above" rather than typing "1".
let enum_field = move |label: Cow<'static, str>,
fld: DsField,
val: &'a str,
opts: &'static [(&'static str, &'static str)]|
-> Element<'a, Message> {
let options: Vec<DimEnumChoice> = opts
.iter()
.map(|(code, label)| DimEnumChoice {
code: (*code).to_string(),
label: crate::i18n::translate(label).into_owned(),
})
.collect();
let cur = options
.iter()
.find(|choice| choice.code == val.trim())
.cloned()
.unwrap_or_else(|| DimEnumChoice {
code: val.to_string(),
label: val.to_string(),
});
let choice: Element<'a, Message> = if vals.read_only {
crate::ui::read_only::field(cur.to_string().as_str(), 11.0, Length::Fixed(150.0))
} else {
iced::widget::pick_list(Some(cur), options, |value| value.to_string())
.on_select(move |chosen: DimEnumChoice| {
Message::DsEdit(fld.clone(), chosen.code)
})
.text_size(11)
.width(150)
.into()
};
row![lbl(label), choice]
.spacing(8)
.align_y(iced::Center)
.into()
};
// Linear unit formats shared by DIMLUNIT / DIMALTU.
const OPT_LUNIT: &[(&str, &str)] = &[
("1", "Scientific"),
("2", "Decimal"),
("3", "Engineering"),
("4", "Architectural"),
("5", "Fractional"),
("6", "Windows desktop"),
];
const OPT_LINEWEIGHT: &[(&str, &str)] = &[
("-1", "By layer"),
("-2", "By block"),
("-3", "Default"),
("0", "0.00 mm"),
("5", "0.05 mm"),
("9", "0.09 mm"),
("13", "0.13 mm"),
("15", "0.15 mm"),
("18", "0.18 mm"),
("20", "0.20 mm"),
("25", "0.25 mm"),
("30", "0.30 mm"),
("35", "0.35 mm"),
("40", "0.40 mm"),
("50", "0.50 mm"),
("53", "0.53 mm"),
("60", "0.60 mm"),
("70", "0.70 mm"),
("80", "0.80 mm"),
("90", "0.90 mm"),
("100", "1.00 mm"),
("106", "1.06 mm"),
("120", "1.20 mm"),
("140", "1.40 mm"),
("158", "1.58 mm"),
("200", "2.00 mm"),
("211", "2.11 mm"),
];
let alternate_unit_name = OPT_LUNIT
.iter()
.find(|(code, _)| *code == vals.dimaltu.trim())
.map(|(_, label)| crate::i18n::translate(label).into_owned())
.unwrap_or_else(|| vals.dimaltu.to_string());
let zero_controls = |field: DsField,
value: &'a str,
show_feet_inches: bool,
enabled: bool|
-> Element<'a, Message> {
let raw = value.trim().parse::<i16>().unwrap_or(0);
let feet_modes = vec![
DimEnumChoice { code: "0".into(), label: t!("Suppress zero feet and zero inches").into_owned() },
DimEnumChoice { code: "1".into(), label: t!("Show zero feet and zero inches").into_owned() },
DimEnumChoice { code: "2".into(), label: t!("Show zero feet; suppress zero inches").into_owned() },
DimEnumChoice { code: "3".into(), label: t!("Suppress zero feet; show zero inches").into_owned() },
];
let feet_current = feet_modes[(raw & 3) as usize].clone();
let feet: Element<'a, Message> = if show_feet_inches {
let list = iced::widget::pick_list(Some(feet_current.clone()), feet_modes, |choice| choice.to_string())
.text_size(11)
.width(210);
let list: Element<'a, Message> = if enabled && !vals.read_only {
let target = field.clone();
list.on_select(move |choice| {
Message::DsZeroBase(target.clone(), choice.code.parse().unwrap_or(0))
}).into()
} else {
crate::ui::read_only::field(feet_current.to_string().as_str(), 11.0, Length::Fixed(210.0))
};
row![lbl(t!("Feet and inches")), list]
.spacing(8).align_y(iced::Center).into()
} else {
Space::new().height(0).into()
};
let leading = checkbox(raw & 4 != 0)
.label(t!("Suppress leading zeros"))
.size(14)
.text_size(11);
let trailing = checkbox(raw & 8 != 0)
.label(t!("Suppress trailing zeros"))
.size(14)
.text_size(11);
let leading: Element<'a, Message> = if enabled && !vals.read_only {
let target = field.clone();
leading.on_toggle(move |_| Message::DsZeroFlag(target.clone(), 4)).into()
} else {
leading.into()
};
let trailing: Element<'a, Message> = if enabled && !vals.read_only {
trailing.on_toggle(move |_| Message::DsZeroFlag(field.clone(), 8)).into()
} else {
trailing.into()
};
column![feet, leading, trailing].spacing(6).into()
};
// Shared colour selector (main dropdown + "more" palette), reusing the
// existing DsEdit path (the chosen colour is sent as an ACI string).
let color_open = vals.color_open.clone();
let color_row = move |label: Cow<'static, str>, fld: DsField, _val: &'a str| -> Element<'a, Message> {
if vals.read_only {
return row![
lbl(label),
crate::ui::read_only::field(_val, 11.0, Length::Fixed(150.0)),
]
.spacing(8)
.align_y(iced::Center)
.into();
}
let cur = crate::ui::color_select::aci_string_to_color(_val);
let open = color_open.as_ref() == Some(&fld);
let f_sel = fld.clone();
let selector = crate::ui::color_select::color_selector(
cur,
open,
crate::ui::color_select::ColorExtras {
by_layer: true,
by_block: true,
..Default::default()
},
move |c| Message::DsEdit(f_sel.clone(), crate::ui::color_select::color_to_aci_string(c)),
Message::DsColorMore(fld.clone()),
Message::OpenColorWindow(
ColorPickTarget::DimStyle(fld.clone()),
cur,
),
);
row![lbl(label), container(selector).width(150)]
.spacing(8)
.align_y(iced::Center)
.into()
};
// Block / linetype Handle dropdown: pick a block-record (arrowheads) or a
// linetype by name from the available records.
let hrow = move |label: Cow<'static, str>,
options: Vec<String>,
selected: String,
field: &'static str|
-> Element<'a, Message> {
let list: Element<'a, Message> = if vals.read_only {
crate::ui::read_only::field(&selected, 11.0, Length::Fixed(150.0))
} else {
iced::widget::pick_list(Some(selected), options, |value| value.to_string())
.on_select(move |value| Message::DsSetHandle { field, value })
.text_size(11)
.width(150)
.into()
};
row![lbl(label), list]
.spacing(8)
.align_y(iced::Center)
.into()
};
let hrow_enabled = move |label: Cow<'static, str>,
options: Vec<String>,
selected: String,
field: &'static str,
enabled: bool|
-> Element<'a, Message> {
let list = iced::widget::pick_list(Some(selected.clone()), options, |value| value.to_string())
.text_size(11)
.width(150);
let list: Element<'a, Message> = if enabled && !vals.read_only {
list.on_select(move |value| Message::DsSetHandle { field, value }).into()
} else {
crate::ui::read_only::field(&selected, 11.0, Length::Fixed(150.0))
};
row![lbl(label), list]
.spacing(8)
.align_y(iced::Center)
.into()
};
let text_style_field: Element<'a, Message> = if vals.read_only {
crate::ui::read_only::field(&vals.dimtxsty, 11.0, Length::Fixed(150.0))
} else {
iced::widget::pick_list(
Some(vals.dimtxsty.to_string()),
vals.text_style_opts.clone(),
|value| value.to_string(),
)
.on_select(|value| Message::DsEdit(DsField::Dimtxsty, value))
.text_size(11)
.width(150)
.into()
};
let text_height_note: Element<'a, Message> = if let Some(height) = vals.text_style_fixed_height {
text(t!("The selected text style fixes the height at %{height}.", height = height))
.size(10)
.style(muted_style)
.into()
} else {
Space::new().height(0).into()
};
let center_mode = vals.dimcen.trim().parse::<f64>().unwrap_or(0.0);
let tick_on = vals.dimtsz.trim().parse::<f64>().unwrap_or(0.0) > 0.0;
let center_choices = vec![
DimEnumChoice { code: "none".into(), label: t!("None").into_owned() },
DimEnumChoice { code: "mark".into(), label: t!("Center mark").into_owned() },
DimEnumChoice { code: "lines".into(), label: t!("Centerlines").into_owned() },
];
let center_current = if center_mode < 0.0 {
center_choices[2].clone()
} else if center_mode > 0.0 {
center_choices[1].clone()
} else {
center_choices[0].clone()
};
let center_method_field: Element<'a, Message> = if vals.read_only {
crate::ui::read_only::field(center_current.to_string().as_str(), 11.0, Length::Fixed(150.0))
} else {
iced::widget::pick_list(Some(center_current), center_choices, |choice| choice.to_string())
.on_select(|choice| Message::DsCenterMarkMode(choice.code))
.text_size(11)
.width(150)
.into()
};
let tolerance_choices = vec![
DimEnumChoice { code: "none".into(), label: t!("None").into_owned() },
DimEnumChoice { code: "symmetrical".into(), label: t!("Symmetrical").into_owned() },
DimEnumChoice { code: "deviation".into(), label: t!("Deviation").into_owned() },
DimEnumChoice { code: "limits".into(), label: t!("Limits").into_owned() },
DimEnumChoice { code: "basic".into(), label: t!("Basic").into_owned() },
];
let tolerance_current = if vals.dimgap.trim().starts_with('-') {
tolerance_choices[4].clone()
} else if vals.dimlim {
tolerance_choices[3].clone()
} else if vals.dimtol && vals.dimtp.trim() == vals.dimtm.trim() {
tolerance_choices[1].clone()
} else if vals.dimtol {
tolerance_choices[2].clone()
} else {
tolerance_choices[0].clone()
};
let tolerance_method_field: Element<'a, Message> = if vals.read_only {
crate::ui::read_only::field(tolerance_current.to_string().as_str(), 11.0, Length::Fixed(150.0))
} else {
iced::widget::pick_list(
Some(tolerance_current),
tolerance_choices,
|choice| choice.to_string(),
)
.on_select(|choice| Message::DsToleranceMode(choice.code))
.text_size(11)
.width(150)
.into()
};
let tab_content: Element<'_, Message> = match tab {
0 => column![
text(t!("Dimension Line")).size(11).style(primary_style),
row![
lbl(t!("Extend beyond ticks")),
mk_field(DsField::Dimdle, vals.dimdle)
]
.spacing(8)
.align_y(iced::Center),
row![
lbl(t!("Baseline spacing")),
mk_field(DsField::Dimdli, vals.dimdli)
]
.spacing(8)
.align_y(iced::Center),
row![
lbl(t!("Text gap")),
mk_field(DsField::Dimgap, vals.dimgap)
]
.spacing(8)
.align_y(iced::Center),
chk(t!("Suppress first dimension line"), vals.dimsd1, DsField::Dimsd1),
chk(t!("Suppress second dimension line"), vals.dimsd2, DsField::Dimsd2),
text(t!("Extension Line")).size(11).style(primary_style),
row![
lbl(t!("Extend beyond dimension line")),
mk_field(DsField::Dimexe, vals.dimexe)
]
.spacing(8)
.align_y(iced::Center),
row![
lbl(t!("Offset from measured point")),
mk_field(DsField::Dimexo, vals.dimexo)
]
.spacing(8)
.align_y(iced::Center),
chk(t!("Suppress first extension line"), vals.dimse1, DsField::Dimse1),
chk(t!("Suppress second extension line"), vals.dimse2, DsField::Dimse2),
color_row(t!("Dimension line color"), DsField::Dimclrd, vals.dimclrd),
enum_field(t!("Dimension lineweight"), DsField::Dimlwd, vals.dimlwd, OPT_LINEWEIGHT),
color_row(t!("Extension line color"), DsField::Dimclre, vals.dimclre),
enum_field(t!("Extension lineweight"), DsField::Dimlwe, vals.dimlwe, OPT_LINEWEIGHT),
chk(
t!("Use fixed-length extension lines"),
vals.dimfxlon,
DsField::Dimfxlon
),
row![
lbl(t!("Fixed length")),
mk_field_enabled(DsField::Dimfxl, vals.dimfxl, vals.dimfxlon)
]
.spacing(8)
.align_y(iced::Center),
hrow(
t!("Dimension linetype"),
vals.lt_opts.clone(),
vals.dimltex_name.clone(),
"dimltex_handle"
),
hrow(
t!("First extension linetype"),
vals.lt_opts.clone(),
vals.dimltex1_name.clone(),
"dimltex1_handle"
),
hrow(
t!("Second extension linetype"),
vals.lt_opts.clone(),
vals.dimltex2_name.clone(),
"dimltex2_handle"
),
]
.spacing(7)
.into(),
1 => column![
text(t!("Arrowheads")).size(11).style(primary_style),
hrow_enabled(
t!("Both arrowheads"),
vals.block_opts.clone(),
vals.dimblk_name.clone(),
"dimblk",
!vals.dimsah && !tick_on
),
hrow_enabled(
t!("First arrowhead"),
vals.block_opts.clone(),
vals.dimblk1_name.clone(),
"dimblk1",
vals.dimsah && !tick_on
),
hrow_enabled(
t!("Second arrowhead"),
vals.block_opts.clone(),
vals.dimblk2_name.clone(),
"dimblk2",
vals.dimsah && !tick_on
),
hrow(
t!("Leader arrowhead"),
vals.block_opts.clone(),
vals.dimldrblk_name.clone(),
"dimldrblk"
),
row![
lbl(t!("Arrow size")),
mk_field_enabled(DsField::Dimasz, vals.dimasz, !tick_on)
]
.spacing(8)
.align_y(iced::Center),
row![lbl(t!("Center marks")), center_method_field]
.spacing(8)
.align_y(iced::Center),
row![
lbl(t!("Center mark size")),
mk_field_enabled(DsField::Dimcen, vals.dimcen, center_mode != 0.0)
]
.spacing(8)
.align_y(iced::Center),
row![
lbl(t!("Tick size")),
mk_field(DsField::Dimtsz, vals.dimtsz)
]
.spacing(8)
.align_y(iced::Center),
chk(
t!("Use different first and second arrowheads"),
vals.dimsah,
DsField::Dimsah
),
enum_field(
t!("Arc length symbol"),
DsField::Dimarcsym,
vals.dimarcsym,
&[("0", "Before text"), ("1", "Above text"), ("2", "None")],
),
row![
lbl(t!("Radius jog angle (°)")),
mk_field(DsField::Dimjogang, vals.dimjogang)
]
.spacing(8)
.align_y(iced::Center),
]
.spacing(7)
.into(),
2 => column![
text(t!("Text")).size(11).style(primary_style),
row![
lbl(t!("Text height")),
mk_field_enabled(DsField::Dimtxt, vals.dimtxt, vals.text_style_fixed_height.is_none())
]
.spacing(8)
.align_y(iced::Center),
text_height_note,
row![
lbl(t!("Text style")),
text_style_field
]
.spacing(8)
.align_y(iced::Center),
enum_field(
t!("Vertical placement"),
DsField::Dimtad,
vals.dimtad,
&[
("0", "Centered"),
("1", "Above"),
("2", "Outside"),
("3", "JIS"),
("4", "Below"),
],
),
chk(t!("Keep text horizontal when inside"), vals.dimtih, DsField::Dimtih),
chk(t!("Keep text horizontal when outside"), vals.dimtoh, DsField::Dimtoh),
color_row(t!("Text color"), DsField::Dimclrt, vals.dimclrt),
enum_field(
t!("Horizontal placement"),
DsField::Dimjust,
vals.dimjust,
&[
("0", "Centered"),
("1", "At first ext"),
("2", "At second ext"),
("3", "Over first ext"),
("4", "Over second ext"),
],
),
row![
lbl(t!("Vertical offset")),
mk_field(DsField::Dimtvp, vals.dimtvp)
]
.spacing(8)
.align_y(iced::Center),
enum_field(
t!("Text background"),
DsField::Dimtfill,
vals.dimtfill,
&[
("0", "None"),
("1", "Drawing background"),
("2", "Color"),
],
),
if vals.dimtfill.trim() == "2" {
color_row(t!("Background color"), DsField::Dimtfillclr, vals.dimtfillclr)
} else {
row![lbl(t!("Background color")), container(text(t!("Not applicable")).size(11).style(muted_style)).padding([4, 7]).width(150)]
.spacing(8).align_y(iced::Center).into()
},
chk(
t!("Read left to right"),
vals.dimtxtdirection,
DsField::Dimtxtdirection
),
]
.spacing(7)
.into(),
3 => column![
text(t!("Fit options")).size(11).style(primary_style),
enum_field(
t!("When space is limited"),
DsField::Dimatfit,
vals.dimatfit,
&[
("0", "Move text and arrows outside"),
("1", "Move arrows outside first"),
("2", "Move text outside first"),
("3", "Best fit"),
],
),
chk(t!("Always keep text between extension lines"), vals.dimtix, DsField::Dimtix),
chk(t!("Suppress arrows when they do not fit"), vals.dimsoxd, DsField::Dimsoxd),
text(t!("Text movement")).size(11).style(primary_style),
enum_field(
t!("When text is moved"),
DsField::Dimtmove,
vals.dimtmove,
&[
("0", "Keep dimension line with text"),
("1", "Add a leader"),
("2", "Move text freely"),
],
),
text(t!("Scale")).size(11).style(primary_style),
chk(t!("Annotative"), vals.annotative, DsField::Annotative),
row![
lbl(t!("Overall scale")),
if vals.annotative {
container(text(t!("Automatic")).size(11).style(muted_style))
.padding([4, 7])
.width(100)
.into()
} else {
mk_field(DsField::Dimscale, vals.dimscale)
}
]
.spacing(8)
.align_y(iced::Center),
text(t!("Fine tuning")).size(11).style(primary_style),
chk(t!("Place text manually while creating dimensions"), vals.dimupt, DsField::Dimupt),
chk(t!("Draw dimension line between extension lines"), vals.dimtofl, DsField::Dimtofl),
]
.spacing(8)
.into(),
4 => column![
text(t!("Linear dimensions")).size(11).style(primary_style),
enum_field(t!("Unit format"), DsField::Dimlunit, vals.dimlunit, OPT_LUNIT),
row![lbl(t!("Precision")), mk_field(DsField::Dimdec, vals.dimdec)]
.spacing(8).align_y(iced::Center),
if matches!(vals.dimlunit.trim(), "4" | "5") {
enum_field(
t!("Fraction format"),
DsField::Dimfrac,
vals.dimfrac,
&[("0", "Horizontal"), ("1", "Diagonal"), ("2", "Not stacked")],
)
} else {
row![lbl(t!("Fraction format")), container(text(t!("Not applicable")).size(11).style(muted_style)).padding([4, 7]).width(150)]
.spacing(8).align_y(iced::Center).into()
},
if vals.dimlunit.trim() == "2" {
enum_field(
t!("Decimal separator"),
DsField::Dimdsep,
vals.dimdsep,
&[("46", "Period"), ("44", "Comma"), ("32", "Space")],
)
} else {
row![lbl(t!("Decimal separator")), container(text(t!("Not applicable")).size(11).style(muted_style)).padding([4, 7]).width(150)]
.spacing(8).align_y(iced::Center).into()
},
row![lbl(t!("Round off")), mk_field(DsField::Dimrnd, vals.dimrnd)]
.spacing(8).align_y(iced::Center),
row![lbl(t!("Measurement template")), mk_field(DsField::Dimpost, vals.dimpost)]
.spacing(8).align_y(iced::Center),
text(t!("Use <> as the measured value; text before and after it becomes the prefix and suffix."))
.size(10).style(muted_style),
row![lbl(t!("Measurement scale")), mk_field(DsField::Dimlfac, vals.dimlfac)]
.spacing(8).align_y(iced::Center),
text(t!("Zero suppression")).size(11).style(primary_style),
zero_controls(
DsField::Dimzin,
vals.dimzin,
matches!(vals.dimlunit.trim(), "3" | "4"),
true,
),
text(t!("Angular dimensions")).size(11).style(primary_style),
enum_field(
t!("Unit format"),
DsField::Dimaunit,
vals.dimaunit,
&[("0", "Decimal degrees"), ("1", "Degrees, minutes, seconds"), ("2", "Gradians"), ("3", "Radians"), ("4", "Surveyor's units")],
),
row![lbl(t!("Precision")), mk_field(DsField::Dimadec, vals.dimadec)]
.spacing(8).align_y(iced::Center),
enum_field(
t!("Zero suppression"),
DsField::Dimazin,
vals.dimazin,
&[("0", "None"), ("1", "Leading"), ("2", "Trailing"), ("3", "Leading and trailing")],
),
]
.spacing(8)
.into(),
5 => column![
text(t!("Alternate Units")).size(11).style(primary_style),
chk(
t!("Display alternate units"),
vals.dimalt,
DsField::Dimalt
),
row![
lbl(t!("Multiplier")),
mk_field_enabled(DsField::Dimaltf, vals.dimaltf, vals.dimalt)
]
.spacing(8)
.align_y(iced::Center),
row![
lbl(t!("Precision")),
mk_field_enabled(DsField::Dimaltd, vals.dimaltd, vals.dimalt)
]
.spacing(8)
.align_y(iced::Center),
if vals.dimalt {
enum_field(t!("Unit format"), DsField::Dimaltu, vals.dimaltu, OPT_LUNIT)
} else {
row![lbl(t!("Unit format")), container(text(alternate_unit_name).size(11).style(muted_style)).padding([4, 7]).width(150)]
.spacing(8).align_y(iced::Center).into()
},
row![
lbl(t!("Tolerance precision")),
mk_field_enabled(DsField::Dimalttd, vals.dimalttd, vals.dimalt)
]
.spacing(8)
.align_y(iced::Center),
row![
lbl(t!("Round off")),
mk_field_enabled(DsField::Dimaltrnd, vals.dimaltrnd, vals.dimalt)
]
.spacing(8)
.align_y(iced::Center),
row![
lbl(t!("Measurement template")),
mk_field_enabled(DsField::Dimapost, vals.dimapost, vals.dimalt)
]
.spacing(8)
.align_y(iced::Center),
text(t!("Zero suppression")).size(11).style(primary_style),
zero_controls(
DsField::Dimaltz,
vals.dimaltz,
matches!(vals.dimaltu.trim(), "3" | "4"),
vals.dimalt,
),
text(t!("Tolerance zero suppression")).size(11).style(primary_style),
zero_controls(
DsField::Dimalttz,
vals.dimalttz,
matches!(vals.dimaltu.trim(), "3" | "4"),
vals.dimalt,
),
]
.spacing(7)
.into(),
_ => column![
text(t!("Tolerances")).size(11).style(primary_style),
row![
lbl(t!("Method")),
tolerance_method_field
]
.spacing(8)
.align_y(iced::Center),
row![
lbl(t!("Upper value")),
mk_field_enabled(DsField::Dimtp, vals.dimtp, vals.dimtol || vals.dimlim)
]
.spacing(8)
.align_y(iced::Center),
row![
lbl(t!("Lower value")),
mk_field_enabled(
DsField::Dimtm,
vals.dimtm,
(vals.dimtol || vals.dimlim)
&& !(vals.dimtol && vals.dimtp.trim() == vals.dimtm.trim()),
)
]
.spacing(8)
.align_y(iced::Center),
row![
lbl(t!("Precision")),
mk_field_enabled(DsField::Dimtdec, vals.dimtdec, vals.dimtol || vals.dimlim)
]
.spacing(8)
.align_y(iced::Center),
row![
lbl(t!("Height scale")),
mk_field_enabled(DsField::Dimtfac, vals.dimtfac, vals.dimtol || vals.dimlim)
]
.spacing(8)
.align_y(iced::Center),
enum_field(
t!("Vertical position"),
DsField::Dimtolj,
vals.dimtolj,
&[("0", "Bottom"), ("1", "Middle"), ("2", "Top")],
),
text(t!("Zero suppression")).size(11).style(primary_style),
zero_controls(
DsField::Dimtzin,
vals.dimtzin,
matches!(vals.dimlunit.trim(), "3" | "4"),
vals.dimtol || vals.dimlim,
),
]
.spacing(7)
.into(),
};
let precision = vals.dimdec.trim().parse::<usize>().unwrap_or(2).min(8);
let factor = vals.dimlfac.trim().parse::<f64>().unwrap_or(1.0);
let mut measured = format!("{:.*}", precision, 125.0 * factor);
let zero_flags = vals.dimzin.trim().parse::<i16>().unwrap_or(0);
if zero_flags & 8 != 0 && measured.contains('.') {
while measured.ends_with('0') {
measured.pop();
}
if measured.ends_with('.') {
measured.pop();
}
}
if zero_flags & 4 != 0 && measured.starts_with("0.") {
measured.remove(0);
}
if vals.dimdsep.trim() == "44" {
measured = measured.replace('.', ",");
} else if vals.dimdsep.trim() == "32" {
measured = measured.replace('.', " ");
}
let mut preview_text = if vals.dimpost.contains("<>") {
vals.dimpost.replace("<>", &measured)
} else {
format!("{measured}{}", vals.dimpost)
};
if vals.dimalt {
let alternate = 125.0 * vals.dimaltf.trim().parse::<f64>().unwrap_or(1.0);
let alt_precision = vals.dimaltd.trim().parse::<usize>().unwrap_or(2).min(8);
let alt_value = format!("{:.*}", alt_precision, alternate);
let alternate_text = if vals.dimapost.contains("<>") {
vals.dimapost.replace("<>", &alt_value)
} else {
format!("{alt_value}{}", vals.dimapost)
};
preview_text.push_str(&format!(" [{alternate_text}]"));
}
if vals.dimlim {
preview_text = format!(
"{} / {}",
preview_text,
vals.dimtm.trim()
);
} else if vals.dimtol {
if vals.dimtp.trim() == vals.dimtm.trim() {
preview_text.push_str(&format!(" ±{}", vals.dimtp.trim()));
} else {
preview_text.push_str(&format!(" +{} −{}", vals.dimtp.trim(), vals.dimtm.trim()));
}
}
let preview = canvas(DimensionPreview {
ext1: !vals.dimse1,
ext2: !vals.dimse2,
dim1: !vals.dimsd1,
dim2: !vals.dimsd2,
tick: vals.dimtsz.trim().parse::<f32>().unwrap_or(0.0) > 0.0,
arrow_size: vals.dimasz.trim().parse::<f32>().unwrap_or(1.0).abs() * 5.0 + 5.0,
text_above: vals.dimtad.trim() != "0",
basic: vals.dimgap.trim().starts_with('-'),
text: preview_text,
})
.width(Length::Fill)
.height(Length::Fixed(112.0));
let preview_panel = container(preview)
.width(Length::Fill)
.style(|theme: &Theme| container::Style {
background: Some(Background::Color(theme.palette().background.weak.color)),
border: Border {
color: theme.palette().background.neutral.color,
width: 1.0,
radius: 3.0.into(),
},
..Default::default()
});
let status = if vals.read_only {
t!("Referenced style · Read only")
} else if selected == current {
t!("Current style")
} else if vals.in_use {
t!("Used in drawing")
} else {
t!("Available style")
};
let comparison: Element<'a, Message> = if vals.compare_opts.is_empty() {
Space::new().height(0).into()
} else {
let summary = if vals.comparison_sections.is_empty() {
t!("No differences").into_owned()
} else {
format!("{}: {}", t!("Different sections"), vals.comparison_sections.join(", "))
};
row![
text(t!("Compare with")).size(10).style(muted_style),
iced::widget::pick_list(
Some(vals.compare_name.clone()),
vals.compare_opts.clone(),
|value| value.to_string(),
)
.on_select(Message::DimStyleDialogCompare)
.text_size(11)
.width(150),
text(summary).size(10).style(muted_style),
]
.spacing(8)
.align_y(iced::Center)
.into()
};
let right_panel = container(
column![
row![
text(selected).size(13).style(primary_style),
Space::new().width(Length::Fill),
text(status).size(10).style(muted_style),
]
.align_y(iced::Center),
preview_panel,
comparison,
tabs,
hdivider(Length::Fill),
scrollable(container(tab_content).padding([12, 12]).width(Length::Fill))
.width(Length::Fill)
.height(sizing.height),
]
.spacing(6)
.height(sizing.height),
)
.height(sizing.height)
.width(Length::Fill)
.padding(iced::Padding {
top: 12.0,
right: 0.0,
bottom: 12.0,
left: 0.0,
});
crate::ui::style::style_manager::view(crate::ui::style::style_manager::Scaffold {
sizing,
kind: crate::app::StyleKind::Dim,
styles: &styles,
selected,
current: Some(current),
rename_active,
rename_buf,
on_new: Message::DimStyleDialogNew,
on_copy: Message::DimStyleDialogCopy,
on_delete: Message::DimStyleDialogDelete,
on_select: Message::DimStyleDialogSelect,
on_set_current: Message::DimStyleDialogSetCurrent,
on_apply: Message::DimStyleDialogApply,
read_only: vals.read_only,
editor: right_panel.into(),
})
}