mirror of
https://github.com/fairyglade/ly.git
synced 2026-09-22 12:00:19 +00:00
305 lines
9.4 KiB
Zig
305 lines
9.4 KiB
Zig
const std = @import("std");
|
|
const ly_ui = @import("ly-ui");
|
|
const LogFile = ly_ui.ly_core.LogFile;
|
|
const Widget = ly_ui.Widget;
|
|
const TerminalBuffer = ly_ui.TerminalBuffer;
|
|
const Cell = ly_ui.Cell;
|
|
const Allocator = std.mem.Allocator;
|
|
const InfoLine = @import("../components/InfoLine.zig");
|
|
const Lang = @import("../config/Lang.zig");
|
|
|
|
const zlua = @import("zlua");
|
|
|
|
const ly_lua = @embedFile("ly.lua");
|
|
|
|
const Lua = @This();
|
|
|
|
allocator: Allocator,
|
|
instance: ?Widget = null,
|
|
lua: *zlua.Lua,
|
|
log: *LogFile,
|
|
terminal_buffer: *TerminalBuffer,
|
|
width: usize,
|
|
height: usize,
|
|
margin: usize,
|
|
io: std.Io,
|
|
animation_delay: u16,
|
|
|
|
info_line: *InfoLine,
|
|
fg: u32,
|
|
bg: u32,
|
|
|
|
lang: Lang,
|
|
full_color: bool,
|
|
|
|
lua_error: bool = false,
|
|
lua_error_logged: bool = false,
|
|
lua_str: ?[:0]const u8 = null,
|
|
|
|
pub fn init(
|
|
io: std.Io,
|
|
alloc: Allocator,
|
|
log: *LogFile,
|
|
buf: *TerminalBuffer,
|
|
file: []const u8,
|
|
margin: u8,
|
|
animation_delay: u16,
|
|
info_line: *InfoLine,
|
|
fg: u32,
|
|
bg: u32,
|
|
lang: Lang,
|
|
full_color: bool,
|
|
) !Lua {
|
|
var self: Lua = .{
|
|
.lua = try zlua.Lua.init(alloc),
|
|
.allocator = alloc,
|
|
.terminal_buffer = buf,
|
|
.instance = null,
|
|
.log = log,
|
|
.width = 0,
|
|
.height = 0,
|
|
.margin = margin,
|
|
.io = io,
|
|
.animation_delay = animation_delay,
|
|
.info_line = info_line,
|
|
.fg = fg,
|
|
.bg = bg,
|
|
.lang = lang,
|
|
.full_color = full_color,
|
|
};
|
|
|
|
// exclude IO and debug libraries
|
|
self.lua.openBase();
|
|
self.lua.openBit();
|
|
self.lua.openMath();
|
|
self.lua.openString();
|
|
self.lua.openTable();
|
|
|
|
file_loading: {
|
|
const zf = std.mem.concatWithSentinel(alloc, u8, &[1][]const u8{file}, 0) catch |e| {
|
|
try self.log.err(self.io, "lua", "failed to allocate file path: {}", .{e});
|
|
self.lua_str = "failed to allocate file path!";
|
|
self.info_line.addMessage(lang.err_alloc, self.bg, self.fg) catch {};
|
|
return e;
|
|
};
|
|
defer alloc.free(zf);
|
|
|
|
// create the ly table
|
|
self.lua.newTable();
|
|
self.lua.setGlobal("ly");
|
|
|
|
// create ly.width and ly.height from TerminalBuffer width/height
|
|
self.propagateTerminalBounds();
|
|
|
|
_ = self.lua.getGlobal("ly");
|
|
_ = self.lua.pushString("clock");
|
|
self.lua.pushFunction(luaLyClock);
|
|
self.lua.setTable(-3);
|
|
_ = self.lua.pushString("putCell");
|
|
self.lua.pushFunction(luaPutCell);
|
|
self.lua.setTable(-3);
|
|
_ = self.lua.pushString("putLabel");
|
|
self.lua.pushFunction(luaPutLabel);
|
|
self.lua.setTable(-3);
|
|
_ = self.lua.pushString("putRect");
|
|
self.lua.pushFunction(luaPutRect);
|
|
self.lua.setTable(-3);
|
|
self.lua.setGlobal("ly");
|
|
|
|
self.lua.doFile(zf) catch {
|
|
const errorStr = self.lua.toString(-1) catch unreachable;
|
|
self.lua_str = try self.allocator.dupeSentinel(u8, errorStr, 0);
|
|
try self.log.err(self.io, "lua", "lua error: {s}", .{errorStr});
|
|
self.lua_error = true;
|
|
break :file_loading;
|
|
};
|
|
}
|
|
|
|
return self;
|
|
}
|
|
|
|
fn draw(self: *Lua) void {
|
|
self.propagateTerminalBounds();
|
|
if (self.lua_error) {
|
|
// Ly's Red Screen of Omega-Death:tm:
|
|
const RED: u32 = if (self.full_color) TerminalBuffer.Color.TRUE_RED else TerminalBuffer.Color.ECOL_RED;
|
|
const cell = Cell.init(0x2588, RED, RED);
|
|
for (0..self.terminal_buffer.height) |y|
|
|
for (0..self.terminal_buffer.width) |x|
|
|
cell.put(x, y) catch {};
|
|
if (self.lua_str) |str|
|
|
for (str, 0..) |c, i| {
|
|
const dwidth = @divFloor(self.width, 2);
|
|
const dlen = @divFloor(str.len, 2);
|
|
Cell.init(c, 0x00FFFFFF, 0).put(
|
|
(if (dlen > dwidth) 0 else dwidth - dlen) + i,
|
|
self.margin + 5,
|
|
) catch {};
|
|
};
|
|
if (!self.lua_error_logged) {
|
|
self.info_line.addMessage("lua animation failed", self.bg, self.fg) catch {};
|
|
self.lua_error_logged = true;
|
|
}
|
|
return;
|
|
}
|
|
|
|
_ = self.lua.getGlobal("draw");
|
|
self.lua.protectedCall(.{}) catch {
|
|
const errorStr = self.lua.toString(-1) catch unreachable;
|
|
self.lua_str = std.mem.concatWithSentinel(
|
|
self.allocator,
|
|
u8,
|
|
&.{ "cannot call draw(): ", errorStr },
|
|
0,
|
|
) catch unreachable;
|
|
self.log.err(self.io, "lua", "error (cannot call draw()): {s}", .{errorStr}) catch unreachable;
|
|
self.lua_error = true;
|
|
};
|
|
}
|
|
|
|
fn calculateTimeout(self: *Lua, _: *anyopaque) !?usize {
|
|
return self.animation_delay;
|
|
}
|
|
|
|
fn deinit(self: *Lua) void {
|
|
if (self.lua_str) |str| self.allocator.free(str);
|
|
self.lua.deinit();
|
|
}
|
|
|
|
pub fn widget(self: *Lua) *Widget {
|
|
if (self.instance) |*inst| return inst;
|
|
self.instance = Widget.init(
|
|
"Lua",
|
|
null,
|
|
self,
|
|
deinit,
|
|
null,
|
|
draw,
|
|
null,
|
|
null,
|
|
calculateTimeout,
|
|
);
|
|
return &self.instance.?;
|
|
}
|
|
|
|
fn propagateTerminalBounds(self: *Lua) void {
|
|
if (self.terminal_buffer.height == self.height and
|
|
self.terminal_buffer.width == self.width)
|
|
return;
|
|
self.width = self.terminal_buffer.width;
|
|
self.height = self.terminal_buffer.height;
|
|
_ = self.lua.getGlobal("ly");
|
|
_ = self.lua.pushString("width");
|
|
self.lua.pushInteger(@intCast(self.terminal_buffer.width));
|
|
self.lua.setTable(-3);
|
|
_ = self.lua.pushString("height");
|
|
self.lua.pushInteger(@intCast(self.terminal_buffer.height));
|
|
self.lua.setTable(-3);
|
|
self.lua.setGlobal("ly");
|
|
}
|
|
|
|
fn luaLyClock(state: ?*zlua.LuaState) callconv(.c) c_int {
|
|
var threaded = std.Io.Threaded.init_single_threaded;
|
|
const lua: *zlua.Lua = @ptrCast(@alignCast(state orelse unreachable));
|
|
lua.pushInteger(std.Io.Timestamp.now(threaded.io(), .real).toMicroseconds());
|
|
return 1;
|
|
}
|
|
|
|
fn luaPutCell(state: ?*zlua.LuaState) callconv(.c) c_int {
|
|
const lua: *zlua.Lua = @ptrCast(@alignCast(state orelse unreachable));
|
|
const MSG = "ly.putCell: cannot convert %s-typed ";
|
|
const byte = lua.toNumeric(u32, 1) catch {
|
|
const t = lua.typeName(lua.typeOf(1));
|
|
lua.raiseErrorStr(MSG ++ "byte to u32", .{t.ptr});
|
|
};
|
|
const fg = lua.toNumeric(u32, 2) catch {
|
|
const t = lua.typeName(lua.typeOf(2));
|
|
lua.raiseErrorStr(MSG ++ "fg to u32", .{t.ptr});
|
|
};
|
|
const bg = lua.toNumeric(u32, 3) catch {
|
|
const t = lua.typeName(lua.typeOf(3));
|
|
lua.raiseErrorStr(MSG ++ "bg to u32", .{t.ptr});
|
|
};
|
|
const x = lua.toNumeric(usize, 4) catch {
|
|
const t = lua.typeName(lua.typeOf(4));
|
|
lua.raiseErrorStr(MSG ++ "x to usize", .{t.ptr});
|
|
};
|
|
const y = lua.toNumeric(usize, 5) catch {
|
|
const t = lua.typeName(lua.typeOf(5));
|
|
lua.raiseErrorStr(MSG ++ "y to usize", .{t.ptr});
|
|
};
|
|
TerminalBuffer.setCell(x, y, .{
|
|
.fg = fg,
|
|
.bg = bg,
|
|
.ch = byte,
|
|
}) catch {};
|
|
return 0;
|
|
}
|
|
|
|
fn luaPutRect(state: ?*zlua.LuaState) callconv(.c) c_int {
|
|
const lua: *zlua.Lua = @ptrCast(@alignCast(state orelse unreachable));
|
|
const MSG = "ly.putRect: cannot convert %s-typed ";
|
|
const byte = lua.toNumeric(u32, 1) catch {
|
|
const t = lua.typeName(lua.typeOf(1));
|
|
lua.raiseErrorStr(MSG ++ "byte to u32", .{t.ptr});
|
|
};
|
|
const fg = lua.toNumeric(u32, 2) catch {
|
|
const t = lua.typeName(lua.typeOf(2));
|
|
lua.raiseErrorStr(MSG ++ "fg to u32", .{t.ptr});
|
|
};
|
|
const bg = lua.toNumeric(u32, 3) catch {
|
|
const t = lua.typeName(lua.typeOf(3));
|
|
lua.raiseErrorStr(MSG ++ "bg to u32", .{t.ptr});
|
|
};
|
|
const x = lua.toNumeric(usize, 4) catch {
|
|
const t = lua.typeName(lua.typeOf(4));
|
|
lua.raiseErrorStr(MSG ++ "x to usize", .{t.ptr});
|
|
};
|
|
const y = lua.toNumeric(usize, 5) catch {
|
|
const t = lua.typeName(lua.typeOf(5));
|
|
lua.raiseErrorStr(MSG ++ "y to usize", .{t.ptr});
|
|
};
|
|
const w = lua.toNumeric(usize, 6) catch {
|
|
const t = lua.typeName(lua.typeOf(5));
|
|
lua.raiseErrorStr(MSG ++ "w to usize", .{t.ptr});
|
|
};
|
|
const h = lua.toNumeric(usize, 7) catch {
|
|
const t = lua.typeName(lua.typeOf(5));
|
|
lua.raiseErrorStr(MSG ++ "h to usize", .{t.ptr});
|
|
};
|
|
for (0..w) |wx| for (0..h) |hy|
|
|
TerminalBuffer.setCell(x + wx, y + hy, .{
|
|
.fg = fg,
|
|
.bg = bg,
|
|
.ch = byte,
|
|
}) catch {};
|
|
return 0;
|
|
}
|
|
|
|
fn luaPutLabel(state: ?*zlua.LuaState) callconv(.c) c_int {
|
|
const lua: *zlua.Lua = @ptrCast(@alignCast(state orelse unreachable));
|
|
const MSG = "ly.putLabel: cannot convert %s-typed ";
|
|
const str = lua.toString(1) catch {
|
|
const t = lua.typeName(lua.typeOf(2));
|
|
lua.raiseErrorStr(MSG ++ "str to string", .{t.ptr});
|
|
};
|
|
const fg = lua.toNumeric(u32, 2) catch {
|
|
const t = lua.typeName(lua.typeOf(2));
|
|
lua.raiseErrorStr(MSG ++ "fg to u32", .{t.ptr});
|
|
};
|
|
const bg = lua.toNumeric(u32, 3) catch {
|
|
const t = lua.typeName(lua.typeOf(3));
|
|
lua.raiseErrorStr(MSG ++ "bg to u32", .{t.ptr});
|
|
};
|
|
const x = lua.toNumeric(usize, 4) catch {
|
|
const t = lua.typeName(lua.typeOf(4));
|
|
lua.raiseErrorStr(MSG ++ "x to usize", .{t.ptr});
|
|
};
|
|
const y = lua.toNumeric(usize, 5) catch {
|
|
const t = lua.typeName(lua.typeOf(5));
|
|
lua.raiseErrorStr(MSG ++ "y to usize", .{t.ptr});
|
|
};
|
|
TerminalBuffer.drawText(str, x, y, fg, bg) catch {};
|
|
return 0;
|
|
}
|