Compare commits
4 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 5b4e968d1c | |||
| 52cc1e08b6 | |||
| 6605d86916 | |||
| cccc4fb06c |
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.
+83
-49
@@ -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,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 rl "vendor:raylib"
|
||||
|
||||
PADDING :: 10
|
||||
|
||||
gui_control_bar :: proc(rect: rl.Rectangle, sim: ^Simulator) {
|
||||
rl.DrawRectangleLinesEx(rect, 1, rl.DARKGRAY)
|
||||
|
||||
// Cursor moves for every btn call places them left to right with padding
|
||||
cursor : f32 = rect.x + PANEL_PADDING
|
||||
rl.DrawTextEx(sim.font, "Octal Cookie Chip 8 Sim ", {rect.x + PADDING_X, rect.y + 12}, 25, 1, rl.WHITE)
|
||||
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 {
|
||||
sim.paused = false
|
||||
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.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 {
|
||||
r := rl.Rectangle{cursor^, rect.y + PADDING, w, h}
|
||||
r := rl.Rectangle{cursor^, rect.y + PADDING_X, w, h}
|
||||
cursor^ += w + gap
|
||||
return rl.GuiButton(r, label)
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -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"
|
||||
|
||||
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
|
||||
|
||||
// 2 : 1
|
||||
@@ -12,14 +20,14 @@ gui_screen :: proc(rect: rl.Rectangle, sim: ^Simulator) {
|
||||
|
||||
// create viewport for the ratio
|
||||
view := rect
|
||||
avail_space := rect.width / rect.height
|
||||
avail_space := bounds.width / bounds.height
|
||||
|
||||
if avail_space > aspect_ratio {
|
||||
view.width = rect.height * aspect_ratio
|
||||
view.x = rect.x + (rect.width - view.width) * 0.5
|
||||
view.width = bounds.height * aspect_ratio
|
||||
view.x = bounds.x + (bounds.width - view.width) * 0.5
|
||||
} else {
|
||||
view.height = rect.width / aspect_ratio
|
||||
view.y = rect.y + (rect.height - view.height) * 0.5
|
||||
view.height = bounds.width / aspect_ratio
|
||||
view.y = bounds.y + (bounds.height - view.height) * 0.5
|
||||
}
|
||||
|
||||
// get scale
|
||||
@@ -32,9 +40,7 @@ gui_screen :: proc(rect: rl.Rectangle, sim: ^Simulator) {
|
||||
// center frame
|
||||
x := i32(view.x + (view.width - f32(draw_w)) * 0.5)
|
||||
y := i32(view.y + (view.height - f32(draw_h)) * 0.5)
|
||||
|
||||
rl.DrawRectangleLinesEx(rect, 1, rl.DARKGRAY)
|
||||
rl.DrawRectangleLinesEx(view, 2, rl.WHITE)
|
||||
|
||||
if !sim.rom_loaded {
|
||||
// centered drop-zone text
|
||||
@@ -3,12 +3,14 @@ package simulator
|
||||
import "core:fmt"
|
||||
import rl "vendor:raylib"
|
||||
|
||||
StatusIconShape :: enum { CIRCLE, SQUARE }
|
||||
|
||||
// @TODO: render status bar text
|
||||
gui_status_bar :: proc(rect: rl.Rectangle, sim: ^Simulator) {
|
||||
// Left to right text draws
|
||||
rl.DrawRectangleLinesEx(rect, 1, rl.DARKGRAY)
|
||||
|
||||
cursor: f32 = rect.x + PADDING
|
||||
cursor: f32 = rect.x + PADDING_X
|
||||
cy := rect.y + rect.height * 0.5
|
||||
|
||||
if sim.running && !sim.paused {
|
||||
@@ -23,7 +25,7 @@ gui_status_bar :: proc(rect: rl.Rectangle, sim: ^Simulator) {
|
||||
// FPS set far right
|
||||
fps_text := fmt.ctprintf("FPS: %d", rl.GetFPS())
|
||||
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(
|
||||
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) {
|
||||
size: f32 = 10
|
||||
ix := cursor^ + size * 0.5
|
||||
@@ -48,15 +48,15 @@ status_icon :: proc(cursor: ^f32, cy: f32, color: rl.Color, shape: StatusIconSha
|
||||
|
||||
cursor^ += size + 6
|
||||
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) {
|
||||
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) {
|
||||
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
|
||||
}
|
||||
@@ -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,
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -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,12 +4,16 @@ import emu "../machine"
|
||||
import rl "vendor:raylib"
|
||||
|
||||
Simulator :: struct {
|
||||
// Emulator
|
||||
machine: ^emu.System,
|
||||
rom_loaded: bool,
|
||||
running: bool,
|
||||
paused: bool,
|
||||
cycles_per_second: int,
|
||||
// GUI
|
||||
font: rl.Font,
|
||||
active_tab: i32,
|
||||
mem_scroll : rl.Vector2,
|
||||
}
|
||||
|
||||
// Requires an initilized emulatore System Struct
|
||||
|
||||
Reference in New Issue
Block a user