Add accessible label to integration emblem overlay in app cards

This commit is contained in:
lashman
2026-02-27 10:02:53 +02:00
parent 19791168f3
commit 7852e7ab4b

View File

@@ -1,4 +1,5 @@
use gtk::prelude::*;
use gtk::accessible::Property as AccessibleProperty;
use crate::core::database::AppImageRecord;
use crate::core::fuse::FuseStatus;
@@ -17,57 +18,62 @@ pub fn build_app_card(record: &AppImageRecord) -> gtk::FlowBoxChild {
.halign(gtk::Align::Center)
.build();
card.add_css_class("app-card");
card.set_size_request(160, -1);
card.set_size_request(180, -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
// Icon (64x64) with integration emblem overlay
let name = record.app_name.as_deref().unwrap_or(&record.filename);
let icon_widget = widgets::app_icon(
record.icon_path.as_deref(),
name,
64,
);
// If integrated, overlay a small checkmark emblem
if record.integrated {
let overlay = gtk::Overlay::new();
overlay.set_child(Some(&icon_widget));
let emblem = gtk::Image::from_icon_name("emblem-ok-symbolic");
emblem.set_pixel_size(16);
emblem.add_css_class("integration-emblem");
emblem.set_halign(gtk::Align::End);
emblem.set_valign(gtk::Align::End);
emblem.update_property(&[AccessibleProperty::Label("Integrated into desktop menu")]);
overlay.add_overlay(&emblem);
card.append(&overlay);
} else {
card.append(&icon_widget);
}
// App name - use libadwaita built-in heading class
let name_label = gtk::Label::builder()
.label(name)
.css_classes(["app-card-name"])
.css_classes(["heading"])
.ellipsize(gtk::pango::EllipsizeMode::End)
.max_width_chars(18)
.max_width_chars(20)
.build();
// Version
// Version - use libadwaita built-in caption + dimmed
let version_text = record.app_version.as_deref().unwrap_or("");
let version_label = gtk::Label::builder()
.label(version_text)
.css_classes(["app-card-version"])
.css_classes(["caption", "dimmed"])
.ellipsize(gtk::pango::EllipsizeMode::End)
.build();
card.append(&icon);
// File size as subtle caption
let size_text = widgets::format_size(record.size_bytes);
let size_label = gtk::Label::builder()
.label(&size_text)
.css_classes(["caption", "dimmed"])
.build();
card.append(&name_label);
if !version_text.is_empty() {
card.append(&version_label);
}
card.append(&size_label);
// Status badges row
let badges = gtk::Box::builder()
@@ -104,16 +110,40 @@ pub fn build_app_card(record: &AppImageRecord) -> gtk::FlowBoxChild {
}
}
// 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();
// Accessible label for screen readers
let accessible_name = build_accessible_label(record);
child.update_property(&[AccessibleProperty::Label(&accessible_name)]);
child
}
/// Build a descriptive accessible label for screen readers.
fn build_accessible_label(record: &AppImageRecord) -> String {
let name = record.app_name.as_deref().unwrap_or(&record.filename);
let mut parts = vec![name.to_string()];
if let Some(ref ver) = record.app_version {
parts.push(format!("version {}", ver));
}
parts.push(widgets::format_size(record.size_bytes));
if record.integrated {
parts.push("integrated".to_string());
}
if let Some(ref ws) = record.wayland_status {
let status = WaylandStatus::from_str(ws);
if status != WaylandStatus::Unknown {
parts.push(format!("Wayland: {}", status.label()));
}
}
parts.join(", ")
}