tissue 0.0.0

Quickly showcase your machine learning magic! On the tissue, with Rust.
use std::{
    fmt::{Debug, Display},
    str::FromStr,
};

use num_traits::{zero, Float};
use tao::{
    event::{Event, StartCause, WindowEvent},
    event_loop::{ControlFlow, EventLoopBuilder},
    window::WindowBuilder,
};
use wry::{WebViewAttributes, WebViewBuilder};

enum UserEvents {
    ExecEval(String),
}

pub enum Input {
    Number(f64),
    Text(String),
    Slider {
        min: f64,
        max: f64,
        step: f64,
        initial_value: f64,
    },
}

pub fn run<F, P>(predictor: P, inputs: &[Input]) -> wry::Result<()>
where
    F: 'static + Float + Display + FromStr,
    P: 'static + Fn(Vec<F>) -> F,
    <F as FromStr>::Err: Debug,
{
    let mut html = beginning();
    for (idx, input) in inputs.iter().enumerate() {
        html.push_str(&match input {
            Input::Number(iv) => add_number(idx, iv),
            Input::Text(s) => add_text(idx, s),
            Input::Slider {
                min,
                max,
                step,
                initial_value,
            } => add_slider(idx, initial_value, max, min, step),
        });
    }
    html.push_str(&end());

    let event_loop = EventLoopBuilder::<UserEvents>::with_user_event().build();
    let proxy = event_loop.create_proxy();

    let ipc_handler = move |req: String| {
        let _ = proxy.send_event(UserEvents::ExecEval(req));
    };

    let window = WindowBuilder::new()
        .with_title("Tissue")
        .build(&event_loop)
        .unwrap();

    #[cfg(any(
        target_os = "windows",
        target_os = "macos",
        target_os = "ios",
        target_os = "android"
    ))]
    let mut builder = WebViewBuilder::new(&window);

    #[cfg(not(any(
        target_os = "windows",
        target_os = "macos",
        target_os = "ios",
        target_os = "android"
    )))]
    use tao::platform::unix::WindowExtUnix;
    use wry::WebViewBuilderExtUnix;
    let mut builder = {
        let vbox = window.default_vbox().unwrap();
        WebViewBuilder::new_gtk(vbox)
    };

    let mut webview_settings = WebViewAttributes::default();
    webview_settings.devtools = true;
    builder.attrs = webview_settings;
    let _webview = builder
        .with_html(&html)
        .with_ipc_handler(ipc_handler)
        .build()
        .unwrap();

    event_loop.run(move |event, _, control_flow| {
        *control_flow = ControlFlow::Wait;

        match event {
            Event::UserEvent(UserEvents::ExecEval(req)) => {
                let number_strings = req.split(",");
                let mut inputs = vec![zero(); 0];
                for number in number_strings {
                    inputs.push(number.parse().unwrap());
                }

                let y = predictor(inputs);
                _webview
                    .evaluate_script_with_callback(
                        &*format!("document.getElementById('output').value = {}", y),
                        |result| println!("{}", result),
                    )
                    .unwrap()
            }
            Event::NewEvents(StartCause::Init) => println!("Wry has started!"),
            Event::WindowEvent {
                event: WindowEvent::CloseRequested,
                ..
            } => *control_flow = ControlFlow::Exit,
            _ => (),
        }
    });
}

fn add_input(index: usize, input_type: &str, initial_value: &str) -> String {
    format!("\
    <label for=\"exampleInput{index}\" class=\"col-sm-2 col-form-label mt-3\"><i>x<sub>{index}</sub> = </i></label>\
    <div class=\"col-sm-10 mt-3\">\
        <input type=\"{input_type}\" class=\"form-control input\" id=\"exampleInput{index}\" name=\"x{index}\" aria-describedby=\"input6\" placeholder=\"x{index}\" value=\"{initial_value}\">\
    </div>")
}

