summary refs log tree commit diff
path: root/src/transcript.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/transcript.rs')
-rw-r--r--src/transcript.rs143
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);
+            }
+        }
+    }
+}