corundum_framework_gui/settings/
launcher.rs1use corundum_framework_platform::launcher::{self, LauncherSpec, Status};
4
5use crate::tr;
6
7pub struct LauncherRow {
20 spec: LauncherSpec,
21 status_of: fn(&LauncherSpec) -> Status,
23 status: Option<Status>,
25 error: Option<String>,
27 #[cfg(test)]
29 shown: Option<&'static str>,
30}
31
32#[derive(Clone, Copy)]
34enum Act {
35 Remove,
36 Add,
37}
38
39impl LauncherRow {
40 pub fn new(spec: LauncherSpec) -> LauncherRow {
42 LauncherRow::with_status(spec, launcher::status)
43 }
44
45 pub(crate) fn with_status(
48 spec: LauncherSpec,
49 status_of: fn(&LauncherSpec) -> Status,
50 ) -> LauncherRow {
51 LauncherRow {
52 spec,
53 status_of,
54 status: None,
55 error: None,
56 #[cfg(test)]
57 shown: None,
58 }
59 }
60
61 pub fn ui(&mut self, ui: &mut egui::Ui) {
63 let status = *self
64 .status
65 .get_or_insert_with(|| (self.status_of)(&self.spec));
66 let (act, id) = match status {
67 Status::Registered | Status::Stale => (Act::Remove, "corundum-launcher-remove"),
68 Status::Absent => (Act::Add, "corundum-launcher-add"),
69 Status::Unsupported => {
70 #[cfg(test)]
71 {
72 self.shown = None;
73 }
74 return;
75 }
76 };
77 #[cfg(test)]
78 {
79 self.shown = Some(id);
80 }
81 let mut clicked = None;
82 ui.horizontal(|ui| {
83 ui.label(tr!(ui, "corundum-launcher-in-menu"));
84 if ui.button(tr!(ui, id)).clicked() {
85 clicked = Some(act);
86 }
87 });
88 if let Some(act) = clicked {
89 self.act(ui.ctx(), act);
90 }
91 if let Some(error) = &self.error {
92 ui.colored_label(
93 ui.visuals().error_fg_color,
94 tr!(ui, "corundum-launcher-failed", error = error.as_str()),
95 );
96 }
97 }
98
99 #[cfg_attr(not(feature = "serde"), allow(unused_variables))]
101 fn act(&mut self, ctx: &egui::Context, act: Act) {
102 let done = match act {
103 Act::Remove => launcher::unregister(&self.spec),
104 Act::Add => launcher::register(&self.spec).map(|_| ()),
105 };
106 match done {
107 Ok(()) => {
108 self.error = None;
109 #[cfg(feature = "serde")]
110 crate::store::update(ctx, |s| {
111 s.launcher = Some(matches!(act, Act::Add));
112 });
113 }
114 Err(err) => self.error = Some(err.to_string()),
115 }
116 self.status = None;
117 }
118}
119
120#[cfg(test)]
121mod tests {
122 use super::*;
123 use crate::test_support::render;
124 use std::sync::atomic::{AtomicUsize, Ordering};
125
126 const SPEC: LauncherSpec = LauncherSpec {
127 id: "net.example.Test",
128 name: "Test",
129 description: "A test app",
130 category: "Utility",
131 icon_svg: b"<svg/>",
132 bundle_prefix: "net.example",
133 };
134
135 fn shown(status_of: fn(&LauncherSpec) -> Status) -> Option<&'static str> {
136 let mut row = LauncherRow::with_status(SPEC, status_of);
137 render([600.0, 400.0], |ui| row.ui(ui));
138 row.shown
139 }
140
141 #[test]
142 fn an_absent_entry_offers_add_back() {
143 assert_eq!(shown(|_| Status::Absent), Some("corundum-launcher-add"));
144 }
145
146 #[test]
147 fn a_registered_or_stale_entry_offers_remove() {
148 assert_eq!(
149 shown(|_| Status::Registered),
150 Some("corundum-launcher-remove")
151 );
152 assert_eq!(shown(|_| Status::Stale), Some("corundum-launcher-remove"));
153 }
154
155 #[test]
156 fn without_an_app_menu_there_is_no_button() {
157 assert_eq!(shown(|_| Status::Unsupported), None);
158 }
159
160 #[test]
161 fn without_an_app_menu_the_row_is_not_drawn() {
162 let drawn = |status_of: fn(&LauncherSpec) -> Status| {
163 let mut row = LauncherRow::with_status(SPEC, status_of);
164 let mut moved = false;
165 render([600.0, 400.0], |ui| {
166 let before = ui.cursor().min;
167 row.ui(ui);
168 moved = ui.cursor().min != before;
169 });
170 moved
171 };
172 assert!(drawn(|_| Status::Absent));
173 assert!(!drawn(|_| Status::Unsupported));
174 }
175
176 #[test]
177 fn the_status_is_looked_up_once() {
178 static CALLS: AtomicUsize = AtomicUsize::new(0);
179 let mut row = LauncherRow::with_status(SPEC, |_| {
180 CALLS.fetch_add(1, Ordering::Relaxed);
181 Status::Absent
182 });
183 render([600.0, 400.0], |ui| row.ui(ui));
184 render([600.0, 400.0], |ui| row.ui(ui));
185 assert_eq!(CALLS.load(Ordering::Relaxed), 1);
186 }
187}