#[cfg(target_arch = "wasm32")]
use wasm_bindgen::prelude::*;
#[cfg(target_arch = "wasm32")]
use web_sys::{CanvasRenderingContext2d, HtmlCanvasElement};
use super::api;
use super::canvas::{CellBuffer, Color, Rect};
use super::widgets::Brick;
use super::{GraphMode, WidgetKind, WidgetSpec};
#[cfg(target_arch = "wasm32")]
#[wasm_bindgen]
pub struct WasmWidget {
inner: Box<dyn Brick>,
buffer: CellBuffer,
}
#[cfg(target_arch = "wasm32")]
#[wasm_bindgen]
impl WasmWidget {
#[wasm_bindgen(constructor)]
pub fn new_graph(data: Vec<f64>, title: Option<String>, color: Option<String>) -> Self {
let graph = api::plot(data, title, color, None);
Self {
inner: Box::new(graph),
buffer: CellBuffer::new(80, 24),
}
}
#[wasm_bindgen]
pub fn new_meter(value: f64, max: f64, label: Option<String>) -> Self {
let meter = api::meter(value, max, label, None);
Self {
inner: Box::new(meter),
buffer: CellBuffer::new(80, 1),
}
}
#[wasm_bindgen]
pub fn new_gauge(value: f64, label: Option<String>) -> Self {
let gauge = api::gauge(value, label);
Self {
inner: Box::new(gauge),
buffer: CellBuffer::new(40, 1),
}
}
#[wasm_bindgen]
pub fn render_to_canvas(&mut self, ctx: &CanvasRenderingContext2d, width: u32, height: u32) {
use super::canvas::Canvas;
let char_width = 8.0;
let char_height = 16.0;
let cols = (width as f32 / char_width) as usize;
let rows = (height as f32 / char_height) as usize;
if self.buffer.width() != cols || self.buffer.height() != rows {
self.buffer.resize(cols, rows);
}
self.buffer.clear();
self.inner.layout(self.buffer.bounds());
self.inner.paint(&mut self.buffer);
ctx.set_font("16px monospace");
for y in 0..self.buffer.height() {
for x in 0..self.buffer.width() {
if let Some(cell) = self.buffer.get(x, y) {
let (r, g, b) = cell.fg.to_rgb8();
ctx.set_fill_style_str(&format!("rgb({},{},{})", r, g, b));
let ch_str = cell.ch.to_string();
let _ = ctx.fill_text(
&ch_str,
x as f64 * char_width as f64,
(y + 1) as f64 * char_height as f64,
);
}
}
}
}
#[wasm_bindgen]
pub fn to_string(&mut self) -> String {
use super::canvas::Canvas;
self.buffer.clear();
self.inner.layout(self.buffer.bounds());
self.inner.paint(&mut self.buffer);
let mut output = String::new();
for y in 0..self.buffer.height() {
for x in 0..self.buffer.width() {
if let Some(cell) = self.buffer.get(x, y) {
output.push(cell.ch);
}
}
output.push('\n');
}
output
}
}
#[cfg(target_arch = "wasm32")]
#[wasm_bindgen]
pub fn create_widget_from_json(json: &str) -> Result<WasmWidget, JsValue> {
let spec: WidgetSpec = serde_json::from_str(json)
.map_err(|e| JsValue::from_str(&format!("JSON parse error: {}", e)))?;
let widget = api::spec_to_widget(&spec);
Ok(WasmWidget {
inner: widget,
buffer: CellBuffer::new(80, 24),
})
}
#[cfg(target_arch = "wasm32")]
#[wasm_bindgen(start)]
pub fn wasm_init() {
#[cfg(feature = "console_error_panic_hook")]
console_error_panic_hook::set_once();
}
#[cfg(not(target_arch = "wasm32"))]
pub struct WasmWidget {
inner: Box<dyn Brick>,
buffer: CellBuffer,
}
#[cfg(not(target_arch = "wasm32"))]
impl WasmWidget {
pub fn new_graph(data: Vec<f64>, title: Option<String>, color: Option<String>) -> Self {
let graph = api::plot(data, title, color, None);
Self {
inner: Box::new(graph),
buffer: CellBuffer::new(80, 24),
}
}
pub fn new_meter(value: f64, max: f64, label: Option<String>) -> Self {
let meter = api::meter(value, max, label, None);
Self {
inner: Box::new(meter),
buffer: CellBuffer::new(80, 1),
}
}
pub fn to_string(&mut self) -> String {
use super::canvas::Canvas;
self.buffer.clear();
self.inner.layout(self.buffer.bounds());
self.inner.paint(&mut self.buffer);
let mut output = String::new();
for y in 0..self.buffer.height() {
for x in 0..self.buffer.width() {
if let Some(cell) = self.buffer.get(x, y) {
output.push(cell.ch);
}
}
output.push('\n');
}
output
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_wasm_widget_creation_wa01() {
let widget = WasmWidget::new_graph(vec![1.0, 2.0, 3.0], None, None);
assert_eq!(widget.buffer.width(), 80);
}
#[test]
fn test_wasm_meter_wa02() {
let widget = WasmWidget::new_meter(50.0, 100.0, Some("CPU".to_string()));
assert_eq!(widget.buffer.height(), 1);
}
#[test]
fn test_wasm_to_string_wa05() {
let mut widget = WasmWidget::new_graph(vec![1.0, 2.0, 3.0], None, None);
let output = widget.to_string();
assert!(!output.is_empty());
}
#[test]
fn test_wasm_graph_with_title_wa03() {
let widget = WasmWidget::new_graph(
vec![1.0, 2.0, 3.0, 4.0, 5.0],
Some("Test Graph".to_string()),
None,
);
assert_eq!(widget.buffer.width(), 80);
assert_eq!(widget.buffer.height(), 24);
}
#[test]
fn test_wasm_graph_with_color_wa04() {
let widget = WasmWidget::new_graph(vec![10.0, 20.0, 30.0], None, Some("blue".to_string()));
assert_eq!(widget.buffer.width(), 80);
}
#[test]
fn test_wasm_graph_with_title_and_color_wa06() {
let widget = WasmWidget::new_graph(
vec![5.0, 10.0, 15.0, 20.0],
Some("My Chart".to_string()),
Some("green".to_string()),
);
assert_eq!(widget.buffer.width(), 80);
assert_eq!(widget.buffer.height(), 24);
}
#[test]
fn test_wasm_meter_no_label_wa07() {
let widget = WasmWidget::new_meter(75.0, 100.0, None);
assert_eq!(widget.buffer.height(), 1);
assert_eq!(widget.buffer.width(), 80);
}
#[test]
fn test_wasm_meter_zero_value_wa08() {
let widget = WasmWidget::new_meter(0.0, 100.0, Some("Empty".to_string()));
assert_eq!(widget.buffer.height(), 1);
}
#[test]
fn test_wasm_meter_max_value_wa09() {
let widget = WasmWidget::new_meter(100.0, 100.0, Some("Full".to_string()));
assert_eq!(widget.buffer.height(), 1);
}
#[test]
fn test_wasm_graph_empty_data_wa10() {
let widget = WasmWidget::new_graph(vec![], None, None);
assert_eq!(widget.buffer.width(), 80);
}
#[test]
fn test_wasm_graph_single_point_wa11() {
let widget = WasmWidget::new_graph(vec![42.0], None, None);
assert_eq!(widget.buffer.width(), 80);
}
#[test]
fn test_wasm_meter_to_string_wa12() {
let mut widget = WasmWidget::new_meter(50.0, 100.0, Some("RAM".to_string()));
let output = widget.to_string();
assert!(!output.is_empty());
assert!(output.contains('\n')); }
#[test]
fn test_wasm_graph_large_data_wa13() {
let data: Vec<f64> = (0..1000).map(|i| (i as f64).sin() * 100.0).collect();
let widget = WasmWidget::new_graph(data, Some("Sine Wave".to_string()), None);
assert_eq!(widget.buffer.width(), 80);
}
#[test]
fn test_wasm_graph_negative_values_wa14() {
let widget = WasmWidget::new_graph(vec![-10.0, -5.0, 0.0, 5.0, 10.0], None, None);
assert_eq!(widget.buffer.width(), 80);
}
#[test]
fn test_wasm_graph_to_string_lines_wa15() {
let mut widget = WasmWidget::new_graph(vec![1.0, 2.0, 3.0], None, None);
let output = widget.to_string();
let line_count = output.lines().count();
assert!(line_count > 0);
}
#[test]
fn test_wasm_meter_large_max_wa16() {
let widget = WasmWidget::new_meter(50.0, 1_000_000.0, Some("Big".to_string()));
assert_eq!(widget.buffer.height(), 1);
}
#[test]
fn test_wasm_graph_buffer_dimensions_wa17() {
let widget = WasmWidget::new_graph(vec![1.0, 2.0], None, None);
assert_eq!(widget.buffer.width(), 80);
assert_eq!(widget.buffer.height(), 24);
}
}