From c3e5db68890fa0c3edc6b493dc4015e420ba36a3 Mon Sep 17 00:00:00 2001 From: Julian Mutter Date: Sun, 26 May 2024 14:29:16 +0200 Subject: [PATCH] Try out different autosuggestion methods --- src/ui/autosuggestion_element.rs | 32 ++++++++++ src/ui/autosuggestion_popover.rs | 104 +++++++++++++++++++++++++++++++ src/ui/mod.rs | 2 + src/ui/sheet_edit_dialog.rs | 71 ++++++++++++++++++++- 4 files changed, 207 insertions(+), 2 deletions(-) create mode 100644 src/ui/autosuggestion_element.rs create mode 100644 src/ui/autosuggestion_popover.rs diff --git a/src/ui/autosuggestion_element.rs b/src/ui/autosuggestion_element.rs new file mode 100644 index 0000000..de97bff --- /dev/null +++ b/src/ui/autosuggestion_element.rs @@ -0,0 +1,32 @@ +use gtk::prelude::*; +use relm4::prelude::*; + +pub struct AutosuggestionElementModel { + pub label: String, +} + +#[relm4::factory(pub)] +impl FactoryComponent for AutosuggestionElementModel { + type Init = String; + type ParentWidget = gtk::ListBox; + type CommandOutput = (); + type Input = (); + type Output = (); + + view! { + #[root] + gtk::ListBoxRow { + gtk::Label { + set_label: &self.label, + set_halign: gtk::Align::Start, + set_margin_all: 0, + } + } + } + + fn init_model(label: Self::Init, _index: &DynamicIndex, _sender: FactorySender) -> Self { + AutosuggestionElementModel { label } + } + + fn update(&mut self, msg: Self::Input, _sender: FactorySender) {} +} diff --git a/src/ui/autosuggestion_popover.rs b/src/ui/autosuggestion_popover.rs new file mode 100644 index 0000000..c23d280 --- /dev/null +++ b/src/ui/autosuggestion_popover.rs @@ -0,0 +1,104 @@ +use gtk::prelude::*; + +use relm4::component::{AsyncComponent, AsyncComponentParts}; +use relm4::factory::FactoryVecDeque; +use relm4::gtk; +use relm4::{AsyncComponentSender, RelmListBoxExt}; + +use super::autosuggestion_element::AutosuggestionElementModel; + +pub struct AutosuggestionPopoverModel { + hidden: bool, + suggestions: Vec, + suggestion_rows: FactoryVecDeque, +} + +#[derive(Debug)] +pub enum AutosuggestionPopoverInput { + Show, + Hide, + SetSuggestions(Vec), + SuggestionIndexSelected(i32), + None, +} + +#[derive(Debug)] +pub enum AutosuggestionPopoverOutput { + SuggestionSelected(String), +} + +#[relm4::component(pub, async)] +impl AsyncComponent for AutosuggestionPopoverModel { + type Init = (); + type Input = AutosuggestionPopoverInput; + type Output = AutosuggestionPopoverOutput; + type CommandOutput = (); + + view! { + gtk::Popover { + #[watch] + set_visible: !model.hidden, + set_has_arrow: false, + model.suggestion_rows.widget() -> &relm4::gtk::ListBox { + set_hexpand: true, + // set_orientation: gtk::Orientation::Vertical, + // set_spacing: 5, + connect_row_activated[sender] => move |list_box, row| { + let index = list_box.index_of_child(row).unwrap(); + sender.input(AutosuggestionPopoverInput::SuggestionIndexSelected(index)); + }, + }, + } + } + + async fn init( + _params: Self::Init, + root: Self::Root, + sender: AsyncComponentSender, + ) -> AsyncComponentParts { + let suggestion_rows = FactoryVecDeque::builder() + .launch(gtk::ListBox::default()) + .forward(sender.input_sender(), |_| AutosuggestionPopoverInput::None); + + let model = AutosuggestionPopoverModel { + hidden: true, + suggestions: Vec::new(), + suggestion_rows, + }; + let widgets = view_output!(); + + AsyncComponentParts { model, widgets } + } + + // TODO: init_loading_widgets + + async fn update( + &mut self, + msg: Self::Input, + _sender: AsyncComponentSender, + _root: &Self::Root, + ) { + match msg { + AutosuggestionPopoverInput::Show => self.hidden = false, + AutosuggestionPopoverInput::Hide => self.hidden = true, + AutosuggestionPopoverInput::SetSuggestions(suggestions) => { + self.suggestion_rows.guard().clear(); + for suggestion in suggestions.iter() { + self.suggestion_rows + .guard() + .push_back(suggestion.to_string()); + } + self.suggestions = suggestions; + } + AutosuggestionPopoverInput::None => {} + AutosuggestionPopoverInput::SuggestionIndexSelected(index) => { + let suggestion = self.suggestions.get(index as usize).unwrap(); + _sender + .output(AutosuggestionPopoverOutput::SuggestionSelected( + suggestion.to_string(), + )) + .unwrap(); + } + } + } +} diff --git a/src/ui/mod.rs b/src/ui/mod.rs index 2b1b37e..0c07866 100644 --- a/src/ui/mod.rs +++ b/src/ui/mod.rs @@ -1,4 +1,6 @@ pub mod app; +pub mod autosuggestion_element; +pub mod autosuggestion_popover; pub mod mcdu; pub mod sheet_edit_dialog; pub mod sheet_listing; diff --git a/src/ui/sheet_edit_dialog.rs b/src/ui/sheet_edit_dialog.rs index 052e07b..57c7a10 100644 --- a/src/ui/sheet_edit_dialog.rs +++ b/src/ui/sheet_edit_dialog.rs @@ -3,7 +3,11 @@ use std::sync::Arc; use relm4::{ component::{AsyncComponent, AsyncComponentParts, Connector}, - gtk::EntryBuffer, + gtk::{ + gio::ListStore, + glib::{self, GString, Type, Value}, + EntryBuffer, EntryCompletion, + }, prelude::*, AsyncComponentSender, }; @@ -11,15 +15,19 @@ use relm4_components::alert::{Alert, AlertMsg, AlertSettings}; use crate::{database::Database, sheet::Sheet, sheet_dao}; +use super::autosuggestion_popover::{AutosuggestionPopoverInput, AutosuggestionPopoverModel}; + pub struct SheetEditDialogModel { database: Arc, hidden: bool, sheet: Option, name_entry_buffer: EntryBuffer, composer_entry_buffer: EntryBuffer, + composer_entry_completion: EntryCompletion, is_book: bool, book_sheets: Vec<(String, String, i64)>, alert_empty_fields: Connector, + autosuggestion_popover: AsyncController, } pub struct SheetEditDialogInit { @@ -31,6 +39,8 @@ pub struct SheetEditDialogInit { pub enum SheetEditDialogInput { Accept, Cancel, + ComposerTextChanged(String), + ComposerSuggestionSelected(String), } #[derive(Debug)] @@ -74,7 +84,9 @@ impl AsyncComponent for SheetEditDialogModel { }, gtk::Entry { set_buffer: &model.composer_entry_buffer, + set_completion: Some(&model.composer_entry_completion), set_hexpand: true, + connect_changed[sender] => move |entry| sender.input(SheetEditDialogInput::ComposerTextChanged(entry.text().to_string())), }, }, gtk::Box { @@ -103,7 +115,8 @@ impl AsyncComponent for SheetEditDialogModel { set_label : "Confirm", connect_clicked[sender] => move |_| sender.input(SheetEditDialogInput::Accept) }, - } + }, + // model.autosuggestion_popover.widget(), } } } @@ -148,12 +161,39 @@ impl AsyncComponent for SheetEditDialogModel { } }; + let composer_entry_completion = EntryCompletion::new(); + let data = [ + "France".to_string(), + "Italy".to_string(), + "Sweden".to_string(), + "Switzerland".to_string(), + ]; + let store = gtk::ListStore::new(&[glib::Type::STRING]); + for d in data.iter() { + store.set(&store.append(), &[(0, &d)]); + } + + composer_entry_completion.set_model(Some(&store)); + // Use the first (and only) column available to set the autocompletion text + composer_entry_completion.set_text_column(0); + // how many keystrokes to wait before attempting to autocomplete? + composer_entry_completion.set_minimum_key_length(1); + // whether the completions should be presented in a popup window + composer_entry_completion.set_popup_completion(true); + + let autosuggestion_popover = AutosuggestionPopoverModel::builder() + .launch(()) + .forward(sender.input_sender(), |output| match output { + crate::ui::autosuggestion_popover::AutosuggestionPopoverOutput::SuggestionSelected(suggestion) => SheetEditDialogInput::ComposerSuggestionSelected(suggestion), + }); + let model = SheetEditDialogModel { database: params.database, hidden: false, sheet: Some(sheet), name_entry_buffer: EntryBuffer::new(Some(sheet_name)), composer_entry_buffer: EntryBuffer::new(Some(sheet_composer)), + composer_entry_completion, is_book, book_sheets: Vec::new(), alert_empty_fields: Alert::builder().transient_for(&root).launch(AlertSettings { @@ -165,6 +205,7 @@ impl AsyncComponent for SheetEditDialogModel { cancel_label: None, option_label: None, }), + autosuggestion_popover, }; let widgets = view_output!(); @@ -224,6 +265,32 @@ impl AsyncComponent for SheetEditDialogModel { self.hidden = true; self.sheet = None; } + SheetEditDialogInput::ComposerTextChanged(composer_name) => { + // let suggestions = vec![ + // "Hello".into(), + // "World".into(), + // "Seattle".into(), + // "Salzburg".into(), + // ]; + + // self.autosuggestion_popover + // .emit(AutosuggestionPopoverInput::SetSuggestions(suggestions)); + // self.autosuggestion_popover + // .emit(AutosuggestionPopoverInput::Show); + // self.model.filtered_suggestions = self + // .model + // .suggestions + // .iter() + // .filter(|suggestion| suggestion.starts_with(&text)) + // .cloned() + // .collect(); + // self.update_suggestions(); + } + SheetEditDialogInput::ComposerSuggestionSelected(composer_name) => { + self.composer_entry_buffer.set_text(composer_name); + self.autosuggestion_popover + .emit(AutosuggestionPopoverInput::Hide); + } } } }