feat: add picker UI (live-filtered thumbnail grid)
This commit is contained in:
@@ -0,0 +1,2 @@
|
||||
pub mod editor;
|
||||
pub mod picker;
|
||||
@@ -0,0 +1,105 @@
|
||||
use crate::icon_index::{filter_icons, list_svg_icons};
|
||||
use gtk::glib;
|
||||
use gtk::prelude::*;
|
||||
use gtk::{Box as GtkBox, FlowBox, Label, Orientation, Picture, ScrolledWindow, SearchEntry};
|
||||
use std::cell::RefCell;
|
||||
use std::path::PathBuf;
|
||||
use std::rc::Rc;
|
||||
|
||||
const RESULT_LIMIT: usize = 300;
|
||||
|
||||
pub fn build_picker_page(
|
||||
breeze_root: PathBuf,
|
||||
on_select: impl Fn(PathBuf) + 'static,
|
||||
) -> gtk::Widget {
|
||||
let root = GtkBox::new(Orientation::Vertical, 8);
|
||||
root.set_margin_top(8);
|
||||
root.set_margin_bottom(8);
|
||||
root.set_margin_start(8);
|
||||
root.set_margin_end(8);
|
||||
|
||||
let search = SearchEntry::new();
|
||||
search.set_placeholder_text(Some("Search icons…"));
|
||||
root.append(&search);
|
||||
|
||||
let flow_box = FlowBox::new();
|
||||
flow_box.set_valign(gtk::Align::Start);
|
||||
flow_box.set_max_children_per_line(10);
|
||||
flow_box.set_selection_mode(gtk::SelectionMode::Single);
|
||||
|
||||
let scroller = ScrolledWindow::new();
|
||||
scroller.set_vexpand(true);
|
||||
scroller.set_child(Some(&flow_box));
|
||||
root.append(&scroller);
|
||||
|
||||
let all_icons = Rc::new(list_svg_icons(&breeze_root));
|
||||
// Paths for whatever's currently shown in flow_box, in the same order
|
||||
// as its children — FlowBoxChild::index() looks up into this. Simpler
|
||||
// and unsafe-free compared to attaching data to each child widget.
|
||||
let current_matches: Rc<RefCell<Vec<PathBuf>>> = Rc::new(RefCell::new(Vec::new()));
|
||||
|
||||
{
|
||||
let current_matches = Rc::clone(¤t_matches);
|
||||
flow_box.connect_child_activated(move |_, child| {
|
||||
let index = child.index();
|
||||
if index < 0 {
|
||||
return;
|
||||
}
|
||||
if let Some(path) = current_matches.borrow().get(index as usize) {
|
||||
on_select(path.clone());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
let populate = {
|
||||
let flow_box = flow_box.clone();
|
||||
let all_icons = Rc::clone(&all_icons);
|
||||
let current_matches = Rc::clone(¤t_matches);
|
||||
move |query: &str| {
|
||||
while let Some(child) = flow_box.first_child() {
|
||||
flow_box.remove(&child);
|
||||
}
|
||||
|
||||
let matches: Vec<PathBuf> = filter_icons(&all_icons, query, RESULT_LIMIT)
|
||||
.into_iter()
|
||||
.cloned()
|
||||
.collect();
|
||||
|
||||
for path in &matches {
|
||||
let item_box = GtkBox::new(Orientation::Vertical, 4);
|
||||
item_box.set_size_request(96, 96);
|
||||
|
||||
let picture = Picture::new();
|
||||
if let Ok(texture) = gtk::gdk::Texture::from_filename(path) {
|
||||
picture.set_paintable(Some(&texture));
|
||||
}
|
||||
picture.set_size_request(64, 64);
|
||||
item_box.append(&picture);
|
||||
|
||||
let name = path
|
||||
.file_name()
|
||||
.map(|n| n.to_string_lossy().to_string())
|
||||
.unwrap_or_default();
|
||||
let label = Label::new(Some(&name));
|
||||
label.set_ellipsize(gtk::pango::EllipsizeMode::Middle);
|
||||
label.set_max_width_chars(14);
|
||||
item_box.append(&label);
|
||||
|
||||
flow_box.append(&item_box);
|
||||
}
|
||||
|
||||
*current_matches.borrow_mut() = matches;
|
||||
}
|
||||
};
|
||||
|
||||
populate("");
|
||||
|
||||
{
|
||||
let populate = populate.clone();
|
||||
search.connect_search_changed(move |entry| {
|
||||
populate(&entry.text());
|
||||
});
|
||||
}
|
||||
|
||||
root.upcast()
|
||||
}
|
||||
Reference in New Issue
Block a user