diff --git a/.gitmodules b/.gitmodules index bad17c2..eeb9fe5 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,6 @@ [submodule "libs/raylib-zig"] path = libs/raylib-zig url = git@github.com:Not-Nik/raylib-zig.git +[submodule "libs/raylib"] + path = libs/raylib + url = https://github.com/ryupold/raylib.zig diff --git a/build.zig b/build.zig index 50633e5..8d58041 100644 --- a/build.zig +++ b/build.zig @@ -1,5 +1,6 @@ const std = @import("std"); -const rl = @import("libs/raylib-zig/build.zig"); +// const rl = @import("libs/raylib-zig/build.zig"); +const raylib = @import("libs/raylib/build.zig"); pub fn build(b: *std.Build) !void { const target = b.standardTargetOptions(.{}); @@ -31,9 +32,11 @@ pub fn build(b: *std.Build) !void { const exe = b.addExecutable(.{ .name = "chip8-zig", .root_source_file = .{ .path = "src/main.zig" }, .optimize = optimize, .target = target }); - rl.link(b, exe, target, optimize); - exe.addModule("raylib", rl.getModule(b, "libs/raylib-zig")); - exe.addModule("raylib-math", rl.math.getModule(b, "libs/raylib-zig")); + raylib.addTo(b, exe, target, optimize); + + // rl.link(b, exe, target, optimize); + // exe.addModule("raylib", rl.getModule(b, "libs/raylib-zig")); + // exe.addModule("raylib-math", rl.math.getModule(b, "libs/raylib-zig")); const run_cmd = b.addRunArtifact(exe); const run_step = b.step("run", "Run chip8-zig"); diff --git a/build.zig.zon b/build.zig.zon index fea3cc5..48dd11b 100644 --- a/build.zig.zon +++ b/build.zig.zon @@ -1,5 +1,5 @@ .{ - .name = "zig-raylib", + .name = "chip8-zig", .version = "0.1.0", .dependencies = .{ .raylib = .{ diff --git a/libs/raylib b/libs/raylib new file mode 160000 index 0000000..6424582 --- /dev/null +++ b/libs/raylib @@ -0,0 +1 @@ +Subproject commit 642458242078d4e3299ea31c9a6c415c65a4e907 diff --git a/src/ROMs/br8kout.ch8 b/src/ROMs/br8kout.ch8 new file mode 100644 index 0000000..7e675f0 Binary files /dev/null and b/src/ROMs/br8kout.ch8 differ diff --git a/src/chip.zig b/src/chip.zig index 0b5013b..813eaec 100644 --- a/src/chip.zig +++ b/src/chip.zig @@ -50,13 +50,15 @@ pub fn init(allocator: *const Allocator) !Self { const display_width = 64; const display_height = 32; const memory_size = 4096; + const memory = try allocator.alloc(u8, memory_size); + @memset(memory, 0); var self = Self { .allocator = allocator, .display = try allocator.alloc(bool, display_width * display_height), .display_width = display_width, .display_height = display_height, - .memory = try allocator.alloc(u8, memory_size), + .memory = memory, .stack = [1]u16{0} ** 16, .V = [1]u8{0} ** 16, .I = 0, @@ -388,12 +390,11 @@ pub fn run_instruction(self: *Self, inst: u16) !void { self.V[x] = (self.V[x] >> 1); } else if (inst & 0x000F == 0x7) { // 8xy7 - SUBN Vx, Vy - const Vx = get_inst_x(inst); - const Vy = get_inst_y(inst); - const sub: i16 = self.V[Vy] - self.V[Vx]; - const not_borrow = @intFromBool(sub > 0); - self.V[Vx] = @intCast(sub & 0x00FF); - self.V[0xF] = not_borrow; + const x = get_inst_x(inst); + const y = get_inst_y(inst); + const result = @subWithOverflow(self.V[y], self.V[x]); + self.V[x] = result[0]; + self.V[0xF] = (1 - result[1]); } else if (inst & 0x000F == 0xE) { // 8xyE - Vx SHR 1 const Vx = get_inst_x(inst); diff --git a/src/fonts/generic-mono.otf b/src/fonts/generic-mono.otf new file mode 100644 index 0000000..422eed3 Binary files /dev/null and b/src/fonts/generic-mono.otf differ diff --git a/src/main.zig b/src/main.zig index 879c563..9d09f28 100755 --- a/src/main.zig +++ b/src/main.zig @@ -1,11 +1,17 @@ const rl = @import("raylib"); const std = @import("std"); +const print = std.debug.print; +const Allocator = std.mem.Allocator; const ChipContext = @import("chip.zig"); const RaylibChip = @import("raylib-chip.zig"); const assert = std.debug.assert; +const Tab = enum { + MemoryView +}; + pub fn gen_sin_wave(wave: *rl.Wave, frequency: f32) void { assert(wave.sampleSize == 16); // Only 16 bits are supported @@ -25,6 +31,574 @@ fn megabytes(amount: usize) usize { return amount * 1024 * 1024; } +fn nibble_to_char(nibble: u4) u8 { + if (0 <= nibble and nibble <= 9) { + return '0' + @as(u8, nibble); + } else { + return 'A' + @as(u8, nibble - 10); + } +} + +fn hex_to_strz(str: [:0]u8, number: u32) void { + var i: i32 = @intCast(str.len-1); + var leftover = number; + while (leftover > 0 and i >= 0): (leftover >>= 4) { + const nibble: u4 = @intCast(leftover & 0b1111); + str[@intCast(i)] = nibble_to_char(nibble); + i -= 1; + } + + while (i >= 0): (i -= 1) { + str[@intCast(i)] = '0'; + } +} + +fn is_point_inside(px: f32, py: f32, x: f32, y: f32, width: f32, height: f32) bool { + return (x <= px and px < x+width) and (y <= py and y < y+height); +} + +fn is_point_inside_rect(px: f32, py: f32, rect: rl.Rectangle) bool { + return is_point_inside(px, py, rect.x, rect.y, rect.width, rect.height); +} + +fn clamp(value: f32, min: f32, max: f32) f32 { + return @min(@max(value, min), max); +} + +const UI = struct { + const TransformFrame = struct { + ox: f32 = 0, + oy: f32 = 0, + sx: f32 = 1, + sy: f32 = 1, + }; + + frames: [16]TransformFrame, + top_frame: u32, + + mouse: rl.Vector2, + mouse_delta: rl.Vector2, + + pub fn init() UI { + return UI{ + .frames = [1]TransformFrame{ TransformFrame{} } ** 16, + .top_frame = 0, + .mouse = rl.Vector2.zero(), + .mouse_delta = rl.Vector2.zero(), + }; + } + + pub fn pushTransform(self: *UI) void { + assert(self.top_frame < self.frames.len-1); + + rl.rlPushMatrix(); + self.top_frame += 1; + self.frames[self.top_frame] = self.frames[self.top_frame - 1]; + } + + pub fn pushTransformT(self: *UI, transform: TransformFrame) void { + self.pushTransform(); + self.translate(transform.ox, transform.oy); + self.scale(transform.sx, transform.sy); + } + + pub fn translate(self: *UI, x: f32, y: f32) void { + const top_frame = &self.frames[self.top_frame]; + top_frame.ox += x * top_frame.sx; + top_frame.oy += y * top_frame.sy; + rl.rlTranslatef(x, y, 0); + } + + pub fn scale(self: *UI, x: f32, y: f32) void { + const top_frame = &self.frames[self.top_frame]; + top_frame.sx *= x; + top_frame.sy *= y; + rl.rlScalef(x, y, 0); + } + + pub fn popTransform(self: *UI) void { + rl.rlPopMatrix(); + self.top_frame -= 1; + } + + pub fn update(self: *UI) void { + assert(self.top_frame == 0); // Check if 'pushTransform()' and 'popTransform()' are paired + + self.frames[0] = .{}; + self.mouse = rl.GetMousePosition(); + self.mouse_delta = rl.GetMouseDelta(); + } + + /// Screen space -> UI space + pub fn apply_transform(self: *UI, vec2: rl.Vector2) rl.Vector2 { + const top_frame = &self.frames[self.top_frame]; + return rl.Vector2{ + .x = (vec2.x - top_frame.ox) * top_frame.sx, + .y = (vec2.y - top_frame.oy) * top_frame.sy + }; + } + + pub fn apply_scale(self: *UI, vec2: rl.Vector2) rl.Vector2 { + const top_frame = &self.frames[self.top_frame]; + return rl.Vector2{ + .x = vec2.x * top_frame.sx, + .y = vec2.y * top_frame.sy, + }; + } + + pub fn get_mouse(self: *UI) rl.Vector2 { + return self.apply_transform(self.mouse); + } + + pub fn get_mouse_delta(self: *UI) rl.Vector2 { + return self.apply_scale(self.mouse_delta); + } + + pub fn is_mouse_inside(self: *UI, x: f32, y: f32, width: f32, height: f32) bool { + const mouse = self.get_mouse(); + return (x <= mouse.x and mouse.x < x+width) and (y <= mouse.y and mouse.y < y+height); + } + + pub fn is_mouse_inside_rect(self: *UI, rect: rl.Rectangle) bool { + return self.is_mouse_inside(rect.x, rect.y, rect.width, rect.height); + } + + pub fn is_mouse_down() bool { + return rl.IsMouseButtonDown(rl.MouseButton.MOUSE_BUTTON_LEFT); + } + + pub fn is_mouse_up() bool { + return rl.IsMouseButtonUp(rl.MouseButton.MOUSE_BUTTON_LEFT); + } + + pub fn was_secondary_mouse_pressed() bool { + return rl.IsMouseButtonPressed(rl.MouseButton.MOUSE_BUTTON_RIGHT); + } + + pub fn is_holding_mouse(self: *UI, rect: rl.Rectangle, state: *bool) bool { + if (!state.* and UI.is_mouse_down() and self.is_mouse_inside_rect(rect)) { + state.* = true; + return true; + } + if (UI.is_mouse_up()) { + state.* = false; + return true; + } + return false; + } +}; + +const UIBox = struct { + x: f32, + y: f32, + width: f32, + height: f32, + + vert_margin: f32, + horz_margin: f32, + + pub fn init(x: f32, y: f32, width: f32, height: f32) UIBox { + return UIBox { + .x = x, + .y = y, + .width = width, + .height = height, + .vert_margin = 0, + .horz_margin = 0, + }; + } + + pub fn init_rect(rectangle: rl.Rectangle) UIBox { + return UIBox.init(rectangle.x, rectangle.y, rectangle.width, rectangle.height); + } + + pub fn body_x(self: *UIBox) f32 { + return self.x + self.horz_margin; + } + + pub fn body_y(self: *UIBox) f32 { + return self.y + self.vert_margin; + } + + pub fn body_width(self: *UIBox) f32 { + return self.width - 2*self.horz_margin; + } + + pub fn body_height(self: *UIBox) f32 { + return self.height - 2*self.vert_margin; + } + + pub fn body_rect(self: *UIBox) rl.Rectangle { + return rl.Rectangle{ + .x = self.body_x(), + .y = self.body_y(), + .width = self.body_width(), + .height = self.body_height() + }; + } + + pub fn rect(self: *UIBox) rl.Rectangle { + return rl.Rectangle{ + .x = self.x, + .y = self.y, + .width = self.width, + .height = self.height + }; + } +}; + +const HorizontalLayout = struct { + x: f32, + y: f32, + width: f32, + height: f32, + + used_width: f32, + + pub fn init(x: f32, y: f32, width: f32, height: f32) HorizontalLayout { + return HorizontalLayout{ + .x = x, + .y = y, + .width = width, + .height = height, + .used_width = 0 + }; + } + + pub fn next_x(self: *HorizontalLayout) f32 { + return self.x + self.used_width; + } + + pub fn next_y(self: *HorizontalLayout) f32 { + return self.y; + } + + pub fn push_rect(self: *HorizontalLayout, width: f32) rl.Rectangle { + const rect = rl.Rectangle{ + .x = self.next_x(), + .y = self.next_y(), + .width = width, + .height = self.height + }; + self.push(width); + return rect; + } + + pub fn push(self: *HorizontalLayout, width: f32) void { + self.used_width += width; + } + + pub fn used_size(self: *HorizontalLayout) rl.Vector2 { + return rl.Vector2{ .x = self.used_width, .y = self.height }; + } +}; + +const Range = struct { start: u32 = 0, end: u32 = 0 }; + +const MemoryView = struct { + const Highlight = struct { + range: Range, + color: rl.Color + }; + + font: *const rl.Font, + font_size: f32, + + base_address: u32, + memory: []u8, + scroll: f32 = 0, + + scrolling: bool = false, + selecting: bool = false, + editing: bool = false, + + editing_byte: u32 = 0, + selection_pivot: u32 = 0, + allocator: *const Allocator, + + grey_out_zeros: bool = true, + + text_color: rl.Color = rl.BLACK, + dim_text_color: rl.Color = rl.GRAY, + + row_width: u5 = 16, + + pub fn init(memory: []u8, font: *const rl.Font, font_size: f32, allocator: *const Allocator) MemoryView { + return MemoryView{ + .allocator = allocator, + .base_address = 0, + .memory = memory, + .font = font, + .font_size = font_size, + }; + } + + pub fn get_memory_row_count(self: *const MemoryView) f32 { + return @ceil(@as(f32, @floatFromInt(self.memory.len)) / @as(f32, @floatFromInt(self.row_width))); + } + + pub fn get_visible_row_count(self: *const MemoryView, height: f32) f32 { + return @min(height / self.font_size, self.get_memory_row_count()); + } + + pub fn get_max_scroll(self: *const MemoryView, height: f32) f32 { + return @max(self.get_memory_row_count() - self.get_visible_row_count(height), 0); + } + + pub fn show(self: *MemoryView, ui: *UI, x: f32, y: f32, width: f32, height: f32, selection: *Range) !void { + if (ui.is_mouse_inside(x, y, width, height)) { + self.scroll -= rl.GetMouseWheelMove(); + } + + self.scroll = clamp(self.scroll, 0, self.get_max_scroll(height)); + + const from_row: u32 = @intFromFloat(@floor(self.scroll)); + const to_row: u32 = @intFromFloat(@ceil(self.scroll + self.get_visible_row_count(height))); + + const scroll_offset = @rem(self.scroll, 1) * self.font_size; + var layout = HorizontalLayout.init(x, y - scroll_offset, width, height); + + layout.push(try self.show_address_column(ui, layout.next_x(), layout.next_y(), from_row, to_row)); + layout.push(self.show_hex_column(ui, layout.next_x(), layout.next_y(), selection, from_row, to_row)); + layout.push(self.show_ascii_column(ui, layout.next_x(), layout.next_y(), selection, from_row, to_row)); + layout.push(self.show_scrollbar(ui, layout.next_x(), y, height)); + } + + pub fn show_address_column(self: *MemoryView, ui: *UI, x: f32, y: f32, from_row: u32, to_row: u32) !f32 { + const font = self.font.*; + const font_size = self.font_size; + const margin = font_size/2; + + const memory_size_f32: f32 = @floatFromInt(self.memory.len); + const memory_size_log10: u32 = @intFromFloat(@floor(@log2(memory_size_f32)/2)); + const address_column_max_chars: u32 = memory_size_log10 + 1; + const row_count = (to_row - from_row); + + var label_buf = try self.allocator.allocSentinel(u8, address_column_max_chars, 0); + defer self.allocator.free(label_buf); + + hex_to_strz(label_buf, 0); + const column_width = rl.MeasureTextEx(font, label_buf, font_size, 0).x + 2*margin; + + ui.pushTransformT(.{ .ox = x, .oy = y }); + for (0..row_count) |i| { + const row = from_row + @as(u32, @intCast(i)); + const row_address = self.base_address + row*self.row_width; + hex_to_strz(label_buf, row_address); + + const cell_rect = rl.Rectangle { + .x = 0, + .y = self.font_size * @as(f32, @floatFromInt(i)), + .height = self.font_size, + .width = column_width + }; + if (ui.is_mouse_inside_rect(cell_rect)) { + rl.DrawRectangleRec(cell_rect, rl.RED); + } + + const text_pos = rl.Vector2{ .x = cell_rect.x + margin, .y = cell_rect.y }; + rl.DrawTextEx(font, label_buf, text_pos, font_size, 0, self.text_color); + } + ui.popTransform(); + + return column_width; + } + + fn is_in_range(x: u32, from: u32, to: u32) bool { + return from <= x and x < to; + } + + pub fn show_hex_column(self: *MemoryView, ui: *UI, x: f32, y: f32, selection: *Range, from_row: u32, to_row: u32) f32 { + assert(self.row_width <= 16); + + const font = self.font.*; + const font_size = self.font_size; + const margin = font_size/6; + + var middle_margin: f32 = 0.0; + if (self.row_width == 16) { + middle_margin = font_size/2; + } + + const cell_width = rl.MeasureTextEx(font, "00", font_size, 0).x; + const row_count = (to_row - from_row); + + const highlights = [_]Highlight{ + Highlight{ .range = .{ .start = 16, .end = 17, }, .color = rl.RED }, + Highlight{ .range = selection.*, .color = rl.RED }, + }; + + ui.pushTransformT(.{ .ox = x, .oy = y }); + var cell_bufz = [_:0]u8{0} ** 2; + for (0..row_count) |i| { + const row = from_row + @as(u32, @intCast(i)); + const row_memory_idx: u32 = @intCast(row*self.row_width); + + var cell_rects: [16]rl.Rectangle = undefined; + const to_column = @min(self.memory.len - row*self.row_width, self.row_width); + for (0..to_column) |column| { + var cell_rect = rl.Rectangle { + .x = (cell_width + 2*margin) * @as(f32, @floatFromInt(column)), + .y = 0, + .width = cell_width + 2*margin, + .height = self.font_size + }; + + if (column >= self.row_width/2) { + cell_rect.x += middle_margin; + } + + cell_rects[column] = cell_rect; + } + + for (highlights) |highlight| { + const highlight_start = highlight.range.start; + const highlight_end = highlight.range.end; + if (row_memory_idx >= highlight_end) continue; + if (row_memory_idx+self.row_width <= highlight_start) continue; + + var highlight_from_column: u32 = undefined; + if (row_memory_idx > highlight_start) { + highlight_from_column = 0; + } else { + highlight_from_column = @mod(highlight_start, self.row_width); + } + + var highlight_to_column: u32 = undefined; + if (row_memory_idx+self.row_width <= highlight_end) { + highlight_to_column = self.row_width-1; + } else { + highlight_to_column = @mod(highlight_end-1, self.row_width); + } + + const from_cell = cell_rects[highlight_from_column]; + const to_cell = cell_rects[highlight_to_column]; + rl.DrawRectangleRec(rl.Rectangle{ + .x = from_cell.x, + .y = from_cell.y, + .width = (to_cell.x+to_cell.width) - from_cell.x, + .height = (to_cell.y+to_cell.height) - from_cell.y, + }, highlight.color); + } + + for (0..to_column) |column| { + const memory_idx: u32 = row_memory_idx + @as(u32, @intCast(column)); + if (ui.is_holding_mouse(cell_rects[column], &self.selecting)) { + if (self.selecting) { + self.selection_pivot = memory_idx; + selection.start = memory_idx; + selection.end = memory_idx+1; + } + } + + if (self.selecting) { + if (ui.is_mouse_inside_rect(cell_rects[column])) { + if (memory_idx > self.selection_pivot) { + selection.start = self.selection_pivot; + selection.end = memory_idx+1; + } else { + selection.start = memory_idx; + selection.end = self.selection_pivot+1; + } + } + } + + const text_pos = rl.Vector2{ .x = cell_rects[column].x + margin }; + const value = self.memory[memory_idx]; + hex_to_strz(&cell_bufz, value); + var color = self.text_color; + if (self.grey_out_zeros and value == 0) { + color = self.dim_text_color; + } + rl.DrawTextEx(font, &cell_bufz, text_pos, font_size, 0, color); + } + ui.translate(0, font_size); + } + ui.popTransform(); + + return (cell_width + 2*margin)*@as(f32, @floatFromInt(self.row_width)) + middle_margin; + } + + pub fn show_ascii_column(self: *MemoryView, ui: *UI, x: f32, y: f32, selection: *Range, from_row: u32, to_row: u32) f32 { + const font = self.font.*; + const font_size = self.font_size; + const margin = font_size/2; + + const cell_width = rl.MeasureTextEx(font, ".", font_size, 0).x; + const row_count = (to_row - from_row); + + ui.pushTransformT(.{ .ox = x+margin, .oy = y }); + for (0..row_count) |i| { + const row = from_row + @as(u32, @intCast(i)); + + const to_column = @min(self.memory.len - row*self.row_width, self.row_width); + for (0..to_column) |column| { + const memory_idx: u32 = @intCast(row*self.row_width + column); + const value = self.memory[memory_idx]; + var char: u8 = 'A'; + if (std.ascii.isPrint(value)) { + char = value; + } else { + char = '.'; + } + + const pos_x = cell_width * @as(f32, @floatFromInt(column)); + if (selection.start <= memory_idx and memory_idx < selection.end) { + const cell_rect = rl.Rectangle{ + .x = pos_x, + .y = 0, + .height = self.font_size, + .width = cell_width + }; + rl.DrawRectangleRec(cell_rect, rl.RED); + } + + const char_str = [2]u8 { char, 0 }; + rl.DrawTextEx(font, char_str[0..1 :0], rl.Vector2{ .x = pos_x }, font_size, 0, self.text_color); + } + + ui.translate(0, font_size); + } + ui.popTransform(); + + return cell_width*@as(f32, @floatFromInt(self.row_width)) + 2*margin; + } + + pub fn show_scrollbar(self: *MemoryView, ui: *UI, x: f32, y: f32, height: f32) f32 { + const max_scroll = self.get_max_scroll(height); + if (max_scroll == 0) { + return 0; + } + + const visible_row_count = self.get_visible_row_count(height); + const memory_row_count = self.get_memory_row_count(); + const visible_percent = visible_row_count / memory_row_count; + const scroll_percent = self.scroll / max_scroll; + const scrollbar_height = visible_percent * height; + + const scrollbar = rl.Rectangle { + .x = x, + .y = y + scroll_percent * (height - scrollbar_height), + .width = self.font_size, + .height = scrollbar_height + }; + + + var color = rl.BLACK; + _ = ui.is_holding_mouse(scrollbar, &self.scrolling); + if (self.scrolling) { + const mouse_dy = rl.GetMouseDelta().y; + self.scroll += (mouse_dy / (height - scrollbar_height) * max_scroll); + self.scroll = clamp(self.scroll, 0, max_scroll); + color = rl.DARKGRAY; + } + + rl.DrawRectangleRec(scrollbar, color); + + return scrollbar.width; + } +}; + pub fn main() anyerror!void { var program_memory = try std.heap.page_allocator.alloc(u8, megabytes(2)); var fba = std.heap.FixedBufferAllocator.init(program_memory); @@ -33,7 +607,7 @@ pub fn main() anyerror!void { var chip = try ChipContext.init(&allocator); defer chip.deinit(); - chip.set_memory(0x200, @embedFile("ROMs/morse_demo.ch8")); + chip.set_memory(0x200, @embedFile("ROMs/br8kout.ch8")); { // const file = try std.fs.cwd().openFile("ROMs/morse_demo.ch8", .{ .mode = .read_only }); @@ -42,16 +616,22 @@ pub fn main() anyerror!void { } const pixel_size = 20; - const screen_width: i32 = @as(i32, chip.display_width) * pixel_size; - const screen_height: i32 = @as(i32, chip.display_height) * pixel_size; + const initial_screen_width: i32 = @as(i32, chip.display_width) * pixel_size; + const initial_screen_height: i32 = @as(i32, chip.display_height) * pixel_size; - rl.initWindow(screen_width, screen_height, "CHIP-8"); - defer rl.closeWindow(); + rl.SetConfigFlags(rl.ConfigFlags{ .FLAG_WINDOW_RESIZABLE = true }); + rl.InitWindow(initial_screen_width, initial_screen_height, "CHIP-8"); + defer rl.CloseWindow(); - rl.initAudioDevice(); - defer rl.closeAudioDevice(); + rl.InitAudioDevice(); + defer rl.CloseAudioDevice(); - rl.setTargetFPS(60); + rl.SetTargetFPS(60); + + const font_size = 24; + const font_ttf_default_numchars = 95; // TTF font generation default charset: 95 glyphs (ASCII 32..126) + const font = rl.LoadFontEx("src/fonts/generic-mono.otf", font_size, null, font_ttf_default_numchars); + defer rl.UnloadFont(font); const sample_rate = 44100; var data = try allocator.alloc(i16, sample_rate); @@ -64,24 +644,63 @@ pub fn main() anyerror!void { .data = @ptrCast(data.ptr), }; gen_sin_wave(&chip_wave, 440); - var chip_sound = rl.loadSoundFromWave(chip_wave); - defer rl.unloadSound(chip_sound); - rl.setSoundVolume(chip_sound, 0.2); + var chip_sound = rl.LoadSoundFromWave(chip_wave); + defer rl.UnloadSound(chip_sound); + rl.SetSoundVolume(chip_sound, 0.2); var raylib_chip = RaylibChip.init(&chip, &chip_sound); + // var raylib_chip = RaylibChip.init(&chip, null); raylib_chip.tick_speed = 500; raylib_chip.timer_speed = 60; - while (!rl.windowShouldClose()) { - var dt = rl.getFrameTime(); + var edit_mode = false; + var tab: Tab = .MemoryView; + + // var temp_mem = [1]u8{0xAA} ** (16*80 + 10); + // var memory_view = MemoryView.init(&temp_mem, &font, 32); + var memory_view = MemoryView.init(chip.memory, &font, font_size, &allocator); + var selected_memory = Range{}; + var ui = UI.init(); + + while (!rl.WindowShouldClose()) { + const screen_width = rl.GetScreenWidth(); + const screen_height = rl.GetScreenHeight(); + + var dt = rl.GetFrameTime(); raylib_chip.update(dt); - { - rl.beginDrawing(); - defer rl.endDrawing(); + if (rl.IsKeyPressed(rl.KeyboardKey.KEY_TAB)) { + edit_mode = !edit_mode; + } - rl.clearBackground(rl.Color.white); - raylib_chip.render(0, 0, screen_width, screen_height); + if (edit_mode) { + if (rl.IsKeyPressed(rl.KeyboardKey.KEY_ONE)) { + tab = .MemoryView; + } + } + + rl.BeginDrawing(); + defer rl.EndDrawing(); + + if (!edit_mode) { + rl.ClearBackground(rl.DARKGRAY); + + const scale_x = @divFloor(screen_width, chip.display_width); + const scale_y = @divFloor(screen_height, chip.display_height); + const min_scale = @min(scale_x, scale_y); + + const display_width = chip.display_width * min_scale; + const display_height = chip.display_height * min_scale; + const display_x = @divFloor(screen_width - display_width, 2); + const display_y = @divFloor(screen_height - display_height, 2); + raylib_chip.render(display_x, display_y, display_width, display_height); + } else { + rl.ClearBackground(rl.RAYWHITE); + ui.update(); + + if (tab == .MemoryView) { + try memory_view.show(&ui, 0, 0, @floatFromInt(screen_width), @floatFromInt(screen_height), &selected_memory); + } } } } diff --git a/src/raylib-chip.zig b/src/raylib-chip.zig index 02cc77e..e9288bb 100644 --- a/src/raylib-chip.zig +++ b/src/raylib-chip.zig @@ -8,16 +8,16 @@ on_color: rl.Color, off_color: rl.Color, timer_speed: f32, tick_speed: f32, -beep_sound: *rl.Sound, +beep_sound: ?*rl.Sound, tick_time: f32, timer_time: f32, -pub fn init(chip: *ChipContext, beep_sound: *rl.Sound) Self { +pub fn init(chip: *ChipContext, beep_sound: ?*rl.Sound) Self { return Self{ .chip = chip, - .off_color = rl.Color.black, - .on_color = rl.Color.ray_white, + .off_color = rl.BLACK, + .on_color = rl.RAYWHITE, .timer_speed = 60, .tick_speed = 500, .tick_time = 0, @@ -28,26 +28,26 @@ pub fn init(chip: *ChipContext, beep_sound: *rl.Sound) Self { pub fn update_input(self: *Self) void { const keys = [16]rl.KeyboardKey{ - .key_x, - .key_one, - .key_two, - .key_three, - .key_q, - .key_w, - .key_e, - .key_a, - .key_s, - .key_d, - .key_z, - .key_c, - .key_four, - .key_r, - .key_f, - .key_v, + .KEY_X, + .KEY_ONE, + .KEY_TWO, + .KEY_THREE, + .KEY_Q, + .KEY_W, + .KEY_E, + .KEY_A, + .KEY_S, + .KEY_D, + .KEY_Z, + .KEY_C, + .KEY_FOUR, + .KEY_R, + .KEY_F, + .KEY_V, }; for (0.., keys) |i, key| { - self.chip.input[i] = rl.isKeyDown(key); + self.chip.input[i] = rl.IsKeyDown(key); } } @@ -70,14 +70,15 @@ pub fn update(self: *Self, dt: f32) void { self.timer_time -= 1/self.timer_speed; } - const beep_sound = self.beep_sound.*; - if (self.chip.ST > 0) { - if (!rl.isSoundPlaying(beep_sound)) { - rl.playSound(beep_sound); - } - } else { - if (rl.isSoundPlaying(beep_sound)) { - rl.stopSound(beep_sound); + if (self.beep_sound) |beep_sound| { + if (self.chip.ST > 0) { + if (!rl.IsSoundPlaying(beep_sound.*)) { + rl.PlaySound(beep_sound.*); + } + } else { + if (rl.IsSoundPlaying(beep_sound.*)) { + rl.StopSound(beep_sound.*); + } } } } @@ -86,14 +87,14 @@ pub fn render(self: *Self, x: i32, y: i32, width: i32, height: i32) void { const pixel_width = @divFloor(width, self.chip.display_width); const pixel_height = @divFloor(height, self.chip.display_height); - rl.drawRectangle(0, 0, width, height, self.off_color); + rl.DrawRectangle(x, y, width, height, self.off_color); for (0..self.chip.display_height) |oy| { for (0..self.chip.display_width) |ox| { if (self.chip.display_get(@intCast(ox), @intCast(oy))) { const ix = x + @as(i32, @intCast(ox)) * pixel_width; const iy = y + @as(i32, @intCast(oy)) * pixel_height; - rl.drawRectangle(ix, iy, pixel_width, pixel_height, self.on_color); + rl.DrawRectangle(ix, iy, pixel_width, pixel_height, self.on_color); } } }