use bladvak::BladvakApp;
#[cfg(target_arch = "wasm32")]
use bladvak::ErrorManager;
use bladvak::eframe;
use bladvak::eframe::egui;
#[derive(serde::Serialize, serde::Deserialize, Debug)]
pub enum SendType {
Code(String),
Result(String),
Quit,
}
#[derive(serde::Serialize, serde::Deserialize, Debug, Default)]
pub struct BaobabApp {
value: String,
old_values: Vec<SendType>,
#[cfg(target_arch = "wasm32")]
#[serde(skip)]
channels: (Option<SendType>, Option<SendType>),
#[cfg(not(target_arch = "wasm32"))]
#[serde(skip)]
channels: Option<(
std::sync::mpsc::Sender<SendType>,
std::sync::mpsc::Receiver<SendType>,
)>,
#[cfg(target_arch = "wasm32")]
#[serde(skip)]
context: boa_engine::Context,
}
impl BaobabApp {
pub(crate) fn send_command(&mut self, value: SendType) -> Result<(), String> {
#[cfg(not(target_arch = "wasm32"))]
{
let Some(channels) = &self.channels else {
return Err("No channels".to_string());
};
if let Err(e) = channels.0.send(value) {
return Err(format!("Failed to send code to JS engine: {}", e));
}
Ok(())
}
#[cfg(target_arch = "wasm32")]
{
self.channels.0 = Some(value);
Ok(())
}
}
#[cfg(target_arch = "wasm32")]
pub(crate) fn receive_command(&mut self) -> Option<SendType> {
self.channels.0.take()
}
#[cfg(target_arch = "wasm32")]
pub(crate) fn send_result(&mut self, value: SendType) -> Result<(), String> {
self.channels.1 = Some(value);
Ok(())
}
pub(crate) fn receive_result(&mut self) -> Option<SendType> {
#[cfg(not(target_arch = "wasm32"))]
{
let Some(channels) = &self.channels else {
return None;
};
channels.1.try_recv().ok()
}
#[cfg(target_arch = "wasm32")]
self.channels.1.take()
}
#[cfg(not(target_arch = "wasm32"))]
pub(crate) fn run_baobab_thread(
recv_js: std::sync::mpsc::Receiver<SendType>,
send_res: std::sync::mpsc::Sender<SendType>,
) -> std::thread::JoinHandle<Result<(), std::io::Error>> {
use boa_engine::{Context, Source};
use std::thread;
thread::spawn(move || {
let mut context = Context::builder().build().map_err(|e| {
std::io::Error::other(format!("Failed to create Boa Context: {}", e))
})?;
while let Ok(sent) = recv_js.recv() {
match sent {
SendType::Quit => break,
SendType::Code(codeline) => {
let val = match context.eval(Source::from_bytes(&codeline)) {
Ok(res) => {
format!("{}", res.display())
}
Err(e) => e.to_string(),
};
send_res.send(SendType::Result(val)).map_err(|e| {
std::io::Error::other(format!(
"Failed to send result back to main thread: {}",
e
))
})?;
}
_ => continue,
}
}
Ok(())
})
}
#[cfg(target_arch = "wasm32")]
pub(crate) fn run_baobab(&mut self, ctx: &egui::Context, error_manager: &mut ErrorManager) {
let Some(value) = self.receive_command() else {
return;
};
let context = &mut self.context;
match value {
SendType::Quit => {
ctx.send_viewport_cmd(egui::ViewportCommand::Close);
}
SendType::Code(codeline) => {
let val = match context.eval(boa_engine::Source::from_bytes(&codeline)) {
Ok(res) => {
format!("{}", res.display())
}
Err(e) => e.to_string(),
};
if let Err(err) = self.send_result(SendType::Result(val)) {
error_manager.add_error(err);
}
}
_ => {}
}
}
}
impl BladvakApp<'_> for BaobabApp {
fn try_new_with_args(
saved_state: Self,
_cc: &eframe::CreationContext<'_>,
_args: &[String],
_error_manager: &mut bladvak::ErrorManager,
) -> Result<Self, bladvak::AppError> {
let Self {
old_values, value, ..
} = saved_state;
#[cfg(not(target_arch = "wasm32"))]
{
use std::sync::mpsc::channel;
let (send_js, recv_js) = channel::<SendType>();
let (send_res, recv_red) = channel::<SendType>();
let handle = BaobabApp::run_baobab_thread(recv_js, send_res);
drop(handle);
let channels = Some((send_js, recv_red));
Ok(Self {
old_values,
value,
channels,
})
}
#[cfg(target_arch = "wasm32")]
{
let context = boa_engine::Context::builder()
.build()
.map_err(|e| format!("Failed to create Boa Context: {}", e))?;
let channels = (None, None);
Ok(Self {
old_values,
value,
#[cfg(target_arch = "wasm32")]
context,
channels,
})
}
}
fn top_panel(&mut self, _ui: &mut egui::Ui, _error_manager: &mut bladvak::ErrorManager) {}
fn handle_file(&mut self, _bytes: bladvak::File) -> Result<(), bladvak::AppError> {
Ok(())
}
fn menu_file(&mut self, _ui: &mut egui::Ui, _error_manager: &mut bladvak::ErrorManager) {}
fn name() -> String {
env!("CARGO_PKG_NAME").to_string()
}
fn version() -> String {
env!("CARGO_PKG_VERSION").to_string()
}
fn repo_url() -> String {
"https://github.com/Its-Just-Nans/baobab".to_string()
}
fn is_open_button(&self) -> bool {
false
}
fn central_panel(
&mut self,
ui: &mut bladvak::eframe::egui::Ui,
error_manager: &mut bladvak::ErrorManager,
) {
egui::Panel::bottom("bottom_panel").show(ui, |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)) {
#[cfg(target_arch = "wasm32")]
{
let input = self.value.clone();
self.old_values
.push(SendType::Code(format!("{} {}", ">>", input.clone())));
if let Err(err) = self.send_command(SendType::Code(input)) {
error_manager.add_error(err);
}
wid.request_focus();
self.value.clear();
}
#[cfg(not(target_arch = "wasm32"))]
{
let input = self.value.clone();
if input == "exit"
|| input == "exit;"
|| input == "exit()"
|| input == "exit();"
{
if let Err(err) = self.send_command(SendType::Quit) {
error_manager.add_error(err);
}
ui.ctx().send_viewport_cmd(egui::ViewportCommand::Close);
wid.request_focus();
self.value.clear();
} else {
self.old_values.push(SendType::Code(format!(
"{} {}",
">>",
input.clone()
)));
if let Err(err) = self.send_command(SendType::Code(input)) {
error_manager.add_error(err);
}
wid.request_focus();
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));
}
}
if let Some(result) = self.receive_result() {
self.old_values.push(result);
}
});
});
egui::CentralPanel::default().show(ui, |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();
}
_ => {}
});
});
});
#[cfg(target_arch = "wasm32")]
self.run_baobab(ui.ctx(), error_manager);
}
}