fn add_number(index: usize, initial_value: &f64) -> String {
    add_input(index, "text", &initial_value.to_string())
}

fn add_text(index: usize, initial_value: &str) -> String {
    add_input(index, "text", initial_value)
}

fn add_slider(index: usize, initial_value: &f64, max: &f64, min: &f64, step: &f64) -> String {
    format!("\
    <label for=\"exampleInput{index}\" class=\"col-sm-2 col-form-label mt-3\"><i>x<sub>{index}</sub> = </i></label>\
    <div class=\"col-sm-10 mt-3 form-group\" style=\"display: flex\">\
            <input type=\"text\" class=\"form-control col-sm-2\" value=\"{initial_value}\" readonly>
            <input type=\"range\" class=\"form-control input col-sm-10\" min=\"{min}\" max=\"{max}\" step=\"{step}\" id=\"exampleInput{index}\" name=\"x{index}\" aria-describedby=\"input6\" placeholder=\"x{index}\" value=\"{initial_value}\" oninput=\"this.previousElementSibling.value = this.value\">\
    </div>")
}

fn beginning() -> String {
    "<html lang=\"en\">
        <head>
            <meta charset=\"utf-8\">
            <meta name=\"viewport\" content=\"width=device-width, initial-scale=1, shrink-to-fit=no\">
            <link rel=\"stylesheet\" href=\"https://cdn.jsdelivr.net/npm/bootstrap@4.3.1/dist/css/bootstrap.min.css\" integrity=\"sha384-ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/iJTQUOhcWr7x9JvoRxT2MZw1T\" crossorigin=\"anonymous\">
            <script type=\"text/javascript\">
                function run_calculation() {
                    var numbers = [];
                    var classes = document.getElementsByClassName('input');
                    Array.from(classes).forEach((x, i) => numbers.push(Number(x.value)));
                    ipc.postMessage(numbers.toString());
                }
            </script>
        </head>
        <body>
            <div class=\"container\">
                <div class=\"row mt-3\">
                    <div class=\"col text-center\">
                        <form action=\"#\" method=\"POST\" onsubmit=\"run_calculation()\">
                            <div class=\"form-group row\" id=\"input-group\">".to_string()
}

fn end() -> String {
    "                        </div>         
        
                            <div class=\"form-group\" id=\"submit\">
                                <button type=\"submit\" class=\"btn btn-primary\">Submit</button>
                            </div>
                        
                            <div class=\"form-group row\" id=\"output-group\">
                                <label for=\"output\" class=\"col-sm-2 col-form-label\"><i>y = </i></label>
                                <div class=\"col-sm-10\">
                                    <input type=\"text\" class=\"form-control\" id=\"output\" name=\"output\" aria-describedby=\"output\" readonly>
                                </div>
                            </div>
                        </form>
                    </div>
                </div>  
            </div>
            <script src=\"https://code.jquery.com/jquery-3.3.1.slim.min.js\" integrity=\"sha384-q8i/X+965DzO0rT7abK41JStQIAqVgRVzpbzo5smXKp4YfRvH+8abtTE1Pi6jizo\" crossorigin=\"anonymous\"></script>
            <script src=\"https://cdn.jsdelivr.net/npm/popper.js@1.14.7/dist/umd/popper.min.js\" integrity=\"sha384-UO2eT0CpHqdSJQ6hJty5KVphtPhzWj9WO1clHTMGa3JDZwrnQq4sF86dIHNDz0W1\" crossorigin=\"anonymous\"></script>
            <script src=\"https://cdn.jsdelivr.net/npm/bootstrap@4.3.1/dist/js/bootstrap.min.js\" integrity=\"sha384-JjSmVgyd0p3pXB1rRibZUAYoIIy6OrQ6VrjIEaFf/nJGzIxFDsf4x0xIM+B07jRM\" crossorigin=\"anonymous\"></script>
        </body>
    </html>".to_string()
}