From 7cf1b9efc75db9a4634427c8e1d7386193516497 Mon Sep 17 00:00:00 2001 From: Javier Feliz Date: Thu, 4 Sep 2025 21:06:18 -0400 Subject: [PATCH] Had claude un-abstract all that BS --- src/main.rs | 10 +-- src/ui/controller.rs | 107 ------------------------- src/ui/gtk/mod.rs | 186 ++++++++++++++++++------------------------- src/ui/mod.rs | 4 - src/ui/traits.rs | 40 ---------- 5 files changed, 82 insertions(+), 265 deletions(-) delete mode 100644 src/ui/controller.rs delete mode 100644 src/ui/traits.rs diff --git a/src/main.rs b/src/main.rs index 282a462..bd1b419 100644 --- a/src/main.rs +++ b/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(); diff --git a/src/ui/controller.rs b/src/ui/controller.rs deleted file mode 100644 index dc031d6..0000000 --- a/src/ui/controller.rs +++ /dev/null @@ -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>, - ui: GtkLauncherUI, - event_receiver: Receiver, - 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 - } -} diff --git a/src/ui/gtk/mod.rs b/src/ui/gtk/mod.rs index 655f2fb..4f02162 100644 --- a/src/ui/gtk/mod.rs +++ b/src/ui/gtk/mod.rs @@ -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>, - current_results: Vec>, // Keep reference for indexing + launcher: Rc>, + 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,100 @@ 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) { - 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)); + let mut launcher_ref = launcher_for_search.borrow_mut(); + let results = if query.trim().is_empty() { + launcher_ref.get_default_results() + } else { + launcher_ref.search(&query) + }; + + // Update the list store + list_store_for_search.remove_all(); + for (index, entry) in results.iter().enumerate() { + let item_obj = LauncherItemObject::new( + entry.title(), + entry.description(), + entry.icon(), + index + ); + list_store_for_search.append(&item_obj); + } }); // 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() { + 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::() { let index = item_obj.index(); - let _ = sender_clone.send(UIEvent::ItemActivated(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_clone = self.selection.clone(); + 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_clone.selected(); - let n_items = selection_clone.model().unwrap().n_items(); + let current_pos = selection_for_keys.selected(); + let n_items = selection_for_keys.model().unwrap().n_items(); if current_pos < n_items - 1 { - selection_clone.set_selected(current_pos + 1); + selection_for_keys.set_selected(current_pos + 1); } else if n_items > 0 && current_pos == gtk::INVALID_LIST_POSITION { - selection_clone.set_selected(0); + selection_for_keys.set_selected(0); } gtk::glib::Propagation::Stop } gtk::gdk::Key::Up => { - let current_pos = selection_clone.selected(); + let current_pos = selection_for_keys.selected(); if current_pos > 0 { - selection_clone.set_selected(current_pos - 1); + selection_for_keys.set_selected(current_pos - 1); } gtk::glib::Propagation::Stop } _ => gtk::glib::Propagation::Proceed, } }); - self.search_input.add_controller(search_key_controller); + 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(); + let app_for_esc = app.clone(); window_key_controller.connect_key_pressed(move |_controller, keyval, _keycode, _state| { if keyval == gtk::gdk::Key::Escape { - let _ = sender_clone.send(UIEvent::CloseRequested); + app_for_esc.quit(); gtk::glib::Propagation::Stop } else { gtk::glib::Propagation::Proceed } }); - self.window.add_controller(window_key_controller); + window.add_controller(window_key_controller); // Connect list activation signal - let sender_clone = sender; - self.list_view.connect_activate(move |_, position| { - let _ = sender_clone.send(UIEvent::ItemActivated(position as usize)); + 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), + } }); - } -} -impl LauncherUI for GtkLauncherUI { - fn show(&self) { - self.window.present(); - } - - fn hide(&self) { - self.window.close(); - } - - fn set_results(&mut self, results: &[Box]) { - // 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 - }) - .collect(); - - // Add all entries to the store + // 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(), @@ -297,45 +284,30 @@ impl LauncherUI for GtkLauncherUI { entry.icon(), index ); - self.list_store.append(&item_obj); + 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 + + Self { + window, + list_view, + list_store, + selection, + search_input, + launcher, + app: app.clone(), } } - - fn is_visible(&self) -> bool { - self.window.is_visible() - } } -// Helper struct to store launcher item data -struct SimpleListItem { - title: String, - description: Option, - 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 { - 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(); } } diff --git a/src/ui/mod.rs b/src/ui/mod.rs index 573b037..ab8a1dd 100644 --- a/src/ui/mod.rs +++ b/src/ui/mod.rs @@ -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; \ No newline at end of file diff --git a/src/ui/traits.rs b/src/ui/traits.rs deleted file mode 100644 index 42ccbbb..0000000 --- a/src/ui/traits.rs +++ /dev/null @@ -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]); - - /// 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, -} \ No newline at end of file