Files
simple-multimidia-track-aud…/src/ui/components/sync_offset_field.rs
T

154 lines
5.5 KiB
Rust

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<String>,
/// Percentual de velocidade original (ex: "99.983" → scale 0.99983). Padrão: "100.00".
pub drift_raw: String,
drift_error: Option<String>,
/// Velocidade sugerida calculada externamente (% inteiro). Exibida como botão 💡.
suggestion_pct: Option<f64>,
}
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<f64>) {
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::<f64>() {
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<SyncOffset> {
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::<f64>() {
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
}
}
}
}