diff options
Diffstat (limited to 'src/transcript.rs')
-rw-r--r-- | src/transcript.rs | 143 |
1 files changed, 143 insertions, 0 deletions
diff --git a/src/transcript.rs b/src/transcript.rs new file mode 100644 index 0000000..2bddb72 --- /dev/null +++ b/src/transcript.rs @@ -0,0 +1,143 @@ +use gtk::{ListBox, pango::WrapMode, prelude::*}; +use relm4::prelude::*; + +use crate::subtitle_extractor::{StreamIndex, SubtitleCue, TRACKS}; + +#[derive(Debug)] +pub enum SubtitleCueOutput { + SeekTo(gst::ClockTime), +} + +#[relm4::factory(pub)] +impl FactoryComponent for SubtitleCue { + type Init = Self; + type Input = (); + type Output = SubtitleCueOutput; + type CommandOutput = (); + type ParentWidget = gtk::ListBox; + + view! { + gtk::Button { + inline_css: "padding: 5px; border-radius: 0;", + connect_clicked: { + let start = self.start; + move |_| { + sender.output(SubtitleCueOutput::SeekTo(start)).unwrap() + } + }, + + gtk::Label { + set_label: &self.text, + set_wrap: true, + set_wrap_mode: WrapMode::Word, + set_xalign: 0.0, + add_css_class: "body", + } + } + } + + fn init_model(init: Self::Init, _index: &Self::Index, _sender: FactorySender<Self>) -> Self { + init + } +} + +pub struct Transcript { + active_stream_index: Option<StreamIndex>, + active_cues: FactoryVecDeque<SubtitleCue>, + pending_scroll: Option<usize>, +} + +#[derive(Debug)] +pub enum TranscriptMsg { + NewCue(StreamIndex, SubtitleCue), + SelectTrack(StreamIndex), + ScrollToCue(usize), +} + +#[derive(Debug)] +pub enum TranscriptOutput { + SeekTo(gst::ClockTime), +} + +pub struct TranscriptWidgets { + viewport: gtk::Viewport, +} + +impl SimpleComponent for Transcript { + type Init = (); + type Input = TranscriptMsg; + type Output = TranscriptOutput; + type Widgets = TranscriptWidgets; + type Root = gtk::ScrolledWindow; + + fn init( + _init: Self::Init, + root: Self::Root, + sender: ComponentSender<Self>, + ) -> ComponentParts<Self> { + let listbox = ListBox::builder() + .selection_mode(gtk::SelectionMode::None) + .build(); + + let active_cues = + FactoryVecDeque::builder() + .launch(listbox) + .forward(sender.output_sender(), |output| match output { + SubtitleCueOutput::SeekTo(pos) => TranscriptOutput::SeekTo(pos), + }); + + let model = Self { + active_stream_index: None, + active_cues, + pending_scroll: None, + }; + + let widgets = TranscriptWidgets { + viewport: gtk::Viewport::builder().build(), + }; + + widgets.viewport.set_child(Some(model.active_cues.widget())); + root.set_child(Some(&widgets.viewport)); + + ComponentParts { model, widgets } + } + + fn init_root() -> Self::Root { + gtk::ScrolledWindow::new() + } + + fn update(&mut self, msg: Self::Input, _sender: ComponentSender<Self>) { + self.pending_scroll = None; + + match msg { + TranscriptMsg::NewCue(stream_index, cue) => { + if self.active_stream_index == Some(stream_index) { + self.active_cues.guard().push_back(cue); + } + } + TranscriptMsg::SelectTrack(stream_index) => { + self.active_stream_index = Some(stream_index); + + // Clear current widgets and populate with selected track's cues + self.active_cues.guard().clear(); + let tracks = TRACKS.read(); + if let Some(track) = tracks.get(&stream_index) { + for cue in &track.cues { + self.active_cues.guard().push_back(cue.clone()); + } + } + } + TranscriptMsg::ScrollToCue(ix) => { + self.pending_scroll = Some(ix); + } + } + } + + fn update_view(&self, widgets: &mut Self::Widgets, _sender: ComponentSender<Self>) { + if let Some(ix) = self.pending_scroll { + if let Some(row) = self.active_cues.widget().row_at_index(ix as i32) { + widgets.viewport.scroll_to(&row, None); + } + } + } +} |