Implement Driftwood AppImage manager

This commit is contained in:
2026-02-26 23:04:27 +02:00
commit c3620b487d
33 changed files with 10399 additions and 0 deletions

119
src/ui/app_card.rs Normal file
View File

@@ -0,0 +1,119 @@
use gtk::prelude::*;
use crate::core::database::AppImageRecord;
use crate::core::fuse::FuseStatus;
use crate::core::wayland::WaylandStatus;
use super::widgets;
/// Build a grid card for an AppImage.
pub fn build_app_card(record: &AppImageRecord) -> gtk::FlowBoxChild {
let card = gtk::Box::builder()
.orientation(gtk::Orientation::Vertical)
.spacing(6)
.margin_top(12)
.margin_bottom(12)
.margin_start(12)
.margin_end(12)
.halign(gtk::Align::Center)
.build();
card.add_css_class("app-card");
card.set_size_request(160, -1);
// Icon (48x48)
let icon = if let Some(ref icon_path) = record.icon_path {
let path = std::path::Path::new(icon_path);
if path.exists() {
let paintable = gtk::gdk::Texture::from_filename(path).ok();
let image = gtk::Image::builder()
.pixel_size(48)
.build();
if let Some(texture) = paintable {
image.set_paintable(Some(&texture));
} else {
image.set_icon_name(Some("application-x-executable-symbolic"));
}
image
} else {
gtk::Image::builder()
.icon_name("application-x-executable-symbolic")
.pixel_size(48)
.build()
}
} else {
gtk::Image::builder()
.icon_name("application-x-executable-symbolic")
.pixel_size(48)
.build()
};
// App name
let name = record.app_name.as_deref().unwrap_or(&record.filename);
let name_label = gtk::Label::builder()
.label(name)
.css_classes(["app-card-name"])
.ellipsize(gtk::pango::EllipsizeMode::End)
.max_width_chars(18)
.build();
// Version
let version_text = record.app_version.as_deref().unwrap_or("");
let version_label = gtk::Label::builder()
.label(version_text)
.css_classes(["app-card-version"])
.ellipsize(gtk::pango::EllipsizeMode::End)
.build();
card.append(&icon);
card.append(&name_label);
if !version_text.is_empty() {
card.append(&version_label);
}
// Status badges row
let badges = gtk::Box::builder()
.orientation(gtk::Orientation::Horizontal)
.spacing(4)
.halign(gtk::Align::Center)
.build();
badges.add_css_class("badge-row");
// Wayland status badge
if let Some(ref ws) = record.wayland_status {
let status = WaylandStatus::from_str(ws);
if status != WaylandStatus::Unknown {
badges.append(&widgets::status_badge(status.label(), status.badge_class()));
}
}
// FUSE status badge
if let Some(ref fs) = record.fuse_status {
let status = FuseStatus::from_str(fs);
if !status.is_functional() {
badges.append(&widgets::status_badge(status.label(), status.badge_class()));
}
}
// Update available badge
if record.latest_version.is_some() {
if let (Some(ref latest), Some(ref current)) =
(&record.latest_version, &record.app_version)
{
if crate::core::updater::version_is_newer(latest, current) {
badges.append(&widgets::status_badge("Update", "info"));
}
}
}
// Integration badge (only show if not integrated, to reduce clutter)
if !record.integrated {
badges.append(&widgets::status_badge("Not integrated", "neutral"));
}
card.append(&badges);
let child = gtk::FlowBoxChild::builder()
.child(&card)
.build();
child
}