155 lines
5.1 KiB
Rust
155 lines
5.1 KiB
Rust
//! Individual tile widget for the launcher grid
|
|
|
|
use gtk4::glib;
|
|
use gtk4::prelude::*;
|
|
use gtk4::subclass::prelude::*;
|
|
use shepherd_api::EntryView;
|
|
use std::cell::RefCell;
|
|
|
|
mod imp {
|
|
use super::*;
|
|
|
|
#[derive(Default)]
|
|
pub struct LauncherTile {
|
|
pub entry: RefCell<Option<EntryView>>,
|
|
pub icon: gtk4::Image,
|
|
pub label: gtk4::Label,
|
|
}
|
|
|
|
#[glib::object_subclass]
|
|
impl ObjectSubclass for LauncherTile {
|
|
const NAME: &'static str = "ShepherdLauncherTile";
|
|
type Type = super::LauncherTile;
|
|
type ParentType = gtk4::Button;
|
|
}
|
|
|
|
impl ObjectImpl for LauncherTile {
|
|
fn constructed(&self) {
|
|
self.parent_constructed();
|
|
|
|
let obj = self.obj();
|
|
|
|
// Create layout
|
|
let content = gtk4::Box::new(gtk4::Orientation::Vertical, 8);
|
|
content.set_halign(gtk4::Align::Center);
|
|
content.set_valign(gtk4::Align::Center);
|
|
|
|
// Icon
|
|
self.icon.set_pixel_size(96);
|
|
self.icon.set_icon_name(Some("application-x-executable"));
|
|
content.append(&self.icon);
|
|
|
|
// Label
|
|
self.label.set_wrap(true);
|
|
self.label.set_wrap_mode(gtk4::pango::WrapMode::Word);
|
|
self.label.set_justify(gtk4::Justification::Center);
|
|
self.label.set_max_width_chars(12);
|
|
self.label.add_css_class("tile-label");
|
|
content.append(&self.label);
|
|
|
|
obj.set_child(Some(&content));
|
|
obj.add_css_class("launcher-tile");
|
|
obj.add_css_class("flat");
|
|
obj.set_size_request(160, 160);
|
|
obj.set_can_focus(true);
|
|
}
|
|
}
|
|
|
|
impl WidgetImpl for LauncherTile {}
|
|
impl ButtonImpl for LauncherTile {}
|
|
}
|
|
|
|
glib::wrapper! {
|
|
pub struct LauncherTile(ObjectSubclass<imp::LauncherTile>)
|
|
@extends gtk4::Button, gtk4::Widget,
|
|
@implements gtk4::Accessible, gtk4::Actionable, gtk4::Buildable, gtk4::ConstraintTarget;
|
|
}
|
|
|
|
impl LauncherTile {
|
|
pub fn new() -> Self {
|
|
glib::Object::builder().build()
|
|
}
|
|
|
|
pub fn set_entry(&self, entry: EntryView) {
|
|
let imp = self.imp();
|
|
|
|
// Set label
|
|
imp.label.set_text(&entry.label);
|
|
|
|
// Determine fallback icon based on entry kind
|
|
let fallback_icon = match entry.kind_tag {
|
|
shepherd_api::EntryKindTag::Process => "application-x-executable",
|
|
shepherd_api::EntryKindTag::Snap => "application-x-executable",
|
|
shepherd_api::EntryKindTag::Steam => "application-x-executable",
|
|
shepherd_api::EntryKindTag::Flatpak => "application-x-executable",
|
|
shepherd_api::EntryKindTag::Vm => "computer",
|
|
shepherd_api::EntryKindTag::Media => "video-x-generic",
|
|
shepherd_api::EntryKindTag::Custom => "applications-other",
|
|
};
|
|
|
|
// Set icon, first trying to load as an image file, then as an icon name
|
|
if let Some(ref icon_ref) = entry.icon_ref {
|
|
let mut loaded = false;
|
|
|
|
// First, try to load as an image file (JPG, PNG, etc.)
|
|
// Expand ~ to home directory if present
|
|
let expanded_path = if icon_ref.starts_with("~/") {
|
|
if let Some(home) = dirs::home_dir() {
|
|
icon_ref.replacen("~", &home.to_string_lossy(), 1)
|
|
} else {
|
|
icon_ref.clone()
|
|
}
|
|
} else {
|
|
icon_ref.clone()
|
|
};
|
|
|
|
let path = std::path::Path::new(&expanded_path);
|
|
if path.exists() && path.is_file() {
|
|
// Try to load as an image file
|
|
imp.icon.set_from_file(Some(path));
|
|
loaded = true;
|
|
}
|
|
|
|
// If not loaded as a file, try as an icon name from the theme
|
|
if !loaded {
|
|
let icon_theme = gtk4::IconTheme::for_display(&self.display());
|
|
if icon_theme.has_icon(icon_ref) {
|
|
imp.icon.set_icon_name(Some(icon_ref));
|
|
} else {
|
|
imp.icon.set_icon_name(Some(fallback_icon));
|
|
}
|
|
}
|
|
} else {
|
|
imp.icon.set_icon_name(Some(fallback_icon));
|
|
}
|
|
|
|
// Entry is available if enabled and has no blocking reasons
|
|
let available = entry.enabled && entry.reasons.is_empty();
|
|
self.set_sensitive(available);
|
|
|
|
// Add tooltip with reason if not available
|
|
if !available && !entry.reasons.is_empty() {
|
|
// Format the first reason for tooltip
|
|
let reason_text = format!("{:?}", entry.reasons[0]);
|
|
self.set_tooltip_text(Some(&reason_text));
|
|
} else {
|
|
self.set_tooltip_text(None);
|
|
}
|
|
|
|
*imp.entry.borrow_mut() = Some(entry);
|
|
}
|
|
|
|
pub fn entry(&self) -> Option<EntryView> {
|
|
self.imp().entry.borrow().clone()
|
|
}
|
|
|
|
pub fn entry_id(&self) -> Option<shepherd_util::EntryId> {
|
|
self.imp().entry.borrow().as_ref().map(|e| e.entry_id.clone())
|
|
}
|
|
}
|
|
|
|
impl Default for LauncherTile {
|
|
fn default() -> Self {
|
|
Self::new()
|
|
}
|
|
}
|