diff --git a/src/scene_manager.odin b/src/scene_manager.odin index b182e08..aa37234 100644 --- a/src/scene_manager.odin +++ b/src/scene_manager.odin @@ -15,23 +15,149 @@ Scene_Type :: enum { Scene :: struct { type: Scene_Type, level: Maybe(Level), - canvas: Maybe(UI_Canvas), - winner_is_player: bool, // Store win state for rendering + canvas: Maybe(Canvas), + winner_is_player: bool, } // ------------------------------------------------------------------------------ // UI Definitions // ------------------------------------------------------------------------------ -UI_Canvas :: struct { - bounds: rl.Rectangle, - padding: f32, - item_height: f32, - item_spaceing: f32, +UI_Menu :: struct { + items: [8]cstring, + count: int, + selected_index: int, +} + +Canvas :: struct { + bounds: rl.Rectangle, + padding: f32, + item_height: f32, + item_spaceing: f32, + menu: Maybe(UI_Menu), +} + +get_canvas_item_rec :: proc(canvas: Canvas, index: int, top_margin: f32 = 120) -> rl.Rectangle { + y_pos := (canvas.bounds.y + top_margin) + (f32(index) * (canvas.item_height + canvas.item_spaceing)) + + return rl.Rectangle{ + x = canvas.bounds.x + canvas.padding, + y = y_pos, + width = canvas.bounds.width - (canvas.padding * 2.0), + height = canvas.item_height + } +} + +render_main_menu :: proc(scene: Scene) { + canvas, ok := scene.canvas.? + // Assert or log error if canvas is unexpectedly missing + if !ok { return } + + // Safely unwrap the embedded menu state + menu, menu_ok := canvas.menu.? + if !menu_ok { return } + + // draw canvas base background + rl.DrawRectangleRec(canvas.bounds, rl.BLACK) + + // draw the menu header + t_text: cstring = "PONG" + t_width := rl.MeasureText(t_text, 40) + t_x := i32(canvas.bounds.width * 0.5) - (t_width / 2) + rl.DrawText(t_text, t_x, i32(canvas.padding), 40, rl.WHITE) + + // Iterate up to menu.count using the data inside UI_Menu + for i in 0 ..< menu.count { + item := menu.items[i] + item_rec := get_canvas_item_rec(canvas, i, top_margin = 150.0) + + text_width := rl.MeasureText(item, 20) + text_x := i32(item_rec.x + (item_rec.width * 0.5)) - (text_width / 2) + text_y := i32(item_rec.y + (item_rec.height * 0.5)) - 10 + + // Highlight selected item box & text + is_selected := (i == menu.selected_index) + border_color := is_selected ? rl.ORANGE : rl.DARKGRAY + text_color := is_selected ? rl.ORANGE : rl.WHITE + + rl.DrawRectangleLinesEx(item_rec, 3, border_color) + rl.DrawText(item, text_x, text_y, 20, text_color) + } +} + +update_main_menu :: proc(scene: ^Scene, input: Input_Event) { + // pointer to Canvas + canvas, canvas_ok := &scene.canvas.? + if !canvas_ok do return + + // pointer to UI_Menu + menu, menu_ok := &canvas.menu.? + if !menu_ok do return + + // get some navigation events + if input.up_pressed { + menu.selected_index = (menu.selected_index - 1 + menu.count) % menu.count + } + if input.down_pressed { + menu.selected_index = (menu.selected_index + 1) % menu.count + } + + if input.confirm_pressed { + switch menu.selected_index { + case 0: // Start Game + emit_event(Event{ type = .Game_Start }) + case 1: // Options + // @todo open options scene/overlay + case 2: // Quit + emit_event(Event{ type = .Game_Quit }) + } + } +} + +// @todo +render_game_over :: proc(scene: Scene) { + rl.DrawRectangleRec( + rl.Rectangle{0, 0, f32(VIEWPORT_W), f32(VIEWPORT_H)}, + rl.Color{0, 0, 0, 200}, + ) + + msg :cstring = scene.winner_is_player ? "YOU WIN!" : "GAME OVER" + text_color := scene.winner_is_player ? rl.GREEN : rl.RED + + rl.DrawText(msg, VIEWPORT_W / 2 - 100, VIEWPORT_H / 2 - 40, 40, text_color) + rl.DrawText("Press P to Play Again", VIEWPORT_W / 2 - 110, VIEWPORT_H / 2 + 10, 20, rl.LIGHTGRAY) +} + +// @todo +render_pause_menu :: proc(scene: Scene) { + rl.DrawRectangleRec( + rl.Rectangle{0, 0, f32(VIEWPORT_W), f32(VIEWPORT_H)}, + rl.Color{0, 0, 0, 140}, + ) + rl.DrawText("PAUSED", VIEWPORT_W / 2 - 80, VIEWPORT_H / 2 - 40, 40, rl.WHITE) + rl.DrawText("Press P to Resume", VIEWPORT_W / 2 - 90, VIEWPORT_H / 2 + 10, 20, rl.LIGHTGRAY) } // ------------------------------------------------------------------------------ // Scene Constructors // ------------------------------------------------------------------------------ +make_main_menu :: proc() -> Scene { + return Scene{ + type = .Main_Menu, + canvas = Canvas{ + bounds = rl.Rectangle{0, 0, VIEWPORT_W, VIEWPORT_H}, + padding = 20, + item_height = 40, + item_spaceing = 15, + menu = UI_Menu{ + items = {0 = "Start Game", 1 = "Options", 2 = "Quit"}, + count = 3, + selected_index = 0, + }, + }, + } +} + +// @todo make_gameplay_scene :: proc() -> Scene { return Scene{ type = .Gameplay, @@ -39,24 +165,19 @@ make_gameplay_scene :: proc() -> Scene { } } +// @todo make_pause_overlay :: proc() -> Scene { return Scene{ type = .Pause_Menu, - canvas = UI_Canvas{}, - } -} - -make_main_menu :: proc() -> Scene { - return Scene{ - type = .Main_Menu, - canvas = UI_Canvas{}, + canvas = Canvas{}, } } +// @todo make_game_over_overlay :: proc(winner_is_player: bool) -> Scene { return Scene{ type = .Game_Over, - canvas = UI_Canvas{}, + canvas = Canvas{}, winner_is_player = winner_is_player, } } @@ -72,6 +193,9 @@ scene_update :: proc(scene: ^Scene, game: ^Game, input: Input_Event, dt: f32) { // dispatch scene behavior #partial switch scene.type { + case .Main_Menu: + update_main_menu(scene, input) + case .Gameplay: if input.pause_pressed { game.overlay_scene = make_pause_overlay() @@ -83,20 +207,27 @@ scene_update :: proc(scene: ^Scene, game: ^Game, input: Input_Event, dt: f32) { } case .Game_Over: - if input.pause_pressed { // Press P or your start key to restart + // Press P or your start key to restart + if input.pause_pressed { game.overlay_scene = nil - game.scene = make_gameplay_scene() // Reset the base level + game.scene = make_gameplay_scene() } - - case .Main_Menu: - // @todo: handle menu selection / start game } // Loop over global frame events for event in GLOBAL_EVENTS { #partial switch event.type { + case .Game_Start: + // clear old state + game.overlay_scene = nil + + // swap to the gameplay scene + // @todo consider different levels here? + game.scene = make_gameplay_scene() case .Game_Over: game.overlay_scene = make_game_over_overlay(event.winner_is_player) + case .Game_Quit: + game.should_close = true } } } @@ -111,27 +242,11 @@ scene_render :: proc(scene: ^Scene) { // render scene/overlay ui elements #partial switch scene.type { case .Pause_Menu: - rl.DrawRectangleRec( - rl.Rectangle{0, 0, f32(VIEWPORT_W), f32(VIEWPORT_H)}, - rl.Color{0, 0, 0, 140}, - ) - rl.DrawText("PAUSED", VIEWPORT_W / 2 - 80, VIEWPORT_H / 2 - 40, 40, rl.WHITE) - rl.DrawText("Press P to Resume", VIEWPORT_W / 2 - 90, VIEWPORT_H / 2 + 10, 20, rl.LIGHTGRAY) - + render_pause_menu(scene^) case .Game_Over: - rl.DrawRectangleRec( - rl.Rectangle{0, 0, f32(VIEWPORT_W), f32(VIEWPORT_H)}, - rl.Color{0, 0, 0, 200}, - ) - - msg :cstring = scene.winner_is_player ? "YOU WIN!" : "GAME OVER" - text_color := scene.winner_is_player ? rl.GREEN : rl.RED - - rl.DrawText(msg, VIEWPORT_W / 2 - 100, VIEWPORT_H / 2 - 40, 40, text_color) - rl.DrawText("Press P to Play Again", VIEWPORT_W / 2 - 110, VIEWPORT_H / 2 + 10, 20, rl.LIGHTGRAY) - + render_game_over(scene^) case .Main_Menu: - rl.DrawText("PONG", VIEWPORT_W / 2 - 50, VIEWPORT_H / 3, 40, rl.WHITE) - rl.DrawText("Press ENTER to Start", VIEWPORT_W / 2 - 110, VIEWPORT_H / 2, 20, rl.LIGHTGRAY) + render_main_menu(scene^) } -} \ No newline at end of file +} +