Compare commits

..
14 Commits
16 changed files with 243 additions and 53 deletions
+3
View File
@@ -0,0 +1,3 @@
out/
assets/
gamecontrollerdb.txt
+85 -2
View File
@@ -1,3 +1,86 @@
# pong_raylib # PONG 2026
Classic pong implemented in Odin and Raylib A deliberately over engineered Pong clone in [Odin](https://odin-lang.org) with [raylib](https://www.raylib.com).
Retro line-based graphics with a CRT post-processing shader.
## Screenshots
![Main menu](screenshots/main_menu.png)
![Settings menu](screenshots/settings_menu.png)
![Gameplay](screenshots/gameplay.png)
## Why Over-Engineered?
While the game could have been done in a single file, the goal was to structure the project like a small production game instead:
- **Manager-per-subsystem**: audio, input, event, level, particle, and scene managers, each in its own file
- **Event system**: decoupled communication between systems (`event_manager.odin`) instead of direct calls
- **Scene management**: state/scene stack for menus, gameplay, etc. (`scene_manager.odin`)
- **Entity layer**: shared base entity with ball, player, and opponent specializations
- **Input abstraction**: keyboard and gamepad behind one interface
- **Data-driven assets**: audio and shaders loaded from `assets/`, not embedded
The idea behind breaking it into systems is so that it can help build my foundations into more complex games.
## Features
- Single-player vs AI opponent
- CRT fragment shader (`assets/shaders/crt.fs`)
- Particle effects, sound effects, music
- Gamepad support via SDL controller mappings
## Controls
| Action | Keyboard | Gamepad |
|--------|----------|---------|
| Move | W(up) S(down) | Left Joystick |
| Select | Enter | A button |
| Pause | Spacebar | Start button |
## Requirements
Please note, I have not built this on MacOs or Windows, only linux.
While in theory if you have the odin compiler it should just work as there is no external libs besides those bundled with Odin.
- Odin compiler (version dev-2026-10:e87445a58)
- raylib (bundled with Odin as `vendor:raylib`)
- GPU/driver with OpenGL 3.3 support (shader uses `#version 330`)
## Build & Run
./build.sh # see build.sh for flags and options
./out/pong
## Project Layout
assets/
audio/ music, SFX, UI sounds
shaders/ crt.fs
screenshots/ README images
src/
main.odin entry point
game.odin game state and loop
*_manager.odin audio, event, input, level, particle, scene
entity*.odin base entity, ball, player, opponent
out/ build output
build.sh build script
## Credits
Sourced from the web, not mine.
| File | Title | Author | Source | License |
|------|-------|--------|--------|---------|
| `UI, gameplay effects` | Impact Sounds 1.0 + Interface Sounds 1.0 | Kenney | https://www.kenney.nl | [CC0 1.0](http://creativecommons.org/publicdomain/zero/1.0/) |
| `Background Music` | 5 Action Chiptunes By Juhani Junkala | Juhani Junkala | <download page URL> | CC0 1.0 |
### Third-Party
- `gamecontrollerdb.txt`: [SDL_GameControllerDB](https://github.com/mdqinc/SDL_GameControllerDB) (zlib)
- [raylib](https://github.com/raysan5/raylib) by Ramon Santamaria (@raysan5), zlib
- [Odin](https://github.com/odin-lang/Odin), BSD-3-Clause
## License
MIT, see [LICENSE](LICENSE).
Any third-party assets retain their original licenses, listed above.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+48
View File
@@ -0,0 +1,48 @@
#version 330
in vec2 fragTexCoord;
in vec4 fragColor;
out vec4 finalColor;
uniform sampler2D texture0;
uniform vec4 colDiffuse;
// Adjust these values to tweak the aesthetic:
const float SCANLINE_INTENSITY = 0.25; // Lower value = softer lines (0.08 to 0.20)
const float BLOOM_AMOUNT = 0.10; // Slight phosphor bleed/glow
const float CRT_CURVATURE = 0.03; // Subtle screen bulge (0.0 = flat)
void main() {
// 1. Subtle screen curvature distortion
vec2 uv = fragTexCoord;
vec2 dc = abs(0.5 - uv);
uv.x += uv.x * (dc.y * dc.y) * CRT_CURVATURE;
uv.y += uv.y * (dc.x * dc.x) * CRT_CURVATURE;
// Discard pixels outside the curved screen bounds
if (uv.x < 0.0 || uv.x > 1.0 || uv.y < 0.0 || uv.y > 1.0) {
finalColor = vec4(0.0, 0.0, 0.0, 1.0);
return;
}
// 2. Sample pixel with slight horizontal blur (Phosphor bleed)
vec4 col = texture(texture0, uv);
vec4 col_left = texture(texture0, uv - vec2(0.0008, 0.0));
vec4 col_right = texture(texture0, uv + vec2(0.0008, 0.0));
col.rgb = mix(col.rgb, (col_left.rgb + col_right.rgb) * 0.5, BLOOM_AMOUNT);
// 3. Gaussian scanline profile matching 360 vertical lines
float scanline = sin(uv.y * 360.0 * 3.14159);
// Smooth power curve prevents harsh pitch-black cutoffs
float scanline_factor = pow(0.5 + 0.5 * scanline, 1.8);
// Apply scanlines smoothly
col.rgb *= mix(1.0 - SCANLINE_INTENSITY, 1.0, scanline_factor);
// 4. Subtle corner vignette
float vig = (16.0 * uv.x * uv.y * (1.0 - uv.x) * (1.0 - uv.y));
col.rgb *= pow(vig, 0.05);
finalColor = col * colDiffuse * fragColor;
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 632 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 400 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 420 KiB

+4 -4
View File
@@ -20,7 +20,7 @@ Sound_ID :: enum {
Audio_Manager :: struct { Audio_Manager :: struct {
sounds: map[Sound_ID]rl.Sound, sounds: map[Sound_ID]rl.Sound,
music: rl.Music, music: rl.Music,
music_loaded: bool, music_loaded: bool, //
sfx_vol: f32, // User SFX volume setting sfx_vol: f32, // User SFX volume setting
user_music_vol: f32, // User Music volume setting user_music_vol: f32, // User Music volume setting
@@ -40,13 +40,13 @@ audio_init :: proc(audio: ^Audio_Manager) {
audio.sounds = make(map[Sound_ID]rl.Sound) audio.sounds = make(map[Sound_ID]rl.Sound)
// Load SFX // Load SFX
audio.sfx_vol = 0.3 audio.sfx_vol = 0.4
audio.sounds[.UI_Select] = rl.LoadSound("assets/audio/ui/select.ogg") audio.sounds[.UI_Select] = rl.LoadSound("assets/audio/ui/select.ogg")
audio.sounds[.Ball_Collision] = rl.LoadSound("assets/audio/gameplay/ballhit.ogg") audio.sounds[.Ball_Collision] = rl.LoadSound("assets/audio/gameplay/ballhit.ogg")
// Load Music Stream // Load Music Stream
audio.user_music_vol = 0.1 audio.user_music_vol = 0.4
audio.current_music_vol = 0.1 audio.current_music_vol = 0.4
audio.fade_state = .None audio.fade_state = .None
audio.music = rl.LoadMusicStream("assets/audio/gameplay/music/track_1.wav") audio.music = rl.LoadMusicStream("assets/audio/gameplay/music/track_1.wav")
audio.music_loaded = true audio.music_loaded = true
+1
View File
@@ -120,6 +120,7 @@ check_ball_paddle_collision :: proc(ball: ^Entity, paddle: Entity) -> bool {
} }
increment_score :: proc(score_side: ^int, max_score: int, player: bool, ball: ^Entity) { increment_score :: proc(score_side: ^int, max_score: int, player: bool, ball: ^Entity) {
emit_event(.Score_Change)
score_side^ += 1 score_side^ += 1
if score_side^ == max_score { if score_side^ == max_score {
emit_event(Game_Over_Data{player}) emit_event(Game_Over_Data{player})
+5 -9
View File
@@ -58,21 +58,13 @@ process_events :: proc(game: ^Game) {
push_scene(game, make_game_over_overlay(e.winner_is_player)) push_scene(game, make_game_over_overlay(e.winner_is_player))
case Ball_Collision_Data: case Ball_Collision_Data:
// normalize the speed between 0.0 -> 1.0
// map that value to the camera displacement
min_speed: f32 = 200.0
max_speed: f32 = 550.0
speed_factor := clamp((e.speed - min_speed) / (max_speed - min_speed), 0, 1.0)
intensity := 0.5 + (speed_factor * 4.5)
game_trigger_shake(game, intensity, duration = 0.15)
audio_play_sfx(&game.audio, .Ball_Collision) audio_play_sfx(&game.audio, .Ball_Collision)
case Simple_Event: case Simple_Event:
#partial switch e { #partial switch e {
case .Game_Start: case .Game_Start:
set_root_scene(game, make_gameplay_scene()) set_root_scene(game, make_gameplay_scene())
game.transition = {true, 0, 0.2}
audio_fade_in(&game.audio, duration = 1.5) audio_fade_in(&game.audio, duration = 1.5)
case .Game_Pause: case .Game_Pause:
@@ -81,6 +73,9 @@ process_events :: proc(game: ^Game) {
case .Game_Resume: case .Game_Resume:
pop_scene(game) pop_scene(game)
case .Score_Change:
game_trigger_shake(game, 10, duration = 0.20)
case .Game_Settings: case .Game_Settings:
parent: Maybe(Scene_Type) = nil parent: Maybe(Scene_Type) = nil
if active := get_active_scene(game); active != nil { if active := get_active_scene(game); active != nil {
@@ -92,6 +87,7 @@ process_events :: proc(game: ^Game) {
case .Game_Main_Menu: case .Game_Main_Menu:
set_root_scene(game, make_main_menu()) set_root_scene(game, make_main_menu())
game.transition = {true, 0, 0.3}
audio_fade_out(&game.audio, duration = 1.0) audio_fade_out(&game.audio, duration = 1.0)
case .Game_Back: case .Game_Back:
+63 -10
View File
@@ -6,7 +6,7 @@ import rl "vendor:raylib"
VIEWPORT_W :: 640 VIEWPORT_W :: 640
VIEWPORT_H :: 360 VIEWPORT_H :: 360
WINDOW_TITLE :: "Pong" WINDOW_TITLE :: "PONG 2026"
Resolution :: struct { Resolution :: struct {
width: i32, width: i32,
@@ -16,16 +16,27 @@ Resolution :: struct {
// Preset window dimensions // Preset window dimensions
RESOLUTIONS := [?]Resolution{ RESOLUTIONS := [?]Resolution{
{1280, 720, "1280x720"}, {0, 0, "Fullscreen"},
{1024, 768, "1024x768"},
{1280, 720, "1280x720"},
{1366, 768, "1366x768"},
{1600, 900, "1600x900"},
{1920, 1080, "1920x1080"}, {1920, 1080, "1920x1080"},
{2560, 1440, "2560x1440"}, {2560, 1440, "2560x1440"},
{3840, 2160, "3840x2160"}, {3840, 2160, "3840x2160"},
} }
Transition :: struct {
active: bool,
elapsed: f32,
duration: f32,
}
Game :: struct { Game :: struct {
viewport: rl.RenderTexture2D, viewport: rl.RenderTexture2D,
bg_texture: rl.Texture2D, bg_texture: rl.Texture2D,
crt_shader: rl.Shader, crt_shader: rl.Shader,
transition: Transition,
scene_stack: [dynamic]Scene, scene_stack: [dynamic]Scene,
should_close: bool, should_close: bool,
audio: Audio_Manager, audio: Audio_Manager,
@@ -38,7 +49,11 @@ Game :: struct {
// resolution state // resolution state
window_w: i32, window_w: i32,
window_h: i32, window_h: i32,
res_index: int res_index: int,
// Controller state.
controller_gid: i32,
controller_connected: bool,
} }
// AO: lets me load this at compile time! // AO: lets me load this at compile time!
@@ -48,14 +63,18 @@ gamepad_compat := #load("../gamecontrollerdb.txt", string)
// Game Lifecycle // Game Lifecycle
// ------------------------------------------------------------------------------ // ------------------------------------------------------------------------------
game_init :: proc(game: ^Game) { game_init :: proc(game: ^Game) {
rl.SetConfigFlags({.WINDOW_ALWAYS_RUN, .MSAA_4X_HINT}) rl.SetConfigFlags({.WINDOW_ALWAYS_RUN, .MSAA_4X_HINT, .WINDOW_RESIZABLE})
game.res_index = 2 game.controller_gid = -1
game.res_index = 0
game.window_w = RESOLUTIONS[game.res_index].width game.window_w = RESOLUTIONS[game.res_index].width
game.window_h = RESOLUTIONS[game.res_index].height game.window_h = RESOLUTIONS[game.res_index].height
rl.InitWindow(game.window_w, game.window_h, WINDOW_TITLE) rl.InitWindow(game.window_w, game.window_h, WINDOW_TITLE)
rl.SetTargetFPS(60) rl.SetTargetFPS(60)
rl.SetExitKey(.KEY_NULL) rl.SetExitKey(.KEY_NULL)
rl.HideCursor()
game_set_resolution(game, game.res_index)
gamepad_cstr := strings.clone_to_cstring(gamepad_compat) gamepad_cstr := strings.clone_to_cstring(gamepad_compat)
defer delete(gamepad_cstr) defer delete(gamepad_cstr)
@@ -79,9 +98,14 @@ game_init :: proc(game: ^Game) {
bg_img := rl.GenImageGradientRadial(VIEWPORT_W, VIEWPORT_H, 0.3, bg_center, bg_corner) bg_img := rl.GenImageGradientRadial(VIEWPORT_W, VIEWPORT_H, 0.3, bg_center, bg_corner)
game.bg_texture = rl.LoadTextureFromImage(bg_img) game.bg_texture = rl.LoadTextureFromImage(bg_img)
rl.UnloadImage(bg_img) // Free CPU memory rl.UnloadImage(bg_img) // Free CPU memory
game.crt_shader = rl.LoadShader(nil, "assets/shaders/crt.fs") game.crt_shader = rl.LoadShader(nil, "assets/shaders/crt.fs")
// some ray gui color updates for now
rl.GuiSetStyle(.SLIDER, i32(rl.GuiControlProperty.BASE_COLOR_NORMAL), 0x00000000)
rl.GuiSetStyle(.SLIDER, i32(rl.GuiControlProperty.BORDER_WIDTH), 0x00000000)
rl.GuiSetStyle(.SLIDER, i32(rl.GuiControlProperty.BASE_COLOR_PRESSED), 0x66BFFFFF)
rl.GuiSetStyle(.SLIDER, i32(rl.GuiControlProperty.TEXT_COLOR_FOCUSED), 0x66BFFFFF)
// Setup 2D camera matching the 640x360 viewport center // Setup 2D camera matching the 640x360 viewport center
game.camera = rl.Camera2D{ game.camera = rl.Camera2D{
target = rl.Vector2{VIEWPORT_W / 2.0, VIEWPORT_H / 2.0}, target = rl.Vector2{VIEWPORT_W / 2.0, VIEWPORT_H / 2.0},
@@ -90,13 +114,14 @@ game_init :: proc(game: ^Game) {
zoom = 1.0, zoom = 1.0,
} }
game.transition = {true, 0, 0.2}
game.should_close = false game.should_close = false
set_root_scene(game, make_main_menu()) set_root_scene(game, make_main_menu())
} }
game_update :: proc(game: ^Game) { game_update :: proc(game: ^Game) {
dt := rl.GetFrameTime() dt := rl.GetFrameTime()
input := poll_input() input := poll_input(game)
// ------------------------------------------------------------------------- // -------------------------------------------------------------------------
// Update Camera Shake // Update Camera Shake
@@ -121,10 +146,15 @@ game_update :: proc(game: ^Game) {
} }
active_scene := get_active_scene(game) active_scene := get_active_scene(game)
if active_scene != nil {
if game.transition.active {
game.transition.elapsed += dt
if game.transition.elapsed >= game.transition.duration do game.transition.active = false
} else if active_scene != nil {
scene_update(active_scene, game, input, dt) scene_update(active_scene, game, input, dt)
} }
// background music buffer streaming // background music buffer streaming
audio_update(&game.audio, dt) audio_update(&game.audio, dt)
@@ -146,13 +176,20 @@ game_render :: proc(game: ^Game) {
for &scene in game.scene_stack { for &scene in game.scene_stack {
scene_render(&scene) scene_render(&scene)
} }
// when game transition active increment its elapsed time
// render will check and display accordingly
if game.transition.active {
a := u8(255 * (1 - clamp(game.transition.elapsed / game.transition.duration, 0, 1)))
rl.DrawRectangle(0, 0, VIEWPORT_W, VIEWPORT_H, {0, 0, 0, a})
}
rl.EndMode2D() rl.EndMode2D()
rl.EndTextureMode() rl.EndTextureMode()
render_game_to_window(game) render_game_to_window(game)
rl.DrawFPS(10, game.window_h - 20) // when DEBUG do rl.DrawFPS(10, rl.GetScreenHeight() - 20)
rl.EndDrawing() rl.EndDrawing()
} }
@@ -183,6 +220,17 @@ game_trigger_shake :: proc(game: ^Game, intensity: f32, duration: f32 = 0.15) {
game_set_resolution :: proc(game: ^Game, res_index: int) { game_set_resolution :: proc(game: ^Game, res_index: int) {
game.res_index = clamp(res_index, 0, len(RESOLUTIONS) - 1) game.res_index = clamp(res_index, 0, len(RESOLUTIONS) - 1)
borderless := rl.IsWindowState({.BORDERLESS_WINDOWED_MODE})
if game.res_index == 0 {
if !borderless do rl.ToggleBorderlessWindowed()
return
}
if borderless {
rl.ToggleBorderlessWindowed()
rl.RestoreWindow()
}
target_w := RESOLUTIONS[game.res_index].width target_w := RESOLUTIONS[game.res_index].width
target_h := RESOLUTIONS[game.res_index].height target_h := RESOLUTIONS[game.res_index].height
@@ -200,13 +248,18 @@ game_set_resolution :: proc(game: ^Game, res_index: int) {
render_game_to_window :: proc(game: ^Game) { render_game_to_window :: proc(game: ^Game) {
texture := game.viewport.texture texture := game.viewport.texture
w, h := f32(rl.GetScreenWidth()), f32(rl.GetScreenHeight())
s := min(w / VIEWPORT_W, h / VIEWPORT_H)
dst := rl.Rectangle{(w - VIEWPORT_W*s)/2, (h - VIEWPORT_H*s)/2, VIEWPORT_W*s, VIEWPORT_H*s}
rl.BeginShaderMode(game.crt_shader) rl.BeginShaderMode(game.crt_shader)
rl.DrawTexturePro( rl.DrawTexturePro(
texture, texture,
// src: inverted y coordinate flips opengl texture buffer correctly // src: inverted y coordinate flips opengl texture buffer correctly
rl.Rectangle{0, 0, f32(texture.width), -f32(texture.height)}, rl.Rectangle{0, 0, f32(texture.width), -f32(texture.height)},
// dst: scales viewport canvas to fill window resolution // dst: scales viewport canvas to fill window resolution
rl.Rectangle{0, 0, f32(game.window_w), f32(game.window_h)}, // rl.Rectangle{0, 0, f32(game.window_w), f32(game.window_h)},
dst,
rl.Vector2{}, rl.Vector2{},
0, 0,
rl.WHITE, rl.WHITE,
+19 -13
View File
@@ -1,5 +1,6 @@
package game package game
import "core:strings"
import rl "vendor:raylib" import rl "vendor:raylib"
// ------------------------------------------------------------------------------ // ------------------------------------------------------------------------------
@@ -21,35 +22,40 @@ Input_Event :: struct {
} }
// Currently active controller // Currently active controller
active_gamepad_id: i32 = -1 is_real_pad :: proc(i: i32) -> bool {
return rl.IsGamepadAvailable(i) &&
!strings.contains(string(rl.GetGamepadName(i)), "Mouse") &&
!strings.contains(string(rl.GetGamepadName(i)), "Keyboard")
}
update_active_gamepad :: proc() { update_active_gamepad :: proc(game: ^Game) {
// is there currently a controller, if so check if its still connected. // is there currently a controller, if so check if its still connected.
if active_gamepad_id != -1 { if game.controller_gid != -1 {
if rl.IsGamepadAvailable(active_gamepad_id) { if rl.IsGamepadAvailable(game.controller_gid) {
// do nothing still connected // do nothing still connected
return return
} }
// else we lost connection // else we lost connection
active_gamepad_id = -1 game.controller_gid = -1
rl.TraceLog(.WARNING, "Controller disconnected!!") game.controller_connected = false
} }
for i: i32 = 0; i < 4; i += 1 { for i: i32 = 0; i < 4; i += 1 {
if rl.IsGamepadAvailable(i) { if rl.IsGamepadAvailable(i) && is_real_pad(i) {
active_gamepad_id = i game.controller_connected = true
game.controller_gid = i
return return
} }
} }
} }
poll_input :: proc() -> Input_Event { poll_input :: proc(game: ^Game) -> Input_Event {
input_event := Input_Event{} input_event := Input_Event{}
input_dir := rl.Vector2{0, 0} input_dir := rl.Vector2{0, 0}
update_active_gamepad() update_active_gamepad(game)
gamepad_active := active_gamepad_id != -1 gamepad_active := game.controller_connected
gid := active_gamepad_id gid := game.controller_gid
// -------------------------------------------------------------------------- // --------------------------------------------------------------------------
// Continuous Input (keyboard + gamepad d-pad) // Continuous Input (keyboard + gamepad d-pad)
@@ -86,7 +92,7 @@ poll_input :: proc() -> Input_Event {
input_event.right_pressed = rl.IsKeyPressed(.D) || rl.IsKeyPressed(.RIGHT) || input_event.right_pressed = rl.IsKeyPressed(.D) || rl.IsKeyPressed(.RIGHT) ||
(gamepad_active && rl.IsGamepadButtonPressed(gid, .LEFT_FACE_RIGHT)) (gamepad_active && rl.IsGamepadButtonPressed(gid, .LEFT_FACE_RIGHT))
input_event.confirm_pressed = rl.IsKeyPressed(.ENTER) || rl.IsKeyPressed(.SPACE) || input_event.confirm_pressed = rl.IsKeyPressed(.ENTER) ||
(gamepad_active && rl.IsGamepadButtonPressed(gid, .RIGHT_FACE_DOWN)) (gamepad_active && rl.IsGamepadButtonPressed(gid, .RIGHT_FACE_DOWN))
input_event.pause_pressed = rl.IsKeyPressed(.SPACE) || input_event.pause_pressed = rl.IsKeyPressed(.SPACE) ||
+1 -1
View File
@@ -120,7 +120,7 @@ render_hud :: proc(level: Level) {
p1_size := rl.MeasureTextEx(font, "PLAYER 1", HUD_FONT_SIZE, HUD_FONT_SPACING) p1_size := rl.MeasureTextEx(font, "PLAYER 1", HUD_FONT_SIZE, HUD_FONT_SPACING)
p1_pos := rl.Vector2{HUD_PADDING, HUD_PADDING} p1_pos := rl.Vector2{HUD_PADDING, HUD_PADDING}
rl.DrawTextEx(font, "PLAYER 1", p1_pos, HUD_FONT_SIZE, HUD_FONT_SPACING, rl.WHITE) rl.DrawTextEx(font, "PLAYER", p1_pos, HUD_FONT_SIZE, HUD_FONT_SPACING, rl.WHITE)
opp_size := rl.MeasureTextEx(font, "COMPUTER", HUD_FONT_SIZE, HUD_FONT_SPACING) opp_size := rl.MeasureTextEx(font, "COMPUTER", HUD_FONT_SIZE, HUD_FONT_SPACING)
opp_pos := rl.Vector2{f32(VIEWPORT_W) - HUD_PADDING - opp_size.x, HUD_PADDING} opp_pos := rl.Vector2{f32(VIEWPORT_W) - HUD_PADDING - opp_size.x, HUD_PADDING}
+4 -4
View File
@@ -177,8 +177,8 @@ list_menu_items :: proc(canvas: Canvas, menu: UI_Menu) {
item_rec := get_canvas_item_rec(canvas, i, top_margin = UI_TOP_MARGIN_LIST) item_rec := get_canvas_item_rec(canvas, i, top_margin = UI_TOP_MARGIN_LIST)
is_selected := (i == menu.selected_index) is_selected := (i == menu.selected_index)
border_color := is_selected ? rl.ORANGE : rl.DARKGRAY border_color := is_selected ? rl.SKYBLUE : rl.DARKGRAY
text_color := is_selected ? rl.ORANGE : rl.WHITE text_color := is_selected ? rl.SKYBLUE : rl.WHITE
rl.DrawRectangleLinesEx(item_rec, UI_ITEM_BORDER_THICK, border_color) rl.DrawRectangleLinesEx(item_rec, UI_ITEM_BORDER_THICK, border_color)
@@ -191,7 +191,7 @@ list_menu_items :: proc(canvas: Canvas, menu: UI_Menu) {
case UI_Slider: case UI_Slider:
slider_rec := item_rec slider_rec := item_rec
text_width := rl.MeasureText(v.label, UI_LABEL_FONT_SIZE) text_width := 31
slider_rec.x = slider_rec.x + f32(text_width * 2) slider_rec.x = slider_rec.x + f32(text_width * 2)
slider_rec.width = (slider_rec.width - f32(text_width * 2)) slider_rec.width = (slider_rec.width - f32(text_width * 2))
@@ -250,7 +250,7 @@ render_main_menu :: proc(scene: Scene) {
menu, menu_ok := canvas.menu.? menu, menu_ok := canvas.menu.?
assert(menu_ok, "render_main_menu: Canvas is missing a UI_Menu component") assert(menu_ok, "render_main_menu: Canvas is missing a UI_Menu component")
t_text: cstring = "PONG" t_text: cstring = WINDOW_TITLE
t_width := rl.MeasureText(t_text, UI_TITLE_FONT_SIZE) t_width := rl.MeasureText(t_text, UI_TITLE_FONT_SIZE)
t_x := i32(canvas.bounds.width * 0.5) - (t_width / 2) t_x := i32(canvas.bounds.width * 0.5) - (t_width / 2)
rl.DrawText(t_text, t_x, i32(canvas.padding), UI_TITLE_FONT_SIZE, rl.WHITE) rl.DrawText(t_text, t_x, i32(canvas.padding), UI_TITLE_FONT_SIZE, rl.WHITE)