#![warn(missing_docs)]
pub mod ui;
pub mod element;
pub mod event;
pub mod ui_ref;
pub mod graphics;
pub mod window;
pub mod respack;
mod msgsender;
mod ui_data;
use std::error::Error;
use std::fmt;
use std::path::Path;
use std::str::FromStr;
use std::sync::{Arc, Mutex};
use std::time::Duration;
use std::collections::HashMap;
use futures::Future;
use ui::Gui;
use ui_ref::UiRef;
use window::Menu;
use crate::ui::Ui;
pub type Filemap = HashMap<String, Vec<u8>>;
type JSType = serde_json::Value;
type JSMap = serde_json::Map<String, JSType>;
pub type Result<T> = core::result::Result<T, GemGuiError>;
#[derive(Copy, Clone, Debug)]
pub struct Rect<T>
where T: FromStr + Copy { x: T,
y: T,
width: T,
height: T,
}
impl<T> Rect<T>
where T: FromStr + Copy {
pub fn new(x: T, y: T, width: T, height: T) -> Rect<T> {
Rect{x, y, width, height}
}
pub fn x(&self) -> T {
self.x
}
pub fn y(&self) -> T {
self.y
}
pub fn width(&self) -> T {
self.width
}
pub fn height(&self) -> T {
self.height
}
}
#[derive(Clone)]
pub struct Value<T>
where T: Clone {
value: Arc<Mutex<T>>,
}
impl<T> Value<T>
where T: Clone {
pub fn new(value : T) -> Value<T>
where T: Clone {
Value{value: Arc::new(Mutex::new(value))}
}
pub fn assign(&self, new: T) {
let mut l = self.value.lock().unwrap();
*l = new;
}
pub fn cloned(&self) -> T {
let l = self.value.lock().unwrap();
l.clone()
}
}
pub fn filemap_from(resource_map: &[(&'static str, &'static str)]) -> Filemap {
let mut filemap = Filemap::new();
for resource in resource_map {
let res = base64::decode(resource.1).unwrap();
let key = resource.0.to_string();
if filemap.contains_key(&key) {
eprintln!("Warning: {:#?} already in resources", &key);
}
filemap.insert(key, res);
}
filemap
}
#[derive(serde::Serialize, Debug, Default)]
struct JSMessageTx<'a> {
element: &'a str,
#[serde(rename = "type")]
_type: &'a str,
#[serde(skip_serializing_if = "Option::is_none")]
html: Option<&'a str>,
#[serde(skip_serializing_if = "Option::is_none")]
msgid: Option<i32>,
#[serde(skip_serializing_if = "Option::is_none")]
event: Option<&'a str>,
#[serde(skip_serializing_if = "Option::is_none")]
properties: Option<&'a Vec<JSType>>,
#[serde(skip_serializing_if = "Option::is_none")]
throttle: Option<&'a str>,
#[serde(skip_serializing_if = "Option::is_none")]
html_element: Option<&'a str>,
#[serde(skip_serializing_if = "Option::is_none")]
new_id: Option<&'a str>,
#[serde(skip_serializing_if = "Option::is_none")]
query_id: Option<&'a str>,
#[serde(skip_serializing_if = "Option::is_none")]
query: Option<&'a str>,
#[serde(skip_serializing_if = "Option::is_none")]
query_params: Option<&'a Vec<String>>,
#[serde(skip_serializing_if = "Option::is_none")]
style: Option<&'a str>,
#[serde(skip_serializing_if = "Option::is_none")]
value: Option<&'a str>,
#[serde(skip_serializing_if = "Option::is_none")]
attribute: Option<&'a str>,
#[serde(skip_serializing_if = "Option::is_none")]
remove: Option<&'a str>,
#[serde(skip_serializing_if = "Option::is_none")]
eval: Option<&'a str>,
#[serde(skip_serializing_if = "Option::is_none")]
logging: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
debug: Option<&'a str>,
#[serde(skip_serializing_if = "Option::is_none")]
alert: Option<&'a str>,
#[serde(skip_serializing_if = "Option::is_none")]
open: Option<&'a JSMap>,
#[serde(skip_serializing_if = "Option::is_none")]
batches: Option<&'a Vec<JSType>>,
#[serde(skip_serializing_if = "Option::is_none")]
add: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
name: Option<&'a str>,
#[serde(skip_serializing_if = "Option::is_none")]
image: Option<&'a str>,
#[serde(skip_serializing_if = "Option::is_none")]
rect: Option<Vec<f32>>,
#[serde(skip_serializing_if = "Option::is_none")]
clip: Option<Vec<f32>>,
#[serde(skip_serializing_if = "Option::is_none")]
pos: Option<Vec<f32>>,
#[serde(skip_serializing_if = "Option::is_none")]
commands: Option<&'a Vec<String>>,
#[serde(skip_serializing_if = "Option::is_none")]
extension_id: Option<&'a str>,
#[serde(skip_serializing_if = "Option::is_none")]
extension_call: Option<&'a str>,
#[serde(skip_serializing_if = "Option::is_none")]
extension_params: Option<&'a JSMap>,
}
#[derive(serde::Deserialize, serde::Serialize, Debug)]
struct JSMessageRx {
#[serde(rename = "type")]
_type: String,
#[serde(skip_serializing_if = "Option::is_none")]
element: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
event: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
properties: Option<JSMap>,
}
pub (crate) fn value_to_string_list(value: JSType) -> Option<Vec<String>> {
if ! value.is_array() {
eprintln!("Values is not an array {value}");
return None;
}
let array = value.as_array().unwrap();
let mut result = Vec::new();
for v in array.iter() {
if v.is_string() {
result.push(String::from(v.as_str().unwrap()));
} else {
eprintln!("item not understood ... todo {v}");
return None;
}
}
Some(result)
}
pub (crate) fn value_to_string_map(value: JSType) -> Option<HashMap<String, String>> {
if ! value.is_object() {
eprintln!("Values is not an object {value}");
return None;
}
let obj = value.as_object().unwrap();
let mut result = HashMap::new();
for v in obj.iter() {
if v.1.is_string() {
result.insert( v.0.clone(), String::from(v.1.as_str().unwrap()));
} else if v.1.is_boolean() || v.1.is_number() { result.insert( v.0.clone(), format!("{}", v.1));
} else {
eprintln!("item not understood ... todo {}", v.1);
return None;
}
}
Some(result)
}
pub (crate) fn value_to_string(value: JSType) -> Option<String> {
if ! value.is_string() {
eprintln!("Values is not an string {value}");
return None;
}
Some(String::from(value.as_str().unwrap()))
}
#[derive(Debug, Clone)]
pub enum GemGuiError {
Err(String),
}
impl Error for GemGuiError {}
impl fmt::Display for GemGuiError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
Self::Err(e) => write!(f, "GemGui error: {e}"),
}
}
}
impl From<std::io::Error> for GemGuiError {
fn from(err: std::io::Error) -> GemGuiError {
Self::Err(err.to_string())
}
}
impl GemGuiError {
fn new(err: &str) -> GemGuiError {
GemGuiError::Err(err.to_string())
}
fn error<T, Str>(err: Str) -> Result<T>
where Str: Into<String> {
let err = err.into();
Err(GemGuiError::new(&err))
}
}
pub fn filemap_from_dir<DirName>(path: DirName) -> std::io::Result<Filemap>
where DirName: AsRef<Path>{
let dirname = path.as_ref().to_str().unwrap();
let dir = std::fs::read_dir(dirname);
if dir.is_err() {
return Err(std::io::Error::last_os_error());
}
let mut filemap = Filemap::new();
for entry in dir.unwrap() {
let file = entry?;
if file.file_type()?.is_file() {
let contents = std::fs::read(file.path())?;
let name = file.file_name().into_string().unwrap();
filemap.insert(name, contents);
}
}
Ok(filemap)
}
pub fn version() -> (u32, u32, u32) {
const MAJOR: &str = env!("CARGO_PKG_VERSION_MAJOR");
const MINOR: &str = env!("CARGO_PKG_VERSION_MINOR");
const PATCH: &str = env!("CARGO_PKG_VERSION_PATCH");
(MAJOR.parse::<u32>().unwrap(), MINOR.parse::<u32>().unwrap(), PATCH.parse::<u32>().unwrap())
}
pub fn next_free_port(port: u16) -> u16 {
let mut next_port = port;
while ! port_scanner::local_port_available(next_port) {
next_port += 1;
}
next_port
}
pub fn wait_free_port(port: u16, max_wait: Duration) -> bool {
let mut elapsed =Duration::ZERO;
while ! port_scanner::local_port_available(port) {
let sleep = std::time::Duration::from_secs(1);
std::thread::sleep(sleep);
elapsed += sleep;
if elapsed >= max_wait {
return false
}
}
true
}
fn create_application<CB, Fut, Create>(filemap: Filemap, index_html: &str, port: u16, application_cb: CB, mut on_create: Create) -> Result<()>
where CB: FnMut(UiRef)-> Fut + Send + Clone + 'static,
Fut: Future<Output = ()> + Send + 'static,
Create: FnMut(&mut Gui) {
debug_assert!(filemap.contains_key(index_html));
let result: Arc<Mutex<Option<GemGuiError>>> = Arc::new(Mutex::new(None));
tokio::runtime::Builder::new_multi_thread()
.enable_all()
.build()
.unwrap()
.block_on(async {
let ui = Gui::new(filemap, index_html, port);
if ui.is_err() {
let mut r = result.lock().unwrap();
*r = ui.err();
return;
}
let mut ui = ui.unwrap();
on_create(&mut ui);
ui.on_start_async(application_cb);
let rr = ui.run().await;
if rr.is_err() {
let mut r = result.lock().unwrap();
*r = rr.err();
}
});
let r = result.lock().unwrap();
match r.clone() {
None => Ok(()),
Some(e) => Err(e),
}
}
pub fn application<CB, Fut>(filemap: Filemap, index_html: &str, port: u16, application_cb: CB) -> Result<()>
where CB: FnMut(UiRef)-> Fut + Send + Clone + 'static,
Fut: Future<Output = ()> + Send + 'static {
create_application(filemap, index_html, port, application_cb, |_|{})
}
#[allow(clippy::too_many_arguments)]
pub fn window_application<CB, Fut, OptionalMenu>(
filemap: Filemap,
index_html: &str,
port: u16,
application_cb: CB,
title: &str,
width:u32,
height: u32,
parameters: &[(&str, &str)],
flags: u32,
menu: OptionalMenu) -> Result<()>
where CB: FnMut(UiRef)-> Fut + Send + Clone + 'static,
Fut: Future<Output = ()> + Send + 'static,
OptionalMenu: Into<Option<Menu>> {
let menu = menu.into();
match menu {
Some(menu) => {
create_application(filemap, index_html, port, application_cb, move |ui| {
let menu = menu.clone();
ui.set_python_gui(title, width, height, parameters, flags, menu);})
},
None => create_application(filemap, index_html, port, application_cb, move |ui| {
ui.set_python_gui(title, width, height, parameters, flags, None);})
}
}
pub fn default_error(ui: UiRef, err_msg: String) {
eprint!("Exit on error: ");
let json = serde_json::from_str::<HashMap<String, String>>(&err_msg);
match json {
Ok(json) => {
eprintln!("Error: {}\nElement: {}\nTrace: {}\n", json["error"], json["element"], json.get("trace").unwrap_or(&"no trace".to_string()));
},
_ => eprintln!("{err_msg}")
}
ui.exit(); }