Set up to allow styling and user styling in the future
This commit is contained in:
parent
beecf03025
commit
7c310b36e1
11
src/main.rs
11
src/main.rs
@ -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
111
src/ui/gtk/default.css
Normal 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 */
|
||||||
|
}
|
@ -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(
|
||||||
|
Loading…
x
Reference in New Issue
Block a user