Merge remote-tracking branch 'upstream/main' into fix/716-perpendicular-tracking

This commit is contained in:
gianlucafiore 2026-08-09 11:43:08 -03:00
commit af7613f1d8
38 changed files with 45433 additions and 32398 deletions

View file

@ -339,10 +339,8 @@ pub(super) fn polar_constrain(
))
}
/// Polar constraint that only engages when the cursor is within `tol_px`
/// screen pixels of the nearest polar ray; otherwise the cursor is left free
/// so POLAR behaves as if off when pointing away from every angle (issue #70).
pub(super) fn polar_constrain_near(
/// Return the polar-constrained point while its ray is engaged.
pub(super) fn polar_constrain_if_near(
pt: glam::DVec3,
base: glam::DVec3,
step_deg: f32,
@ -351,7 +349,7 @@ pub(super) fn polar_constrain_near(
bounds: iced::Rectangle,
tol_px: f32,
xf: &UcsXform,
) -> glam::DVec3 {
) -> Option<glam::DVec3> {
let snapped = polar_constrain(pt, base, step_deg, xf);
let to_screen = |w: glam::DVec3| {
let ndc = view_rot.project_point3((w - eye).as_vec3());
@ -362,11 +360,22 @@ pub(super) fn polar_constrain_near(
};
let (cx, cy) = to_screen(pt);
let (sx, sy) = to_screen(snapped);
if ((cx - sx).powi(2) + (cy - sy).powi(2)).sqrt() <= tol_px {
snapped
} else {
pt
}
(((cx - sx).powi(2) + (cy - sy).powi(2)).sqrt() <= tol_px).then_some(snapped)
}
/// Constrain near an engaged polar ray; otherwise keep the cursor free.
pub(super) fn polar_constrain_near(
pt: glam::DVec3,
base: glam::DVec3,
step_deg: f32,
view_rot: glam::Mat4,
eye: glam::DVec3,
bounds: iced::Rectangle,
tol_px: f32,
xf: &UcsXform,
) -> glam::DVec3 {
polar_constrain_if_near(pt, base, step_deg, view_rot, eye, bounds, tol_px, xf)
.unwrap_or(pt)
}
/// Hard axis lock (#312): the locked ray's direction — the nearest polar

View file

@ -430,8 +430,7 @@ pub(super) struct OpenCADStudio {
/// cursor is on a tracking ray. Lets a typed distance place a point along
/// the ray from the tracking point (issue #69). `None` when not aligned.
otrack_active: Option<(glam::DVec3, glam::DVec3)>,
/// Kind of the currently active tracking reference, kept separately from
/// `otrack_active` so typed-distance behavior remains unchanged.
/// Active OTRACK ray kind, separate from typed-distance geometry.
otrack_kind: Option<crate::snap::TrackingKind>,
/// Whether Tangent snap was enabled before a tangent-pick command started.
pre_cmd_tangent: Option<bool>,

View file

@ -1384,7 +1384,7 @@ pub(super) fn on_open_file(&mut self) -> Task<Message> {
&& destination_is_current
{
self.command_line.push_error_once(
crate::tr!("recovery-save-new-file-required").as_ref(),
crate::tr!("recovery", "save-new-file-required").as_ref(),
);
self.restore_failed_save_continuation(continuation, i);
self.active_tab = i;

View file

@ -3137,6 +3137,8 @@ impl OpenCADStudio {
self.snapper.otrack_enabled ^= true;
if !self.snapper.otrack_enabled {
self.snapper.clear_tracking();
self.otrack_active = None;
self.otrack_kind = None;
}
Task::none()
}
@ -3997,23 +3999,23 @@ impl OpenCADStudio {
}
Text::EmptyOrUnsupported => {
self.command_line.push_error(
crate::tr!("clipboard-no-supported-content").as_ref(),
crate::tr!("clipboard", "no-supported-content").as_ref(),
);
Task::none()
}
Text::Unavailable => {
self.command_line
.push_error(crate::tr!("clipboard-unavailable").as_ref());
.push_error(crate::tr!("clipboard", "unavailable").as_ref());
Task::none()
}
Text::Occupied => {
self.command_line
.push_error(crate::tr!("clipboard-occupied").as_ref());
.push_error(crate::tr!("clipboard", "occupied").as_ref());
Task::none()
}
Text::ConversionFailed => {
self.command_line
.push_error(crate::tr!("clipboard-conversion-failed").as_ref());
.push_error(crate::tr!("clipboard", "conversion-failed").as_ref());
Task::none()
}
}

View file

@ -4,8 +4,9 @@
use super::util::*;
use super::{format_size, VIEWCUBE_HIT_SIZE};
use crate::app::helpers::{
axis_lock_apply, axis_lock_capture, drafting_axes, drafting_constrain, parse_coord, polar_constrain_near,
ucs_rotate_vec, ucs_to_wcs, ucs_z_axis, CoordKind,
axis_lock_apply, axis_lock_capture, drafting_axes, drafting_constrain, parse_coord,
polar_constrain_if_near, 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;
@ -117,6 +118,45 @@ fn over_ucs_icon(p: Point, h: &crate::ui::overlay::UcsIconHit) -> bool {
}
impl OpenCADStudio {
fn active_construction_ray(
&self,
tab: usize,
cursor: glam::DVec3,
base: glam::DVec3,
view_rot: glam::Mat4,
eye: glam::DVec3,
bounds: iced::Rectangle,
) -> Option<(glam::DVec3, glam::DVec3)> {
let ucs = self.tabs[tab].ucs_xform();
let mut target = if self.ortho_mode {
drafting_constrain(
cursor,
base,
&ucs,
self.isometric_drafting,
self.iso_plane,
self.snap_angle_deg,
)
} else if self.polar_mode {
polar_constrain_if_near(
cursor,
base,
self.polar_increment_deg,
view_rot,
eye,
bounds,
self.snapper.osnap_radius_px,
&ucs,
)?
} else {
return None;
};
if self.tabs[tab].active_ucs.is_none() {
target.z = base.z;
}
Some((base, target))
}
/// Write PDSIZE from the dialog buffer with the current relative/absolute
/// sign. A relative size is stored negative; absolute positive. Switching to
/// absolute with an empty/zero size seeds a positive value from the current
@ -1034,60 +1074,10 @@ impl OpenCADStudio {
// snapping must drop its foot from this point, including when a
// hot-grip set is moved by the same drag vector.
self.snapper.from_point = Some(grip.origin_world.as_vec3());
self.snapper.from_point = Some(grip.origin_world.as_vec3());
// Keep the updated drafting basis from main so grid/snap follows the
// current drafting axes, including isometric drafting and SNAPANG.
let (go, gr) = self.drafting_grid_basis(i);
let base = grip.origin_world;
let ucs_xf = self.tabs[i].ucs_xform();
let mut construction_cursor = if self.ortho_mode {
Some(drafting_constrain(
raw,
base,
&ucs_xf,
self.isometric_drafting,
self.iso_plane,
self.snap_angle_deg,
))
} else if self.polar_mode {
let pt = polar_constrain_near(
raw,
base,
self.polar_increment_deg,
view_rot,
eye,
bounds,
self.snapper.osnap_radius_px,
&ucs_xf,
);
// POLAR being enabled is not enough: the cursor must actually
// be engaged on one of the polar tracking rays.
let delta = pt - base;
let step = (self.polar_increment_deg as f64).to_radians();
let angle = delta.y.atan2(delta.x);
let polar_engaged = step > 1e-9
&& (((angle / step).round() * step - angle).abs() < 1e-3)
&& (delta.x * delta.x + delta.y * delta.y).sqrt() > 1e-9;
if polar_engaged {
Some(pt)
} else {
None
}
} else {
None
};
if let Some(pt) = construction_cursor.as_mut() {
if self.tabs[i].active_ucs.is_none() {
pt.z = base.z;
}
}
let construction_ray =
self.active_construction_ray(i, raw, base, view_rot, eye, bounds);
// `raw` is already model space (viewport camera or paper→model),
// and the wires are model space, so the snap result is model.
let snap_hit = self.snapper.snap(
@ -1099,7 +1089,7 @@ impl OpenCADStudio {
bounds,
go,
gr,
construction_cursor,
construction_ray,
);
self.tabs[i].snap_result = snap_hit;
@ -1519,60 +1509,18 @@ impl OpenCADStudio {
// the downcast point here.
self.snapper.from_point = self.last_point.map(|p| p.as_vec3());
// Build the temporary construction direction before OSNAP.
// Intersection can then test the active rubber-band against
// existing geometry (#704).
let construction_cursor = if is_window_corner {
let construction_ray = if is_window_corner {
None
} else {
self.last_point.and_then(|base| {
let ucs_xf = self.tabs[i].ucs_xform();
let mut pt = if self.ortho_mode {
Some(drafting_constrain(
snap_cursor,
base,
&ucs_xf,
self.isometric_drafting,
self.iso_plane,
self.snap_angle_deg,
))
} else if self.polar_mode {
let pt = polar_constrain_near(
snap_cursor,
base,
self.polar_increment_deg,
view_rot,
eye,
bounds,
self.snapper.osnap_radius_px,
&ucs_xf,
);
// POLAR being enabled is not enough: the cursor must actually
// be engaged on one of the polar tracking rays.
let delta = pt - base;
let step = (self.polar_increment_deg as f64).to_radians();
let angle = delta.y.atan2(delta.x);
let polar_engaged = step > 1e-9
&& (((angle / step).round() * step - angle).abs() < 1e-3)
&& (delta.x * delta.x + delta.y * delta.y).sqrt() > 1e-9;
if polar_engaged {
Some(pt)
} else {
None
}
} else {
None
}?;
if self.tabs[i].active_ucs.is_none() {
pt.z = base.z;
}
Some(pt)
self.active_construction_ray(
i,
snap_cursor,
base,
view_rot,
eye,
bounds,
)
})
};
@ -1585,7 +1533,7 @@ impl OpenCADStudio {
bounds,
go,
gr,
construction_cursor,
construction_ray,
)
};
@ -2907,57 +2855,18 @@ impl OpenCADStudio {
let (go, gr) = self.drafting_grid_basis(i);
self.snapper.from_point = self.last_point.map(|p| p.as_vec3());
let construction_cursor = if is_window_corner {
let construction_ray = if is_window_corner {
None
} else {
self.last_point.and_then(|base| {
let ucs_xf = self.tabs[i].ucs_xform();
let mut pt = if self.ortho_mode {
Some(drafting_constrain(
self.active_construction_ray(
i,
snap_cursor,
base,
&ucs_xf,
self.isometric_drafting,
self.iso_plane,
self.snap_angle_deg,
))
} else if self.polar_mode {
let pt = polar_constrain_near(
snap_cursor,
base,
self.polar_increment_deg,
view_rot,
eye,
bounds,
self.snapper.osnap_radius_px,
&ucs_xf,
);
// POLAR being enabled is not enough: the cursor must actually
// be engaged on one of the polar tracking rays.
let delta = pt - base;
let step = (self.polar_increment_deg as f64).to_radians();
let angle = delta.y.atan2(delta.x);
let polar_engaged = step > 1e-9
&& (((angle / step).round() * step - angle).abs() < 1e-3)
&& (delta.x * delta.x + delta.y * delta.y).sqrt() > 1e-9;
if polar_engaged {
Some(pt)
} else {
None
}
} else {
None
}?;
if self.tabs[i].active_ucs.is_none() {
pt.z = base.z;
}
Some(pt)
view_rot,
eye,
bounds,
)
})
};
@ -2970,7 +2879,7 @@ impl OpenCADStudio {
bounds,
go,
gr,
construction_cursor,
construction_ray,
)
};
// Snap runs in model space; the result is already model.

View file

@ -449,7 +449,7 @@ pub(super) fn viewport_controls<'a>(
crate::ui::icons::SPLIT_V,
false,
Message::SplitModelViewport(false),
crate::tr!("viewport-split-vertical"),
crate::tr!("viewport", "split-vertical"),
"VPORTS 2V",
))
.push(sep())
@ -457,7 +457,7 @@ pub(super) fn viewport_controls<'a>(
crate::ui::icons::SPLIT_H,
false,
Message::SplitModelViewport(true),
crate::tr!("viewport-split-horizontal"),
crate::tr!("viewport", "split-horizontal"),
"VPORTS 2H",
));
// Drag handle + close: only meaningful with more than one model tile.
@ -478,7 +478,7 @@ pub(super) fn viewport_controls<'a>(
)
.interaction(iced::mouse::Interaction::Grab)
.on_press(Message::PaneMoveStart);
let drag = viewport_tooltip(drag, crate::tr!("viewport-move"), "VPORTS");
let drag = viewport_tooltip(drag, crate::tr!("viewport", "move"), "VPORTS");
bar = bar
.push(sep())
.push(drag)
@ -486,7 +486,7 @@ pub(super) fn viewport_controls<'a>(
.push(danger_btn(
crate::ui::icons::CLOSE,
Message::CloseModelViewport,
crate::tr!("viewport-close"),
crate::tr!("viewport", "close"),
"VPORTS SINGLE",
));
}

View file

@ -812,10 +812,10 @@ impl OpenCADStudio {
let tracking_hint = match self.otrack_kind {
Some(crate::snap::TrackingKind::Perpendicular) => {
Some("Perpendicular".to_string())
Some(crate::tr!("common", "perpendicular"))
}
Some(crate::snap::TrackingKind::Extension) => {
Some("Extension".to_string())
Some(crate::tr!("common", "extension"))
}
_ => None,
};
@ -2636,7 +2636,7 @@ fn start_page_content<'a>(
button(
row![
crate::ui::icons::themed_danger_text(crate::ui::icons::HEART, 14.0),
text(crate::tr!("start-donate")).size(14),
text(crate::tr!("start", "donate")).size(14),
]
.spacing(5)
.align_y(iced::Center),
@ -2650,8 +2650,8 @@ fn start_page_content<'a>(
};
let primary_row = WrapFlow::new(vec![
outline_btn(crate::tr!("start-new-drawing"), Message::TabNew).into(),
outline_btn(crate::tr!("start-open-file"), Message::OpenFile).into(),
outline_btn(crate::tr!("start", "new-drawing"), Message::TabNew).into(),
outline_btn(crate::tr!("start", "open-file"), Message::OpenFile).into(),
donate_btn.into(),
])
.spacing_x(12.0)
@ -2661,16 +2661,16 @@ fn start_page_content<'a>(
#[cfg_attr(target_arch = "wasm32", allow(unused_mut))]
let mut secondary_items: Vec<Element<'a, Message>> = vec![
outline_btn(
crate::tr!("start-send-feedback"),
crate::tr!("start", "send-feedback"),
Message::RibbonToolClick {
tool_id: "REPORT".to_string(),
event: crate::modules::ModuleEvent::Command("REPORT".to_string()),
},
)
.into(),
outline_btn(crate::tr!("action-options"), Message::OptionsOpen).into(),
outline_btn(crate::tr!("action", "options"), Message::OptionsOpen).into(),
];
secondary_items.push(outline_btn(crate::tr!("action-plugins"), Message::PluginManagerOpen).into());
secondary_items.push(outline_btn(crate::tr!("action", "plugins"), Message::PluginManagerOpen).into());
// The web build is already in the browser, so only the desktop offers a
// link to the web version.
#[cfg(not(target_arch = "wasm32"))]
@ -2693,7 +2693,7 @@ fn start_page_content<'a>(
.report_natural_width(action_width_out.clone());
let sponsors = column![
text(crate::tr!("start-sponsors")).size(15),
text(crate::tr!("start", "sponsors")).size(15),
mouse_area(
container(
iced::widget::svg(iced::widget::svg::Handle::from_memory(include_bytes!(
@ -2795,7 +2795,7 @@ fn start_page_content<'a>(
// whole thumbnail remains visible when that width changes.
let thumb_h =
(panel_w - VIDEO_PANEL_PADDING * 2.0 - VIDEO_SCROLL_GUTTER) * 9.0 / 16.0;
let mut list = column![text(crate::tr!("start-tutorials")).size(15)]
let mut list = column![text(crate::tr!("start", "tutorials")).size(15)]
.spacing(10)
.width(Fill)
// Keep the scrollbar off the thumbnails.
@ -2835,14 +2835,14 @@ fn start_page_content<'a>(
}
if videos.is_empty() {
let note = if videos_loading {
crate::tr!("start-loading-videos")
crate::tr!("start", "loading-videos")
} else {
crate::tr!("start-videos-online")
crate::tr!("start", "videos-online")
};
list = list.push(text(note).size(12).style(start_muted_style));
}
let playlist_btn = mouse_area(
container(text(crate::tr!("start-open-playlist")).size(12))
container(text(crate::tr!("start", "open-playlist")).size(12))
.padding([6, 10])
.width(Fill)
.center_x(Fill)
@ -2895,7 +2895,7 @@ fn start_page_content<'a>(
// web builds read the CI-generated snapshot. Both sources mark pinned
// discussions and sort them before the rest of the list.
let discussions_panel: Element<'a, Message> = {
let mut list = column![text(crate::tr!("start-discussions")).size(15)]
let mut list = column![text(crate::tr!("start", "discussions")).size(15)]
.spacing(8)
.width(Fill);
for discussion in discussions {
@ -2908,7 +2908,7 @@ fn start_page_content<'a>(
.align_y(iced::Center);
if discussion.pinned {
meta = meta.push(
text(crate::tr!("start-pinned"))
text(crate::tr!("start", "pinned"))
.size(10)
.style(start_primary_style),
);
@ -2951,14 +2951,14 @@ fn start_page_content<'a>(
}
if discussions.is_empty() {
let note = if discussions_loading {
crate::tr!("start-loading-discussions")
crate::tr!("start", "loading-discussions")
} else {
crate::tr!("start-discussions-online")
crate::tr!("start", "discussions-online")
};
list = list.push(text(note).size(12).style(start_muted_style));
}
let open_btn = mouse_area(
container(text(crate::tr!("start-open-discussions")).size(12))
container(text(crate::tr!("start", "open-discussions")).size(12))
.padding([6, 10])
.width(Fill)
.center_x(Fill)
@ -3015,7 +3015,7 @@ fn start_page_content<'a>(
// shows, so the rail always invites support.
let supporters: Element<'a, Message> = {
let mut list = column![
text(crate::tr!("start-supporters")).size(15),
text(crate::tr!("start", "supporters")).size(15),
Space::new().height(iced::Length::Fixed(12.0)),
]
.spacing(6)
@ -3040,7 +3040,7 @@ fn start_page_content<'a>(
container(
iced::widget::row![
crate::ui::icons::themed_danger_text(crate::ui::icons::HEART, 13.0),
text(crate::tr!("start-support-on-patreon")).size(12),
text(crate::tr!("start", "support-on-patreon")).size(12),
]
.spacing(6)
.align_y(iced::Center),
@ -3158,11 +3158,11 @@ fn start_page_content<'a>(
})
};
let tab_bar = Row::with_children(vec![
tab_btn(crate::tr!("start-recent-files"), super::StartSection::Recent).into(),
tab_btn(crate::tr!("start-videos"), super::StartSection::Videos).into(),
tab_btn(crate::tr!("start-welcome"), super::StartSection::Welcome).into(),
tab_btn(crate::tr!("start-discussions"), super::StartSection::Discussions).into(),
tab_btn(crate::tr!("start-supporters"), super::StartSection::Supporters).into(),
tab_btn(crate::tr!("start", "recent-files"), super::StartSection::Recent).into(),
tab_btn(crate::tr!("start", "videos"), super::StartSection::Videos).into(),
tab_btn(crate::tr!("start", "welcome"), super::StartSection::Welcome).into(),
tab_btn(crate::tr!("start", "discussions"), super::StartSection::Discussions).into(),
tab_btn(crate::tr!("start", "supporters"), super::StartSection::Supporters).into(),
])
.spacing(6.0)
.align_y(iced::Center)
@ -3238,11 +3238,11 @@ pub(super) fn recent_files_panel<'a>(
) -> Element<'a, Message> {
// Title mirrors the Supporters rail: size 15 in the bright text colour,
// followed by a 12px gap before the content.
let title = text(crate::tr!("start-recent-documents")).size(15);
let title = text(crate::tr!("start", "recent-documents")).size(15);
let body: Element<'a, Message> = if recents.is_empty() {
container(
text(crate::tr!("start-no-recent-files"))
text(crate::tr!("start", "no-recent-files"))
.size(12)
.style(start_muted_style)
)
@ -3270,7 +3270,7 @@ pub(super) fn recent_files_panel<'a>(
// directory line.
#[cfg(target_arch = "wasm32")]
let dir = if dir.is_empty() {
crate::tr!("start-browser-storage")
crate::tr!("start", "browser-storage")
} else {
dir
};
@ -3381,7 +3381,7 @@ pub(super) fn recent_files_panel<'a>(
.padding([2, 6])
.width(iced::Length::Fixed(46.0));
let limit_row = row![
text(crate::tr!("start-keep-recent-files")).size(11).style(start_muted_style).width(Fill),
text(crate::tr!("start", "keep-recent-files")).size(11).style(start_muted_style).width(Fill),
button(crate::ui::icons::themed(crate::ui::icons::MINUS, 11.0))
.on_press(Message::SetRecentLimit(shown.saturating_sub(STEP)))
.padding([3, 6])

View file

@ -10,43 +10,43 @@ impl OpenCADStudio {
pub(super) fn modal_title(&self) -> String {
use super::super::ModalKind as K;
match self.active_modal {
Some(K::About) => crate::tr!("modal-about"),
Some(K::Shortcuts) => crate::tr!("modal-keyboard-shortcuts"),
Some(K::Aliases) => crate::tr!("modal-command-aliases"),
Some(K::Options) => crate::tr!("action-options"),
Some(K::FindReplace) => crate::tr!("modal-find-replace"),
Some(K::PluginManager) => crate::tr!("modal-plugin-manager"),
Some(K::UpdateNotice) => crate::tr!("modal-update-available"),
Some(K::Layers) => crate::tr!("modal-layer-manager"),
Some(K::LayerStateManager) => crate::tr!("modal-layer-state-manager"),
Some(K::About) => crate::tr!("modal", "about"),
Some(K::Shortcuts) => crate::tr!("modal", "keyboard-shortcuts"),
Some(K::Aliases) => crate::tr!("modal", "command-aliases"),
Some(K::Options) => crate::tr!("action", "options"),
Some(K::FindReplace) => crate::tr!("modal", "find-replace"),
Some(K::PluginManager) => crate::tr!("modal", "plugin-manager"),
Some(K::UpdateNotice) => crate::tr!("modal", "update-available"),
Some(K::Layers) => crate::tr!("modal", "layer-manager"),
Some(K::LayerStateManager) => crate::tr!("modal", "layer-state-manager"),
Some(K::LayerTranslator) => crate::t!("Layer Translator").into_owned(),
Some(K::DrawingUnits) => crate::t!("Drawing Units").into_owned(),
Some(K::DraftingSettings) => crate::t!("Drafting Settings").into_owned(),
Some(K::LayerStateEditor) => crate::tr!("modal-edit-layer-state"),
Some(K::Plot) => crate::tr!("modal-plot"),
Some(K::LayerStateEditor) => crate::tr!("modal", "edit-layer-state"),
Some(K::Plot) => crate::tr!("modal", "plot"),
Some(K::PrintAll) => t!("Print All").into_owned(),
Some(K::LayoutManager) => crate::tr!("modal-layout-manager"),
Some(K::ScaleManager) => crate::tr!("modal-scale-manager"),
Some(K::AnnoObjectScale) => crate::tr!("modal-annotation-object-scale"),
Some(K::Plotstyle) => crate::tr!("modal-plot-style-editor"),
Some(K::TextStyle) => crate::tr!("modal-text-style-manager"),
Some(K::MlStyle) => crate::tr!("modal-multiline-style-manager"),
Some(K::TableStyle) => crate::tr!("modal-table-style-manager"),
Some(K::MLeaderStyle) => crate::tr!("modal-multileader-style-manager"),
Some(K::DimStyle) => crate::tr!("modal-dimension-style-manager"),
Some(K::AssocPrompt) => crate::tr!("modal-default-application"),
Some(K::AecDropWarning) => crate::tr!("modal-save-warning"),
Some(K::LayoutManager) => crate::tr!("modal", "layout-manager"),
Some(K::ScaleManager) => crate::tr!("modal", "scale-manager"),
Some(K::AnnoObjectScale) => crate::tr!("modal", "annotation-object-scale"),
Some(K::Plotstyle) => crate::tr!("modal", "plot-style-editor"),
Some(K::TextStyle) => crate::tr!("modal", "text-style-manager"),
Some(K::MlStyle) => crate::tr!("modal", "multiline-style-manager"),
Some(K::TableStyle) => crate::tr!("modal", "table-style-manager"),
Some(K::MLeaderStyle) => crate::tr!("modal", "multileader-style-manager"),
Some(K::DimStyle) => crate::tr!("modal", "dimension-style-manager"),
Some(K::AssocPrompt) => crate::tr!("modal", "default-application"),
Some(K::AecDropWarning) => crate::tr!("modal", "save-warning"),
#[cfg(not(target_arch = "wasm32"))]
Some(K::FileInUse) => crate::tr!("modal-unable-save"),
Some(K::FileInUse) => crate::tr!("modal", "unable-save"),
#[cfg(not(target_arch = "wasm32"))]
Some(K::ExternalChange) => crate::tr!("modal-drawing-changed"),
Some(K::LayerDeleteWarning) => crate::tr!("modal-delete-layer"),
Some(K::Unsaved) => crate::tr!("modal-unsaved-changes"),
Some(K::PointStyle) => crate::tr!("modal-point-style"),
Some(K::AttributeEditor) => crate::tr!("modal-attribute-editor"),
Some(K::SaveDialog) => crate::tr!("modal-save-drawing-as"),
Some(K::Recovery) => crate::tr!("modal-recovery-report"),
Some(K::RecoveryPrompt) => crate::tr!("modal-recovery-prompt"),
Some(K::ExternalChange) => crate::tr!("modal", "drawing-changed"),
Some(K::LayerDeleteWarning) => crate::tr!("modal", "delete-layer"),
Some(K::Unsaved) => crate::tr!("modal", "unsaved-changes"),
Some(K::PointStyle) => crate::tr!("modal", "point-style"),
Some(K::AttributeEditor) => crate::tr!("modal", "attribute-editor"),
Some(K::SaveDialog) => crate::tr!("modal", "save-drawing-as"),
Some(K::Recovery) => crate::tr!("modal", "recovery-report"),
Some(K::RecoveryPrompt) => crate::tr!("modal", "recovery-prompt"),
None => String::new(),
}
}
@ -1528,7 +1528,7 @@ fn unsaved_changes_dialog_window(
column![
text(name.to_owned()).size(14),
iced::widget::Space::new().height(4),
text(crate::tr!("modal-unsaved-save-prompt"))
text(crate::tr!("modal", "unsaved-save-prompt"))
.size(12)
.style(dialog_muted_text_style),
iced::widget::Space::new().height(20),