mod.rs - icy_draw - icy_draw is the successor to mystic draw. fork / mirror
(HTM) git clone https://git.drkhsh.at/icy_draw.git
(DIR) Log
(DIR) Files
(DIR) Refs
(DIR) README
(DIR) LICENSE
---
mod.rs (9036B)
---
1 #![allow(clippy::needless_range_loop)]
2
3 use std::path::PathBuf;
4
5 use eframe::egui::{self, TextEdit, Ui};
6 use egui_file::FileDialog;
7 use egui_modal::Modal;
8 use i18n_embed_fl::fl;
9 use icy_engine::{BufferType, Rectangle, SaveOptions, TextPane};
10
11 use crate::{AnsiEditor, Message, ModalDialog, TerminalResult, SETTINGS};
12
13 mod ansi;
14 mod artworx;
15 mod ascii;
16 mod atascii;
17 mod avatar;
18 mod bin;
19 mod ice_draw;
20 mod pcboard;
21 mod png;
22 mod tundra_draw;
23 mod xbin;
24
25 pub struct ExportFileDialog {
26 pub should_commit: bool,
27 pub file_name: PathBuf,
28 folder_dialog: Option<FileDialog>,
29 format_type: i32,
30 buffer_type: BufferType,
31 }
32
33 impl ExportFileDialog {
34 pub fn new(buf: &icy_engine::Buffer) -> Self {
35 let file_name = match &buf.file_name {
36 Some(path) => {
37 let mut p = path.clone();
38 let desc: &[(&str, CreateSettingsFunction, &str)] = if matches!(buf.buffer_type, BufferType::Atascii) {
39 &ATASCII_TYPE_DESCRIPTIONS
40 } else {
41 &TYPE_DESCRIPTIONS
42 };
43 let format_type = get_format_type(buf.buffer_type, path) as usize;
44 let ext = desc[format_type].2;
45 p.set_extension(ext);
46 p
47 }
48 _ => PathBuf::from("Untitled.ans"),
49 };
50 let format_type = get_format_type(buf.buffer_type, &file_name);
51
52 ExportFileDialog {
53 should_commit: false,
54 file_name,
55 format_type,
56 folder_dialog: None,
57 buffer_type: buf.buffer_type,
58 } // self.file_name.set_extension(TYPE_DESCRIPTIONS[format_type].2);
59 }
60 }
61
62 fn get_format_type(buf: BufferType, path: &std::path::Path) -> i32 {
63 if let Some(ext) = path.extension() {
64 if let Some(ext) = ext.to_str() {
65 let ext = ext.to_lowercase();
66 let desc: &[(&str, CreateSettingsFunction, &str)] = if matches!(buf, BufferType::Atascii) {
67 &ATASCII_TYPE_DESCRIPTIONS
68 } else {
69 &TYPE_DESCRIPTIONS
70 };
71 for i in 0..desc.len() {
72 let td = desc[i];
73 if ext == td.2 {
74 return i as i32;
75 }
76 }
77 }
78 }
79 0
80 }
81
82 impl ModalDialog for ExportFileDialog {
83 fn show(&mut self, ctx: &egui::Context) -> bool {
84 if let Some(ed) = &mut self.folder_dialog {
85 if ed.show(ctx).selected() {
86 if let Some(res) = ed.path() {
87 self.file_name = res.to_path_buf();
88 }
89 self.folder_dialog = None
90 } else {
91 return false;
92 }
93 }
94
95 let mut result = false;
96
97 let modal = Modal::new(ctx, "export_file-dialog");
98 modal.show(|ui| {
99 ui.set_width(550.);
100 ui.set_height(320.);
101
102 modal.title(ui, fl!(crate::LANGUAGE_LOADER, "export-title"));
103
104 modal.frame(ui, |ui| {
105 let desc: &[(&str, CreateSettingsFunction, &str)] = if matches!(self.buffer_type, BufferType::Atascii) {
106 &ATASCII_TYPE_DESCRIPTIONS
107 } else {
108 &TYPE_DESCRIPTIONS
109 };
110
111 egui::Grid::new("paste_mode_grid")
112 .num_columns(2)
113 .spacing([4.0, 8.0])
114 .min_row_height(24.)
115 .show(ui, |ui| {
116 ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| {
117 ui.label(fl!(crate::LANGUAGE_LOADER, "export-file-label"));
118 });
119 ui.horizontal(|ui| {
120 let mut path_edit = self.file_name.to_string_lossy().to_string();
121 let response = ui.add(TextEdit::singleline(&mut path_edit).desired_width(450.));
122 if response.changed() {
123 self.file_name = path_edit.into();
124 let format_type = get_format_type(self.buffer_type, &self.file_name);
125 if format_type >= 0 {
126 self.format_type = format_type;
127 }
128 }
129 if ui.add(egui::Button::new("…").wrap(false)).clicked() {
130 let mut initial_path = None;
131 crate::set_default_initial_directory_opt(&mut initial_path);
132 let mut dialog = FileDialog::save_file(initial_path);
133 dialog.open();
134 self.folder_dialog = Some(dialog);
135
136 ui.close_menu();
137 }
138 });
139 ui.end_row();
140
141 ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| {
142 ui.label(fl!(crate::LANGUAGE_LOADER, "export-format-label"));
143 });
144 egui::ComboBox::from_id_source("format_combo")
145 .selected_text(desc[self.format_type as usize].0)
146 .width(190.)
147 .show_ui(ui, |ui| {
148 (0..desc.len()).for_each(|i| {
149 let td = desc[i];
150 if ui.selectable_value(&mut self.format_type, i as i32, td.0).clicked() {
151 self.file_name.set_extension(td.2);
152 }
153 });
154 });
155 ui.end_row();
156 });
157
158 ui.separator();
159
160 unsafe {
161 desc[self.format_type as usize].1(ui, &mut SETTINGS.save_options);
162 }
163 });
164
165 ui.add_space(ui.available_height() - 23.0);
166
167 modal.buttons(ui, |ui| {
168 if ui.button(fl!(crate::LANGUAGE_LOADER, "export-button-title")).clicked() {
169 self.should_commit = true;
170 result = true;
171 }
172 if ui.button(fl!(crate::LANGUAGE_LOADER, "new-file-cancel")).clicked() {
173 result = true;
174 }
175 });
176 });
177 modal.open();
178 result
179 }
180
181 fn should_commit(&self) -> bool {
182 self.should_commit
183 }
184
185 fn commit(&self, editor: &mut AnsiEditor) -> TerminalResult<Option<crate::Message>> {
186 if let Some(ext) = self.file_name.extension() {
187 if let Some(ext) = ext.to_str() {
188 let ext = ext.to_lowercase();
189 if ext == "png" {
190 let lock = &editor.buffer_view.lock();
191 let buf = lock.get_buffer();
192 let (size, pixels) = buf.render_to_rgba(Rectangle::from(0, 0, buf.get_width(), buf.get_height()));
193 let image_buffer = image::RgbaImage::from_raw(size.width as u32, size.height as u32, pixels);
194 match image_buffer {
195 Some(img) => {
196 if let Err(err) = img.save(&self.file_name) {
197 return Ok(Some(Message::ShowError(format!("Failed to save image: {}", err))));
198 }
199 }
200 None => {
201 return Ok(Some(Message::ShowError("Failed to save image".to_string())));
202 }
203 }
204
205 return Ok(None);
206 }
207 }
208 }
209 unsafe {
210 editor.save_content(self.file_name.as_path(), &SETTINGS.save_options)?;
211 }
212 Ok(None)
213 }
214 }
215
216 type CreateSettingsFunction = fn(&mut Ui, &mut SaveOptions);
217
218 const TYPE_DESCRIPTIONS: [(&str, CreateSettingsFunction, &str); 12] = [
219 ("Ansi (.ans)", ansi::create_settings_page, "ans"),
220 ("Avatar (.avt)", avatar::create_settings_page, "avt"),
221 ("PCBoard (.pcb)", pcboard::create_settings_page, "pcb"),
222 ("Ascii (.asc)", ascii::create_settings_page, "asc"),
223 ("Artworx (.adf)", artworx::create_settings_page, "adf"),
224 ("Ice Draw (.idf)", ice_draw::create_settings_page, "idf"),
225 ("Tundra Draw (.tnd)", tundra_draw::create_settings_page, "tnd"),
226 ("Bin (.bin)", bin::create_settings_page, "bin"),
227 ("XBin (.xb)", xbin::create_settings_page, "xb"),
228 ("CtrlA (.msg)", pcboard::create_settings_page, "msg"),
229 ("Renegade (.an1)", pcboard::create_settings_page, "an1"),
230 ("PNG (.png)", png::create_settings_page, "png"),
231 ];
232
233 const ATASCII_TYPE_DESCRIPTIONS: [(&str, CreateSettingsFunction, &str); 3] = [
234 ("Atascii (.ata)", atascii::create_settings_page, "ata"),
235 ("XBin (.xb)", xbin::create_settings_page, "xb"),
236 ("PNG (.png)", png::create_settings_page, "png"),
237 ];