use gtk::gdk::keys::constants::{Escape as ESCAPE, Return as RETURN, Up as UP, Down as DOWN}; use gtk::{ApplicationWindow, SearchEntry, Box, ListStore, TreeView, ScrolledWindow, TreePath}; use gtk::prelude::*; use gtk::gdk::{self, ModifierType}; use std::io; use std::rc::Rc; use gtk::glib; fn get_options() -> Vec { let mut v = Vec::new(); for line in io::stdin().lines() { v.push(line.unwrap().trim_end_matches("\n").to_string()); } v } fn filter_matches(f : &String, options : &Vec) -> Vec { let mut v = Vec::new(); if f.is_empty() { return options.clone() } for option in options.iter() { if option.starts_with(f) { v.push(option.clone()); } } for option in options.iter() { if option.ends_with(f) && !v.contains(option) { v.push(option.clone()); } } for option in options.iter() { if option.contains(f) && !v.contains(option) { v.push(option.clone()); } } v } fn build_ui(application: >k::Application) { let provider = gtk::CssProvider::new(); let style = include_bytes!("style.css"); provider.load_from_data(style).expect("failed to load default style"); gtk::StyleContext::add_provider_for_screen( &gdk::Screen::default().expect("error initializing gtk css provider"), &provider, gtk::STYLE_PROVIDER_PRIORITY_APPLICATION ); let options = Rc::new(get_options()); let window = Rc::new(ApplicationWindow::new(application)); window.set_title("Hyprmenu"); window.set_default_size(600, 200); window.set_app_paintable(true); let container = Box::new(gtk::Orientation::Vertical, 0); container.set_widget_name("hyprmenu"); window.add(&container); let entry = Rc::new(SearchEntry::new()); entry.set_widget_name("search"); let scroll = Rc::new(ScrolledWindow::new( gtk::Adjustment::NONE, gtk::Adjustment::NONE )); scroll.set_policy(gtk::PolicyType::Never, gtk::PolicyType::Always); scroll.set_widget_name("scolllist"); scroll.set_vexpand(true); let liststore = Rc::new(ListStore::new(&[glib::Type::STRING])); for option in options.iter() { liststore.set(&liststore.append(), &[(0, option)]); } let treeview = Rc::new(TreeView::with_model(&*liststore)); treeview.set_widget_name("item"); treeview.set_vexpand(true); scroll.add(&*treeview); let renderer = gtk::CellRendererText::new(); let column = gtk::TreeViewColumn::new(); TreeViewColumnExt::pack_start(&column, &renderer, true); TreeViewColumnExt::add_attribute(&column, &renderer, "text", 0); treeview.set_headers_visible(false); treeview.append_column(&column); treeview.set_can_focus(false); let path = TreePath::new_first(); treeview.set_cursor(&path, gtk::TreeViewColumn::NONE, false); { let scroll = Rc::clone(&scroll.clone()); let options = Rc::clone(&options); let treeview = Rc::clone(&treeview); let liststore = Rc::clone(&liststore); entry.connect_changed(move |entry| { if entry.text().is_empty() { scroll.vadjustment().set_value(0.0); } liststore.clear(); for option in filter_matches(&entry.text().to_string(), &options) { liststore.set(&liststore.append(), &[(0, &option)]); } treeview.set_model(Some(&*liststore)); let path = TreePath::new_first(); treeview.set_cursor(&path, gtk::TreeViewColumn::NONE, false); }); } { let win = &window; let window = Rc::clone(&window); let treeview = Rc::clone(&treeview); let entry = Rc::clone(&entry); let liststore = Rc::clone(&liststore); win.connect_key_press_event(move |_, event| { match event.keyval() { ESCAPE => { window.close(); } RETURN => { let sel = treeview.cursor(); if !event.state().contains(ModifierType::CONTROL_MASK) { if let Some(sel) = sel.0 { let item : usize = sel.to_string().parse().unwrap(); let mut i = 0; for option in filter_matches(&entry.text().to_string(), &options) { if i == item { println!("{}", option); break; } i += 1; } } else { println!("{}", entry.text()); } } else { println!("{}", entry.text()); } window.close(); } UP => { if let Some(mut c) = treeview.cursor().0 { c.prev(); let it = liststore.iter(&c).unwrap(); let next_path = liststore.path(&it).unwrap(); treeview.set_cursor(&next_path, gtk::TreeViewColumn::NONE, false); } return Inhibit(true) } DOWN => { if let Some(mut c) = treeview.cursor().0 { c.next(); if let Some(it) = liststore.iter(&c) { let next_path = liststore.path(&it).unwrap(); treeview.set_cursor(&next_path, gtk::TreeViewColumn::NONE, false); } } return Inhibit(true) } _ => () } Inhibit(false) }); } container.add(&*entry); container.add(&*scroll); window.show_all(); } fn main() { let application = gtk::Application::new( Some("org.n8.hyprmenu"), Default::default(), ); application.connect_activate(build_ui); application.run(); }