1mod config;
2mod window;
3
4use afrim::frontend::{Command, Frontend};
5use afrish::*;
6use anyhow::{anyhow, Result};
7use std::sync::{
8 mpsc::{Receiver, Sender},
9 OnceLock,
10};
11use std::thread;
12use window::{toolkit::ToolKit, tooltip::ToolTip};
13
14pub use config::Config;
15
16pub struct Wish {
17 window: &'static afrish::TkTopLevel,
18 tooltip: ToolTip,
19 toolkit: ToolKit,
20 tx: Option<Sender<Command>>,
21 rx: Option<Receiver<Command>>,
22}
23
24impl Wish {
25 fn init() -> &'static afrish::TkTopLevel {
26 static WISH: OnceLock<afrish::TkTopLevel> = OnceLock::new();
27 WISH.get_or_init(|| {
28 let wish = if cfg!(debug_assertions) {
29 afrish::trace_with("wish").unwrap()
30 } else {
31 afrish::start_wish().unwrap()
32 };
33
34 wish.on_close(Self::kill);
41
42 wish
43 })
44 }
45
46 pub fn from_config(config: config::Config) -> Self {
47 let wish = Self::init();
48 let tooltip = ToolTip::new(config.theme.to_owned().unwrap_or_default());
49 let toolkit = ToolKit::new(config.to_owned());
50
51 Wish {
52 window: wish,
53 tooltip,
54 toolkit,
55 tx: None,
56 rx: None,
57 }
58 }
59
60 pub fn raise_error<T: std::fmt::Debug>(message: &str, detail: T) {
61 afrish::message_box()
62 .parent(Self::init())
63 .icon(IconImage::Error)
64 .title("Unexpected Error")
65 .message(message)
66 .detail(&format!("{detail:?}"))
67 .show();
68 Self::kill();
69 }
70
71 fn build(&mut self) {
72 self.tooltip.build(afrish::make_toplevel(self.window));
73 self.toolkit.build(self.window.to_owned());
74 }
75
76 pub fn kill() {
80 afrish::end_wish();
81 }
82}
83
84impl Frontend for Wish {
85 fn init(&mut self, tx: Sender<Command>, rx: Receiver<Command>) -> Result<()> {
86 self.tx = Some(tx);
87 self.rx = Some(rx);
88 self.build();
89
90 Ok(())
91 }
92 fn listen(&mut self) -> Result<()> {
93 if self.tx.as_ref().and(self.rx.as_ref()).is_none() {
94 return Err(anyhow!("you should config the channel first!"));
95 }
96
97 thread::spawn(afrish::mainloop);
99
100 let tx = self.tx.as_ref().unwrap();
101
102 loop {
103 let command = self.rx.as_ref().unwrap().recv()?;
104 match command {
105 Command::ScreenSize(screen) => self.tooltip.update_screen(screen),
106 Command::Position(position) => self.tooltip.update_position(position),
107 Command::InputText(input) => self.tooltip.set_input_text(input),
108 Command::PageSize(size) => self.tooltip.set_page_size(size),
109 Command::State(state) => self.toolkit.set_idle_state(state),
110 Command::Predicate(predicate) => self.tooltip.add_predicate(predicate),
111 Command::Update => self.tooltip.update(),
112 Command::Clear => self.tooltip.clear(),
113 Command::SelectPreviousPredicate => self.tooltip.select_previous_predicate(),
114 Command::SelectNextPredicate => self.tooltip.select_next_predicate(),
115 Command::SelectedPredicate => {
116 if let Some(predicate) = self.tooltip.get_selected_predicate() {
117 tx.send(Command::Predicate(predicate.to_owned()))?;
118 } else {
119 tx.send(Command::NoPredicate)?;
120 }
121 }
122 Command::NOP => {
123 if let Some(state) = self.toolkit.new_idle_state() {
124 tx.send(Command::State(state))?;
125 } else {
126 tx.send(Command::NOP)?;
127 }
128 }
129 Command::End => {
130 tx.send(Command::End)?;
131 self.window.destroy();
132
133 return Ok(());
134 }
135 _ => (),
136 }
137 }
138 }
139}
140
141#[cfg(test)]
142mod tests {
143 use crate::{Config, Wish};
144 use afrim::frontend::{Command, Frontend, Predicate};
145 use std::path::Path;
146 use std::sync::mpsc;
147 use std::thread;
148 use std::time::Duration;
149
150 #[test]
151 fn test_api() {
152 let config = Config::from_file(Path::new("data/full_sample.toml")).unwrap();
153 let mut afrim_wish = Wish::from_config(config);
154 assert!(afrim_wish.listen().is_err());
155 let (tx1, rx1) = mpsc::channel();
156 let (tx2, rx2) = mpsc::channel();
157
158 let afrim_wish_thread = thread::spawn(move || {
159 afrim_wish.init(tx2, rx1).unwrap();
160 afrim_wish.listen().unwrap();
161 });
162
163 tx1.send(Command::NOP).unwrap();
164 assert_eq!(rx2.recv().unwrap(), Command::NOP);
165
166 tx1.send(Command::ScreenSize((480, 320))).unwrap();
168 tx1.send(Command::Clear).unwrap();
169 tx1.send(Command::SelectNextPredicate).unwrap();
170 tx1.send(Command::SelectPreviousPredicate).unwrap();
171 tx1.send(Command::SelectedPredicate).unwrap();
172 assert_eq!(rx2.recv().unwrap(), Command::NoPredicate);
173 tx1.send(Command::Update).unwrap();
174
175 tx1.send(Command::PageSize(3)).unwrap();
177 tx1.send(Command::InputText("Test started!".to_owned()))
178 .unwrap();
179 tx1.send(Command::Predicate(Predicate {
180 code: "test".to_owned(),
181 remaining_code: "123".to_owned(),
182 texts: vec!["ok".to_owned()],
183 can_commit: false,
184 }))
185 .unwrap();
186 tx1.send(Command::Predicate(Predicate {
187 code: "test1".to_owned(),
188 remaining_code: "23".to_owned(),
189 texts: vec!["ok".to_owned()],
190 can_commit: false,
191 }))
192 .unwrap();
193 tx1.send(Command::Predicate(Predicate {
194 code: "test12".to_owned(),
195 remaining_code: "3".to_owned(),
196 texts: vec!["ok".to_owned()],
197 can_commit: false,
198 }))
199 .unwrap();
200 tx1.send(Command::Predicate(Predicate {
201 code: "test123".to_owned(),
202 remaining_code: "".to_owned(),
203 texts: vec!["ok".to_owned()],
204 can_commit: false,
205 }))
206 .unwrap();
207 tx1.send(Command::Predicate(Predicate {
208 code: "test1234".to_owned(),
209 remaining_code: "".to_owned(),
210 texts: vec!["".to_owned()],
211 can_commit: false,
212 }))
213 .unwrap();
214 tx1.send(Command::Update).unwrap();
215
216 (0..100).for_each(|i| {
218 if i % 10 != 0 {
219 return;
220 };
221 let i = i as f64;
222 tx1.send(Command::Position((i, i))).unwrap();
223 thread::sleep(Duration::from_millis(100));
224 });
225
226 tx1.send(Command::SelectPreviousPredicate).unwrap();
228 tx1.send(Command::SelectedPredicate).unwrap();
229 assert_eq!(
230 rx2.recv().unwrap(),
231 Command::Predicate(Predicate {
232 code: "test123".to_owned(),
233 remaining_code: "".to_owned(),
234 texts: vec!["ok".to_owned()],
235 can_commit: false,
236 })
237 );
238 tx1.send(Command::SelectNextPredicate).unwrap();
239 tx1.send(Command::SelectedPredicate).unwrap();
240 assert_eq!(
241 rx2.recv().unwrap(),
242 Command::Predicate(Predicate {
243 code: "test".to_owned(),
244 remaining_code: "123".to_owned(),
245 texts: vec!["ok".to_owned()],
246 can_commit: false,
247 })
248 );
249 tx1.send(Command::Update).unwrap();
250
251 tx1.send(Command::State(true)).unwrap();
253 tx1.send(Command::State(false)).unwrap();
254
255 tx1.send(Command::End).unwrap();
257 assert_eq!(rx2.recv().unwrap(), Command::End);
258 assert!(rx2.recv().is_err());
259
260 afrim_wish_thread.join().unwrap();
262 }
263}