- decks package: Hiragana, Katakana, Hangul jamo and Thai consonants (Thai readings carry the letter's name word, since sounds repeat) - Per-script fonts: Jua for Hangul, Mali for Thai (both OFL) - Memory game works with any deck; Easy 3x4, Normal 4x6, Hard 4x8 - Language menus pick a deck and difficulty, with number/arrow key shortcuts - Tests cover every deck, difficulty, and font glyph coverage Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
96 lines
3.4 KiB
Odin
96 lines
3.4 KiB
Odin
package ui
|
|
|
|
import raylib "vendor:raylib"
|
|
import assets "../assets"
|
|
import audio "../audio"
|
|
import themes "../themes"
|
|
|
|
Font_Style :: assets.Font_Style
|
|
|
|
TEXT_SPACING :: 1
|
|
|
|
@(private)
|
|
any_hovered: bool
|
|
|
|
// Call at the start of each frame's drawing
|
|
begin_frame :: proc() {
|
|
any_hovered = false
|
|
}
|
|
|
|
// Call at the end of each frame's drawing
|
|
end_frame :: proc() {
|
|
raylib.SetMouseCursor(any_hovered ? .POINTING_HAND : .DEFAULT)
|
|
}
|
|
|
|
// Mark that something clickable is under the mouse (shows the hand cursor)
|
|
set_hovered :: proc() {
|
|
any_hovered = true
|
|
}
|
|
|
|
measure_text :: proc(text: cstring, size: f32, style: Font_Style = .REGULAR) -> raylib.Vector2 {
|
|
return raylib.MeasureTextEx(assets.get_font(style), text, size, TEXT_SPACING)
|
|
}
|
|
|
|
draw_text :: proc(text: cstring, pos: raylib.Vector2, size: f32, color: raylib.Color, style: Font_Style = .REGULAR) {
|
|
raylib.DrawTextEx(assets.get_font(style), text, pos, size, TEXT_SPACING, color)
|
|
}
|
|
|
|
// Draws text horizontally centered on center_x with its top at y
|
|
draw_text_centered :: proc(text: cstring, center_x, y, size: f32, color: raylib.Color,
|
|
style: Font_Style = .REGULAR, shadow := false) {
|
|
w := measure_text(text, size, style).x
|
|
pos := raylib.Vector2{center_x - w / 2, y}
|
|
if shadow {
|
|
draw_text(text, pos + {0, size * 0.05}, size, raylib.Fade(themes.BLACK, 0.18), style)
|
|
}
|
|
draw_text(text, pos, size, color, style)
|
|
}
|
|
|
|
// Draws text centered in a rectangle
|
|
draw_text_in_rect :: proc(text: cstring, rect: raylib.Rectangle, size: f32, color: raylib.Color,
|
|
style: Font_Style = .REGULAR) {
|
|
dims := measure_text(text, size, style)
|
|
pos := raylib.Vector2{rect.x + (rect.width - dims.x) / 2, rect.y + (rect.height - dims.y) / 2}
|
|
draw_text(text, pos, size, color, style)
|
|
}
|
|
|
|
draw_title :: proc(text: cstring, y: f32, color: raylib.Color, size: f32 = themes.TITLE_FONT_SIZE) {
|
|
draw_text_centered(text, themes.SCREEN_WIDTH / 2, y, size, color, style = .BOLD, shadow = true)
|
|
}
|
|
|
|
// A rounded button with hover, press and shadow states. Returns true when clicked
|
|
// or when its hotkey is pressed.
|
|
button :: proc(rect: raylib.Rectangle, text: cstring, color: raylib.Color,
|
|
text_color := themes.WHITE, font_size: f32 = themes.BUTTON_FONT_SIZE,
|
|
hotkey := raylib.KeyboardKey.KEY_NULL, selected := false) -> bool {
|
|
hovered := raylib.CheckCollisionPointRec(raylib.GetMousePosition(), rect)
|
|
pressed := hovered && raylib.IsMouseButtonDown(.LEFT)
|
|
if hovered { set_hovered() }
|
|
|
|
lift: f32 = 0
|
|
fill := color
|
|
if hovered {
|
|
lift = 2
|
|
fill = raylib.ColorBrightness(color, 0.12)
|
|
}
|
|
if pressed {
|
|
lift = -1
|
|
fill = raylib.ColorBrightness(color, -0.08)
|
|
}
|
|
|
|
shadow := raylib.Rectangle{rect.x, rect.y + 4, rect.width, rect.height}
|
|
raylib.DrawRectangleRounded(shadow, 0.4, 12, raylib.Fade(themes.BLACK, 0.18))
|
|
body := raylib.Rectangle{rect.x, rect.y - lift, rect.width, rect.height}
|
|
raylib.DrawRectangleRounded(body, 0.4, 12, fill)
|
|
if selected {
|
|
ring := raylib.Rectangle{body.x - 4, body.y - 4, body.width + 8, body.height + 8}
|
|
raylib.DrawRectangleRoundedLinesEx(ring, 0.4, 12, 3, color)
|
|
}
|
|
draw_text_in_rect(text, body, font_size, text_color, style = .BOLD)
|
|
|
|
clicked := hovered && raylib.IsMouseButtonPressed(.LEFT)
|
|
if hotkey != .KEY_NULL && raylib.IsKeyPressed(hotkey) { clicked = true }
|
|
if clicked { audio.play(.CLICK) }
|
|
return clicked
|
|
}
|