Restructure into themed screens with flag card backs, real font, flip animation and sound
- Replace the bogus DroidSansJapanese.ttf (an HTML page) with M PLUS Rounded 1c, loaded with LoadFontEx and explicit kana codepoints - Theme struct per language with flag-based card backs (Hinomaru, Taegukgi, Trairong) - Card art rendered to textures so the flip squashes the whole card - Synthesized flip/match/mismatch/win/click sounds (new audio package) - Shared ui package for text and buttons; Screen enum replaces i32 ids - Kana <-> romaji pairs drawn from all 46 basic hiragana Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
5557de62a3
commit
5806f0498c
11 files changed
+1046
-425
No files matched your search
+88
-90
@@ -1,113 +1,111 @@
|
||||
package screens
|
||||
|
||||
import "core:math"
|
||||
import raylib "vendor:raylib"
|
||||
import card "../card"
|
||||
import themes "../themes"
|
||||
import assets "../assets"
|
||||
import ui "../ui"
|
||||
|
||||
draw_button :: proc(x, y, w, h: i32, text: cstring, base_color, hover_color: raylib.Color, mouse_x, mouse_y: i32) -> bool {
|
||||
is_hovered := mouse_x >= x && mouse_x <= x + w && mouse_y >= y && mouse_y <= y + h
|
||||
color := base_color
|
||||
if is_hovered {
|
||||
color = hover_color
|
||||
}
|
||||
raylib.DrawRectangle(x, y, w, h, color)
|
||||
raylib.DrawRectangleLines(x, y, w, h, themes.BLACK)
|
||||
font := assets.get_font()
|
||||
text_w := raylib.MeasureText(text, themes.BUTTON_FONT_SIZE)
|
||||
text_x := x + (w - text_w) / 2
|
||||
text_y := y + (h - themes.BUTTON_FONT_SIZE) / 2
|
||||
raylib.DrawTextEx(font, text, raylib.Vector2{f32(text_x), f32(text_y)}, f32(themes.BUTTON_FONT_SIZE), 0, themes.WHITE)
|
||||
return is_hovered && raylib.IsMouseButtonPressed(raylib.MouseButton.LEFT)
|
||||
LANGUAGES :: [3]struct{ theme: themes.Theme, screen: themes.Screen }{
|
||||
{themes.JAPAN, .JAPANESE_MENU},
|
||||
{themes.KOREA, .KOREAN_MENU},
|
||||
{themes.THAI, .THAI_MENU},
|
||||
}
|
||||
|
||||
draw_title :: proc(text: cstring, y: i32, color: raylib.Color) {
|
||||
font := assets.get_font()
|
||||
text_w := raylib.MeasureText(text, themes.TITLE_FONT_SIZE)
|
||||
x := (themes.SCREEN_WIDTH - text_w) / 2
|
||||
raylib.DrawTextEx(font, text, raylib.Vector2{f32(x + 4), f32(y + 4)}, f32(themes.TITLE_FONT_SIZE), 0, themes.BLACK)
|
||||
raylib.DrawTextEx(font, text, raylib.Vector2{f32(x), f32(y)}, f32(themes.TITLE_FONT_SIZE), 0, color)
|
||||
// Draws a card back like a playing card lying on the table
|
||||
@(private)
|
||||
draw_card_art :: proc(flag: themes.Flag, rect: raylib.Rectangle, rotation: f32 = 0) {
|
||||
texture := card.get_back_art(flag)
|
||||
source := raylib.Rectangle{0, 0, f32(texture.width), -f32(texture.height)}
|
||||
dest := raylib.Rectangle{rect.x + rect.width / 2, rect.y + rect.height / 2, rect.width, rect.height}
|
||||
origin := raylib.Vector2{rect.width / 2, rect.height / 2}
|
||||
shadow := dest
|
||||
shadow.x += 3
|
||||
shadow.y += 6
|
||||
raylib.DrawRectanglePro(shadow, origin, rotation, raylib.Fade(themes.BLACK, 0.15))
|
||||
raylib.DrawTexturePro(texture, source, dest, origin, rotation, themes.WHITE)
|
||||
}
|
||||
|
||||
draw_centered_text :: proc(text: cstring, y: i32, font_size: i32, color: raylib.Color) {
|
||||
font := assets.get_font()
|
||||
text_w := raylib.MeasureText(text, font_size)
|
||||
x := (themes.SCREEN_WIDTH - text_w) / 2
|
||||
raylib.DrawTextEx(font, text, raylib.Vector2{f32(x + 4), f32(y + 4)}, f32(font_size), 0, themes.BLACK)
|
||||
raylib.DrawTextEx(font, text, raylib.Vector2{f32(x), f32(y)}, f32(font_size), 0, color)
|
||||
main_menu :: proc() -> themes.Screen {
|
||||
next := themes.Screen.MAIN_MENU
|
||||
raylib.ClearBackground(themes.MENU_BACKGROUND)
|
||||
|
||||
// Thin stripes along the top in each language's primary color
|
||||
stripe_w: i32 = themes.SCREEN_WIDTH / 3
|
||||
for lang, i in LANGUAGES {
|
||||
raylib.DrawRectangle(i32(i) * stripe_w, 0, stripe_w, 8, lang.theme.primary)
|
||||
}
|
||||
|
||||
ui.draw_title("HanjaKanaMun", 70, themes.SUMI_INK, 84)
|
||||
ui.draw_text_centered("Learn Japanese, Korean and Thai scripts", themes.SCREEN_WIDTH / 2, 170, themes.SUBTITLE_FONT_SIZE, themes.DARK_GRAY)
|
||||
|
||||
tile_w: f32 = 150
|
||||
tile_h: f32 = 200
|
||||
gap: f32 = 110
|
||||
total := 3 * tile_w + 2 * gap
|
||||
start_x := (themes.SCREEN_WIDTH - total) / 2
|
||||
// Gentle bobbing so the menu feels alive
|
||||
time := f32(raylib.GetTime())
|
||||
for lang, i in LANGUAGES {
|
||||
x := start_x + f32(i) * (tile_w + gap)
|
||||
bob := math.sin(time * 1.6 + f32(i) * 1.1) * 4
|
||||
rotation := f32(i - 1) * 4
|
||||
draw_card_art(lang.theme.flag, {x, 250 + bob, tile_w, tile_h}, rotation)
|
||||
button_rect := raylib.Rectangle{x + tile_w / 2 - themes.BUTTON_WIDTH / 2 + 30, 500, themes.BUTTON_WIDTH - 60, themes.BUTTON_HEIGHT}
|
||||
if ui.button(button_rect, lang.theme.name, lang.theme.primary) {
|
||||
next = lang.screen
|
||||
}
|
||||
}
|
||||
|
||||
ui.draw_text_centered("Esc to go back · Esc on this screen quits", themes.SCREEN_WIDTH / 2, 660, 18, raylib.Fade(themes.DARK_GRAY, 0.7))
|
||||
return next
|
||||
}
|
||||
|
||||
draw_main_menu :: proc(mouse_x, mouse_y: i32) -> i32 {
|
||||
raylib.ClearBackground(themes.WHITE)
|
||||
|
||||
draw_title("HanjaKanaMun", 100, themes.JAPAN_PRIMARY)
|
||||
draw_centered_text("Learn Japanese, Korean, and Thai", 180, themes.SUBTITLE_FONT_SIZE, themes.DARK_GRAY)
|
||||
|
||||
button_w := themes.BUTTON_WIDTH
|
||||
button_h := themes.BUTTON_HEIGHT
|
||||
button_space: i32 = 40
|
||||
total_w := button_w * 3 + button_space * 2
|
||||
start_x := (themes.SCREEN_WIDTH - total_w) / 2
|
||||
start_y := themes.SCREEN_HEIGHT / 2 + 100
|
||||
|
||||
if draw_button(start_x, start_y, button_w, button_h, "Japanese", themes.JAPAN_PRIMARY, themes.JAPAN_ACCENT, mouse_x, mouse_y) {
|
||||
return themes.JAPANESE_SCREEN
|
||||
// Shared layout for a language's menu screen. Returns the clicked option index, -1 for back, or nil.
|
||||
@(private)
|
||||
language_menu :: proc(theme: themes.Theme, subtitle: cstring, options: []cstring) -> Maybe(int) {
|
||||
raylib.ClearBackground(theme.background)
|
||||
raylib.DrawRectangle(0, 0, themes.SCREEN_WIDTH, 8, theme.primary)
|
||||
|
||||
ui.draw_title(theme.name, 70, theme.primary)
|
||||
ui.draw_text_centered(subtitle, themes.SCREEN_WIDTH / 2, 170, themes.SUBTITLE_FONT_SIZE, theme.ink)
|
||||
|
||||
// A small fan of card backs
|
||||
for i in 0 ..< 3 {
|
||||
offset := f32(i - 1)
|
||||
draw_card_art(theme.flag, {themes.SCREEN_WIDTH / 2 - 55 + offset * 70, 235 + abs(offset) * 10, 110, 146}, offset * 9)
|
||||
}
|
||||
|
||||
if draw_button(start_x + button_w + button_space, start_y, button_w, button_h, "Korean", themes.KOREA_PRIMARY, themes.KOREA_ACCENT, mouse_x, mouse_y) {
|
||||
return themes.KOREAN_SCREEN
|
||||
|
||||
result: Maybe(int)
|
||||
y: f32 = 440
|
||||
for option, i in options {
|
||||
if ui.button({(themes.SCREEN_WIDTH - themes.BUTTON_WIDTH) / 2, y, themes.BUTTON_WIDTH, themes.BUTTON_HEIGHT}, option, theme.primary) {
|
||||
result = i
|
||||
}
|
||||
y += themes.BUTTON_HEIGHT + 20
|
||||
}
|
||||
|
||||
if draw_button(start_x + i32(button_w) + i32(button_space) + i32(button_w) + i32(button_space), start_y, button_w, button_h, "Thai", themes.THAI_PRIMARY, themes.THAI_ACCENT, mouse_x, mouse_y) {
|
||||
return themes.THAI_SCREEN
|
||||
if ui.button({(themes.SCREEN_WIDTH - themes.BUTTON_WIDTH) / 2, y, themes.BUTTON_WIDTH, themes.BUTTON_HEIGHT}, "Back", theme.ink) {
|
||||
result = -1
|
||||
}
|
||||
|
||||
return themes.MAIN_MENU
|
||||
return result
|
||||
}
|
||||
|
||||
draw_japanese_screen :: proc(mouse_x, mouse_y: i32) -> i32 {
|
||||
raylib.ClearBackground(themes.WHITE)
|
||||
|
||||
draw_title("Japanese", 100, themes.JAPAN_PRIMARY)
|
||||
draw_centered_text("Select a game to play", 180, themes.SUBTITLE_FONT_SIZE, themes.DARK_GRAY)
|
||||
|
||||
button_w := themes.BUTTON_WIDTH
|
||||
button_h := themes.BUTTON_HEIGHT
|
||||
button_space := 40
|
||||
start_y := themes.SCREEN_HEIGHT / 2
|
||||
start_x := (themes.SCREEN_WIDTH - button_w) / 2
|
||||
|
||||
if draw_button(start_x, start_y, button_w, button_h, "Memory Game", themes.JAPAN_PRIMARY, themes.JAPAN_ACCENT, mouse_x, mouse_y) {
|
||||
return themes.JAPANESE_MEMORY
|
||||
}
|
||||
|
||||
back_y := i32(start_y) + i32(button_h) + i32(button_space)
|
||||
if draw_button(start_x, back_y, button_w, button_h, "Back", themes.JAPAN_PRIMARY, themes.JAPAN_ACCENT, mouse_x, mouse_y) {
|
||||
return themes.MAIN_MENU
|
||||
}
|
||||
|
||||
return themes.JAPANESE_SCREEN
|
||||
japanese_menu :: proc() -> themes.Screen {
|
||||
choice, clicked := language_menu(themes.JAPAN, "ひらがな · Hiragana", {"Memory Game"}).?
|
||||
if !clicked { return .JAPANESE_MENU }
|
||||
return choice == 0 ? .JAPANESE_MEMORY : .MAIN_MENU
|
||||
}
|
||||
|
||||
draw_korean_screen :: proc(mouse_x, mouse_y: i32) -> i32 {
|
||||
raylib.ClearBackground(themes.WHITE)
|
||||
draw_title("Korean", 100, themes.KOREA_PRIMARY)
|
||||
draw_centered_text("Coming Soon", 180, themes.SUBTITLE_FONT_SIZE, themes.DARK_GRAY)
|
||||
|
||||
if draw_button((themes.SCREEN_WIDTH - themes.BUTTON_WIDTH) / 2, themes.SCREEN_HEIGHT / 2, themes.BUTTON_WIDTH, themes.BUTTON_HEIGHT, "Back", themes.KOREA_PRIMARY, themes.KOREA_ACCENT, mouse_x, mouse_y) {
|
||||
return themes.MAIN_MENU
|
||||
korean_menu :: proc() -> themes.Screen {
|
||||
if _, clicked := language_menu(themes.KOREA, "Hangul · coming soon", {}).?; clicked {
|
||||
return .MAIN_MENU
|
||||
}
|
||||
|
||||
return themes.KOREAN_SCREEN
|
||||
return .KOREAN_MENU
|
||||
}
|
||||
|
||||
draw_thai_screen :: proc(mouse_x, mouse_y: i32) -> i32 {
|
||||
raylib.ClearBackground(themes.WHITE)
|
||||
draw_title("Thai", 100, themes.THAI_PRIMARY)
|
||||
draw_centered_text("Coming Soon", 180, themes.SUBTITLE_FONT_SIZE, themes.DARK_GRAY)
|
||||
|
||||
if draw_button((themes.SCREEN_WIDTH - themes.BUTTON_WIDTH) / 2, themes.SCREEN_HEIGHT / 2, themes.BUTTON_WIDTH, themes.BUTTON_HEIGHT, "Back", themes.THAI_PRIMARY, themes.THAI_ACCENT, mouse_x, mouse_y) {
|
||||
return themes.MAIN_MENU
|
||||
thai_menu :: proc() -> themes.Screen {
|
||||
if _, clicked := language_menu(themes.THAI, "Thai script · coming soon", {}).?; clicked {
|
||||
return .MAIN_MENU
|
||||
}
|
||||
|
||||
return themes.THAI_SCREEN
|
||||
return .THAI_MENU
|
||||
}
|
||||
Reference in new issue
Block a user