function setup_button(button, on_click, label_text, text_color, w, h) { if (label_text != null) { button.add([ text(label_text, { width: w / 1.5, size: 28, }), text_color, pos(w / 2 - (label_text.length * 8), h / 2 - 18) ]); } button.onClick(on_click); button.onHover(() => { button.scale = vec2(1.025, 1.025); setCursor("pointer"); }); button.onHoverEnd(() => { button.scale = vec2(1, 1); setCursor("default"); }); return button; } function create_button(x, y, w, h, label_text, bg, text_color, on_click) { let button = add([ rect(w, h), pos(x, y), bg, area(), ]); return setup_button(button, on_click, label_text, text_color, w, h); } function create_texturebutton(x, y, image_name, on_click) { let button = add([ sprite(image_name), pos(x, y), area(), ]); return setup_button(button, on_click); } function create_sprite(x, y, image_name) { let image_sprite = add([ sprite(image_name), pos(x, y) ]) return image_sprite; } function create_slider(x, y, w, min_val, max_val, initial_val, on_change) { const slider_height = 15; const handle_size = 30; let slider_container = add([ pos(x, y), ]); let track = slider_container.add([ rect(w, slider_height), pos(0, handle_size / 2 - slider_height / 2), color(100, 100, 100), area() ]); let value = initial_val; let handle_x = ((value - min_val) / (max_val - min_val)) * w; let handle = slider_container.add([ rect(handle_size, handle_size), pos(handle_x - handle_size / 2, 0), color(255, 255, 255), area(), "slider_handle" ]); let value_label = slider_container.add([ text(value.toFixed(0), { size: 16 }), pos(w + 10, handle_size / 2 - 8), color(255, 255, 255), ]); let is_dragging = false; handle.onHover(() => { setCursor("pointer"); }); handle.onHoverEnd(() => { if (!is_dragging) { setCursor("default"); } }); handle.onMousePress(() => { is_dragging = true; setCursor("grabbing"); }); onMouseRelease(() => { if (is_dragging) { is_dragging = false; setCursor("default"); } }); onMouseMove(() => { if (is_dragging) { let mouse_x = mousePos().x - slider_container.pos.x; mouse_x = Math.max(0, Math.min(w, mouse_x)); handle.pos.x = mouse_x - handle_size / 2; value = min_val + (mouse_x / w) * (max_val - min_val); value_label.text = value.toFixed(0); if (on_change) { on_change(value); } } }); track.onHover(() => { setCursor("pointer"); }); track.onHoverEnd(() => { setCursor("default"); }); track.onClick(() => { if (!is_dragging) { let mouse_x = mousePos().x - slider_container.pos.x; mouse_x = Math.max(0, Math.min(w, mouse_x)); handle.pos.x = mouse_x - handle_size / 2; value = min_val + (mouse_x / w) * (max_val - min_val); value_label.text = value.toFixed(0); if (on_change) { on_change(value); } } }); track.use(area()); return { obj: slider_container, getValue: () => value, setValue: (new_val) => { value = Math.max(min_val, Math.min(max_val, new_val)); handle_x = ((value - min_val) / (max_val - min_val)) * w; handle.pos.x = handle_x - handle_size / 2; value_label.text = value.toFixed(0); } }; } function create_dropdown(x, y, w, h, options, initial_index, on_select) { let selected_index = initial_index || 0; let is_open = false; let dropdown = add([ pos(x, y), z(10), ]); let selected_box = dropdown.add([ rect(w, h), pos(0, 0), color(60, 60, 60), area(), outline(2, rgb(100, 100, 100)), ]); let selected_text = dropdown.add([ text(options[selected_index], { size: 20 }), pos(10, h / 2 - 10), color(255, 255, 255), ]); let arrow = dropdown.add([ text("▼", { size: 16 }), pos(w - 25, h / 2 - 8), color(200, 200, 200), ]); let options_container = null; let option_items = []; function create_options_menu() { if (options_container) { destroy(options_container); } options_container = dropdown.add([ pos(0, h + 2), z(20), ]); option_items = []; options.forEach((option, index) => { let option_box = options_container.add([ rect(w, h), pos(0, index * h), color(50, 50, 50), area(), outline(1, rgb(80, 80, 80)), ]); let option_text = options_container.add([ text(option, { size: 20 }), pos(10, index * h + h / 2 - 10), color(255, 255, 255), ]); option_box.onHover(() => { option_box.color = rgb(80, 80, 80); setCursor("pointer"); }); option_box.onHoverEnd(() => { option_box.color = rgb(50, 50, 50); setCursor("default"); }); option_box.onClick(() => { selected_index = index; selected_text.text = options[index]; close_dropdown(); if (on_select) { on_select(options[index], index); } }); option_items.push({ box: option_box, text: option_text }); }); } function open_dropdown() { is_open = true; arrow.text = "▲"; create_options_menu(); } function close_dropdown() { is_open = false; arrow.text = "▼"; if (options_container) { destroy(options_container); options_container = null; } } selected_box.onHover(() => { selected_box.color = rgb(70, 70, 70); setCursor("pointer"); }); selected_box.onHoverEnd(() => { selected_box.color = rgb(60, 60, 60); setCursor("default"); }); selected_box.onClick(() => { if (is_open) { close_dropdown(); } else { open_dropdown(); } }); onClick(() => { if (is_open) { let mouse = mousePos(); let in_bounds = mouse.x >= x && mouse.x <= x + w && mouse.y >= y && mouse.y <= y + h + (options.length * h); if (!in_bounds) { close_dropdown(); } } }); return { obj: dropdown, getValue: () => options[selected_index], getIndex: () => selected_index, setIndex: (index) => { if (index >= 0 && index < options.length) { selected_index = index; selected_text.text = options[index]; } }, close: close_dropdown }; } function create_label(x, y, label_text, font_size) { return add([ text(label_text, { size: font_size, }), color(0, 0, 0), pos(x, y) ]) } function horizontal_buttons(start_x, start_y, buttons, width, height, space_between) { for (let i = 0; i < buttons.length; i++) { create_button(start_x + i * (width + space_between), start_y, width, height, buttons[i][0], buttons[i][1], buttons[i][2], buttons[i][3]) } } function vertical_buttons(start_x, start_y, buttons, width, height, space_between) { for (let i = 0; i < buttons.length; i++) { create_button(start_x, start_y + i * (height + space_between), width, height, buttons[i][0], buttons[i][1], buttons[i][2], buttons[i][3]) } } function scene_lambda(scene, args) { return () => { go(scene, args); } }