this repo has no description
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

gpt5-rust pt5 refactor

alice 4419236d 588c96e1

+367 -371
+245
tic80_rust/src/gfx/framebuffer.rs
··· 1 + use std::sync::OnceLock; 2 + 3 + // Default 16-color TIC-80 palette (sRGB) as RGBA8 4 + const PALETTE: [(u8, u8, u8, u8); 16] = [ 5 + (0x00, 0x00, 0x00, 0xFF), 6 + (0x1D, 0x2B, 0x53, 0xFF), 7 + (0x7E, 0x25, 0x53, 0xFF), 8 + (0x00, 0x87, 0x51, 0xFF), 9 + (0xAB, 0x52, 0x36, 0xFF), 10 + (0x5F, 0x57, 0x4F, 0xFF), 11 + (0xC2, 0xC3, 0xC7, 0xFF), 12 + (0xFF, 0xF1, 0xE8, 0xFF), 13 + (0xFF, 0x00, 0x4D, 0xFF), 14 + (0xFF, 0xA3, 0x00, 0xFF), 15 + (0xFF, 0xEC, 0x27, 0xFF), 16 + (0x00, 0xE4, 0x36, 0xFF), 17 + (0x29, 0xAD, 0xFF, 0xFF), 18 + (0x83, 0x76, 0x9C, 0xFF), 19 + (0xFF, 0x77, 0xA8, 0xFF), 20 + (0xFF, 0xCC, 0xAA, 0xFF), 21 + ]; 22 + 23 + const WIDTH: u32 = 240; 24 + const HEIGHT: u32 = 136; 25 + 26 + // TIC-80 default font bytes as 8 rows per glyph, 1 bit per pixel (LSB is x=0) 27 + static FONT_TEXT: &str = include_str!("../../../src/core/font.inl"); 28 + static FONT_BYTES: OnceLock<Vec<u8>> = OnceLock::new(); 29 + 30 + fn font_bytes() -> &'static [u8] { 31 + FONT_BYTES.get_or_init(|| { 32 + // Parse C-style hex/decimal list into bytes 33 + let mut out = Vec::with_capacity(1024); 34 + for tok in FONT_TEXT.split(|c: char| c.is_whitespace() || c == ',') { 35 + if tok.is_empty() { 36 + continue; 37 + } 38 + let t = tok.trim(); 39 + let val = if let Some(hex) = t.strip_prefix("0x").or_else(|| t.strip_prefix("0X")) { 40 + u8::from_str_radix(hex, 16).ok() 41 + } else { 42 + t.parse::<u8>().ok() 43 + }; 44 + if let Some(b) = val { 45 + out.push(b); 46 + } 47 + } 48 + out 49 + }) 50 + } 51 + 52 + pub struct Framebuffer { 53 + // 240x136 palette indices (0..15) 54 + idx: Vec<u8>, 55 + } 56 + 57 + impl Framebuffer { 58 + pub fn new() -> Self { 59 + Self { 60 + idx: vec![0; (WIDTH * HEIGHT) as usize], 61 + } 62 + } 63 + 64 + // cls(color): fill framebuffer with palette index 65 + pub fn cls(&mut self, color: u8) { 66 + self.idx.fill(color & 0x0F); 67 + } 68 + 69 + // pix(x,y[,color]): if Some(color) -> write; else -> read 70 + pub fn pix(&mut self, x: i32, y: i32, color: Option<u8>) -> Option<u8> { 71 + if x < 0 || y < 0 || x as u32 >= WIDTH || y as u32 >= HEIGHT { 72 + return None; 73 + } 74 + let i = (y as u32 * WIDTH + x as u32) as usize; 75 + match color { 76 + Some(c) => { 77 + self.idx[i] = c & 0x0F; 78 + None 79 + } 80 + None => Some(self.idx[i] & 0x0F), 81 + } 82 + } 83 + 84 + // Blit to RGBA buffer for pixels 85 + pub fn blit_to_rgba(&self, rgba: &mut [u8]) { 86 + for (i, idx) in self.idx.iter().copied().enumerate() { 87 + let (r, g, b, a) = PALETTE[(idx & 0x0F) as usize]; 88 + let o = i * 4; 89 + rgba[o] = r; 90 + rgba[o + 1] = g; 91 + rgba[o + 2] = b; 92 + rgba[o + 3] = a; 93 + } 94 + } 95 + 96 + // Draw a line using integer Bresenham 97 + pub fn line(&mut self, x0: i32, y0: i32, x1: i32, y1: i32, color: u8) { 98 + let mut x0 = x0; 99 + let mut y0 = y0; 100 + let dx = (x1 - x0).abs(); 101 + let sx = if x0 < x1 { 1 } else { -1 }; 102 + let dy = -(y1 - y0).abs(); 103 + let sy = if y0 < y1 { 1 } else { -1 }; 104 + let mut err = dx + dy; 105 + let c = color & 0x0F; 106 + loop { 107 + let _ = self.pix(x0, y0, Some(c)); 108 + if x0 == x1 && y0 == y1 { 109 + break; 110 + } 111 + let e2 = 2 * err; 112 + if e2 >= dy { 113 + err += dy; 114 + x0 += sx; 115 + } 116 + if e2 <= dx { 117 + err += dx; 118 + y0 += sy; 119 + } 120 + } 121 + } 122 + 123 + // Filled rectangle 124 + pub fn rect(&mut self, x: i32, y: i32, w: i32, h: i32, color: u8) { 125 + if w <= 0 || h <= 0 { 126 + return; 127 + } 128 + let c = color & 0x0F; 129 + for yy in y..y + h { 130 + for xx in x..x + w { 131 + let _ = self.pix(xx, yy, Some(c)); 132 + } 133 + } 134 + } 135 + 136 + // Print text using TIC-80 default font (5x8 glyphs, 1px spacing) 137 + #[allow(clippy::too_many_arguments)] 138 + pub fn print_text( 139 + &mut self, 140 + text: &str, 141 + x: i32, 142 + mut y: i32, 143 + color: u8, 144 + fixed: bool, 145 + scale: i32, 146 + _small: bool, 147 + ) -> i32 { 148 + // Match TIC-80 print/drawText behavior 149 + const GLYPH_W: usize = 8; 150 + const GLYPH_H: usize = 8; 151 + const ADV: i32 = 6; // TIC_FONT_WIDTH 152 + if scale <= 0 { 153 + return 0; 154 + } 155 + let cidx = color & 0x0F; 156 + let font = font_bytes(); 157 + 158 + let mut pos = x; 159 + let mut max_pos = x; 160 + 161 + for ch in text.chars() { 162 + if ch == '\n' { 163 + if pos > max_pos { 164 + max_pos = pos; 165 + } 166 + pos = x; 167 + y += ADV * scale; // TIC_FONT_HEIGHT (6) 168 + continue; 169 + } 170 + 171 + let code = (ch as u32 & 0x7F) as usize; 172 + let base = code * GLYPH_H; 173 + if base + GLYPH_H > font.len() { 174 + pos += ADV * scale; 175 + continue; 176 + } 177 + 178 + let (start_col, width_cols) = if !fixed { 179 + let mut left = GLYPH_W; 180 + let mut right = 0; 181 + for row in 0..GLYPH_H { 182 + let mask = font[base + row]; 183 + if mask != 0 { 184 + let mut l = 0; 185 + while l < GLYPH_W && ((mask >> l) & 1) == 0 { 186 + l += 1; 187 + } 188 + let mut r = GLYPH_W; 189 + while r > 0 && ((mask >> (r - 1)) & 1) == 0 { 190 + r -= 1; 191 + } 192 + if l < left { 193 + left = l; 194 + } 195 + if r > right { 196 + right = r; 197 + } 198 + } 199 + } 200 + let width = right.saturating_sub(left); 201 + (left, width) 202 + } else { 203 + (0, GLYPH_W) 204 + }; 205 + 206 + // Draw glyph 207 + for row in 0..GLYPH_H { 208 + let mask = font[base + row]; 209 + for col in 0..width_cols { 210 + let bit_idx = start_col + col; 211 + if bit_idx < GLYPH_W && ((mask >> bit_idx) & 1) != 0 { 212 + let px = pos + (col as i32) * scale; 213 + let py = y + (row as i32) * scale; 214 + for sy in 0..scale { 215 + for sx in 0..scale { 216 + let _ = self.pix(px + sx, py + sy, Some(cidx)); 217 + } 218 + } 219 + } 220 + } 221 + } 222 + 223 + // Advance 224 + if !fixed { 225 + if width_cols > 0 { 226 + pos += ((width_cols as i32) + 1) * scale; 227 + } else { 228 + pos += ADV * scale; 229 + } 230 + } else { 231 + pos += ADV * scale; 232 + } 233 + } 234 + 235 + if pos > max_pos { 236 + pos - x 237 + } else { 238 + max_pos - x 239 + } 240 + } 241 + } 242 + 243 + pub fn dimensions() -> (u32, u32) { 244 + (WIDTH, HEIGHT) 245 + }
+10 -371
tic80_rust/src/main.rs
··· 2 2 use std::rc::Rc; 3 3 use std::time::{Duration, Instant}; 4 4 5 - use mlua::{Function, Lua, MultiValue, RegistryKey, Result as LuaResult, Value}; 6 5 use pixels::{Error, Pixels, SurfaceTexture}; 7 - use std::sync::OnceLock; 8 6 use winit::dpi::LogicalSize; 9 7 use winit::event::{ElementState, Event, KeyboardInput, VirtualKeyCode, WindowEvent}; 10 8 use winit::event_loop::{ControlFlow, EventLoop}; 11 9 use winit::window::WindowBuilder; 12 10 13 - const WIDTH: u32 = 240; 14 - const HEIGHT: u32 = 136; 15 - 16 - // Default 16-color TIC-80 palette (sRGB) as RGBA8 17 - const PALETTE: [(u8, u8, u8, u8); 16] = [ 18 - (0x00, 0x00, 0x00, 0xFF), 19 - (0x1D, 0x2B, 0x53, 0xFF), 20 - (0x7E, 0x25, 0x53, 0xFF), 21 - (0x00, 0x87, 0x51, 0xFF), 22 - (0xAB, 0x52, 0x36, 0xFF), 23 - (0x5F, 0x57, 0x4F, 0xFF), 24 - (0xC2, 0xC3, 0xC7, 0xFF), 25 - (0xFF, 0xF1, 0xE8, 0xFF), 26 - (0xFF, 0x00, 0x4D, 0xFF), 27 - (0xFF, 0xA3, 0x00, 0xFF), 28 - (0xFF, 0xEC, 0x27, 0xFF), 29 - (0x00, 0xE4, 0x36, 0xFF), 30 - (0x29, 0xAD, 0xFF, 0xFF), 31 - (0x83, 0x76, 0x9C, 0xFF), 32 - (0xFF, 0x77, 0xA8, 0xFF), 33 - (0xFF, 0xCC, 0xAA, 0xFF), 34 - ]; 35 - 36 - // TIC-80 default 5x8 font as 8 rows per glyph, 5 bits per row (LSB masked with 0x1F), ASCII indexed. 37 - static FONT_TEXT: &str = include_str!("../../src/core/font.inl"); 38 - static FONT_BYTES: OnceLock<Vec<u8>> = OnceLock::new(); 39 - 40 - fn font_bytes() -> &'static [u8] { 41 - FONT_BYTES.get_or_init(|| { 42 - // Parse C-style hex list into bytes 43 - let mut out = Vec::with_capacity(1024); 44 - for tok in FONT_TEXT.split(|c: char| c.is_whitespace() || c == ',') { 45 - if tok.is_empty() { 46 - continue; 47 - } 48 - let t = tok.trim(); 49 - let val = if let Some(hex) = t.strip_prefix("0x").or_else(|| t.strip_prefix("0X")) { 50 - u8::from_str_radix(hex, 16).ok() 51 - } else { 52 - // fallback: decimal 53 - t.parse::<u8>().ok() 54 - }; 55 - if let Some(b) = val { 56 - out.push(b); 57 - } 58 - } 59 - out 60 - }) 11 + mod gfx { 12 + pub mod framebuffer; 61 13 } 62 - 63 - struct Framebuffer { 64 - // 240x136 palette indices (0..15) 65 - idx: Vec<u8>, 14 + mod script { 15 + pub mod lua_runner; 66 16 } 67 - 68 - impl Framebuffer { 69 - fn new() -> Self { 70 - Self { 71 - idx: vec![0; (WIDTH * HEIGHT) as usize], 72 - } 73 - } 74 - 75 - // cls(color): fill framebuffer with palette index 76 - fn cls(&mut self, color: u8) { 77 - self.idx.fill(color & 0x0F); 78 - } 79 - 80 - // pix(x,y[,color]): if Some(color) -> write; else -> read 81 - fn pix(&mut self, x: i32, y: i32, color: Option<u8>) -> Option<u8> { 82 - if x < 0 || y < 0 || x as u32 >= WIDTH || y as u32 >= HEIGHT { 83 - return None; 84 - } 85 - let i = (y as u32 * WIDTH + x as u32) as usize; 86 - match color { 87 - Some(c) => { 88 - self.idx[i] = c & 0x0F; 89 - None 90 - } 91 - None => Some(self.idx[i] & 0x0F), 92 - } 93 - } 94 - 95 - // Blit to RGBA buffer for pixels 96 - fn blit_to_rgba(&self, rgba: &mut [u8]) { 97 - for (i, idx) in self.idx.iter().copied().enumerate() { 98 - let (r, g, b, a) = PALETTE[(idx & 0x0F) as usize]; 99 - let o = i * 4; 100 - rgba[o] = r; 101 - rgba[o + 1] = g; 102 - rgba[o + 2] = b; 103 - rgba[o + 3] = a; 104 - } 105 - } 106 - 107 - // Draw a line using integer Bresenham 108 - fn line(&mut self, x0: i32, y0: i32, x1: i32, y1: i32, color: u8) { 109 - let mut x0 = x0; 110 - let mut y0 = y0; 111 - let dx = (x1 - x0).abs(); 112 - let sx = if x0 < x1 { 1 } else { -1 }; 113 - let dy = -(y1 - y0).abs(); 114 - let sy = if y0 < y1 { 1 } else { -1 }; 115 - let mut err = dx + dy; 116 - let c = color & 0x0F; 117 - loop { 118 - let _ = self.pix(x0, y0, Some(c)); 119 - if x0 == x1 && y0 == y1 { 120 - break; 121 - } 122 - let e2 = 2 * err; 123 - if e2 >= dy { 124 - err += dy; 125 - x0 += sx; 126 - } 127 - if e2 <= dx { 128 - err += dx; 129 - y0 += sy; 130 - } 131 - } 132 - } 133 - 134 - // Filled rectangle 135 - fn rect(&mut self, x: i32, y: i32, w: i32, h: i32, color: u8) { 136 - if w <= 0 || h <= 0 { 137 - return; 138 - } 139 - let c = color & 0x0F; 140 - for yy in y..y + h { 141 - for xx in x..x + w { 142 - let _ = self.pix(xx, yy, Some(c)); 143 - } 144 - } 145 - } 146 - 147 - // Print text using TIC-80 default font (5x8 glyphs, 1px spacing) 148 - #[allow(clippy::too_many_arguments)] 149 - fn print_text( 150 - &mut self, 151 - text: &str, 152 - x: i32, 153 - mut y: i32, 154 - color: u8, 155 - fixed: bool, 156 - scale: i32, 157 - _small: bool, 158 - ) -> i32 { 159 - // Match TIC-80 print/drawText: 160 - // - draw from 8x8 tile 161 - // - fixed: draw full 8 columns, advance by TIC_FONT_WIDTH (6) 162 - // - non-fixed: trim empty columns [start,end) and advance by (width+1) if width>0 else TIC_FONT_WIDTH 163 - const GLYPH_W: usize = 8; 164 - const GLYPH_H: usize = 8; 165 - const ADV: i32 = 6; // TIC_FONT_WIDTH 166 - if scale <= 0 { 167 - return 0; 168 - } 169 - let cidx = color & 0x0F; 170 - let font = font_bytes(); 171 - 172 - let mut pos = x; 173 - let mut max_pos = x; 174 - 175 - for ch in text.chars() { 176 - if ch == '\n' { 177 - if pos > max_pos { 178 - max_pos = pos; 179 - } 180 - pos = x; 181 - y += ADV * scale; // TIC uses TIC_FONT_HEIGHT (6); same as ADV here 182 - continue; 183 - } 184 - 185 - let code = (ch as u32 & 0x7F) as usize; 186 - let base = code * GLYPH_H; 187 - if base + GLYPH_H > font.len() { 188 - pos += ADV * scale; 189 - continue; 190 - } 191 - 192 - let (start_col, width_cols) = if !fixed { 193 - let mut left = GLYPH_W; 194 - let mut right = 0; 195 - for row in 0..GLYPH_H { 196 - let mask = font[base + row]; 197 - if mask != 0 { 198 - let mut l = 0; 199 - while l < GLYPH_W && ((mask >> l) & 1) == 0 { 200 - l += 1; 201 - } 202 - let mut r = GLYPH_W; 203 - while r > 0 && ((mask >> (r - 1)) & 1) == 0 { 204 - r -= 1; 205 - } 206 - if l < left { 207 - left = l; 208 - } 209 - if r > right { 210 - right = r; 211 - } 212 - } 213 - } 214 - let width = right.saturating_sub(left); 215 - (left, width) 216 - } else { 217 - (0, GLYPH_W) 218 - }; 219 - 220 - // Draw glyph 221 - for row in 0..GLYPH_H { 222 - let mask = font[base + row]; 223 - for col in 0..width_cols { 224 - let bit_idx = start_col + col; 225 - if bit_idx < GLYPH_W && ((mask >> bit_idx) & 1) != 0 { 226 - let px = pos + (col as i32) * scale; 227 - let py = y + (row as i32) * scale; 228 - for sy in 0..scale { 229 - for sx in 0..scale { 230 - let _ = self.pix(px + sx, py + sy, Some(cidx)); 231 - } 232 - } 233 - } 234 - } 235 - } 236 - 237 - // Advance 238 - if !fixed { 239 - if width_cols > 0 { 240 - pos += ((width_cols as i32) + 1) * scale; 241 - } else { 242 - pos += ADV * scale; 243 - } 244 - } else { 245 - pos += ADV * scale; 246 - } 247 - } 248 - 249 - if pos > max_pos { 250 - pos - x 251 - } else { 252 - max_pos - x 253 - } 254 - } 255 - } 17 + use gfx::framebuffer::{dimensions, Framebuffer}; 18 + use script::lua_runner::LuaRunner; 256 19 257 20 // Simple fixed-step ticker at ~60 FPS 258 21 struct Ticker { ··· 296 59 end 297 60 "#; 298 61 299 - struct LuaRunner { 300 - lua: Lua, 301 - tic_key: Option<RegistryKey>, 302 - } 303 - 304 - impl LuaRunner { 305 - fn new(fb: Rc<RefCell<Framebuffer>>, script_src: &str) -> LuaResult<Self> { 306 - let lua = Lua::new(); 307 - let tic_key = { 308 - let globals = lua.globals(); 309 - 310 - // Bind cls(color) 311 - let fb_cls = fb.clone(); 312 - let cls_fn = lua.create_function(move |_, color: Option<u8>| { 313 - fb_cls.borrow_mut().cls(color.unwrap_or(0)); 314 - Ok(()) 315 - })?; 316 - globals.set("cls", cls_fn)?; 317 - 318 - // Bind pix(x,y[,color]) -> color or nil 319 - let fb_pix = fb.clone(); 320 - let pix_fn = lua.create_function(move |_, (x, y, color): (i32, i32, Option<u8>)| { 321 - let res = fb_pix.borrow_mut().pix(x, y, color); 322 - Ok(res) 323 - })?; 324 - globals.set("pix", pix_fn)?; 325 - 326 - // line(x0,y0,x1,y1,color) 327 - let fb_line = fb.clone(); 328 - let line_fn = lua.create_function( 329 - move |_, (x0, y0, x1, y1, color): (f32, f32, f32, f32, u8)| { 330 - fb_line 331 - .borrow_mut() 332 - .line(x0 as i32, y0 as i32, x1 as i32, y1 as i32, color); 333 - Ok(()) 334 - }, 335 - )?; 336 - globals.set("line", line_fn)?; 337 - 338 - // rect(x,y,w,h,color) 339 - let fb_rect = fb.clone(); 340 - let rect_fn = 341 - lua.create_function(move |_, (x, y, w, h, color): (i32, i32, i32, i32, u8)| { 342 - fb_rect.borrow_mut().rect(x, y, w, h, color); 343 - Ok(()) 344 - })?; 345 - globals.set("rect", rect_fn)?; 346 - 347 - // print(text, x=0, y=0, color=15, fixed=false, scale=1, small=false) -> width 348 - let fb_print = fb.clone(); 349 - let print_fn = lua.create_function(move |_, args: MultiValue| { 350 - // Defaults 351 - let mut text = String::new(); 352 - let mut x: i32 = 0; 353 - let mut y: i32 = 0; 354 - let mut color: u8 = 15; 355 - let mut fixed = false; 356 - let mut scale: i32 = 1; 357 - let mut small = false; 358 - 359 - // Parse arguments by position 360 - for (i, v) in args.iter().enumerate() { 361 - match (i, v) { 362 - (0, Value::String(s)) => { 363 - text = s.to_str()?.to_string(); 364 - } 365 - (1, Value::Integer(n)) => { 366 - x = *n as i32; 367 - } 368 - (2, Value::Integer(n)) => { 369 - y = *n as i32; 370 - } 371 - (3, Value::Integer(n)) => { 372 - color = (*n).clamp(0, 255) as u8; 373 - } 374 - (4, Value::Boolean(b)) => { 375 - fixed = *b; 376 - } 377 - (5, Value::Integer(n)) => { 378 - scale = (*n as i32).max(1); 379 - } 380 - (6, Value::Boolean(b)) => { 381 - small = *b; 382 - } 383 - _ => {} 384 - } 385 - } 386 - 387 - let width = fb_print 388 - .borrow_mut() 389 - .print_text(&text, x, y, color, fixed, scale, small); 390 - Ok(width) 391 - })?; 392 - globals.set("print", print_fn)?; 393 - 394 - // Load script 395 - lua.load(script_src).set_name("cart").exec()?; 396 - 397 - // Call BOOT() if present 398 - if let Ok(boot) = globals.get::<_, Function>("BOOT") { 399 - let _ = boot.call::<_, ()>(()); 400 - } 401 - 402 - // Cache TIC if present 403 - match globals.get::<_, Option<Function>>("TIC")? { 404 - Some(f) => Some(lua.create_registry_value(f)?), 405 - None => None, 406 - } 407 - }; 408 - 409 - Ok(Self { lua, tic_key }) 410 - } 411 - 412 - fn tick(&self) { 413 - if let Some(key) = &self.tic_key { 414 - if let Ok(func) = self.lua.registry_value::<Function>(key) { 415 - let _ = func.call::<_, ()>(()); 416 - } 417 - } 418 - } 419 - } 420 - 421 62 fn run() -> Result<(), Error> { 422 63 let event_loop = EventLoop::new(); 423 64 const SCALE: f64 = 3.0; // default integer scaling 424 - let size = LogicalSize::new((WIDTH as f64) * SCALE, (HEIGHT as f64) * SCALE); 65 + let (width, height) = dimensions(); 66 + let size = LogicalSize::new((width as f64) * SCALE, (height as f64) * SCALE); 425 67 let window = WindowBuilder::new() 426 68 .with_title("tic80_rust – Milestone 1 (GUI + cls/pix)") 427 69 .with_inner_size(size) ··· 431 73 432 74 let window_size = window.inner_size(); 433 75 let surface_texture = SurfaceTexture::new(window_size.width, window_size.height, &window); 434 - let mut pixels = Pixels::new(WIDTH, HEIGHT, surface_texture)?; 76 + let mut pixels = Pixels::new(width, height, surface_texture)?; 77 + 435 78 let fb = Rc::new(RefCell::new(Framebuffer::new())); 436 79 let mut ticker = Ticker::new(); 437 - 438 - // Lua runner with default script 439 80 let lua_runner = LuaRunner::new(fb.clone(), DEFAULT_LUA).ok(); 440 81 441 82 event_loop.run(move |event, _, control_flow| { ··· 476 117 _ => {} 477 118 } 478 119 }); 479 - 480 - // Unreachable with current event loop 481 120 } 482 121 483 122 fn main() {
+112
tic80_rust/src/script/lua_runner.rs
··· 1 + use std::cell::RefCell; 2 + use std::rc::Rc; 3 + 4 + use mlua::{Function, Lua, MultiValue, RegistryKey, Result as LuaResult, Value}; 5 + 6 + use crate::gfx::framebuffer::Framebuffer; 7 + 8 + pub struct LuaRunner { 9 + lua: Lua, 10 + tic_key: Option<RegistryKey>, 11 + } 12 + 13 + impl LuaRunner { 14 + pub fn new(fb: Rc<RefCell<Framebuffer>>, script_src: &str) -> LuaResult<Self> { 15 + let lua = Lua::new(); 16 + let tic_key = { 17 + let globals = lua.globals(); 18 + 19 + // cls(color) 20 + let fb_cls = fb.clone(); 21 + let cls_fn = lua.create_function(move |_, color: Option<u8>| { 22 + fb_cls.borrow_mut().cls(color.unwrap_or(0)); 23 + Ok(()) 24 + })?; 25 + globals.set("cls", cls_fn)?; 26 + 27 + // pix(x,y[,color]) 28 + let fb_pix = fb.clone(); 29 + let pix_fn = lua.create_function(move |_, (x, y, color): (i32, i32, Option<u8>)| { 30 + let res = fb_pix.borrow_mut().pix(x, y, color); 31 + Ok(res) 32 + })?; 33 + globals.set("pix", pix_fn)?; 34 + 35 + // line(x0,y0,x1,y1,color) 36 + let fb_line = fb.clone(); 37 + let line_fn = lua.create_function( 38 + move |_, (x0, y0, x1, y1, color): (f32, f32, f32, f32, u8)| { 39 + fb_line 40 + .borrow_mut() 41 + .line(x0 as i32, y0 as i32, x1 as i32, y1 as i32, color); 42 + Ok(()) 43 + }, 44 + )?; 45 + globals.set("line", line_fn)?; 46 + 47 + // rect(x,y,w,h,color) 48 + let fb_rect = fb.clone(); 49 + let rect_fn = 50 + lua.create_function(move |_, (x, y, w, h, color): (i32, i32, i32, i32, u8)| { 51 + fb_rect.borrow_mut().rect(x, y, w, h, color); 52 + Ok(()) 53 + })?; 54 + globals.set("rect", rect_fn)?; 55 + 56 + // print(text, x=0, y=0, color=15, fixed=false, scale=1, small=false) -> width 57 + let fb_print = fb.clone(); 58 + let print_fn = lua.create_function(move |_, args: MultiValue| { 59 + let mut text = String::new(); 60 + let mut x: i32 = 0; 61 + let mut y: i32 = 0; 62 + let mut color: u8 = 15; 63 + let mut fixed = false; 64 + let mut scale: i32 = 1; 65 + let mut small = false; 66 + 67 + for (i, v) in args.iter().enumerate() { 68 + match (i, v) { 69 + (0, Value::String(s)) => text = s.to_str()?.to_string(), 70 + (1, Value::Integer(n)) => x = *n as i32, 71 + (2, Value::Integer(n)) => y = *n as i32, 72 + (3, Value::Integer(n)) => color = (*n).clamp(0, 255) as u8, 73 + (4, Value::Boolean(b)) => fixed = *b, 74 + (5, Value::Integer(n)) => scale = (*n as i32).max(1), 75 + (6, Value::Boolean(b)) => small = *b, 76 + _ => {} 77 + } 78 + } 79 + 80 + let width = fb_print 81 + .borrow_mut() 82 + .print_text(&text, x, y, color, fixed, scale, small); 83 + Ok(width) 84 + })?; 85 + globals.set("print", print_fn)?; 86 + 87 + // Load script 88 + lua.load(script_src).set_name("cart").exec()?; 89 + 90 + // Call BOOT() if present 91 + if let Ok(boot) = globals.get::<_, Function>("BOOT") { 92 + let _ = boot.call::<_, ()>(()); 93 + } 94 + 95 + // Cache TIC if present 96 + match globals.get::<_, Option<Function>>("TIC")? { 97 + Some(f) => Some(lua.create_registry_value(f)?), 98 + None => None, 99 + } 100 + }; 101 + 102 + Ok(Self { lua, tic_key }) 103 + } 104 + 105 + pub fn tick(&self) { 106 + if let Some(key) = &self.tic_key { 107 + if let Ok(func) = self.lua.registry_value::<Function>(key) { 108 + let _ = func.call::<_, ()>(()); 109 + } 110 + } 111 + } 112 + }