Compare commits

...

4 Commits

Author SHA1 Message Date
jasonhilder 5b4e968d1c Added some default game roms 2026-06-17 07:48:34 +02:00
jasonhilder 52cc1e08b6 Small additions 2026-06-17 07:48:25 +02:00
jasonhilder 6605d86916 Added/updated components to be isolated rectangles. 2026-06-17 07:48:07 +02:00
jasonhilder cccc4fb06c Removed unneeded components, renamed reused.
Updated the components to start with a gui_ prefix, changed the layout
idea that each component is responsible for their own bounding box and
inner content.
2026-06-17 07:46:24 +02:00
36 changed files with 526 additions and 233 deletions
+1
View File
@@ -1 +1,2 @@
external/ external/
assets/
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+81 -47
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)
DISPLAY_V_RATIO :: f32(0.40)
// @ Fixed heights
CONTROL_BAR_H :: f32(50) CONTROL_BAR_H :: f32(50)
STATUS_BAR_H :: f32(30) STATUS_BAR_H :: f32(30)
PANEL_PADDING :: 10 // @ Layout constants
PANEL_HEADER :: 24 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
}
}
@@ -3,15 +3,16 @@ package simulator
import "core:log" import "core:log"
import rl "vendor:raylib" import rl "vendor:raylib"
PADDING :: 10
gui_control_bar :: proc(rect: rl.Rectangle, sim: ^Simulator) { gui_control_bar :: proc(rect: rl.Rectangle, sim: ^Simulator) {
rl.DrawRectangleLinesEx(rect, 1, rl.DARKGRAY) rl.DrawRectangleLinesEx(rect, 1, rl.DARKGRAY)
// Cursor moves for every btn call places them left to right with padding rl.DrawTextEx(sim.font, "Octal Cookie Chip 8 Sim ", {rect.x + PADDING_X, rect.y + 12}, 25, 1, rl.WHITE)
cursor : f32 = rect.x + PANEL_PADDING text_size := rl.MeasureTextEx(sim.font, "Octal Cookie Chip 8 Sim ", 25, 1)
if btn(&cursor, rect, BUTTON_HEIGHT, BUTTON_WIDTH, PANEL_PADDING, "RUN") { // Cursor moves for every btn call places them left to right with padding
cursor : f32 = text_size.x + 5 + rect.x + PADDING_X
if btn(&cursor, rect, BUTTON_HEIGHT, BUTTON_WIDTH, PADDING_X, "RUN") {
if sim.rom_loaded { if sim.rom_loaded {
sim.paused = false sim.paused = false
sim.running = true sim.running = true
@@ -20,14 +21,18 @@ gui_control_bar :: proc(rect: rl.Rectangle, sim: ^Simulator) {
} }
} }
if btn(&cursor, rect, BUTTON_HEIGHT, BUTTON_WIDTH, PANEL_PADDING, "PAUSE") { if btn(&cursor, rect, BUTTON_HEIGHT, BUTTON_WIDTH, PADDING_X, "PAUSE") {
sim.paused = true sim.paused = true
sim.running = false sim.running = false
} }
if btn(&cursor, rect, BUTTON_HEIGHT, BUTTON_WIDTH, PADDING_X, "RESET") {
}
} }
btn :: proc(cursor: ^f32, rect: rl.Rectangle, h, w, gap: f32, label: cstring) -> bool { btn :: proc(cursor: ^f32, rect: rl.Rectangle, h, w, gap: f32, label: cstring) -> bool {
r := rl.Rectangle{cursor^, rect.y + PADDING, w, h} r := rl.Rectangle{cursor^, rect.y + PADDING_X, w, h}
cursor^ += w + gap cursor^ += w + gap
return rl.GuiButton(r, label) return rl.GuiButton(r, label)
} }
+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,
)
}
}
@@ -3,6 +3,14 @@ package simulator
import rl "vendor:raylib" import rl "vendor:raylib"
gui_screen :: proc(rect: rl.Rectangle, sim: ^Simulator) { gui_screen :: proc(rect: rl.Rectangle, sim: ^Simulator) {
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, nil)
s := sim.machine s := sim.machine
// 2 : 1 // 2 : 1
@@ -12,14 +20,14 @@ gui_screen :: proc(rect: rl.Rectangle, sim: ^Simulator) {
// create viewport for the ratio // create viewport for the ratio
view := rect view := rect
avail_space := rect.width / rect.height avail_space := bounds.width / bounds.height
if avail_space > aspect_ratio { if avail_space > aspect_ratio {
view.width = rect.height * aspect_ratio view.width = bounds.height * aspect_ratio
view.x = rect.x + (rect.width - view.width) * 0.5 view.x = bounds.x + (bounds.width - view.width) * 0.5
} else { } else {
view.height = rect.width / aspect_ratio view.height = bounds.width / aspect_ratio
view.y = rect.y + (rect.height - view.height) * 0.5 view.y = bounds.y + (bounds.height - view.height) * 0.5
} }
// get scale // get scale
@@ -32,9 +40,7 @@ gui_screen :: proc(rect: rl.Rectangle, sim: ^Simulator) {
// center frame // center frame
x := i32(view.x + (view.width - f32(draw_w)) * 0.5) x := i32(view.x + (view.width - f32(draw_w)) * 0.5)
y := i32(view.y + (view.height - f32(draw_h)) * 0.5) y := i32(view.y + (view.height - f32(draw_h)) * 0.5)
rl.DrawRectangleLinesEx(rect, 1, rl.DARKGRAY) rl.DrawRectangleLinesEx(rect, 1, rl.DARKGRAY)
rl.DrawRectangleLinesEx(view, 2, rl.WHITE)
if !sim.rom_loaded { if !sim.rom_loaded {
// centered drop-zone text // centered drop-zone text
@@ -3,12 +3,14 @@ package simulator
import "core:fmt" import "core:fmt"
import rl "vendor:raylib" import rl "vendor:raylib"
StatusIconShape :: enum { CIRCLE, SQUARE }
// @TODO: render status bar text // @TODO: render status bar text
gui_status_bar :: proc(rect: rl.Rectangle, sim: ^Simulator) { gui_status_bar :: proc(rect: rl.Rectangle, sim: ^Simulator) {
// Left to right text draws // Left to right text draws
rl.DrawRectangleLinesEx(rect, 1, rl.DARKGRAY) rl.DrawRectangleLinesEx(rect, 1, rl.DARKGRAY)
cursor: f32 = rect.x + PADDING cursor: f32 = rect.x + PADDING_X
cy := rect.y + rect.height * 0.5 cy := rect.y + rect.height * 0.5
if sim.running && !sim.paused { if sim.running && !sim.paused {
@@ -23,7 +25,7 @@ gui_status_bar :: proc(rect: rl.Rectangle, sim: ^Simulator) {
// FPS set far right // FPS set far right
fps_text := fmt.ctprintf("FPS: %d", rl.GetFPS()) fps_text := fmt.ctprintf("FPS: %d", rl.GetFPS())
fps_width := rl.MeasureTextEx(sim.font, fps_text, f32(sim.font.baseSize), 1).x fps_width := rl.MeasureTextEx(sim.font, fps_text, f32(sim.font.baseSize), 1).x
fps_x := rect.x + rect.width - PADDING - fps_width fps_x := rect.x + rect.width - PADDING_X - fps_width
rl.DrawTextEx( rl.DrawTextEx(
sim.font, sim.font,
@@ -35,8 +37,6 @@ gui_status_bar :: proc(rect: rl.Rectangle, sim: ^Simulator) {
) )
} }
StatusIconShape :: enum { CIRCLE, SQUARE }
status_icon :: proc(cursor: ^f32, cy: f32, color: rl.Color, shape: StatusIconShape, label: cstring, font: rl.Font) { status_icon :: proc(cursor: ^f32, cy: f32, color: rl.Color, shape: StatusIconShape, label: cstring, font: rl.Font) {
size: f32 = 10 size: f32 = 10
ix := cursor^ + size * 0.5 ix := cursor^ + size * 0.5
@@ -48,15 +48,15 @@ status_icon :: proc(cursor: ^f32, cy: f32, color: rl.Color, shape: StatusIconSha
cursor^ += size + 6 cursor^ += size + 6
rl.DrawTextEx(font, label, {cursor^, cy - f32(font.baseSize) * 0.5}, f32(font.baseSize), 1, rl.RAYWHITE) rl.DrawTextEx(font, label, {cursor^, cy - f32(font.baseSize) * 0.5}, f32(font.baseSize), 1, rl.RAYWHITE)
cursor^ += rl.MeasureTextEx(font, label, f32(font.baseSize), 1).x + PADDING cursor^ += rl.MeasureTextEx(font, label, f32(font.baseSize), 1).x + PADDING_X
} }
status_divider :: proc(cursor: ^f32, cy: f32) { status_divider :: proc(cursor: ^f32, cy: f32) {
rl.DrawLineV({cursor^, cy - 8}, {cursor^, cy + 8}, rl.DARKGRAY) rl.DrawLineV({cursor^, cy - 8}, {cursor^, cy + 8}, rl.DARKGRAY)
cursor^ += PADDING cursor^ += PADDING_X
} }
status_text :: proc(cursor: ^f32, cy: f32, text: cstring, font: rl.Font) { status_text :: proc(cursor: ^f32, cy: f32, text: cstring, font: rl.Font) {
rl.DrawTextEx(font, text, {cursor^, cy - f32(font.baseSize) * 0.5}, f32(font.baseSize), 1, rl.RAYWHITE) rl.DrawTextEx(font, text, {cursor^, cy - f32(font.baseSize) * 0.5}, f32(font.baseSize), 1, rl.RAYWHITE)
cursor^ += rl.MeasureTextEx(font, text, f32(font.baseSize), 1).x + PADDING cursor^ += rl.MeasureTextEx(font, text, f32(font.baseSize), 1).x + PADDING_X
} }
+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,
)
}
}
-155
View File
@@ -1,155 +0,0 @@
package simulator
import "core:log"
import "core:strings"
import rl "vendor:raylib"
import emu "../machine"
import tfd "../../external/tinyfiledialogs"
gui_left_panel :: proc(rect: rl.Rectangle, sim: ^Simulator) {
// ── Top panel and components ──
top_panel := rl.Rectangle {
rect.x,
rect.y,
rect.width,
rect.height / 2,
}
rl.DrawRectangleLinesEx(top_panel, 1, rl.DARKGRAY)
// Dropzone/file loader
file_loader_rect := rl.Rectangle {
top_panel.x,
top_panel.y,
top_panel.width,
(top_panel.height / 3) - PANEL_HEADER - PANEL_PADDING * 2,
}
gui_file_loader(file_loader_rect, sim)
// ── Bottom panel and components ──
bottom_panel := rl.Rectangle {
rect.x,
rect.y + top_panel.height,
rect.width,
rect.height / 2,
}
rl.DrawRectangleLinesEx(bottom_panel, 1, rl.GREEN)
gui_key_pad(bottom_panel, sim.machine.keypad, sim.font)
}
gui_file_loader :: proc(rect: rl.Rectangle, sim: ^Simulator) {
rl.GuiPanel(rect, "Rom / File")
// drop-zone occupies the panel's content area, minus space for the button
drop_zone := rl.Rectangle {
rect.x + PANEL_PADDING,
rect.y + PANEL_HEADER + PANEL_PADDING,
rect.width - PANEL_PADDING * 2,
rect.height - PANEL_HEADER - PANEL_PADDING * 2 - BUTTON_HEIGHT - PANEL_PADDING,
}
rl.DrawRectangleLinesEx(drop_zone, 1, rl.GREEN)
// 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 + PANEL_PADDING,
BUTTON_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
sim.running = true
sim.paused = false
}
// 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)
}
}
gui_key_pad :: proc(rect: rl.Rectangle, display: [16]bool, font: rl.Font) {
rl.GuiPanel(rect, "Input / Keypad")
content := rl.Rectangle {
rect.x + PANEL_PADDING,
rect.y + PANEL_HEADER + PANEL_PADDING,
rect.width - PANEL_PADDING * 2,
rect.height - PANEL_HEADER - PANEL_PADDING * 2,
}
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,
)
}
}
-8
View File
@@ -1,8 +0,0 @@
package simulator
import m "../machine"
import rl "vendor:raylib"
gui_right_panel :: proc(rect: rl.Rectangle, s: ^m.System) {
rl.DrawRectangleLinesEx(rect, 1, rl.DARKGRAY)
}
+4
View File
@@ -4,12 +4,16 @@ import emu "../machine"
import rl "vendor:raylib" import rl "vendor:raylib"
Simulator :: struct { Simulator :: struct {
// Emulator
machine: ^emu.System, machine: ^emu.System,
rom_loaded: bool, rom_loaded: bool,
running: bool, running: bool,
paused: bool, paused: bool,
cycles_per_second: int, cycles_per_second: int,
// GUI
font: rl.Font, font: rl.Font,
active_tab: i32,
mem_scroll : rl.Vector2,
} }
// Requires an initilized emulatore System Struct // Requires an initilized emulatore System Struct