use adw::prelude::*; use gtk::{gio, glib}; use relm4::prelude::*; use crate::{subtitles::StreamIndex, util::Tracker}; glib::wrapper! { pub struct TrackInfo(ObjectSubclass); } impl TrackInfo { pub fn new( stream_index: StreamIndex, language: Option<&'static str>, title: Option, ) -> Self { glib::Object::builder() .property("stream-index", stream_index as i64) .property("language", language.unwrap_or_default()) .property("title", title.unwrap_or_default()) .build() } pub fn get_stream_index(&self) -> StreamIndex { let index: i64 = self.property("stream-index"); index as usize } } mod imp { use gtk::{glib, prelude::*, subclass::prelude::*}; use std::cell::RefCell; #[derive(Default, glib::Properties)] #[properties(wrapper_type = super::TrackInfo)] pub struct TrackInfo { #[property(get, set)] stream_index: RefCell, #[property(get, set)] language: RefCell, #[property(get, set)] title: RefCell, } #[glib::object_subclass] impl ObjectSubclass for TrackInfo { const NAME: &'static str = "TrackInfo"; type Type = super::TrackInfo; } #[glib::derived_properties] impl ObjectImpl for TrackInfo {} } pub struct TrackSelector { track_list_model: gio::ListStore, track_ix: Option, } pub struct TrackSelectorInit { pub title: &'static str, pub subtitle: Option<&'static str>, } #[derive(Debug)] pub enum TrackSelectorMsg { SetListModel(gio::ListStore), Changed(StreamIndex), } #[derive(Debug)] pub enum TrackSelectorOutput { Changed(StreamIndex), } #[relm4::component(pub)] impl SimpleComponent for TrackSelector { type Init = TrackSelectorInit; type Input = TrackSelectorMsg; type Output = TrackSelectorOutput; view! { #[root] #[name(primary_combo)] adw::ComboRow { set_title: init.title, set_subtitle?: init.subtitle, set_factory: Some(&track_factory), #[watch] set_model: Some(&model.track_list_model), // #[watch] // set_selected: model.track_ix.map_or(gtk::INVALID_LIST_POSITION, |ix| get_list_ix_from_stream_ix(&model.track_list_model, ix)), connect_selected_notify[sender] => move |combo| { if let Some(stream_ix) = get_stream_ix_from_combo(combo) { println!("selected {}", stream_ix); sender.input(TrackSelectorMsg::Changed(stream_ix)); } else { println!("selected none"); } }, }, #[name(track_factory)] gtk::SignalListItemFactory { connect_setup => move |_, list_item| { let list_item = list_item.downcast_ref::().unwrap(); let vbox = gtk::Box::new(gtk::Orientation::Vertical, 0); let language_label = gtk::Label::new(None); language_label.set_halign(gtk::Align::Start); language_label.set_ellipsize(gtk::pango::EllipsizeMode::End); let title_label = gtk::Label::new(None); title_label.set_halign(gtk::Align::Start); title_label.set_ellipsize(gtk::pango::EllipsizeMode::End); title_label.add_css_class("subtitle"); vbox.append(&language_label); vbox.append(&title_label); list_item.set_child(Some(&vbox)); }, connect_bind => move |_, list_item| { let list_item = list_item.downcast_ref::().unwrap(); let item = list_item.item().unwrap(); let track_info = item.downcast_ref::().unwrap(); let vbox = list_item.child().unwrap().downcast::().unwrap(); let language_label = vbox.first_child().unwrap().downcast::().unwrap(); let title_label = vbox.last_child().unwrap().downcast::().unwrap(); let language = track_info.language(); let title = track_info.title(); let language_text = if !language.is_empty() { &language } else { "Unknown Language" }; language_label.set_text(&language_text); title_label.set_text(&title); title_label.set_visible(!title.is_empty()); }, }, } fn init( init: Self::Init, root: Self::Root, sender: ComponentSender, ) -> ComponentParts { let track_list_model = gio::ListStore::new::(); let model = Self { track_list_model: track_list_model, track_ix: None, }; let widgets = view_output!(); ComponentParts { model, widgets } } fn update(&mut self, msg: Self::Input, sender: ComponentSender) { match msg { TrackSelectorMsg::SetListModel(list_model) => { self.track_list_model = list_model; } TrackSelectorMsg::Changed(track_ix) => { println!("changed {:?}", track_ix); self.track_ix = Some(track_ix); sender .output(TrackSelectorOutput::Changed(track_ix)) .unwrap(); } } } } fn get_stream_ix_from_combo(combo: &adw::ComboRow) -> Option { let ix = combo .selected_item()? .downcast_ref::() .unwrap() .get_stream_index(); Some(ix) } fn get_list_ix_from_stream_ix(list_model: &gio::ListStore, stream_ix: StreamIndex) -> u32 { for i in 0..list_model.n_items() { if let Some(item) = list_model.item(i) { if let Some(track_info) = item.downcast_ref::() { if track_info.get_stream_index() == stream_ix { return i; } } } } panic!("Stream index {} not found in list model", stream_ix); }