Had claude un-abstract all that BS
This commit is contained in:
parent
2d909dd716
commit
7cf1b9efc7
10
src/main.rs
10
src/main.rs
@ -2,7 +2,6 @@ use gtk::Application;
|
||||
use gtk::prelude::*;
|
||||
use waycast::launcher::WaycastLauncher;
|
||||
use waycast::plugins;
|
||||
use waycast::ui::controller::LauncherController;
|
||||
use waycast::ui::gtk::GtkLauncherUI;
|
||||
|
||||
fn main() {
|
||||
@ -17,12 +16,9 @@ fn main() {
|
||||
.add_plugin(Box::new(plugins::file_search::FileSearchPlugin::new()))
|
||||
.init();
|
||||
|
||||
// Create the GTK UI
|
||||
let ui = GtkLauncherUI::new(app);
|
||||
|
||||
// Create and run the controller
|
||||
let controller = LauncherController::new(launcher, ui, app.clone());
|
||||
controller.run();
|
||||
// Create and show the GTK UI
|
||||
let ui = GtkLauncherUI::new(app, launcher);
|
||||
ui.show();
|
||||
});
|
||||
|
||||
app.run();
|
||||
|
@ -1,107 +0,0 @@
|
||||
use super::gtk::GtkLauncherUI;
|
||||
use super::traits::{LauncherUI, UIEvent};
|
||||
use crate::{LaunchError, launcher::WaycastLauncher};
|
||||
use gio::prelude::ApplicationExt;
|
||||
use gtk::glib;
|
||||
use std::cell::RefCell;
|
||||
use std::rc::Rc;
|
||||
use std::sync::mpsc::{self, Receiver};
|
||||
|
||||
/// Controller that coordinates between the core launcher and UI
|
||||
pub struct LauncherController {
|
||||
launcher: Rc<RefCell<WaycastLauncher>>,
|
||||
ui: GtkLauncherUI,
|
||||
event_receiver: Receiver<UIEvent>,
|
||||
app: gtk::Application,
|
||||
}
|
||||
|
||||
impl LauncherController {
|
||||
pub fn new(launcher: WaycastLauncher, mut ui: GtkLauncherUI, app: gtk::Application) -> Self {
|
||||
let (event_sender, event_receiver) = mpsc::channel();
|
||||
|
||||
// Set up the event sender in the UI
|
||||
ui.set_event_sender(event_sender);
|
||||
|
||||
Self {
|
||||
launcher: Rc::new(RefCell::new(launcher)),
|
||||
ui,
|
||||
event_receiver,
|
||||
app,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn initialize(&mut self) {
|
||||
// Populate with default results
|
||||
let mut launcher = self.launcher.borrow_mut();
|
||||
let results = launcher.get_default_results();
|
||||
self.ui.set_results(results);
|
||||
}
|
||||
|
||||
pub fn show(&self) {
|
||||
self.ui.show();
|
||||
}
|
||||
|
||||
pub fn handle_event(&mut self, event: UIEvent) -> Result<(), LaunchError> {
|
||||
match event {
|
||||
UIEvent::SearchChanged(query) => {
|
||||
let mut launcher = self.launcher.borrow_mut();
|
||||
let results = if query.trim().is_empty() {
|
||||
launcher.get_default_results()
|
||||
} else {
|
||||
launcher.search(&query)
|
||||
};
|
||||
self.ui.set_results(results);
|
||||
}
|
||||
|
||||
UIEvent::ItemActivated(index) => match self.launcher.borrow().execute_item(index) {
|
||||
Ok(_) => {
|
||||
// Exit the application completely instead of just hiding
|
||||
self.app.quit();
|
||||
}
|
||||
Err(e) => {
|
||||
eprintln!("Failed to launch item: {:?}", e);
|
||||
return Err(e);
|
||||
}
|
||||
},
|
||||
|
||||
UIEvent::ItemSelected(_index) => {
|
||||
// Handle selection change if needed
|
||||
// For now, this is just for keyboard navigation
|
||||
}
|
||||
|
||||
UIEvent::CloseRequested => {
|
||||
// Exit the application completely instead of just hiding
|
||||
self.app.quit();
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn process_events(&mut self) -> Result<(), LaunchError> {
|
||||
while let Ok(event) = self.event_receiver.try_recv() {
|
||||
self.handle_event(event)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn run(mut self) {
|
||||
self.initialize();
|
||||
self.show();
|
||||
|
||||
// Set up periodic event processing using glib's idle callback
|
||||
let controller = Rc::new(RefCell::new(self));
|
||||
let controller_clone = controller.clone();
|
||||
|
||||
glib::idle_add_local(move || {
|
||||
if let Ok(mut ctrl) = controller_clone.try_borrow_mut() {
|
||||
if let Err(e) = ctrl.process_events() {
|
||||
eprintln!("Error processing events: {:?}", e);
|
||||
}
|
||||
}
|
||||
glib::ControlFlow::Continue
|
||||
});
|
||||
|
||||
// The GTK main loop will handle the rest
|
||||
}
|
||||
}
|
@ -1,10 +1,9 @@
|
||||
use crate::LauncherListItem;
|
||||
use super::traits::{LauncherUI, UIEvent};
|
||||
use crate::launcher::WaycastLauncher;
|
||||
use gtk::gdk::Texture;
|
||||
use gtk::gdk_pixbuf::Pixbuf;
|
||||
use gtk::prelude::*;
|
||||
use gtk::{
|
||||
Application, ApplicationWindow, Box as GtkBox, Entry, EventControllerKey, IconTheme, Image,
|
||||
ApplicationWindow, Box as GtkBox, Entry, EventControllerKey, IconTheme, Image,
|
||||
Label, ListView, Orientation, ScrolledWindow, SignalListItemFactory, SingleSelection,
|
||||
};
|
||||
use gio::ListStore;
|
||||
@ -12,7 +11,9 @@ use gtk::subclass::prelude::ObjectSubclassIsExt;
|
||||
use gtk4_layer_shell as layerShell;
|
||||
use layerShell::LayerShell;
|
||||
use std::path::PathBuf;
|
||||
use std::sync::mpsc::Sender;
|
||||
use std::rc::Rc;
|
||||
use std::cell::RefCell;
|
||||
use gio::prelude::ApplicationExt;
|
||||
|
||||
// GObject wrapper to store LauncherListItem in GTK's model system
|
||||
mod imp {
|
||||
@ -75,12 +76,13 @@ pub struct GtkLauncherUI {
|
||||
list_store: ListStore,
|
||||
selection: SingleSelection,
|
||||
search_input: Entry,
|
||||
event_sender: Option<Sender<UIEvent>>,
|
||||
current_results: Vec<Box<dyn LauncherListItem>>, // Keep reference for indexing
|
||||
launcher: Rc<RefCell<WaycastLauncher>>,
|
||||
app: gtk::Application,
|
||||
}
|
||||
|
||||
impl GtkLauncherUI {
|
||||
pub fn new(app: &Application) -> Self {
|
||||
pub fn new(app: >k::Application, launcher: WaycastLauncher) -> Self {
|
||||
let launcher = Rc::new(RefCell::new(launcher));
|
||||
let window = ApplicationWindow::builder()
|
||||
.application(app)
|
||||
.title("Waycast")
|
||||
@ -181,115 +183,20 @@ impl GtkLauncherUI {
|
||||
// Set initial focus to search input so user can start typing immediately
|
||||
search_input.grab_focus();
|
||||
|
||||
Self {
|
||||
window,
|
||||
list_view,
|
||||
list_store,
|
||||
selection,
|
||||
search_input,
|
||||
event_sender: None,
|
||||
current_results: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_event_sender(&mut self, sender: Sender<UIEvent>) {
|
||||
self.event_sender = Some(sender.clone());
|
||||
|
||||
// Connect search input signal
|
||||
let sender_clone = sender.clone();
|
||||
self.search_input.connect_changed(move |entry| {
|
||||
// Set up event handlers directly
|
||||
let launcher_for_search = launcher.clone();
|
||||
let list_store_for_search = list_store.clone();
|
||||
search_input.connect_changed(move |entry| {
|
||||
let query = entry.text().to_string();
|
||||
let _ = sender_clone.send(UIEvent::SearchChanged(query));
|
||||
});
|
||||
|
||||
// Connect Enter key activation for search input
|
||||
let sender_clone = sender.clone();
|
||||
let selection_clone = self.selection.clone();
|
||||
self.search_input.connect_activate(move |_| {
|
||||
if let Some(selected_item) = selection_clone.selected_item() {
|
||||
if let Some(item_obj) = selected_item.downcast_ref::<LauncherItemObject>() {
|
||||
let index = item_obj.index();
|
||||
let _ = sender_clone.send(UIEvent::ItemActivated(index));
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Add key handler for launcher-style navigation
|
||||
let search_key_controller = EventControllerKey::new();
|
||||
let selection_clone = self.selection.clone();
|
||||
search_key_controller.connect_key_pressed(move |_controller, keyval, _keycode, _state| {
|
||||
match keyval {
|
||||
gtk::gdk::Key::Down => {
|
||||
let current_pos = selection_clone.selected();
|
||||
let n_items = selection_clone.model().unwrap().n_items();
|
||||
if current_pos < n_items - 1 {
|
||||
selection_clone.set_selected(current_pos + 1);
|
||||
} else if n_items > 0 && current_pos == gtk::INVALID_LIST_POSITION {
|
||||
selection_clone.set_selected(0);
|
||||
}
|
||||
gtk::glib::Propagation::Stop
|
||||
}
|
||||
gtk::gdk::Key::Up => {
|
||||
let current_pos = selection_clone.selected();
|
||||
if current_pos > 0 {
|
||||
selection_clone.set_selected(current_pos - 1);
|
||||
}
|
||||
gtk::glib::Propagation::Stop
|
||||
}
|
||||
_ => gtk::glib::Propagation::Proceed,
|
||||
}
|
||||
});
|
||||
self.search_input.add_controller(search_key_controller);
|
||||
|
||||
// Add ESC key handler at window level
|
||||
let window_key_controller = EventControllerKey::new();
|
||||
let sender_clone = sender.clone();
|
||||
window_key_controller.connect_key_pressed(move |_controller, keyval, _keycode, _state| {
|
||||
if keyval == gtk::gdk::Key::Escape {
|
||||
let _ = sender_clone.send(UIEvent::CloseRequested);
|
||||
gtk::glib::Propagation::Stop
|
||||
let mut launcher_ref = launcher_for_search.borrow_mut();
|
||||
let results = if query.trim().is_empty() {
|
||||
launcher_ref.get_default_results()
|
||||
} else {
|
||||
gtk::glib::Propagation::Proceed
|
||||
}
|
||||
});
|
||||
self.window.add_controller(window_key_controller);
|
||||
launcher_ref.search(&query)
|
||||
};
|
||||
|
||||
// Connect list activation signal
|
||||
let sender_clone = sender;
|
||||
self.list_view.connect_activate(move |_, position| {
|
||||
let _ = sender_clone.send(UIEvent::ItemActivated(position as usize));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
impl LauncherUI for GtkLauncherUI {
|
||||
fn show(&self) {
|
||||
self.window.present();
|
||||
}
|
||||
|
||||
fn hide(&self) {
|
||||
self.window.close();
|
||||
}
|
||||
|
||||
fn set_results(&mut self, results: &[Box<dyn LauncherListItem>]) {
|
||||
// Clear the list store
|
||||
self.list_store.remove_all();
|
||||
|
||||
// Store results for indexing
|
||||
self.current_results = results.iter()
|
||||
.map(|item| {
|
||||
// Create a simple wrapper that implements the trait
|
||||
// This is a workaround since we can't clone trait objects
|
||||
Box::new(SimpleListItem {
|
||||
title: item.title(),
|
||||
description: item.description(),
|
||||
icon: item.icon(),
|
||||
original_ptr: 0, // Not used for execution
|
||||
}) as Box<dyn LauncherListItem>
|
||||
})
|
||||
.collect();
|
||||
|
||||
// Add all entries to the store
|
||||
// Update the list store
|
||||
list_store_for_search.remove_all();
|
||||
for (index, entry) in results.iter().enumerate() {
|
||||
let item_obj = LauncherItemObject::new(
|
||||
entry.title(),
|
||||
@ -297,45 +204,110 @@ impl LauncherUI for GtkLauncherUI {
|
||||
entry.icon(),
|
||||
index
|
||||
);
|
||||
self.list_store.append(&item_obj);
|
||||
list_store_for_search.append(&item_obj);
|
||||
}
|
||||
});
|
||||
|
||||
// Connect Enter key activation for search input
|
||||
let launcher_for_enter = launcher.clone();
|
||||
let selection_for_enter = selection.clone();
|
||||
let app_for_enter = app.clone();
|
||||
search_input.connect_activate(move |_| {
|
||||
if let Some(selected_item) = selection_for_enter.selected_item() {
|
||||
if let Some(item_obj) = selected_item.downcast_ref::<LauncherItemObject>() {
|
||||
let index = item_obj.index();
|
||||
match launcher_for_enter.borrow().execute_item(index) {
|
||||
Ok(_) => app_for_enter.quit(),
|
||||
Err(e) => eprintln!("Failed to launch app: {:?}", e),
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Add key handler for launcher-style navigation
|
||||
let search_key_controller = EventControllerKey::new();
|
||||
let selection_for_keys = selection.clone();
|
||||
search_key_controller.connect_key_pressed(move |_controller, keyval, _keycode, _state| {
|
||||
match keyval {
|
||||
gtk::gdk::Key::Down => {
|
||||
let current_pos = selection_for_keys.selected();
|
||||
let n_items = selection_for_keys.model().unwrap().n_items();
|
||||
if current_pos < n_items - 1 {
|
||||
selection_for_keys.set_selected(current_pos + 1);
|
||||
} else if n_items > 0 && current_pos == gtk::INVALID_LIST_POSITION {
|
||||
selection_for_keys.set_selected(0);
|
||||
}
|
||||
gtk::glib::Propagation::Stop
|
||||
}
|
||||
gtk::gdk::Key::Up => {
|
||||
let current_pos = selection_for_keys.selected();
|
||||
if current_pos > 0 {
|
||||
selection_for_keys.set_selected(current_pos - 1);
|
||||
}
|
||||
gtk::glib::Propagation::Stop
|
||||
}
|
||||
_ => gtk::glib::Propagation::Proceed,
|
||||
}
|
||||
});
|
||||
search_input.add_controller(search_key_controller);
|
||||
|
||||
// Add ESC key handler at window level
|
||||
let window_key_controller = EventControllerKey::new();
|
||||
let app_for_esc = app.clone();
|
||||
window_key_controller.connect_key_pressed(move |_controller, keyval, _keycode, _state| {
|
||||
if keyval == gtk::gdk::Key::Escape {
|
||||
app_for_esc.quit();
|
||||
gtk::glib::Propagation::Stop
|
||||
} else {
|
||||
gtk::glib::Propagation::Proceed
|
||||
}
|
||||
});
|
||||
window.add_controller(window_key_controller);
|
||||
|
||||
// Connect list activation signal
|
||||
let launcher_for_activate = launcher.clone();
|
||||
let app_for_activate = app.clone();
|
||||
list_view.connect_activate(move |_, position| {
|
||||
match launcher_for_activate.borrow().execute_item(position as usize) {
|
||||
Ok(_) => app_for_activate.quit(),
|
||||
Err(e) => eprintln!("Failed to launch app: {:?}", e),
|
||||
}
|
||||
});
|
||||
|
||||
// Initialize with default results
|
||||
let mut launcher_ref = launcher.borrow_mut();
|
||||
let results = launcher_ref.get_default_results();
|
||||
for (index, entry) in results.iter().enumerate() {
|
||||
let item_obj = LauncherItemObject::new(
|
||||
entry.title(),
|
||||
entry.description(),
|
||||
entry.icon(),
|
||||
index
|
||||
);
|
||||
list_store.append(&item_obj);
|
||||
}
|
||||
|
||||
// Select the first item if available
|
||||
if self.list_store.n_items() > 0 {
|
||||
self.selection.set_selected(0);
|
||||
}
|
||||
if list_store.n_items() > 0 {
|
||||
selection.set_selected(0);
|
||||
}
|
||||
drop(launcher_ref); // Release the borrow
|
||||
|
||||
fn is_visible(&self) -> bool {
|
||||
self.window.is_visible()
|
||||
Self {
|
||||
window,
|
||||
list_view,
|
||||
list_store,
|
||||
selection,
|
||||
search_input,
|
||||
launcher,
|
||||
app: app.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Helper struct to store launcher item data
|
||||
struct SimpleListItem {
|
||||
title: String,
|
||||
description: Option<String>,
|
||||
icon: String,
|
||||
original_ptr: usize, // Not used for execution, just for storage
|
||||
}
|
||||
|
||||
impl LauncherListItem for SimpleListItem {
|
||||
fn title(&self) -> String {
|
||||
self.title.clone()
|
||||
}
|
||||
|
||||
fn description(&self) -> Option<String> {
|
||||
self.description.clone()
|
||||
}
|
||||
|
||||
fn icon(&self) -> String {
|
||||
self.icon.clone()
|
||||
}
|
||||
|
||||
fn execute(&self) -> Result<(), crate::LaunchError> {
|
||||
// This should never be called - the controller handles execution
|
||||
// using the original items
|
||||
Err(crate::LaunchError::CouldNotLaunch("Cannot execute from UI wrapper".into()))
|
||||
impl GtkLauncherUI {
|
||||
pub fn show(&self) {
|
||||
self.window.present();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,8 +1,4 @@
|
||||
pub mod traits;
|
||||
pub mod controller;
|
||||
pub mod gtk;
|
||||
|
||||
// Re-export commonly used items
|
||||
pub use traits::{LauncherUI, UIEvent, ControllerEvent};
|
||||
pub use controller::LauncherController;
|
||||
pub use gtk::GtkLauncherUI;
|
@ -1,40 +0,0 @@
|
||||
use crate::LauncherListItem;
|
||||
|
||||
/// Trait defining the interface for any launcher UI implementation
|
||||
pub trait LauncherUI {
|
||||
/// Show the launcher UI
|
||||
fn show(&self);
|
||||
|
||||
/// Hide the launcher UI
|
||||
fn hide(&self);
|
||||
|
||||
/// Update the UI with new search results
|
||||
fn set_results(&mut self, results: &[Box<dyn LauncherListItem>]);
|
||||
|
||||
/// Check if the UI is currently visible
|
||||
fn is_visible(&self) -> bool;
|
||||
}
|
||||
|
||||
/// Events that the UI can send to the controller
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum UIEvent {
|
||||
/// User typed in the search box
|
||||
SearchChanged(String),
|
||||
/// User selected an item (by index)
|
||||
ItemSelected(usize),
|
||||
/// User activated an item (pressed Enter or clicked)
|
||||
ItemActivated(usize),
|
||||
/// User requested to close the launcher (Escape key)
|
||||
CloseRequested,
|
||||
}
|
||||
|
||||
/// Events that the controller can send to the UI
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum ControllerEvent {
|
||||
/// Results have been updated
|
||||
ResultsUpdated,
|
||||
/// An error occurred
|
||||
Error(String),
|
||||
/// Close the UI
|
||||
Close,
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user