use corundum_framework_platform::launcher::{self, LauncherSpec, Status};
use crate::tr;
pub struct LauncherRow {
spec: LauncherSpec,
status_of: fn(&LauncherSpec) -> Status,
status: Option<Status>,
error: Option<String>,
#[cfg(test)]
shown: Option<&'static str>,
}
#[derive(Clone, Copy)]
enum Act {
Remove,
Add,
}
impl LauncherRow {
pub fn new(spec: LauncherSpec) -> LauncherRow {
LauncherRow::with_status(spec, launcher::status)
}
pub(crate) fn with_status(
spec: LauncherSpec,
status_of: fn(&LauncherSpec) -> Status,
) -> LauncherRow {
LauncherRow {
spec,
status_of,
status: None,
error: None,
#[cfg(test)]
shown: None,
}
}
pub fn ui(&mut self, ui: &mut egui::Ui) {
let status = *self
.status
.get_or_insert_with(|| (self.status_of)(&self.spec));
let act = match status {
Status::Registered | Status::Stale => Some((Act::Remove, "corundum-launcher-remove")),
Status::Absent => Some((Act::Add, "corundum-launcher-add")),
Status::Unsupported => None,
};
#[cfg(test)]
{
self.shown = act.map(|(_, id)| id);
}
let mut clicked = None;
ui.horizontal(|ui| {
ui.label(tr!(ui, "corundum-launcher-in-menu"));
if let Some((act, id)) = act
&& ui.button(tr!(ui, id)).clicked()
{
clicked = Some(act);
}
});
if let Some(act) = clicked {
self.act(ui.ctx(), act);
}
if let Some(error) = &self.error {
ui.colored_label(
ui.visuals().error_fg_color,
tr!(ui, "corundum-launcher-failed", error = error.as_str()),
);
}
}
#[cfg_attr(not(feature = "serde"), allow(unused_variables))]
fn act(&mut self, ctx: &egui::Context, act: Act) {
let done = match act {
Act::Remove => launcher::unregister(&self.spec),
Act::Add => launcher::register(&self.spec).map(|_| ()),
};
match done {
Ok(()) => {
self.error = None;
#[cfg(feature = "serde")]
crate::store::update(ctx, |s| {
s.launcher = Some(matches!(act, Act::Add));
});
}
Err(err) => self.error = Some(err.to_string()),
}
self.status = None;
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::test_support::render;
use std::sync::atomic::{AtomicUsize, Ordering};
const SPEC: LauncherSpec = LauncherSpec {
id: "net.example.Test",
name: "Test",
description: "A test app",
category: "Utility",
icon_svg: b"<svg/>",
bundle_prefix: "net.example",
};
fn shown(status_of: fn(&LauncherSpec) -> Status) -> Option<&'static str> {
let mut row = LauncherRow::with_status(SPEC, status_of);
render([600.0, 400.0], |ui| row.ui(ui));
row.shown
}
#[test]
fn an_absent_entry_offers_add_back() {
assert_eq!(shown(|_| Status::Absent), Some("corundum-launcher-add"));
}
#[test]
fn a_registered_or_stale_entry_offers_remove() {
assert_eq!(
shown(|_| Status::Registered),
Some("corundum-launcher-remove")
);
assert_eq!(shown(|_| Status::Stale), Some("corundum-launcher-remove"));
}
#[test]
fn without_an_app_menu_there_is_no_button() {
assert_eq!(shown(|_| Status::Unsupported), None);
}
#[test]
fn the_status_is_looked_up_once() {
static CALLS: AtomicUsize = AtomicUsize::new(0);
let mut row = LauncherRow::with_status(SPEC, |_| {
CALLS.fetch_add(1, Ordering::Relaxed);
Status::Absent
});
render([600.0, 400.0], |ui| row.ui(ui));
render([600.0, 400.0], |ui| row.ui(ui));
assert_eq!(CALLS.load(Ordering::Relaxed), 1);
}
}