tally/src/tui/programmer.zig

200 lines
7.6 KiB
Zig

//! Programmer mode drawing for the TUI.
const std = @import("std");
const vaxis = @import("vaxis");
const vxfw = vaxis.vxfw;
const engine = @import("engine");
const draw = @import("draw.zig");
const tui = @import("../tui.zig");
const C = draw.C;
pub fn drawProgrammerMode(app: *tui.App, surface: *vxfw.Surface, width: u16, height: u16) void {
_ = width;
const bw = app.prog_config.bit_width;
const val = app.prog_value & bw.mask();
const focused = app.prog_field;
// Config display
var config_buf: [80]u8 = undefined;
const config_str = std.fmt.bufPrint(&config_buf, "Bits: {d} Signed: {s} Endian: LE", .{
bw.bits(),
if (app.prog_config.signedness == .signed) "yes" else "no",
}) catch "Bits: ??";
draw.writeStr(surface, 2, 2, config_str, .{ .fg = C.muted });
// Bit grid
const grid_start: u16 = 4;
drawBitGrid(app, surface, grid_start, val, bw);
// Multi-base display below bit grid
const grid_rows: u16 = (@as(u16, bw.bits()) + 31) / 32;
const base_start: u16 = grid_start + grid_rows + 1;
const int = engine.types.Integer{ .raw = val, .bit_width = bw, .signedness = .signed };
// DEC(s)
var sdec_buf: [256]u8 = undefined;
const sdec = engine.formatter.formatDecimalSigned(&sdec_buf, int.signedValue());
const sdec_style: vaxis.Style = if (focused == .dec_signed)
.{ .fg = C.bg, .bg = C.cyan, .bold = true }
else
.{ .fg = C.cyan };
draw.writeStr(surface, base_start, 2, "DEC(s):", sdec_style);
draw.writeStr(surface, base_start, 11, sdec.display, if (focused == .dec_signed) .{ .fg = C.fg, .bold = true } else .{ .fg = C.fg });
// DEC(u)
var udec_buf: [256]u8 = undefined;
const udec = engine.formatter.formatDecimalUnsigned(&udec_buf, val);
const udec_style: vaxis.Style = if (focused == .dec_unsigned)
.{ .fg = C.bg, .bg = C.cyan, .bold = true }
else
.{ .fg = C.cyan };
draw.writeStr(surface, base_start + 1, 2, "DEC(u):", udec_style);
draw.writeStr(surface, base_start + 1, 11, udec.display, if (focused == .dec_unsigned) .{ .fg = C.fg, .bold = true } else .{ .fg = C.fg });
// HEX
var hex_buf: [256]u8 = undefined;
const hex = engine.formatter.formatHex(&hex_buf, val, bw);
const hex_style: vaxis.Style = if (focused == .hex)
.{ .fg = C.bg, .bg = C.green, .bold = true }
else
.{ .fg = C.cyan };
draw.writeStr(surface, base_start + 2, 2, "HEX:", hex_style);
if (focused == .hex) {
drawFieldWithCursor(surface, base_start + 2, 11, hex.display, app.bit_cursor, 4, bw.bits(), C.green);
} else {
draw.writeStr(surface, base_start + 2, 11, hex.display, .{ .fg = C.green });
}
// OCT
var oct_buf: [256]u8 = undefined;
const oct = engine.formatter.formatOctal(&oct_buf, val, bw);
const oct_style: vaxis.Style = if (focused == .oct)
.{ .fg = C.bg, .bg = C.purple, .bold = true }
else
.{ .fg = C.cyan };
draw.writeStr(surface, base_start + 3, 2, "OCT:", oct_style);
if (focused == .oct) {
drawFieldWithCursor(surface, base_start + 3, 11, oct.display, app.bit_cursor, 3, bw.bits(), C.purple);
} else {
draw.writeStr(surface, base_start + 3, 11, oct.display, .{ .fg = C.purple });
}
// BIN
var bin_buf: [512]u8 = undefined;
const bin = engine.formatter.formatBinary(&bin_buf, val, bw);
const bin_style: vaxis.Style = if (focused == .bin)
.{ .fg = C.bg, .bg = C.yellow, .bold = true }
else
.{ .fg = C.cyan };
draw.writeStr(surface, base_start + 4, 2, "BIN:", bin_style);
if (focused == .bin) {
drawFieldWithCursor(surface, base_start + 4, 11, bin.display, app.bit_cursor, 1, bw.bits(), C.yellow);
} else {
draw.writeStr(surface, base_start + 4, 11, bin.display, .{ .fg = C.yellow });
}
// History
const hist_start = base_start + 6;
const hist_end = height -| 4;
if (hist_start < hist_end) {
tui.drawHistory(app.history.items, surface, hist_start, hist_end);
}
// Separator + input + status
draw.fillRow(surface, height -| 3, '-', .{ .fg = C.dim });
app.drawInput(surface, height -| 2);
draw.fillRow(surface, height -| 1, ' ', .{ .fg = C.muted, .bg = C.bg });
const status = if (focused == .bits)
"Arrows:nav | Space:toggle | Up/Down:field | Ctrl-W:width | Tab:mode | Ctrl-C:quit"
else
"Up/Down:field | Enter:set value | Ctrl-W:width | Tab:mode | ?:help | Ctrl-C:quit";
draw.writeStr(surface, height -| 1, 1, status, .{ .fg = C.muted, .bg = C.bg });
}
/// Draw a field's display string with a cursor highlighting the digit at bit_cursor position.
/// `bits_per_digit` is 4 for hex, 3 for oct, 1 for bin.
fn drawFieldWithCursor(surface: *vxfw.Surface, row: u16, col: u16, text: []const u8, bit_cursor: u7, bits_per_digit: u8, total_bits: u8, color: vaxis.Cell.Color) void {
_ = total_bits;
// Count actual displayed digits (non-space, non-prefix characters)
var start: usize = 0;
if (text.len >= 2 and text[0] == '0' and (text[1] == 'o' or text[1] == 'x' or text[1] == 'b')) {
start = 2;
}
var displayed_digits: u16 = 0;
for (text[start..]) |ch| {
if (ch != ' ') displayed_digits += 1;
}
// The cursor is at bit_cursor, which represents a digit position from LSB
const cursor_digit_from_lsb: u16 = @as(u16, bit_cursor) / bits_per_digit;
// Convert to position from MSB (0 = leftmost displayed digit)
const cursor_digit_from_msb: u16 = if (cursor_digit_from_lsb < displayed_digits)
displayed_digits - 1 - cursor_digit_from_lsb
else
0;
// Draw prefix
var text_col: u16 = col;
for (text[0..start]) |ch| {
draw.writeChar(surface, row, text_col, ch, .{ .fg = color });
text_col += 1;
}
// Draw digits with cursor highlight
var digit_idx: u16 = 0;
for (text[start..]) |ch| {
if (ch == ' ') {
draw.writeChar(surface, row, text_col, ' ', .{ .fg = color });
} else {
const style: vaxis.Style = if (digit_idx == cursor_digit_from_msb)
.{ .fg = C.bg, .bg = color, .bold = true }
else
.{ .fg = color };
draw.writeChar(surface, row, text_col, ch, style);
digit_idx += 1;
}
text_col += 1;
}
}
fn drawBitGrid(app: *tui.App, surface: *vxfw.Surface, start_row: u16, val: u128, bw: engine.types.BitWidth) void {
const width_bits = bw.bits();
const bits_per_row: u8 = if (width_bits > 32) 32 else width_bits;
const num_rows: u16 = (@as(u16, width_bits) + bits_per_row - 1) / bits_per_row;
const grid_focused = app.prog_field == .bits;
var row = start_row;
var bit_row: u16 = 0;
while (bit_row < num_rows) : (bit_row += 1) {
const row_start_bit: u8 = @intCast(width_bits - 1 - bit_row * bits_per_row);
var col: u16 = 2;
var b: u8 = 0;
while (b < bits_per_row) : (b += 1) {
const bit_idx = row_start_bit -| b;
if (bit_idx >= width_bits) break;
if (b > 0 and b % 4 == 0) col += 1;
const bit_val: u8 = @intCast((val >> @intCast(bit_idx)) & 1);
const is_cursor = grid_focused and (bit_idx == app.bit_cursor);
const style: vaxis.Style = if (is_cursor) .{
.fg = C.bg,
.bg = C.cyan,
.bold = true,
} else if (bit_val == 1) .{
.fg = C.green,
.bold = true,
} else if (grid_focused) .{
.fg = C.muted,
} else .{
.fg = C.dim,
};
draw.writeChar(surface, row, col, '0' + bit_val, style);
col += 1;
}
row += 1;
}
}