use eframe::egui; use crate::domain::value_objects::SyncOffset; /// Campo de entrada para offset de sincronização em segundos (ex: "-1.2"). /// Converte para SyncOffset(ms) internamente ao confirmar. /// Também suporta campo opcional de velocidade original (%) para correção de drift via mkvmerge. pub struct SyncOffsetField { pub raw: String, pub error: Option, /// Percentual de velocidade original (ex: "99.983" → scale 0.99983). Padrão: "100.00". pub drift_raw: String, drift_error: Option, /// Velocidade sugerida calculada externamente (% inteiro). Exibida como botão 💡. suggestion_pct: Option, } impl SyncOffsetField { pub fn new(initial: SyncOffset) -> Self { let seconds = initial.as_ms() as f64 / 1000.0; SyncOffsetField { raw: format!("{:.1}", seconds), error: None, drift_raw: "100.00".to_string(), drift_error: None, suggestion_pct: None, } } /// Define a velocidade sugerida (%) calculada a partir das durações do vídeo e da faixa. /// `None` remove o botão de sugestão. pub fn set_suggestion(&mut self, pct: Option) { self.suggestion_pct = pct; } /// Renderiza o campo de offset (sem o campo de drift). pub fn ui(&mut self, ui: &mut egui::Ui, label: &str) -> bool { let mut changed = false; ui.horizontal(|ui| { ui.label(label); if ui.text_edit_singleline(&mut self.raw).changed() { changed = true; self.error = None; } ui.label("s"); }); if let Some(ref err) = self.error { ui.colored_label(egui::Color32::RED, err); } changed } /// Renderiza offset + campo de velocidade original (drift) quando `mkvmerge_available`. /// /// - Quando `mkvmerge_available = false`: campo de velocidade é exibido desabilitado /// com tooltip pedindo instalação do MKVToolNix. /// - Quando `scale ≠ 1.0`: exibe aviso discreto em laranja. pub fn ui_with_drift(&mut self, ui: &mut egui::Ui, label: &str, mkvmerge_available: bool) -> bool { let offset_changed = self.ui(ui, label); ui.horizontal(|ui| { ui.label("Velocidade original (%):"); let resp = ui.add_enabled( mkvmerge_available, egui::TextEdit::singleline(&mut self.drift_raw).desired_width(70.0), ); if !mkvmerge_available { resp.on_hover_text( "Instale MKVToolNix para usar correção de drift de velocidade.", ); } // Botão de sugestão calculada a partir das durações if mkvmerge_available { if let Some(pct) = self.suggestion_pct { let warn = (pct - 100.0).abs() > 0.5; let hover = format!( "Velocidade sugerida: {:.3}%{}", pct, if warn { "\n⚠ Diferença >0.5% — verifique se é o mesmo conteúdo" } else { " — clique para aplicar" } ); let color = if warn { egui::Color32::from_rgb(255, 160, 0) } else { egui::Color32::from_rgb(80, 180, 80) }; if ui .add(egui::Button::new( egui::RichText::new("💡").color(color).small(), )) .on_hover_text(hover) .clicked() { self.drift_raw = format!("{:.3}", pct); } } } if let Ok(pct) = self.drift_raw.trim().parse::() { if (pct - 100.0).abs() > 0.001 { ui.colored_label( egui::Color32::from_rgb(255, 160, 0), "⚠ requer mkvmerge", ); } } }); if let Some(ref err) = self.drift_error { ui.colored_label(egui::Color32::RED, err); } offset_changed } /// Tenta converter o valor atual para SyncOffset. pub fn parse(&mut self) -> Option { match SyncOffset::from_seconds_str(&self.raw) { Ok(offset) => { self.error = None; Some(offset) } Err(e) => { self.error = Some(format!("Valor inválido: {}", e)); None } } } /// Converte o percentual de velocidade para fator de escala (f64). /// "100.00" → 1.0, "99.983" → 0.99983. /// Retorna `1.0` em caso de erro (fallback seguro). pub fn parse_drift(&mut self) -> f64 { let trimmed = self.drift_raw.trim().trim_end_matches('%'); match trimmed.parse::() { Ok(pct) if pct >= 1.0 && pct <= 999.99 => { self.drift_error = None; pct / 100.0 } Ok(_) => { self.drift_error = Some("Velocidade deve estar entre 1.0% e 999.99%".to_string()); 1.0 } Err(_) => { self.drift_error = Some("Valor inválido".to_string()); 1.0 } } } }