forked from 1b-agent/opencadstudio-upstream
2267 lines
97 KiB
Rust
2267 lines
97 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::GeometricTolerance) => crate::t!("Geometric Tolerance").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 => 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,
|
|
{
|
|
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,
|
|
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, ¤t);
|
|
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::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,
|
|
¤t,
|
|
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<acadrust::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 {
|
|
acadrust::EntityType::Text(_) => "TEXT",
|
|
acadrust::EntityType::MText(_) => "MTEXT",
|
|
acadrust::EntityType::Insert(_) => "BLOCK",
|
|
acadrust::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 acadrust::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 acadrust::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 acadrust::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<acadrust::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<acadrust::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<acadrust::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: acadrust::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: acadrust::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, acadrust::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(idx)) => self
|
|
.tabs
|
|
.get(*idx)
|
|
.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,
|
|
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> {
|
|
button(text(label.into()).size(13))
|
|
.on_press(message)
|
|
.style(style)
|
|
.padding([6, 18])
|
|
.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()
|
|
}
|