Add accessible label to integration emblem overlay in app cards
This commit is contained in:
@@ -1,4 +1,5 @@
|
|||||||
use gtk::prelude::*;
|
use gtk::prelude::*;
|
||||||
|
use gtk::accessible::Property as AccessibleProperty;
|
||||||
|
|
||||||
use crate::core::database::AppImageRecord;
|
use crate::core::database::AppImageRecord;
|
||||||
use crate::core::fuse::FuseStatus;
|
use crate::core::fuse::FuseStatus;
|
||||||
@@ -17,57 +18,62 @@ pub fn build_app_card(record: &AppImageRecord) -> gtk::FlowBoxChild {
|
|||||||
.halign(gtk::Align::Center)
|
.halign(gtk::Align::Center)
|
||||||
.build();
|
.build();
|
||||||
card.add_css_class("app-card");
|
card.add_css_class("app-card");
|
||||||
card.set_size_request(160, -1);
|
card.set_size_request(180, -1);
|
||||||
|
|
||||||
// Icon (48x48)
|
// Icon (64x64) with integration emblem overlay
|
||||||
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 = 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()
|
let name_label = gtk::Label::builder()
|
||||||
.label(name)
|
.label(name)
|
||||||
.css_classes(["app-card-name"])
|
.css_classes(["heading"])
|
||||||
.ellipsize(gtk::pango::EllipsizeMode::End)
|
.ellipsize(gtk::pango::EllipsizeMode::End)
|
||||||
.max_width_chars(18)
|
.max_width_chars(20)
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
// Version
|
// Version - use libadwaita built-in caption + dimmed
|
||||||
let version_text = record.app_version.as_deref().unwrap_or("");
|
let version_text = record.app_version.as_deref().unwrap_or("");
|
||||||
let version_label = gtk::Label::builder()
|
let version_label = gtk::Label::builder()
|
||||||
.label(version_text)
|
.label(version_text)
|
||||||
.css_classes(["app-card-version"])
|
.css_classes(["caption", "dimmed"])
|
||||||
.ellipsize(gtk::pango::EllipsizeMode::End)
|
.ellipsize(gtk::pango::EllipsizeMode::End)
|
||||||
.build();
|
.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);
|
card.append(&name_label);
|
||||||
if !version_text.is_empty() {
|
if !version_text.is_empty() {
|
||||||
card.append(&version_label);
|
card.append(&version_label);
|
||||||
}
|
}
|
||||||
|
card.append(&size_label);
|
||||||
|
|
||||||
// Status badges row
|
// Status badges row
|
||||||
let badges = gtk::Box::builder()
|
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);
|
card.append(&badges);
|
||||||
|
|
||||||
let child = gtk::FlowBoxChild::builder()
|
let child = gtk::FlowBoxChild::builder()
|
||||||
.child(&card)
|
.child(&card)
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
|
// Accessible label for screen readers
|
||||||
|
let accessible_name = build_accessible_label(record);
|
||||||
|
child.update_property(&[AccessibleProperty::Label(&accessible_name)]);
|
||||||
|
|
||||||
child
|
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(", ")
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user