Skip to content

Instantly share code, notes, and snippets.

@naranyala
Last active July 22, 2025 17:46
Show Gist options
  • Select an option

  • Save naranyala/c974b6158e82ed9120ed0c57e43c815c to your computer and use it in GitHub Desktop.

Select an option

Save naranyala/c974b6158e82ed9120ed0c57e43c815c to your computer and use it in GitHub Desktop.
for the community, a modal dialog implementation of raylib using odin programming
package main
import "core:fmt"
import "core:strings"
import rl "vendor:raylib"
// Package-level variable for animation state
scale: f32 = 0
ui_modal :: proc(
title: string,
message: string,
is_open: ^bool,
buttons: []string,
bounds: rl.Rectangle = {0, 0, 400, 300},
anim_speed: f32 = 0.15,
) -> int {
// Create modifiable copy of bounds
modal_bounds := bounds
// Center if using default position
if modal_bounds.x == 0 && modal_bounds.y == 0 {
modal_bounds.x = (f32(rl.GetScreenWidth()) - modal_bounds.width) / 2
modal_bounds.y = (f32(rl.GetScreenHeight()) - modal_bounds.height) / 2
}
// Animation state
target_scale := is_open^ ? 1.0 : 0.0
scale += (f32(target_scale) - scale) * anim_speed
// Early exit if closed
if scale <= 0.01 && !is_open^ {
return -1
}
mouse := rl.GetMousePosition()
result := -1
// Constants for layout
PADDING :: 20
TITLE_HEIGHT :: 40
BUTTON_HEIGHT :: 40
BUTTON_SPACING :: 10
// Draw overlay
if scale > 0 {
rl.DrawRectangle(
0,
0,
rl.GetScreenWidth(),
rl.GetScreenHeight(),
rl.Color{0, 0, 0, u8(200 * scale)},
)
}
// Draw modal content
if scale > 0.01 {
// Calculate animated bounds
scaled_bounds := modal_bounds
scaled_bounds.width *= scale
scaled_bounds.height *= scale
scaled_bounds.x = modal_bounds.x + (modal_bounds.width - scaled_bounds.width) / 2
scaled_bounds.y = modal_bounds.y + (modal_bounds.height - scaled_bounds.height) / 2
// Main panel
rl.DrawRectangleRec(scaled_bounds, rl.WHITE)
rl.DrawRectangleLinesEx(scaled_bounds, 2, rl.BLACK)
// Title area
title_rect := rl.Rectangle {
scaled_bounds.x + PADDING,
scaled_bounds.y + PADDING,
scaled_bounds.width - 2 * PADDING,
TITLE_HEIGHT,
}
rl.DrawTextEx(
rl.GetFontDefault(),
strings.clone_to_cstring(title),
rl.Vector2{title_rect.x, title_rect.y},
24,
1,
rl.BLACK,
)
// Message area - using DrawTextEx with word wrap
msg_pos := rl.Vector2{scaled_bounds.x + PADDING, scaled_bounds.y + PADDING + TITLE_HEIGHT}
msg_width := scaled_bounds.width - 2 * PADDING
rl.DrawTextEx(
rl.GetFontDefault(),
strings.clone_to_cstring(message),
msg_pos,
20,
1,
rl.DARKGRAY,
)
// Buttons
button_width :=
(scaled_bounds.width - 2 * PADDING - f32(len(buttons) - 1) * BUTTON_SPACING) /
f32(len(buttons))
button_y := scaled_bounds.y + scaled_bounds.height - BUTTON_HEIGHT - PADDING
for button, i in buttons {
button_rect := rl.Rectangle {
scaled_bounds.x + PADDING + f32(i) * (button_width + BUTTON_SPACING),
button_y,
button_width,
BUTTON_HEIGHT,
}
// Button state
hovered := rl.CheckCollisionPointRec(mouse, button_rect)
color := hovered ? rl.SKYBLUE : rl.LIGHTGRAY
// Draw button
rl.DrawRectangleRec(button_rect, color)
rl.DrawRectangleLinesEx(button_rect, 1, rl.BLACK)
// Center text
btn_text := strings.clone_to_cstring(button)
defer delete(btn_text)
text_width := rl.MeasureText(btn_text, 20)
rl.DrawText(
btn_text,
i32(button_rect.x + (button_rect.width - f32(text_width)) / 2),
i32(button_rect.y + BUTTON_HEIGHT / 2 - 10),
20,
rl.BLACK,
)
// Handle click
if hovered && rl.IsMouseButtonReleased(.LEFT) {
result = i
is_open^ = false
}
}
// Close when clicking outside
if rl.IsMouseButtonPressed(.LEFT) && !rl.CheckCollisionPointRec(mouse, scaled_bounds) {
is_open^ = false
}
}
return result
}
main :: proc() {
rl.InitWindow(800, 600, "Modal Demo")
defer rl.CloseWindow()
show_modal := false
last_result := -1
for !rl.WindowShouldClose() {
if rl.IsKeyPressed(.F) {
show_modal = !show_modal
}
rl.BeginDrawing()
defer rl.EndDrawing()
rl.ClearBackground(rl.RAYWHITE)
rl.DrawText("Press F to toggle modal", 20, 20, 20, rl.BLACK)
// Show modal
result := ui_modal(
"Confirm Action",
"Are you sure you want to proceed?\nThis action cannot be undone.\n\nPlease confirm your choice:",
&show_modal,
[]string{"Yes", "No", "Cancel"},
anim_speed = 0.2,
)
if result >= 0 {
last_result = result
}
if last_result >= 0 {
status := fmt.tprintf("Last selection: %d", last_result)
status_cstr := strings.clone_to_cstring(status)
defer delete(status_cstr)
rl.DrawText(status_cstr, 20, 50, 20, rl.BLACK)
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment