From 93ad8a5cdeb8421bcf02195bc470257935062138 Mon Sep 17 00:00:00 2001 From: Alec Goncharow Date: Wed, 27 May 2026 14:34:32 -0400 Subject: Working widget idea, makings of a maze app Forgot to commit as I went over the weekend, which is fine, not a ton of interesting stuff just yet. --- src/menu_app.jai | 217 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 217 insertions(+) create mode 100644 src/menu_app.jai (limited to 'src/menu_app.jai') diff --git a/src/menu_app.jai b/src/menu_app.jai new file mode 100644 index 0000000..1570a1f --- /dev/null +++ b/src/menu_app.jai @@ -0,0 +1,217 @@ +#import "Basic"; +#import "Math"; + +Button :: struct { + x0, y0, x1, y1: float; + text: string; + alpha: float = 0.4; + scale: float = 1.0; +} + +Menu_Data :: struct { + selection: int; + font: *Simp.Dynamic_Font; + buttons: [..] Button; +} + +init_menu_app :: (widget: *Widget_State) { + data := New(Menu_Data); + data.selection = 0; + data.font = null; + + // Define the buttons up front + array_add(*data.buttons, .{ 100.0, 500.0, 400.0, 600.0, "Example widget", 0.4, 1.0 }); + array_add(*data.buttons, .{ 100.0, 350.0, 400.0, 450.0, "Maze App", 0.4, 1.0 }); + array_add(*data.buttons, .{ 100.0, 200.0, 400.0, 300.0, "Quit", 0.4, 1.0 }); + + widget.data = data; + + widget.init = (widget: *Widget_State) { + data := cast(*Menu_Data) widget.data; + // Load the font + data.font = Simp.get_font_at_size("data", "Anonymous_Pro.ttf", 48); + + // Dynamically resize our buttons so they always fit their text! + if data.font { + for * data.buttons { + text_width := Simp.prepare_text(data.font, it.text); + center_x := (it.x0 + it.x1) / 2.0; + + // Ensure the button is at least as wide as the text + 60px of padding + needed_width := cast(float)text_width + 60.0; + actual_width := max(it.x1 - it.x0, needed_width); + + it.x0 = center_x - actual_width / 2.0; + it.x1 = center_x + actual_width / 2.0; + } + } + }; + + widget.update = update; + widget.handle_input = handle_input; + widget.render = render; + + widget.shutdown = (widget: *Widget_State) { + data := cast(*Menu_Data) widget.data; + array_free(data.buttons); + free(data); + }; +} + +#scope_file + +time_selected := 0.0; + +update :: (widget: *Widget_State, dt: float) { + data := cast(*Menu_Data) widget.data; + time_selected += dt; + debug_add_value("time_selected: %", time_selected); + + for * data.buttons { + // 1. Set our targets + target_scale := ifx data.selection == it_index then 1.05 else 1.0; + + target_alpha: float; + if data.selection == it_index { + // make button GROW + target_alpha = 1.0; + target_scale = target_scale + (sin(cast(float)time_selected * 2.0) * 0.03); + // STOP GROW + if time_selected > (3.14 / 4.0) { + target_scale = 1.08; + } + } else { + // Dimmer when unselected + target_alpha = 0.4; + } + + // 2. Smoothly lerp current values towards the targets using exponential decay + it.scale = it.scale + (target_scale - it.scale) * (15.0 * dt); + it.alpha = it.alpha + (target_alpha - it.alpha) * (15.0 * dt); + } +} + +handle_input :: (widget: *Widget_State, occluded: bool) -> (occludes: bool) { + data := cast(*Menu_Data) widget.data; + start_select := data.selection; + + occludes := false; + if !occluded { + // Query the mouse position! By passing true for right_handed, + // Y=0 is at the bottom, which perfectly matches our rendering projection. + mouse_x, mouse_y, success := get_mouse_pointer_position(window, true); + if success { + mouse_x_f := cast(float) mouse_x; + mouse_y_f := cast(float) mouse_y; + for data.buttons { + if mouse_x_f >= it.x0 && mouse_x_f <= it.x1 && mouse_y_f >= it.y0 && mouse_y_f <= it.y1 { + occludes = true; + data.selection = it_index; + } + } + } + if Input.mouse_delta_x + Input.mouse_delta_y + Input.mouse_delta_z != 0 { + if !occludes { + data.selection = -1; // Deselect if moving mouse outside + } + } + } else { + data.selection = -1; + } + + for event: Input.events_this_frame { + if event.type == .KEYBOARD && event.key_pressed { + if event.key_code == .ARROW_UP { + if data.selection <= 0 { + data.selection = data.buttons.count - 1; + } else { + data.selection -= 1; + } + } + if event.key_code == .ARROW_DOWN { + data.selection += 1; + if data.selection >= data.buttons.count then data.selection = 0; + } + if event.key_code == .ENTER || event.key_code == .MOUSE_BUTTON_LEFT { + // Execute button action + if data.selection == 0 { + switch_app(*example_state); + } else if data.selection == 1 { + switch_app(*maze_state); + } else if data.selection == 2 { + exit(0); + } + } + } + } + + if start_select != data.selection { + time_selected = 0; + } + + return occludes; +} + +render :: (widget: *Widget_State) { + data := cast(*Menu_Data) widget.data; + // Clear to a blueish color to represent the menu + Simp.clear_render_target(0.2, 0.3, 0.4, 1.0); + + Simp.set_shader_for_color(true); + Simp.immediate_begin(); + Simp.immediate_set_2d_projection(window_width, window_height); + + // Iterate through and draw all buttons dynamically based on their struct definition + for data.buttons { + color := ifx data.selection == it_index then Vector4.{0.8, 0.8, 0.2, it.alpha} else Vector4.{0.5, 0.5, 0.5, it.alpha}; + + // Scale the button from its center + center_x := (it.x0 + it.x1) / 2.0; + center_y := (it.y0 + it.y1) / 2.0; + width := it.x1 - it.x0; + height := it.y1 - it.y0; + + draw_x0 := center_x - (width / 2.0) * it.scale; + draw_x1 := center_x + (width / 2.0) * it.scale; + draw_y0 := center_y - (height / 2.0) * it.scale; + draw_y1 := center_y + (height / 2.0) * it.scale; + + Simp.immediate_quad(draw_x0, draw_y0, draw_x1, draw_y1, color); + } + + Simp.immediate_flush(); + + // Draw text on the buttons (perfectly centered!) + if data.font { + for data.buttons { + text_width := Simp.prepare_text(data.font, it.text); + + // Calculate the dead-center of the unscaled button + center_x := (it.x0 + it.x1) / 2.0; + center_y := (it.y0 + it.y1) / 2.0; + + // Calculate text placement (subtract half text width/height) + // Note: Font size is 48, so we subtract ~24 for vertical centering + text_x := center_x - (cast(float)text_width / 2.0); + text_y := center_y - 24.0 + 8.0; // 8.0 is a minor visual baseline tweak + + // 1. Generate the unscaled quads in world space + Simp.generate_quads_for_prepared_text(data.font, cast(int)text_x, cast(int)text_y, 0); + + // 2. Mathematically scale every quad outward from the button's center! + for * quad: data.font.current_quads { + quad.p0.x = center_x + (quad.p0.x - center_x) * it.scale; + quad.p0.y = center_y + (quad.p0.y - center_y) * it.scale; + quad.p1.x = center_x + (quad.p1.x - center_x) * it.scale; + quad.p1.y = center_y + (quad.p1.y - center_y) * it.scale; + quad.p2.x = center_x + (quad.p2.x - center_x) * it.scale; + quad.p2.y = center_y + (quad.p2.y - center_y) * it.scale; + quad.p3.x = center_x + (quad.p3.x - center_x) * it.scale; + quad.p3.y = center_y + (quad.p3.y - center_y) * it.scale; + } + + // 3. Draw the newly scaled quads + Simp.draw_generated_quads(data.font, .{0, 0, 0, it.alpha}); + } + } +} -- cgit v1.2.3-70-g09d2