| 
									
										
										
										
											2023-09-30 00:22:33 +02:00
										 |  |  | use crate::search_tag::MusicusSearchTag;
 | 
					
						
							| 
									
										
										
										
											2023-09-30 19:10:32 +02:00
										 |  |  | use adw::{gdk, gio, glib, glib::clone, glib::subclass::Signal, prelude::*, subclass::prelude::*};
 | 
					
						
							| 
									
										
										
										
											2023-09-30 00:22:33 +02:00
										 |  |  | use once_cell::sync::Lazy;
 | 
					
						
							| 
									
										
										
										
											2023-09-30 19:10:32 +02:00
										 |  |  | use std::{cell::RefCell, time::Duration};
 | 
					
						
							| 
									
										
										
										
											2023-09-30 00:22:33 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  | mod imp {
 | 
					
						
							|  |  |  |     use super::*;
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     #[derive(Debug, Default, gtk::CompositeTemplate)]
 | 
					
						
							|  |  |  |     #[template(file = "data/ui/search_entry.blp")]
 | 
					
						
							|  |  |  |     pub struct MusicusSearchEntry {
 | 
					
						
							|  |  |  |         #[template_child]
 | 
					
						
							|  |  |  |         pub tags_box: TemplateChild<gtk::Box>,
 | 
					
						
							|  |  |  |         #[template_child]
 | 
					
						
							|  |  |  |         pub text: TemplateChild<gtk::Text>,
 | 
					
						
							|  |  |  |         #[template_child]
 | 
					
						
							|  |  |  |         pub clear_icon: TemplateChild<gtk::Image>,
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         pub tags: RefCell<Vec<MusicusSearchTag>>,
 | 
					
						
							| 
									
										
										
										
											2023-09-30 19:10:32 +02:00
										 |  |  |         pub query_changed: RefCell<Option<gio::Cancellable>>,
 | 
					
						
							| 
									
										
										
										
											2023-09-30 00:22:33 +02:00
										 |  |  |     }
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     #[glib::object_subclass]
 | 
					
						
							|  |  |  |     impl ObjectSubclass for MusicusSearchEntry {
 | 
					
						
							|  |  |  |         const NAME: &'static str = "MusicusSearchEntry";
 | 
					
						
							|  |  |  |         type Type = super::MusicusSearchEntry;
 | 
					
						
							|  |  |  |         type ParentType = gtk::Box;
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         fn class_init(klass: &mut Self::Class) {
 | 
					
						
							|  |  |  |             klass.bind_template();
 | 
					
						
							|  |  |  |             klass.bind_template_instance_callbacks();
 | 
					
						
							|  |  |  |             klass.set_css_name("entry");
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             klass.add_shortcut(
 | 
					
						
							|  |  |  |                 >k::Shortcut::builder()
 | 
					
						
							|  |  |  |                     .trigger(>k::KeyvalTrigger::new(
 | 
					
						
							|  |  |  |                         gdk::Key::Escape,
 | 
					
						
							|  |  |  |                         gdk::ModifierType::empty(),
 | 
					
						
							|  |  |  |                     ))
 | 
					
						
							|  |  |  |                     .action(>k::CallbackAction::new(|widget, _| match widget
 | 
					
						
							|  |  |  |                         .downcast_ref::<super::MusicusSearchEntry>(
 | 
					
						
							|  |  |  |                     ) {
 | 
					
						
							|  |  |  |                         Some(obj) => {
 | 
					
						
							|  |  |  |                             obj.reset();
 | 
					
						
							|  |  |  |                             true
 | 
					
						
							|  |  |  |                         }
 | 
					
						
							|  |  |  |                         None => false,
 | 
					
						
							|  |  |  |                     }))
 | 
					
						
							|  |  |  |                     .build(),
 | 
					
						
							|  |  |  |             );
 | 
					
						
							|  |  |  |         }
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         fn instance_init(obj: &glib::subclass::InitializingObject<Self>) {
 | 
					
						
							|  |  |  |             obj.init_template();
 | 
					
						
							|  |  |  |         }
 | 
					
						
							|  |  |  |     }
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     impl ObjectImpl for MusicusSearchEntry {
 | 
					
						
							|  |  |  |         fn constructed(&self) {
 | 
					
						
							|  |  |  |             let controller = gtk::GestureClick::new();
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             controller.connect_pressed(|gesture, _, _, _| {
 | 
					
						
							|  |  |  |                 gesture.set_state(gtk::EventSequenceState::Claimed);
 | 
					
						
							|  |  |  |             });
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             controller.connect_released(clone!(@weak self as _self => move |_, _, _, _| {
 | 
					
						
							|  |  |  |                 _self.obj().reset();
 | 
					
						
							|  |  |  |             }));
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             self.clear_icon.add_controller(controller);
 | 
					
						
							|  |  |  |         }
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         fn signals() -> &'static [Signal] {
 | 
					
						
							| 
									
										
										
										
											2023-09-30 19:10:32 +02:00
										 |  |  |             static SIGNALS: Lazy<Vec<Signal>> = Lazy::new(|| {
 | 
					
						
							|  |  |  |                 vec![
 | 
					
						
							|  |  |  |                     Signal::builder("activate").build(),
 | 
					
						
							|  |  |  |                     Signal::builder("query-changed").build(),
 | 
					
						
							|  |  |  |                 ]
 | 
					
						
							|  |  |  |             });
 | 
					
						
							| 
									
										
										
										
											2023-09-30 00:22:33 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  |             SIGNALS.as_ref()
 | 
					
						
							|  |  |  |         }
 | 
					
						
							|  |  |  |     }
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     impl WidgetImpl for MusicusSearchEntry {
 | 
					
						
							|  |  |  |         fn grab_focus(&self) -> bool {
 | 
					
						
							|  |  |  |             self.text.grab_focus_without_selecting()
 | 
					
						
							|  |  |  |         }
 | 
					
						
							|  |  |  |     }
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     impl BoxImpl for MusicusSearchEntry {}
 | 
					
						
							|  |  |  | }
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | glib::wrapper! {
 | 
					
						
							|  |  |  |     pub struct MusicusSearchEntry(ObjectSubclass<imp::MusicusSearchEntry>)
 | 
					
						
							|  |  |  |         @extends gtk::Widget;
 | 
					
						
							|  |  |  | }
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | #[gtk::template_callbacks]
 | 
					
						
							|  |  |  | impl MusicusSearchEntry {
 | 
					
						
							|  |  |  |     pub fn new() -> Self {
 | 
					
						
							|  |  |  |         glib::Object::new()
 | 
					
						
							|  |  |  |     }
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-09-30 19:10:32 +02:00
										 |  |  |     pub fn connect_query_changed<F: Fn(&Self) + 'static>(&self, f: F) -> glib::SignalHandlerId {
 | 
					
						
							|  |  |  |         self.connect_local("query-changed", true, move |values| {
 | 
					
						
							|  |  |  |             let obj = values[0].get::<Self>().unwrap();
 | 
					
						
							|  |  |  |             f(&obj);
 | 
					
						
							|  |  |  |             None
 | 
					
						
							|  |  |  |         })
 | 
					
						
							|  |  |  |     }
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-09-30 00:22:33 +02:00
										 |  |  |     pub fn set_key_capture_widget(&self, widget: &impl IsA<gtk::Widget>) {
 | 
					
						
							|  |  |  |         let controller = gtk::EventControllerKey::new();
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         controller.connect_key_pressed(clone!(@weak self as _self => @default-return glib::Propagation::Proceed, move |controller, _, _, _| {
 | 
					
						
							|  |  |  |             match controller.forward(&_self.imp().text.get()) {
 | 
					
						
							|  |  |  |                 true => {
 | 
					
						
							|  |  |  |                     _self.grab_focus();
 | 
					
						
							|  |  |  |                     glib::Propagation::Stop
 | 
					
						
							|  |  |  |                 },
 | 
					
						
							|  |  |  |                 false => glib::Propagation::Proceed,
 | 
					
						
							|  |  |  |             }
 | 
					
						
							|  |  |  |         }));
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         controller.connect_key_released(clone!(@weak self as _self => move |controller, _, _, _| {
 | 
					
						
							|  |  |  |             controller.forward(&_self.imp().text.get());
 | 
					
						
							|  |  |  |         }));
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         widget.add_controller(controller);
 | 
					
						
							|  |  |  |     }
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     pub fn reset(&self) {
 | 
					
						
							|  |  |  |         let mut tags = self.imp().tags.borrow_mut();
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         while let Some(tag) = tags.pop() {
 | 
					
						
							|  |  |  |             self.imp().tags_box.remove(&tag);
 | 
					
						
							|  |  |  |         }
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         self.imp().text.set_text("");
 | 
					
						
							|  |  |  |     }
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     pub fn add_tag(&self, name: &str) {
 | 
					
						
							|  |  |  |         self.imp().text.set_text("");
 | 
					
						
							|  |  |  |         let tag = MusicusSearchTag::new(name);
 | 
					
						
							|  |  |  |         self.imp().tags_box.append(&tag);
 | 
					
						
							|  |  |  |         self.imp().tags.borrow_mut().push(tag);
 | 
					
						
							|  |  |  |     }
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-09-30 19:10:32 +02:00
										 |  |  |     pub fn query(&self) -> String {
 | 
					
						
							|  |  |  |         self.imp().text.text().to_string()
 | 
					
						
							|  |  |  |     }
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-09-30 00:22:33 +02:00
										 |  |  |     #[template_callback]
 | 
					
						
							|  |  |  |     fn activate(&self, _: >k::Text) {
 | 
					
						
							|  |  |  |         self.emit_by_name::<()>("activate", &[]);
 | 
					
						
							|  |  |  |     }
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     #[template_callback]
 | 
					
						
							|  |  |  |     fn backspace(&self, text: >k::Text) {
 | 
					
						
							|  |  |  |         if text.cursor_position() == 0 {
 | 
					
						
							|  |  |  |             if let Some(tag) = self.imp().tags.borrow_mut().pop() {
 | 
					
						
							|  |  |  |                 self.imp().tags_box.remove(&tag);
 | 
					
						
							|  |  |  |             }
 | 
					
						
							|  |  |  |         }
 | 
					
						
							|  |  |  |     }
 | 
					
						
							| 
									
										
										
										
											2023-09-30 19:10:32 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  |     #[template_callback]
 | 
					
						
							|  |  |  |     async fn text_changed(&self, text: >k::Text) {
 | 
					
						
							|  |  |  |         self.imp().clear_icon.set_visible(!text.text().is_empty());
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         if let Some(cancellable) = self.imp().query_changed.borrow_mut().take() {
 | 
					
						
							|  |  |  |             cancellable.cancel();
 | 
					
						
							|  |  |  |         }
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         let cancellable = gio::Cancellable::new();
 | 
					
						
							|  |  |  |         self.imp().query_changed.replace(Some(cancellable.clone()));
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         let _ = gio::CancellableFuture::new(
 | 
					
						
							|  |  |  |             async {
 | 
					
						
							|  |  |  |                 glib::timeout_future(Duration::from_millis(150)).await;
 | 
					
						
							|  |  |  |                 self.emit_by_name::<()>("query-changed", &[]);
 | 
					
						
							|  |  |  |             },
 | 
					
						
							|  |  |  |             cancellable,
 | 
					
						
							|  |  |  |         )
 | 
					
						
							|  |  |  |         .await;
 | 
					
						
							|  |  |  |     }
 | 
					
						
							| 
									
										
										
										
											2023-09-30 00:22:33 +02:00
										 |  |  | }
 |