//! 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, pub lt_opts: Vec, pub text_style_opts: Vec, pub text_style_fixed_height: Option, pub compare_name: String, pub compare_opts: Vec, pub comparison_sections: Vec, pub read_only: bool, pub in_use: bool, /// Colour field whose expanded palette is currently open. pub color_open: Option, } 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 for DimensionPreview { type State = (); fn draw( &self, _state: &(), renderer: &iced::Renderer, theme: &Theme, bounds: Rectangle, _cursor: mouse::Cursor, ) -> Vec { 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, 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 = 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::().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, 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, 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::().unwrap_or(0.0); let tick_on = vals.dimtsz.trim().parse::().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::().unwrap_or(2).min(8); let factor = vals.dimlfac.trim().parse::().unwrap_or(1.0); let mut measured = format!("{:.*}", precision, 125.0 * factor); let zero_flags = vals.dimzin.trim().parse::().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::().unwrap_or(1.0); let alt_precision = vals.dimaltd.trim().parse::().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::().unwrap_or(0.0) > 0.0, arrow_size: vals.dimasz.trim().parse::().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(), }) }