1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
//! Baobab App Ui
use eframe::egui;
use std::sync::mpsc::{Receiver, Sender};
/// Types of messages sent between UI and JS engine
#[derive(Debug)]
pub enum SendType {
/// Send Code as string to evaluate
Code(String),
/// Result from evaluation
Result(String),
/// Quit signal
Quit,
}
/// The Baobab App
pub struct BaobabApp {
/// Current value
value: String,
/// Previous values
old_values: Vec<SendType>,
/// Sender to JS engine
send_js: Sender<SendType>,
/// Receiver from JS engine
recv_res: Receiver<SendType>,
}
impl BaobabApp {
/// Create a new Baobab App
pub fn new(
cc: &eframe::CreationContext<'_>,
send_js: Sender<SendType>,
recv_res: Receiver<SendType>,
) -> Self {
// This is also where you can customize the look and feel of egui using
// `cc.egui_ctx.set_visuals` and `cc.egui_ctx.set_fonts`.
// Load previous app state (if any).
// Note that you must enable the `persistence` feature for this to work.
let saved = match cc.storage {
Some(store) => eframe::get_value::<String>(store, eframe::APP_KEY),
_ => None,
};
Self {
old_values: Default::default(),
value: saved.unwrap_or_default(),
send_js,
recv_res,
}
}
}
impl eframe::App for BaobabApp {
fn save(&mut self, storage: &mut dyn eframe::Storage) {
eframe::set_value(storage, eframe::APP_KEY, &self.value);
}
/// Called each time the UI needs repainting, which may be many times per second.
fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) {
egui::TopBottomPanel::top("top_panel").show(ctx, |ui| {
// The top panel is often a good place for a menu bar:
egui::MenuBar::new().ui(ui, |ui| {
// NOTE: no File->Quit on web pages!
let is_web = cfg!(target_arch = "wasm32");
if !is_web {
ui.menu_button("File", |ui| {
if ui.button("Quit").clicked() {
ctx.send_viewport_cmd(egui::ViewportCommand::Close);
}
});
ui.add_space(16.0);
}
egui::widgets::global_theme_preference_buttons(ui);
});
});
egui::TopBottomPanel::bottom("bottom_panel").show(ctx, |ui| {
ui.horizontal(|ui| {
ui.label(">>");
let wid = ui.text_edit_singleline(&mut self.value);
if ui.input(|i| i.key_pressed(egui::Key::Enter)) {
self.old_values.push(SendType::Code(format!(
"{} {}",
">>",
self.value.clone()
)));
if let Err(e) = self.send_js.send(SendType::Code(self.value.clone())) {
eprintln!("Failed to send code to JS engine: {}", e);
}
self.value.clear();
}
if ui.input(|i| i.key_pressed(egui::Key::ArrowUp)) {
if let Some(SendType::Code(s)) =
self.old_values.get(self.old_values.len().wrapping_sub(2))
{
self.value = s.to_string().replace(">> ", "");
}
let text_edit_id = wid.id;
if let Some(mut state) = egui::TextEdit::load_state(ui.ctx(), text_edit_id) {
let ccursor = egui::text::CCursor::new(self.value.chars().count());
state
.cursor
.set_char_range(Some(egui::text::CCursorRange::one(ccursor)));
state.store(ui.ctx(), text_edit_id);
ui.ctx().memory_mut(|mem| mem.request_focus(text_edit_id));
}
}
self.recv_res.try_iter().for_each(|v| {
self.old_values.push(v);
});
wid.request_focus();
});
});
egui::CentralPanel::default().show(ctx, |ui| {
let rect = ui.available_rect_before_wrap();
let scroll_area = egui::ScrollArea::vertical()
.max_height(rect.height())
.stick_to_bottom(true);
scroll_area.show(ui, |ui| {
self.old_values.iter().for_each(|v| match v {
SendType::Code(c) => {
ui.label(c);
}
SendType::Result(r) => {
ui.label(r);
ui.separator();
}
_ => {}
});
});
});
}
}