forked from 1b-agent/opencadstudio-upstream
On native the button opens the OS save dialog (folder + name + overwrite) and on web it starts the download, so it does not itself save — "Continue" reads truer than "Save". Co-Authored-By: Claude Opus 4.8 <[email protected]>
1116 lines
43 KiB
Rust
1116 lines
43 KiB
Rust
use super::super::{Message, OpenCADStudio};
|
|
use iced::widget::{button, column, container, pick_list, row, text, Space};
|
|
use iced::{Background, Border, Color, Element, Fill, Theme};
|
|
|
|
impl OpenCADStudio {
|
|
/// Build the currently-open modal dialog's content (Plan B), or `None`.
|
|
/// Each former pop-up window is constructed here and given a bounded size
|
|
/// (About shrinks to its content). Rendered as an overlay by `view_main`.
|
|
pub(super) fn modal_content(&self) -> Option<Element<'_, Message>> {
|
|
fn sized<'a>(e: Element<'a, Message>, w: u16, h: u16) -> Element<'a, Message> {
|
|
iced::widget::container(e)
|
|
.width(iced::Length::Fixed(w as f32))
|
|
.height(iced::Length::Fixed(h as f32))
|
|
.into()
|
|
}
|
|
Some(match self.active_modal? {
|
|
super::super::ModalKind::About => crate::ui::window::about::view_window(),
|
|
super::super::ModalKind::Shortcuts => {
|
|
sized(crate::ui::window::shortcuts::view_window(&self.shortcut_overrides), 720, 520)
|
|
}
|
|
super::super::ModalKind::Aliases => {
|
|
sized(crate::ui::window::alias_editor::view_window(&self.alias_editor_rows), 480, 520)
|
|
}
|
|
super::super::ModalKind::PluginManager => sized(
|
|
crate::ui::window::plugin_manager::view_window(
|
|
&self.disabled_plugins,
|
|
&self.external_plugins,
|
|
&self.loaded_plugin_ids,
|
|
crate::ui::window::plugin_manager::MarketView {
|
|
registry: &self.plugin_registry,
|
|
input: &self.plugin_repo_input,
|
|
repos: &self.plugin_repos,
|
|
release_tags: &self.repo_release_tags,
|
|
selected_tag: &self.repo_selected_tag,
|
|
status: &self.marketplace_status,
|
|
},
|
|
),
|
|
520,
|
|
460,
|
|
),
|
|
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(crate::ui::window::update_notice::view_window(latest, body), 560, 460)
|
|
}
|
|
super::super::ModalKind::Layers => {
|
|
let tab = &self.tabs[self.active_tab];
|
|
sized(tab.layers.view_window(), 900, 360)
|
|
}
|
|
super::super::ModalKind::Plot => {
|
|
sized(crate::ui::window::plot::view_window(&self.plot_dialog), 760, 540)
|
|
}
|
|
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(
|
|
crate::ui::window::layout_manager::view_window(
|
|
layouts,
|
|
&self.layout_manager_selected,
|
|
&self.layout_manager_rename_buf,
|
|
current,
|
|
),
|
|
640,
|
|
320,
|
|
)
|
|
}
|
|
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(
|
|
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,
|
|
),
|
|
520,
|
|
360,
|
|
)
|
|
}
|
|
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(
|
|
crate::ui::style::anno_object_scale::view_window(&label, &scales),
|
|
360,
|
|
420,
|
|
)
|
|
}
|
|
super::super::ModalKind::Plotstyle => sized(
|
|
crate::ui::style::plotstyle::view_window(
|
|
self.active_plot_style.as_ref(),
|
|
self.plotstyle_panel_aci,
|
|
&self.ps_color_buf,
|
|
&self.ps_lineweight_buf,
|
|
&self.ps_screening_buf,
|
|
),
|
|
780,
|
|
540,
|
|
),
|
|
super::super::ModalKind::TextStyle => {
|
|
let tab = &self.tabs[self.active_tab];
|
|
let styles: Vec<String> = tab
|
|
.scene
|
|
.document
|
|
.text_styles
|
|
.iter()
|
|
.map(|s| s.name.clone())
|
|
.collect();
|
|
let (backward, upside_down, annotative) = tab
|
|
.scene
|
|
.document
|
|
.text_styles
|
|
.get(&self.textstyle_selected)
|
|
.map(|s| (s.flags.backward, s.flags.upside_down, s.annotative))
|
|
.unwrap_or((false, false, false));
|
|
sized(
|
|
crate::ui::style::textstyle::view_window(crate::ui::style::textstyle::TextStyleView {
|
|
styles,
|
|
selected: &self.textstyle_selected,
|
|
current: &tab.scene.document.header.current_text_style_name,
|
|
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,
|
|
annotative,
|
|
rename_active: self.style_rename.as_deref(),
|
|
rename_buf: &self.style_rename_buf,
|
|
}),
|
|
// Wider than the old 620 window: the TTF system-font panel
|
|
// (Plan B / web fonts) added a column.
|
|
860,
|
|
480,
|
|
)
|
|
}
|
|
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,
|
|
});
|
|
sized(
|
|
crate::ui::style::mlstyle::view_window(
|
|
styles,
|
|
&self.mlstyle_selected,
|
|
selected_style,
|
|
tab.scene.document.header.multiline_style.clone(),
|
|
self.style_rename.as_deref(),
|
|
&self.style_rename_buf,
|
|
),
|
|
620,
|
|
420,
|
|
)
|
|
}
|
|
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,
|
|
});
|
|
sized(
|
|
crate::ui::style::tablestyle::view_window(
|
|
styles,
|
|
&self.tablestyle_selected,
|
|
&self.ribbon.active_table_style,
|
|
selected_style,
|
|
&self.ts_hmargin,
|
|
&self.ts_vmargin,
|
|
&self.ts_description,
|
|
&self.ts_cell_textstyle,
|
|
&self.ts_cell_height,
|
|
&self.ts_cell_textcolor,
|
|
&self.ts_cell_fillcolor,
|
|
&self.ts_cell_datatype,
|
|
&self.ts_cell_unittype,
|
|
&self.ts_cell_format,
|
|
&self.ts_border_lw,
|
|
&self.ts_border_color,
|
|
&self.ts_border_spacing,
|
|
self.style_rename.as_deref(),
|
|
&self.style_rename_buf,
|
|
self.ts_color_open,
|
|
),
|
|
620,
|
|
420,
|
|
)
|
|
}
|
|
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 lt_opts: Vec<String> = vec!["None".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(|| "None".to_string()),
|
|
None => "None".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),
|
|
opt_block(s.arrowhead_handle),
|
|
opt_ts(s.text_style_handle),
|
|
opt_block(s.block_content_handle),
|
|
),
|
|
None => Default::default(),
|
|
};
|
|
sized(
|
|
crate::ui::style::mleaderstyle::view_window(crate::ui::style::mleaderstyle::MLeaderStyleView {
|
|
styles,
|
|
selected: &self.mleaderstyle_selected,
|
|
style: selected_style,
|
|
current: tab.active_mleader_style.clone(),
|
|
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,
|
|
line_weight: &self.mls_line_weight,
|
|
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,
|
|
lt_opts,
|
|
textstyle_opts,
|
|
line_type_name,
|
|
arrowhead_name,
|
|
text_style_name,
|
|
block_content_name,
|
|
rename_active: self.style_rename.as_deref(),
|
|
rename_buf: &self.style_rename_buf,
|
|
color_open: self.mls_color_open,
|
|
}),
|
|
560,
|
|
560,
|
|
)
|
|
}
|
|
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().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 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 (
|
|
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(crate::ui::style::dimstyle::view_window(
|
|
styles,
|
|
&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,
|
|
dimfit: &self.ds_dimfit,
|
|
dimdsep: &self.ds_dimdsep,
|
|
dimrnd: &self.ds_dimrnd,
|
|
dimzin: &self.ds_dimzin,
|
|
dimfrac: &self.ds_dimfrac,
|
|
dimaunit: &self.ds_dimaunit,
|
|
dimadec: &self.ds_dimadec,
|
|
dimunit: &self.ds_dimunit,
|
|
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,
|
|
dimblk1_name,
|
|
dimblk2_name,
|
|
dimldrblk_name,
|
|
dimltex_name,
|
|
dimltex1_name,
|
|
dimltex2_name,
|
|
block_opts,
|
|
lt_opts,
|
|
color_open: self.ds_color_open.clone(),
|
|
},
|
|
self.style_rename.as_deref(),
|
|
&self.style_rename_buf,
|
|
), 720, 560)
|
|
}
|
|
super::super::ModalKind::AssocPrompt => sized(default_assoc_dialog_window(), 440, 210),
|
|
super::super::ModalKind::AecDropWarning => {
|
|
let src_label = self
|
|
.tabs
|
|
.get(self.active_tab)
|
|
.map(|t| crate::io::format_for_version(t.scene.document.version, false))
|
|
.unwrap_or_else(|| "DWG".to_string());
|
|
sized(
|
|
aec_drop_dialog_window(
|
|
self.aec_drop_count,
|
|
&self.save_dialog_format,
|
|
&src_label,
|
|
),
|
|
480,
|
|
230,
|
|
)
|
|
}
|
|
super::super::ModalKind::LayerDeleteWarning => {
|
|
let (names, count) = self
|
|
.layer_delete_pending
|
|
.clone()
|
|
.unwrap_or_else(|| (Vec::new(), 0));
|
|
sized(layer_delete_warning_window(&names, count), 440, 200)
|
|
}
|
|
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(),
|
|
};
|
|
sized(unsaved_changes_dialog_window(&tab_name), 420, 160)
|
|
}
|
|
super::super::ModalKind::PointStyle => sized(
|
|
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,
|
|
),
|
|
360,
|
|
470,
|
|
),
|
|
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(
|
|
crate::ui::window::attribute_editor::view_window(
|
|
&self.attr_editor_block,
|
|
&self.attr_editor_rows,
|
|
self.attr_editor_selected,
|
|
self.attr_editor_tab,
|
|
layers,
|
|
linetypes,
|
|
styles,
|
|
),
|
|
640,
|
|
500,
|
|
)
|
|
}
|
|
super::super::ModalKind::SaveDialog => {
|
|
// Native omits the file-name field (the OS dialog collects it),
|
|
// so it stands shorter than the web build.
|
|
#[cfg(target_arch = "wasm32")]
|
|
let h = 200;
|
|
#[cfg(not(target_arch = "wasm32"))]
|
|
let h = 150;
|
|
sized(
|
|
save_as_dialog_window(&self.save_dialog_filename, &self.save_dialog_format),
|
|
420,
|
|
h,
|
|
)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
const SAVE_FORMAT_OPTIONS: &[&str] = &[
|
|
"DWG 2018", "DWG 2013", "DWG 2010", "DWG 2007", "DWG 2004", "DWG 2000", "DWG R14", "DXF 2018",
|
|
"DXF 2013", "DXF 2010", "DXF 2007", "DXF 2004", "DXF 2000", "DXF R14",
|
|
];
|
|
|
|
/// 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) -> Element<'a, Message> {
|
|
const BG: Color = Color {
|
|
r: 0.15,
|
|
g: 0.15,
|
|
b: 0.17,
|
|
a: 1.0,
|
|
};
|
|
const BORDER: Color = Color {
|
|
r: 0.32,
|
|
g: 0.32,
|
|
b: 0.36,
|
|
a: 1.0,
|
|
};
|
|
const TEXT: Color = Color {
|
|
r: 0.90,
|
|
g: 0.90,
|
|
b: 0.90,
|
|
a: 1.0,
|
|
};
|
|
const DIM: Color = Color {
|
|
r: 0.58,
|
|
g: 0.58,
|
|
b: 0.62,
|
|
a: 1.0,
|
|
};
|
|
const BTN_OK: Color = Color {
|
|
r: 0.20,
|
|
g: 0.46,
|
|
b: 0.80,
|
|
a: 1.0,
|
|
};
|
|
const BTN_HOV: Color = Color {
|
|
r: 0.26,
|
|
g: 0.55,
|
|
b: 0.92,
|
|
a: 1.0,
|
|
};
|
|
const BTN_GREY: Color = Color {
|
|
r: 0.26,
|
|
g: 0.26,
|
|
b: 0.29,
|
|
a: 1.0,
|
|
};
|
|
const BTN_GHOV: Color = Color {
|
|
r: 0.34,
|
|
g: 0.34,
|
|
b: 0.38,
|
|
a: 1.0,
|
|
};
|
|
|
|
let btn = |lbl: &'static str, msg: Message, base: Color, hov: Color| {
|
|
button(text(lbl).size(12).color(TEXT))
|
|
.on_press(msg)
|
|
.style(move |_: &Theme, st| button::Style {
|
|
background: Some(Background::Color(
|
|
if matches!(st, button::Status::Hovered | button::Status::Pressed) {
|
|
hov
|
|
} else {
|
|
base
|
|
},
|
|
)),
|
|
text_color: TEXT,
|
|
border: Border {
|
|
color: BORDER,
|
|
width: 1.0,
|
|
radius: 4.0.into(),
|
|
},
|
|
..Default::default()
|
|
})
|
|
.padding([4, 12])
|
|
};
|
|
|
|
let sel_fmt = SAVE_FORMAT_OPTIONS.iter().copied().find(|&s| s == format);
|
|
let label = |s: &'static str| text(s).size(11).color(DIM);
|
|
|
|
let mut items: Vec<Element<'a, Message>> = Vec::new();
|
|
items.push(text("Save Drawing As").size(14).color(TEXT).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")]
|
|
{
|
|
const INPUT_BG: Color = Color {
|
|
r: 0.10,
|
|
g: 0.10,
|
|
b: 0.12,
|
|
a: 1.0,
|
|
};
|
|
let input_sty =
|
|
|_: &Theme, _: iced::widget::text_input::Status| iced::widget::text_input::Style {
|
|
background: Background::Color(INPUT_BG),
|
|
border: Border {
|
|
color: BORDER,
|
|
width: 1.0,
|
|
radius: 4.0.into(),
|
|
},
|
|
icon: TEXT,
|
|
placeholder: DIM,
|
|
value: TEXT,
|
|
selection: Color {
|
|
r: 0.20,
|
|
g: 0.46,
|
|
b: 0.80,
|
|
a: 0.45,
|
|
},
|
|
};
|
|
items.push(
|
|
row![
|
|
label("File name:").width(70),
|
|
iced::widget::text_input("drawing.dwg", filename)
|
|
.on_input(Message::SaveDialogFilenameChanged)
|
|
.style(input_sty)
|
|
.size(13)
|
|
.padding([5, 8])
|
|
.width(Fill),
|
|
]
|
|
.align_y(iced::Alignment::Center)
|
|
.spacing(6)
|
|
.into(),
|
|
);
|
|
items.push(Space::new().height(8).into());
|
|
}
|
|
#[cfg(not(target_arch = "wasm32"))]
|
|
let _ = filename;
|
|
|
|
items.push(
|
|
row![
|
|
label("Format:").width(70),
|
|
pick_list(SAVE_FORMAT_OPTIONS, sel_fmt, |s: &str| {
|
|
Message::SaveDialogFormatChanged(s.to_string())
|
|
})
|
|
.width(Fill),
|
|
]
|
|
.align_y(iced::Alignment::Center)
|
|
.spacing(6)
|
|
.into(),
|
|
);
|
|
items.push(Space::new().height(16).into());
|
|
items.push(
|
|
row![
|
|
Space::new().width(Fill),
|
|
btn("Continue", Message::SaveDialogConfirm, BTN_OK, BTN_HOV),
|
|
Space::new().width(8),
|
|
btn("Cancel", Message::SaveDialogCancel, BTN_GREY, BTN_GHOV),
|
|
]
|
|
.into(),
|
|
);
|
|
|
|
let body = column(items).spacing(0);
|
|
|
|
container(body)
|
|
.style(|_: &Theme| container::Style {
|
|
background: Some(Background::Color(BG)),
|
|
..Default::default()
|
|
})
|
|
.padding([14, 16])
|
|
.width(Fill)
|
|
.height(Fill)
|
|
.into()
|
|
}
|
|
|
|
fn unsaved_changes_dialog_window(name: &str) -> Element<'static, Message> {
|
|
const BG: Color = Color {
|
|
r: 0.18,
|
|
g: 0.18,
|
|
b: 0.20,
|
|
a: 1.0,
|
|
};
|
|
const BORDER_COL: Color = Color {
|
|
r: 0.38,
|
|
g: 0.38,
|
|
b: 0.42,
|
|
a: 1.0,
|
|
};
|
|
const TEXT_COL: Color = Color {
|
|
r: 0.90,
|
|
g: 0.90,
|
|
b: 0.90,
|
|
a: 1.0,
|
|
};
|
|
const BTN_SAVE: Color = Color {
|
|
r: 0.20,
|
|
g: 0.46,
|
|
b: 0.80,
|
|
a: 1.0,
|
|
};
|
|
const BTN_HOVER: Color = Color {
|
|
r: 0.26,
|
|
g: 0.55,
|
|
b: 0.92,
|
|
a: 1.0,
|
|
};
|
|
const BTN_DISC: Color = Color {
|
|
r: 0.28,
|
|
g: 0.28,
|
|
b: 0.30,
|
|
a: 1.0,
|
|
};
|
|
const BTN_DHOV: Color = Color {
|
|
r: 0.36,
|
|
g: 0.36,
|
|
b: 0.40,
|
|
a: 1.0,
|
|
};
|
|
|
|
let body_text = format!("Do you want to save changes to \"{}\"?", name);
|
|
|
|
let btn = |label: &'static str, msg: Message, base: Color, hov: Color| {
|
|
button(text(label).size(13).color(TEXT_COL))
|
|
.on_press(msg)
|
|
.style(move |_: &Theme, status| button::Style {
|
|
background: Some(Background::Color(match status {
|
|
button::Status::Hovered | button::Status::Pressed => hov,
|
|
_ => base,
|
|
})),
|
|
text_color: TEXT_COL,
|
|
border: Border {
|
|
color: BORDER_COL,
|
|
width: 1.0,
|
|
radius: 4.0.into(),
|
|
},
|
|
shadow: iced::Shadow::default(),
|
|
snap: false,
|
|
})
|
|
.padding([6, 18])
|
|
};
|
|
|
|
container(
|
|
column![
|
|
text(body_text).size(13).color(TEXT_COL),
|
|
iced::widget::Space::new().height(20),
|
|
row![
|
|
btn("Save", Message::UnsavedDialogSave, BTN_SAVE, BTN_HOVER),
|
|
iced::widget::Space::new().width(8),
|
|
btn("Discard", Message::UnsavedDialogDiscard, BTN_DISC, BTN_DHOV),
|
|
iced::widget::Space::new().width(8),
|
|
btn("Cancel", Message::UnsavedDialogCancel, BTN_DISC, BTN_DHOV),
|
|
],
|
|
]
|
|
.spacing(0),
|
|
)
|
|
.style(move |_: &Theme| container::Style {
|
|
background: Some(Background::Color(BG)),
|
|
..Default::default()
|
|
})
|
|
.center(Fill)
|
|
.padding([24, 28])
|
|
.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) -> Element<'static, Message> {
|
|
const BG: Color = Color { r: 0.18, g: 0.18, b: 0.20, a: 1.0 };
|
|
const BORDER_COL: Color = Color { r: 0.38, g: 0.38, b: 0.42, a: 1.0 };
|
|
const TEXT_COL: Color = Color { r: 0.90, g: 0.90, b: 0.90, a: 1.0 };
|
|
const BTN_SAVE: Color = Color { r: 0.20, g: 0.46, b: 0.80, a: 1.0 };
|
|
const BTN_HOVER: Color = Color { r: 0.26, g: 0.55, b: 0.92, a: 1.0 };
|
|
const BTN_DISC: Color = Color { r: 0.28, g: 0.28, b: 0.30, a: 1.0 };
|
|
const BTN_DHOV: Color = Color { r: 0.36, g: 0.36, b: 0.40, a: 1.0 };
|
|
|
|
let body_text = format!(
|
|
"This drawing contains {count} AEC/Civil objects that \"{target}\" \
|
|
cannot store, so they will not be saved.\n\n\
|
|
To keep them, save in the source version ({src_version})."
|
|
);
|
|
|
|
let btn = |label: &'static str, msg: Message, base: Color, hov: Color| {
|
|
button(text(label).size(13).color(TEXT_COL))
|
|
.on_press(msg)
|
|
.style(move |_: &Theme, status| button::Style {
|
|
background: Some(Background::Color(match status {
|
|
button::Status::Hovered | button::Status::Pressed => hov,
|
|
_ => base,
|
|
})),
|
|
text_color: TEXT_COL,
|
|
border: Border {
|
|
color: BORDER_COL,
|
|
width: 1.0,
|
|
radius: 4.0.into(),
|
|
},
|
|
shadow: iced::Shadow::default(),
|
|
snap: false,
|
|
})
|
|
.padding([6, 14])
|
|
};
|
|
|
|
container(
|
|
column![
|
|
text(body_text).size(13).color(TEXT_COL),
|
|
iced::widget::Space::new().height(20),
|
|
row![
|
|
btn("Save in source version", Message::AecDropSameVersion, BTN_SAVE, BTN_HOVER),
|
|
iced::widget::Space::new().width(8),
|
|
btn("Save anyway", Message::AecDropProceed, BTN_DISC, BTN_DHOV),
|
|
iced::widget::Space::new().width(8),
|
|
btn("Back", Message::AecDropBack, BTN_DISC, BTN_DHOV),
|
|
],
|
|
]
|
|
.spacing(0),
|
|
)
|
|
.style(move |_: &Theme| container::Style {
|
|
background: Some(Background::Color(BG)),
|
|
..Default::default()
|
|
})
|
|
.center(Fill)
|
|
.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) -> Element<'static, Message> {
|
|
const BG: Color = Color { r: 0.18, g: 0.18, b: 0.20, a: 1.0 };
|
|
const BORDER_COL: Color = Color { r: 0.38, g: 0.38, b: 0.42, a: 1.0 };
|
|
const TEXT_COL: Color = Color { r: 0.90, g: 0.90, b: 0.90, a: 1.0 };
|
|
const BTN_DEL: Color = Color { r: 0.72, g: 0.26, b: 0.24, a: 1.0 };
|
|
const BTN_DEL_HOV: Color = Color { r: 0.84, g: 0.32, b: 0.30, a: 1.0 };
|
|
const BTN_CANCEL: Color = Color { r: 0.28, g: 0.28, b: 0.30, a: 1.0 };
|
|
const BTN_CANCEL_HOV: Color = Color { r: 0.36, g: 0.36, b: 0.40, a: 1.0 };
|
|
|
|
let obj = if count == 1 { "object" } else { "objects" };
|
|
let subject = if names.len() == 1 {
|
|
format!("Layer \"{}\"", names[0])
|
|
} else {
|
|
format!("{} selected layers", names.len())
|
|
};
|
|
let body_text = format!(
|
|
"{subject} still {} {count} {obj}.\n\nDeleting will also remove {} from the drawing. Continue?",
|
|
if names.len() == 1 { "has" } else { "hold" },
|
|
if count == 1 { "that object" } else { "those objects" }
|
|
);
|
|
|
|
let btn = |label: &'static str, msg: Message, base: Color, hov: Color| {
|
|
button(text(label).size(13).color(TEXT_COL))
|
|
.on_press(msg)
|
|
.style(move |_: &Theme, status| button::Style {
|
|
background: Some(Background::Color(match status {
|
|
button::Status::Hovered | button::Status::Pressed => hov,
|
|
_ => base,
|
|
})),
|
|
text_color: TEXT_COL,
|
|
border: Border { color: BORDER_COL, width: 1.0, radius: 4.0.into() },
|
|
shadow: iced::Shadow::default(),
|
|
snap: false,
|
|
})
|
|
.padding([6, 18])
|
|
};
|
|
|
|
container(
|
|
column![
|
|
text(body_text).size(13).color(TEXT_COL),
|
|
iced::widget::Space::new().height(20),
|
|
row![
|
|
btn("Delete Objects", Message::LayerDeleteConfirm, BTN_DEL, BTN_DEL_HOV),
|
|
iced::widget::Space::new().width(8),
|
|
btn("Cancel", Message::CloseModal, BTN_CANCEL, BTN_CANCEL_HOV),
|
|
],
|
|
]
|
|
.spacing(0),
|
|
)
|
|
.style(move |_: &Theme| container::Style {
|
|
background: Some(Background::Color(BG)),
|
|
..Default::default()
|
|
})
|
|
.center(Fill)
|
|
.padding([24, 28])
|
|
.into()
|
|
}
|
|
|
|
/// First-launch prompt offering to register Open CAD Studio as the default
|
|
/// handler for .dwg / .dxf. "Yes" runs the platform association call; "Not now"
|
|
/// just dismisses. Either answer flips the persisted `default_assoc_prompted`
|
|
/// flag so the dialog never reappears.
|
|
fn default_assoc_dialog_window() -> Element<'static, Message> {
|
|
const BG: Color = Color {
|
|
r: 0.18,
|
|
g: 0.18,
|
|
b: 0.20,
|
|
a: 1.0,
|
|
};
|
|
const BORDER_COL: Color = Color {
|
|
r: 0.38,
|
|
g: 0.38,
|
|
b: 0.42,
|
|
a: 1.0,
|
|
};
|
|
const TEXT_COL: Color = Color {
|
|
r: 0.90,
|
|
g: 0.90,
|
|
b: 0.90,
|
|
a: 1.0,
|
|
};
|
|
const DIM_COL: Color = Color {
|
|
r: 0.62,
|
|
g: 0.62,
|
|
b: 0.66,
|
|
a: 1.0,
|
|
};
|
|
const BTN_YES: Color = Color {
|
|
r: 0.20,
|
|
g: 0.46,
|
|
b: 0.80,
|
|
a: 1.0,
|
|
};
|
|
const BTN_YHOV: Color = Color {
|
|
r: 0.26,
|
|
g: 0.55,
|
|
b: 0.92,
|
|
a: 1.0,
|
|
};
|
|
const BTN_NO: Color = Color {
|
|
r: 0.28,
|
|
g: 0.28,
|
|
b: 0.30,
|
|
a: 1.0,
|
|
};
|
|
const BTN_NHOV: Color = Color {
|
|
r: 0.36,
|
|
g: 0.36,
|
|
b: 0.40,
|
|
a: 1.0,
|
|
};
|
|
|
|
let btn = |label: &'static str, msg: Message, base: Color, hov: Color| {
|
|
button(text(label).size(13).color(TEXT_COL))
|
|
.on_press(msg)
|
|
.style(move |_: &Theme, status| button::Style {
|
|
background: Some(Background::Color(match status {
|
|
button::Status::Hovered | button::Status::Pressed => hov,
|
|
_ => base,
|
|
})),
|
|
text_color: TEXT_COL,
|
|
border: Border {
|
|
color: BORDER_COL,
|
|
width: 1.0,
|
|
radius: 4.0.into(),
|
|
},
|
|
shadow: iced::Shadow::default(),
|
|
snap: false,
|
|
})
|
|
.padding([6, 18])
|
|
};
|
|
|
|
container(
|
|
column![
|
|
text("Make Open CAD Studio your default CAD app?")
|
|
.size(15)
|
|
.color(TEXT_COL),
|
|
iced::widget::Space::new().height(10),
|
|
text("Open .dwg and .dxf drawings in Open CAD Studio by default. You can change this later in your system settings.")
|
|
.size(12)
|
|
.color(DIM_COL),
|
|
iced::widget::Space::new().height(22),
|
|
row![
|
|
iced::widget::Space::new().width(Fill),
|
|
btn("Not now", Message::AssocPromptNo, BTN_NO, BTN_NHOV),
|
|
iced::widget::Space::new().width(8),
|
|
btn("Yes, set as default", Message::AssocPromptYes, BTN_YES, BTN_YHOV),
|
|
]
|
|
.align_y(iced::Center),
|
|
]
|
|
.spacing(0),
|
|
)
|
|
.style(move |_: &Theme| container::Style {
|
|
background: Some(Background::Color(BG)),
|
|
..Default::default()
|
|
})
|
|
.center(Fill)
|
|
.padding([24, 28])
|
|
.into()
|
|
}
|
|
|