diff --git a/src/horizontal-layout.zig b/src/horizontal-layout.zig new file mode 100644 index 0000000..de98976 --- /dev/null +++ b/src/horizontal-layout.zig @@ -0,0 +1,38 @@ +const Self = @This(); +const rl = @import("raylib"); + +bounds: rl.Rectangle, +gap: f32, +used_width: f32, + +pub fn init(gap: f32, bounds: rl.Rectangle) Self { + return Self{ + .bounds = bounds, + .gap = gap, + .used_width = 0 + }; +} + +pub fn column_sized(self: *Self, width: f32, height: f32) rl.Rectangle { + const rect = rl.Rectangle{ + .x = self.bounds.x + self.used_width, + .y = self.bounds.y, + .width = width, + .height = height, + }; + self.used_width += width + self.gap; + return rect; +} + +pub fn column(self: *Self, width: f32) rl.Rectangle { + return self.column_sized(width, self.bounds.height); +} + +pub fn column_full(self: *Self) rl.Rectangle { + const left_width = self.bounds.width - self.used_width; + return self.column_sized(left_width, self.bounds.height); +} + +pub fn add_gap(self: *Self, gap: f32) void { + self.used_width += gap; +} diff --git a/src/main-scene.zig b/src/main-scene.zig index cabc286..fa67073 100644 --- a/src/main-scene.zig +++ b/src/main-scene.zig @@ -5,6 +5,7 @@ const std = @import("std"); const roms = @import("./roms.zig"); const GUILayout = @import("./gui-layout.zig"); const VerticalLayout = @import("./vertical-layout.zig"); +const HorizontalLayout = @import("./horizontal-layout.zig"); const ROM = roms.ROM; const ChipContext = @import("chip.zig"); @@ -71,7 +72,8 @@ pub fn init(allocator: Allocator) !Self { .chip_sound = chip_sound, }; - self.set_rom(3); + const breakout = comptime roms.findIndexbyName(&rom_list, "br8kout") orelse unreachable; + self.set_rom(@intCast(breakout)); return self; } @@ -100,6 +102,7 @@ pub fn update(self: *Self, dt: f32) void { rl.ResumeSound(self.chip_sound); self.raylib_chip.update(dt); } else { + self.raylib_chip.update_input(); rl.PauseSound(self.chip_sound); } } @@ -184,7 +187,7 @@ fn GuiListViewMinWidth(allocator: Allocator, height: i32, items: []const []const } pub fn getWindowBodyBounds(bounds: rl.Rectangle) rl.Rectangle { - const status_bar_height = 24; // defined as RAYGUI_WINDOWBOX_STATUSBAR_HEIGHT in raygui.h + const status_bar_height = gui.RAYGUI_WINDOWBOX_STATUSBAR_HEIGHT; const margin = 8; return rl.Rectangle { @@ -203,6 +206,55 @@ pub fn pushGuiWindowBox(layout: *GUILayout, bounds: rl.Rectangle, title: [*:0]co return gui.GuiWindowBox(bounds, title) != 0; } +fn GetColor(hex_value: i32) rl.Color { + const hex_value_unsigned: u32 = @bitCast(hex_value); + return rl.Color{ + .r = @truncate(hex_value_unsigned >> 24), + .g = @truncate(hex_value_unsigned >> 16), + .b = @truncate(hex_value_unsigned >> 8), + .a = @truncate(hex_value_unsigned) + }; +} + +fn drawKeyboard(bounds: rl.Rectangle, keyboard_labels: [16]u8, keyboard_state: [16]bool) void { + const font_size = gui.GuiGetStyle(.DEFAULT , @intFromEnum(gui.GuiDefaultProperty.TEXT_SIZE))*2; + const text_color_normal = GetColor(gui.GuiGetStyle(.LABEL, @intFromEnum(gui.GuiControlProperty.TEXT_COLOR_NORMAL))); + const text_color_pressed = GetColor(gui.GuiGetStyle(.LABEL, @intFromEnum(gui.GuiControlProperty.TEXT_COLOR_PRESSED))); + const border_color_normal = GetColor(gui.GuiGetStyle(.LABEL, @intFromEnum(gui.GuiControlProperty.BORDER_COLOR_NORMAL))); + const border_color_pressed = GetColor(gui.GuiGetStyle(.LABEL, @intFromEnum(gui.GuiControlProperty.BORDER_COLOR_PRESSED))); + + const cell_margin = 2; + const cell_size = font_size+2*cell_margin; + rl.rlPushMatrix(); + rl.rlTranslatef(bounds.x, bounds.y, 0); + + const key_indexes = [16]usize { + 0x1, 0x2, 0x3, 0xC, + 0x4, 0x5, 0x6, 0xD, + 0x7, 0x8, 0x9, 0xE, + 0xA, 0x0, 0xB, 0xF, + }; + for (0.., key_indexes) |i, key_index| { + const cell_y: i32 = @intCast(@divTrunc(i, 4)); + const cell_x: i32 = @intCast(i % 4); + const label = [_:0]u8{ keyboard_labels[key_index], 0 }; + const label_width = rl.MeasureText(&label, font_size); + + var color = text_color_normal; + var border_color = border_color_normal; + if (keyboard_state[key_index]) { + color = text_color_pressed; + color = border_color_pressed; + } + const x = cell_x*(cell_size-1); + const y = cell_y*(cell_size-1); + rl.DrawRectangleLines(x, y, cell_size, cell_size, border_color); + rl.DrawText(&label, x + @divTrunc(cell_size - label_width, 2), y+cell_margin, font_size, color); + } + + rl.rlPopMatrix(); +} + pub fn drawGui(self: *Self, allocator: Allocator) !void { if (rl.IsKeyPressed(.KEY_TAB)) { self.is_gui_open = !self.is_gui_open; @@ -231,38 +283,75 @@ pub fn drawGui(self: *Self, allocator: Allocator) !void { .width = window_width, .height = window_height }; - if (pushGuiWindowBox(&window_layout, window, "CHIP-8 Settings")) { + if (pushGuiWindowBox(&window_layout, window, "Settings")) { self.is_gui_open = false; } - var vertical_layout = VerticalLayout.init(4, getWindowBodyBounds(window)); + const rom_list_names = try allocator.alloc([]const u8, rom_list.len); + defer allocator.free(rom_list_names); - if (gui.GuiButton(vertical_layout.row_sized(100, 24), "Reset") == 1) { - self.reset_rom(); + for (0.., rom_list) |i, rom| { + rom_list_names[i] = rom.name; } - _ = gui.GuiLabel(vertical_layout.row(16), "Current ROM:"); - { // ROM list view - const rom_list_names = try allocator.alloc([]const u8, rom_list.len); - defer allocator.free(rom_list_names); + const rom_list_height = 100; + const rom_list_width = try GuiListViewMinWidth(allocator, rom_list_height, rom_list_names) + 20; - for (0.., rom_list) |i, rom| { - rom_list_names[i] = rom.name; + var columns = HorizontalLayout.init(32, getWindowBodyBounds(window)); + + { // First column + var column1 = VerticalLayout.init(4, columns.column(@floatFromInt(rom_list_width))); + if (gui.GuiButton(column1.row_sized(100, 24), "Reset") == 1) { + self.reset_rom(); } - const list_height = 100; - const list_width = try GuiListViewMinWidth(allocator, list_height, rom_list_names) + 20; - var selected_rom = self.selected_rom_index; - _ = try GuiListView( - allocator, - vertical_layout.row_sized(@floatFromInt(list_width), list_height), - rom_list_names, - &self.rom_list_scroll_index, - &selected_rom - ); + _ = gui.GuiLabel(column1.row(16), "Current ROM:"); + { // ROM list view + var selected_rom = self.selected_rom_index; + _ = try GuiListView( + allocator, + column1.row_sized(@floatFromInt(rom_list_width), rom_list_height), + rom_list_names, + &self.rom_list_scroll_index, + &selected_rom + ); - if (selected_rom != self.selected_rom_index) { - self.set_rom(selected_rom); + if (selected_rom != self.selected_rom_index) { + self.set_rom(selected_rom); + } + } + } + + { // Controls column + var column2 = VerticalLayout.init(0, columns.column(200)); + + _ = gui.GuiLabel(column2.row(16), "Controls:"); + _ = gui.GuiLabel(column2.row(16), "TAB - Toggle settings (this window)"); + column2.add_gap(8); + + var keyboard_columns = HorizontalLayout.init(0, column2.row_full()); + const keyboard_width = 128; + + { + var keyboard_column1 = VerticalLayout.init(0, keyboard_columns.column(keyboard_width)); + _ = gui.GuiLabel(keyboard_column1.row(16), "Input state (QWERTY):"); + var keyboard_labels: [16]u8 = undefined; + for (0.., RaylibChip.default_controls) |i, key| { + keyboard_labels[i] = @intCast(@intFromEnum(key)); + } + drawKeyboard(keyboard_column1.row_full(), keyboard_labels, self.chip.input); + } + + { + var keyboard_column2 = VerticalLayout.init(0, keyboard_columns.column(keyboard_width)); + _ = gui.GuiLabel(keyboard_column2.row(16), "Input state:"); + var reference_labels = [16]u8{ + '0', '1', '2', '3', + '4', '5', '6', '7', + '8', '9', 'A', 'B', + 'C', 'D', 'E', 'F', + }; + drawKeyboard(keyboard_column2.row_full(), reference_labels, self.chip.input); } } diff --git a/src/raylib-chip.zig b/src/raylib-chip.zig index 7ed32e2..703f74a 100644 --- a/src/raylib-chip.zig +++ b/src/raylib-chip.zig @@ -3,6 +3,25 @@ const rl = @import("raylib"); const ChipContext = @import("chip.zig"); const print = @import("std").debug.print; +pub const default_controls = [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, +}; + chip: *ChipContext, on_color: rl.Color, off_color: rl.Color, @@ -27,26 +46,7 @@ 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, - }; - - for (0.., keys) |i, key| { + for (0.., default_controls) |i, key| { self.chip.input[i] = rl.IsKeyDown(key); } } diff --git a/src/roms.zig b/src/roms.zig index 07f046e..2242686 100644 --- a/src/roms.zig +++ b/src/roms.zig @@ -23,3 +23,12 @@ pub fn listROMs() [options.roms.len]ROM { return roms; } + +pub fn findIndexbyName(roms: []const ROM, name: []const u8) ?usize { + for (0.., roms) |i, rom| { + if (std.mem.eql(u8, rom.name, name)) { + return i; + } + } + return null; +} diff --git a/src/vertical-layout.zig b/src/vertical-layout.zig index 573fc99..3a03a55 100644 --- a/src/vertical-layout.zig +++ b/src/vertical-layout.zig @@ -3,27 +3,36 @@ const rl = @import("raylib"); bounds: rl.Rectangle, gap: f32, -used: f32, +used_height: f32, pub fn init(gap: f32, bounds: rl.Rectangle) Self { return Self{ .bounds = bounds, .gap = gap, - .used = 0 + .used_height = 0 }; } pub fn row_sized(self: *Self, width: f32, height: f32) rl.Rectangle { const rect = rl.Rectangle{ .x = self.bounds.x, - .y = self.bounds.y + self.used, + .y = self.bounds.y + self.used_height, .width = width, .height = height, }; - self.used += height + self.gap; + self.used_height += height + self.gap; return rect; } pub fn row(self: *Self, height: f32) rl.Rectangle { return self.row_sized(self.bounds.width, height); } + +pub fn row_full(self: *Self) rl.Rectangle { + const left_height = self.bounds.height - self.used_height; + return self.row_sized(self.bounds.width, left_height); +} + +pub fn add_gap(self: *Self, gap: f32) void { + self.used_height += gap; +}