Split export dialog into simple/advanced
This commit is contained in:
parent
e03d12009f
commit
93a29192fd
|
|
@ -41,35 +41,61 @@ pub struct ExportDialog {
|
||||||
/// Output file path
|
/// Output file path
|
||||||
pub output_path: Option<PathBuf>,
|
pub output_path: Option<PathBuf>,
|
||||||
|
|
||||||
/// Selected audio preset index (for UI)
|
|
||||||
pub selected_audio_preset: usize,
|
|
||||||
|
|
||||||
/// Error message (if any)
|
/// Error message (if any)
|
||||||
pub error_message: Option<String>,
|
pub error_message: Option<String>,
|
||||||
|
|
||||||
|
/// Whether advanced settings are shown
|
||||||
|
pub show_advanced: bool,
|
||||||
|
|
||||||
|
/// Selected video preset index
|
||||||
|
pub selected_video_preset: usize,
|
||||||
|
|
||||||
|
/// Output filename (editable text, without directory)
|
||||||
|
pub output_filename: String,
|
||||||
|
|
||||||
|
/// Output directory
|
||||||
|
pub output_dir: PathBuf,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for ExportDialog {
|
impl Default for ExportDialog {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
|
let home = std::env::var("HOME")
|
||||||
|
.map(PathBuf::from)
|
||||||
|
.unwrap_or_else(|_| PathBuf::from("."));
|
||||||
|
let music_dir = {
|
||||||
|
let m = home.join("Music");
|
||||||
|
if m.is_dir() { m } else { home }
|
||||||
|
};
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
open: false,
|
open: false,
|
||||||
export_type: ExportType::Audio,
|
export_type: ExportType::Audio,
|
||||||
audio_settings: AudioExportSettings::default(),
|
audio_settings: AudioExportSettings::standard_mp3(),
|
||||||
video_settings: VideoExportSettings::default(),
|
video_settings: VideoExportSettings::default(),
|
||||||
include_audio: true,
|
include_audio: true,
|
||||||
output_path: None,
|
output_path: None,
|
||||||
selected_audio_preset: 0,
|
|
||||||
error_message: None,
|
error_message: None,
|
||||||
|
show_advanced: false,
|
||||||
|
selected_video_preset: 0,
|
||||||
|
output_filename: String::new(),
|
||||||
|
output_dir: music_dir,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ExportDialog {
|
impl ExportDialog {
|
||||||
/// Open the dialog with default settings
|
/// Open the dialog with default settings
|
||||||
pub fn open(&mut self, timeline_duration: f64) {
|
pub fn open(&mut self, timeline_duration: f64, project_name: &str) {
|
||||||
self.open = true;
|
self.open = true;
|
||||||
self.audio_settings.end_time = timeline_duration;
|
self.audio_settings.end_time = timeline_duration;
|
||||||
self.video_settings.end_time = timeline_duration;
|
self.video_settings.end_time = timeline_duration;
|
||||||
self.error_message = None;
|
self.error_message = None;
|
||||||
|
|
||||||
|
// Pre-populate filename from project name if not already set
|
||||||
|
if self.output_filename.is_empty() || !self.output_filename.contains(project_name) {
|
||||||
|
let ext = self.audio_settings.format.extension();
|
||||||
|
self.output_filename = format!("{}.{}", project_name, ext);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Close the dialog
|
/// Close the dialog
|
||||||
|
|
@ -78,6 +104,27 @@ impl ExportDialog {
|
||||||
self.error_message = None;
|
self.error_message = None;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Update the filename extension to match the current format
|
||||||
|
fn update_filename_extension(&mut self) {
|
||||||
|
let ext = match self.export_type {
|
||||||
|
ExportType::Audio => self.audio_settings.format.extension(),
|
||||||
|
ExportType::Video => self.video_settings.codec.container_format(),
|
||||||
|
};
|
||||||
|
// Replace extension in filename
|
||||||
|
if let Some(dot_pos) = self.output_filename.rfind('.') {
|
||||||
|
self.output_filename.truncate(dot_pos + 1);
|
||||||
|
self.output_filename.push_str(ext);
|
||||||
|
} else if !self.output_filename.is_empty() {
|
||||||
|
self.output_filename.push('.');
|
||||||
|
self.output_filename.push_str(ext);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Build the full output path from directory + filename
|
||||||
|
fn build_output_path(&self) -> PathBuf {
|
||||||
|
self.output_dir.join(&self.output_filename)
|
||||||
|
}
|
||||||
|
|
||||||
/// Render the export dialog
|
/// Render the export dialog
|
||||||
///
|
///
|
||||||
/// Returns Some(ExportResult) if the user clicked Export, None otherwise.
|
/// Returns Some(ExportResult) if the user clicked Export, None otherwise.
|
||||||
|
|
@ -109,30 +156,42 @@ impl ExportDialog {
|
||||||
|
|
||||||
// Export type selection (tabs)
|
// Export type selection (tabs)
|
||||||
ui.horizontal(|ui| {
|
ui.horizontal(|ui| {
|
||||||
ui.selectable_value(&mut self.export_type, ExportType::Audio, "🎵 Audio");
|
if ui.selectable_value(&mut self.export_type, ExportType::Audio, "Audio").clicked() {
|
||||||
ui.selectable_value(&mut self.export_type, ExportType::Video, "🎬 Video");
|
self.update_filename_extension();
|
||||||
|
}
|
||||||
|
if ui.selectable_value(&mut self.export_type, ExportType::Video, "Video").clicked() {
|
||||||
|
self.update_filename_extension();
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
ui.add_space(12.0);
|
ui.add_space(12.0);
|
||||||
ui.separator();
|
ui.separator();
|
||||||
ui.add_space(12.0);
|
ui.add_space(12.0);
|
||||||
|
|
||||||
// Render either audio or video settings
|
// Basic settings
|
||||||
match self.export_type {
|
match self.export_type {
|
||||||
ExportType::Audio => self.render_audio_settings(ui),
|
ExportType::Audio => self.render_audio_basic(ui),
|
||||||
ExportType::Video => self.render_video_settings(ui),
|
ExportType::Video => self.render_video_basic(ui),
|
||||||
}
|
}
|
||||||
|
|
||||||
ui.add_space(12.0);
|
ui.add_space(12.0);
|
||||||
|
|
||||||
// Time range (common to both)
|
// Output file
|
||||||
self.render_time_range(ui);
|
|
||||||
|
|
||||||
ui.add_space(12.0);
|
|
||||||
|
|
||||||
// Output file path (common to both)
|
|
||||||
self.render_output_selection(ui);
|
self.render_output_selection(ui);
|
||||||
|
|
||||||
|
ui.add_space(4.0);
|
||||||
|
|
||||||
|
// Advanced toggle
|
||||||
|
ui.toggle_value(&mut self.show_advanced, "Advanced settings");
|
||||||
|
|
||||||
|
if self.show_advanced {
|
||||||
|
ui.add_space(8.0);
|
||||||
|
match self.export_type {
|
||||||
|
ExportType::Audio => self.render_audio_advanced(ui),
|
||||||
|
ExportType::Video => self.render_video_advanced(ui),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
ui.add_space(16.0);
|
ui.add_space(16.0);
|
||||||
|
|
||||||
// Buttons
|
// Buttons
|
||||||
|
|
@ -160,66 +219,49 @@ impl ExportDialog {
|
||||||
}
|
}
|
||||||
|
|
||||||
if should_export {
|
if should_export {
|
||||||
|
self.output_path = Some(self.build_output_path());
|
||||||
return self.handle_export();
|
return self.handle_export();
|
||||||
}
|
}
|
||||||
|
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Render audio export settings UI
|
/// Render basic audio settings (format + filename)
|
||||||
fn render_audio_settings(&mut self, ui: &mut egui::Ui) {
|
fn render_audio_basic(&mut self, ui: &mut egui::Ui) {
|
||||||
// Preset selection
|
|
||||||
ui.heading("Preset");
|
|
||||||
ui.horizontal(|ui| {
|
|
||||||
let presets = [
|
|
||||||
("High Quality WAV", AudioExportSettings::high_quality_wav()),
|
|
||||||
("High Quality FLAC", AudioExportSettings::high_quality_flac()),
|
|
||||||
("Standard MP3", AudioExportSettings::standard_mp3()),
|
|
||||||
("Standard AAC", AudioExportSettings::standard_aac()),
|
|
||||||
("High Quality MP3", AudioExportSettings::high_quality_mp3()),
|
|
||||||
("High Quality AAC", AudioExportSettings::high_quality_aac()),
|
|
||||||
("Podcast MP3", AudioExportSettings::podcast_mp3()),
|
|
||||||
("Podcast AAC", AudioExportSettings::podcast_aac()),
|
|
||||||
];
|
|
||||||
|
|
||||||
egui::ComboBox::from_id_salt("export_preset")
|
|
||||||
.selected_text(presets[self.selected_audio_preset].0)
|
|
||||||
.show_ui(ui, |ui| {
|
|
||||||
for (i, (name, _)) in presets.iter().enumerate() {
|
|
||||||
if ui.selectable_value(&mut self.selected_audio_preset, i, *name).clicked() {
|
|
||||||
// Save current time range before applying preset
|
|
||||||
let saved_start = self.audio_settings.start_time;
|
|
||||||
let saved_end = self.audio_settings.end_time;
|
|
||||||
self.audio_settings = presets[i].1.clone();
|
|
||||||
// Restore time range
|
|
||||||
self.audio_settings.start_time = saved_start;
|
|
||||||
self.audio_settings.end_time = saved_end;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
ui.add_space(12.0);
|
|
||||||
|
|
||||||
ui.add_space(12.0);
|
|
||||||
|
|
||||||
// Format settings
|
|
||||||
ui.heading("Format");
|
|
||||||
ui.horizontal(|ui| {
|
ui.horizontal(|ui| {
|
||||||
ui.label("Format:");
|
ui.label("Format:");
|
||||||
|
let prev_format = self.audio_settings.format;
|
||||||
egui::ComboBox::from_id_salt("audio_format")
|
egui::ComboBox::from_id_salt("audio_format")
|
||||||
.selected_text(self.audio_settings.format.name())
|
.selected_text(self.audio_settings.format.name())
|
||||||
.show_ui(ui, |ui| {
|
.show_ui(ui, |ui| {
|
||||||
ui.selectable_value(&mut self.audio_settings.format, AudioFormat::Wav, "WAV (Uncompressed)");
|
|
||||||
ui.selectable_value(&mut self.audio_settings.format, AudioFormat::Flac, "FLAC (Lossless)");
|
|
||||||
ui.selectable_value(&mut self.audio_settings.format, AudioFormat::Mp3, "MP3");
|
ui.selectable_value(&mut self.audio_settings.format, AudioFormat::Mp3, "MP3");
|
||||||
ui.selectable_value(&mut self.audio_settings.format, AudioFormat::Aac, "AAC");
|
ui.selectable_value(&mut self.audio_settings.format, AudioFormat::Aac, "AAC");
|
||||||
|
ui.selectable_value(&mut self.audio_settings.format, AudioFormat::Flac, "FLAC (Lossless)");
|
||||||
|
ui.selectable_value(&mut self.audio_settings.format, AudioFormat::Wav, "WAV (Uncompressed)");
|
||||||
});
|
});
|
||||||
|
if self.audio_settings.format != prev_format {
|
||||||
|
self.update_filename_extension();
|
||||||
|
// Apply sensible defaults when switching formats
|
||||||
|
match self.audio_settings.format {
|
||||||
|
AudioFormat::Mp3 => {
|
||||||
|
self.audio_settings.sample_rate = 44100;
|
||||||
|
self.audio_settings.bitrate_kbps = 192;
|
||||||
|
}
|
||||||
|
AudioFormat::Aac => {
|
||||||
|
self.audio_settings.sample_rate = 44100;
|
||||||
|
self.audio_settings.bitrate_kbps = 256;
|
||||||
|
}
|
||||||
|
AudioFormat::Flac | AudioFormat::Wav => {
|
||||||
|
self.audio_settings.sample_rate = 48000;
|
||||||
|
self.audio_settings.bit_depth = 24;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
|
||||||
ui.add_space(8.0);
|
/// Render advanced audio settings (sample rate, channels, bit depth, bitrate, time range)
|
||||||
|
fn render_audio_advanced(&mut self, ui: &mut egui::Ui) {
|
||||||
// Audio settings
|
|
||||||
ui.horizontal(|ui| {
|
ui.horizontal(|ui| {
|
||||||
ui.label("Sample Rate:");
|
ui.label("Sample Rate:");
|
||||||
egui::ComboBox::from_id_salt("sample_rate")
|
egui::ComboBox::from_id_salt("sample_rate")
|
||||||
|
|
@ -237,8 +279,6 @@ impl ExportDialog {
|
||||||
ui.radio_value(&mut self.audio_settings.channels, 2, "Stereo");
|
ui.radio_value(&mut self.audio_settings.channels, 2, "Stereo");
|
||||||
});
|
});
|
||||||
|
|
||||||
ui.add_space(8.0);
|
|
||||||
|
|
||||||
// Format-specific settings
|
// Format-specific settings
|
||||||
if self.audio_settings.format.supports_bit_depth() {
|
if self.audio_settings.format.supports_bit_depth() {
|
||||||
ui.horizontal(|ui| {
|
ui.horizontal(|ui| {
|
||||||
|
|
@ -261,12 +301,48 @@ impl ExportDialog {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ui.add_space(8.0);
|
||||||
|
|
||||||
|
// Time range
|
||||||
|
self.render_time_range(ui);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Render video export settings UI
|
/// Video presets: (name, codec, quality, width, height, fps)
|
||||||
fn render_video_settings(&mut self, ui: &mut egui::Ui) {
|
const VIDEO_PRESETS: &'static [(&'static str, VideoCodec, VideoQuality, u32, u32, f64)] = &[
|
||||||
// Codec selection
|
("1080p H.264 (Standard)", VideoCodec::H264, VideoQuality::High, 1920, 1080, 30.0),
|
||||||
ui.heading("Codec");
|
("1080p H.264 60fps", VideoCodec::H264, VideoQuality::High, 1920, 1080, 60.0),
|
||||||
|
("4K H.264", VideoCodec::H264, VideoQuality::VeryHigh, 3840, 2160, 30.0),
|
||||||
|
("720p H.264 (Small)", VideoCodec::H264, VideoQuality::Medium, 1280, 720, 30.0),
|
||||||
|
("1080p H.265 (Smaller)", VideoCodec::H265, VideoQuality::High, 1920, 1080, 30.0),
|
||||||
|
("1080p VP9 (WebM)", VideoCodec::VP9, VideoQuality::High, 1920, 1080, 30.0),
|
||||||
|
("1080p ProRes 422", VideoCodec::ProRes422, VideoQuality::VeryHigh, 1920, 1080, 30.0),
|
||||||
|
];
|
||||||
|
|
||||||
|
/// Render basic video settings (preset dropdown)
|
||||||
|
fn render_video_basic(&mut self, ui: &mut egui::Ui) {
|
||||||
|
ui.horizontal(|ui| {
|
||||||
|
ui.label("Preset:");
|
||||||
|
egui::ComboBox::from_id_salt("video_preset")
|
||||||
|
.selected_text(Self::VIDEO_PRESETS[self.selected_video_preset].0)
|
||||||
|
.show_ui(ui, |ui| {
|
||||||
|
for (i, preset) in Self::VIDEO_PRESETS.iter().enumerate() {
|
||||||
|
if ui.selectable_value(&mut self.selected_video_preset, i, preset.0).clicked() {
|
||||||
|
let (_, codec, quality, w, h, fps) = *preset;
|
||||||
|
self.video_settings.codec = codec;
|
||||||
|
self.video_settings.quality = quality;
|
||||||
|
self.video_settings.width = Some(w);
|
||||||
|
self.video_settings.height = Some(h);
|
||||||
|
self.video_settings.framerate = fps;
|
||||||
|
self.update_filename_extension();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Render advanced video settings (codec, resolution, framerate, quality, time range)
|
||||||
|
fn render_video_advanced(&mut self, ui: &mut egui::Ui) {
|
||||||
ui.horizontal(|ui| {
|
ui.horizontal(|ui| {
|
||||||
ui.label("Codec:");
|
ui.label("Codec:");
|
||||||
egui::ComboBox::from_id_salt("video_codec")
|
egui::ComboBox::from_id_salt("video_codec")
|
||||||
|
|
@ -280,44 +356,34 @@ impl ExportDialog {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
ui.add_space(12.0);
|
|
||||||
|
|
||||||
// Resolution
|
|
||||||
ui.heading("Resolution");
|
|
||||||
ui.horizontal(|ui| {
|
ui.horizontal(|ui| {
|
||||||
ui.label("Width:");
|
ui.label("Resolution:");
|
||||||
let mut custom_width = self.video_settings.width.unwrap_or(1920);
|
let mut custom_width = self.video_settings.width.unwrap_or(1920);
|
||||||
if ui.add(egui::DragValue::new(&mut custom_width).range(1..=7680)).changed() {
|
if ui.add(egui::DragValue::new(&mut custom_width).range(1..=7680)).changed() {
|
||||||
self.video_settings.width = Some(custom_width);
|
self.video_settings.width = Some(custom_width);
|
||||||
}
|
}
|
||||||
|
ui.label("x");
|
||||||
ui.label("Height:");
|
|
||||||
let mut custom_height = self.video_settings.height.unwrap_or(1080);
|
let mut custom_height = self.video_settings.height.unwrap_or(1080);
|
||||||
if ui.add(egui::DragValue::new(&mut custom_height).range(1..=4320)).changed() {
|
if ui.add(egui::DragValue::new(&mut custom_height).range(1..=4320)).changed() {
|
||||||
self.video_settings.height = Some(custom_height);
|
self.video_settings.height = Some(custom_height);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Resolution presets
|
|
||||||
ui.horizontal(|ui| {
|
ui.horizontal(|ui| {
|
||||||
if ui.button("1080p").clicked() {
|
if ui.small_button("1080p").clicked() {
|
||||||
self.video_settings.width = Some(1920);
|
self.video_settings.width = Some(1920);
|
||||||
self.video_settings.height = Some(1080);
|
self.video_settings.height = Some(1080);
|
||||||
}
|
}
|
||||||
if ui.button("4K").clicked() {
|
if ui.small_button("4K").clicked() {
|
||||||
self.video_settings.width = Some(3840);
|
self.video_settings.width = Some(3840);
|
||||||
self.video_settings.height = Some(2160);
|
self.video_settings.height = Some(2160);
|
||||||
}
|
}
|
||||||
if ui.button("720p").clicked() {
|
if ui.small_button("720p").clicked() {
|
||||||
self.video_settings.width = Some(1280);
|
self.video_settings.width = Some(1280);
|
||||||
self.video_settings.height = Some(720);
|
self.video_settings.height = Some(720);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
ui.add_space(12.0);
|
|
||||||
|
|
||||||
// Framerate
|
|
||||||
ui.heading("Framerate");
|
|
||||||
ui.horizontal(|ui| {
|
ui.horizontal(|ui| {
|
||||||
ui.label("FPS:");
|
ui.label("FPS:");
|
||||||
egui::ComboBox::from_id_salt("framerate")
|
egui::ComboBox::from_id_salt("framerate")
|
||||||
|
|
@ -329,10 +395,6 @@ impl ExportDialog {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
ui.add_space(12.0);
|
|
||||||
|
|
||||||
// Quality
|
|
||||||
ui.heading("Quality");
|
|
||||||
ui.horizontal(|ui| {
|
ui.horizontal(|ui| {
|
||||||
ui.label("Quality:");
|
ui.label("Quality:");
|
||||||
egui::ComboBox::from_id_salt("video_quality")
|
egui::ComboBox::from_id_salt("video_quality")
|
||||||
|
|
@ -345,10 +407,12 @@ impl ExportDialog {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
ui.add_space(12.0);
|
|
||||||
|
|
||||||
// Include audio checkbox
|
|
||||||
ui.checkbox(&mut self.include_audio, "Include Audio");
|
ui.checkbox(&mut self.include_audio, "Include Audio");
|
||||||
|
|
||||||
|
ui.add_space(8.0);
|
||||||
|
|
||||||
|
// Time range
|
||||||
|
self.render_time_range(ui);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Render time range UI (common to both audio and video)
|
/// Render time range UI (common to both audio and video)
|
||||||
|
|
@ -358,7 +422,6 @@ impl ExportDialog {
|
||||||
ExportType::Video => (&mut self.video_settings.start_time, &mut self.video_settings.end_time),
|
ExportType::Video => (&mut self.video_settings.start_time, &mut self.video_settings.end_time),
|
||||||
};
|
};
|
||||||
|
|
||||||
ui.heading("Time Range");
|
|
||||||
ui.horizontal(|ui| {
|
ui.horizontal(|ui| {
|
||||||
ui.label("Start:");
|
ui.label("Start:");
|
||||||
ui.add(egui::DragValue::new(start_time)
|
ui.add(egui::DragValue::new(start_time)
|
||||||
|
|
@ -377,46 +440,32 @@ impl ExportDialog {
|
||||||
ui.label(format!("Duration: {:.2} seconds", duration));
|
ui.label(format!("Duration: {:.2} seconds", duration));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Render output file selection UI (common to both audio and video)
|
/// Render output file selection UI
|
||||||
fn render_output_selection(&mut self, ui: &mut egui::Ui) {
|
fn render_output_selection(&mut self, ui: &mut egui::Ui) {
|
||||||
ui.heading("Output");
|
|
||||||
ui.horizontal(|ui| {
|
ui.horizontal(|ui| {
|
||||||
let path_text = self.output_path.as_ref()
|
ui.label("Save to:");
|
||||||
.map(|p| p.display().to_string())
|
let dir_text = self.output_dir.display().to_string();
|
||||||
.unwrap_or_else(|| "No file selected".to_string());
|
ui.label(&dir_text);
|
||||||
|
if ui.button("Change...").clicked() {
|
||||||
ui.label("File:");
|
if let Some(dir) = rfd::FileDialog::new()
|
||||||
ui.text_edit_singleline(&mut path_text.clone());
|
.set_directory(&self.output_dir)
|
||||||
|
.pick_folder()
|
||||||
if ui.button("Browse...").clicked() {
|
|
||||||
// Determine file extension and filter based on export type
|
|
||||||
let (default_name, filter_name, extensions) = match self.export_type {
|
|
||||||
ExportType::Audio => {
|
|
||||||
let ext = self.audio_settings.format.extension();
|
|
||||||
(format!("audio.{}", ext), "Audio", vec![ext])
|
|
||||||
}
|
|
||||||
ExportType::Video => {
|
|
||||||
let ext = self.video_settings.codec.container_format();
|
|
||||||
(format!("video.{}", ext), "Video", vec![ext])
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
if let Some(path) = rfd::FileDialog::new()
|
|
||||||
.set_file_name(&default_name)
|
|
||||||
.add_filter(filter_name, &extensions)
|
|
||||||
.save_file()
|
|
||||||
{
|
{
|
||||||
self.output_path = Some(path);
|
self.output_dir = dir;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
ui.horizontal(|ui| {
|
||||||
|
ui.label("Filename:");
|
||||||
|
ui.text_edit_singleline(&mut self.output_filename);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Handle export button click
|
/// Handle export button click
|
||||||
fn handle_export(&mut self) -> Option<ExportResult> {
|
fn handle_export(&mut self) -> Option<ExportResult> {
|
||||||
// Check if output path is set
|
if self.output_filename.trim().is_empty() {
|
||||||
if self.output_path.is_none() {
|
self.error_message = Some("Please enter a filename".to_string());
|
||||||
self.error_message = Some("Please select an output file".to_string());
|
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2157,7 +2157,12 @@ impl EditorApp {
|
||||||
println!("Menu: Export");
|
println!("Menu: Export");
|
||||||
// Open export dialog with calculated timeline endpoint
|
// Open export dialog with calculated timeline endpoint
|
||||||
let timeline_endpoint = self.action_executor.document().calculate_timeline_endpoint();
|
let timeline_endpoint = self.action_executor.document().calculate_timeline_endpoint();
|
||||||
self.export_dialog.open(timeline_endpoint);
|
// Derive project name from the .beam file path, falling back to document name
|
||||||
|
let project_name = self.current_file_path.as_ref()
|
||||||
|
.and_then(|p| p.file_stem())
|
||||||
|
.map(|s| s.to_string_lossy().into_owned())
|
||||||
|
.unwrap_or_else(|| self.action_executor.document().name.clone());
|
||||||
|
self.export_dialog.open(timeline_endpoint, &project_name);
|
||||||
}
|
}
|
||||||
MenuAction::Quit => {
|
MenuAction::Quit => {
|
||||||
println!("Menu: Quit");
|
println!("Menu: Quit");
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue