|
| 1 | +#include <android/asset_manager_jni.h> |
| 2 | +#include <android/log.h> |
| 3 | +#include <game-activity/native_app_glue/android_native_app_glue.h> |
| 4 | + |
| 5 | +#include <iostream> |
| 6 | +#include <random> |
| 7 | + |
| 8 | +// clang-format off |
| 9 | +#include "src/vulkan_wrapper.h" |
| 10 | +#include "src/app.h" |
| 11 | +// clang-format on |
| 12 | + |
| 13 | +revector::App *app; |
| 14 | + |
| 15 | +constexpr bool USE_VULKAN = false; |
| 16 | +bool vulkan_initialized = false; |
| 17 | + |
| 18 | +constexpr int DPI_STANDARD = 96; |
| 19 | + |
| 20 | +using namespace revector; |
| 21 | + |
| 22 | +using Pathfinder::Vec2; |
| 23 | +using Pathfinder::Vec3; |
| 24 | + |
| 25 | +class MyNode : public Node { |
| 26 | + std::shared_ptr<ToggleButtonGroup> button_group; |
| 27 | + |
| 28 | + void custom_ready() override { |
| 29 | + auto vbox_container = std::make_shared<VBoxContainer>(); |
| 30 | + vbox_container->set_separation(8); |
| 31 | + vbox_container->set_position({10, 10}); |
| 32 | + add_child(vbox_container); |
| 33 | + |
| 34 | + { |
| 35 | + auto button = std::make_shared<Button>(); |
| 36 | + button->container_sizing.flag_h = ContainerSizingFlag::ShrinkStart; |
| 37 | + vbox_container->add_child(button); |
| 38 | + |
| 39 | + auto callback = []() { Logger::info("Button triggered"); }; |
| 40 | + button->connect_signal("triggered", callback); |
| 41 | + } |
| 42 | + |
| 43 | + { |
| 44 | + auto button = std::make_shared<Button>(); |
| 45 | + button->set_icon_normal(std::make_shared<VectorImage>(get_asset_dir("icons/Node_Button.svg"))); |
| 46 | + button->container_sizing.flag_h = ContainerSizingFlag::ShrinkStart; |
| 47 | + vbox_container->add_child(button); |
| 48 | + } |
| 49 | + |
| 50 | + { |
| 51 | + auto check_button = std::make_shared<CheckButton>(); |
| 52 | + check_button->container_sizing.flag_h = ContainerSizingFlag::ShrinkStart; |
| 53 | + vbox_container->add_child(check_button); |
| 54 | + |
| 55 | + auto callback = [](bool toggled) { Logger::info("Button toggled"); }; |
| 56 | + check_button->connect_signal("toggled", callback); |
| 57 | + } |
| 58 | + |
| 59 | + { |
| 60 | + auto container_group = std::make_shared<VBoxContainer>(); |
| 61 | + container_group->set_separation(8); |
| 62 | + container_group->set_position({10, 200}); |
| 63 | + add_child(container_group); |
| 64 | + |
| 65 | + auto label = std::make_shared<Label>(); |
| 66 | + label->set_text("Toggle Button Group"); |
| 67 | + container_group->add_child(label); |
| 68 | + |
| 69 | + button_group = std::make_shared<ToggleButtonGroup>(); |
| 70 | + |
| 71 | + for (int i = 0; i < 3; ++i) { |
| 72 | + auto check_button = std::make_shared<RadioButton>(); |
| 73 | + check_button->container_sizing.flag_h = ContainerSizingFlag::ShrinkStart; |
| 74 | + container_group->add_child(check_button); |
| 75 | + button_group->add_button(check_button); |
| 76 | + } |
| 77 | + } |
| 78 | + } |
| 79 | +}; |
| 80 | + |
| 81 | +// Process the next main command. |
| 82 | +void handle_cmd(android_app *app_ctx, int32_t cmd) { |
| 83 | + switch (cmd) { |
| 84 | + case APP_CMD_START: |
| 85 | + __android_log_print(ANDROID_LOG_INFO, "revector", "APP_CMD_START"); |
| 86 | + break; |
| 87 | + case APP_CMD_RESUME: |
| 88 | + __android_log_print(ANDROID_LOG_INFO, "revector", "APP_CMD_RESUME"); |
| 89 | + break; |
| 90 | + case APP_CMD_PAUSE: |
| 91 | + __android_log_print(ANDROID_LOG_INFO, "revector", "APP_CMD_PAUSE"); |
| 92 | + break; |
| 93 | + case APP_CMD_STOP: |
| 94 | + __android_log_print(ANDROID_LOG_INFO, "revector", "APP_CMD_STOP"); |
| 95 | + break; |
| 96 | + case APP_CMD_DESTROY: |
| 97 | + __android_log_print(ANDROID_LOG_INFO, "revector", "APP_CMD_DESTROY"); |
| 98 | + break; |
| 99 | + case APP_CMD_INIT_WINDOW: { |
| 100 | + __android_log_print(ANDROID_LOG_INFO, "revector", "APP_CMD_INIT_WINDOW"); |
| 101 | + |
| 102 | + AConfiguration *config = AConfiguration_new(); |
| 103 | + AConfiguration_fromAssetManager(config, app_ctx->activity->assetManager); |
| 104 | + int32_t dpi = AConfiguration_getDensity(config); |
| 105 | + float dpi_scale = (float)dpi / DPI_STANDARD; |
| 106 | + AConfiguration_delete(config); |
| 107 | + __android_log_print(ANDROID_LOG_INFO, "revector", "DPI: %d, Expected scale factor: %.1f", dpi, dpi_scale); |
| 108 | + |
| 109 | + auto window_size = |
| 110 | + Pathfinder::Vec2I(ANativeWindow_getWidth(app_ctx->window), ANativeWindow_getHeight(app_ctx->window)); |
| 111 | + |
| 112 | + app = new revector::App(app_ctx->window, app_ctx->activity->assetManager, window_size, true, USE_VULKAN); |
| 113 | + app->set_custom_scaling_factor(dpi_scale); |
| 114 | + |
| 115 | + app->get_tree_root()->add_child(std::make_shared<MyNode>()); |
| 116 | + } break; |
| 117 | + case APP_CMD_TERM_WINDOW: { |
| 118 | + __android_log_print(ANDROID_LOG_INFO, "revector", "APP_CMD_TERM_WINDOW"); |
| 119 | + |
| 120 | + app->single_run_cleanup(); |
| 121 | + |
| 122 | + // The window is being hidden or closed or rotated, clean it up. |
| 123 | + delete app; |
| 124 | + app = nullptr; |
| 125 | + } break; |
| 126 | + default: |
| 127 | + __android_log_print(ANDROID_LOG_INFO, "revector", "Event not handled: %d", cmd); |
| 128 | + } |
| 129 | +} |
| 130 | + |
| 131 | +void handle_motion_event(GameActivityMotionEvent *event) { |
| 132 | + if (!app) { |
| 133 | + return; |
| 134 | + } |
| 135 | + |
| 136 | + int32_t actionMasked = event->action & AMOTION_EVENT_ACTION_MASK; |
| 137 | + int32_t pointerIndex = 0; |
| 138 | + |
| 139 | + // Only get pointer index for specific actions (DOWN/UP) |
| 140 | + if (actionMasked == AMOTION_EVENT_ACTION_POINTER_DOWN || actionMasked == AMOTION_EVENT_ACTION_POINTER_UP) { |
| 141 | + pointerIndex = |
| 142 | + (event->action & AMOTION_EVENT_ACTION_POINTER_INDEX_MASK) >> AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT; |
| 143 | + } |
| 144 | + |
| 145 | + // Get X/Y coordinates for the affected pointer |
| 146 | + float x = GameActivityPointerAxes_getAxisValue(&event->pointers[pointerIndex], AMOTION_EVENT_AXIS_X); |
| 147 | + float y = GameActivityPointerAxes_getAxisValue(&event->pointers[pointerIndex], AMOTION_EVENT_AXIS_Y); |
| 148 | + |
| 149 | + float x_pos = x / app->get_scaling_factor(); |
| 150 | + float y_pos = y / app->get_scaling_factor(); |
| 151 | + |
| 152 | + auto input_server = InputServer::get_singleton(); |
| 153 | + |
| 154 | + switch (actionMasked) { |
| 155 | + case AMOTION_EVENT_ACTION_DOWN: { |
| 156 | + InputEvent input_event{}; |
| 157 | + input_event.type = InputEventType::MouseButton; |
| 158 | + input_event.args.mouse_button.button = 0; |
| 159 | + input_event.args.mouse_button.pressed = true; |
| 160 | + input_event.args.mouse_button.position = {(float)x_pos, (float)y_pos}; |
| 161 | + input_server->input_queue.push_back(input_event); |
| 162 | + input_server->cursor_position = {(float)x_pos, (float)y_pos}; |
| 163 | + } break; |
| 164 | + case AMOTION_EVENT_ACTION_UP: { |
| 165 | + InputEvent input_event{}; |
| 166 | + input_event.type = InputEventType::MouseButton; |
| 167 | + input_event.args.mouse_button.button = 0; |
| 168 | + input_event.args.mouse_button.pressed = false; |
| 169 | + input_event.args.mouse_button.position = {(float)x_pos, (float)y_pos}; |
| 170 | + input_server->input_queue.push_back(input_event); |
| 171 | + |
| 172 | + input_server->cursor_position = {(float)x_pos, (float)y_pos}; |
| 173 | + } break; |
| 174 | + case AMOTION_EVENT_ACTION_HOVER_MOVE: |
| 175 | + case AMOTION_EVENT_ACTION_MOVE: { |
| 176 | + InputEvent input_event{}; |
| 177 | + input_event.type = InputEventType::MouseMotion; |
| 178 | + input_event.args.mouse_motion.position = {(float)x_pos, (float)y_pos}; |
| 179 | + input_server->last_cursor_position = input_server->cursor_position; |
| 180 | + |
| 181 | + input_server->cursor_position = {(float)x_pos, (float)y_pos}; |
| 182 | + |
| 183 | + input_event.args.mouse_motion.relative = input_server->cursor_position - input_server->last_cursor_position; |
| 184 | + input_server->input_queue.push_back(input_event); |
| 185 | + } break; |
| 186 | + case AMOTION_EVENT_ACTION_SCROLL: { |
| 187 | + __android_log_print(ANDROID_LOG_INFO, "Pathfinder", "INPUT: SCROLL (%.1f, %.1f)", x, y); |
| 188 | + } break; |
| 189 | + } |
| 190 | +} |
| 191 | + |
| 192 | +void android_main(struct android_app *app_ctx) { |
| 193 | + // Set the callback to process system events. |
| 194 | + app_ctx->onAppCmd = handle_cmd; |
| 195 | + |
| 196 | + // In NativeActivity, you would do this: app->onInputEvent = engine_handle_input; |
| 197 | + // In GameActivity, you should NOT do this. The field is unused/removed for input. |
| 198 | + |
| 199 | + // It's also recommended to nullify any filters to ensure all input is received: |
| 200 | + android_app_set_key_event_filter(app_ctx, nullptr); |
| 201 | + android_app_set_motion_event_filter(app_ctx, nullptr); |
| 202 | + |
| 203 | + if (USE_VULKAN && !vulkan_initialized) { |
| 204 | + InitVulkan(); |
| 205 | + vulkan_initialized = true; |
| 206 | + } |
| 207 | + |
| 208 | + // Used to poll the events in the main loop. |
| 209 | + int events; |
| 210 | + android_poll_source *source; |
| 211 | + |
| 212 | + // Main loop. |
| 213 | + do { |
| 214 | + if (ALooper_pollOnce(0, nullptr, &events, (void **)&source) >= 0) { |
| 215 | + if (source != nullptr) { |
| 216 | + source->process(app_ctx, source); |
| 217 | + } |
| 218 | + } |
| 219 | + |
| 220 | + // 2. Get the current input buffer |
| 221 | + android_input_buffer *inputBuffer = android_app_swap_input_buffers(app_ctx); |
| 222 | + |
| 223 | + // 3. Process Motion Events (Touch/Controller Analog) |
| 224 | + if (inputBuffer && inputBuffer->motionEventsCount) { |
| 225 | + for (int i = 0; i < inputBuffer->motionEventsCount; i++) { |
| 226 | + // Process inputBuffer->motionEvents[i] (a GameActivityMotionEvent*) |
| 227 | + handle_motion_event(&inputBuffer->motionEvents[i]); |
| 228 | + } |
| 229 | + // Clear the motion events queue for the next frame |
| 230 | + android_app_clear_motion_events(inputBuffer); |
| 231 | + } |
| 232 | + |
| 233 | + if (app) { |
| 234 | + app->single_run(); |
| 235 | + } |
| 236 | + } while (app_ctx->destroyRequested == 0); |
| 237 | +} |
0 commit comments