From 6605d869160f2ebd1a929f0a317ea8fad411f546 Mon Sep 17 00:00:00 2001 From: Jason Hilder Date: Wed, 17 Jun 2026 07:48:07 +0200 Subject: [PATCH] Added/updated components to be isolated rectangles. --- src/simulator/gui.odin | 132 ++++++++++++++++++----------- src/simulator/gui_bottom_tabs.odin | 34 ++++++++ src/simulator/gui_cpu.odin | 104 +++++++++++++++++++++++ src/simulator/gui_file_loader.odin | 77 +++++++++++++++++ src/simulator/gui_key_pad.odin | 65 ++++++++++++++ src/simulator/gui_tab_memory.odin | 126 +++++++++++++++++++++++++++ 6 files changed, 489 insertions(+), 49 deletions(-) create mode 100644 src/simulator/gui_bottom_tabs.odin create mode 100644 src/simulator/gui_cpu.odin create mode 100644 src/simulator/gui_file_loader.odin create mode 100644 src/simulator/gui_key_pad.odin create mode 100644 src/simulator/gui_tab_memory.odin diff --git a/src/simulator/gui.odin b/src/simulator/gui.odin index 1412f4e..c0761fd 100644 --- a/src/simulator/gui.odin +++ b/src/simulator/gui.odin @@ -3,29 +3,36 @@ package simulator import emu "../machine" import rl "vendor:raylib" -// Initial window size +// @ Window WINDOW_WIDTH :: 1920 WINDOW_HEIGHT :: 1080 -// @TODO: If this grows lets move it into its own file -// ─── Layout constants ─────────────────────────────────────────────────── -SIDEBAR_PERCENT :: 0.20 -DISPLAY_PERCENT :: 0.30 -CONTROL_BAR_H :: f32(50) -STATUS_BAR_H :: f32(30) -PANEL_PADDING :: 10 -PANEL_HEADER :: 24 +// @ Layout proportions +// Sidebar takes 20% of screen width on each side; display takes the rest. +// The center column splits vertically: 40% display, 60% debug panel. +SIDEBAR_PERCENT :: f32(0.20) +DISPLAY_V_RATIO :: f32(0.40) +// @ Fixed heights +CONTROL_BAR_H :: f32(50) +STATUS_BAR_H :: f32(30) +// @ Layout constants +PADDING_X :: f32(10) +PADDING_Y :: f32(8) +PANEL_HEADER :: f32(24) +// @ Buttons BUTTON_HEIGHT :: 30 BUTTON_WIDTH :: 120 +// @ Fonts BIG_FONT_SIZE :: 20 KEYPAD_FONT_SIZE :: 18 Layout :: struct { control_bar : rl.Rectangle, - left_panel : rl.Rectangle, + file_loader : rl.Rectangle, + keypad : rl.Rectangle, display : rl.Rectangle, bottom_panel : rl.Rectangle, - right_panel : rl.Rectangle, + cpu : rl.Rectangle, status_bar : rl.Rectangle, } @@ -46,7 +53,7 @@ run_gui :: proc(sim: ^Simulator) { rl.GuiLoadStyle("./assets/raygui_styles/style_dark.rgs") rl.GuiSetFont(font) - rl.GuiSetStyle(.DEFAULT, cast(i32)rl.GuiDefaultProperty.TEXT_SIZE, 18) + rl.GuiSetStyle(.DEFAULT, i32(rl.GuiDefaultProperty.TEXT_SIZE), 18) // Draw each of the components in its own window within the main window for !rl.WindowShouldClose() { @@ -79,9 +86,26 @@ run_gui :: proc(sim: ^Simulator) { } } + // Top + // ------------------------------------------ gui_control_bar(layout.control_bar, sim) - gui_left_panel(layout.left_panel, sim) + + // Left + // ------------------------------------------ + gui_file_loader(layout.file_loader, sim) + gui_key_pad(layout.keypad, sim.machine.keypad, sim.font) + + // Center + // ------------------------------------------ gui_screen(layout.display, sim) + + // Right + // ------------------------------------------ + gui_cpu(layout.cpu, sim) + + // Bottom + // ------------------------------------------ + gui_bottom_tabs(layout.bottom_panel, sim) gui_status_bar(layout.status_bar, sim) rl.EndDrawing() @@ -95,64 +119,74 @@ run_gui :: proc(sim: ^Simulator) { // @TODO: If this grows lets move it into its own file calc_layout :: proc(screen_width: f32, screen_height: f32) -> Layout { - top_h := CONTROL_BAR_H - bottom_h := STATUS_BAR_H - content_h := screen_height - top_h - bottom_h - content_y := top_h + // Control bar is a fixed height frozen at top of gui, all items start below it. + y_pos := CONTROL_BAR_H + x_pos := f32(0) - sidebar_w := screen_width * SIDEBAR_PERCENT + // Usable gui vertical space + visible_height := screen_height - CONTROL_BAR_H - STATUS_BAR_H + sidebar_width := screen_width * SIDEBAR_PERCENT + display_width := screen_width - (sidebar_width * 2) - sidebar_width + display_height := visible_height * DISPLAY_V_RATIO - screen_h_ratio := 0.40 - bottom_panel_h := content_h * f32(1.0 - screen_h_ratio) - screen_h := content_h * f32(screen_h_ratio) - memory_h := content_h - screen_h - - center_x := sidebar_w - center_w := screen_width - sidebar_w * 2 + x_center := sidebar_width + y_center := screen_width - sidebar_width * 2 return Layout { + // Left Area control_bar = rl.Rectangle{ x = 0, y = 0, width = screen_width, - height = top_h, + height = CONTROL_BAR_H, }, - - left_panel = rl.Rectangle{ + file_loader = rl.Rectangle{ x = 0, - y = top_h, - width = sidebar_w, - height = content_h, + y = y_pos, + width = sidebar_width, + height = visible_height / 3 + }, + keypad = rl.Rectangle{ + x = 0, + y = (y_pos + visible_height - visible_height / 2), + width = sidebar_width, + height = visible_height / 2 }, - // CHIP-8 screen (top center) + // ------------------------------------------ + // Center Area display = rl.Rectangle{ - x = center_x, - y = top_h, - width = center_w, - height = screen_h, + x = sidebar_width, + y = y_pos, + width = display_width, + height = display_height, }, - // MEMORY / DEBUG panel (bottom center) + // ------------------------------------------ + // Bottom Area bottom_panel = rl.Rectangle{ - x = center_x, - y = top_h + screen_h, - width = center_w, - height = memory_h, + x = sidebar_width, + y = y_pos + display_height, + width = screen_width - sidebar_width, + height = visible_height - display_height, }, - right_panel = rl.Rectangle{ - x = screen_width - sidebar_w, - y = top_h, - width = sidebar_w, - height = content_h, + // ------------------------------------------ + // Right Area + cpu = rl.Rectangle { + x = sidebar_width + display_width, + y = y_pos, + width = sidebar_width * 2, + height = display_height }, + // ------------------------------------------ + // Bottom Area status_bar = rl.Rectangle{ - x = 0, - y = screen_height - bottom_h, + x = x_pos, + y = y_pos + visible_height, width = screen_width, - height = bottom_h, + height = STATUS_BAR_H, }, } } diff --git a/src/simulator/gui_bottom_tabs.odin b/src/simulator/gui_bottom_tabs.odin new file mode 100644 index 0000000..5a88b74 --- /dev/null +++ b/src/simulator/gui_bottom_tabs.odin @@ -0,0 +1,34 @@ +package simulator + +import rl "vendor:raylib" + +gui_bottom_tabs :: proc(rect: rl.Rectangle, sim: ^Simulator) { + rl.DrawRectangleLinesEx(rect, 1, rl.GRAY) + + // Setup tab bar + tabs := [?]cstring{ "Memory", "Disassembly", "Log" } + + tab_bar_rect := rl.Rectangle{ + rect.x, + rect.y, + rect.width, + BUTTON_HEIGHT + PADDING_Y + } + + // inside draw loop: + rl.GuiTabBar(tab_bar_rect, &tabs[0], i32(len(tabs)), &sim.active_tab) + + bounds := rl.Rectangle { + x = rect.x + PADDING_X, + y = rect.y + PADDING_Y + tab_bar_rect.height, + width = rect.width - (PADDING_X * 2), + height = rect.height - (PADDING_Y * 2) - tab_bar_rect.height, + } + + switch sim.active_tab { + case 0: // draw registers panel + gui_tab_memory(bounds, sim) + case 1: // draw memory panel + case 2: // draw display panel + } +} diff --git a/src/simulator/gui_cpu.odin b/src/simulator/gui_cpu.odin new file mode 100644 index 0000000..8036bba --- /dev/null +++ b/src/simulator/gui_cpu.odin @@ -0,0 +1,104 @@ +package simulator + +import rl "vendor:raylib" + +CPU_SECTION_H :: 100 // enough for 3 rows of labels +REG_CELL_H :: 40 // fixed, won't grow with the panel + +gui_cpu :: proc(rect: rl.Rectangle, sim: ^Simulator) { + rl.DrawRectangleLinesEx(rect, 1, rl.GRAY) + + bounds := rl.Rectangle { + x = rect.x + PADDING_X, + y = rect.y + PADDING_Y, + width = rect.width - (PADDING_X * 2), + height = rect.height - (PADDING_Y * 2), + } + rl.GuiPanel(bounds, "CPU / Registers") + + cpu_rect := rl.Rectangle { + x = bounds.x + PADDING_X, + y = bounds.y + PADDING_Y + PANEL_HEADER, + width = bounds.width - (PADDING_X * 2), + height = CPU_SECTION_H - (PADDING_Y * 2), + } + + // Grid constants + COLS :: 2 + ROWS :: 3 + LRATIO :: f32(0.6) + + cell_w := cpu_rect.width / COLS + cell_h := cpu_rect.height / ROWS + + labels := [5]string{ "Progam Counter", "Increment pointer", "Stack Pointer", "Delay Timer", "Sound Timer" } + values := [5]u16{ + sim.machine.pc, + sim.machine.i, + u16(sim.machine.sp), + u16(sim.machine.delay_timer), + u16(sim.machine.sound_timer), + } + + for index in 0..<5 { + col := index % COLS + row := index / COLS + + cell_x := cpu_rect.x + f32(col) * cell_w + cell_y := cpu_rect.y + f32(row) * cell_h + + label_rect := rl.Rectangle{cell_x + 20, cell_y, cell_w,cell_h} + box_rect := rl.Rectangle{cell_x + cell_w * LRATIO, cell_y, cell_w * (1 - LRATIO), cell_h} + + rl.GuiLabel(label_rect, rl.TextFormat("%s", labels[index])) + rl.DrawRectangleLinesEx(box_rect, 1, rl.DARKGRAY) + + // Right-aligned value + value_text := rl.TextFormat("0x%04X", values[index]) + text_w := rl.MeasureText(value_text, 18) + value_x := box_rect.x + box_rect.width - f32(text_w) - 4 + rl.GuiLabel({value_x, box_rect.y, box_rect.width, box_rect.height}, value_text) + } + + + register_bounds := rl.Rectangle { + x = bounds.x, + y = bounds.y + PANEL_HEADER + PADDING_Y + CPU_SECTION_H, + width = bounds.width, + height = bounds.height - PANEL_HEADER - PADDING_Y - CPU_SECTION_H, + } + rl.GuiPanel(register_bounds, "Registers") + + register_rect := rl.Rectangle { + x = register_bounds.x + PADDING_X, + y = register_bounds.y + PADDING_Y + PANEL_HEADER, + width = register_bounds.width - (PADDING_X * 2), + height = register_bounds.height - (PADDING_Y * 2) - PANEL_HEADER, + } + + // V registers grid (V0–VF, 16 registers) + REG_COLS :: 4 + REG_ROWS :: 4 + + reg_cell_w := register_rect.width / REG_COLS + + for index in 0..<16 { + col := index % REG_COLS + row := index / REG_COLS + + cell_x := register_rect.x + f32(col) * reg_cell_w + cell_y := register_rect.y + f32(row) * REG_CELL_H + + label_rect := rl.Rectangle{cell_x + 20, cell_y, reg_cell_w, REG_CELL_H} + box_rect := rl.Rectangle{cell_x + reg_cell_w * LRATIO, cell_y, reg_cell_w * (1 - LRATIO), REG_CELL_H} + + rl.GuiLabel(label_rect, rl.TextFormat("V%X", index)) + rl.DrawRectangleLinesEx(box_rect, 1, rl.DARKGRAY) + + // Right-aligned value + value_text := rl.TextFormat("0x%02X", sim.machine.v[index]) + text_w := rl.MeasureText(value_text, 18) + value_x := box_rect.x + box_rect.width - f32(text_w) - 4 + rl.GuiLabel({value_x, box_rect.y, box_rect.width, box_rect.height}, value_text) + } +} diff --git a/src/simulator/gui_file_loader.odin b/src/simulator/gui_file_loader.odin new file mode 100644 index 0000000..9a56f6f --- /dev/null +++ b/src/simulator/gui_file_loader.odin @@ -0,0 +1,77 @@ +package simulator + +import "core:log" +import rl "vendor:raylib" + +import emu "../machine" +import tfd "../../external/tinyfiledialogs" + +gui_file_loader :: proc(rect: rl.Rectangle, sim: ^Simulator) { + rl.DrawRectangleLinesEx(rect, 1, rl.GRAY) + + bounds := rl.Rectangle { + x = rect.x + PADDING_X, + y = rect.y + PADDING_Y, + width = rect.width - (PADDING_X * 2), + height = rect.height - (PADDING_Y * 2), + } + rl.GuiPanel(bounds, "Rom / File") + + // drop-zone occupies the panel's content area, minus space for the button + drop_zone := rl.Rectangle { + bounds.x + PADDING_X, + bounds.y + PADDING_Y, + bounds.width - (PADDING_X * 2), + bounds.height - ((PADDING_Y * 2) + (BUTTON_HEIGHT + PADDING_Y)) + } + + // centered drop-zone text + text: cstring = "Drop a CHIP-8 ROM here" + text_width := rl.MeasureText(text, BIG_FONT_SIZE) + text_x := drop_zone.x + (drop_zone.width - f32(text_width)) / 2 + text_y := drop_zone.y + (drop_zone.height - f32(BIG_FONT_SIZE)) / 2 + rl.DrawTextEx(sim.font, text, {text_x, text_y}, BIG_FONT_SIZE, 1, rl.WHITE) + + // open rom button below drop-zone + btn_rect := rl.Rectangle { + drop_zone.x, + drop_zone.y + drop_zone.height + PADDING_Y, + drop_zone.width, + BUTTON_HEIGHT, + } + + if rl.GuiButton(btn_rect, "Open ROM") { + ret := tfd.openFileDialog("Open File Dialog", nil, 0, nil, nil, 0,) + rom_path := string(ret) + if rom_path == "" do return + + // reset machine state + emu.reset_machine(sim.machine) + + // load new rom + err := emu.load_rom(sim.machine, rom_path) + if err != nil { + // @TODO: update status bar here + panic("failed to load rom!") + } + + sim.rom_loaded = true + } + + // Handle file drop + if rl.IsFileDropped() { + dropped_file := rl.LoadDroppedFiles() + if dropped_file.count > 0 { + mouse := rl.GetMousePosition() + + if rl.CheckCollisionPointRec(mouse, drop_zone) { + path_str := string(dropped_file.paths[0]) + log.info("file dropped: ", path_str) + // @TODO: Stop sim, reset mem etc, load new rom + } else { + log.info("File dropped outside drop zone, ignoring") + } + } + rl.UnloadDroppedFiles(dropped_file) + } +} diff --git a/src/simulator/gui_key_pad.odin b/src/simulator/gui_key_pad.odin new file mode 100644 index 0000000..141ab72 --- /dev/null +++ b/src/simulator/gui_key_pad.odin @@ -0,0 +1,65 @@ +package simulator + +import "core:strings" +import rl "vendor:raylib" + + +gui_key_pad :: proc(rect: rl.Rectangle, display: [16]bool, font: rl.Font) { + rl.DrawRectangleLinesEx(rect, 1, rl.GRAY) + + bounds := rl.Rectangle { + x = rect.x + PADDING_X, + y = rect.y + PADDING_Y, + width = rect.width - (PADDING_X * 2), + height = rect.height - (PADDING_Y * 2), + } + rl.GuiPanel(bounds, "Input / Keypad") + + content := rl.Rectangle { + bounds.x, + bounds.y + PANEL_HEADER, + bounds.width, + bounds.height - PANEL_HEADER, + } + + Key :: struct { + label: string, + index: int, + } + + keys := [16]Key { + {"1", 1}, {"2", 2}, {"3", 3}, {"C", 12}, + {"4", 4}, {"5", 5}, {"6", 6}, {"D", 13}, + {"7", 7}, {"8", 8}, {"9", 9}, {"E", 14}, + {"A", 10}, {"0", 0}, {"B", 11}, {"F", 15}, + } + + btn_width := content.width / 4 + btn_height := content.height / 4 + + for val, idx in keys { + str := strings.clone_to_cstring(val.label) + defer delete(str) + + ri := idx / 4 + ci := idx % 4 + irect := rl.Rectangle { + x = content.x + btn_width * f32(ci), + y = content.y + btn_height * f32(ri), + width = btn_width, + height = btn_height, + } + + if display[val.index] { rl.DrawRectangleRec(irect, rl.BLACK) } + + rl.DrawRectangleLinesEx(irect, 1, rl.GRAY) + rl.DrawTextEx( + font, + str, + rl.Vector2{irect.x + btn_width / 2, irect.y + btn_height / 2}, + KEYPAD_FONT_SIZE, + 1, + rl.WHITE, + ) + } +} diff --git a/src/simulator/gui_tab_memory.odin b/src/simulator/gui_tab_memory.odin new file mode 100644 index 0000000..4e2b705 --- /dev/null +++ b/src/simulator/gui_tab_memory.odin @@ -0,0 +1,126 @@ +package simulator + +import "core:fmt" +import "core:strings" +import rl "vendor:raylib" + +MEM_INDICATOR_W :: 14 +MEM_ADDRESS_W :: 72 +MEM_ROW_H :: 20 +MEM_HEADER_H :: 24 +MEM_BYTES_PER_ROW :: 16 +MEM_TOTAL_ROWS :: 256 +MEM_FONT_ROWS :: 5 +MEM_ROM_START :: 512 +MEM_VIRTUAL_ROWS :: MEM_FONT_ROWS + 1 + (256 - 32) + +MEM_COL_LABELS := [16]string{"0","1","2","3","4","5","6","7","8","9","A","B","C","D","E","F"} + +byte_col_w :: proc(rect: rl.Rectangle) -> f32 { + used := f32(MEM_INDICATOR_W + MEM_ADDRESS_W + 12) + return (rect.width - used) / 16 +} + +gui_tab_memory :: proc(rect: rl.Rectangle, sim: ^Simulator) { + rl.DrawRectangleRec(rect, rl.DARKGRAY) + + // Header background + rl.DrawRectangle(i32(rect.x), i32(rect.y), i32(rect.width), MEM_HEADER_H, rl.BLACK) + + // Header: Address label + rl.DrawTextEx(sim.font, "Address", {rect.x + MEM_INDICATOR_W, rect.y + 4}, 16, 1, rl.GRAY) + + // Header: column labels 0-F + col_w := byte_col_w(rect) + for col in 0..<16 { + x := rect.x + MEM_INDICATOR_W + MEM_ADDRESS_W + f32(col) * col_w + label := strings.clone_to_cstring(MEM_COL_LABELS[col], context.temp_allocator) + rl.DrawTextEx(sim.font, label, {x, rect.y + 4}, 16, 1, rl.GRAY) + } + + // Header separator + rl.DrawLine( + i32(rect.x), i32(rect.y + MEM_HEADER_H), + i32(rect.x + rect.width), i32(rect.y + MEM_HEADER_H), + rl.GRAY, + ) + + // Scroll panel area (below header) + panel_rect := rl.Rectangle{ + rect.x, + rect.y + MEM_HEADER_H, + rect.width, + rect.height - MEM_HEADER_H, + } + + content_rect := rl.Rectangle{ + 0, 0, + rect.width - 12, + f32(MEM_VIRTUAL_ROWS) * MEM_ROW_H, + } + + view : rl.Rectangle + rl.GuiScrollPanel(panel_rect, nil, content_rect, &sim.mem_scroll, &view) + + rl.BeginScissorMode(i32(view.x), i32(view.y), i32(view.width), i32(view.height)) + + draw_row := 0 + + // --- Font rows (0x000 - 0x04F) --- + for row in 0..