Inline some clones to make code more readable, add config, implement dimming -> locking -> sleeping into idle daemon, fix waiting on network thread, implement run menu with application launching and math evaluation,
This commit is contained in:
@@ -1,27 +1,44 @@
|
||||
use gtk::prelude::{BoxExt, GtkWindowExt, WidgetExt};
|
||||
use crate::LOGGER;
|
||||
use crate::backend::window_manager_hypr::HyprlandComms;
|
||||
use freedesktop_desktop_entry::{default_paths, get_languages_from_env};
|
||||
use glib::clone;
|
||||
use glib::prelude::Cast;
|
||||
use gtk::gdk::{Display, Monitor};
|
||||
use gtk::prelude::{
|
||||
BoxExt, ButtonExt, DisplayExt, EditableExt, EntryExt, GtkWindowExt, ListModelExt, MonitorExt,
|
||||
WidgetExt,
|
||||
};
|
||||
use gtk::{Align, Application, ApplicationWindow, Orientation, PolicyType, gdk};
|
||||
use gtk4_layer_shell::{KeyboardMode, Layer, LayerShell};
|
||||
use gtk4_layer_shell::{Edge, KeyboardMode, Layer, LayerShell};
|
||||
use jmb_logger_rs::*;
|
||||
use nucleo_matcher::pattern::{CaseMatching, Normalization, Pattern};
|
||||
use nucleo_matcher::{Config, Matcher};
|
||||
use std::cell::RefCell;
|
||||
use std::collections::HashMap;
|
||||
use std::process::Command;
|
||||
use std::rc::Rc;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct RunMenu {
|
||||
window: ApplicationWindow,
|
||||
entry: gtk::Entry,
|
||||
entries: Rc<RefCell<HashMap<String, Vec<String>>>>,
|
||||
}
|
||||
|
||||
impl RunMenu {
|
||||
pub fn create(application: Application) -> Self {
|
||||
let window = gtk::ApplicationWindow::builder()
|
||||
let window = ApplicationWindow::builder()
|
||||
.application(&application)
|
||||
.title("JmbRunMenu")
|
||||
.default_width(500)
|
||||
.default_height(300)
|
||||
.css_classes(["run-menu"])
|
||||
.build();
|
||||
|
||||
window.init_layer_shell();
|
||||
window.set_layer(Layer::Overlay);
|
||||
window.set_halign(Align::Center);
|
||||
window.set_valign(Align::Center);
|
||||
window.set_size_request(500, 300);
|
||||
window.set_width_request(500);
|
||||
window.set_anchor(Edge::Top, true);
|
||||
window.set_keyboard_mode(KeyboardMode::Exclusive);
|
||||
|
||||
let v_box = gtk::Box::new(Orientation::Vertical, 0);
|
||||
@@ -31,28 +48,109 @@ impl RunMenu {
|
||||
|
||||
let scrolled = gtk::ScrolledWindow::new();
|
||||
scrolled.set_policy(PolicyType::Never, PolicyType::Automatic);
|
||||
scrolled.set_vexpand(true);
|
||||
scrolled.set_valign(Align::Fill);
|
||||
scrolled.set_vexpand(false);
|
||||
scrolled.set_propagate_natural_height(true);
|
||||
scrolled.set_min_content_height(0);
|
||||
scrolled.set_max_content_height(300);
|
||||
|
||||
let content_box = gtk::Box::new(Orientation::Vertical, 0);
|
||||
content_box.add_css_class("content-box");
|
||||
|
||||
for _ in 0..10 {
|
||||
let label = gtk::Label::new(Some("Test"));
|
||||
content_box.append(&label);
|
||||
}
|
||||
|
||||
content_box.set_hexpand(true);
|
||||
scrolled.set_child(Some(&content_box));
|
||||
|
||||
v_box.set_vexpand(false);
|
||||
v_box.set_valign(Align::Start);
|
||||
v_box.append(&entry);
|
||||
v_box.append(&scrolled);
|
||||
|
||||
window.set_child(Some(&v_box));
|
||||
|
||||
let display = Display::default().expect("Couldn't get display");
|
||||
let monitor = display
|
||||
.monitors()
|
||||
.item(HyprlandComms::current_monitor() as u32)
|
||||
.expect("Couldn't get monitor")
|
||||
.downcast::<Monitor>()
|
||||
.ok();
|
||||
window.set_monitor(monitor.as_ref());
|
||||
if let Some(monitor) = monitor {
|
||||
let geometry = monitor.geometry();
|
||||
let (_, natural_height, _, _) =
|
||||
window.child().unwrap().measure(Orientation::Vertical, -1);
|
||||
println!("Monitor height: {}", geometry.height());
|
||||
let top_margin = (geometry.height()) / 2;
|
||||
window.set_margin(Edge::Top, top_margin - 100);
|
||||
} else {
|
||||
window.set_valign(Align::Center);
|
||||
}
|
||||
|
||||
content_box
|
||||
.observe_children()
|
||||
.connect_items_changed(move |model, _, _, _| {
|
||||
if model.n_items() == 0 {
|
||||
println!("Hide");
|
||||
} else {
|
||||
println!("Reveal");
|
||||
}
|
||||
});
|
||||
|
||||
let window_clone = window.clone();
|
||||
let instance = Self {
|
||||
window: window_clone,
|
||||
entry: entry.clone(),
|
||||
entries: Rc::new(RefCell::new(HashMap::new())),
|
||||
};
|
||||
let instance_clone = instance.clone();
|
||||
let matcher_rc = Rc::new(RefCell::new(Matcher::new(Config::DEFAULT)));
|
||||
entry.connect_changed(clone!(
|
||||
#[strong]
|
||||
content_box,
|
||||
move |e| {
|
||||
while let Some(child) = content_box.first_child() {
|
||||
content_box.remove(&child);
|
||||
}
|
||||
if e.text_length() > 0 {
|
||||
let text = e.text();
|
||||
if text.starts_with("=") {
|
||||
if let Ok(eval) = meval::eval_str(&text[1..]) {
|
||||
let label = gtk::Label::new(Some(&format!("{eval}")));
|
||||
label.add_css_class("exec-button");
|
||||
label.set_halign(Align::Start);
|
||||
label.set_hexpand(true);
|
||||
content_box.append(&label);
|
||||
}
|
||||
} else {
|
||||
instance_clone.list_matching(e, &content_box, &mut matcher_rc.borrow_mut());
|
||||
instance_clone.window.set_default_size(500, -1);
|
||||
}
|
||||
}
|
||||
}
|
||||
));
|
||||
entry.connect_has_focus_notify(clone!(
|
||||
#[strong]
|
||||
content_box,
|
||||
move |e| {
|
||||
if let Some(child) = content_box.first_child() {
|
||||
if e.has_focus() {
|
||||
child.add_css_class("suggested");
|
||||
} else {
|
||||
child.remove_css_class("suggested");
|
||||
}
|
||||
}
|
||||
}
|
||||
));
|
||||
entry.connect_activate(clone!(
|
||||
#[strong]
|
||||
content_box,
|
||||
move |_| {
|
||||
if let Some(child) = content_box.first_child()
|
||||
&& let Ok(button) = child.downcast::<gtk::Button>()
|
||||
{
|
||||
button.activate();
|
||||
}
|
||||
}
|
||||
));
|
||||
|
||||
let instance_clone = instance.clone();
|
||||
let key_controller = gtk::EventControllerKey::new();
|
||||
key_controller.connect_key_pressed(move |_, key, _, _| {
|
||||
@@ -69,9 +167,82 @@ impl RunMenu {
|
||||
|
||||
pub fn hide(&self) {
|
||||
self.window.set_visible(false);
|
||||
self.entry.set_text("");
|
||||
}
|
||||
|
||||
pub fn show(&self) {
|
||||
pub fn show(&mut self) {
|
||||
self.load_desktop_files();
|
||||
self.window.present();
|
||||
}
|
||||
|
||||
fn create_entry(&self, content_box: >k::Box, entry: &String) -> gtk::Button {
|
||||
let entry_clone = entry.clone();
|
||||
let button = gtk::Button::new();
|
||||
button.set_halign(Align::Fill);
|
||||
button.set_hexpand(true);
|
||||
|
||||
let label = gtk::Label::new(Some(entry));
|
||||
label.set_halign(Align::Start);
|
||||
button.set_child(Some(&label));
|
||||
|
||||
button.add_css_class("exec-button");
|
||||
let entry_map = self.entries.clone();
|
||||
let instance = self.clone();
|
||||
button.connect_clicked(move |_| {
|
||||
let map = entry_map.borrow();
|
||||
let exec = map.get(&entry_clone);
|
||||
if let Some(exec) = exec {
|
||||
let executable = &exec[0];
|
||||
if let Err(err) = Command::new(executable).args(&exec[1..]).spawn() {
|
||||
warn!(&LOGGER, "Couldn't execute app: {err}");
|
||||
}
|
||||
instance.hide();
|
||||
}
|
||||
});
|
||||
content_box.append(&button);
|
||||
button
|
||||
}
|
||||
|
||||
fn list_matching(
|
||||
&self,
|
||||
entry: >k::Entry,
|
||||
content_box: >k::Box,
|
||||
matcher: &mut Matcher,
|
||||
) -> bool {
|
||||
let keys: Vec<String> = self.entries.borrow().keys().map(|i| i.clone()).collect();
|
||||
let mut first = true;
|
||||
let mut added = false;
|
||||
for (entry, _) in Pattern::parse(&entry.text(), CaseMatching::Ignore, Normalization::Smart)
|
||||
.match_list(keys, matcher)
|
||||
.iter()
|
||||
{
|
||||
let button = self.create_entry(content_box, &entry);
|
||||
if first {
|
||||
button.add_css_class("suggested");
|
||||
first = false;
|
||||
}
|
||||
added = true;
|
||||
}
|
||||
added
|
||||
}
|
||||
|
||||
fn load_desktop_files(&mut self) {
|
||||
let locales = get_languages_from_env();
|
||||
|
||||
let entries = freedesktop_desktop_entry::Iter::new(default_paths())
|
||||
.entries(Some(&locales))
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let mut entry_map = self.entries.borrow_mut();
|
||||
entry_map.clear();
|
||||
|
||||
for entry in entries {
|
||||
if let Ok(exec) = entry.parse_exec() {
|
||||
let name = entry.full_name(&locales);
|
||||
if let Some(name) = name {
|
||||
entry_map.insert(name.to_string(), exec);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user