Had claude un-abstract all that BS

This commit is contained in:
Javier Feliz 2025-09-04 21:06:18 -04:00
parent 2d909dd716
commit 7cf1b9efc7
5 changed files with 82 additions and 265 deletions

View File

@ -2,7 +2,6 @@ use gtk::Application;
use gtk::prelude::*; use gtk::prelude::*;
use waycast::launcher::WaycastLauncher; use waycast::launcher::WaycastLauncher;
use waycast::plugins; use waycast::plugins;
use waycast::ui::controller::LauncherController;
use waycast::ui::gtk::GtkLauncherUI; use waycast::ui::gtk::GtkLauncherUI;
fn main() { fn main() {
@ -17,12 +16,9 @@ fn main() {
.add_plugin(Box::new(plugins::file_search::FileSearchPlugin::new())) .add_plugin(Box::new(plugins::file_search::FileSearchPlugin::new()))
.init(); .init();
// Create the GTK UI // Create and show the GTK UI
let ui = GtkLauncherUI::new(app); let ui = GtkLauncherUI::new(app, launcher);
ui.show();
// Create and run the controller
let controller = LauncherController::new(launcher, ui, app.clone());
controller.run();
}); });
app.run(); app.run();

View File

@ -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
}
}

View File

@ -1,10 +1,9 @@
use crate::LauncherListItem; use crate::launcher::WaycastLauncher;
use super::traits::{LauncherUI, UIEvent};
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::{ 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, Label, ListView, Orientation, ScrolledWindow, SignalListItemFactory, SingleSelection,
}; };
use gio::ListStore; use gio::ListStore;
@ -12,7 +11,9 @@ use gtk::subclass::prelude::ObjectSubclassIsExt;
use gtk4_layer_shell as layerShell; use gtk4_layer_shell as layerShell;
use layerShell::LayerShell; use layerShell::LayerShell;
use std::path::PathBuf; 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 // GObject wrapper to store LauncherListItem in GTK's model system
mod imp { mod imp {
@ -75,12 +76,13 @@ pub struct GtkLauncherUI {
list_store: ListStore, list_store: ListStore,
selection: SingleSelection, selection: SingleSelection,
search_input: Entry, search_input: Entry,
event_sender: Option<Sender<UIEvent>>, launcher: Rc<RefCell<WaycastLauncher>>,
current_results: Vec<Box<dyn LauncherListItem>>, // Keep reference for indexing app: gtk::Application,
} }
impl GtkLauncherUI { impl GtkLauncherUI {
pub fn new(app: &Application) -> Self { pub fn new(app: &gtk::Application, launcher: WaycastLauncher) -> Self {
let launcher = Rc::new(RefCell::new(launcher));
let window = ApplicationWindow::builder() let window = ApplicationWindow::builder()
.application(app) .application(app)
.title("Waycast") .title("Waycast")
@ -181,115 +183,100 @@ impl GtkLauncherUI {
// Set initial focus to search input so user can start typing immediately // Set initial focus to search input so user can start typing immediately
search_input.grab_focus(); search_input.grab_focus();
Self { // Set up event handlers directly
window, let launcher_for_search = launcher.clone();
list_view, let list_store_for_search = list_store.clone();
list_store, search_input.connect_changed(move |entry| {
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| {
let query = entry.text().to_string(); 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 // Connect Enter key activation for search input
let sender_clone = sender.clone(); let launcher_for_enter = launcher.clone();
let selection_clone = self.selection.clone(); let selection_for_enter = selection.clone();
self.search_input.connect_activate(move |_| { let app_for_enter = app.clone();
if let Some(selected_item) = selection_clone.selected_item() { 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>() { if let Some(item_obj) = selected_item.downcast_ref::<LauncherItemObject>() {
let index = item_obj.index(); 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 // Add key handler for launcher-style navigation
let search_key_controller = EventControllerKey::new(); 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| { search_key_controller.connect_key_pressed(move |_controller, keyval, _keycode, _state| {
match keyval { match keyval {
gtk::gdk::Key::Down => { gtk::gdk::Key::Down => {
let current_pos = selection_clone.selected(); let current_pos = selection_for_keys.selected();
let n_items = selection_clone.model().unwrap().n_items(); let n_items = selection_for_keys.model().unwrap().n_items();
if current_pos < n_items - 1 { 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 { } 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::glib::Propagation::Stop
} }
gtk::gdk::Key::Up => { gtk::gdk::Key::Up => {
let current_pos = selection_clone.selected(); let current_pos = selection_for_keys.selected();
if current_pos > 0 { 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::Stop
} }
_ => gtk::glib::Propagation::Proceed, _ => 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 // Add ESC key handler at window level
let window_key_controller = EventControllerKey::new(); 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| { window_key_controller.connect_key_pressed(move |_controller, keyval, _keycode, _state| {
if keyval == gtk::gdk::Key::Escape { if keyval == gtk::gdk::Key::Escape {
let _ = sender_clone.send(UIEvent::CloseRequested); app_for_esc.quit();
gtk::glib::Propagation::Stop gtk::glib::Propagation::Stop
} else { } else {
gtk::glib::Propagation::Proceed gtk::glib::Propagation::Proceed
} }
}); });
self.window.add_controller(window_key_controller); window.add_controller(window_key_controller);
// Connect list activation signal // Connect list activation signal
let sender_clone = sender; let launcher_for_activate = launcher.clone();
self.list_view.connect_activate(move |_, position| { let app_for_activate = app.clone();
let _ = sender_clone.send(UIEvent::ItemActivated(position as usize)); 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 { // Initialize with default results
fn show(&self) { let mut launcher_ref = launcher.borrow_mut();
self.window.present(); let results = launcher_ref.get_default_results();
}
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
for (index, entry) in results.iter().enumerate() { for (index, entry) in results.iter().enumerate() {
let item_obj = LauncherItemObject::new( let item_obj = LauncherItemObject::new(
entry.title(), entry.title(),
@ -297,45 +284,30 @@ impl LauncherUI for GtkLauncherUI {
entry.icon(), entry.icon(),
index index
); );
self.list_store.append(&item_obj); list_store.append(&item_obj);
} }
// Select the first item if available // Select the first item if available
if self.list_store.n_items() > 0 { if list_store.n_items() > 0 {
self.selection.set_selected(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 impl GtkLauncherUI {
struct SimpleListItem { pub fn show(&self) {
title: String, self.window.present();
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()))
} }
} }

View File

@ -1,8 +1,4 @@
pub mod traits;
pub mod controller;
pub mod gtk; pub mod gtk;
// Re-export commonly used items // Re-export commonly used items
pub use traits::{LauncherUI, UIEvent, ControllerEvent};
pub use controller::LauncherController;
pub use gtk::GtkLauncherUI; pub use gtk::GtkLauncherUI;

View File

@ -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,
}