243 lines
5.8 KiB
Odin
243 lines
5.8 KiB
Odin
package simulator
|
|
|
|
import emu "../machine"
|
|
import rl "vendor:raylib"
|
|
|
|
// Window
|
|
WINDOW_WIDTH :: 1920
|
|
WINDOW_HEIGHT :: 1080
|
|
|
|
// 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
|
|
LINE_HEIGHT :: 22
|
|
|
|
Layout :: struct {
|
|
control_bar : rl.Rectangle,
|
|
file_loader : rl.Rectangle,
|
|
keypad : rl.Rectangle,
|
|
display : rl.Rectangle,
|
|
bottom_panel : rl.Rectangle,
|
|
cpu : rl.Rectangle,
|
|
status_bar : rl.Rectangle,
|
|
info_box : rl.Rectangle,
|
|
}
|
|
|
|
init :: proc(sim: ^Simulator) {
|
|
_sim = sim
|
|
|
|
// desktop
|
|
when ODIN_OS != .JS {
|
|
rl.SetConfigFlags({.WINDOW_RESIZABLE})
|
|
rl.SetTargetFPS(60)
|
|
}
|
|
|
|
// web
|
|
when ODIN_OS == .JS {
|
|
rl.SetConfigFlags({.VSYNC_HINT})
|
|
}
|
|
|
|
rl.InitWindow(WINDOW_WIDTH, WINDOW_HEIGHT, "Octal Cookie - Chip 8 Simulator")
|
|
rl.InitAudioDevice()
|
|
|
|
// Load sound
|
|
beep := rl.LoadSound("./assets/sounds/beep.wav")
|
|
_sim.sound = beep
|
|
|
|
// Load fonts
|
|
font := rl.LoadFontEx("./assets/fonts/Inter_18pt-Regular.ttf", 18, nil, 0)
|
|
rl.SetTextureFilter(font.texture, .BILINEAR)
|
|
_sim.font = font
|
|
|
|
rl.GuiLoadStyleDefault()
|
|
rl.GuiLoadStyle("./assets/raygui_styles/genesis.rgs")
|
|
|
|
rl.GuiSetFont(font)
|
|
rl.GuiSetStyle(.DEFAULT, i32(rl.GuiDefaultProperty.TEXT_SIZE), 18)
|
|
}
|
|
|
|
update :: proc() {
|
|
sim := _sim
|
|
|
|
// Recalculate layout each frame based on current window size
|
|
// Pass these down to gui functions so they can setup their sizes?
|
|
screen_width := f32(rl.GetScreenWidth())
|
|
screen_height := f32(rl.GetScreenHeight())
|
|
sidebar_width := screen_width * 0.20
|
|
|
|
// set all the layout structs dynamically with the screen size
|
|
layout := calc_layout(screen_width, screen_height)
|
|
|
|
rl.BeginDrawing()
|
|
rl.ClearBackground(rl.Color{0x18, 0x18, 0x18, 0xFF})
|
|
|
|
cycles := int(sim.cpu_hz / SIM_FPS)
|
|
if (!sim.paused) {
|
|
// Cycle the machine to update memory etc
|
|
emu.run_machine(sim.machine, cycles)
|
|
tick_timers(sim)
|
|
}
|
|
|
|
if(sim.paused && sim.step) {
|
|
// Cycle the machine to update memory etc
|
|
emu.run_machine(sim.machine, 1)
|
|
tick_timers(sim)
|
|
sim.step = false
|
|
}
|
|
|
|
// Top
|
|
// ------------------------------------------
|
|
gui_control_bar(layout.control_bar, sim)
|
|
|
|
// Left
|
|
// ------------------------------------------
|
|
when ODIN_OS != .JS { 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_panel(layout.bottom_panel, sim)
|
|
gui_status_bar(layout.status_bar, sim)
|
|
|
|
// Info Box
|
|
// ------------------------------------------
|
|
gui_info_box(layout.info_box, sim, screen_width, screen_height)
|
|
|
|
rl.EndDrawing()
|
|
free_all(context.temp_allocator)
|
|
}
|
|
|
|
shutdown :: proc() {
|
|
sim := _sim
|
|
rl.UnloadFont(sim.font)
|
|
rl.UnloadSound(sim.sound)
|
|
rl.CloseAudioDevice()
|
|
rl.CloseWindow()
|
|
}
|
|
|
|
should_run :: proc() -> bool {
|
|
when ODIN_OS != .JS {
|
|
return !rl.WindowShouldClose()
|
|
}
|
|
return true // web loop is controlled by JS requestAnimationFrame
|
|
}
|
|
|
|
tick_timers :: proc(sim: ^Simulator) {
|
|
beep := sim.sound
|
|
|
|
if sim.machine.delay_timer > 0 do sim.machine.delay_timer -= 1
|
|
if sim.machine.sound_timer > 0 {
|
|
sim.machine.sound_timer -= 1
|
|
if !rl.IsSoundPlaying(beep) do rl.PlaySound(beep)
|
|
} else {
|
|
// only stop if actually playing
|
|
if rl.IsSoundPlaying(beep) do rl.StopSound(beep)
|
|
}
|
|
}
|
|
|
|
calc_layout :: proc(screen_width: f32, screen_height: f32) -> Layout {
|
|
// 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)
|
|
|
|
// 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
|
|
keypad_height := (y_pos + visible_height - visible_height / 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 = CONTROL_BAR_H,
|
|
},
|
|
file_loader = rl.Rectangle{
|
|
x = 0,
|
|
y = y_pos,
|
|
width = sidebar_width,
|
|
height = keypad_height - y_pos
|
|
},
|
|
keypad = rl.Rectangle{
|
|
x = 0,
|
|
y = keypad_height,
|
|
width = sidebar_width,
|
|
height = visible_height / 2
|
|
},
|
|
|
|
// ------------------------------------------
|
|
// Center Area
|
|
display = rl.Rectangle{
|
|
x = sidebar_width,
|
|
y = y_pos,
|
|
width = display_width,
|
|
height = display_height,
|
|
},
|
|
|
|
// ------------------------------------------
|
|
// Bottom Area
|
|
bottom_panel = rl.Rectangle{
|
|
x = sidebar_width,
|
|
y = y_pos + display_height,
|
|
width = screen_width - sidebar_width,
|
|
height = visible_height - display_height,
|
|
},
|
|
|
|
// ------------------------------------------
|
|
// 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 = x_pos,
|
|
y = y_pos + visible_height,
|
|
width = screen_width,
|
|
height = STATUS_BAR_H,
|
|
},
|
|
|
|
// ------------------------------------------
|
|
// Info Box
|
|
info_box = rl.Rectangle{
|
|
x = (screen_width / 2 - 200),
|
|
y = (screen_height / 2 - 200),
|
|
width = 400,
|
|
height = 140
|
|
}
|
|
}
|
|
}
|