prophecy 0.5.0

FutureSDR GUI
Documentation
use futures::StreamExt;
use gloo_net::websocket::Message;
use gloo_net::websocket::futures::WebSocket;
use leptos::html::Canvas;
use leptos::logging::*;
use leptos::prelude::*;
use leptos::task::spawn_local;
use leptos::wasm_bindgen::*;
use std::cell::RefCell;
use std::rc::Rc;
use web_sys::HtmlCanvasElement;
use web_sys::WebGlProgram;
use web_sys::WebGlRenderingContext as GL;

struct RenderState {
    canvas: HtmlCanvasElement,
    gl: GL,
    shader: WebGlProgram,
    vertex_len: i32,
}

#[component]
/// Constellation Sink
pub fn ConstellationSink(
    #[prop(into)] width: Signal<f32>,
    #[prop(optional, into, default = "ws://127.0.0.1:9002".to_string())] websocket: String,
) -> impl IntoView {
    let data = Rc::new(RefCell::new(None));
    {
        let data = data.clone();
        spawn_local(async move {
            let mut ws = WebSocket::open(&websocket).unwrap();
            while let Some(msg) = ws.next().await {
                match msg {
                    Ok(Message::Bytes(b)) => {
                        *data.borrow_mut() = Some(b);
                    }
                    _ => {
                        log!("ConstellationSink: WebSocket {:?}", msg);
                    }
                }
            }
            log!("ConstellationSink: WebSocket Closed");
        });
    }

    let canvas_ref = NodeRef::<Canvas>::new();
    Effect::new(move || {
        if let Some(canvas) = canvas_ref.get() {
            let gl: GL = canvas
                .get_context("webgl")
                .unwrap()
                .unwrap()
                .dyn_into()
                .unwrap();

            let vert_code = r"
                attribute vec2 coordinates;
                uniform float u_width;

                void main(void) {
                    float x = coordinates.x / u_width;
                    float y = coordinates.y / u_width;
                    gl_Position = vec4(x, y, 0.0, 1.0);
                    gl_PointSize = 10.0;
                }
            ";

            let vert_shader = gl.create_shader(GL::VERTEX_SHADER).unwrap();
            gl.shader_source(&vert_shader, vert_code);
            gl.compile_shader(&vert_shader);

            let frag_code = r"
                precision mediump float;

                void main(void) {
                    gl_FragColor = vec4(0, 0.5, 0.5, 0.4);
                }
            ";

            let frag_shader = gl.create_shader(GL::FRAGMENT_SHADER).unwrap();
            gl.shader_source(&frag_shader, frag_code);
            gl.compile_shader(&frag_shader);

            let shader = gl.create_program().unwrap();
            gl.attach_shader(&shader, &vert_shader);
            gl.attach_shader(&shader, &frag_shader);
            gl.link_program(&shader);
            gl.use_program(Some(&shader));

            let u_min = gl.get_uniform_location(&shader, "u_width");
            gl.uniform1f(u_min.as_ref(), width());

            let vertex_buffer = gl.create_buffer().unwrap();
            gl.bind_buffer(GL::ARRAY_BUFFER, Some(&vertex_buffer));

            let state = Rc::new(RefCell::new(RenderState {
                canvas,
                gl,
                shader,
                vertex_len: 0,
            }));
            request_animation_frame(render(state, data.clone()))
        }
    });

    view! { <canvas node_ref=canvas_ref style="width: 100%; height: 100%" /> }
}

fn render(
    state: Rc<RefCell<RenderState>>,
    data: Rc<RefCell<Option<Vec<u8>>>>,
) -> impl FnOnce() + 'static {
    move || {
        {
            let RenderState {
                canvas,
                gl,
                shader,
                vertex_len,
            } = &mut (*state.borrow_mut());

            let display_width = canvas.client_width() as u32;
            let display_height = canvas.client_height() as u32;

            let need_resize = canvas.width() != display_width || canvas.height() != display_height;

            if need_resize {
                canvas.set_width(display_width);
                canvas.set_height(display_height);
                gl.viewport(0, 0, display_width as i32, display_height as i32);
            }

            if let Some(bytes) = data.borrow_mut().take() {
                gl.buffer_data_with_u8_array(GL::ARRAY_BUFFER, &bytes, GL::DYNAMIC_DRAW);

                *vertex_len = bytes.len() as i32 / 8;
            };

            let position = gl.get_attrib_location(shader, "coordinates") as u32;
            gl.vertex_attrib_pointer_with_i32(position, 2, GL::FLOAT, false, 0, 0);
            gl.enable_vertex_attrib_array(position);
            gl.draw_arrays(GL::POINTS, 0, *vertex_len);
        }
        request_animation_frame(render(state, data))
    }
}