Files
OneCAD-Draft/src/app/view/modal.rs
T
PanosandClaude Opus 5 9fe2e4fa58 fix(view): hold ZOOMFACTOR to its own range, finish the exponential zoom
The exponential camera step is the right call. `0.9 ^ delta` is strictly
positive for every real delta, in and out cancel exactly where the
subtraction lost a percent of the view on every round trip, and the file's
own header has promised an "exponential feel" since it was written. Three
surfaces were left behind it, and the wider range exposed a fourth thing.

The range of the system variable. 3 to 100 is the range ZOOMFACTOR has, and
the User Preferences page was built against it; `SETVAR ZOOMFACTOR` accepts
that and nothing else again. A faster wheel is the Options window's business
instead: the slider covers the variable's own range, the field beside it
reaches 500, and a read-out says what a notch actually does ("10.0% a
notch") so the number being set is readable rather than implied. A value
above 100 therefore lives only in this application's own configuration file,
where it is nobody else's to read, and a bare `SETVAR ZOOMFACTOR` still
reports it. The bounds and the clamp live in one place now
(`settings::clamp_zoom_factor`): four literal copies of `3, 100` are what
let a surface be missed in the first place.

A floating viewport in MSPACE. It zooms its own view through
`zoom_active_viewport`, which kept subtracting: the factor reached zero at
6.67 steps, so from ZOOMFACTOR 360 up every notch inside a viewport bottomed
out on the clamp — a tenfold jump a notch, and the top third of the slider
doing nothing at all. Exponential there too, from a base of 0.85 so the
default notch still moves a viewport the 15% it always has. Each surface
keeps its own long-standing feel rather than inheriting the other's.

The trackpad pinch. `pinch_zoom_steps` was calibrated against the
subtraction (`1 - step / 10 = 1 / (1 + m)`) so that a pinch of m leaves the
view exactly 1 + m times closer — the property docs/trackpad.md documents
and the reason the gesture tracks the fingers at all. Inverting the zoom law
by hand is what let the two drift apart, so the step is asked of the camera
now (`Camera::zoom_steps_for_ratio`), which cannot drift by construction.
`pinch_is_one_to_one` recomputed the old formula rather than the camera's,
so it kept passing through all of this; it zooms a real camera and measures
the distance it lands at instead.

Also: `zoom` had a floor and no ceiling, and zooming out multiplies. At
ZOOMFACTOR 500 a notch out is x2.4, so about ninety notches — a couple of
seconds of scrolling — ran the distance past what an f32 far plane
(`distance * 1000`) can hold, and the viewport went black on an infinite
projection. A degenerate pinch magnification of -1 or beyond reached the
same place through the other side. Both are bounded now.

Also: two doc comments quoted the linear law (`zoom_view_at_cursor` and the
one on `pinch_zoom_steps`), and docs/trackpad.md stated the pinch identity
and claimed the tests covered it against the camera. All updated, with what
a notch does and where each surface's range comes from written down.

Tests, each confirmed against the behaviour it describes: the zoom law at
every factor the application can hold (positive, monotonic in the setting,
in and out cancelling), the ceiling under a long scroll out in both
projections, the published inverse and what it does with a nonsense ratio,
the viewport factor and a real viewport zoomed three notches at the top of
the range, the SETVAR range including a value the Options field set, the
Options field (above 100, above the ceiling, cleared, not a number), its
agreement with the slider, Close putting both the value and the field back,
and the read-out's percentages. 1882 lib tests pass.

The two new Options strings are not in the locale catalog yet, so they
render in English until it is regenerated.

Co-Authored-By: Claude Opus 5 <[email protected]>
2026-10-02 10:51:26 +03:00

2355 lines
102 KiB
Rust

use super::super::{Message, OpenCADStudio};
use crate::t;
use iced::widget::{button, column, container, row, text, Space};
use iced::{Background, Element, Fill, Fit, Theme};
use std::borrow::Cow;
impl OpenCADStudio {
/// Title shown in the active modal's title bar. Keep in sync with the
/// [`Self::modal_content`] dispatch.
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::NamedParameters) => crate::t!("Named Parameters").into_owned(),
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::DonationPrompt) => crate::tr!("donation", "title"),
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::BlockDefinition) => crate::t!("Block Definition").into_owned(),
Some(K::PdfAttach) => match self.pdf_attach.as_ref().map(|s| s.kind) {
Some(codec::entities::UnderlayType::Dwf) => crate::t!("Attach DWF Underlay").into_owned(),
Some(codec::entities::UnderlayType::Dgn) => crate::t!("Attach DGN Underlay").into_owned(),
_ => crate::t!("Attach PDF Underlay").into_owned(),
},
Some(K::PointCloudAttach) => crate::t!("Attach Point Cloud").into_owned(),
Some(K::PointCloudColorMap) => crate::t!("Point Cloud Color Map").into_owned(),
Some(K::PcSection) => crate::t!("Extract Section Lines from Point Cloud").into_owned(),
Some(K::UnderlayLayers) => crate::t!("Underlay Layers").into_owned(),
Some(K::PdfImportSettings) => crate::t!("PDF Import Settings").into_owned(),
Some(K::PdfImportFile) => crate::t!("Import PDF").into_owned(),
Some(K::XrefAttach) => crate::t!("Attach External Reference").into_owned(),
Some(K::WriteBlock) => crate::t!("Write Block").into_owned(),
Some(K::GeometricTolerance) => crate::t!("Geometric Tolerance").into_owned(),
Some(K::AttDef) => crate::t!("Attribute Definition").into_owned(),
Some(K::AttDefEdit) => crate::t!("Edit Attribute Definition").into_owned(),
Some(K::DraftingSettings) => crate::t!("Drafting Settings").into_owned(),
Some(K::AutoConstrainSettings) => crate::t!("Constraint Settings").into_owned(),
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::Hyperlink) => crate::t!("Hyperlink").into_owned(),
Some(K::InsertTable) => crate::t!("Insert Table").into_owned(),
Some(K::DataLinkManager) => crate::t!("Data Link Manager").into_owned(),
Some(K::DataExtraction) => crate::t!("Data Extraction Wizard").into_owned(),
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"),
#[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::MissingFonts) => crate::t!("Missing fonts").into_owned(),
Some(K::RecoveryPrompt) => crate::tr!("modal", "recovery-prompt"),
Some(K::GpuWarning) => crate::tr!("gpu", "title"),
Some(K::XrefHelp) => crate::t!("Reference Manager Help").into_owned(),
None => String::new(),
}
}
pub(super) fn plot_modal_content<'s>(
&'s self,
extra: iced::Vector,
) -> Element<'s, Message> {
sized_flow(
extra,
940,
690,
|flow| {
crate::ui::window::plot::view_window(
&self.plot_dialog,
self.print_all_options,
flow,
)
},
)
}
/// Build the currently-open modal dialog's content (Plan B), or `None`.
/// Iced 0.15 measures the content first, so dialogs start at their natural
/// size and overflowing regions become scrollable where a cap is supplied.
pub(super) fn modal_content<'s>(&'s self) -> Option<Element<'s, Message>> {
let ex = self.modal_resize;
Some(match self.active_modal? {
super::super::ModalKind::About => {
automatic_flow(ex, crate::ui::window::about::view_window)
}
super::super::ModalKind::XrefHelp => {
automatic_flow(ex, crate::ui::window::xref_help::view_window)
}
super::super::ModalKind::Shortcuts => {
// Keys claimed by two rows — the cells turn red and a
// persistent warning names the command already using each
// key; the last row wins in the binding map on Apply.
let mut key_rows: std::collections::BTreeMap<String, Vec<&(String, String)>> =
std::collections::BTreeMap::new();
for row in &self.shortcut_editor_rows {
let key = crate::app::shortcuts::normalize_key(&row.0);
if !key.is_empty() {
key_rows.entry(key).or_default().push(row);
}
}
let duplicate_keys: Vec<String> = key_rows
.iter()
.filter(|(_, rows)| rows.len() > 1)
.map(|(key, _)| key.clone())
.collect();
let duplicate_conflicts: Vec<(String, String)> = key_rows
.iter()
.filter(|(_, rows)| rows.len() > 1)
.map(|(key, rows)| {
// Name the established binding: the first row with
// this key that actually has a command.
let command = rows
.iter()
.find(|(_, command)| !command.is_empty())
.map(|(_, command)| command.clone())
.unwrap_or_default();
(key.clone(), command)
})
.collect();
let duplicate_set: rustc_hash::FxHashSet<String> =
duplicate_keys.into_iter().collect();
// Commands the dispatcher can't run: not a registered
// command, plugin command, alias, or input action.
let valid: rustc_hash::FxHashSet<String> =
crate::command::all_registered_command_names()
.into_iter()
.map(str::to_uppercase)
.chain(
self.command_line
.dynamic_commands
.iter()
.map(|cmd| cmd.to_uppercase()),
)
.chain(self.command_aliases.keys().cloned())
.chain(
crate::app::shortcuts::INPUT_ACTIONS
.iter()
.map(|action| action.to_string()),
)
.collect();
let unknown_commands: Vec<String> = self
.shortcut_editor_rows
.iter()
.filter_map(|(_, command)| {
let command = command.trim();
(!command.is_empty() && !valid.contains(command))
.then(|| command.to_string())
})
.collect();
let unknown_set: rustc_hash::FxHashSet<String> =
unknown_commands.into_iter().collect();
sized_flow(
ex,
720,
520,
|flow| {
crate::ui::window::shortcuts::view_window(
&self.shortcut_editor_rows,
self.shortcut_capture_row,
self.shortcut_pending_add,
self.shortcut_reset_confirm,
&duplicate_set,
&duplicate_conflicts,
&unknown_set,
self.shortcut_close_confirm,
flow,
)
},
)
}
super::super::ModalKind::Aliases => {
// Aliases claimed by two rows — the cells turn red and a
// persistent warning names the command already using each
// alias; the last row wins in the alias map on Apply.
let mut alias_rows: std::collections::BTreeMap<String, Vec<&(String, String)>> =
std::collections::BTreeMap::new();
for row in &self.alias_editor_rows {
let alias = row.0.trim().to_uppercase();
if !alias.is_empty() {
alias_rows.entry(alias).or_default().push(row);
}
}
let duplicate_aliases: Vec<String> = alias_rows
.iter()
.filter(|(_, rows)| rows.len() > 1)
.map(|(alias, _)| alias.clone())
.collect();
let duplicate_conflicts: Vec<(String, String)> = alias_rows
.iter()
.filter(|(_, rows)| rows.len() > 1)
.map(|(alias, rows)| {
let command = rows
.iter()
.find(|(_, command)| !command.is_empty())
.map(|(_, command)| command.clone())
.unwrap_or_default();
(alias.clone(), command)
})
.collect();
let duplicate_set: rustc_hash::FxHashSet<String> =
duplicate_aliases.into_iter().collect();
// Commands the dispatcher can't run: not a registered
// command, plugin command, or input action.
let valid: rustc_hash::FxHashSet<String> =
crate::command::all_registered_command_names()
.into_iter()
.map(str::to_uppercase)
.chain(
self.command_line
.dynamic_commands
.iter()
.map(|cmd| cmd.to_uppercase()),
)
.chain(
crate::app::shortcuts::INPUT_ACTIONS
.iter()
.map(|action| action.to_string()),
)
.collect();
let unknown_commands: Vec<String> = self
.alias_editor_rows
.iter()
.filter_map(|(_, command)| {
let command = command.trim();
(!command.is_empty() && !valid.contains(command))
.then(|| command.to_string())
})
.collect();
let unknown_set: rustc_hash::FxHashSet<String> =
unknown_commands.into_iter().collect();
sized_flow(
ex,
480,
520,
|flow| {
crate::ui::window::alias_editor::view_window(
&self.alias_editor_rows,
self.alias_pending_add,
self.alias_reset_confirm,
&duplicate_set,
&duplicate_conflicts,
&unknown_set,
self.alias_close_confirm,
flow,
)
},
)
}
super::super::ModalKind::NamedParameters => {
let scene = &self.tabs[self.active_tab].scene;
sized_flow(
ex,
820,
520,
|flow| {
crate::ui::window::named_parameters::view_window(
&self.named_parameter_editor_rows,
scene,
flow,
)
},
)
}
super::super::ModalKind::Options => {
let dirty = self.options_dirty();
let close_confirm = self.options_close_confirm;
sized_flow(
ex,
880,
620,
|flow| {
crate::ui::window::options::view_window(
&self.default_save_format,
self.file_assoc_enabled,
self.show_constraint_values,
&self.ui_theme,
&self.theme_color_inputs,
self.language,
self.options_tab,
self.cursor_size,
crate::ui::window::options::SelectionPrefs {
pick_box: self.pick_box,
pick_add: self.pick_add,
pick_drag_rect: self.pick_drag_rect,
grip_object_limit: self.grip_object_limit,
selection_cycling: self.selection_cycling,
},
crate::ui::window::options::AppPrefs {
savetime_min: self.savetime_min,
backup_on_save: self.backup_on_save,
page_setup_on_new_layout: self.plot_dialog.page_setup_on_new_layout,
textfill: crate::scene::text::sdf_atlas::textfill(),
cliprompt_lines: self.cliprompt_lines,
commandline_fade_ms: self.commandline_fade_ms,
zoom_wheel_reversed: self.zoom_wheel_reversed,
zoom_factor: self.zoom_factor,
texteditmode: self.texteditmode,
dimension_continue_mode: self.dimension_continue_mode,
qdim_snap_priority: self.quick_dimension_snap_priority,
annotation_auto_scale: self.annotation_auto_scale,
polar_increment_deg: self.polar_increment_deg,
show_viewcube: self.show_viewcube,
show_ucs_icon: self.show_ucs_icon,
ucs_icon_at_origin: self.ucs_icon_at_origin,
right_click_mode: self.right_click_mode,
right_click_hold_ms: self.right_click_hold_ms,
},
crate::ui::window::options::spacemouse::view(
self.spacemouse_preferences, self.spacemouse.status(),
self.spacemouse_paused, self.spacemouse_details,
),
&self.snap_angle_input,
&self.zoom_factor_input,
{
let header = self
.tabs
.get(self.active_tab)
.map(|tab| &tab.scene.document.header);
crate::ui::window::options::DrawingPrefs {
available: header.is_some(),
isolines: header.map_or(4, |h| h.isolines),
display_silhouette: header
.is_some_and(|h| h.display_silhouette),
surface_u: header.map_or(6, |h| h.surface_u_density),
surface_v: header.map_or(6, |h| h.surface_v_density),
surface_type: header.map_or(6, |h| h.surface_type),
record_solid_history: header
.is_some_and(|h| h.record_solid_history),
show_solid_history: header
.map_or(1, |h| h.show_solid_history),
}
},
{
#[cfg(not(target_arch = "wasm32"))]
{
crate::ui::window::options::Folders {
config: crate::config::config_dir()
.map(|p| p.display().to_string()),
plot_styles: crate::io::plot_style::plot_styles_dir()
.ok()
.map(|p| p.display().to_string()),
plugins: crate::plugin::external::plugins_dir()
.map(|p| p.display().to_string()),
autosave: crate::config::config_dir()
.map(|_| std::env::temp_dir().display().to_string()),
}
}
#[cfg(target_arch = "wasm32")]
{
crate::ui::window::options::Folders::default()
}
},
self.double_click_block_refedit,
self.double_click_block_attedit,
self.cursor_type,
self.crosshair_color,
&self.crosshair_color_input,
self.lineweight_display_scale,
&self.model_space,
&self.model_bg_input,
&self.paper_bg_input,
&self.desk_bg_input,
self.bg_picker,
dirty,
close_confirm,
flow,
)
},
)
}
super::super::ModalKind::DraftingSettings => {
let state = self.drafting_settings_state.as_ref();
let dirty = self.drafting_settings_dirty();
let confirm = self.drafting_settings_close_confirm;
sized_flow(
ex,
780,
500,
|flow| {
if let Some(state) = state {
crate::ui::window::drafting_settings::view_window(
state,
dirty,
confirm,
flow,
)
} else {
iced::widget::Space::new().into()
}
},
)
}
super::super::ModalKind::AutoConstrainSettings => sized_flow(
ex,
620,
610,
|flow| {
crate::ui::window::auto_constrain_settings::view_window(
&self.auto_constrain_settings,
self.auto_constrain_selected_row,
&self.auto_constrain_distance_input,
&self.auto_constrain_angle_input,
flow,
)
},
),
super::super::ModalKind::FindReplace => automatic_flow(ex, |flow| {
crate::ui::window::find_replace::view_window(
&self.find_replace.search,
&self.find_replace.replacement,
&self.find_replace.status,
flow,
)
}),
super::super::ModalKind::PluginManager => {
#[cfg(not(target_arch = "wasm32"))]
{
sized_flow(
ex,
940,
600,
|flow| {
crate::ui::window::plugin_manager::view_window(
&self.disabled_plugins,
&self.external_plugins,
&self.loaded_plugin_ids,
&self.plugin_load_errors,
crate::ui::window::plugin_manager::MarketView {
registry: &self.plugin_registry,
registry_loading: self.plugin_registry_loading,
registry_error: self.plugin_registry_error.as_deref(),
registry_error_details_open: self
.plugin_registry_error_details_open,
input: &self.plugin_repo_input,
search: &self.plugin_search_input,
repos: &self.plugin_repos,
release_tags: &self.repo_release_tags,
selected_tag: &self.repo_selected_tag,
selected_repo: self.selected_plugin_repo.as_deref(),
readmes: &self.plugin_readmes,
readme_loading: &self.plugin_readme_loading,
status: &self.marketplace_status,
},
&self.active_theme,
flow,
)
},
)
}
#[cfg(target_arch = "wasm32")]
{
automatic_flow(ex, |flow| {
container(crate::ui::window::plugin_manager::view_web_notice())
.width(flow.width)
.height(flow.height)
.into()
})
}
}
super::super::ModalKind::UpdateNotice => {
let latest = self.update_notice_version.as_deref().unwrap_or("?");
let body = self.update_notice_body.as_deref().unwrap_or("");
sized_flow(
ex,
560,
460,
|flow| crate::ui::window::update_notice::view_window(latest, body, flow),
)
}
super::super::ModalKind::Layers => {
let tab = &self.tabs[self.active_tab];
sized_flow(
ex,
900,
360,
|flow| tab.layers.view_window(self.layer_name_col_w, flow),
)
}
super::super::ModalKind::LayerTranslator => {
use crate::modules::draw::layers::laytrans;
let i = self.active_tab;
let current = self.tabs[i].active_layer.clone();
let sources = laytrans::source_layers(&self.tabs[i].scene, &current);
let state = self.layer_translator.as_ref()?;
sized_flow(ex, 760, 460, |flow| {
crate::ui::window::layer_translator::view_window(
state,
sources.clone(),
flow,
)
})
}
super::super::ModalKind::DrawingUnits => {
let state = self.drawing_units.as_ref()?;
sized_flow(ex, 560, 420, |flow| {
crate::ui::window::drawing_units::view_window(state, flow)
})
}
super::super::ModalKind::BlockDefinition => {
let state = self.block_definition.as_ref()?;
sized_flow(ex, 580, 390, |flow| {
crate::ui::window::block_definition::view_window(state, flow)
})
}
super::super::ModalKind::PdfAttach => {
let state = self.pdf_attach.as_ref()?;
sized_flow(ex, 880, 540, |flow| {
crate::ui::window::pdf_dialogs::view_attach(state, flow)
})
}
super::super::ModalKind::PointCloudAttach => {
let state = self.point_cloud_attach.as_ref()?;
sized_flow(ex, 880, 560, |flow| {
crate::ui::window::pdf_dialogs::view_point_cloud_attach(state, flow)
})
}
super::super::ModalKind::PointCloudColorMap => {
let state = self.point_cloud_color_map.as_ref()?;
sized_flow(ex, 760, 560, |flow| {
crate::ui::window::pdf_dialogs::view_point_cloud_color_map(state, flow)
})
}
super::super::ModalKind::PcSection => {
let state = self.pc_section.as_ref()?;
sized_flow(ex, 760, 470, |flow| crate::ui::window::pdf_dialogs::view_pc_section(state, flow))
}
super::super::ModalKind::UnderlayLayers => {
let state = self.underlay_layers.as_ref()?;
sized_flow(ex, 460, 520, |flow| {
crate::ui::window::pdf_dialogs::view_layers(state, flow)
})
}
super::super::ModalKind::PdfImportSettings => {
let settings = self.pdf_import_settings.as_ref()?;
sized_flow(ex, 640, 380, |flow| {
crate::ui::window::pdf_dialogs::view_import_settings(settings, flow)
})
}
super::super::ModalKind::PdfImportFile => {
let state = self.pdf_import_file.as_ref()?;
sized_flow(ex, 960, 560, |flow| {
crate::ui::window::pdf_dialogs::view_import_file(state, flow)
})
}
super::super::ModalKind::XrefAttach => {
let state = self.xref_attach.as_ref()?;
let height = if state.details { 520 } else { 450 };
sized_flow(ex, 740, height, |flow| {
crate::ui::window::xref_attach::view_window(state, flow)
})
}
super::super::ModalKind::WriteBlock => {
let state = self.wblock.as_ref()?;
sized_flow(ex, 440, 395, |flow| {
crate::ui::window::wblock::view_window(state, flow)
})
}
super::super::ModalKind::AttDef => {
let state = self.attdef_dialog.as_ref()?;
sized_flow(ex, 720, 520, |flow| crate::ui::window::attdef_dialog::view(state, flow))
}
super::super::ModalKind::AttDefEdit => {
let state = self.attdef_edit.as_ref()?;
sized_flow(ex, 460, 220, |flow| crate::ui::window::attdef_dialog::view_edit(state, flow))
}
super::super::ModalKind::GeometricTolerance => {
let state = self.geometric_tolerance.as_ref()?;
sized_flow(ex, 670, 530, |flow| {
crate::ui::window::geometric_tolerance::view_window(state, flow)
})
}
super::super::ModalKind::LayerStateManager => {
let states = self.tabs[self.active_tab].scene.document.layer_states();
sized_flow(
ex,
720,
420,
|flow| {
crate::ui::window::layer_state_manager::view_window(
states.clone(),
self.layer_state_selected.as_deref(),
&self.layer_state_name_buf,
&self.layer_state_description_buf,
&self.layer_state_filter,
flow,
)
},
)
}
super::super::ModalKind::LayerStateEditor => {
let tab = &self.tabs[self.active_tab];
if let Some(state) = self.layer_state_edit_draft.as_ref() {
let mut linetypes: Vec<String> = tab
.scene
.document
.line_types
.iter()
.map(|line_type| line_type.name.clone())
.collect();
for layer in &state.layers {
if !layer.line_type.is_empty()
&& !linetypes
.iter()
.any(|name| name.eq_ignore_ascii_case(&layer.line_type))
{
linetypes.push(layer.line_type.clone());
}
}
linetypes.sort_by_key(|name| name.to_lowercase());
sized_flow(
ex,
1180,
560,
|flow| {
crate::ui::window::layer_state_manager::view_editor(
state,
&self.layer_state_edit_filter,
self.layer_state_edit_color_open,
linetypes.clone(),
flow,
)
},
)
} else {
automatic_flow(ex, |flow| {
container(text(t!("The selected layer state is no longer available.")))
.padding(16)
.width(flow.width)
.height(flow.height)
.into()
})
}
}
super::super::ModalKind::Plot => self.plot_modal_content(ex),
super::super::ModalKind::PrintAll => sized_flow(
ex,
520,
420,
|flow| {
crate::ui::window::print_all::view_window(
&self.print_all_layouts,
self.plot_dialog.printer.as_deref(),
flow,
)
},
),
super::super::ModalKind::LayoutManager => {
let i = self.active_tab;
let layouts = self.tabs[i].scene.layout_names();
let current = self.tabs[i].scene.current_layout.clone();
sized_flow(
ex,
640,
320,
|flow| {
crate::ui::window::layout_manager::view_window(
layouts.clone(),
&self.layout_manager_selected,
&self.layout_manager_rename_buf,
current.clone(),
flow,
)
},
)
}
super::super::ModalKind::ScaleManager => {
let tab = &self.tabs[self.active_tab];
let scales: Vec<(String, String)> = tab
.scene
.scale_list()
.into_iter()
.map(|(name, _, _)| {
let ratio = tab
.scene
.scale_paper_drawing(&name)
.map(|(p, d)| format!("{p}:{d}"))
.unwrap_or_default();
(name, ratio)
})
.collect();
let current = tab.scene.document.header.current_annotation_scale.clone();
sized_flow(
ex,
520,
360,
|flow| {
crate::ui::style::scale_manager::view_window(
&scales,
&self.scale_manager_selected,
&current,
self.scale_rename.as_deref(),
&self.scale_rename_buf,
&self.scale_manager_paper_buf,
&self.scale_manager_drawing_buf,
flow,
)
},
)
}
super::super::ModalKind::AnnoObjectScale => {
let tab = &self.tabs[self.active_tab];
let entity = self.anno_object_scale_target;
// Which scales the object currently has a representation for.
let members: Vec<codec::types::Handle> = entity
.map(|h| {
crate::scene::annotative::object_scale_memberships(
&tab.scene.document,
h,
)
.into_iter()
.map(|(_, sh)| sh)
.collect()
})
.unwrap_or_default();
let label = entity
.and_then(|h| tab.scene.document.get_entity(h))
.map(|e| match e {
codec::EntityType::Text(_) => "TEXT",
codec::EntityType::MText(_) => "MTEXT",
codec::EntityType::Insert(_) => "BLOCK",
codec::EntityType::MultiLeader(_) => "MULTILEADER",
_ => "OBJECT",
})
.unwrap_or("—");
let scales: Vec<(String, String, bool)> = tab
.scene
.scale_list()
.into_iter()
.map(|(name, _, _)| {
let sh = tab.scene.scale_object_handle(&name);
let ratio = tab
.scene
.scale_paper_drawing(&name)
.map(|(p, d)| format!("{p}:{d}"))
.unwrap_or_default();
let is_member = sh.map(|h| members.contains(&h)).unwrap_or(false);
(name, ratio, is_member)
})
.collect();
sized_flow(
ex,
360,
420,
|flow| {
crate::ui::style::anno_object_scale::view_window(
&label,
&scales,
flow,
)
},
)
}
super::super::ModalKind::InsertTable => sized_flow(
ex,
620,
650,
|flow| crate::ui::window::annotation_data::table_insert_view(&self.table_insert, flow),
),
super::super::ModalKind::DataLinkManager => sized_flow(
ex,
760,
560,
|flow| crate::ui::window::annotation_data::data_link_view(&self.data_link_manager, flow),
),
super::super::ModalKind::DataExtraction => sized_flow(
ex,
780,
570,
|flow| crate::ui::window::annotation_data::data_extraction_view(&self.data_extraction, flow),
),
super::super::ModalKind::Plotstyle => sized_flow(
ex,
780,
540,
|flow| {
crate::ui::style::plotstyle::view_window(
&self.tabs[self.active_tab].scene.document,
self.active_plot_style.as_ref(),
self.plotstyle_panel_aci,
&self.ps_color_buf,
&self.ps_lineweight_buf,
&self.ps_screening_buf,
flow,
)
},
),
super::super::ModalKind::TextStyle => {
let tab = &self.tabs[self.active_tab];
let doc = &tab.scene.document;
let styles: Vec<String> = tab
.scene
.document
.text_styles
.iter()
.map(|s| s.name.clone())
.collect();
let selected_style = doc
.text_styles
.get(&self.textstyle_selected);
let (backward, upside_down, vertical, annotative, read_only) = selected_style
.map(|style| {
(
style.flags.backward,
style.flags.upside_down,
style.is_vertical,
style.annotative,
style.xref_dependent,
)
})
.unwrap_or((false, false, false, false, false));
let in_use = self.style_in_use(
crate::app::StyleKind::Text,
&self.textstyle_selected,
);
let compare_opts: Vec<String> = styles
.iter()
.filter(|name| !name.eq_ignore_ascii_case(&self.textstyle_selected))
.cloned()
.collect();
let compare_name = compare_opts
.iter()
.find(|name| name.eq_ignore_ascii_case(&self.textstyle_compare))
.cloned()
.or_else(|| compare_opts.first().cloned())
.unwrap_or_default();
let mut comparison_sections = Vec::new();
if let (Some(a), Some(b)) = (selected_style, doc.text_styles.get(&compare_name)) {
if (
&a.font_file,
&a.big_font_file,
&a.true_type_font,
a.is_shape_file,
) != (
&b.font_file,
&b.big_font_file,
&b.true_type_font,
b.is_shape_file,
) {
comparison_sections.push(crate::i18n::translate("Fonts").into_owned());
}
if (a.height, a.width_factor, a.oblique_angle, a.last_height)
!= (b.height, b.width_factor, b.oblique_angle, b.last_height)
{
comparison_sections.push(crate::i18n::translate("Size").into_owned());
}
if (a.flags, a.is_vertical, a.annotative)
!= (b.flags, b.is_vertical, b.annotative)
{
comparison_sections.push(crate::i18n::translate("Effects").into_owned());
}
}
sized_flow(
ex,
960,
680,
|flow| {
crate::ui::style::textstyle::view_window(
crate::ui::style::textstyle::TextStyleView {
styles: styles.clone(),
selected: &self.textstyle_selected,
current: &tab.scene.document.header.current_text_style_name,
tab: self.textstyle_tab,
compare_name: compare_name.clone(),
compare_opts: compare_opts.clone(),
comparison_sections: comparison_sections.clone(),
read_only,
in_use,
font_buf: &self.textstyle_font,
width_buf: &self.textstyle_width,
oblique_buf: &self.textstyle_oblique,
height_buf: &self.textstyle_height,
bigfont_buf: &self.textstyle_bigfont,
ttf_buf: &self.textstyle_ttf,
backward,
upside_down,
vertical,
annotative,
rename_active: self.style_rename.as_deref(),
rename_buf: &self.style_rename_buf,
},
flow,
)
},
)
}
super::super::ModalKind::MlStyle => {
use codec::objects::ObjectType;
let tab = &self.tabs[self.active_tab];
let styles: Vec<String> = tab
.scene
.document
.objects
.values()
.filter_map(|o| match o {
ObjectType::MLineStyle(s) => Some(s.name.clone()),
_ => None,
})
.collect();
let selected_style = tab.scene.document.objects.values().find_map(|o| match o {
ObjectType::MLineStyle(s) if s.name == self.mlstyle_selected => Some(s),
_ => None,
});
let compare_opts: Vec<String> = styles
.iter()
.filter(|name| !name.eq_ignore_ascii_case(&self.mlstyle_selected))
.cloned()
.collect();
let compare_name = compare_opts
.iter()
.find(|name| name.eq_ignore_ascii_case(&self.mlstyle_compare))
.cloned()
.or_else(|| compare_opts.first().cloned())
.unwrap_or_default();
let compare_style = tab.scene.document.objects.values().find_map(|object| match object {
ObjectType::MLineStyle(style) if style.name == compare_name => Some(style),
_ => None,
});
let mut comparison_sections = Vec::new();
if let (Some(a), Some(b)) = (selected_style, compare_style) {
if (&a.description, a.flags, a.fill_color, a.start_angle, a.end_angle)
!= (&b.description, b.flags, b.fill_color, b.start_angle, b.end_angle)
{
comparison_sections.push(crate::i18n::translate("Caps and Fill").into_owned());
}
if a.elements != b.elements {
comparison_sections.push(crate::i18n::translate("Elements").into_owned());
}
}
let in_use = self.style_in_use(
crate::app::StyleKind::MLine,
&self.mlstyle_selected,
);
sized_flow(
ex,
960,
680,
|flow| {
crate::ui::style::mlstyle::view_window(
crate::ui::style::mlstyle::MlStyleView {
styles: styles.clone(),
selected: &self.mlstyle_selected,
style: selected_style,
current: tab.scene.document.header.multiline_style.clone(),
tab: self.mlstyle_tab,
compare_name: compare_name.clone(),
compare_opts: compare_opts.clone(),
comparison_sections: comparison_sections.clone(),
in_use,
description: &self.mln_description,
start_angle: &self.mln_start_angle,
end_angle: &self.mln_end_angle,
fill_color: &self.mln_fill_color,
elements: &self.mln_elements,
rename_active: self.style_rename.as_deref(),
rename_buf: &self.style_rename_buf,
},
flow,
)
},
)
}
super::super::ModalKind::TableStyle => {
use codec::objects::ObjectType;
let tab = &self.tabs[self.active_tab];
let styles: Vec<String> = tab
.scene
.document
.objects
.values()
.filter_map(|o| match o {
ObjectType::TableStyle(s) => Some(s.name.clone()),
_ => None,
})
.collect();
let selected_style = tab.scene.document.objects.values().find_map(|o| match o {
ObjectType::TableStyle(s) if s.name == self.tablestyle_selected => Some(s),
_ => None,
});
let compare_opts: Vec<String> = styles
.iter()
.filter(|name| !name.eq_ignore_ascii_case(&self.tablestyle_selected))
.cloned()
.collect();
let compare_name = compare_opts
.iter()
.find(|name| name.eq_ignore_ascii_case(&self.tablestyle_compare))
.cloned()
.or_else(|| compare_opts.first().cloned())
.unwrap_or_default();
let compare_style = tab.scene.document.objects.values().find_map(|object| match object {
ObjectType::TableStyle(style) if style.name == compare_name => Some(style),
_ => None,
});
let mut comparison_sections = Vec::new();
if let (Some(a), Some(b)) = (selected_style, compare_style) {
if (
&a.description,
a.flow_direction,
a.horizontal_margin,
a.vertical_margin,
a.title_suppressed,
a.header_suppressed,
a.annotative,
) != (
&b.description,
b.flow_direction,
b.horizontal_margin,
b.vertical_margin,
b.title_suppressed,
b.header_suppressed,
b.annotative,
) {
comparison_sections.push(crate::i18n::translate("General").into_owned());
}
for (label, different) in [
("Data Row", a.data_row_style != b.data_row_style),
("Header Row", a.header_row_style != b.header_row_style),
("Title Row", a.title_row_style != b.title_row_style),
] {
if different {
comparison_sections.push(crate::i18n::translate(label).into_owned());
}
}
}
let in_use = self.style_in_use(
crate::app::StyleKind::Table,
&self.tablestyle_selected,
);
sized_flow(
ex,
960,
680,
|flow| {
crate::ui::style::tablestyle::view_window(
crate::ui::style::tablestyle::TableStyleView {
styles: styles.clone(),
selected: &self.tablestyle_selected,
current: &tab.scene.document.header.current_table_style_name,
style: selected_style,
tab: self.tablestyle_tab,
compare_name: compare_name.clone(),
compare_opts: compare_opts.clone(),
comparison_sections: comparison_sections.clone(),
in_use,
hmargin: &self.ts_hmargin,
vmargin: &self.ts_vmargin,
description: &self.ts_description,
cell_textstyle: &self.ts_cell_textstyle,
cell_height: &self.ts_cell_height,
cell_textcolor: &self.ts_cell_textcolor,
cell_fillcolor: &self.ts_cell_fillcolor,
cell_datatype: &self.ts_cell_datatype,
cell_unittype: &self.ts_cell_unittype,
cell_format: &self.ts_cell_format,
border_lw: &self.ts_border_lw,
border_color: &self.ts_border_color,
border_spacing: &self.ts_border_spacing,
rename_active: self.style_rename.as_deref(),
rename_buf: &self.style_rename_buf,
color_open: self.ts_color_open,
},
flow,
)
},
)
}
super::super::ModalKind::MLeaderStyle => {
use codec::objects::ObjectType;
let tab = &self.tabs[self.active_tab];
let styles: Vec<String> = tab
.scene
.document
.objects
.values()
.filter_map(|o| match o {
ObjectType::MultiLeaderStyle(s) => Some(s.name.clone()),
_ => None,
})
.collect();
let selected_style = tab.scene.document.objects.values().find_map(|o| match o {
ObjectType::MultiLeaderStyle(s) if s.name == self.mleaderstyle_selected => {
Some(s)
}
_ => None,
});
let doc = &tab.scene.document;
let mut block_opts: Vec<String> = vec!["None".to_string()];
block_opts.extend(doc.block_records.iter().map(|b| b.name.clone()));
let mut arrow_opts: Vec<String> = vec!["Closed filled".to_string()];
arrow_opts.extend(doc.block_records.iter().map(|b| b.name.clone()));
let mut lt_opts: Vec<String> = vec!["ByBlock".to_string()];
lt_opts.extend(doc.line_types.iter().map(|lt| lt.name.clone()));
let mut textstyle_opts: Vec<String> = vec!["None".to_string()];
textstyle_opts.extend(doc.text_styles.iter().map(|t| t.name.clone()));
let opt_block = |h: Option<codec::types::Handle>| -> String {
match h {
Some(h) => doc
.block_records
.iter()
.find(|b| b.handle == h)
.map(|b| b.name.clone())
.unwrap_or_else(|| "None".to_string()),
None => "None".to_string(),
}
};
let opt_lt = |h: Option<codec::types::Handle>| -> String {
match h {
Some(h) => doc
.line_types
.iter()
.find(|lt| lt.handle == h)
.map(|lt| lt.name.clone())
.unwrap_or_else(|| "ByBlock".to_string()),
None => "ByBlock".to_string(),
}
};
let opt_ts = |h: Option<codec::types::Handle>| -> String {
match h {
Some(h) => doc
.text_styles
.iter()
.find(|t| t.handle == h)
.map(|t| t.name.clone())
.unwrap_or_else(|| "None".to_string()),
None => "None".to_string(),
}
};
let (line_type_name, arrowhead_name, text_style_name, block_content_name) =
match selected_style {
Some(s) => (
opt_lt(s.line_type_handle),
s.arrowhead_handle
.and_then(|handle| {
doc.block_records
.iter()
.find(|record| record.handle == handle)
.map(|record| record.name.clone())
})
.unwrap_or_else(|| "Closed filled".to_string()),
opt_ts(s.text_style_handle),
opt_block(s.block_content_handle),
),
None => Default::default(),
};
let compare_opts: Vec<String> = styles
.iter()
.filter(|name| !name.eq_ignore_ascii_case(&self.mleaderstyle_selected))
.cloned()
.collect();
let compare_name = compare_opts
.iter()
.find(|name| name.eq_ignore_ascii_case(&self.mleaderstyle_compare))
.cloned()
.or_else(|| compare_opts.first().cloned())
.unwrap_or_default();
let compare_style = doc.objects.values().find_map(|object| match object {
ObjectType::MultiLeaderStyle(style) if style.name == compare_name => Some(style),
_ => None,
});
let mut comparison_sections = Vec::new();
if let (Some(a), Some(b)) = (selected_style, compare_style) {
if (
&a.description,
a.path_type,
a.line_color,
a.line_type_handle,
a.line_weight,
a.arrowhead_handle,
a.arrowhead_size,
a.break_gap_size,
) != (
&b.description,
b.path_type,
b.line_color,
b.line_type_handle,
b.line_weight,
b.arrowhead_handle,
b.arrowhead_size,
b.break_gap_size,
) {
comparison_sections.push(crate::i18n::translate("Leader Format").into_owned());
}
if (
a.enable_landing,
a.enable_dogleg,
a.landing_distance,
a.landing_gap,
a.scale_factor,
a.align_space,
a.max_leader_points,
a.first_segment_angle,
a.second_segment_angle,
a.leader_draw_order,
a.multileader_draw_order,
a.is_annotative,
) != (
b.enable_landing,
b.enable_dogleg,
b.landing_distance,
b.landing_gap,
b.scale_factor,
b.align_space,
b.max_leader_points,
b.first_segment_angle,
b.second_segment_angle,
b.leader_draw_order,
b.multileader_draw_order,
b.is_annotative,
) {
comparison_sections.push(crate::i18n::translate("Leader Structure").into_owned());
}
if (
a.content_type,
&a.default_text,
a.text_style_handle,
a.text_height,
a.text_color,
a.text_angle_type,
a.text_alignment,
) != (
b.content_type,
&b.default_text,
b.text_style_handle,
b.text_height,
b.text_color,
b.text_angle_type,
b.text_alignment,
) || (
a.text_left_attachment,
a.text_right_attachment,
a.text_top_attachment,
a.text_bottom_attachment,
a.text_attachment_direction,
a.text_frame,
a.text_always_left,
) != (
b.text_left_attachment,
b.text_right_attachment,
b.text_top_attachment,
b.text_bottom_attachment,
b.text_attachment_direction,
b.text_frame,
b.text_always_left,
) {
comparison_sections.push(crate::i18n::translate("Content").into_owned());
}
if (
a.block_content_handle,
a.block_content_color,
a.block_content_connection,
a.block_content_rotation,
a.block_content_scale_x,
a.block_content_scale_y,
a.block_content_scale_z,
a.enable_block_scale,
a.enable_block_rotation,
) != (
b.block_content_handle,
b.block_content_color,
b.block_content_connection,
b.block_content_rotation,
b.block_content_scale_x,
b.block_content_scale_y,
b.block_content_scale_z,
b.enable_block_scale,
b.enable_block_rotation,
) {
comparison_sections.push(crate::i18n::translate("Block Content").into_owned());
}
}
let in_use = self.style_in_use(
crate::app::StyleKind::MLeader,
&self.mleaderstyle_selected,
);
sized_flow(
ex,
960,
680,
|flow| {
crate::ui::style::mleaderstyle::view_window(
crate::ui::style::mleaderstyle::MLeaderStyleView {
styles: styles.clone(),
selected: &self.mleaderstyle_selected,
style: selected_style,
current: tab.active_mleader_style.clone(),
tab: self.mleaderstyle_tab,
compare_name: compare_name.clone(),
compare_opts: compare_opts.clone(),
comparison_sections: comparison_sections.clone(),
in_use,
landing_distance: &self.mls_landing_distance,
landing_gap: &self.mls_landing_gap,
arrowhead_size: &self.mls_arrowhead_size,
text_height: &self.mls_text_height,
scale_factor: &self.mls_scale_factor,
break_gap: &self.mls_break_gap,
first_seg_angle: &self.mls_first_seg_angle,
second_seg_angle: &self.mls_second_seg_angle,
max_points: &self.mls_max_points,
default_text: &self.mls_default_text,
line_color: &self.mls_line_color,
text_color: &self.mls_text_color,
description: &self.mls_description,
align_space: &self.mls_align_space,
block_color: &self.mls_block_color,
block_rotation: &self.mls_block_rotation,
block_scale_x: &self.mls_block_scale_x,
block_scale_y: &self.mls_block_scale_y,
block_scale_z: &self.mls_block_scale_z,
block_opts: block_opts.clone(),
arrow_opts: arrow_opts.clone(),
lt_opts: lt_opts.clone(),
textstyle_opts: textstyle_opts.clone(),
line_type_name: line_type_name.clone(),
arrowhead_name: arrowhead_name.clone(),
text_style_name: text_style_name.clone(),
block_content_name: block_content_name.clone(),
rename_active: self.style_rename.as_deref(),
rename_buf: &self.style_rename_buf,
color_open: self.mls_color_open,
},
flow,
)
},
)
}
super::super::ModalKind::DimStyle => {
let tab = &self.tabs[self.active_tab];
let styles: Vec<String> = tab
.scene
.document
.dim_styles
.iter()
.map(|s| s.name.clone())
.collect();
let doc = &tab.scene.document;
// Dropdown options (names must match the records exactly so the
// selection can be resolved back to a handle on the update side).
let mut block_opts: Vec<String> = vec!["Default".to_string()];
block_opts.extend(
doc.block_records
.iter()
.filter(|b| {
!b.is_layout()
&& !b.is_model_space()
&& !b.is_paper_space()
&& !b.flags.is_xref
&& !b.flags.is_xref_overlay
&& !b.flags.is_external
&& !b.name.starts_with('*')
})
.map(|b| b.name.clone()),
);
let mut lt_opts: Vec<String> = vec!["ByBlock".to_string()];
lt_opts.extend(doc.line_types.iter().map(|lt| lt.name.clone()));
let text_style_opts: Vec<String> =
doc.text_styles.iter().map(|style| style.name.clone()).collect();
let text_style_fixed_height = doc
.text_styles
.get(&self.ds_dimtxsty)
.map(|style| style.height)
.filter(|height| *height > 0.0);
let blk_name = |h: codec::types::Handle| -> String {
if h.is_null() {
"Default".to_string()
} else {
doc.block_records
.iter()
.find(|b| b.handle == h)
.map(|b| b.name.clone())
.unwrap_or_else(|| "Default".to_string())
}
};
let lt_name = |h: codec::types::Handle| -> String {
if h.is_null() {
"ByBlock".to_string()
} else {
doc.line_types
.iter()
.find(|lt| lt.handle == h)
.map(|lt| lt.name.clone())
.unwrap_or_else(|| "ByBlock".to_string())
}
};
let ds_sel = doc.dim_styles.get(&self.dimstyle_selected);
let read_only = false;
let in_use = doc.entities().any(|entity| {
matches!(entity, codec::EntityType::Dimension(dimension)
if dimension.base().style_name.eq_ignore_ascii_case(&self.dimstyle_selected))
});
let compare_opts: Vec<String> = styles
.iter()
.filter(|name| !name.eq_ignore_ascii_case(&self.dimstyle_selected))
.cloned()
.collect();
let compare_name = compare_opts
.iter()
.find(|name| name.eq_ignore_ascii_case(&self.dimstyle_compare))
.cloned()
.or_else(|| compare_opts.first().cloned())
.unwrap_or_default();
let mut comparison_sections = Vec::new();
if let (Some(a), Some(b)) = (ds_sel, doc.dim_styles.get(&compare_name)) {
if (a.dimdle, a.dimdli, a.dimgap, a.dimclrd, a.dimlwd, a.dimsd1, a.dimsd2)
!= (b.dimdle, b.dimdli, b.dimgap, b.dimclrd, b.dimlwd, b.dimsd1, b.dimsd2)
|| (a.dimexe, a.dimexo, a.dimclre, a.dimlwe, a.dimse1, a.dimse2, a.dimfxl, a.dimfxlon)
!= (b.dimexe, b.dimexo, b.dimclre, b.dimlwe, b.dimse1, b.dimse2, b.dimfxl, b.dimfxlon)
|| (a.dimltex_handle, a.dimltex1_handle, a.dimltex2_handle)
!= (b.dimltex_handle, b.dimltex1_handle, b.dimltex2_handle)
{
comparison_sections.push(crate::i18n::translate("Lines").into_owned());
}
if (a.dimasz, a.dimblk, a.dimblk1, a.dimblk2, a.dimldrblk, a.dimsah, a.dimcen, a.dimtsz)
!= (b.dimasz, b.dimblk, b.dimblk1, b.dimblk2, b.dimldrblk, b.dimsah, b.dimcen, b.dimtsz)
|| (a.dimarcsym, a.dimjogang) != (b.dimarcsym, b.dimjogang)
{
comparison_sections.push(crate::i18n::translate("Symbols and Arrows").into_owned());
}
if (a.dimclrt, a.dimtxt, &a.dimtxsty, a.dimjust, a.dimtad, a.dimtvp)
!= (b.dimclrt, b.dimtxt, &b.dimtxsty, b.dimjust, b.dimtad, b.dimtvp)
|| (a.dimtih, a.dimtoh, a.dimtfill, a.dimtfillclr, a.dimtxtdirection)
!= (b.dimtih, b.dimtoh, b.dimtfill, b.dimtfillclr, b.dimtxtdirection)
{
comparison_sections.push(crate::i18n::translate("Text").into_owned());
}
if (a.dimatfit, a.dimtix, a.dimsoxd, a.dimtmove, a.dimupt, a.dimtofl, a.dimscale, a.annotative)
!= (b.dimatfit, b.dimtix, b.dimsoxd, b.dimtmove, b.dimupt, b.dimtofl, b.dimscale, b.annotative)
{
comparison_sections.push(crate::i18n::translate("Fit").into_owned());
}
if (a.dimlfac, a.dimlunit, a.dimdec, &a.dimpost, a.dimdsep, a.dimrnd, a.dimzin, a.dimfrac)
!= (b.dimlfac, b.dimlunit, b.dimdec, &b.dimpost, b.dimdsep, b.dimrnd, b.dimzin, b.dimfrac)
|| (a.dimaunit, a.dimadec, a.dimazin) != (b.dimaunit, b.dimadec, b.dimazin)
{
comparison_sections.push(crate::i18n::translate("Primary Units").into_owned());
}
if (a.dimalt, a.dimaltf, a.dimaltd, a.dimaltu, a.dimalttd, a.dimaltrnd, &a.dimapost, a.dimaltz, a.dimalttz)
!= (b.dimalt, b.dimaltf, b.dimaltd, b.dimaltu, b.dimalttd, b.dimaltrnd, &b.dimapost, b.dimaltz, b.dimalttz)
{
comparison_sections.push(crate::i18n::translate("Alternate Units").into_owned());
}
if (a.dimtol, a.dimlim, a.dimtp, a.dimtm, a.dimtdec, a.dimtfac, a.dimtolj, a.dimtzin)
!= (b.dimtol, b.dimlim, b.dimtp, b.dimtm, b.dimtdec, b.dimtfac, b.dimtolj, b.dimtzin)
{
comparison_sections.push(crate::i18n::translate("Tolerances").into_owned());
}
}
let (
dimblk_name,
dimblk1_name,
dimblk2_name,
dimldrblk_name,
dimltex_name,
dimltex1_name,
dimltex2_name,
) = match ds_sel {
Some(d) => (
blk_name(d.dimblk),
blk_name(d.dimblk1),
blk_name(d.dimblk2),
blk_name(d.dimldrblk),
lt_name(d.dimltex_handle),
lt_name(d.dimltex1_handle),
lt_name(d.dimltex2_handle),
),
None => Default::default(),
};
sized_flow(ex, 960, 680, |flow| {
crate::ui::style::dimstyle::view_window(
styles.clone(),
&self.dimstyle_selected,
&self.tabs[self.active_tab]
.scene
.document
.header
.current_dimstyle_name,
self.dimstyle_tab,
crate::ui::style::dimstyle::DimStyleValues {
dimdle: &self.ds_dimdle,
dimdli: &self.ds_dimdli,
dimgap: &self.ds_dimgap,
dimexe: &self.ds_dimexe,
dimexo: &self.ds_dimexo,
dimsd1: self.ds_dimsd1,
dimsd2: self.ds_dimsd2,
dimse1: self.ds_dimse1,
dimse2: self.ds_dimse2,
dimasz: &self.ds_dimasz,
dimcen: &self.ds_dimcen,
dimtsz: &self.ds_dimtsz,
dimtxt: &self.ds_dimtxt,
dimtxsty: &self.ds_dimtxsty,
dimtad: &self.ds_dimtad,
dimtih: self.ds_dimtih,
dimtoh: self.ds_dimtoh,
dimscale: &self.ds_dimscale,
dimlfac: &self.ds_dimlfac,
dimlunit: &self.ds_dimlunit,
dimdec: &self.ds_dimdec,
dimpost: &self.ds_dimpost,
dimtol: self.ds_dimtol,
dimlim: self.ds_dimlim,
dimtp: &self.ds_dimtp,
dimtm: &self.ds_dimtm,
dimtdec: &self.ds_dimtdec,
dimtfac: &self.ds_dimtfac,
annotative: self.ds_annotative,
dimclrd: &self.ds_dimclrd,
dimlwd: &self.ds_dimlwd,
dimclre: &self.ds_dimclre,
dimlwe: &self.ds_dimlwe,
dimfxl: &self.ds_dimfxl,
dimfxlon: self.ds_dimfxlon,
dimsah: self.ds_dimsah,
dimarcsym: &self.ds_dimarcsym,
dimjogang: &self.ds_dimjogang,
dimclrt: &self.ds_dimclrt,
dimjust: &self.ds_dimjust,
dimtvp: &self.ds_dimtvp,
dimtfill: &self.ds_dimtfill,
dimtfillclr: &self.ds_dimtfillclr,
dimtxtdirection: self.ds_dimtxtdirection,
dimatfit: &self.ds_dimatfit,
dimtix: self.ds_dimtix,
dimsoxd: self.ds_dimsoxd,
dimtmove: &self.ds_dimtmove,
dimupt: self.ds_dimupt,
dimtofl: self.ds_dimtofl,
dimdsep: &self.ds_dimdsep,
dimrnd: &self.ds_dimrnd,
dimzin: &self.ds_dimzin,
dimfrac: &self.ds_dimfrac,
dimaunit: &self.ds_dimaunit,
dimadec: &self.ds_dimadec,
dimazin: &self.ds_dimazin,
dimalt: self.ds_dimalt,
dimaltf: &self.ds_dimaltf,
dimaltd: &self.ds_dimaltd,
dimaltu: &self.ds_dimaltu,
dimalttd: &self.ds_dimalttd,
dimaltrnd: &self.ds_dimaltrnd,
dimapost: &self.ds_dimapost,
dimaltz: &self.ds_dimaltz,
dimalttz: &self.ds_dimalttz,
dimtolj: &self.ds_dimtolj,
dimtzin: &self.ds_dimtzin,
dimblk_name: dimblk_name.clone(),
dimblk1_name: dimblk1_name.clone(),
dimblk2_name: dimblk2_name.clone(),
dimldrblk_name: dimldrblk_name.clone(),
dimltex_name: dimltex_name.clone(),
dimltex1_name: dimltex1_name.clone(),
dimltex2_name: dimltex2_name.clone(),
block_opts: block_opts.clone(),
lt_opts: lt_opts.clone(),
text_style_opts: text_style_opts.clone(),
text_style_fixed_height,
compare_name: compare_name.clone(),
compare_opts: compare_opts.clone(),
comparison_sections: comparison_sections.clone(),
read_only,
in_use,
color_open: self.ds_color_open.clone(),
},
self.style_rename.as_deref(),
&self.style_rename_buf,
flow,
)
})
}
super::super::ModalKind::AssocPrompt => {
automatic_flow(ex, default_assoc_dialog_window)
}
super::super::ModalKind::DonationPrompt => {
sized_flow(ex, 540, 360, donation_dialog_window)
}
super::super::ModalKind::GpuWarning => {
sized_flow(ex, 520, 400, |flow| gpu_warning_window(&self.gpu_status, flow))
}
super::super::ModalKind::AecDropWarning => {
let src_label = self
.tabs
.get(self.active_tab)
.map(|t| {
let is_dxf = t
.current_path
.as_ref()
.and_then(|path| path.extension())
.and_then(|extension| extension.to_str())
.map(|extension| extension.eq_ignore_ascii_case("dxf"))
.unwrap_or(false);
let version = if is_dxf {
t.scene.document.version
} else {
t.scene
.document
.dwg_source_version
.unwrap_or(t.scene.document.version)
};
crate::io::format_for_version(version, is_dxf)
})
.unwrap_or_else(|| "DWG".to_string());
automatic_flow(ex, |flow| {
aec_drop_dialog_window(
self.aec_drop_count,
&self.save_dialog_format,
&src_label,
flow,
)
})
}
#[cfg(not(target_arch = "wasm32"))]
super::super::ModalKind::FileInUse => {
let (path, error) = self
.pending_save_failure
.as_ref()
.map(|failure| {
(
failure.path.display().to_string(),
failure.error.clone(),
)
})
.unwrap_or_default();
automatic_flow(ex, |flow| file_in_use_dialog_window(&path, &error, flow))
}
#[cfg(not(target_arch = "wasm32"))]
super::super::ModalKind::ExternalChange => {
let path = self
.pending_external_change
.as_ref()
.map(|conflict| conflict.path.display().to_string())
.unwrap_or_default();
automatic_flow(ex, |flow| external_change_dialog_window(&path, flow))
}
super::super::ModalKind::LayerDeleteWarning => {
let (names, count) = self
.layer_delete_pending
.clone()
.unwrap_or_else(|| (Vec::new(), 0));
automatic_flow(ex, |flow| layer_delete_warning_window(&names, count, flow))
}
super::super::ModalKind::Unsaved => {
let tab_name = match &self.pending_close {
Some(super::super::PendingClose::Tab(tab_id)) => self
.tabs
.iter()
.find(|t| t.id == *tab_id)
.map(|t| t.tab_display_name())
.unwrap_or_default(),
Some(super::super::PendingClose::Quit) => self
.tabs
.iter()
.find(|t| t.dirty)
.map(|t| t.tab_display_name())
.unwrap_or_default(),
None => String::new(),
};
automatic_flow(ex, |flow| unsaved_changes_dialog_window(&tab_name, flow))
}
super::super::ModalKind::PointStyle => sized_flow(
ex,
360,
470,
|flow| {
crate::ui::style::point_style::view_window(
self.tabs[self.active_tab]
.scene
.document
.header
.point_display_mode,
self.point_size_relative,
&self.point_size_buf,
flow,
)
},
),
super::super::ModalKind::Hyperlink => sized_flow(ex, 560, 260, |flow| {
hyperlink_dialog_window(
&self.hyperlink_editor_url,
&self.hyperlink_editor_description,
self.hyperlink_editor_mixed,
flow,
)
}),
super::super::ModalKind::AttributeEditor => {
let doc = &self.tabs[self.active_tab].scene.document;
let layers: Vec<String> = doc.layers.iter().map(|l| l.name.clone()).collect();
let mut linetypes: Vec<String> = vec!["ByLayer".to_string()];
linetypes.extend(
doc.line_types
.iter()
.map(|lt| lt.name.clone())
.filter(|n| !n.is_empty() && n != "ByLayer"),
);
let styles: Vec<String> = doc
.text_styles
.iter()
.map(|s| s.name.trim().to_string())
.filter(|n| !n.is_empty())
.collect();
sized_flow(
ex,
640,
500,
|flow| {
crate::ui::window::attribute_editor::view_window(
&self.attr_editor_block,
&self.attr_editor_rows,
self.attr_editor_selected,
self.attr_editor_tab,
layers.clone(),
linetypes.clone(),
styles.clone(),
flow,
)
},
)
}
super::super::ModalKind::SaveDialog => {
automatic_flow(ex, |flow| {
save_as_dialog_window(
&self.save_dialog_filename,
&self.save_dialog_format,
flow,
)
})
}
super::super::ModalKind::MissingFonts => {
let fonts = self.missing_fonts.as_ref()?;
let font_source = &self.font_source_input;
automatic_flow(ex, |flow| {
crate::ui::window::missing_fonts::view_window(
fonts,
&font_source,
self.missing_fonts_downloading,
flow,
)
})
}
super::super::ModalKind::Recovery => {
let report = self.recovery_report.as_ref()?;
sized_flow(ex, 680, 460, |flow| {
crate::ui::window::recovery::view_window(
report,
self.pending_opens.is_empty(),
flow,
)
})
}
super::super::ModalKind::RecoveryPrompt => {
let opening = self.opening.as_ref()?;
let error = opening.recovery_error.as_deref()?;
automatic_flow(ex, |flow| {
crate::ui::window::recovery::view_prompt(
&opening.name,
error,
flow,
)
})
}
})
}
}
fn sized_flow<'a>(
extra: iced::Vector,
max_width: u16,
max_height: u16,
mut build: impl FnMut(crate::ui::modal::ModalSizing) -> Element<'a, Message>,
) -> Element<'a, Message> {
crate::ui::modal::intrinsic(
build(crate::ui::modal::ModalSizing::INTRINSIC),
build(crate::ui::modal::ModalSizing::FILL),
iced::Size::new(max_width as f32, max_height as f32),
extra,
)
}
fn automatic_flow<'a>(
extra: iced::Vector,
mut build: impl FnMut(crate::ui::modal::ModalSizing) -> Element<'a, Message>,
) -> Element<'a, Message> {
crate::ui::modal::intrinsic(
build(crate::ui::modal::ModalSizing::INTRINSIC),
build(crate::ui::modal::ModalSizing::FILL),
iced::Size::new(f32::INFINITY, f32::INFINITY),
extra,
)
}
fn dialog_button<'a>(
label: impl Into<Cow<'a, str>>,
message: Message,
style: fn(&Theme, button::Status) -> button::Style,
) -> Element<'a, Message> {
crate::ui::style::form::dialog_button_styled(label.into(), message, style).into()
}
fn dialog_body_style(theme: &Theme) -> container::Style {
let palette = theme.palette();
container::Style {
background: Some(Background::Color(palette.background.base.color)),
text_color: Some(palette.background.base.text),
..Default::default()
}
}
fn dialog_muted_text_style(theme: &Theme) -> iced::widget::text::Style {
iced::widget::text::Style {
color: Some(theme.palette().background.base.text.scale_alpha(0.68)),
}
}
fn hyperlink_dialog_window<'a>(
url: &'a str,
description: &'a str,
mixed: bool,
sizing: crate::ui::modal::ModalSizing,
) -> Element<'a, Message> {
let label = |value: Cow<'static, str>| {
text(value)
.size(11)
.style(dialog_muted_text_style)
.width(90)
};
let mut items: Vec<Element<'a, Message>> = Vec::new();
if mixed {
items.push(
text(t!("Selected objects have different hyperlink values."))
.size(11)
.style(dialog_muted_text_style)
.into(),
);
items.push(Space::new().height(8).into());
}
items.push(
row![
label(t!("URL:")),
iced::widget::text_input("https://", url)
.on_input(Message::HyperlinkUrlChanged)
.size(13)
.padding([5, 8])
.width(Fill),
]
.spacing(8)
.align_y(iced::Alignment::Center)
.width(sizing.width)
.into(),
);
items.push(Space::new().height(8).into());
items.push(
row![
label(t!("Description:")),
iced::widget::text_input("", description)
.on_input(Message::HyperlinkDescriptionChanged)
.size(13)
.padding([5, 8])
.width(Fill),
]
.spacing(8)
.align_y(iced::Alignment::Center)
.width(sizing.width)
.into(),
);
items.push(Space::new().height(Fill).into());
items.push(
row![
dialog_button(t!("Remove"), Message::HyperlinkRemove, button::danger),
Space::new().width(Fill),
dialog_button(t!("Cancel"), Message::HyperlinkCancel, button::secondary),
Space::new().width(8),
dialog_button(t!("OK"), Message::HyperlinkApply, button::primary),
]
.align_y(iced::Alignment::Center)
.into(),
);
container(
column(items)
.spacing(0)
.width(sizing.width)
.height(sizing.height),
)
.style(dialog_body_style)
.padding([14, 16])
.into()
}
/// Compact Save-As options dialog: pick the format/version and a default file
/// name. The destination folder and overwrite confirmation come from the
/// native OS save dialog (native) or the browser download (web) that follows.
fn save_as_dialog_window<'a>(
filename: &'a str,
format: &'a str,
sizing: crate::ui::modal::ModalSizing,
) -> Element<'a, Message> {
let sel_fmt = crate::io::SAVE_FORMAT_OPTIONS
.iter()
.copied()
.find(|&s| s == format);
let label = |s: Cow<'static, str>| text(s).size(11).style(dialog_muted_text_style);
let field_width = if matches!(sizing.width, iced::Length::Fill) {
Fill
} else {
iced::Length::Shrink
};
let mut items: Vec<Element<'a, Message>> = Vec::new();
items.push(text(t!("Save Drawing As")).size(14).into());
items.push(Space::new().height(12).into());
// Web has no native file dialog, so the file name is typed here. On native
// the OS save dialog collects the name, so this field is omitted.
#[cfg(target_arch = "wasm32")]
{
items.push(
row![
label(t!("File name:")).width(70),
iced::widget::text_input("drawing.dwg", filename)
.on_input(Message::SaveDialogFilenameChanged)
.size(13)
.padding([5, 8])
.width(field_width),
]
.align_y(iced::Alignment::Center)
.spacing(6)
.width(sizing.width)
.into(),
);
items.push(Space::new().height(8).into());
}
#[cfg(not(target_arch = "wasm32"))]
let _ = filename;
items.push(
row![
label(t!("Format:")).width(70),
iced::widget::pick_list(
sel_fmt,
crate::io::SAVE_FORMAT_OPTIONS,
|value| value.to_string(),
)
.on_select(|s: &str| Message::SaveDialogFormatChanged(s.to_string()))
.width(field_width),
]
.align_y(iced::Alignment::Center)
.spacing(6)
.width(sizing.width)
.into(),
);
items.push(Space::new().height(16).into());
items.push(
row![
Space::new().width(Fit),
dialog_button(t!("Save as..."), Message::SaveDialogConfirm, button::primary),
Space::new().width(8),
dialog_button(t!("Cancel"), Message::SaveDialogCancel, button::secondary),
]
.into(),
);
let body = column(items)
.spacing(0)
.width(sizing.width)
.height(sizing.height);
container(body)
.style(dialog_body_style)
.padding([14, 16])
.width(sizing.width)
.height(sizing.height)
.into()
}
fn unsaved_changes_dialog_window(
name: &str,
sizing: crate::ui::modal::ModalSizing,
) -> Element<'static, Message> {
container(
column![
text(name.to_owned()).size(14),
iced::widget::Space::new().height(4),
text(crate::tr!("modal", "unsaved-save-prompt"))
.size(12)
.style(dialog_muted_text_style),
iced::widget::Space::new().height(20),
row![
dialog_button(t!("Save"), Message::UnsavedDialogSave, button::primary),
iced::widget::Space::new().width(8),
dialog_button(t!("Discard"), Message::UnsavedDialogDiscard, button::danger),
iced::widget::Space::new().width(8),
dialog_button(t!("Cancel"), Message::UnsavedDialogCancel, button::secondary),
],
]
.spacing(0),
)
.style(dialog_body_style)
.center_x(sizing.width)
.center_y(sizing.height)
.padding([24, 28])
.into()
}
#[cfg(not(target_arch = "wasm32"))]
fn file_in_use_dialog_window(
path: &str,
error: &str,
sizing: crate::ui::modal::ModalSizing,
) -> Element<'static, Message> {
let file_name = std::path::Path::new(path)
.file_name()
.map(|name| name.to_string_lossy().into_owned())
.unwrap_or_else(|| t!("Drawing").into_owned());
let heading = t!("\"%{file_name}\" could not be saved.", file_name = file_name);
let path_line = t!("Path: %{path}", path = path);
let details = t!("Details: %{error}", error = error);
container(
column![
text(heading).size(14),
Space::new().height(8),
text(t!(
"The file is open or being used by another application. Close it there and retry, or save this drawing under a different name."
))
.size(13)
.width(Fit),
Space::new().height(12),
text(path_line).size(11).style(dialog_muted_text_style).width(Fit),
Space::new().height(4),
text(details).size(11).style(dialog_muted_text_style).width(Fit),
Space::new().height(18),
row![
dialog_button(
t!("Retry"),
Message::SaveFileInUseRetry,
button::primary
),
Space::new().width(8),
dialog_button(
t!("Save As"),
Message::SaveFileInUseSaveAs,
button::secondary
),
Space::new().width(8),
dialog_button(
t!("Cancel"),
Message::SaveFileInUseCancel,
button::secondary
),
],
]
.spacing(0),
)
.style(dialog_body_style)
.padding([18, 20])
.width(sizing.width)
.height(sizing.height)
.into()
}
#[cfg(not(target_arch = "wasm32"))]
fn external_change_dialog_window(
path: &str,
sizing: crate::ui::modal::ModalSizing,
) -> Element<'static, Message> {
let file_name = std::path::Path::new(path)
.file_name()
.map(|name| name.to_string_lossy().into_owned())
.unwrap_or_else(|| t!("Drawing").into_owned());
let heading = t!("\"%{file_name}\" was changed by another application.", file_name = file_name);
let path_line = t!("Path: %{path}", path = path);
container(
column![
text(heading).size(14),
Space::new().height(8),
text(t!(
"Saving now could destroy those external changes. Reload the disk copy, save your local work elsewhere, or explicitly overwrite it."
))
.size(13)
.width(Fit),
Space::new().height(12),
text(path_line).size(11).style(dialog_muted_text_style).width(Fit),
Space::new().height(18),
row![
dialog_button(
t!("Reload from Disk"),
Message::ExternalChangeReload,
button::primary
),
Space::new().width(8),
dialog_button(
t!("Save As"),
Message::ExternalChangeSaveAs,
button::secondary
),
Space::new().width(8),
dialog_button(
t!("Overwrite"),
Message::ExternalChangeOverwrite,
button::danger
),
Space::new().width(8),
dialog_button(
t!("Cancel"),
Message::ExternalChangeCancel,
button::secondary
),
],
]
.spacing(0),
)
.style(dialog_body_style)
.padding([18, 20])
.width(sizing.width)
.height(sizing.height)
.into()
}
/// Warning shown before a lossy Save-As: the drawing carries unsupported
/// (AEC / application) objects that survive only as verbatim source-version
/// bytes, so saving to a different version or to DXF would drop them. Offers to
/// save in the source version (keep them) or proceed (drop them).
fn aec_drop_dialog_window(
count: usize,
target: &str,
src_version: &str,
sizing: crate::ui::modal::ModalSizing,
) -> Element<'static, Message> {
let body_text = t!(
"This drawing contains %{count} AEC/Civil objects that \"%{target}\" cannot store, so they will not be saved.\n\nTo keep them, save in the source version (%{src_version}).",
count = count,
target = target,
src_version = src_version,
);
container(
column![
text(body_text).size(13),
iced::widget::Space::new().height(20),
row![
dialog_button(
t!("Save in source version"),
Message::AecDropSameVersion,
button::primary
),
iced::widget::Space::new().width(8),
dialog_button(t!("Save anyway"), Message::AecDropProceed, button::warning),
iced::widget::Space::new().width(8),
dialog_button(t!("Back"), Message::AecDropBack, button::secondary),
],
]
.spacing(0),
)
.style(dialog_body_style)
.center_x(sizing.width)
.center_y(sizing.height)
.padding([24, 28])
.into()
}
/// Confirmation shown when the chosen Save-As filename already exists in the
/// target folder. "Replace" overwrites; "Cancel" returns to the Save dialog.
/// Confirm deleting layer(s) that still have objects on them. "Delete Objects"
/// erases them and removes the layers; "Cancel" leaves everything.
fn layer_delete_warning_window(
names: &[String],
count: usize,
sizing: crate::ui::modal::ModalSizing,
) -> Element<'static, Message> {
let obj = if count == 1 { t!("object") } else { t!("objects") };
let has = if names.len() == 1 { t!("has") } else { t!("hold") };
let those = if count == 1 {
t!("that object")
} else {
t!("those objects")
};
let subject = if names.len() == 1 {
t!("Layer \"%{name}\"", name = names[0]).into_owned()
} else {
t!("%{count} selected layers", count = names.len()).into_owned()
};
let body_text = t!(
"%{subject} still %{has} %{count} %{obj}.\n\nDeleting will also remove %{those} from the drawing. Continue?",
subject = subject,
has = has,
count = count,
obj = obj,
those = those,
);
container(
column![
text(body_text).size(13),
iced::widget::Space::new().height(20),
row![
dialog_button(
t!("Delete Objects"),
Message::LayerDeleteConfirm,
button::danger
),
iced::widget::Space::new().width(8),
dialog_button(t!("Cancel"), Message::CloseModal, button::secondary),
],
]
.spacing(0),
)
.style(dialog_body_style)
.center_x(sizing.width)
.center_y(sizing.height)
.padding([24, 28])
.into()
}
/// What the graphics verdict means and what usually fixes it. Two
/// situations share the dialog — a software rasterizer (slow, but drawing)
/// and no renderer at all (a blank viewport) — and the remedy hint is per
/// platform. "OK" closes it for this session; the status-bar pill brings it
/// back. "Don't show again" silences this verdict only, so a different
/// failure on the same machine still prompts.
fn gpu_warning_window(
status: &crate::scene::pipeline::GpuStatus,
sizing: crate::ui::modal::ModalSizing,
) -> Element<'static, Message> {
use crate::scene::pipeline::GpuStatus;
let (headline, consequences) = match status {
GpuStatus::Software(adapter) => (
crate::tr!("gpu", "software-headline", adapter = adapter.name.clone()),
Some(crate::tr!("gpu", "software-consequences")),
),
_ => (crate::tr!("gpu", "no-renderer-headline"), None),
};
let mut body = column![text(headline).size(13)].spacing(8);
if let Some(consequences) = consequences {
body = body.push(text(consequences).size(13));
}
body = body.push(
text(crate::app::startup::gpu_platform_hint())
.size(13)
.style(dialog_muted_text_style),
);
container(
column![
body,
Space::new().height(20),
row![
dialog_button(t!("OK"), Message::CloseModal, button::primary),
Space::new().width(8),
dialog_button(
crate::tr!("gpu", "silence"),
Message::GpuWarningSilence,
button::secondary
),
],
]
.spacing(0),
)
.style(dialog_body_style)
.center_x(sizing.width)
.center_y(sizing.height)
.padding([24, 28])
.into()
}
fn donation_dialog_window(sizing: crate::ui::modal::ModalSizing) -> Element<'static, Message> {
container(
column![
text(crate::tr!("donation", "heading")).size(18),
row![
text(crate::tr!("donation", "body"))
.size(14)
.width(Fill),
crate::ui::icons::themed(crate::ui::icons::HEART, 52.0),
]
.spacing(20)
.align_y(iced::Center),
row![
Space::new().width(Fill),
dialog_button(
crate::tr!("start", "donate"),
Message::DonationPromptDonate,
button::primary,
),
dialog_button(
crate::tr!("donation", "decline"),
Message::CloseModal,
button::secondary,
),
]
.spacing(8)
.align_y(iced::Center),
]
.spacing(18)
.width(sizing.width),
)
.style(dialog_body_style)
.padding([24, 28])
.into()
}
/// One-time default application prompt.
fn default_assoc_dialog_window(
sizing: crate::ui::modal::ModalSizing,
) -> Element<'static, Message> {
container(
column![
text(t!("Make Open CAD Studio your default CAD app?"))
.size(15),
iced::widget::Space::new().height(10),
text(t!("Open .dwg and .dxf drawings in Open CAD Studio by default. You can change this later in your system settings."))
.size(12)
.style(dialog_muted_text_style),
iced::widget::Space::new().height(22),
row![
iced::widget::Space::new().width(Fit),
dialog_button(t!("Not now"), Message::AssocPromptNo, button::secondary),
iced::widget::Space::new().width(8),
dialog_button(
t!("Yes, set as default"),
Message::AssocPromptYes,
button::primary
),
]
.align_y(iced::Center),
]
.spacing(0),
)
.style(dialog_body_style)
.center_x(sizing.width)
.center_y(sizing.height)
.padding([24, 28])
.into()
}