200 lines
7.6 KiB
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;
|
|
}
|
|
}
|