[UI] Android ImGui touch and mouse input

This commit is contained in:
Triang3l
2022-07-14 21:13:40 +03:00
parent 3a065c35f0
commit 7b8281aee0
11 changed files with 467 additions and 23 deletions

View File

@@ -9,10 +9,16 @@
#include "xenia/ui/window_android.h"
#include <android/input.h>
#include <jni.h>
#include <algorithm>
#include <cstdint>
#include <memory>
#include <utility>
#include "xenia/base/assert.h"
#include "xenia/base/logging.h"
#include "xenia/base/math.h"
#include "xenia/ui/surface_android.h"
#include "xenia/ui/windowed_app_context_android.h"
@@ -55,6 +61,181 @@ void AndroidWindow::OnActivitySurfaceLayoutChange() {
}
}
bool AndroidWindow::OnActivitySurfaceMotionEvent(jobject event) {
auto& android_app_context =
static_cast<const AndroidWindowedAppContext&>(app_context());
JNIEnv* jni_env = android_app_context.ui_thread_jni_env();
const AndroidWindowedAppContext::JniIDs& jni_ids =
android_app_context.jni_ids();
int32_t source =
jni_env->CallIntMethod(event, jni_ids.motion_event_get_source);
switch (source) {
case AINPUT_SOURCE_TOUCHSCREEN: {
// Returning true for all touch events regardless of whether they have
// been handled to keep receiving touch events for the pointers in the
// event (if returning false for a down event, no more events will be sent
// for the pointers in it), and also because there are multiple pointers
// in a single event, and different handler invocations may result in
// different is_handled.
WindowDestructionReceiver destruction_receiver(this);
int32_t action_and_pointer_index =
jni_env->CallIntMethod(event, jni_ids.motion_event_get_action);
int32_t action = action_and_pointer_index & AMOTION_EVENT_ACTION_MASK;
// For pointer ACTION_POINTER_DOWN and ACTION_POINTER_UP.
int32_t pointer_index = (action_and_pointer_index &
AMOTION_EVENT_ACTION_POINTER_INDEX_MASK) >>
AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT;
if (action == AMOTION_EVENT_ACTION_POINTER_DOWN ||
action == AMOTION_EVENT_ACTION_POINTER_UP) {
int32_t touch_pointer_index =
(action_and_pointer_index &
AMOTION_EVENT_ACTION_POINTER_INDEX_MASK) >>
AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT;
TouchEvent e(
this,
jni_env->CallIntMethod(event, jni_ids.motion_event_get_pointer_id,
touch_pointer_index),
(action == AMOTION_EVENT_ACTION_POINTER_DOWN)
? TouchEvent::Action::kDown
: TouchEvent::Action::kUp,
jni_env->CallFloatMethod(event, jni_ids.motion_event_get_x,
pointer_index),
jni_env->CallFloatMethod(event, jni_ids.motion_event_get_y,
pointer_index));
OnTouchEvent(e, destruction_receiver);
if (destruction_receiver.IsWindowDestroyed()) {
return true;
}
} else {
TouchEvent::Action touch_event_action;
switch (action) {
case AMOTION_EVENT_ACTION_DOWN:
touch_event_action = TouchEvent::Action::kDown;
break;
case AMOTION_EVENT_ACTION_UP:
touch_event_action = TouchEvent::Action::kUp;
break;
case AMOTION_EVENT_ACTION_MOVE:
touch_event_action = TouchEvent::Action::kMove;
break;
case AMOTION_EVENT_ACTION_CANCEL:
touch_event_action = TouchEvent::Action::kCancel;
break;
default:
return true;
}
int32_t touch_pointer_count = jni_env->CallIntMethod(
event, jni_ids.motion_event_get_pointer_count);
for (int32_t i = 0; i < touch_pointer_count; ++i) {
TouchEvent e(
this,
jni_env->CallIntMethod(event, jni_ids.motion_event_get_pointer_id,
i),
touch_event_action,
jni_env->CallFloatMethod(event, jni_ids.motion_event_get_x, i),
jni_env->CallFloatMethod(event, jni_ids.motion_event_get_y, i));
OnTouchEvent(e, destruction_receiver);
if (destruction_receiver.IsWindowDestroyed()) {
return true;
}
}
}
return true;
} break;
case AINPUT_SOURCE_MOUSE: {
WindowDestructionReceiver destruction_receiver(this);
// X and Y can be outside the View (have negative coordinates, or beyond
// the size of the element), and not only for ACTION_HOVER_EXIT (it's
// predeced by ACTION_HOVER_MOVE, at least on Android API level 30, also
// with out-of-bounds coordinates), when moving the mouse outside the
// View, or when starting moving the mouse when the pointer was previously
// outside the View in some cases.
int32_t mouse_x = int32_t(
std::min(float(GetActualPhysicalWidth()),
std::max(0.0f, jni_env->CallFloatMethod(
event, jni_ids.motion_event_get_x, 0))) +
0.5f);
int32_t mouse_y = int32_t(
std::min(float(GetActualPhysicalHeight()),
std::max(0.0f, jni_env->CallFloatMethod(
event, jni_ids.motion_event_get_y, 0))) +
0.5f);
static const MouseEvent::Button kMouseEventButtons[] = {
MouseEvent::Button::kLeft, MouseEvent::Button::kRight,
MouseEvent::Button::kMiddle, MouseEvent::Button::kX1,
MouseEvent::Button::kX2,
};
static constexpr uint32_t kUsedMouseButtonMask =
(UINT32_C(1) << xe::countof(kMouseEventButtons)) - 1;
uint32_t new_mouse_button_state = uint32_t(
jni_env->CallIntMethod(event, jni_ids.motion_event_get_button_state));
// OnMouseUp.
uint32_t mouse_buttons_remaining =
mouse_button_state_ & ~new_mouse_button_state & kUsedMouseButtonMask;
uint32_t mouse_button_index;
while (
xe::bit_scan_forward(mouse_buttons_remaining, &mouse_button_index)) {
mouse_buttons_remaining &= ~(UINT32_C(1) << mouse_button_index);
MouseEvent e(this, kMouseEventButtons[mouse_button_index], mouse_x,
mouse_y);
OnMouseUp(e, destruction_receiver);
if (destruction_receiver.IsWindowDestroyed()) {
return true;
}
}
// Generic OnMouseMove regardless of the action since any event can
// provide new coordinates.
{
MouseEvent e(this, MouseEvent::Button::kNone, mouse_x, mouse_y);
OnMouseMove(e, destruction_receiver);
if (destruction_receiver.IsWindowDestroyed()) {
return true;
}
}
// OnMouseWheel.
// The axis value may be outside -1...1 if multiple scrolls have occurred
// quickly.
int32_t scroll_x = int32_t(
jni_env->CallFloatMethod(event, jni_ids.motion_event_get_axis_value,
AMOTION_EVENT_AXIS_HSCROLL, 0) *
float(MouseEvent::kScrollPerDetent));
int32_t scroll_y = int32_t(
jni_env->CallFloatMethod(event, jni_ids.motion_event_get_axis_value,
AMOTION_EVENT_AXIS_VSCROLL, 0) *
float(MouseEvent::kScrollPerDetent));
if (scroll_x || scroll_y) {
MouseEvent e(this, MouseEvent::Button::kNone, mouse_x, mouse_y,
scroll_x, scroll_y);
OnMouseWheel(e, destruction_receiver);
if (destruction_receiver.IsWindowDestroyed()) {
return true;
}
}
// OnMouseDown.
mouse_buttons_remaining =
new_mouse_button_state & ~mouse_button_state_ & kUsedMouseButtonMask;
while (
xe::bit_scan_forward(mouse_buttons_remaining, &mouse_button_index)) {
mouse_buttons_remaining &= ~(UINT32_C(1) << mouse_button_index);
MouseEvent e(this, kMouseEventButtons[mouse_button_index], mouse_x,
mouse_y);
OnMouseDown(e, destruction_receiver);
if (destruction_receiver.IsWindowDestroyed()) {
return true;
}
}
// Update the button state for state differences.
mouse_button_state_ = new_mouse_button_state;
return true;
} break;
}
return false;
}
uint32_t AndroidWindow::GetLatestDpiImpl() const {
auto& android_app_context =
static_cast<const AndroidWindowedAppContext&>(app_context());
@@ -62,6 +243,9 @@ uint32_t AndroidWindow::GetLatestDpiImpl() const {
}
bool AndroidWindow::OpenImpl() {
// Reset the input.
mouse_button_state_ = 0;
// The window is a proxy between the main activity and Xenia, so there can be
// only one open window for an activity.
auto& android_app_context =