Added/updated components to be isolated rectangles.

This commit is contained in:
2026-06-17 07:48:07 +02:00
parent cccc4fb06c
commit 6605d86916
6 changed files with 489 additions and 49 deletions
+83 -49
View File
@@ -3,29 +3,36 @@ package simulator
import emu "../machine" import emu "../machine"
import rl "vendor:raylib" import rl "vendor:raylib"
// Initial window size // @ Window
WINDOW_WIDTH :: 1920 WINDOW_WIDTH :: 1920
WINDOW_HEIGHT :: 1080 WINDOW_HEIGHT :: 1080
// @TODO: If this grows lets move it into its own file // @ Layout proportions
// ─── Layout constants ─────────────────────────────────────────────────── // Sidebar takes 20% of screen width on each side; display takes the rest.
SIDEBAR_PERCENT :: 0.20 // The center column splits vertically: 40% display, 60% debug panel.
DISPLAY_PERCENT :: 0.30 SIDEBAR_PERCENT :: f32(0.20)
CONTROL_BAR_H :: f32(50) DISPLAY_V_RATIO :: f32(0.40)
STATUS_BAR_H :: f32(30) // @ Fixed heights
PANEL_PADDING :: 10 CONTROL_BAR_H :: f32(50)
PANEL_HEADER :: 24 STATUS_BAR_H :: f32(30)
// @ Layout constants
PADDING_X :: f32(10)
PADDING_Y :: f32(8)
PANEL_HEADER :: f32(24)
// @ Buttons
BUTTON_HEIGHT :: 30 BUTTON_HEIGHT :: 30
BUTTON_WIDTH :: 120 BUTTON_WIDTH :: 120
// @ Fonts
BIG_FONT_SIZE :: 20 BIG_FONT_SIZE :: 20
KEYPAD_FONT_SIZE :: 18 KEYPAD_FONT_SIZE :: 18
Layout :: struct { Layout :: struct {
control_bar : rl.Rectangle, control_bar : rl.Rectangle,
left_panel : rl.Rectangle, file_loader : rl.Rectangle,
keypad : rl.Rectangle,
display : rl.Rectangle, display : rl.Rectangle,
bottom_panel : rl.Rectangle, bottom_panel : rl.Rectangle,
right_panel : rl.Rectangle, cpu : rl.Rectangle,
status_bar : rl.Rectangle, status_bar : rl.Rectangle,
} }
@@ -46,7 +53,7 @@ run_gui :: proc(sim: ^Simulator) {
rl.GuiLoadStyle("./assets/raygui_styles/style_dark.rgs") rl.GuiLoadStyle("./assets/raygui_styles/style_dark.rgs")
rl.GuiSetFont(font) 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 // Draw each of the components in its own window within the main window
for !rl.WindowShouldClose() { for !rl.WindowShouldClose() {
@@ -79,9 +86,26 @@ run_gui :: proc(sim: ^Simulator) {
} }
} }
// Top
// ------------------------------------------
gui_control_bar(layout.control_bar, sim) 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) 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) gui_status_bar(layout.status_bar, sim)
rl.EndDrawing() rl.EndDrawing()
@@ -95,64 +119,74 @@ run_gui :: proc(sim: ^Simulator) {
// @TODO: If this grows lets move it into its own file // @TODO: If this grows lets move it into its own file
calc_layout :: proc(screen_width: f32, screen_height: f32) -> Layout { calc_layout :: proc(screen_width: f32, screen_height: f32) -> Layout {
top_h := CONTROL_BAR_H // Control bar is a fixed height frozen at top of gui, all items start below it.
bottom_h := STATUS_BAR_H y_pos := CONTROL_BAR_H
content_h := screen_height - top_h - bottom_h x_pos := f32(0)
content_y := top_h
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 x_center := sidebar_width
bottom_panel_h := content_h * f32(1.0 - screen_h_ratio) y_center := screen_width - sidebar_width * 2
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
return Layout { return Layout {
// Left Area
control_bar = rl.Rectangle{ control_bar = rl.Rectangle{
x = 0, x = 0,
y = 0, y = 0,
width = screen_width, width = screen_width,
height = top_h, height = CONTROL_BAR_H,
}, },
file_loader = rl.Rectangle{
left_panel = rl.Rectangle{
x = 0, x = 0,
y = top_h, y = y_pos,
width = sidebar_w, width = sidebar_width,
height = content_h, 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{ display = rl.Rectangle{
x = center_x, x = sidebar_width,
y = top_h, y = y_pos,
width = center_w, width = display_width,
height = screen_h, height = display_height,
}, },
// MEMORY / DEBUG panel (bottom center) // ------------------------------------------
// Bottom Area
bottom_panel = rl.Rectangle{ bottom_panel = rl.Rectangle{
x = center_x, x = sidebar_width,
y = top_h + screen_h, y = y_pos + display_height,
width = center_w, width = screen_width - sidebar_width,
height = memory_h, height = visible_height - display_height,
}, },
right_panel = rl.Rectangle{ // ------------------------------------------
x = screen_width - sidebar_w, // Right Area
y = top_h, cpu = rl.Rectangle {
width = sidebar_w, x = sidebar_width + display_width,
height = content_h, y = y_pos,
width = sidebar_width * 2,
height = display_height
}, },
// ------------------------------------------
// Bottom Area
status_bar = rl.Rectangle{ status_bar = rl.Rectangle{
x = 0, x = x_pos,
y = screen_height - bottom_h, y = y_pos + visible_height,
width = screen_width, width = screen_width,
height = bottom_h, height = STATUS_BAR_H,
}, },
} }
} }
+34
View File
@@ -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
}
}
+104
View File
@@ -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 (V0VF, 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)
}
}
+77
View File
@@ -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)
}
}
+65
View File
@@ -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,
)
}
}
+126
View File
@@ -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..<MEM_FONT_ROWS {
addr := row * MEM_BYTES_PER_ROW
row_y := panel_rect.y + f32(draw_row) * MEM_ROW_H + sim.mem_scroll.y
draw_memory_row(rect, sim, draw_row, addr, row_y)
draw_row += 1
}
// --- Divider ---
divider_y := panel_rect.y + f32(draw_row) * MEM_ROW_H + sim.mem_scroll.y
rl.DrawRectangle(i32(rect.x), i32(divider_y), i32(rect.width), MEM_ROW_H, rl.ColorAlpha(rl.BLACK, 0.4))
rl.DrawTextEx(
sim.font,
"---- reserved (0x0050 - 0x01FF) ----",
{rect.x + MEM_INDICATOR_W, divider_y + 2},
16, 1, rl.DARKGRAY,
)
draw_row += 1
// --- ROM rows (0x200 onwards) ---
for i := 0; MEM_ROM_START + i * MEM_BYTES_PER_ROW < 4096; i += 1 {
addr := MEM_ROM_START + i * MEM_BYTES_PER_ROW
row_y := panel_rect.y + f32(draw_row) * MEM_ROW_H + sim.mem_scroll.y
draw_memory_row(rect, sim, draw_row, addr, row_y)
draw_row += 1
}
rl.EndScissorMode()
}
draw_memory_row :: proc(rect: rl.Rectangle, sim: ^Simulator, visual_row: int, addr: int, row_y: f32) {
col_w := byte_col_w(rect)
if visual_row % 2 == 0 {
rl.DrawRectangle(i32(rect.x), i32(row_y), i32(rect.width), MEM_ROW_H, rl.ColorAlpha(rl.BLACK, 0.2))
}
addr_str := fmt.tprintf("%04X", addr)
rl.DrawTextEx(
sim.font,
strings.clone_to_cstring(addr_str, context.temp_allocator),
{rect.x + MEM_INDICATOR_W, row_y + 2},
16, 1, rl.RAYWHITE,
)
for col in 0..<16 {
byte_val := sim.machine.memory[addr + col]
byte_str := fmt.tprintf("%02X", byte_val)
x := rect.x + MEM_INDICATOR_W + MEM_ADDRESS_W + f32(col) * col_w
color := rl.RAYWHITE if byte_val != 0 else rl.GRAY
rl.DrawTextEx(
sim.font,
strings.clone_to_cstring(byte_str, context.temp_allocator),
{x, row_y + 2},
16, 1, color,
)
}
}