use eframe::egui; use crate::domain::entities::{MediaTrackInfo, TrackKind}; use crate::domain::value_objects::{SyncOffset, TrackId}; use crate::ui::components::sync_offset_field::SyncOffsetField; use std::collections::HashMap; /// Evento emitido por interações com a lista de faixas existentes. pub enum ExistingTrackEvent { OffsetChanged(TrackId, SyncOffset), /// O fator de escala temporal de uma faixa existente foi alterado. DriftChanged(TrackId, f64), /// O usuário solicitou exportar a faixa identificada por `TrackId`. ExportRequested(TrackId), } /// Lista as faixas detectadas no arquivo de vídeo e permite editar o offset e o drift de cada uma. pub struct ExistingTrackList { offset_fields: HashMap, /// Campos de drift raw por track id (percentual digitado pelo usuário). drift_fields: HashMap, } impl ExistingTrackList { pub fn new() -> Self { ExistingTrackList { offset_fields: HashMap::new(), drift_fields: HashMap::new(), } } /// Sincroniza os campos de offset com as faixas atuais do projeto. pub fn sync_tracks(&mut self, tracks: &[MediaTrackInfo]) { for track in tracks { self.offset_fields .entry(track.id.val()) .or_insert_with(|| SyncOffsetField::new(track.offset)); self.drift_fields .entry(track.id.val()) .or_insert_with(|| format!("{:.5}", track.drift_scale * 100.0)); } } /// Renderiza a lista. Retorna eventos de alteração de offset e drift. /// /// `mkvmerge_available`: quando `true`, exibe a coluna de velocidade original (%). pub fn ui( &mut self, ui: &mut egui::Ui, tracks: &[MediaTrackInfo], mkvmerge_available: bool, ) -> Vec { let mut events = Vec::new(); ui.group(|ui| { ui.heading("Faixas existentes"); if tracks.is_empty() { ui.label("Nenhuma faixa detectada."); return; } let num_cols = if mkvmerge_available { 7 } else { 6 }; egui::Grid::new("existing_tracks_grid") .num_columns(num_cols) .striped(true) .show(ui, |ui| { ui.strong("#"); ui.strong("Tipo"); ui.strong("Codec"); ui.strong("Duração"); ui.strong("Atraso"); if mkvmerge_available { ui.strong("Velocidade (%)"); } ui.strong(""); ui.end_row(); for track in tracks { let editable = matches!(track.kind, TrackKind::Audio | TrackKind::Subtitle); let kind_label = match track.kind { TrackKind::Video => "Vídeo", TrackKind::Audio => "Áudio", TrackKind::Subtitle => "Legenda", TrackKind::Data => "Dados", }; let lang = track .language .as_ref() .map(|l| format!(" ({})", l)) .unwrap_or_default(); ui.label(format!("{}", track.stream_index)); ui.label(format!("{}{}", kind_label, lang)); ui.label(&track.codec); // Duração match track.duration_ms { Some(ms) => ui.label(format_duration(ms)), None => ui.label("-"), }; // Offset field let field = self .offset_fields .entry(track.id.val()) .or_insert_with(|| SyncOffsetField::new(track.offset)); field.ui(ui, ""); if let Some(offset) = field.parse() { if offset != track.offset { events.push(ExistingTrackEvent::OffsetChanged(track.id, offset)); } } // Drift field (somente para áudio e legenda quando mkvmerge disponível) if mkvmerge_available { if editable { let drift_raw = self .drift_fields .entry(track.id.val()) .or_insert_with(|| format!("{:.5}", track.drift_scale * 100.0)); let resp = ui.add( egui::TextEdit::singleline(drift_raw).desired_width(80.0), ); if resp.changed() { if let Ok(pct) = drift_raw.trim().parse::() { if pct >= 1.0 && pct <= 999.99 { let scale = pct / 100.0; if (scale - track.drift_scale).abs() > 1e-9 { events.push(ExistingTrackEvent::DriftChanged( track.id, scale, )); } } } } } else { ui.label(""); // célula vazia para vídeo/dados } } // Botão de exportar (somente Áudio e Legenda) match track.kind { TrackKind::Audio | TrackKind::Subtitle => { if ui.small_button("⬇ Exportar").clicked() { events.push(ExistingTrackEvent::ExportRequested(track.id)); } } _ => { ui.label(""); // célula vazia para alinhamento } } ui.end_row(); } }); }); events } } /// Formata duração em ms para exibição: "3:45" ou "1:23:45". fn format_duration(ms: u64) -> String { let total_secs = ms / 1000; let h = total_secs / 3600; let m = (total_secs % 3600) / 60; let s = total_secs % 60; if h > 0 { format!("{h}:{m:02}:{s:02}") } else { format!("{m}:{s:02}") } }