-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
5 changed files
with
382 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,110 @@ | ||
use std::{fs::File, io::Write}; | ||
|
||
use eframe::egui::{Context, Id, ScrollArea, TextEdit, TextStyle, Ui, Window}; | ||
use egui_extras::syntax_highlighting::{highlight, CodeTheme}; | ||
|
||
use crate::{err, impl_sub_window}; | ||
|
||
use super::{main::SubWindow, pyenv::PyEnv}; | ||
|
||
pub struct CodeEditor { | ||
code: String, | ||
save_to: String, | ||
pyenv: PyEnv, | ||
} | ||
|
||
impl Default for CodeEditor { | ||
fn default() -> Self { | ||
Self { | ||
code: "\ | ||
from tester import * | ||
print('Hello, world!') | ||
".to_string(), | ||
save_to: "".to_string(), | ||
pyenv: PyEnv::default(), | ||
} | ||
} | ||
} | ||
|
||
impl CodeEditor { | ||
fn editor(&mut self, ui: &mut Ui) { | ||
let mut layout = |ui: &Ui, string: &str, width: f32| { | ||
let theme = CodeTheme::from_style(ui.style()); | ||
let mut job = highlight(ui.ctx(), &theme, string, "Python"); | ||
job.wrap.max_width = width; | ||
ui.fonts(|f| f.layout_job(job)) | ||
}; | ||
|
||
ScrollArea::vertical().show(ui, |ui| { | ||
ui.add( | ||
TextEdit::multiline(&mut self.code) | ||
.font(TextStyle::Monospace) | ||
.code_editor() | ||
.desired_rows(25) | ||
.lock_focus(true) | ||
.desired_width(f32::INFINITY) | ||
.layouter(&mut layout), | ||
) | ||
}); | ||
} | ||
fn run_code(&mut self) { | ||
self.pyenv.run_code(&self.code); | ||
} | ||
fn save_code(&mut self, ui: &mut Ui) { | ||
if ui.button("Write to file").clicked() { | ||
println!("Write to file: {}", self.save_to); | ||
let f = File::options() | ||
.append(true) | ||
.create(true) | ||
.open(&self.save_to); | ||
match f { | ||
Ok(mut file) => { | ||
let e = file.write_all(self.code.as_bytes()); | ||
if let Err(e) = e { | ||
err!("Write to file error: {}", e); | ||
} else { | ||
self.code.clear(); | ||
} | ||
} | ||
Err(e) => { | ||
err!("Open file error: {}", e); | ||
} | ||
} | ||
} | ||
} | ||
fn bottom_butt(&mut self, ui: &mut Ui) { | ||
ui.horizontal(|ui| { | ||
if ui.button("Run").clicked() { | ||
self.run_code(); | ||
} | ||
self.save_code(ui); | ||
}); | ||
} | ||
fn bottom(&mut self, ui: &mut Ui) { | ||
ui.vertical(|ui| { | ||
ui.horizontal(|ui| { | ||
ui.label("Save to:"); | ||
ui.text_edit_singleline(&mut self.save_to); | ||
}); | ||
self.bottom_butt(ui); | ||
}); | ||
} | ||
fn show(&mut self, ui: &mut Ui) { | ||
ui.vertical(|ui| { | ||
self.editor(ui); | ||
self.bottom(ui); | ||
}); | ||
} | ||
} | ||
|
||
impl SubWindow for CodeEditor { | ||
fn show(&mut self, ctx: &Context, title: &str, id: &Id, open: &mut bool) { | ||
let window = Window::new(title).id(id.to_owned()).open(open).resizable([true, true]); | ||
window.show(ctx, |ui| { | ||
self.show(ui); | ||
}); | ||
} | ||
} | ||
|
||
impl_sub_window!(CodeEditor, "CodeEditor"); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,174 @@ | ||
//! Main UI render for the APP | ||
use std::error::Error; | ||
|
||
use eframe::{ | ||
egui::{Context, Id, SidePanel, Ui, ViewportBuilder}, | ||
run_native, App, Frame, NativeOptions, | ||
}; | ||
|
||
use crate::info; | ||
|
||
/// Main UI struct | ||
/// | ||
/// NOTICE! NOTICE! This will block the main thread. If you have any other tasks to do, please run them in a separate thread. | ||
/// Or, use IPC to communicate with the UI process. | ||
#[derive(Default)] | ||
pub struct AppUi {} | ||
|
||
impl AppUi { | ||
pub fn new() -> Result<Self, Box<dyn Error>> { | ||
let options = NativeOptions { | ||
viewport: ViewportBuilder::default() | ||
.with_title("AutoTestor") | ||
.with_inner_size([800.0, 600.0]), | ||
..Default::default() | ||
}; | ||
|
||
run_native( | ||
"AutoTestor", | ||
options, | ||
Box::new(|_cc| Ok(Box::<MyApp>::default())), | ||
)?; | ||
|
||
Ok(AppUi {}) | ||
} | ||
} | ||
|
||
struct SubWindowHolder { | ||
window: Box<dyn SubWindow>, | ||
id: Id, | ||
title: String, | ||
open: bool, | ||
} | ||
|
||
struct MyApp { | ||
sub_window_creator: Vec<Box<DynSubWindowCreator>>, // We ensure that the sub windows only work in the main thread | ||
sub_windows: Vec<SubWindowHolder>, | ||
sub_window_idx: usize, | ||
} | ||
|
||
impl Default for MyApp { | ||
fn default() -> Self { | ||
let mut sub_window_creator = Vec::new(); | ||
for c in inventory::iter::<SubWindowCollector> { | ||
let f = c.create; | ||
let w = f(); | ||
sub_window_creator.push(w); | ||
} | ||
Self { | ||
sub_window_creator, | ||
sub_windows: Vec::new(), | ||
sub_window_idx: 0, | ||
} | ||
} | ||
} | ||
|
||
impl MyApp { | ||
fn sub_window_pannel(&mut self, ctx: &Context, ui: &mut Ui) { | ||
ui.label("SubWindow Panel"); | ||
ui.vertical(|ui| { | ||
for creator in &self.sub_window_creator { | ||
let name = creator.name(); | ||
if ui.button(name).clicked() { | ||
let title = format!("{}: {}", name, self.sub_window_idx); | ||
let id = Id::new(self.sub_window_idx); | ||
info!("Try create sub window: {}", title); | ||
self.sub_window_idx += 1; | ||
self.sub_windows.push(SubWindowHolder { | ||
window: creator.open(), | ||
id, | ||
title, | ||
open: true, | ||
}); | ||
} | ||
} | ||
self.sub_windows.retain(|w| w.open); | ||
for w in &mut self.sub_windows { | ||
w.window.show(ctx, &w.title, &w.id, &mut w.open); | ||
} | ||
}); | ||
} | ||
} | ||
|
||
impl App for MyApp { | ||
fn update(&mut self, ctx: &Context, frame: &mut Frame) { | ||
let _ = frame; | ||
SidePanel::right("SubWindow Panel") | ||
.default_width(200.0) | ||
.show(ctx, |ui| { | ||
self.sub_window_pannel(ctx, ui); | ||
}); | ||
} | ||
} | ||
|
||
/// SubWindow trait | ||
/// | ||
/// This trait is used to hold the sub window. | ||
/// This is because the egui is a immediate mode UI. when you display a new window, it will be shown THAT particular frame. if you need that window to stay, you need to create a new window AGAIN next frame too and egui using the window's name (or other id source), egui will internally keep track of its position / focus status etc.. | ||
/// So, we need some way to keep track of the sub window. | ||
pub trait SubWindow { | ||
/// Show the window, this will be called every frame. Your window is identified by the `id` parameter. | ||
/// However, that doesn't mean you should change the title, as this contains the window number, useful for the user. | ||
fn show(&mut self, ctx: &Context, title: &str, id: &Id, open: &mut bool); | ||
} | ||
|
||
pub trait SubWindowCreator { | ||
fn name(&self) -> &str; | ||
fn open(&self) -> Box<dyn SubWindow>; | ||
} | ||
|
||
/// Snippet to register a sub window | ||
/// | ||
/// # Arguments | ||
/// $name: The struct name of the sub window | ||
/// $window_name: The name of the window, will become the title of the window | ||
/// | ||
/// # Example | ||
/// `impl_sub_window!(TestUiStruct, "TestUiName");` | ||
/// where TestUiStruct implements SubWindow trait and Default trait | ||
/// | ||
/// # Notice | ||
/// If you found rust-analyzer gives "invalid metavariable expression", this is a nightly feature, you can ignore it. It will work. | ||
/// The problem is on `${concat()}` macro. Just suppress it. | ||
#[macro_export] | ||
macro_rules! impl_sub_window { | ||
($name:ident, $window_name:literal) => { | ||
struct ${concat($name, Creator)} {} | ||
|
||
impl $crate::ui::main::SubWindowCreator for ${concat($name, Creator)} { | ||
fn name(&self) -> &str { | ||
$window_name | ||
} | ||
fn open(&self) -> Box<dyn $crate::ui::main::SubWindow> { | ||
Box::new($name::default()) | ||
} | ||
} | ||
|
||
#[allow(non_snake_case)] | ||
fn ${concat(create_, $name)}() -> Box<$crate::ui::main::DynSubWindowCreator> { | ||
Box::new(${concat($name, Creator)} {}) | ||
} | ||
|
||
inventory::submit! { | ||
$crate::ui::main::SubWindowCollector { | ||
create: ${concat(create_, $name)}, | ||
} | ||
} | ||
}; | ||
} | ||
|
||
/// Type should return from the creator. | ||
#[doc(hidden)] | ||
pub type DynSubWindowCreator = dyn SubWindowCreator + Send + Sync + 'static; | ||
|
||
#[doc(hidden)] | ||
/// We need to use a function to create the sub window creator on start time. | ||
pub type SubWindowCreatorCreator = fn() -> Box<DynSubWindowCreator>; | ||
|
||
#[doc(hidden)] | ||
pub struct SubWindowCollector { | ||
pub create: SubWindowCreatorCreator, | ||
} | ||
|
||
inventory::collect!(SubWindowCollector); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
//! Parts to handle the UI render part | ||
//! | ||
//! If only use CLI feats, this part can be ignored; | ||
//! however, GUI part may need this part show what's going on. | ||
//! Or, use to create needle for GUI part. | ||
//! | ||
pub mod main; | ||
pub mod code_editor; | ||
pub mod pyenv; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,54 @@ | ||
use std::{collections::HashMap, sync::LazyLock}; | ||
|
||
use pyo3::{ | ||
ffi::{c_str, PyImport_AddModule, PyModule_GetDict}, | ||
prepare_freethreaded_python, | ||
types::{IntoPyDict, PyAnyMethods, PyDict, PyDictMethods}, | ||
Bound, Py, Python, | ||
}; | ||
|
||
use crate::{err, info}; | ||
|
||
pub struct PyEnv { | ||
globals: Py<PyDict>, | ||
locals: Py<PyDict>, | ||
} | ||
|
||
impl Default for PyEnv { | ||
fn default() -> Self { | ||
static GLOBALS: LazyLock<Py<PyDict>> = LazyLock::new(|| { | ||
prepare_freethreaded_python(); | ||
Python::with_gil(|py| unsafe { | ||
let mptr = PyImport_AddModule(c_str!("__main__").as_ptr()); | ||
if mptr.is_null() { | ||
panic!("Failed to get __main__ module"); | ||
} | ||
let globals = PyModule_GetDict(mptr); | ||
Py::from_owned_ptr(py, globals) | ||
}) | ||
}); | ||
prepare_freethreaded_python(); | ||
Python::with_gil(|py| { | ||
let globals = GLOBALS.clone_ref(py); | ||
globals.bind(py).set_item("__virt__", 1).unwrap(); // Force to copy globals dict, otherwise drop one PyEnv will affect others | ||
let locals = unsafe { | ||
let mptr = globals.as_ptr(); | ||
Py::from_owned_ptr(py, mptr) | ||
}; | ||
Self { globals, locals } | ||
}) | ||
} | ||
} | ||
|
||
impl PyEnv { | ||
pub fn run_code(&mut self, code: &str) { | ||
Python::with_gil(|py| { | ||
let globals = self.globals.bind(py); | ||
let locals = self.locals.bind(py); | ||
let e = py.run_bound(code, Some(globals), Some(locals)); | ||
if let Err(e) = e { | ||
err!("Run code error: {}", e); | ||
} | ||
}); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
#![feature(macro_metavar_expr_concat)] | ||
|
||
use tester::{ | ||
impl_sub_window, | ||
ui::main::{AppUi, SubWindow}, | ||
}; | ||
|
||
use eframe::egui::{Context, Id, Window}; | ||
|
||
pub struct TestUi {} | ||
|
||
impl Default for TestUi { | ||
fn default() -> Self { | ||
Self {} | ||
} | ||
} | ||
|
||
impl SubWindow for TestUi { | ||
fn show(&mut self, ctx: &Context, title: &str, id: &Id, open: &mut bool) { | ||
Window::new(title) | ||
.id(id.to_owned()) | ||
.open(open) | ||
.show(ctx, |ui| { | ||
ui.label("TestUi"); | ||
}); | ||
} | ||
} | ||
|
||
impl_sub_window!(TestUi, "TestUi"); | ||
|
||
fn main() { | ||
let _ui = AppUi::new().unwrap(); | ||
return (); | ||
} |