Set up to allow styling and user styling in the future

This commit is contained in:
Javier Feliz 2025-09-04 21:35:59 -04:00
parent beecf03025
commit 7c310b36e1
3 changed files with 196 additions and 2 deletions

View File

@ -18,6 +18,17 @@ fn main() {
// Create and show the GTK UI // Create and show the GTK UI
let ui = GtkLauncherUI::new(app, launcher); let ui = GtkLauncherUI::new(app, launcher);
// Apply built-in default styles
if let Err(e) = ui.apply_default_css() {
eprintln!("Warning: Could not apply default styles: {}", e);
}
// Optionally apply user CSS overrides
// if let Err(_) = ui.apply_css("waycast.css") {
// // Silently ignore if user hasn't provided custom CSS
// }
ui.show(); ui.show();
}); });

111
src/ui/gtk/default.css Normal file
View File

@ -0,0 +1,111 @@
/* Default CSS styles for Waycast launcher */
/* Not too much so that colors follow system theme */
/* Main window */
#launcher-window {
/* background-color: rgba(0, 0, 0, 0.9);
border-radius: 12px;
border: 1px solid rgba(255, 255, 255, 0.1); */
}
/* Main container */
#main-container {
padding: 16px;
}
/* Search input */
#search-input {
font-size: 16px;
padding: 12px;
border-radius: 8px;
margin-bottom: 12px;
/* border: 1px solid rgba(255, 255, 255, 0.2);
background-color: rgba(255, 255, 255, 0.1);
color: white; */
}
#search-input:focus {
/* background-color: rgba(255, 255, 255, 0.15);
border-color: rgba(74, 144, 226, 0.6); */
box-shadow: 0 0 0 2px rgba(74, 144, 226, 0.3);
}
/* Results container */
#results-container {
border-radius: 8px;
/* background-color: rgba(255, 255, 255, 0.05); */
}
/* Results list */
#results-list {
/* background-color: transparent; */
}
/* Individual list items */
#list-item {
padding: 8px 12px;
border-radius: 6px;
margin: 2px;
}
#list-item:hover {
/* background-color: rgba(255, 255, 255, 0.1); */
}
#list-item:selected {
/* background-color: rgba(74, 144, 226, 0.4); */
}
/* Item icon */
#item-icon {
margin-right: 12px;
}
/* Item text container */
#item-text {
/* Text styling handled by individual elements */
}
/* Item title */
#item-title {
font-weight: 600;
font-size: 14px;
/* color: white; */
}
/* Item description */
#item-description {
font-size: 11px;
/* color: rgba(255, 255, 255, 0.7); */
margin-top: 2px;
}
/* CSS Classes (alternative to IDs) */
.launcher-window {
/* Alternative window styling */
}
.launcher-search {
/* Alternative search styling */
}
.launcher-item {
/* Alternative item styling */
}
.launcher-item:hover {
/* Alternative hover state */
}
.launcher-title {
/* Alternative title styling */
}
.launcher-description {
/* Alternative description styling */
}
.launcher-icon {
/* Alternative icon styling */
}

View File

@ -10,7 +10,7 @@ use gio::ListStore;
use gtk::subclass::prelude::ObjectSubclassIsExt; 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::{Path, PathBuf};
use std::rc::Rc; use std::rc::Rc;
use std::cell::RefCell; use std::cell::RefCell;
use gio::prelude::ApplicationExt; use gio::prelude::ApplicationExt;
@ -99,6 +99,8 @@ impl GtkLauncherUI {
let search_input = Entry::new(); let search_input = Entry::new();
search_input.set_placeholder_text(Some("Search...")); search_input.set_placeholder_text(Some("Search..."));
search_input.set_widget_name("search-input");
search_input.add_css_class("launcher-search");
let scrolled_window = ScrolledWindow::new(); let scrolled_window = ScrolledWindow::new();
scrolled_window.set_min_content_height(300); scrolled_window.set_min_content_height(300);
@ -113,6 +115,8 @@ impl GtkLauncherUI {
// 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);
container.set_widget_name("list-item");
container.add_css_class("launcher-item");
list_item.set_child(Some(&container)); list_item.set_child(Some(&container));
}); });
@ -151,16 +155,22 @@ impl GtkLauncherUI {
} }
} }
image.set_pixel_size(icon_size); image.set_pixel_size(icon_size);
image.set_widget_name("item-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.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);
title_label.set_ellipsize(gtk::pango::EllipsizeMode::End); title_label.set_ellipsize(gtk::pango::EllipsizeMode::End);
title_label.set_widget_name("item-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
@ -168,7 +178,8 @@ impl GtkLauncherUI {
let desc_label = Label::new(Some(&description)); let desc_label = Label::new(Some(&description));
desc_label.set_xalign(0.0); desc_label.set_xalign(0.0);
desc_label.set_ellipsize(gtk::pango::EllipsizeMode::Middle); desc_label.set_ellipsize(gtk::pango::EllipsizeMode::Middle);
desc_label.add_css_class("dim-label"); // Make it visually secondary desc_label.set_widget_name("item-description");
desc_label.add_css_class("launcher-description");
desc_label.set_opacity(0.7); desc_label.set_opacity(0.7);
text_box.append(&desc_label); text_box.append(&desc_label);
} }
@ -181,11 +192,21 @@ impl GtkLauncherUI {
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);
list_view.set_widget_name("results-list");
list_view.add_css_class("launcher-list");
scrolled_window.set_child(Some(&list_view)); scrolled_window.set_child(Some(&list_view));
scrolled_window.set_widget_name("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.add_css_class("launcher-main");
window.set_child(Some(&main_box)); window.set_child(Some(&main_box));
window.set_widget_name("launcher-window");
window.add_css_class("launcher-window");
// Set up layer shell so the launcher can float // Set up layer shell so the launcher can float
window.init_layer_shell(); window.init_layer_shell();
@ -330,6 +351,57 @@ impl GtkLauncherUI {
pub fn show(&self) { pub fn show(&self) {
self.window.present(); self.window.present();
} }
/// Apply default built-in CSS styles
pub fn apply_default_css(&self) -> Result<(), String> {
const DEFAULT_CSS: &str = include_str!("default.css");
let css_provider = gtk::CssProvider::new();
css_provider.load_from_data(DEFAULT_CSS);
if let Some(display) = gtk::gdk::Display::default() {
gtk::style_context_add_provider_for_display(
&display,
&css_provider,
gtk::STYLE_PROVIDER_PRIORITY_APPLICATION,
);
Ok(())
} else {
Err("Could not get default display".to_string())
}
}
pub fn apply_css<P: AsRef<Path>>(&self, css_path: P) -> Result<(), String> {
let css_provider = gtk::CssProvider::new();
// Check if file exists first
if !css_path.as_ref().exists() {
return Err(format!("CSS file does not exist: {}", css_path.as_ref().display()));
}
// Try to load the CSS file
// Note: load_from_path doesn't return a Result, it panics on error
// So we'll use a different approach with error handling
use std::fs;
match fs::read_to_string(css_path.as_ref()) {
Ok(css_content) => {
css_provider.load_from_data(&css_content);
// Apply the CSS to the display
if let Some(display) = gtk::gdk::Display::default() {
gtk::style_context_add_provider_for_display(
&display,
&css_provider,
gtk::STYLE_PROVIDER_PRIORITY_APPLICATION,
);
Ok(())
} else {
Err("Could not get default display".to_string())
}
}
Err(e) => Err(format!("Failed to read CSS file: {}", e))
}
}
} }
fn find_icon_file( fn find_icon_file(