use std::cell::RefCell;
use std::collections::HashMap;
use std::rc::Rc;
use std::sync::{Arc, Mutex, OnceLock};
use std::ffi::c_void;
use std::time::Duration;
use core_mumu::parser::interpreter::{Interpreter, DynamicFn, DynamicFnInfo};
use core_mumu::parser::types::{FunctionValue, Value};
use minifb::{Window, WindowOptions, Scale, ScaleMode};
#[derive(Clone)]
enum Shape {
Pixel {
x: i32,
y: i32,
},
Rectangle {
x1: i32,
y1: i32,
x2: i32,
y2: i32,
},
Triangle {
x1: i32,
y1: i32,
x2: i32,
y2: i32,
x3: i32,
y3: i32,
},
Line {
x1: i32,
y1: i32,
x2: i32,
y2: i32,
},
}
#[derive(Clone)]
pub struct ShapeItem {
handle_id: i32,
shape: Shape,
color: u32,
}
pub struct WindowHandle {
pub window: Option<Window>,
pub width: usize,
pub height: usize,
pub buffer: Vec<u32>,
pub items: Vec<ShapeItem>,
}
thread_local! {
static REGISTRY: RefCell<HashMap<i32, Rc<RefCell<WindowHandle>>>> =
RefCell::new(HashMap::new());
}
static POLLER_INSTALLED: OnceLock<bool> = OnceLock::new();
fn poll_kanvas(interp: &mut Interpreter) -> usize {
std::thread::sleep(Duration::from_millis(15));
let mut remove_list = Vec::new();
REGISTRY.with(|r| {
let map = r.borrow();
for (&win_id, rc_wh) in map.iter() {
let mut wh = rc_wh.borrow_mut();
if let Some(win) = wh.window.as_mut() {
win.update();
if !win.is_open() {
remove_list.push(win_id);
}
}
}
});
if !remove_list.is_empty() {
REGISTRY.with(|r| {
let mut map = r.borrow_mut();
for rid in remove_list {
if interp.is_verbose() {
eprintln!("[kanvas poll_kanvas] removing window {}", rid);
}
map.remove(&rid);
}
});
}
let remaining = REGISTRY.with(|r| r.borrow().len());
if remaining > 0 { 1 } else { 0 }
}
fn install_kanvas_poller_if_needed(interp: &mut Interpreter) {
if POLLER_INSTALLED.get().is_none() {
if interp.is_verbose() {
eprintln!("[kanvas] Installing poller => poll_kanvas");
}
interp.add_poller(Arc::new(Mutex::new(move |intrp: &mut Interpreter| {
poll_kanvas(intrp)
})));
POLLER_INSTALLED.set(true).ok();
}
}
fn parse_color_hex(s: &str) -> Result<u32, String> {
let trimmed = s.trim();
if !trimmed.starts_with('#') {
return Err(format!("Color must start with '#', got '{}'", s));
}
let hexpart = &trimmed[1..];
let val = u32::from_str_radix(hexpart, 16)
.map_err(|e| format!("Invalid color '{}': {}", s, e))?;
match hexpart.len() {
6 => Ok(0xFF000000 | val),
8 => Ok(val),
_ => Err(format!("Hex color must have 6 or 8 digits, got '{}'", s)),
}
}
fn alpha_blend(dest: u32, src: u32) -> u32 {
let sa = ((src >> 24) & 0xFF) as f32;
let sr = ((src >> 16) & 0xFF) as f32;
let sg = ((src >> 8) & 0xFF) as f32;
let sb = (src & 0xFF) as f32;
let da = ((dest >> 24) & 0xFF) as f32;
let dr = ((dest >> 16) & 0xFF) as f32;
let dg = ((dest >> 8) & 0xFF) as f32;
let db = (dest & 0xFF) as f32;
let sf = sa / 255.0; let df = da / 255.0;
let out_a = sf + df * (1.0 - sf);
if out_a < 1e-6 {
return 0;
}
let out_r = (sr * sf + dr * df * (1.0 - sf)) / out_a;
let out_g = (sg * sf + dg * df * (1.0 - sf)) / out_a;
let out_b = (sb * sf + db * df * (1.0 - sf)) / out_a;
let final_a = (out_a * 255.0).round() as u32;
let final_r = out_r.round() as u32;
let final_g = out_g.round() as u32;
let final_b = out_b.round() as u32;
(final_a << 24) | (final_r << 16) | (final_g << 8) | final_b
}
fn point_in_triangle(px: i32, py: i32, x1: i32, y1: i32, x2: i32, y2: i32, x3: i32, y3: i32) -> bool {
let d1 = (px - x2) * (y1 - y2) - (py - y2) * (x1 - x2);
let d2 = (px - x3) * (y2 - y3) - (py - y3) * (x2 - x3);
let d3 = (px - x1) * (y3 - y1) - (py - y1) * (x3 - x1);
let has_neg = (d1 < 0) || (d2 < 0) || (d3 < 0);
let has_pos = (d1 > 0) || (d2 > 0) || (d3 > 0);
!(has_neg && has_pos)
}
fn clear_buffer_to_black(buf: &mut [u32]) {
for px in buf {
*px = 0xFF000000; }
}
fn draw_line_in_buffer(
buf: &mut [u32],
w: usize,
h: usize,
x1: i32, y1: i32,
x2: i32, y2: i32,
color: u32
) {
let dx = (x2 - x1).abs();
let sx = if x1 < x2 { 1 } else { -1 };
let dy = -(y2 - y1).abs();
let sy = if y1 < y2 { 1 } else { -1 };
let mut err = dx + dy;
let (mut x, mut y) = (x1, y1);
loop {
if x >= 0 && x < w as i32 && y >= 0 && y < h as i32 {
let idx = (y as usize) * w + (x as usize);
buf[idx] = alpha_blend(buf[idx], color);
}
if x == x2 && y == y2 {
break;
}
let e2 = 2 * err;
if e2 >= dy {
err += dy;
x += sx;
}
if e2 <= dx {
err += dx;
y += sy;
}
}
}
fn redraw_all(wh: &mut WindowHandle) {
clear_buffer_to_black(&mut wh.buffer);
for item in wh.items.iter() {
match item.shape {
Shape::Pixel { x, y } => {
if x >= 0 && y >= 0 && (x as usize) < wh.width && (y as usize) < wh.height {
let idx = (y as usize) * wh.width + (x as usize);
let dest = wh.buffer[idx];
wh.buffer[idx] = alpha_blend(dest, item.color);
}
}
Shape::Rectangle { x1, y1, x2, y2 } => {
let xs = x1.min(x2).max(0);
let xe = x1.max(x2).max(0);
let ys = y1.min(y2).max(0);
let ye = y1.max(y2).max(0);
for yy in ys..=ye {
if yy < 0 || (yy as usize) >= wh.height { continue; }
for xx in xs..=xe {
if xx < 0 || (xx as usize) >= wh.width { continue; }
let idx = (yy as usize) * wh.width + (xx as usize);
let dest = wh.buffer[idx];
wh.buffer[idx] = alpha_blend(dest, item.color);
}
}
}
Shape::Triangle { x1, y1, x2, y2, x3, y3 } => {
let xx_min = x1.min(x2).min(x3).max(0);
let xx_max = x1.max(x2).max(x3).max(0);
let yy_min = y1.min(y2).min(y3).max(0);
let yy_max = y1.max(y2).max(y3).max(0);
for yy in yy_min..=yy_max {
if yy < 0 || (yy as usize) >= wh.height { continue; }
for xx in xx_min..=xx_max {
if xx < 0 || (xx as usize) >= wh.width { continue; }
if point_in_triangle(xx, yy, x1, y1, x2, y2, x3, y3) {
let idx = (yy as usize) * wh.width + (xx as usize);
let dest = wh.buffer[idx];
wh.buffer[idx] = alpha_blend(dest, item.color);
}
}
}
}
Shape::Line { x1, y1, x2, y2 } => {
draw_line_in_buffer(
&mut wh.buffer,
wh.width,
wh.height,
x1, y1,
x2, y2,
item.color
);
}
}
}
if let Some(win) = wh.window.as_mut() {
let _ = win.update_with_buffer(&wh.buffer, wh.width, wh.height);
}
}
static mut GLOBAL_ITEM_ID: i32 = 1;
fn kanvas_create_bridge(interp: &mut Interpreter, args: Vec<Value>) -> Result<Value, String> {
if args.len() != 1 {
return Err("kanvas:create => expected 1 argument => KeyedArray".to_string());
}
let keyed = match &args[0] {
Value::KeyedArray(map) => map,
_ => return Err("kanvas:create => argument must be a KeyedArray".to_string()),
};
let mut w: i32 = 640;
let mut h: i32 = 480;
let mut title = "Kanvas Window".to_string();
if let Some(Value::Int(x)) = keyed.get("width") {
w = *x;
}
if let Some(Value::Int(y)) = keyed.get("height") {
h = *y;
}
if let Some(Value::SingleString(tt)) = keyed.get("title") {
title = tt.clone();
}
if w <= 0 || h <= 0 {
return Err(format!("Invalid width/height => {}x{}", w, h));
}
if interp.is_verbose() {
eprintln!("[kanvas:create] size={}x{}, title='{}'", w, h, title);
}
let w_usize = w as usize;
let h_usize = h as usize;
let mut window = Window::new(
&title,
w_usize,
h_usize,
WindowOptions {
scale: Scale::X1,
scale_mode: ScaleMode::Stretch,
..WindowOptions::default()
}
).map_err(|e| format!("Error creating Window => {}", e))?;
let buffer = vec![0xFF000000; w_usize * h_usize];
let _ = window.update_with_buffer(&buffer, w_usize, h_usize);
static mut NEXT_WIN_ID: i32 = 1000;
let window_id: i32;
unsafe {
window_id = NEXT_WIN_ID;
NEXT_WIN_ID += 1;
}
let wh = WindowHandle {
window: Some(window),
width: w_usize,
height: h_usize,
buffer,
items: Vec::new(),
};
REGISTRY.with(|r| {
r.borrow_mut().insert(window_id, Rc::new(RefCell::new(wh)));
});
Ok(Value::Int(window_id))
}
fn kanvas_display_bridge(interp: &mut Interpreter, args: Vec<Value>) -> Result<Value, String> {
if args.len() != 1 {
return Err("kanvas:display => expected 1 argument => handle".to_string());
}
let handle_id = match &args[0] {
Value::Int(i) => *i,
_ => return Err("kanvas:display => first arg must be int handle".to_string()),
};
install_kanvas_poller_if_needed(interp);
for _ in 0..3 {
poll_kanvas(interp);
let still_there = REGISTRY.with(|r| r.borrow().contains_key(&handle_id));
if !still_there {
return Ok(Value::Bool(false));
}
std::thread::sleep(std::time::Duration::from_millis(16));
}
Ok(Value::Bool(true))
}
fn kanvas_remove_bridge(_interp: &mut Interpreter, args: Vec<Value>) -> Result<Value, String> {
if args.len() != 2 {
return Err("kanvas:remove => expected 2 arguments => (windowHandle, itemHandle)".to_string());
}
let win_id = match &args[0] {
Value::Int(i) => *i,
_ => return Err("kanvas:remove => first arg must be window handle int".to_string()),
};
let item_id = match &args[1] {
Value::Int(i) => *i,
_ => return Err("kanvas:remove => second arg must be item handle int".to_string()),
};
let mut found = false;
REGISTRY.with(|r| {
if let Some(rc_wh) = r.borrow().get(&win_id) {
let mut wh = rc_wh.borrow_mut();
let old_len = wh.items.len();
wh.items.retain(|it| it.handle_id != item_id);
if wh.items.len() < old_len {
found = true;
}
}
});
Ok(Value::Bool(found))
}
fn kanvas_refresh_bridge(_interp: &mut Interpreter, args: Vec<Value>) -> Result<Value, String> {
if args.len() != 1 {
return Err("kanvas:refresh => expected 1 argument => windowHandle".to_string());
}
let win_id = match &args[0] {
Value::Int(i) => *i,
_ => return Err("kanvas:refresh => first arg must be window handle int".to_string()),
};
REGISTRY.with(|r| {
if let Some(rc_wh) = r.borrow().get(&win_id) {
let mut wh = rc_wh.borrow_mut();
redraw_all(&mut wh);
}
});
Ok(Value::Bool(true))
}
fn kanvas_pixel_bridge(_interp: &mut Interpreter, args: Vec<Value>) -> Result<Value, String> {
if args.len() != 3 {
return Err("kanvas:pixel => (windowHandle, [x,y], colorString)".to_string());
}
let win_id = match &args[0] {
Value::Int(i) => *i,
_ => return Err("kanvas:pixel => first arg must be window handle int".to_string()),
};
let (px, py) = match &args[1] {
Value::IntArray(v) if v.len() == 2 => (v[0], v[1]),
_ => return Err("kanvas:pixel => second arg must be [x,y]".to_string()),
};
let color_str = match &args[2] {
Value::SingleString(s) => s.clone(),
_ => return Err("kanvas:pixel => third arg must be color string".to_string()),
};
let color_val = parse_color_hex(&color_str)?;
let item_id = unsafe {
GLOBAL_ITEM_ID += 1;
GLOBAL_ITEM_ID
};
REGISTRY.with(|r| {
if let Some(rc_wh) = r.borrow().get(&win_id) {
let mut wh = rc_wh.borrow_mut();
let shape = Shape::Pixel { x: px, y: py };
let new_item = ShapeItem {
handle_id: item_id,
shape,
color: color_val,
};
wh.items.push(new_item);
}
});
Ok(Value::Int(item_id))
}
fn kanvas_rectangle_bridge(_interp: &mut Interpreter, args: Vec<Value>) -> Result<Value, String> {
if args.len() != 3 {
return Err("kanvas:rectangle => (windowHandle, [[x1,y1],[x2,y2]], colorString)".to_string());
}
let win_id = match &args[0] {
Value::Int(i) => *i,
_ => return Err("kanvas:rectangle => first arg must be window handle int".to_string()),
};
let coords = match &args[1] {
Value::Int2DArray(rows) if rows.len() == 2 && rows[0].len() == 2 && rows[1].len() == 2 => {
(rows[0][0], rows[0][1], rows[1][0], rows[1][1])
}
_ => {
return Err("kanvas:rectangle => second arg must be [[x1,y1],[x2,y2]]".to_string());
}
};
let color_str = match &args[2] {
Value::SingleString(s) => s.clone(),
_ => return Err("kanvas:rectangle => third arg must be color string".to_string()),
};
let color_val = parse_color_hex(&color_str)?;
let (x1, y1, x2, y2) = coords;
let item_id = unsafe {
GLOBAL_ITEM_ID += 1;
GLOBAL_ITEM_ID
};
REGISTRY.with(|r| {
if let Some(rc_wh) = r.borrow().get(&win_id) {
let mut wh = rc_wh.borrow_mut();
let shape = Shape::Rectangle { x1, y1, x2, y2 };
let new_item = ShapeItem {
handle_id: item_id,
shape,
color: color_val,
};
wh.items.push(new_item);
}
});
Ok(Value::Int(item_id))
}
fn kanvas_triangle_bridge(_interp: &mut Interpreter, args: Vec<Value>) -> Result<Value, String> {
if args.len() != 3 {
return Err("kanvas:triangle => (windowHandle, [[x1,y1],[x2,y2],[x3,y3]], colorString)".to_string());
}
let win_id = match &args[0] {
Value::Int(i) => *i,
_ => return Err("kanvas:triangle => first arg must be window handle int".to_string()),
};
let (x1, y1, x2, y2, x3, y3) = match &args[1] {
Value::Int2DArray(rows)
if rows.len() == 3 &&
rows[0].len() == 2 &&
rows[1].len() == 2 &&
rows[2].len() == 2 => {
(rows[0][0], rows[0][1], rows[1][0], rows[1][1], rows[2][0], rows[2][1])
}
Value::Float2DArray(rows)
if rows.len() == 3 &&
rows[0].len() == 2 &&
rows[1].len() == 2 &&
rows[2].len() == 2 => {
(
rows[0][0].round() as i32, rows[0][1].round() as i32,
rows[1][0].round() as i32, rows[1][1].round() as i32,
rows[2][0].round() as i32, rows[2][1].round() as i32,
)
}
_ => {
return Err("kanvas:triangle => second arg must be [[x1,y1],[x2,y2],[x3,y3]] (ints or floats)".to_string());
}
};
let color_str = match &args[2] {
Value::SingleString(s) => s.clone(),
_ => return Err("kanvas:triangle => third arg must be color string".to_string()),
};
let color_val = parse_color_hex(&color_str)?;
let item_id = unsafe {
GLOBAL_ITEM_ID += 1;
GLOBAL_ITEM_ID
};
REGISTRY.with(|r| {
if let Some(rc_wh) = r.borrow().get(&win_id) {
let mut wh = rc_wh.borrow_mut();
let shape = Shape::Triangle { x1, y1, x2, y2, x3, y3 };
let new_item = ShapeItem {
handle_id: item_id,
shape,
color: color_val,
};
wh.items.push(new_item);
}
});
Ok(Value::Int(item_id))
}
fn kanvas_line_bridge(_interp: &mut Interpreter, args: Vec<Value>) -> Result<Value, String> {
if args.len() != 3 {
return Err("kanvas:line => (windowHandle, [[x1,y1],[x2,y2]], colorString)".to_string());
}
let win_id = match &args[0] {
Value::Int(i) => *i,
_ => return Err("kanvas:line => first arg must be window handle int".to_string()),
};
let coords = match &args[1] {
Value::Int2DArray(rows) if rows.len() == 2 => {
let row0 = &rows[0];
let row1 = &rows[1];
if row0.len() != 2 || row1.len() != 2 {
return Err("kanvas:line => second arg must be [[x1,y1],[x2,y2]]".to_string());
}
(row0[0], row0[1], row1[0], row1[1])
}
_ => {
return Err("kanvas:line => second arg must be [[x1,y1],[x2,y2]]".to_string());
}
};
let color_str = match &args[2] {
Value::SingleString(s) => s.clone(),
_ => return Err("kanvas:line => third arg must be color string".to_string()),
};
let color_val = parse_color_hex(&color_str)?;
let (x1, y1, x2, y2) = coords;
let item_id = unsafe {
GLOBAL_ITEM_ID += 1;
GLOBAL_ITEM_ID
};
REGISTRY.with(|r| {
if let Some(rc_wh) = r.borrow().get(&win_id) {
let mut wh = rc_wh.borrow_mut();
let shape = Shape::Line { x1, y1, x2, y2 };
let new_item = ShapeItem {
handle_id: item_id,
shape,
color: color_val,
};
wh.items.push(new_item);
}
});
Ok(Value::Int(item_id))
}
#[export_name = "Cargo_lock"]
pub unsafe extern "C" fn cargo_lock(interp_ptr: *mut c_void, _extra: *const c_void) -> i32 {
if interp_ptr.is_null() {
return 1;
}
let interp = &mut *(interp_ptr as *mut Interpreter);
if interp.is_verbose() {
eprintln!("[kanvas Cargo.lock] => registering + installing poller if needed");
}
{
let f: DynamicFn = Arc::new(Mutex::new(kanvas_create_bridge));
let info = DynamicFnInfo::new(f, false);
interp.register_dynamic_function_ex("kanvas:create", info);
interp.set_variable(
"kanvas:create",
Value::Function(Box::new(FunctionValue::Named("kanvas:create".to_string())))
);
}
{
let f: DynamicFn = Arc::new(Mutex::new(kanvas_display_bridge));
let info = DynamicFnInfo::new(f, false);
interp.register_dynamic_function_ex("kanvas:display", info);
interp.set_variable(
"kanvas:display",
Value::Function(Box::new(FunctionValue::Named("kanvas:display".to_string())))
);
}
{
let f: DynamicFn = Arc::new(Mutex::new(kanvas_remove_bridge));
let info = DynamicFnInfo::new(f, false);
interp.register_dynamic_function_ex("kanvas:remove", info);
interp.set_variable(
"kanvas:remove",
Value::Function(Box::new(FunctionValue::Named("kanvas:remove".to_string())))
);
}
{
let f: DynamicFn = Arc::new(Mutex::new(kanvas_refresh_bridge));
let info = DynamicFnInfo::new(f, false);
interp.register_dynamic_function_ex("kanvas:refresh", info);
interp.set_variable(
"kanvas:refresh",
Value::Function(Box::new(FunctionValue::Named("kanvas:refresh".to_string())))
);
}
{
let f: DynamicFn = Arc::new(Mutex::new(kanvas_pixel_bridge));
let info = DynamicFnInfo::new(f, false);
interp.register_dynamic_function_ex("kanvas:pixel", info);
interp.set_variable(
"kanvas:pixel",
Value::Function(Box::new(FunctionValue::Named("kanvas:pixel".to_string())))
);
}
{
let f: DynamicFn = Arc::new(Mutex::new(kanvas_rectangle_bridge));
let info = DynamicFnInfo::new(f, false);
interp.register_dynamic_function_ex("kanvas:rectangle", info);
interp.set_variable(
"kanvas:rectangle",
Value::Function(Box::new(FunctionValue::Named("kanvas:rectangle".to_string())))
);
}
{
let f: DynamicFn = Arc::new(Mutex::new(kanvas_triangle_bridge));
let info = DynamicFnInfo::new(f, false);
interp.register_dynamic_function_ex("kanvas:triangle", info);
interp.set_variable(
"kanvas:triangle",
Value::Function(Box::new(FunctionValue::Named("kanvas:triangle".to_string())))
);
}
{
let f: DynamicFn = Arc::new(Mutex::new(kanvas_line_bridge));
let info = DynamicFnInfo::new(f, false);
interp.register_dynamic_function_ex("kanvas:line", info);
interp.set_variable(
"kanvas:line",
Value::Function(Box::new(FunctionValue::Named("kanvas:line".to_string())))
);
}
install_kanvas_poller_if_needed(interp);
0
}