aboutsummaryrefslogtreecommitdiff
path: root/src/menu_app.jai
diff options
context:
space:
mode:
Diffstat (limited to 'src/menu_app.jai')
-rw-r--r--src/menu_app.jai217
1 files changed, 217 insertions, 0 deletions
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});
+ }
+ }
+}