// ============================================================================ // INPUT MANAGER IMPLEMENTATION // ============================================================================ // Processes touch and button inputs into InputEvent objects #include "input_manager.h" #include "pico/stdlib.h" #include "board_config.h" #include // External interrupt flags from basic1.cpp extern volatile bool touch_interrupt_flag; extern volatile bool button_key0_pressed; extern volatile bool button_key1_pressed; // GameConfig struct definition (matches basic1.cpp) struct GameConfig { uint32_t touch_debounce_ms; uint32_t button_debounce_ms; bool enable_gestures; bool enable_continuous_draw; bool debug_verbose; }; InputManager::InputManager(LowLevelTouch* touch, const GameConfig* config) : touch(touch), config(config) { } bool InputManager::has_touch() const { return touch != nullptr; } bool InputManager::has_buttons() const { #ifdef BUTTON_KEY0_PIN return true; #else return false; #endif } InputEvent InputManager::process_touch_input(uint32_t* last_time) { InputEvent event = {INPUT_NONE, 0, 0, 0, 0, 0, false}; const uint32_t now = to_ms_since_boot(get_absolute_time()); static constexpr uint32_t ACTIVE_TOUCH_SAMPLE_INTERVAL_MS = 8; // Process immediately on IRQ, and continue sampling while a touch is active. // Some controllers only IRQ on edge transitions, so move events require polling. if (!touch_interrupt_flag && *last_time == 0) { return event; // No touch event } // While a touch session is active, avoid reading touch hardware every loop // iteration. IRQ edges (down/up) still bypass this throttle for responsiveness. if (!touch_interrupt_flag && *last_time != 0) { uint32_t elapsed = now - last_touch_sample_ms; if (elapsed < ACTIVE_TOUCH_SAMPLE_INTERVAL_MS) { return event; } } // Always validate via controller state instead of relying on edge flag alone. // Edge chatter can flip touch_event_down without a real touch transition. TouchData touch_data; if (!touch || !touch->read_touch(&touch_data)) { last_touch_sample_ms = now; touch_interrupt_flag = false; return event; } last_touch_sample_ms = now; touch_interrupt_flag = false; // No active touch points: require consecutive empty reads before release to // avoid false TOUCH_UP events from transient controller jitter. if (touch_data.touch_count == 0) { if (*last_time != 0) { no_touch_samples++; if (no_touch_samples >= 2) { *last_time = 0; last_touch_sample_ms = 0; no_touch_samples = 0; event.type = INPUT_TOUCH_UP; event.valid = true; } } return event; } no_touch_samples = 0; // Debounce touch-down/move updates. if (*last_time != 0 && (now - *last_time) < config->touch_debounce_ms) { return event; } // Populate event from first touch point. event.x = touch_data.points[0].x; event.y = touch_data.points[0].y; event.pressure = touch_data.points[0].pressure; event.gesture_code = touch_data.gesture; event.valid = true; if (*last_time == 0) { event.type = INPUT_TOUCH_DOWN; InputType virtual_type; if (check_virtual_buttons(event.x, event.y, virtual_type)) { event.type = virtual_type; event.button_id = (virtual_type == INPUT_BUTTON_0) ? 0 : 1; if (config->debug_verbose) { printf("Virtual button %d pressed via touch\n", event.button_id); } } } else { event.type = INPUT_TOUCH_MOVE; } if (config->enable_gestures && touch_data.gesture != 0) { event.type = INPUT_GESTURE; if (config->debug_verbose) { printf("Gesture: 0x%02X (%s)\n", event.gesture_code, get_gesture_name(event.gesture_code)); } } *last_time = now; return event; } InputEvent InputManager::process_button_input() { InputEvent event = {INPUT_NONE, 0, 0, 0, 0, 0, false}; #ifdef BUTTON_KEY0_PIN // Check KEY0 if (button_key0_pressed) { button_key0_pressed = false; sleep_ms(config->button_debounce_ms); if (gpio_get(BUTTON_KEY0_PIN) == 0) { // Verify still pressed event.type = INPUT_BUTTON_0; event.button_id = 0; event.valid = true; if (config->debug_verbose) { printf("Button KEY0 action triggered\n"); } return event; } } #ifdef BUTTON_KEY1_PIN // Check KEY1 if (button_key1_pressed) { button_key1_pressed = false; sleep_ms(config->button_debounce_ms); if (gpio_get(BUTTON_KEY1_PIN) == 0) { // Verify still pressed event.type = INPUT_BUTTON_1; event.button_id = 1; event.valid = true; if (config->debug_verbose) { printf("Button KEY1 action triggered\n"); } return event; } } #endif #endif return event; } const char* InputManager::get_gesture_name(uint8_t gesture_code) { switch(gesture_code) { case 0x10: return "Move Up"; case 0x14: return "Move Right"; case 0x18: return "Move Down"; case 0x1C: return "Move Left"; case 0x48: return "Zoom In"; case 0x49: return "Zoom Out"; default: return "Unknown"; } } void InputManager::get_virtual_button_regions(int* a_rect, int* b_rect) const { for (int i = 0; i < 4; i++) { a_rect[i] = v_button_a[i]; b_rect[i] = v_button_b[i]; } } void InputManager::set_virtual_button_regions(int ax, int ay, int aw, int ah, int bx, int by, int bw, int bh) { v_button_a[0] = ax; v_button_a[1] = ay; v_button_a[2] = aw; v_button_a[3] = ah; v_button_b[0] = bx; v_button_b[1] = by; v_button_b[2] = bw; v_button_b[3] = bh; v_buttons_active = true; } void InputManager::clear_virtual_button_regions() { v_buttons_active = false; } bool InputManager::check_virtual_buttons(int16_t x, int16_t y, InputType& out_type) const { if (!v_buttons_active) return false; if (x >= v_button_a[0] && x <= v_button_a[0] + v_button_a[2] && y >= v_button_a[1] && y <= v_button_a[1] + v_button_a[3]) { out_type = INPUT_BUTTON_0; return true; } if (x >= v_button_b[0] && x <= v_button_b[0] + v_button_b[2] && y >= v_button_b[1] && y <= v_button_b[1] + v_button_b[3]) { out_type = INPUT_BUTTON_1; return true; } return false; }