mirror of
https://github.com/YGGverse/Yoda.git
synced 2025-03-13 06:01:21 +00:00
draft suggestion autocomplete feature
This commit is contained in:
parent
1c4bde4004
commit
5b0de227c0
@ -48,7 +48,7 @@ impl Bookmark for Button {
|
||||
}
|
||||
|
||||
fn update(&self, profile: &Profile, request: &Entry) {
|
||||
let has_bookmark = profile.bookmark.contains_request(&request.text());
|
||||
let has_bookmark = profile.bookmark.is_match_request(&request.text());
|
||||
self.set_icon_name(icon_name(has_bookmark));
|
||||
self.set_tooltip_text(Some(tooltip_text(has_bookmark)));
|
||||
}
|
||||
|
@ -2,6 +2,7 @@ mod database;
|
||||
mod identity;
|
||||
mod primary_icon;
|
||||
mod search;
|
||||
mod suggestion;
|
||||
|
||||
use super::{ItemAction, Profile};
|
||||
use adw::{prelude::AdwDialogExt, AlertDialog};
|
||||
@ -14,6 +15,7 @@ use gtk::{
|
||||
use primary_icon::PrimaryIcon;
|
||||
use sqlite::Transaction;
|
||||
use std::{cell::Cell, rc::Rc};
|
||||
use suggestion::Suggestion;
|
||||
|
||||
const PREFIX_DOWNLOAD: &str = "download:";
|
||||
const PREFIX_SOURCE: &str = "source:";
|
||||
@ -79,6 +81,9 @@ impl Request for Entry {
|
||||
// Detect primary icon on construct
|
||||
entry.update_primary_icon(profile);
|
||||
|
||||
// Init additional features
|
||||
let suggestion = Suggestion::build(&entry);
|
||||
|
||||
// Connect events
|
||||
entry.connect_icon_release({
|
||||
let profile = profile.clone();
|
||||
@ -108,6 +113,11 @@ impl Request for Entry {
|
||||
// Update icons
|
||||
this.update_primary_icon(&profile);
|
||||
this.update_secondary_icon();
|
||||
|
||||
// Show search suggestions
|
||||
if this.focus_child().is_some() {
|
||||
suggestion.update(&profile, this, None);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -0,0 +1,118 @@
|
||||
mod item;
|
||||
|
||||
use adw::{
|
||||
prelude::{ActionRowExt, PopoverExt, PreferencesRowExt},
|
||||
ActionRow,
|
||||
};
|
||||
use gtk::{
|
||||
gio::{
|
||||
prelude::{Cast, CastNone},
|
||||
ListStore,
|
||||
},
|
||||
prelude::{EntryExt, ListItemExt, WidgetExt},
|
||||
Entry, ListItem, ListView, Popover, SignalListItemFactory, SingleSelection,
|
||||
};
|
||||
pub use item::Item;
|
||||
|
||||
pub struct Suggestion {
|
||||
list_store: ListStore,
|
||||
pub popover: Popover,
|
||||
}
|
||||
|
||||
impl Suggestion {
|
||||
// Constructors
|
||||
|
||||
/// Create new `Self`
|
||||
pub fn build(request: &Entry) -> Self {
|
||||
let list_store = ListStore::new::<Item>();
|
||||
Self {
|
||||
popover: {
|
||||
let p = Popover::builder()
|
||||
.autohide(false)
|
||||
.can_focus(false)
|
||||
.halign(gtk::Align::Start)
|
||||
.child(
|
||||
>k::ScrolledWindow::builder()
|
||||
//.css_classes(["view"])
|
||||
.child(
|
||||
&ListView::builder()
|
||||
.model(
|
||||
&SingleSelection::builder()
|
||||
.model(&list_store)
|
||||
.autoselect(false)
|
||||
.build(),
|
||||
)
|
||||
.factory(&{
|
||||
let f = SignalListItemFactory::new();
|
||||
f.connect_setup(|_, this| {
|
||||
this.downcast_ref::<ListItem>().unwrap().set_child(
|
||||
Some(
|
||||
&ActionRow::builder()
|
||||
.use_markup(true)
|
||||
.use_underline(true)
|
||||
.build(),
|
||||
),
|
||||
)
|
||||
});
|
||||
f.connect_bind(|_, this| {
|
||||
let l = this.downcast_ref::<ListItem>().unwrap();
|
||||
let i = l.item().and_downcast::<Item>().unwrap();
|
||||
let r = l.child().and_downcast::<ActionRow>().unwrap();
|
||||
r.set_title(&i.title());
|
||||
r.set_subtitle(&i.subtitle());
|
||||
});
|
||||
f
|
||||
})
|
||||
.build(),
|
||||
)
|
||||
.max_content_height(400)
|
||||
.hscrollbar_policy(gtk::PolicyType::Never)
|
||||
.propagate_natural_height(true)
|
||||
.propagate_natural_width(true)
|
||||
.build(),
|
||||
)
|
||||
.has_arrow(false)
|
||||
.build();
|
||||
p.set_parent(request);
|
||||
p.set_offset(
|
||||
request
|
||||
.compute_point(request, >k::graphene::Point::zero())
|
||||
.unwrap()
|
||||
.x() as i32,
|
||||
6,
|
||||
);
|
||||
p.connect_realize({
|
||||
let request = request.clone();
|
||||
move |this| this.set_width_request(request.width())
|
||||
});
|
||||
p
|
||||
},
|
||||
list_store,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn update(&self, profile: &super::Profile, request: &Entry, limit: Option<usize>) {
|
||||
use gtk::prelude::EditableExt;
|
||||
use itertools::Itertools;
|
||||
if request.text_length() > 0 {
|
||||
self.list_store.remove_all();
|
||||
let query = request.text();
|
||||
let items = profile.bookmark.contains_request(&query, limit);
|
||||
if !items.is_empty() {
|
||||
for item in items
|
||||
.into_iter()
|
||||
.sorted_by(|a, b| Ord::cmp(&b.request, &a.request))
|
||||
{
|
||||
self.list_store.append(&Item::build(
|
||||
item.request.replace(&*query, &format!("<b>{query}</b>")),
|
||||
item.request.clone(),
|
||||
item.request.clone(),
|
||||
)); // @TODO
|
||||
}
|
||||
self.popover.popup();
|
||||
return;
|
||||
}
|
||||
}
|
||||
self.popover.popdown();
|
||||
}
|
||||
}
|
@ -0,0 +1,19 @@
|
||||
mod imp;
|
||||
|
||||
use gtk::glib::{self, Object};
|
||||
|
||||
glib::wrapper! {
|
||||
pub struct Item(ObjectSubclass<imp::Item>);
|
||||
}
|
||||
|
||||
impl Item {
|
||||
// Constructors
|
||||
|
||||
pub fn build(title: String, subtitle: String, request: String) -> Self {
|
||||
Object::builder()
|
||||
.property("title", title)
|
||||
.property("subtitle", subtitle)
|
||||
.property("request", request)
|
||||
.build()
|
||||
}
|
||||
}
|
@ -0,0 +1,31 @@
|
||||
use gtk::{
|
||||
gio::subclass::prelude::{DerivedObjectProperties, ObjectImpl, ObjectImplExt, ObjectSubclass},
|
||||
glib::{self, Object, Properties},
|
||||
prelude::ObjectExt,
|
||||
};
|
||||
use std::cell::RefCell;
|
||||
|
||||
#[derive(Properties, Default)]
|
||||
#[properties(wrapper_type = super::Item)]
|
||||
pub struct Item {
|
||||
#[property(get, set)]
|
||||
title: RefCell<String>,
|
||||
#[property(get, set)]
|
||||
subtitle: RefCell<String>,
|
||||
#[property(get, set)]
|
||||
request: RefCell<String>,
|
||||
}
|
||||
|
||||
#[glib::object_subclass]
|
||||
impl ObjectSubclass for Item {
|
||||
const NAME: &'static str = "SuggestionItem"; // @TODO make globally unique
|
||||
type Type = super::Item;
|
||||
type ParentType = Object;
|
||||
}
|
||||
|
||||
#[glib::derived_properties]
|
||||
impl ObjectImpl for Item {
|
||||
fn constructed(&self) {
|
||||
self.parent_constructed();
|
||||
}
|
||||
}
|
@ -63,8 +63,13 @@ impl Bookmark {
|
||||
// Getters
|
||||
|
||||
/// Check `request` exists in the memory index
|
||||
pub fn contains_request(&self, request: &str) -> bool {
|
||||
self.memory.borrow_mut().contains_request(request)
|
||||
pub fn is_match_request(&self, request: &str) -> bool {
|
||||
self.memory.borrow_mut().is_match_request(request)
|
||||
}
|
||||
|
||||
/// Find Items match `request`
|
||||
pub fn contains_request(&self, request: &str, limit: Option<usize>) -> Vec<Item> {
|
||||
self.memory.borrow_mut().contains_request(request, limit)
|
||||
}
|
||||
|
||||
/// Get recent Items vector from `memory`, sorted by `ID` DESC
|
||||
|
@ -30,7 +30,7 @@ impl Memory {
|
||||
}
|
||||
|
||||
/// Check `request` exists in the memory index
|
||||
pub fn contains_request(&self, request: &str) -> bool {
|
||||
pub fn is_match_request(&self, request: &str) -> bool {
|
||||
for item in self.0.iter() {
|
||||
if item.request == request {
|
||||
return true;
|
||||
@ -39,6 +39,20 @@ impl Memory {
|
||||
false
|
||||
}
|
||||
|
||||
/// Get Items match `request`
|
||||
pub fn contains_request(&self, request: &str, limit: Option<usize>) -> Vec<Item> {
|
||||
let mut items: Vec<Item> = Vec::new();
|
||||
for (i, item) in self.0.iter().enumerate() {
|
||||
if limit.is_some_and(|l| i > l) {
|
||||
break;
|
||||
}
|
||||
if item.request.contains(request) {
|
||||
items.push(item.clone())
|
||||
}
|
||||
}
|
||||
items
|
||||
}
|
||||
|
||||
/// Get recent Items vector sorted by `ID` DESC
|
||||
pub fn recent(&self, limit: Option<usize>) -> Vec<Item> {
|
||||
let mut recent: Vec<Item> = Vec::new();
|
||||
|
Loading…
x
Reference in New Issue
Block a user