Skip to content

Commit 77873d9

Browse files
authored
patch: update the afrim dependency (#70)
This update will permit to implement more features. By example, pausing/resuming the afrim.
1 parent b425cfc commit 77873d9

File tree

5 files changed

+230
-126
lines changed

5 files changed

+230
-126
lines changed

Cargo.toml

+2-1
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,8 @@ inhibit = ["afrim/inhibit"]
1717
rhai = ["afrim/rhai"]
1818

1919
[dependencies]
20-
afrim = { version = "0.5.4", default-features = false, git = "https://github.com/pythonbrad/afrim", rev = "7e5daba" }
20+
afrim = { version = "0.5.4", default-features = false, git = "https://github.com/pythonbrad/afrim", rev = "fcb7721" }
21+
anyhow = "1.0.82"
2122
clap = "4.5.4"
2223
rstk = "0.3.0"
2324
serde = { version = "1.0.197", features = ["serde_derive"] }

src/config.rs

+7-4
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
1+
use anyhow::{Context, Result};
12
use serde::Deserialize;
2-
use std::{error, fs, path::Path};
3+
use std::{fs, path::Path};
34
use toml::{self};
45

56
#[derive(Clone, Deserialize, Debug, Default)]
@@ -95,9 +96,11 @@ impl Default for Info {
9596
}
9697

9798
impl Config {
98-
pub fn from_file(filepath: &Path) -> Result<Self, Box<dyn error::Error>> {
99-
let content = fs::read_to_string(filepath)?;
100-
let config: Self = toml::from_str(&content)?;
99+
pub fn from_file(filepath: &Path) -> Result<Self> {
100+
let content = fs::read_to_string(filepath)
101+
.with_context(|| format!("Couldn't open file {filepath:?}"))?;
102+
let config: Self = toml::from_str(&content)
103+
.with_context(|| format!("Failed to parse configuration file {filepath:?}"))?;
101104

102105
Ok(config)
103106
}

src/lib.rs

+177-78
Original file line numberDiff line numberDiff line change
@@ -1,159 +1,258 @@
11
mod config;
22
mod window;
33

4-
use afrim::frontend::Frontend;
4+
use afrim::frontend::{Command, Frontend};
5+
use anyhow::{anyhow, Result};
56
use rstk::*;
7+
use std::sync::{
8+
mpsc::{Receiver, Sender},
9+
OnceLock,
10+
};
11+
use std::thread;
612
use window::{rstk_ext::init_rstk_ext, toolkit::ToolKit, tooltip::ToolTip};
713

814
pub use config::Config;
915

10-
#[derive(Clone)]
1116
pub struct Wish {
12-
window: rstk::TkTopLevel,
17+
window: &'static rstk::TkTopLevel,
1318
tooltip: ToolTip,
1419
toolkit: ToolKit,
20+
tx: Option<Sender<Command>>,
21+
rx: Option<Receiver<Command>>,
1522
}
1623

1724
impl Wish {
18-
pub fn init(config: config::Config) -> Self {
19-
let wish = if cfg!(debug_assertions) {
20-
rstk::trace_with("wish").unwrap()
21-
} else {
22-
rstk::start_wish().unwrap()
23-
};
25+
fn init() -> &'static rstk::TkTopLevel {
26+
static WISH: OnceLock<rstk::TkTopLevel> = OnceLock::new();
27+
WISH.get_or_init(|| {
28+
let wish = if cfg!(debug_assertions) {
29+
rstk::trace_with("wish").unwrap()
30+
} else {
31+
rstk::start_wish().unwrap()
32+
};
33+
34+
// The default behavior is to close the window.
35+
// But since this window, represent the main window,
36+
// we don't want an unexpected behavior.
37+
// It's better for us to manage the close.
38+
//
39+
// Note that, this close button is on the title bar.
40+
wish.on_close(Self::kill);
2441

25-
init_rstk_ext();
42+
init_rstk_ext();
43+
44+
wish
45+
})
46+
}
2647

48+
pub fn from_config(config: config::Config) -> Self {
49+
let wish = Self::init();
2750
let tooltip = ToolTip::new(config.theme.to_owned().unwrap_or_default());
2851
let toolkit = ToolKit::new(config.to_owned());
2952

3053
Wish {
3154
window: wish,
3255
tooltip,
3356
toolkit,
57+
tx: None,
58+
rx: None,
3459
}
3560
}
3661

37-
pub fn raise_error(&self, message: &str, detail: &str) {
62+
pub fn raise_error<T: std::fmt::Debug>(message: &str, detail: T) {
3863
rstk::message_box()
39-
.parent(&self.window)
64+
.parent(Self::init())
4065
.icon(IconImage::Error)
4166
.title("Unexpected Error")
4267
.message(message)
43-
.detail(detail)
68+
.detail(&format!("{detail:?}"))
4469
.show();
45-
rstk::end_wish();
70+
Self::kill();
4671
}
4772

48-
pub fn build(&mut self) {
49-
self.tooltip.build(rstk::make_toplevel(&self.window));
73+
fn build(&mut self) {
74+
self.tooltip.build(rstk::make_toplevel(self.window));
5075
self.toolkit.build(self.window.to_owned());
5176
}
5277

53-
pub fn listen(&self) {
54-
rstk::mainloop();
55-
}
56-
57-
pub fn destroy(&self) {
78+
/// End the process (wish and rust).
79+
///
80+
/// Note that a `process::exit` is called internally.
81+
pub fn kill() {
5882
rstk::end_wish();
5983
}
6084
}
6185

6286
impl Frontend for Wish {
63-
fn update_screen(&mut self, screen: (u64, u64)) {
64-
self.tooltip.update_screen(screen);
65-
}
66-
67-
fn update_position(&mut self, position: (f64, f64)) {
68-
self.tooltip.update_position(position);
69-
}
70-
71-
fn set_input(&mut self, text: &str) {
72-
self.tooltip.set_input_text(text);
73-
}
74-
75-
fn set_page_size(&mut self, size: usize) {
76-
self.tooltip.set_page_size(size);
77-
}
87+
fn init(&mut self, tx: Sender<Command>, rx: Receiver<Command>) -> Result<()> {
88+
self.tx = Some(tx);
89+
self.rx = Some(rx);
90+
self.build();
7891

79-
fn add_predicate(&mut self, code: &str, remaining_code: &str, text: &str) {
80-
self.tooltip.add_predicate(code, remaining_code, text);
92+
Ok(())
8193
}
94+
fn listen(&mut self) -> Result<()> {
95+
if self.tx.as_ref().and(self.rx.as_ref()).is_none() {
96+
return Err(anyhow!("you should config the channel first!"));
97+
}
8298

83-
fn clear_predicates(&mut self) {
84-
self.tooltip.clear();
85-
}
99+
// We shouldn't forget to listen for GUI events.
100+
thread::spawn(rstk::mainloop);
86101

87-
fn previous_predicate(&mut self) {
88-
self.tooltip.select_previous_predicate();
89-
}
102+
let tx = self.tx.as_ref().unwrap();
90103

91-
fn next_predicate(&mut self) {
92-
self.tooltip.select_next_predicate();
93-
}
94-
95-
fn get_selected_predicate(&self) -> Option<&(String, String, String)> {
96-
self.tooltip.get_selected_predicate()
97-
}
104+
loop {
105+
let command = self.rx.as_ref().unwrap().recv()?;
106+
match command {
107+
Command::ScreenSize(screen) => self.tooltip.update_screen(screen),
108+
Command::Position(position) => self.tooltip.update_position(position),
109+
Command::InputText(input) => self.tooltip.set_input_text(input),
110+
Command::PageSize(size) => self.tooltip.set_page_size(size),
111+
// TODO: implement the pause/resume.
112+
Command::State(_state) => {}
113+
Command::Predicate(predicate) => self.tooltip.add_predicate(predicate),
114+
Command::Update => self.tooltip.update(),
115+
Command::Clear => self.tooltip.clear(),
116+
Command::SelectPreviousPredicate => self.tooltip.select_previous_predicate(),
117+
Command::SelectNextPredicate => self.tooltip.select_next_predicate(),
118+
Command::SelectedPredicate => {
119+
if let Some(predicate) = self.tooltip.get_selected_predicate() {
120+
tx.send(Command::Predicate(predicate.to_owned()))?;
121+
} else {
122+
tx.send(Command::NoPredicate)?;
123+
}
124+
}
125+
// TODO: complete the implementation
126+
// to send GUI commands such as pause/resume.
127+
Command::NOP => tx.send(Command::NOP)?,
128+
Command::End => {
129+
tx.send(Command::End)?;
130+
self.window.destroy();
98131

99-
fn display(&self) {
100-
self.tooltip.update();
132+
return Ok(());
133+
}
134+
_ => (),
135+
}
136+
}
101137
}
102138
}
103139

104140
#[cfg(test)]
105141
mod tests {
106142
use crate::{Config, Wish};
107-
use afrim::frontend::Frontend;
143+
use afrim::frontend::{Command, Frontend, Predicate};
108144
use std::path::Path;
145+
use std::sync::mpsc;
109146
use std::thread;
110147
use std::time::Duration;
111148

112149
#[test]
113150
fn test_api() {
114151
let config = Config::from_file(Path::new("data/full_sample.toml")).unwrap();
115-
let mut afrim_wish = Wish::init(config);
116-
afrim_wish.build();
152+
let mut afrim_wish = Wish::from_config(config);
153+
assert!(afrim_wish.listen().is_err());
154+
let (tx1, rx1) = mpsc::channel();
155+
let (tx2, rx2) = mpsc::channel();
156+
157+
let afrim_wish_thread = thread::spawn(move || {
158+
afrim_wish.init(tx2, rx1).unwrap();
159+
afrim_wish.listen().unwrap();
160+
});
161+
162+
tx1.send(Command::NOP).unwrap();
163+
assert_eq!(rx2.recv().unwrap(), Command::NOP);
117164

118165
// Test without data.
119-
afrim_wish.clear_predicates();
120-
afrim_wish.next_predicate();
121-
afrim_wish.previous_predicate();
122-
assert!(afrim_wish.get_selected_predicate().is_none());
123-
afrim_wish.display();
166+
tx1.send(Command::ScreenSize((480, 320))).unwrap();
167+
tx1.send(Command::Clear).unwrap();
168+
tx1.send(Command::SelectNextPredicate).unwrap();
169+
tx1.send(Command::SelectPreviousPredicate).unwrap();
170+
tx1.send(Command::SelectedPredicate).unwrap();
171+
assert_eq!(rx2.recv().unwrap(), Command::NoPredicate);
172+
tx1.send(Command::Update).unwrap();
124173

125174
// Test the adding of predicates.
126-
afrim_wish.set_page_size(3);
127-
afrim_wish.set_input("Test started!");
128-
afrim_wish.add_predicate("test", "123", "ok");
129-
afrim_wish.add_predicate("test1", "23", "ok");
130-
afrim_wish.add_predicate("test12", "1", "ok");
131-
afrim_wish.add_predicate("test123", "", "ok");
132-
afrim_wish.add_predicate("test1234", "", "");
133-
afrim_wish.display();
175+
tx1.send(Command::PageSize(3)).unwrap();
176+
tx1.send(Command::InputText("Test started!".to_owned()))
177+
.unwrap();
178+
tx1.send(Command::Predicate(Predicate {
179+
code: "test".to_owned(),
180+
remaining_code: "123".to_owned(),
181+
texts: vec!["ok".to_owned()],
182+
can_commit: false,
183+
}))
184+
.unwrap();
185+
tx1.send(Command::Predicate(Predicate {
186+
code: "test1".to_owned(),
187+
remaining_code: "23".to_owned(),
188+
texts: vec!["ok".to_owned()],
189+
can_commit: false,
190+
}))
191+
.unwrap();
192+
tx1.send(Command::Predicate(Predicate {
193+
code: "test12".to_owned(),
194+
remaining_code: "3".to_owned(),
195+
texts: vec!["ok".to_owned()],
196+
can_commit: false,
197+
}))
198+
.unwrap();
199+
tx1.send(Command::Predicate(Predicate {
200+
code: "test123".to_owned(),
201+
remaining_code: "".to_owned(),
202+
texts: vec!["ok".to_owned()],
203+
can_commit: false,
204+
}))
205+
.unwrap();
206+
tx1.send(Command::Predicate(Predicate {
207+
code: "test1234".to_owned(),
208+
remaining_code: "".to_owned(),
209+
texts: vec!["".to_owned()],
210+
can_commit: false,
211+
}))
212+
.unwrap();
213+
tx1.send(Command::Update).unwrap();
134214

135215
// Test the geometry.
136216
(0..100).for_each(|i| {
137217
if i % 10 != 0 {
138218
return;
139219
};
140220
let i = i as f64;
141-
afrim_wish.update_position((i, i));
221+
tx1.send(Command::Position((i, i))).unwrap();
142222
thread::sleep(Duration::from_millis(100));
143223
});
144224

145225
// Test the navigation.
146-
afrim_wish.previous_predicate();
226+
tx1.send(Command::SelectPreviousPredicate).unwrap();
227+
tx1.send(Command::SelectedPredicate).unwrap();
147228
assert_eq!(
148-
afrim_wish.get_selected_predicate(),
149-
Some(&("test1234".to_owned(), "".to_owned(), "".to_owned()))
229+
rx2.recv().unwrap(),
230+
Command::Predicate(Predicate {
231+
code: "test123".to_owned(),
232+
remaining_code: "".to_owned(),
233+
texts: vec!["ok".to_owned()],
234+
can_commit: false,
235+
})
150236
);
151-
afrim_wish.next_predicate();
237+
tx1.send(Command::SelectNextPredicate).unwrap();
238+
tx1.send(Command::SelectedPredicate).unwrap();
152239
assert_eq!(
153-
afrim_wish.get_selected_predicate(),
154-
Some(&("test".to_owned(), "123".to_owned(), "ok".to_owned()))
240+
rx2.recv().unwrap(),
241+
Command::Predicate(Predicate {
242+
code: "test".to_owned(),
243+
remaining_code: "123".to_owned(),
244+
texts: vec!["ok".to_owned()],
245+
can_commit: false,
246+
})
155247
);
156-
afrim_wish.display();
157-
afrim_wish.destroy();
248+
tx1.send(Command::Update).unwrap();
249+
250+
// We end the communication.
251+
tx1.send(Command::End).unwrap();
252+
assert_eq!(rx2.recv().unwrap(), Command::End);
253+
assert!(rx2.recv().is_err());
254+
255+
// We wait the afrim to end properly.
256+
afrim_wish_thread.join().unwrap();
158257
}
159258
}

0 commit comments

Comments
 (0)