Views seperation
This commit is contained in:
parent
d4d02bd2d9
commit
303f1a9c66
2 changed files with 334 additions and 219 deletions
3
.gitignore
vendored
3
.gitignore
vendored
|
|
@ -1 +1,2 @@
|
||||||
target/
|
target/
|
||||||
|
*.lock
|
||||||
550
src/main.rs
550
src/main.rs
|
|
@ -62,7 +62,7 @@ fn main() -> Result<(), eframe::Error> {
|
||||||
|
|
||||||
let options = eframe::NativeOptions {
|
let options = eframe::NativeOptions {
|
||||||
viewport: egui::ViewportBuilder::default()
|
viewport: egui::ViewportBuilder::default()
|
||||||
.with_inner_size([700.0, 400.0])
|
.with_inner_size([400.0, 200.0])
|
||||||
.with_resizable(true)
|
.with_resizable(true)
|
||||||
.with_decorations(false),
|
.with_decorations(false),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
|
|
@ -162,6 +162,14 @@ fn smart_chunk_text(text: &str, config: &ChunkingConfig) -> Vec<String> {
|
||||||
chunks
|
chunks
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
|
enum CurrentView {
|
||||||
|
Main,
|
||||||
|
TextInput,
|
||||||
|
Configuration,
|
||||||
|
}
|
||||||
|
|
||||||
struct TtsUi {
|
struct TtsUi {
|
||||||
original_text: String,
|
original_text: String,
|
||||||
cleaned_text: String,
|
cleaned_text: String,
|
||||||
|
|
@ -176,6 +184,9 @@ struct TtsUi {
|
||||||
should_auto_process: bool,
|
should_auto_process: bool,
|
||||||
frames_since_load: u32,
|
frames_since_load: u32,
|
||||||
status_receiver: Option<mpsc::Receiver<ProcessingStatus>>,
|
status_receiver: Option<mpsc::Receiver<ProcessingStatus>>,
|
||||||
|
current_view: CurrentView,
|
||||||
|
viewport_expanded: bool,
|
||||||
|
original_size: egui::Vec2,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TtsUi {
|
impl TtsUi {
|
||||||
|
|
@ -197,9 +208,31 @@ impl TtsUi {
|
||||||
should_auto_process: has_text,
|
should_auto_process: has_text,
|
||||||
frames_since_load: 0,
|
frames_since_load: 0,
|
||||||
status_receiver: None,
|
status_receiver: None,
|
||||||
|
current_view: CurrentView::Main,
|
||||||
|
viewport_expanded: false,
|
||||||
|
original_size: egui::Vec2::new(500.0, 200.0),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn switch_to_view(&mut self, view: CurrentView, ctx: &egui::Context) {
|
||||||
|
if self.current_view == view {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
self.current_view = view.clone();
|
||||||
|
|
||||||
|
// Resize viewport based on view
|
||||||
|
let new_size = match view {
|
||||||
|
CurrentView::Main => self.original_size,
|
||||||
|
CurrentView::TextInput => egui::Vec2::new(800.0, 600.0),
|
||||||
|
CurrentView::Configuration => egui::Vec2::new(700.0, 650.0),
|
||||||
|
};
|
||||||
|
|
||||||
|
ctx.send_viewport_cmd(egui::ViewportCommand::InnerSize(new_size));
|
||||||
|
self.viewport_expanded = view != CurrentView::Main;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
fn start_tts_processing(&mut self) {
|
fn start_tts_processing(&mut self) {
|
||||||
if self.is_processing || self.cleaned_text.trim().is_empty() {
|
if self.is_processing || self.cleaned_text.trim().is_empty() {
|
||||||
return;
|
return;
|
||||||
|
|
@ -265,6 +298,9 @@ impl TtsUi {
|
||||||
while let Ok(audio_file) = audio_receiver.recv() {
|
while let Ok(audio_file) = audio_receiver.recv() {
|
||||||
let _ = player_sender.send(ProcessingStatus::PlayingChunk(chunk_num, total_chunks));
|
let _ = player_sender.send(ProcessingStatus::PlayingChunk(chunk_num, total_chunks));
|
||||||
Self::play_audio_sync(&audio_file);
|
Self::play_audio_sync(&audio_file);
|
||||||
|
if chunk_num < total_chunks {
|
||||||
|
let _ = player_sender.send(ProcessingStatus::Idle);
|
||||||
|
}
|
||||||
chunk_num += 1;
|
chunk_num += 1;
|
||||||
|
|
||||||
// If this was the last chunk, we're done
|
// If this was the last chunk, we're done
|
||||||
|
|
@ -387,10 +423,15 @@ impl eframe::App for TtsUi {
|
||||||
self.currently_playing_chunk = None;
|
self.currently_playing_chunk = None;
|
||||||
self.is_processing = false;
|
self.is_processing = false;
|
||||||
}
|
}
|
||||||
|
ProcessingStatus::Idle => {
|
||||||
|
// Reset currently playing chunk when between chunks
|
||||||
|
self.currently_playing_chunk = None;
|
||||||
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
self.processing_status = new_status;
|
self.processing_status = new_status;
|
||||||
}
|
}
|
||||||
|
ctx.request_repaint();
|
||||||
}
|
}
|
||||||
|
|
||||||
if !self.window_loaded {
|
if !self.window_loaded {
|
||||||
|
|
@ -406,228 +447,22 @@ impl eframe::App for TtsUi {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
egui::CentralPanel::default().show(ctx, |ui| {
|
egui::CentralPanel::default().show(ctx, |ui| {
|
||||||
egui::ScrollArea::vertical()
|
match self.current_view {
|
||||||
.auto_shrink([false; 2])
|
CurrentView::Main => self.show_main_view(ui, ctx),
|
||||||
.show(ui, |ui| {
|
CurrentView::TextInput => self.show_text_input_view(ui, ctx),
|
||||||
ui.vertical(|ui| {
|
CurrentView::Configuration => self.show_configuration_view(ui, ctx),
|
||||||
ui.horizontal(|ui| {
|
}
|
||||||
ui.label("Status:");
|
|
||||||
if self.is_processing {
|
|
||||||
ui.spinner();
|
|
||||||
}
|
|
||||||
ui.label(&self.status);
|
|
||||||
});
|
|
||||||
ui.separator();
|
|
||||||
ui.add_space(10.0);
|
|
||||||
|
|
||||||
|
|
||||||
// Current audio display
|
|
||||||
ui.horizontal(|ui| {
|
|
||||||
ui.label("🎵 Currently Playing:");
|
|
||||||
if let Some(playing_chunk) = self.currently_playing_chunk {
|
|
||||||
ui.label(format!("Chunk {}/{}", playing_chunk, self.chunks.len()));
|
|
||||||
} else {
|
|
||||||
match &self.processing_status {
|
|
||||||
ProcessingStatus::Completed => {
|
|
||||||
ui.label("Playback completed");
|
|
||||||
}
|
|
||||||
ProcessingStatus::Error(_) => {
|
|
||||||
ui.label("Error occurred");
|
|
||||||
}
|
|
||||||
ProcessingStatus::ProcessingChunk(_, _) => {
|
|
||||||
ui.label("Preparing audio...");
|
|
||||||
}
|
|
||||||
_ => {
|
|
||||||
ui.label("Ready to start");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Show the chunk that is currently being played
|
|
||||||
if let Some(playing_chunk) = self.currently_playing_chunk {
|
|
||||||
if let Some(playing_text) = self.chunks.get(playing_chunk - 1) {
|
|
||||||
egui::ScrollArea::vertical()
|
|
||||||
.max_height(120.0)
|
|
||||||
.show(ui, |ui| {
|
|
||||||
ui.add(
|
|
||||||
egui::TextEdit::multiline(&mut playing_text.as_str())
|
|
||||||
.desired_width(f32::INFINITY)
|
|
||||||
.desired_rows(1)
|
|
||||||
.interactive(false)
|
|
||||||
);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
match &self.processing_status {
|
|
||||||
ProcessingStatus::Completed => {
|
|
||||||
ui.label("🎉 All audio chunks have been played successfully!");
|
|
||||||
}
|
|
||||||
ProcessingStatus::Error(err) => {
|
|
||||||
ui.colored_label(egui::Color32::RED, format!("❌ Error: {}", err));
|
|
||||||
}
|
|
||||||
ProcessingStatus::ProcessingChunk(current, total) => {
|
|
||||||
ui.label(format!("⏳ Processing chunk {}/{}... Audio will start soon.", current, total));
|
|
||||||
}
|
|
||||||
_ => {
|
|
||||||
// When not playing anything, show the input text for editing
|
|
||||||
if !self.is_processing && self.chunks.is_empty() {
|
|
||||||
egui::ScrollArea::vertical()
|
|
||||||
.max_height(120.0)
|
|
||||||
.show(ui, |ui| {
|
|
||||||
ui.add(
|
|
||||||
egui::TextEdit::multiline(&mut self.cleaned_text)
|
|
||||||
.desired_width(f32::INFINITY)
|
|
||||||
.desired_rows(5)
|
|
||||||
.interactive(!self.is_processing)
|
|
||||||
);
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
ui.label("⏳ Waiting for audio playback to begin...");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ui.add_space(10.0);
|
|
||||||
ui.separator();
|
|
||||||
ui.add_space(5.0);
|
|
||||||
|
|
||||||
|
|
||||||
// Collapsible text input section (moved above current audio)
|
|
||||||
ui.collapsing("📝 Text Input", |ui| {
|
|
||||||
ui.horizontal(|ui| {
|
|
||||||
ui.label("Text to process:");
|
|
||||||
ui.label(format!("({} characters)", self.cleaned_text.len()));
|
|
||||||
if !self.chunks.is_empty() {
|
|
||||||
ui.label(format!("- {} chunks", self.chunks.len()));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
egui::ScrollArea::vertical()
|
|
||||||
.max_height(150.0)
|
|
||||||
.show(ui, |ui| {
|
|
||||||
ui.add(
|
|
||||||
egui::TextEdit::multiline(&mut self.cleaned_text)
|
|
||||||
.desired_width(f32::INFINITY)
|
|
||||||
.desired_rows(8)
|
|
||||||
.interactive(!self.is_processing)
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
// Show all chunks if available
|
|
||||||
if !self.chunks.is_empty() {
|
|
||||||
ui.add_space(5.0);
|
|
||||||
ui.label("📄 All Chunks:");
|
|
||||||
egui::ScrollArea::vertical()
|
|
||||||
.max_height(100.0)
|
|
||||||
.show(ui, |ui| {
|
|
||||||
for (i, chunk) in self.chunks.iter().enumerate() {
|
|
||||||
let chunk_preview = if chunk.len() > 80 {
|
|
||||||
format!("{}...", &chunk.chars().take(80).collect::<String>())
|
|
||||||
} else {
|
|
||||||
chunk.clone()
|
|
||||||
};
|
|
||||||
|
|
||||||
// Highlight current chunk being processed or played
|
|
||||||
let is_current = match &self.processing_status {
|
|
||||||
ProcessingStatus::ProcessingChunk(current, _) => *current == i + 1,
|
|
||||||
ProcessingStatus::PlayingChunk(current, _) => *current == i + 1,
|
|
||||||
_ => false,
|
|
||||||
};
|
|
||||||
|
|
||||||
if is_current {
|
|
||||||
ui.colored_label(egui::Color32::GREEN, format!("▶ {}: {}", i + 1, chunk_preview));
|
|
||||||
} else {
|
|
||||||
ui.label(format!("{}: {}", i + 1, chunk_preview));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
ui.add_space(10.0);
|
|
||||||
|
|
||||||
// Controls
|
|
||||||
ui.horizontal(|ui| {
|
|
||||||
if ui.button("🎵 Process with Kokoro").clicked() && !self.is_processing {
|
|
||||||
self.start_tts_processing();
|
|
||||||
}
|
|
||||||
|
|
||||||
ui.separator();
|
|
||||||
|
|
||||||
if ui.button("❌ Exit").clicked() {
|
|
||||||
ctx.send_viewport_cmd(egui::ViewportCommand::Close);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
ui.add_space(10.0);
|
|
||||||
ui.separator();
|
|
||||||
ui.add_space(5.0);
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// Configuration panel
|
|
||||||
ui.collapsing("⚙ Configuration", |ui| {
|
|
||||||
ui.horizontal(|ui| {
|
|
||||||
ui.label("Speed:");
|
|
||||||
ui.add(egui::Slider::new(&mut self.config.speed, 0.5..=2.0).step_by(0.1));
|
|
||||||
});
|
|
||||||
|
|
||||||
ui.horizontal(|ui| {
|
|
||||||
ui.label("Voice Style:");
|
|
||||||
ui.text_edit_singleline(&mut self.config.voice_style);
|
|
||||||
});
|
|
||||||
|
|
||||||
ui.separator();
|
|
||||||
ui.label("Chunking Settings:");
|
|
||||||
|
|
||||||
ui.horizontal(|ui| {
|
|
||||||
ui.label("Min chunk size:");
|
|
||||||
ui.add(egui::DragValue::new(&mut self.config.chunking.min_chunk_size).clamp_range(20..=500));
|
|
||||||
});
|
|
||||||
|
|
||||||
ui.horizontal(|ui| {
|
|
||||||
ui.label("Max chunk size:");
|
|
||||||
ui.add(egui::DragValue::new(&mut self.config.chunking.max_chunk_size).clamp_range(50..=1000));
|
|
||||||
});
|
|
||||||
|
|
||||||
ui.horizontal(|ui| {
|
|
||||||
ui.label("Min sentences:");
|
|
||||||
ui.add(egui::DragValue::new(&mut self.config.chunking.min_sentences).clamp_range(1..=10));
|
|
||||||
});
|
|
||||||
|
|
||||||
ui.separator();
|
|
||||||
ui.label("Paths:");
|
|
||||||
|
|
||||||
ui.horizontal(|ui| {
|
|
||||||
ui.label("Executable:");
|
|
||||||
ui.text_edit_singleline(&mut self.config.exec_path);
|
|
||||||
});
|
|
||||||
|
|
||||||
ui.horizontal(|ui| {
|
|
||||||
ui.label("Model Path:");
|
|
||||||
ui.text_edit_singleline(&mut self.config.model_path);
|
|
||||||
});
|
|
||||||
|
|
||||||
ui.horizontal(|ui| {
|
|
||||||
ui.label("Voice Data:");
|
|
||||||
ui.text_edit_singleline(&mut self.config.voice_data);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
ui.add_space(10.0);
|
|
||||||
ui.separator();
|
|
||||||
ui.add_space(5.0);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// Close on Escape
|
// Close on Escape
|
||||||
if ctx.input(|i| i.key_pressed(egui::Key::Escape)) {
|
if ctx.input(|i| i.key_pressed(egui::Key::Escape)) {
|
||||||
ctx.send_viewport_cmd(egui::ViewportCommand::Close);
|
if self.current_view != CurrentView::Main {
|
||||||
|
self.switch_to_view(CurrentView::Main, ctx);
|
||||||
|
} else {
|
||||||
|
ctx.send_viewport_cmd(egui::ViewportCommand::Close);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Request repaint to keep checking window load status
|
// Request repaint to keep checking window load status
|
||||||
|
|
@ -635,4 +470,283 @@ impl eframe::App for TtsUi {
|
||||||
ctx.request_repaint();
|
ctx.request_repaint();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TtsUi {
|
||||||
|
fn show_main_view(&mut self, ui: &mut egui::Ui, ctx: &egui::Context) {
|
||||||
|
egui::ScrollArea::vertical()
|
||||||
|
.auto_shrink([false; 2])
|
||||||
|
.show(ui, |ui| {
|
||||||
|
ui.vertical(|ui| {
|
||||||
|
|
||||||
|
// Main control buttons
|
||||||
|
ui.horizontal(|ui| {
|
||||||
|
if ui.button("📝").clicked() {
|
||||||
|
self.switch_to_view(CurrentView::TextInput, ctx);
|
||||||
|
}
|
||||||
|
|
||||||
|
ui.separator();
|
||||||
|
|
||||||
|
if ui.button("⚙").clicked() {
|
||||||
|
self.switch_to_view(CurrentView::Configuration, ctx);
|
||||||
|
}
|
||||||
|
|
||||||
|
ui.separator();
|
||||||
|
|
||||||
|
if ui.button("❌ Exit").clicked() {
|
||||||
|
ctx.send_viewport_cmd(egui::ViewportCommand::Close);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
ui.separator();
|
||||||
|
ui.add_space(10.0);
|
||||||
|
|
||||||
|
// Show the chunk that is currently being played
|
||||||
|
if let Some(playing_chunk) = self.currently_playing_chunk {
|
||||||
|
if let Some(playing_text) = self.chunks.get(playing_chunk - 1) {
|
||||||
|
|
||||||
|
egui::ScrollArea::vertical()
|
||||||
|
.max_height(80.0)
|
||||||
|
.show(ui, |ui| {
|
||||||
|
ui.label(playing_text);
|
||||||
|
});
|
||||||
|
|
||||||
|
ui.add_space(10.0);
|
||||||
|
|
||||||
|
if let ProcessingStatus::PlayingChunk(current, total) = &self.processing_status {
|
||||||
|
ui.label(format!("🎵 {}/{}", current, total));
|
||||||
|
ui.add_space(5.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
match &self.processing_status {
|
||||||
|
ProcessingStatus::Completed => {
|
||||||
|
ui.label("🎉 All audio chunks have been played successfully!");
|
||||||
|
}
|
||||||
|
ProcessingStatus::Error(err) => {
|
||||||
|
ui.colored_label(egui::Color32::RED, format!("❌ Error: {}", err));
|
||||||
|
}
|
||||||
|
ProcessingStatus::ProcessingChunk(current, total) => {
|
||||||
|
ui.label(format!("⏳ Processing chunk {}/{}... Audio will start soon.", current, total));
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
if !self.is_processing && self.chunks.is_empty() {
|
||||||
|
ui.label("📝 Click 'Text Input' to enter text, then process with Kokoro");
|
||||||
|
} else {
|
||||||
|
ui.label("⏳ Waiting for audio playback to begin...");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
fn show_text_input_view(&mut self, ui: &mut egui::Ui, ctx: &egui::Context) {
|
||||||
|
ui.vertical(|ui| {
|
||||||
|
// Header with back button
|
||||||
|
ui.horizontal(|ui| {
|
||||||
|
if ui.button("< Back").clicked() {
|
||||||
|
self.switch_to_view(CurrentView::Main, ctx);
|
||||||
|
}
|
||||||
|
ui.separator();
|
||||||
|
ui.heading("📝 Text Input");
|
||||||
|
});
|
||||||
|
|
||||||
|
ui.separator();
|
||||||
|
ui.add_space(10.0);
|
||||||
|
|
||||||
|
// Text info
|
||||||
|
ui.horizontal(|ui| {
|
||||||
|
ui.label("Text to process:");
|
||||||
|
ui.label(format!("({} characters)", self.cleaned_text.len()));
|
||||||
|
if !self.chunks.is_empty() {
|
||||||
|
ui.label(format!("- {} chunks", self.chunks.len()));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
ui.add_space(10.0);
|
||||||
|
|
||||||
|
// Large text input area
|
||||||
|
ui.label("Text Content:");
|
||||||
|
egui::ScrollArea::vertical()
|
||||||
|
.max_height(300.0)
|
||||||
|
.show(ui, |ui| {
|
||||||
|
ui.add(
|
||||||
|
egui::TextEdit::multiline(&mut self.cleaned_text)
|
||||||
|
.desired_width(f32::INFINITY)
|
||||||
|
.desired_rows(20)
|
||||||
|
.interactive(!self.is_processing)
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Show all chunks if available
|
||||||
|
if !self.chunks.is_empty() {
|
||||||
|
ui.add_space(15.0);
|
||||||
|
ui.label("All Chunks:");
|
||||||
|
egui::ScrollArea::vertical()
|
||||||
|
.max_height(200.0)
|
||||||
|
.show(ui, |ui| {
|
||||||
|
for (i, chunk) in self.chunks.iter().enumerate() {
|
||||||
|
let chunk_preview = if chunk.len() > 120 {
|
||||||
|
format!("{}...", &chunk.chars().take(120).collect::<String>())
|
||||||
|
} else {
|
||||||
|
chunk.clone()
|
||||||
|
};
|
||||||
|
|
||||||
|
// Highlight current chunk being processed or played
|
||||||
|
let is_current = match &self.processing_status {
|
||||||
|
ProcessingStatus::ProcessingChunk(current, _) => *current == i + 1,
|
||||||
|
ProcessingStatus::PlayingChunk(current, _) => *current == i + 1,
|
||||||
|
_ => false,
|
||||||
|
};
|
||||||
|
|
||||||
|
if is_current {
|
||||||
|
ui.colored_label(egui::Color32::GREEN, format!("▶ {}: {}", i + 1, chunk_preview));
|
||||||
|
} else {
|
||||||
|
ui.label(format!("{}: {}", i + 1, chunk_preview));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
ui.add_space(15.0);
|
||||||
|
ui.separator();
|
||||||
|
ui.add_space(10.0);
|
||||||
|
|
||||||
|
// Action buttons
|
||||||
|
ui.horizontal(|ui| {
|
||||||
|
if ui.button("🔄 Re-chunk Text").clicked() {
|
||||||
|
self.chunks = smart_chunk_text(&self.cleaned_text, &self.config.chunking);
|
||||||
|
}
|
||||||
|
|
||||||
|
ui.separator();
|
||||||
|
|
||||||
|
if ui.button("🎵 Process with Kokoro").clicked() && !self.is_processing {
|
||||||
|
self.start_tts_processing();
|
||||||
|
self.switch_to_view(CurrentView::Main, ctx);
|
||||||
|
}
|
||||||
|
|
||||||
|
ui.separator();
|
||||||
|
|
||||||
|
if ui.button("💾 Save & Return").clicked() {
|
||||||
|
self.switch_to_view(CurrentView::Main, ctx);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
fn show_configuration_view(&mut self, ui: &mut egui::Ui, ctx: &egui::Context) {
|
||||||
|
ui.vertical(|ui| {
|
||||||
|
// Header with back button
|
||||||
|
ui.horizontal(|ui| {
|
||||||
|
if ui.button("< Back").clicked() {
|
||||||
|
self.switch_to_view(CurrentView::Main, ctx);
|
||||||
|
}
|
||||||
|
ui.separator();
|
||||||
|
ui.heading("⚙ Configuration");
|
||||||
|
});
|
||||||
|
|
||||||
|
ui.separator();
|
||||||
|
ui.add_space(10.0);
|
||||||
|
|
||||||
|
egui::ScrollArea::vertical()
|
||||||
|
.show(ui, |ui| {
|
||||||
|
ui.vertical(|ui| {
|
||||||
|
// Audio Settings
|
||||||
|
ui.group(|ui| {
|
||||||
|
ui.label("🎵 Audio Settings");
|
||||||
|
ui.add_space(5.0);
|
||||||
|
|
||||||
|
ui.horizontal(|ui| {
|
||||||
|
ui.label("Speed:");
|
||||||
|
ui.add(egui::Slider::new(&mut self.config.speed, 0.5..=2.0).step_by(0.1));
|
||||||
|
ui.label(format!("{:.1}x", self.config.speed));
|
||||||
|
});
|
||||||
|
|
||||||
|
ui.add_space(5.0);
|
||||||
|
|
||||||
|
ui.horizontal(|ui| {
|
||||||
|
ui.label("Voice Style:");
|
||||||
|
ui.text_edit_singleline(&mut self.config.voice_style);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
ui.add_space(15.0);
|
||||||
|
|
||||||
|
// Chunking Settings
|
||||||
|
ui.group(|ui| {
|
||||||
|
ui.label("📄 Chunking Settings");
|
||||||
|
ui.add_space(5.0);
|
||||||
|
|
||||||
|
ui.horizontal(|ui| {
|
||||||
|
ui.label("Min chunk size:");
|
||||||
|
ui.add(egui::DragValue::new(&mut self.config.chunking.min_chunk_size).clamp_range(20..=500));
|
||||||
|
ui.label("characters");
|
||||||
|
});
|
||||||
|
|
||||||
|
ui.horizontal(|ui| {
|
||||||
|
ui.label("Max chunk size:");
|
||||||
|
ui.add(egui::DragValue::new(&mut self.config.chunking.max_chunk_size).clamp_range(50..=1000));
|
||||||
|
ui.label("characters");
|
||||||
|
});
|
||||||
|
|
||||||
|
ui.horizontal(|ui| {
|
||||||
|
ui.label("Min sentences:");
|
||||||
|
ui.add(egui::DragValue::new(&mut self.config.chunking.min_sentences).clamp_range(1..=10));
|
||||||
|
ui.label("per chunk");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
ui.add_space(15.0);
|
||||||
|
|
||||||
|
// Path Settings
|
||||||
|
ui.group(|ui| {
|
||||||
|
ui.label("📁 File Paths");
|
||||||
|
ui.add_space(5.0);
|
||||||
|
|
||||||
|
ui.vertical(|ui| {
|
||||||
|
ui.label("Kokoro Executable:");
|
||||||
|
ui.text_edit_singleline(&mut self.config.exec_path);
|
||||||
|
|
||||||
|
ui.add_space(8.0);
|
||||||
|
|
||||||
|
ui.label("Model Path:");
|
||||||
|
ui.text_edit_singleline(&mut self.config.model_path);
|
||||||
|
|
||||||
|
ui.add_space(8.0);
|
||||||
|
|
||||||
|
ui.label("Voice Data Path:");
|
||||||
|
ui.text_edit_singleline(&mut self.config.voice_data);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
ui.add_space(20.0);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
ui.separator();
|
||||||
|
ui.add_space(10.0);
|
||||||
|
|
||||||
|
// Action buttons
|
||||||
|
ui.horizontal(|ui| {
|
||||||
|
if ui.button("✅ Save & Return").clicked() {
|
||||||
|
self.switch_to_view(CurrentView::Main, ctx);
|
||||||
|
}
|
||||||
|
|
||||||
|
ui.separator();
|
||||||
|
|
||||||
|
if ui.button("❌ Cancel").clicked() {
|
||||||
|
// TODO: Restore previous config values
|
||||||
|
self.switch_to_view(CurrentView::Main, ctx);
|
||||||
|
}
|
||||||
|
|
||||||
|
ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| {
|
||||||
|
if ui.button("🔄 Reset to Defaults").clicked() {
|
||||||
|
self.config = KokoroConfig::default();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue