WIP
This commit is contained in:
parent
7c310b36e1
commit
3cbf7dd341
@ -1,8 +1,8 @@
|
|||||||
use directories::UserDirs;
|
use directories::UserDirs;
|
||||||
use gio::prelude::FileExt;
|
use gio::prelude::FileExt;
|
||||||
use glib::object::Cast;
|
use glib::object::Cast;
|
||||||
|
use std::cell::RefCell;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
use std::{cell::RefCell, env};
|
|
||||||
use walkdir::{DirEntry, WalkDir};
|
use walkdir::{DirEntry, WalkDir};
|
||||||
|
|
||||||
use crate::{LaunchError, LauncherListItem, LauncherPlugin};
|
use crate::{LaunchError, LauncherListItem, LauncherPlugin};
|
||||||
@ -31,10 +31,7 @@ impl LauncherListItem for FileEntry {
|
|||||||
fn execute(&self) -> Result<(), LaunchError> {
|
fn execute(&self) -> Result<(), LaunchError> {
|
||||||
let file_uri = gio::File::for_path(&self.path);
|
let file_uri = gio::File::for_path(&self.path);
|
||||||
let ctx = gio::AppLaunchContext::new();
|
let ctx = gio::AppLaunchContext::new();
|
||||||
match gio::AppInfo::launch_default_for_uri(
|
match gio::AppInfo::launch_default_for_uri(file_uri.uri().as_str(), Some(&ctx)) {
|
||||||
file_uri.uri().as_str(),
|
|
||||||
Some(&ctx),
|
|
||||||
) {
|
|
||||||
Err(_) => Err(LaunchError::CouldNotLaunch(
|
Err(_) => Err(LaunchError::CouldNotLaunch(
|
||||||
"Error opening file".to_string(),
|
"Error opening file".to_string(),
|
||||||
)),
|
)),
|
||||||
@ -57,6 +54,14 @@ impl LauncherListItem for FileEntry {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: There should be a method add_search_path to add new paths
|
||||||
|
// when the plugin is getting initialized. After all paths are
|
||||||
|
// added I should get the lowest common denominator. So for example
|
||||||
|
// if we have:
|
||||||
|
// $HOME/Documents
|
||||||
|
// $HOME/Documents/wallpapers
|
||||||
|
// Then we should just keep $HOME/Documents since wallpapers
|
||||||
|
// will be included in it anyways
|
||||||
pub struct FileSearchPlugin {
|
pub struct FileSearchPlugin {
|
||||||
search_paths: Vec<PathBuf>,
|
search_paths: Vec<PathBuf>,
|
||||||
skip_dirs: Vec<String>,
|
skip_dirs: Vec<String>,
|
||||||
|
@ -1,19 +1,19 @@
|
|||||||
use crate::launcher::WaycastLauncher;
|
use crate::launcher::WaycastLauncher;
|
||||||
|
use gio::ListStore;
|
||||||
|
use gio::prelude::ApplicationExt;
|
||||||
use gtk::gdk::Texture;
|
use gtk::gdk::Texture;
|
||||||
use gtk::gdk_pixbuf::Pixbuf;
|
use gtk::gdk_pixbuf::Pixbuf;
|
||||||
use gtk::prelude::*;
|
use gtk::prelude::*;
|
||||||
use gtk::{
|
|
||||||
ApplicationWindow, Box as GtkBox, Entry, EventControllerKey, IconTheme, Image,
|
|
||||||
Label, ListView, Orientation, ScrolledWindow, SignalListItemFactory, SingleSelection,
|
|
||||||
};
|
|
||||||
use gio::ListStore;
|
|
||||||
use gtk::subclass::prelude::ObjectSubclassIsExt;
|
use gtk::subclass::prelude::ObjectSubclassIsExt;
|
||||||
|
use gtk::{
|
||||||
|
ApplicationWindow, Box as GtkBox, Entry, EventControllerKey, IconTheme, Image, Label, ListView,
|
||||||
|
Orientation, ScrolledWindow, SignalListItemFactory, SingleSelection,
|
||||||
|
};
|
||||||
use gtk4_layer_shell as layerShell;
|
use gtk4_layer_shell as layerShell;
|
||||||
use layerShell::LayerShell;
|
use layerShell::LayerShell;
|
||||||
|
use std::cell::RefCell;
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
use std::cell::RefCell;
|
|
||||||
use gio::prelude::ApplicationExt;
|
|
||||||
|
|
||||||
// GObject wrapper to store LauncherListItem in GTK's model system
|
// GObject wrapper to store LauncherListItem in GTK's model system
|
||||||
mod imp {
|
mod imp {
|
||||||
@ -47,28 +47,28 @@ impl LauncherItemObject {
|
|||||||
pub fn new(title: String, description: Option<String>, icon: String, index: usize) -> Self {
|
pub fn new(title: String, description: Option<String>, icon: String, index: usize) -> Self {
|
||||||
let obj: Self = glib::Object::new();
|
let obj: Self = glib::Object::new();
|
||||||
let imp = obj.imp();
|
let imp = obj.imp();
|
||||||
|
|
||||||
// Store the data
|
// Store the data
|
||||||
*imp.title.borrow_mut() = title;
|
*imp.title.borrow_mut() = title;
|
||||||
*imp.description.borrow_mut() = description;
|
*imp.description.borrow_mut() = description;
|
||||||
*imp.icon.borrow_mut() = icon;
|
*imp.icon.borrow_mut() = icon;
|
||||||
*imp.index.borrow_mut() = index;
|
*imp.index.borrow_mut() = index;
|
||||||
|
|
||||||
obj
|
obj
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn title(&self) -> String {
|
pub fn title(&self) -> String {
|
||||||
self.imp().title.borrow().clone()
|
self.imp().title.borrow().clone()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn icon(&self) -> String {
|
pub fn icon(&self) -> String {
|
||||||
self.imp().icon.borrow().clone()
|
self.imp().icon.borrow().clone()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn description(&self) -> Option<String> {
|
pub fn description(&self) -> Option<String> {
|
||||||
self.imp().description.borrow().clone()
|
self.imp().description.borrow().clone()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn index(&self) -> usize {
|
pub fn index(&self) -> usize {
|
||||||
*self.imp().index.borrow()
|
*self.imp().index.borrow()
|
||||||
}
|
}
|
||||||
@ -76,12 +76,6 @@ impl LauncherItemObject {
|
|||||||
|
|
||||||
pub struct GtkLauncherUI {
|
pub struct GtkLauncherUI {
|
||||||
window: ApplicationWindow,
|
window: ApplicationWindow,
|
||||||
list_view: ListView,
|
|
||||||
list_store: ListStore,
|
|
||||||
selection: SingleSelection,
|
|
||||||
search_input: Entry,
|
|
||||||
launcher: Rc<RefCell<WaycastLauncher>>,
|
|
||||||
app: gtk::Application,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl GtkLauncherUI {
|
impl GtkLauncherUI {
|
||||||
@ -111,7 +105,7 @@ impl GtkLauncherUI {
|
|||||||
|
|
||||||
// Create factory for rendering list items
|
// Create factory for rendering list items
|
||||||
let factory = SignalListItemFactory::new();
|
let factory = SignalListItemFactory::new();
|
||||||
|
|
||||||
// Setup factory to create widgets
|
// Setup factory to create widgets
|
||||||
factory.connect_setup(move |_, list_item| {
|
factory.connect_setup(move |_, list_item| {
|
||||||
let container = GtkBox::new(Orientation::Horizontal, 10);
|
let container = GtkBox::new(Orientation::Horizontal, 10);
|
||||||
@ -119,25 +113,26 @@ impl GtkLauncherUI {
|
|||||||
container.add_css_class("launcher-item");
|
container.add_css_class("launcher-item");
|
||||||
list_item.set_child(Some(&container));
|
list_item.set_child(Some(&container));
|
||||||
});
|
});
|
||||||
|
|
||||||
// Setup factory to bind data to widgets
|
// Setup factory to bind data to widgets
|
||||||
factory.connect_bind(move |_, list_item| {
|
factory.connect_bind(move |_, list_item| {
|
||||||
let child = list_item.child().and_downcast::<GtkBox>().unwrap();
|
let child = list_item.child().and_downcast::<GtkBox>().unwrap();
|
||||||
|
|
||||||
// Clear existing children
|
// Clear existing children
|
||||||
while let Some(first_child) = child.first_child() {
|
while let Some(first_child) = child.first_child() {
|
||||||
child.remove(&first_child);
|
child.remove(&first_child);
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(item_obj) = list_item.item().and_downcast::<LauncherItemObject>() {
|
if let Some(item_obj) = list_item.item().and_downcast::<LauncherItemObject>() {
|
||||||
let display = gtk::gdk::Display::default().unwrap();
|
let display = gtk::gdk::Display::default().unwrap();
|
||||||
let icon_theme = gtk::IconTheme::for_display(&display);
|
let icon_theme = gtk::IconTheme::for_display(&display);
|
||||||
let icon_size = 48;
|
let icon_size = 48;
|
||||||
|
|
||||||
// Create icon
|
// Create icon
|
||||||
let image: gtk::Image;
|
let image: gtk::Image;
|
||||||
if let Some(icon_path) = find_icon_file(&item_obj.icon(), "48", &icon_theme) {
|
if let Some(icon_path) = find_icon_file(&item_obj.icon(), "48", &icon_theme) {
|
||||||
image = match Pixbuf::from_file_at_scale(icon_path, icon_size, icon_size, true) {
|
image = match Pixbuf::from_file_at_scale(icon_path, icon_size, icon_size, true)
|
||||||
|
{
|
||||||
Ok(pb) => {
|
Ok(pb) => {
|
||||||
let tex = Texture::for_pixbuf(&pb);
|
let tex = Texture::for_pixbuf(&pb);
|
||||||
gtk::Image::from_paintable(Some(&tex))
|
gtk::Image::from_paintable(Some(&tex))
|
||||||
@ -157,14 +152,14 @@ impl GtkLauncherUI {
|
|||||||
image.set_pixel_size(icon_size);
|
image.set_pixel_size(icon_size);
|
||||||
image.set_widget_name("item-icon");
|
image.set_widget_name("item-icon");
|
||||||
image.add_css_class("launcher-icon");
|
image.add_css_class("launcher-icon");
|
||||||
|
|
||||||
// Create text container (vertical box for title + description)
|
// Create text container (vertical box for title + description)
|
||||||
let text_box = GtkBox::new(Orientation::Vertical, 2);
|
let text_box = GtkBox::new(Orientation::Vertical, 2);
|
||||||
text_box.set_hexpand(true);
|
text_box.set_hexpand(true);
|
||||||
text_box.set_valign(gtk::Align::Center);
|
text_box.set_valign(gtk::Align::Center);
|
||||||
text_box.set_widget_name("item-text");
|
text_box.set_widget_name("item-text");
|
||||||
text_box.add_css_class("launcher-text");
|
text_box.add_css_class("launcher-text");
|
||||||
|
|
||||||
// Create title label
|
// Create title label
|
||||||
let title_label = Label::new(Some(&item_obj.title()));
|
let title_label = Label::new(Some(&item_obj.title()));
|
||||||
title_label.set_xalign(0.0);
|
title_label.set_xalign(0.0);
|
||||||
@ -172,7 +167,7 @@ impl GtkLauncherUI {
|
|||||||
title_label.set_widget_name("item-title");
|
title_label.set_widget_name("item-title");
|
||||||
title_label.add_css_class("launcher-title");
|
title_label.add_css_class("launcher-title");
|
||||||
text_box.append(&title_label);
|
text_box.append(&title_label);
|
||||||
|
|
||||||
// Create description label if description exists
|
// Create description label if description exists
|
||||||
if let Some(description) = item_obj.description() {
|
if let Some(description) = item_obj.description() {
|
||||||
let desc_label = Label::new(Some(&description));
|
let desc_label = Label::new(Some(&description));
|
||||||
@ -183,12 +178,12 @@ impl GtkLauncherUI {
|
|||||||
desc_label.set_opacity(0.7);
|
desc_label.set_opacity(0.7);
|
||||||
text_box.append(&desc_label);
|
text_box.append(&desc_label);
|
||||||
}
|
}
|
||||||
|
|
||||||
child.append(&image);
|
child.append(&image);
|
||||||
child.append(&text_box);
|
child.append(&text_box);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
let list_view = ListView::new(Some(selection.clone()), Some(factory));
|
let list_view = ListView::new(Some(selection.clone()), Some(factory));
|
||||||
list_view.set_vexpand(true);
|
list_view.set_vexpand(true);
|
||||||
list_view.set_can_focus(true);
|
list_view.set_can_focus(true);
|
||||||
@ -198,12 +193,12 @@ impl GtkLauncherUI {
|
|||||||
scrolled_window.set_child(Some(&list_view));
|
scrolled_window.set_child(Some(&list_view));
|
||||||
scrolled_window.set_widget_name("results-container");
|
scrolled_window.set_widget_name("results-container");
|
||||||
scrolled_window.add_css_class("launcher-results-container");
|
scrolled_window.add_css_class("launcher-results-container");
|
||||||
|
|
||||||
main_box.append(&search_input);
|
main_box.append(&search_input);
|
||||||
main_box.append(&scrolled_window);
|
main_box.append(&scrolled_window);
|
||||||
main_box.set_widget_name("main-container");
|
main_box.set_widget_name("main-container");
|
||||||
main_box.add_css_class("launcher-main");
|
main_box.add_css_class("launcher-main");
|
||||||
|
|
||||||
window.set_child(Some(&main_box));
|
window.set_child(Some(&main_box));
|
||||||
window.set_widget_name("launcher-window");
|
window.set_widget_name("launcher-window");
|
||||||
window.add_css_class("launcher-window");
|
window.add_css_class("launcher-window");
|
||||||
@ -236,7 +231,7 @@ impl GtkLauncherUI {
|
|||||||
} else {
|
} else {
|
||||||
launcher_ref.search(&query)
|
launcher_ref.search(&query)
|
||||||
};
|
};
|
||||||
|
|
||||||
// Update the list store
|
// Update the list store
|
||||||
list_store_for_search.remove_all();
|
list_store_for_search.remove_all();
|
||||||
for (index, entry) in results.iter().enumerate() {
|
for (index, entry) in results.iter().enumerate() {
|
||||||
@ -244,7 +239,7 @@ impl GtkLauncherUI {
|
|||||||
entry.title(),
|
entry.title(),
|
||||||
entry.description(),
|
entry.description(),
|
||||||
entry.icon(),
|
entry.icon(),
|
||||||
index
|
index,
|
||||||
);
|
);
|
||||||
list_store_for_search.append(&item_obj);
|
list_store_for_search.append(&item_obj);
|
||||||
}
|
}
|
||||||
@ -310,7 +305,10 @@ impl GtkLauncherUI {
|
|||||||
let launcher_for_activate = launcher.clone();
|
let launcher_for_activate = launcher.clone();
|
||||||
let app_for_activate = app.clone();
|
let app_for_activate = app.clone();
|
||||||
list_view.connect_activate(move |_, position| {
|
list_view.connect_activate(move |_, position| {
|
||||||
match launcher_for_activate.borrow().execute_item(position as usize) {
|
match launcher_for_activate
|
||||||
|
.borrow()
|
||||||
|
.execute_item(position as usize)
|
||||||
|
{
|
||||||
Ok(_) => app_for_activate.quit(),
|
Ok(_) => app_for_activate.quit(),
|
||||||
Err(e) => eprintln!("Failed to launch app: {:?}", e),
|
Err(e) => eprintln!("Failed to launch app: {:?}", e),
|
||||||
}
|
}
|
||||||
@ -320,15 +318,11 @@ impl GtkLauncherUI {
|
|||||||
let mut launcher_ref = launcher.borrow_mut();
|
let mut launcher_ref = launcher.borrow_mut();
|
||||||
let results = launcher_ref.get_default_results();
|
let results = launcher_ref.get_default_results();
|
||||||
for (index, entry) in results.iter().enumerate() {
|
for (index, entry) in results.iter().enumerate() {
|
||||||
let item_obj = LauncherItemObject::new(
|
let item_obj =
|
||||||
entry.title(),
|
LauncherItemObject::new(entry.title(), entry.description(), entry.icon(), index);
|
||||||
entry.description(),
|
|
||||||
entry.icon(),
|
|
||||||
index
|
|
||||||
);
|
|
||||||
list_store.append(&item_obj);
|
list_store.append(&item_obj);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Select the first item if available
|
// Select the first item if available
|
||||||
if list_store.n_items() > 0 {
|
if list_store.n_items() > 0 {
|
||||||
selection.set_selected(0);
|
selection.set_selected(0);
|
||||||
@ -337,12 +331,6 @@ impl GtkLauncherUI {
|
|||||||
|
|
||||||
Self {
|
Self {
|
||||||
window,
|
window,
|
||||||
list_view,
|
|
||||||
list_store,
|
|
||||||
selection,
|
|
||||||
search_input,
|
|
||||||
launcher,
|
|
||||||
app: app.clone(),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -351,14 +339,14 @@ impl GtkLauncherUI {
|
|||||||
pub fn show(&self) {
|
pub fn show(&self) {
|
||||||
self.window.present();
|
self.window.present();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Apply default built-in CSS styles
|
/// Apply default built-in CSS styles
|
||||||
pub fn apply_default_css(&self) -> Result<(), String> {
|
pub fn apply_default_css(&self) -> Result<(), String> {
|
||||||
const DEFAULT_CSS: &str = include_str!("default.css");
|
const DEFAULT_CSS: &str = include_str!("default.css");
|
||||||
|
|
||||||
let css_provider = gtk::CssProvider::new();
|
let css_provider = gtk::CssProvider::new();
|
||||||
css_provider.load_from_data(DEFAULT_CSS);
|
css_provider.load_from_data(DEFAULT_CSS);
|
||||||
|
|
||||||
if let Some(display) = gtk::gdk::Display::default() {
|
if let Some(display) = gtk::gdk::Display::default() {
|
||||||
gtk::style_context_add_provider_for_display(
|
gtk::style_context_add_provider_for_display(
|
||||||
&display,
|
&display,
|
||||||
@ -370,15 +358,18 @@ impl GtkLauncherUI {
|
|||||||
Err("Could not get default display".to_string())
|
Err("Could not get default display".to_string())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn apply_css<P: AsRef<Path>>(&self, css_path: P) -> Result<(), String> {
|
pub fn apply_css<P: AsRef<Path>>(&self, css_path: P) -> Result<(), String> {
|
||||||
let css_provider = gtk::CssProvider::new();
|
let css_provider = gtk::CssProvider::new();
|
||||||
|
|
||||||
// Check if file exists first
|
// Check if file exists first
|
||||||
if !css_path.as_ref().exists() {
|
if !css_path.as_ref().exists() {
|
||||||
return Err(format!("CSS file does not exist: {}", css_path.as_ref().display()));
|
return Err(format!(
|
||||||
|
"CSS file does not exist: {}",
|
||||||
|
css_path.as_ref().display()
|
||||||
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Try to load the CSS file
|
// Try to load the CSS file
|
||||||
// Note: load_from_path doesn't return a Result, it panics on error
|
// Note: load_from_path doesn't return a Result, it panics on error
|
||||||
// So we'll use a different approach with error handling
|
// So we'll use a different approach with error handling
|
||||||
@ -386,7 +377,7 @@ impl GtkLauncherUI {
|
|||||||
match fs::read_to_string(css_path.as_ref()) {
|
match fs::read_to_string(css_path.as_ref()) {
|
||||||
Ok(css_content) => {
|
Ok(css_content) => {
|
||||||
css_provider.load_from_data(&css_content);
|
css_provider.load_from_data(&css_content);
|
||||||
|
|
||||||
// Apply the CSS to the display
|
// Apply the CSS to the display
|
||||||
if let Some(display) = gtk::gdk::Display::default() {
|
if let Some(display) = gtk::gdk::Display::default() {
|
||||||
gtk::style_context_add_provider_for_display(
|
gtk::style_context_add_provider_for_display(
|
||||||
@ -399,7 +390,7 @@ impl GtkLauncherUI {
|
|||||||
Err("Could not get default display".to_string())
|
Err("Could not get default display".to_string())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Err(e) => Err(format!("Failed to read CSS file: {}", e))
|
Err(e) => Err(format!("Failed to read CSS file: {}", e)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -487,4 +478,4 @@ fn find_icon_file(
|
|||||||
}
|
}
|
||||||
|
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user