futuresdr_frontend/gui/
time.rs1use futures::StreamExt;
2use gloo_render::request_animation_frame;
3use gloo_render::AnimationFrame;
4use rbl_circular_buffer::CircularBuffer;
5use reqwasm::websocket::futures::WebSocket;
6use reqwasm::websocket::Message;
7use wasm_bindgen::prelude::*;
8use wasm_bindgen::JsCast;
9use wasm_bindgen_futures::spawn_local;
10use web_sys::HtmlCanvasElement;
11use web_sys::WebGlRenderingContext as GL;
12use yew::prelude::*;
13
14pub enum Msg {
15 Data(Vec<u8>),
16 Status(String),
17 Render(f64),
18}
19
20#[wasm_bindgen]
21pub fn add_time(id: String, url: String, min: f32, max: f32) {
22 let document = gloo_utils::document();
23 let div = document.query_selector(&id).unwrap().unwrap();
24 yew::start_app_with_props_in_element::<Time>(div, Props { url, min, max });
25}
26
27#[derive(Clone, Properties, Default, PartialEq)]
28pub struct Props {
29 pub url: String,
30 pub min: f32,
31 pub max: f32,
32}
33
34pub struct Time {
35 canvas_ref: NodeRef,
36 _canvas: Option<HtmlCanvasElement>,
37 gl: Option<GL>,
38 _render_loop: Option<AnimationFrame>,
39 buff: CircularBuffer<f32>,
40}
41
42impl Component for Time {
43 type Message = Msg;
44 type Properties = Props;
45
46 fn create(ctx: &Context<Self>) -> Self {
47 let link = ctx.link().clone();
48 let url = ctx.props().url.clone();
49
50 spawn_local(async move {
51 let websocket = WebSocket::open(&url).unwrap();
52 let (_, mut rx) = websocket.split();
53
54 while let Some(msg) = rx.next().await {
55 match msg {
56 Ok(Message::Text(s)) => link.send_message(Msg::Status(s)),
57 Ok(Message::Bytes(v)) => link.send_message(Msg::Data(v)),
58 _ => break,
59 }
60 }
61 });
62
63 Self {
64 canvas_ref: NodeRef::default(),
65 _canvas: None,
66 gl: None,
67 _render_loop: None,
68 buff: CircularBuffer::new(2048),
69 }
70 }
71
72 fn rendered(&mut self, ctx: &Context<Self>, first_render: bool) {
73 let canvas = self.canvas_ref.cast::<HtmlCanvasElement>().unwrap();
74
75 let gl: GL = canvas
76 .get_context("webgl")
77 .unwrap()
78 .unwrap()
79 .dyn_into()
80 .unwrap();
81
82 self._canvas = Some(canvas);
83 self.gl = Some(gl);
84
85 if first_render {
86 let handle = {
87 let link = ctx.link().clone();
88 request_animation_frame(move |time| link.send_message(Msg::Render(time)))
89 };
90 self._render_loop = Some(handle);
91 }
92 }
93
94 fn update(&mut self, ctx: &Context<Self>, msg: Self::Message) -> bool {
95 match msg {
96 Msg::Render(timestamp) => {
97 self.render_gl(timestamp, ctx);
98 }
99 Msg::Data(b) => {
100 let v;
101 unsafe {
102 let s = b.len() / 4;
103 let p = b.as_ptr();
104 v = std::slice::from_raw_parts(p as *const f32, s);
105 }
106 for i in v {
107 self.buff.push(*i);
108 }
109 }
110 Msg::Status(s) => {
111 gloo_console::log!(format!("socket status {:?}", &s));
112 }
113 }
114 true
115 }
116
117 fn view(&self, _ctx: &Context<Self>) -> Html {
118 html! {
119 <canvas ref={self.canvas_ref.clone()} />
120 }
121 }
122}
123
124impl Time {
125 fn render_gl(&mut self, timestamp: f64, ctx: &Context<Self>) {
126 let gl = self.gl.as_ref().expect("GL Context not initialized!");
127
128 let l = self.buff.len();
129 let min = ctx.props().min;
130 let max = ctx.props().max;
131 let vertices: Vec<f32> = (self.buff)
132 .enumerate()
133 .flat_map(|(i, v)| {
134 vec![
135 -1.0 + 2.0 * i as f32 / l as f32,
136 (2.0 * (v - min) / (max - min)) - 1.0,
137 ]
138 })
139 .collect();
140
141 let vertex_buffer = gl.create_buffer().unwrap();
142 let verts = js_sys::Float32Array::from(vertices.as_slice());
143
144 gl.bind_buffer(GL::ARRAY_BUFFER, Some(&vertex_buffer));
145 gl.buffer_data_with_array_buffer_view(GL::ARRAY_BUFFER, &verts, GL::STATIC_DRAW);
146
147 let vert_code = r#"
148 attribute vec2 coordinates;
149 void main(void) {
150 gl_Position = vec4(coordinates, 0.0, 1.0);
151 }
152 "#;
153 let vert_shader = gl.create_shader(GL::VERTEX_SHADER).unwrap();
154 gl.shader_source(&vert_shader, vert_code);
155 gl.compile_shader(&vert_shader);
156
157 let frag_code = r#"
158 void main(void) {
159 gl_FragColor = vec4(1.0, 0.0, 0.0, 0.8);
160 }
161 "#;
162 let frag_shader = gl.create_shader(GL::FRAGMENT_SHADER).unwrap();
163 gl.shader_source(&frag_shader, frag_code);
164 gl.compile_shader(&frag_shader);
165
166 let shader_program = gl.create_program().unwrap();
167 gl.attach_shader(&shader_program, &vert_shader);
168 gl.attach_shader(&shader_program, &frag_shader);
169 gl.link_program(&shader_program);
170
171 gl.use_program(Some(&shader_program));
172
173 let position = gl.get_attrib_location(&shader_program, "coordinates") as u32;
175 gl.vertex_attrib_pointer_with_i32(position, 2, GL::FLOAT, false, 0, 0);
176 gl.enable_vertex_attrib_array(position);
177
178 let time = gl.get_uniform_location(&shader_program, "u_time");
180 gl.uniform1f(time.as_ref(), timestamp as f32);
181
182 gl.draw_arrays(GL::LINE_STRIP, 0, l as i32);
183
184 let handle = {
185 let link = ctx.link().clone();
186 request_animation_frame(move |time| link.send_message(Msg::Render(time)))
187 };
188 self._render_loop = Some(handle);
189 }
190}