mirror of https://github.com/mosra/magnum.git
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
789 lines
37 KiB
789 lines
37 KiB
/* |
|
This file is part of Magnum. |
|
|
|
Copyright © 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, |
|
2020, 2021, 2022, 2023, 2024, 2025 |
|
Vladimír Vondruš <mosra@centrum.cz> |
|
Copyright © 2021 nodoteve <nodoteve@yandex.com> |
|
Copyright © 2025 hsdk123 <daegon.dhsk@outlook.com> |
|
|
|
Permission is hereby granted, free of charge, to any person obtaining a |
|
copy of this software and associated documentation files (the "Software"), |
|
to deal in the Software without restriction, including without limitation |
|
the rights to use, copy, modify, merge, publish, distribute, sublicense, |
|
and/or sell copies of the Software, and to permit persons to whom the |
|
Software is furnished to do so, subject to the following conditions: |
|
|
|
The above copyright notice and this permission notice shall be included |
|
in all copies or substantial portions of the Software. |
|
|
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL |
|
THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING |
|
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER |
|
DEALINGS IN THE SOFTWARE. |
|
*/ |
|
|
|
#include "AndroidApplication.h" |
|
|
|
#include <Corrade/Utility/AndroidLogStreamBuffer.h> |
|
#include <Corrade/Utility/Debug.h> |
|
#include <android_native_app_glue.h> |
|
|
|
#include "Magnum/GL/Version.h" |
|
#include "Magnum/Math/Functions.h" |
|
#include "Magnum/Platform/ScreenedApplication.hpp" |
|
|
|
#include "Implementation/Egl.h" |
|
|
|
/* This function would be stripped by the linker otherwise. Search for the name |
|
below for the complete rant. */ |
|
extern "C" void ANativeActivity_onCreate(struct ANativeActivity*, void*, size_t); |
|
|
|
namespace Magnum { namespace Platform { |
|
|
|
enum class AndroidApplication::Flag: UnsignedByte { |
|
Redraw = 1 << 0 |
|
}; |
|
|
|
struct AndroidApplication::LogOutput { |
|
LogOutput(); |
|
|
|
Utility::AndroidLogStreamBuffer debugBuffer, warningBuffer, errorBuffer; |
|
std::ostream debugStream, warningStream, errorStream; |
|
Debug redirectDebug; |
|
Warning redirectWarning; |
|
Error redirectError; |
|
}; |
|
|
|
AndroidApplication::LogOutput::LogOutput(): |
|
debugBuffer(Utility::AndroidLogStreamBuffer::LogPriority::Info, "magnum"), |
|
warningBuffer(Utility::AndroidLogStreamBuffer::LogPriority::Warning, "magnum"), |
|
errorBuffer(Utility::AndroidLogStreamBuffer::LogPriority::Error, "magnum"), |
|
debugStream(&debugBuffer), warningStream(&warningBuffer), errorStream(&errorBuffer), |
|
redirectDebug{&debugStream}, redirectWarning{&warningStream}, redirectError{&errorStream} |
|
{} |
|
|
|
AndroidApplication::AndroidApplication(const Arguments& arguments): AndroidApplication{arguments, Configuration{}, GLConfiguration{}} {} |
|
|
|
AndroidApplication::AndroidApplication(const Arguments& arguments, const Configuration& configuration): AndroidApplication{arguments, configuration, GLConfiguration{}} {} |
|
|
|
AndroidApplication::AndroidApplication(const Arguments& arguments, const Configuration& configuration, const GLConfiguration& glConfiguration): AndroidApplication{arguments, NoCreate} { |
|
create(configuration, glConfiguration); |
|
} |
|
|
|
AndroidApplication::AndroidApplication(const Arguments& arguments, NoCreateT): _state{arguments}, _context{InPlaceInit, NoCreate} { |
|
/* Redirect debug output to Android log */ |
|
_logOutput.reset(new LogOutput); |
|
} |
|
|
|
AndroidApplication::~AndroidApplication() { |
|
/* Destroy Magnum context first to avoid it potentially accessing the |
|
now-destroyed GL context after */ |
|
_context = Containers::NullOpt; |
|
|
|
eglMakeCurrent(_display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); |
|
eglDestroyContext(_display, _glContext); |
|
eglDestroySurface(_display, _surface); |
|
eglTerminate(_display); |
|
} |
|
|
|
ANativeActivity* AndroidApplication::nativeActivity() { |
|
return _state->activity; |
|
} |
|
|
|
void AndroidApplication::create() { |
|
create(Configuration{}, GLConfiguration{}); |
|
} |
|
|
|
void AndroidApplication::create(const Configuration& configuration) { |
|
create(configuration, GLConfiguration{}); |
|
} |
|
|
|
void AndroidApplication::create(const Configuration& configuration, const GLConfiguration& glConfiguration) { |
|
if(!tryCreate(configuration, glConfiguration)) std::exit(32); |
|
} |
|
|
|
bool AndroidApplication::tryCreate(const Configuration& configuration) { |
|
return tryCreate(configuration, GLConfiguration{}); |
|
} |
|
|
|
bool AndroidApplication::tryCreate(const Configuration& configuration, const GLConfiguration& glConfiguration) { |
|
CORRADE_ASSERT(_context->version() == GL::Version::None, "Platform::AndroidApplication::tryCreate(): context already created", false); |
|
|
|
/* Initialize EGL */ |
|
_display = eglGetDisplay(EGL_DEFAULT_DISPLAY); |
|
if(!eglInitialize(_display, nullptr, nullptr)) { |
|
Error() << "Platform::AndroidApplication::tryCreate(): cannot initialize EGL:" |
|
<< Implementation::eglErrorString(eglGetError()); |
|
return false; |
|
} |
|
|
|
/* Choose config */ |
|
const EGLint configAttributes[] = { |
|
EGL_SURFACE_TYPE, EGL_WINDOW_BIT, |
|
EGL_RED_SIZE, glConfiguration.colorBufferSize().r(), |
|
EGL_GREEN_SIZE, glConfiguration.colorBufferSize().g(), |
|
EGL_BLUE_SIZE, glConfiguration.colorBufferSize().b(), |
|
EGL_ALPHA_SIZE, glConfiguration.colorBufferSize().a(), |
|
EGL_DEPTH_SIZE, glConfiguration.depthBufferSize(), |
|
EGL_STENCIL_SIZE, glConfiguration.stencilBufferSize(), |
|
EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT, |
|
EGL_NONE |
|
}; |
|
EGLint configCount; |
|
EGLConfig config; |
|
if(!eglChooseConfig(_display, configAttributes, &config, 1, &configCount)) { |
|
Error() << "Platform::AndroidApplication::tryCreate(): cannot choose EGL config:" |
|
<< Implementation::eglErrorString(eglGetError()); |
|
return false; |
|
} |
|
|
|
/* Resize native window and match it to the selected format */ |
|
EGLint format; |
|
CORRADE_INTERNAL_ASSERT_OUTPUT(eglGetConfigAttrib(_display, config, EGL_NATIVE_VISUAL_ID, &format)); |
|
ANativeWindow_setBuffersGeometry(_state->window, |
|
configuration.size().isZero() ? 0 : configuration.size().x(), |
|
configuration.size().isZero() ? 0 : configuration.size().y(), format); |
|
|
|
/* Create surface and context */ |
|
if(!(_surface = eglCreateWindowSurface(_display, config, _state->window, nullptr))) { |
|
Error() << "Platform::AndroidApplication::tryCreate(): cannot create EGL window surface:" |
|
<< Implementation::eglErrorString(eglGetError()); |
|
return false; |
|
} |
|
const EGLint contextAttributes[] = { |
|
#ifdef MAGNUM_TARGET_GLES2 |
|
EGL_CONTEXT_CLIENT_VERSION, 2, |
|
#elif defined(MAGNUM_TARGET_GLES) |
|
EGL_CONTEXT_CLIENT_VERSION, 3, |
|
#else |
|
#error Android with desktop OpenGL? Wow, that is a new thing. |
|
#endif |
|
EGL_NONE |
|
}; |
|
if(!(_glContext = eglCreateContext(_display, config, EGL_NO_CONTEXT, contextAttributes))) { |
|
Error() << "Platform::AndroidApplication::tryCreate(): cannot create EGL context:" |
|
<< Implementation::eglErrorString(eglGetError()); |
|
return false; |
|
} |
|
|
|
/* Make the context current */ |
|
CORRADE_INTERNAL_ASSERT_OUTPUT(eglMakeCurrent(_display, _surface, _surface, _glContext)); |
|
|
|
/* Return true if the initialization succeeds */ |
|
return _context->tryCreate(glConfiguration); |
|
} |
|
|
|
Vector2i AndroidApplication::framebufferSize() const { |
|
return {ANativeWindow_getWidth(_state->window), |
|
ANativeWindow_getHeight(_state->window)}; |
|
} |
|
|
|
void AndroidApplication::swapBuffers() { |
|
eglSwapBuffers(_display, _surface); |
|
} |
|
|
|
void AndroidApplication::redraw() { |
|
_flags |= Flag::Redraw; |
|
} |
|
|
|
void AndroidApplication::viewportEvent(ViewportEvent&) {} |
|
void AndroidApplication::focusEvent(FocusEvent&) {} |
|
void AndroidApplication::blurEvent(FocusEvent&) {} |
|
|
|
namespace { |
|
struct Data { |
|
Data(Containers::Pointer<AndroidApplication>(*instancer)(const AndroidApplication::Arguments&), void(*nativeActivity)(ANativeActivity*,void*,size_t)): instancer(instancer), nativeActivity{nativeActivity} {} |
|
|
|
Containers::Pointer<AndroidApplication>(*instancer)(const AndroidApplication::Arguments&); |
|
Containers::Pointer<AndroidApplication> instance; |
|
|
|
void(*nativeActivity)(ANativeActivity*,void*,size_t); |
|
}; |
|
} |
|
|
|
void AndroidApplication::commandEvent(android_app* state, int32_t cmd) { |
|
Data& data = *static_cast<Data*>(state->userData); |
|
|
|
switch(cmd) { |
|
case APP_CMD_SAVE_STATE: |
|
/** @todo Make use of this */ |
|
break; |
|
|
|
case APP_CMD_INIT_WINDOW: |
|
/* Create the application */ |
|
if(!data.instance) { |
|
data.instance = data.instancer(state); |
|
data.instance->drawEvent(); |
|
} |
|
break; |
|
|
|
case APP_CMD_TERM_WINDOW: |
|
/* Destroy the application */ |
|
data.instance.reset(); |
|
break; |
|
|
|
case APP_CMD_GAINED_FOCUS: |
|
case APP_CMD_LOST_FOCUS: { |
|
FocusEvent e; |
|
cmd == APP_CMD_GAINED_FOCUS ? |
|
data.instance->focusEvent(e) : |
|
data.instance->blurEvent(e); |
|
} break; |
|
|
|
case APP_CMD_CONFIG_CHANGED: { |
|
/* This says "the current device configuration has changed", which |
|
is about as vague as it can get. It seems to be that this gets |
|
emitted when screen orientation changes, for example. Fire the |
|
viewport event in this case. */ |
|
ViewportEvent e{{ANativeWindow_getWidth(data.instance->_state->window), |
|
ANativeWindow_getHeight(data.instance->_state->window)}}; |
|
data.instance->viewportEvent(e); |
|
} break; |
|
} |
|
} |
|
|
|
namespace { |
|
|
|
AndroidApplication::Pointers motionEventButtons(AInputEvent* event) { |
|
const std::int32_t buttons = AMotionEvent_getButtonState(event); |
|
AndroidApplication::Pointers pointers; |
|
if(buttons & AMOTION_EVENT_BUTTON_PRIMARY) |
|
pointers |= AndroidApplication::Pointer::MouseLeft; |
|
if(buttons & AMOTION_EVENT_BUTTON_TERTIARY) |
|
pointers |= AndroidApplication::Pointer::MouseMiddle; |
|
if(buttons & AMOTION_EVENT_BUTTON_SECONDARY) |
|
pointers |= AndroidApplication::Pointer::MouseRight; |
|
/** @todo AMOTION_EVENT_BUTTON_BACK, AMOTION_EVENT_BUTTON_FORWARD once it's |
|
possible to verify they match MouseButton4 / MouseButton5 in |
|
GlfwApplication and Sdl2Application */ |
|
return pointers; |
|
} |
|
|
|
Containers::Pair<AndroidApplication::PointerEventSource, AndroidApplication::Pointers> motionEventPointers(AInputEvent* event, std::size_t i, const AndroidApplication::Pointers pressedButtons) { |
|
switch(AMotionEvent_getToolType(event, i)) { |
|
case AMOTION_EVENT_TOOL_TYPE_MOUSE: |
|
/** @todo MouseButton4 / MouseButton5, once they're added & |
|
tested */ |
|
return {AndroidApplication::PointerEventSource::Mouse, |
|
(AndroidApplication::Pointer::MouseLeft| |
|
AndroidApplication::Pointer::MouseMiddle| |
|
AndroidApplication::Pointer::MouseRight) & pressedButtons}; |
|
case AMOTION_EVENT_TOOL_TYPE_FINGER: |
|
return {AndroidApplication::PointerEventSource::Touch, |
|
AndroidApplication::Pointer::Finger}; |
|
case AMOTION_EVENT_TOOL_TYPE_STYLUS: |
|
/** @todo use pressedButtonsPointers once there's additional pen |
|
button enum values */ |
|
return {AndroidApplication::PointerEventSource::Pen, |
|
AndroidApplication::Pointer::Pen}; |
|
case AMOTION_EVENT_TOOL_TYPE_ERASER: |
|
return {AndroidApplication::PointerEventSource::Touch, |
|
AndroidApplication::Pointer::Eraser}; |
|
case AMOTION_EVENT_TOOL_TYPE_UNKNOWN: |
|
default: |
|
return {AndroidApplication::PointerEventSource::Unknown, |
|
AndroidApplication::Pointer::Unknown}; |
|
} |
|
} |
|
|
|
template<class T> Vector2 updatePreviousTouch(T(&previousTouches)[32], const std::int32_t id, const Containers::Optional<Vector2>& position) { |
|
std::size_t firstFree = ~std::size_t{}; |
|
for(std::size_t i = 0; i != Containers::arraySize(previousTouches); ++i) { |
|
/* Previous position found */ |
|
if(previousTouches[i].id == id) { |
|
/* Update with the current position, return delta to previous */ |
|
if(position) { |
|
const Vector2 relative = *position - previousTouches[i].position; |
|
previousTouches[i].position = *position; |
|
return relative; |
|
/* Clear previous position */ |
|
} else { |
|
previousTouches[i].id = ~Int{}; |
|
return {}; |
|
} |
|
/* Unused slot, remember in case there won't be any previous position |
|
found */ |
|
} else if(previousTouches[i].id == ~Int{} && firstFree == ~std::size_t{}) { |
|
firstFree = i; |
|
} |
|
} |
|
|
|
/* If we're not resetting the position and there's a place where to put the |
|
new one, save. Otherwise don't do anything -- the touch that didn't fit |
|
will always report as having no relative position. */ |
|
if(position && firstFree != ~std::size_t{}) { |
|
previousTouches[firstFree].id = id; |
|
previousTouches[firstFree].position = *position; |
|
} |
|
|
|
return {}; |
|
} |
|
|
|
/* Unlike e.g. SDL, which guarantees that pointer IDs are unique among all |
|
pointer types, here they of course don't care. So use the reported ID only |
|
for touches and artificial constants for the rest. */ |
|
std::int32_t pointerIdForSource(AndroidApplication::PointerEventSource source, std::int32_t id) { |
|
switch(source) { |
|
case AndroidApplication::PointerEventSource::Touch: |
|
return id; |
|
case AndroidApplication::PointerEventSource::Mouse: |
|
return -1; |
|
case AndroidApplication::PointerEventSource::Pen: |
|
return -2; |
|
case AndroidApplication::PointerEventSource::Unknown: |
|
return -3; |
|
} |
|
} |
|
|
|
} |
|
|
|
std::int32_t AndroidApplication::inputEvent(android_app* state, AInputEvent* event) { |
|
CORRADE_INTERNAL_ASSERT(static_cast<Data*>(state->userData)->instance); |
|
AndroidApplication& app = *static_cast<Data*>(state->userData)->instance; |
|
if(AInputEvent_getType(event) == AINPUT_EVENT_TYPE_MOTION) { |
|
const std::int32_t action = AMotionEvent_getAction(event) & AMOTION_EVENT_ACTION_MASK; |
|
switch(action) { |
|
case AMOTION_EVENT_ACTION_DOWN: |
|
case AMOTION_EVENT_ACTION_POINTER_DOWN: |
|
case AMOTION_EVENT_ACTION_UP: |
|
case AMOTION_EVENT_ACTION_POINTER_UP: { |
|
/* Figure out which pointer actually changed in given event, |
|
because OF COURSE the API is so horrible that this is |
|
non-trivial. For AMOTION_EVENT_ACTION_DOWN we assume it's |
|
the first ever pointer being pressed, and thus the count |
|
being 1, thus the pointer that changed is the first and |
|
only. */ |
|
std::int32_t pointerChanged; |
|
if(action == AMOTION_EVENT_ACTION_DOWN) { |
|
CORRADE_INTERNAL_ASSERT(AMotionEvent_getPointerCount(event) == 1); |
|
pointerChanged = 0; |
|
/* For AMOTION_EVENT_ACTION_UP it's ... apparently the last |
|
remaining pointer going up. Not the primary one (see below |
|
for the `primary` bit decision tree). The docs make it look |
|
like the event also contains any other pointers, but it's |
|
probably just mentioning the AMotionEvent_getHistoricalX() |
|
etc. fields? Er? Why is it not mentioning that for for |
|
AMOTION_EVENT_ACTION_POINTER_UP then? |
|
https://developer.android.com/reference/android/view/MotionEvent#ACTION_UP */ |
|
} else if(action == AMOTION_EVENT_ACTION_UP) { |
|
CORRADE_INTERNAL_ASSERT(AMotionEvent_getPointerCount(event) == 1); |
|
pointerChanged = 0; |
|
/* The AMOTION_EVENT_ACTION_POINTER_DOWN/_UP actually mean a |
|
secondary pointer was pressed or released. Who would have |
|
thought. In that case, the actual changed pointer is given |
|
to us with this fucking atrocity of a bitmask. Well, |
|
alright, what can I do, but why such a bitmask couldn't be |
|
done above as well, huh??? */ |
|
} else if(action == AMOTION_EVENT_ACTION_POINTER_DOWN || |
|
action == AMOTION_EVENT_ACTION_POINTER_UP) { |
|
pointerChanged = (AMotionEvent_getAction(event) & AMOTION_EVENT_ACTION_POINTER_INDEX_MASK) >> AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT; |
|
} else CORRADE_INTERNAL_ASSERT_UNREACHABLE(); |
|
|
|
const bool press = |
|
action == AMOTION_EVENT_ACTION_DOWN || |
|
action == AMOTION_EVENT_ACTION_POINTER_DOWN; |
|
const Vector2 position{AMotionEvent_getX(event, pointerChanged), |
|
AMotionEvent_getY(event, pointerChanged)}; |
|
|
|
/* Query the currently pressed buttons. If this is not a mouse |
|
event, it'll give back garbage, but that's fine as we won't |
|
use it in that case. Then, based on whether it's a press or |
|
a release, use the previously recorded pointers to figure |
|
out what was actually pressed. */ |
|
const Pointers pressedButtons = motionEventButtons(event); |
|
const Containers::Pair<PointerEventSource, Pointers> sourcePointers = |
|
motionEventPointers(event, pointerChanged, press ? |
|
pressedButtons & ~app._previousPressedButtons : |
|
~pressedButtons & app._previousPressedButtons); |
|
const std::int32_t pointerId = pointerIdForSource(sourcePointers.first(), AMotionEvent_getPointerId(event, pointerChanged)); |
|
|
|
/* Decide whether this is a primary pointer. It's tempting to |
|
use the distinction between _DOWN and _POINTER_DOWN to |
|
distinguish a primary pointer from a secondary one, but |
|
that'd be giving too much credit to this damn API. The |
|
problem is that, if multiple fingers is pressed, _POINTER_UP |
|
is fired if any of the secondary fingers are lifted, but |
|
also if the primary finger is lifted. Which in turn means |
|
the primary finger would be treated as secondary for the |
|
up event, which is wrong. Second, then none of the remaining |
|
fingers then have any reasonable way to get promoted to a |
|
primary one, so they all stay secondary. BUT THEN, if the |
|
last one of the secondary fingers gets lifted, _UP is fired |
|
for it, so it suddenly becomes primary. Which a total trash |
|
fire of a broken behavior. A mention worth a laugh is the |
|
official Android developer blog, where in 2010 they |
|
suggested the same thing --- in particular, when the primary |
|
pointer is lifted, *an arbitrary one* from the rest is |
|
chosen as primary. |
|
https://android-developers.googleblog.com/2010/06/making-sense-of-multitouch.html |
|
Eh. Maybe it makes sense for just two touches, but |
|
definitely not for multiple. So let's just ignore all that |
|
and do it by hand like in Sdl2Application and |
|
EmscriptenApplication, to have consistent behavior across |
|
all. |
|
|
|
Mouse and pen is always a primary pointer. */ |
|
bool primary; |
|
if(sourcePointers.first() == PointerEventSource::Mouse || |
|
sourcePointers.first() == PointerEventSource::Pen) { |
|
primary = true; |
|
|
|
/* For touch update primary finger info */ |
|
} else if(sourcePointers.first() == PointerEventSource::Touch) { |
|
/* If there's no primary finger yet and this is the first |
|
finger pressed (i.e., what AMOTION_EVENT_ACTION_DOWN |
|
implies), it becomes the primary finger. If the primary |
|
finger is lifted, no other finger becomes primary until |
|
all others are lifted as well. Again, this is the same |
|
as in Sdl2Application and EmscriptenApplication. */ |
|
if(app._primaryFingerId == ~Int{} && action == AMOTION_EVENT_ACTION_DOWN) { |
|
CORRADE_INTERNAL_ASSERT(AMotionEvent_getPointerCount(event) == 1); |
|
primary = true; |
|
app._primaryFingerId = pointerId; |
|
/* Otherwise, if this is the primary finger, mark it as |
|
such */ |
|
} else if(app._primaryFingerId == pointerId) { |
|
primary = true; |
|
/* ... but if it's a release, it's no longer primary */ |
|
if(!press) |
|
app._primaryFingerId = ~Int{}; |
|
/* Otherwise this is not the primary finger */ |
|
} else primary = false; |
|
|
|
/* Unknown pointer is probably not a primary one */ |
|
} else primary = false; |
|
|
|
/* The expectation is that the difference betweeen the |
|
previously recorded set of pointers and current one will be |
|
exactly one bit for a pointer type that got either pressed |
|
or released. If it's not, it means we lost some events, and |
|
until API 33+ and AMotionEvent_getActionButton() on |
|
AMOTION_EVENT_BUTTON_PRESS / AMOTION_EVENT_BUTTON_RELEASE, |
|
there's no way to reliably know what concrete mouse / pen |
|
button caused the event. */ |
|
Pointer pointer; |
|
/* http://www.graphics.stanford.edu/~seander/bithacks.html#DetermineIfPowerOf2 */ |
|
if(sourcePointers.second() && !(UnsignedByte(sourcePointers.second()) & (UnsignedByte(sourcePointers.second()) - 1))) |
|
pointer = Pointer(UnsignedByte(sourcePointers.second())); |
|
else |
|
pointer = Pointer::Unknown; |
|
|
|
/** @todo Once there's an ability to actually *know* what |
|
button was pressed or released (API 33+), implement |
|
translation to move events like in GlfwApplication, |
|
Sdl2Application and EmscriptenApplication. With my emulator |
|
testing, where a mouse was interpreted as a stylus (?!), |
|
multiple buttons being pressed didn't even trigger a press |
|
or release event, so this scenario is seemingly impossible |
|
to happen. */ |
|
|
|
/* Assuming there's never more than 256 pointers in a single |
|
event. Even that feels like a lot. */ |
|
PointerEvent e{event, UnsignedByte(pointerChanged), sourcePointers.first(), pointer, primary, pointerId}; |
|
press ? app.pointerPressEvent(e) : app.pointerReleaseEvent(e); |
|
|
|
/* Remember the currently pressed pointers for the next time */ |
|
app._previousPressedButtons = pressedButtons; |
|
|
|
/* If this is a touch press, remember its position for next |
|
events. If this is a touch release, free the slot used by |
|
this identifier for next events. Mouse and pen supports |
|
hover and thus is updated only in AMOTION_EVENT_ACTION_MOVE. |
|
See below for why this has to be remembered at all. */ |
|
if(sourcePointers.first() == PointerEventSource::Touch) { |
|
if(press) |
|
updatePreviousTouch(app._previousTouches, pointerId, position); |
|
else |
|
updatePreviousTouch(app._previousTouches, pointerId, {}); |
|
} |
|
|
|
return e.isAccepted(); |
|
} |
|
|
|
case AMOTION_EVENT_ACTION_MOVE: { |
|
const Pointers pressedButtons = motionEventButtons(event); |
|
|
|
/* Unlike AMOTION_EVENT_ACTION_DOWN / AMOTION_EVENT_ACTION_UP, |
|
the move event can contain multiple moving pointers so |
|
there's no mask telling which pointer moved. Go through all |
|
and emit a move event only for those that changed. */ |
|
bool accepted = false; |
|
const std::size_t pointerCount = AMotionEvent_getPointerCount(event); |
|
for(std::size_t i = 0; i != pointerCount; ++i) { |
|
const Containers::Pair<PointerEventSource, Pointers> sourcePointers = motionEventPointers(event, i, pressedButtons); |
|
const std::int32_t pointerId = pointerIdForSource(sourcePointers.first(), AMotionEvent_getPointerId(event, i)); |
|
const Vector2 position{AMotionEvent_getX(event, i), |
|
AMotionEvent_getY(event, i)}; |
|
|
|
/* Query position relative to the previous one for the same |
|
pointer type and identifier, update it with current. |
|
Ideally I would get it somewhere from the platform APIs. |
|
There's AMotionEvent_getHistoricalX()/Y(), but those are |
|
coalesced events between the previous and currently |
|
fired events, i.e. not the full delta. Documented here: |
|
https://developer.android.com/reference/android/view/MotionEvent#batching |
|
There's also AMOTION_EVENT_AXIS_RELATIVE_X/_Y, but |
|
according to |
|
https://developer.android.com/reference/android/view/MotionEvent#AXIS_X |
|
the coordinate system is different for each event type, |
|
and the last thing I want to do is adding special |
|
handling for things the damn platform API should be |
|
doing for me. */ |
|
Vector2 relativePosition{NoInit}; |
|
if(sourcePointers.first() == PointerEventSource::Mouse || |
|
sourcePointers.first() == PointerEventSource::Pen) { |
|
relativePosition = Math::isNan(app._previousHoverPointerPosition).all() ? |
|
Vector2{} : position - app._previousHoverPointerPosition; |
|
app._previousHoverPointerPosition = position; |
|
} else if(sourcePointers.first() == PointerEventSource::Touch) { |
|
relativePosition = updatePreviousTouch(app._previousTouches, pointerId, position); |
|
} else { |
|
/* No relative position for Unknown */ |
|
relativePosition = {}; |
|
} |
|
|
|
/* Decide whether this is a primary pointer. Mouse and pen |
|
is always a primary pointer. */ |
|
bool primary; |
|
if(sourcePointers.first() == PointerEventSource::Mouse || |
|
sourcePointers.first() == PointerEventSource::Pen) { |
|
primary = true; |
|
/* For touch, it's a primary finger only if it was |
|
registered as such during the last press. If the primary |
|
finger was lifted, no other finger will step into its |
|
place until all others are lifted as well. */ |
|
} else if(sourcePointers.first() == PointerEventSource::Touch) { |
|
primary = app._primaryFingerId == pointerId; |
|
/* Unknown pointer is probably not a primary one */ |
|
} else primary = false; |
|
|
|
/* The thing fires move events right after press events, |
|
with the exact same position, for (emulated?) events at |
|
least. I suppose that's some sort of unasked-for |
|
misfeature for "improving" UX or fixing broken apps. Not |
|
interested, filter those out if the relative position is |
|
zero and the set of pressed buttons is the same. |
|
Hopefully not accepting those doesn't lead to some |
|
strange behavior. */ |
|
if(relativePosition != Vector2{} || pressedButtons != app._previousPressedButtons) { |
|
/* Assuming there's never more than 256 pointers in a |
|
single event. Even that feels like a lot. */ |
|
PointerMoveEvent e{event, UnsignedByte(i), sourcePointers.first(), {}, sourcePointers.second(), primary, pointerId, relativePosition}; |
|
app.pointerMoveEvent(e); |
|
accepted = accepted || e.isAccepted(); |
|
} |
|
} |
|
|
|
/* Remember the currently pressed buttons for the next time. |
|
Ideally should only be needed for AMOTION_EVENT_ACTION_DOWN |
|
and AMOTION_EVENT_ACTION_UP, but if some events get lost, we |
|
have a chance to resynchronize here. */ |
|
app._previousPressedButtons = pressedButtons; |
|
|
|
return accepted; |
|
} |
|
|
|
/* Like AMOTION_EVENT_ACTION_MOVE, but without anything pressed */ |
|
case AMOTION_EVENT_ACTION_HOVER_MOVE: { |
|
/* Assuming there's just one pointer reported for a hover, and |
|
it's either a mouse or a pen. Or something unknown. */ |
|
CORRADE_INTERNAL_ASSERT(AMotionEvent_getPointerCount(event) == 1); |
|
PointerEventSource source; |
|
switch(AMotionEvent_getToolType(event, 0)) { |
|
case AMOTION_EVENT_TOOL_TYPE_MOUSE: |
|
source = AndroidApplication::PointerEventSource::Mouse; |
|
break; |
|
case AMOTION_EVENT_TOOL_TYPE_FINGER: |
|
CORRADE_INTERNAL_ASSERT_UNREACHABLE(); |
|
case AMOTION_EVENT_TOOL_TYPE_STYLUS: |
|
case AMOTION_EVENT_TOOL_TYPE_ERASER: |
|
source = AndroidApplication::PointerEventSource::Pen; |
|
break; |
|
case AMOTION_EVENT_TOOL_TYPE_UNKNOWN: |
|
default: |
|
source = AndroidApplication::PointerEventSource::Unknown; |
|
break; |
|
} |
|
|
|
const std::int32_t pointerId = pointerIdForSource(source, AMotionEvent_getPointerId(event, 0)); |
|
const Vector2 position{AMotionEvent_getX(event, 0), |
|
AMotionEvent_getY(event, 0)}; |
|
const Vector2 relativePosition = |
|
Math::isNan(app._previousHoverPointerPosition).all() ? |
|
Vector2{} : position - app._previousHoverPointerPosition; |
|
|
|
/* Similarly as with AMOTION_EVENT_ACTION_MOVE, the damn thing |
|
fires hover events with zero position delta when scrolling |
|
the mouse wheel. Useless, filter those away. */ |
|
bool accepted = false; |
|
if(relativePosition != Vector2{}) { |
|
PointerMoveEvent e{event, 0, source, {}, {}, true, pointerId, relativePosition}; |
|
app.pointerMoveEvent(e); |
|
accepted = e.isAccepted(); |
|
} |
|
|
|
/* Reset the currently pressed buttons, since there should be |
|
none if we're just hovering */ |
|
app._previousPressedButtons = {}; |
|
/* Remember the current position. See above for why |
|
AMotionEvent_getHistoricalX()/Y() is useless. */ |
|
app._previousHoverPointerPosition = position; |
|
|
|
return accepted; |
|
} |
|
|
|
/** @todo there's AMOTION_EVENT_ACTION_HOVER_ENTER and |
|
AMOTION_EVENT_ACTION_HOVER_EXIT, implement once other apps get |
|
something similar */ |
|
/** @todo AMOTION_EVENT_ACTION_SCROLL */ |
|
} |
|
|
|
/** @todo Implement also other input events */ |
|
} |
|
|
|
return 0; |
|
} |
|
|
|
AndroidApplication::GLConfiguration::GLConfiguration(): |
|
_colorBufferSize{8, 8, 8, 8}, _depthBufferSize{24}, _stencilBufferSize{0} {} |
|
|
|
void AndroidApplication::exec(android_app* state, Containers::Pointer<AndroidApplication>(*instancer)(const Arguments&)) { |
|
state->onAppCmd = commandEvent; |
|
state->onInputEvent = inputEvent; |
|
|
|
/* Long time ago there was a call to app_dummy() that prevented stripping |
|
the ANativeActivity_onCreate() symbol. It was awful enough on its own, |
|
but they decided that it's no longer needed and the PROPER AND BETTER |
|
SOLUTION is to pollute all downstream build scripts with |
|
`-u ANativeActivity_onCreate` passed to linker. That's just fucking |
|
awful. I can't use app_dummy() as it's deprecated, so I'm simply |
|
retrieving the function pointer to the ANativeActivity_onCreate function |
|
and saving it somewhere to convince the linker it's really needed. I |
|
WANT TO SCREAM. https://github.com/android-ndk/ndk/issues/381 */ |
|
/** @todo Make use of saved state */ |
|
Data data{instancer, ANativeActivity_onCreate}; |
|
state->userData = &data; |
|
|
|
/* Poll for event until destroy is requested */ |
|
while(!state->destroyRequested) { |
|
android_poll_source* source = nullptr; |
|
const int result = ALooper_pollOnce( |
|
data.instance && (data.instance->_flags & Flag::Redraw) ? |
|
0 : -1, /* negative value: wait indefinitely until an event appears */ |
|
nullptr, nullptr, reinterpret_cast<void**>(&source)); |
|
|
|
/** @todo shouldn't this be acted upon somehow? the change in |
|
https://github.com/android/ndk-samples/pull/1008 prints some error |
|
message, the docs at https://developer.android.com/ndk/reference/group/looper |
|
don't say anything useful as usual */ |
|
if(result == ALOOPER_POLL_ERROR) |
|
return; |
|
|
|
/* Process this event OH SIR MAY MY POOR EXISTENCE CALL THIS FUNCTION |
|
FOR YOU IF YOU DON'T MIND? */ |
|
if(source) |
|
source->process(state, source); |
|
|
|
/* Redraw the app if it wants to be redrawn. Frame limiting is done by |
|
Android itself */ |
|
if(data.instance && (data.instance->_flags & Flag::Redraw)) |
|
data.instance->drawEvent(); |
|
} |
|
|
|
state->userData = nullptr; |
|
} |
|
|
|
void AndroidApplication::pointerPressEvent(PointerEvent& event) { |
|
#ifdef MAGNUM_BUILD_DEPRECATED |
|
if(!event.isPrimary()) |
|
return; |
|
|
|
CORRADE_IGNORE_DEPRECATED_PUSH |
|
MouseEvent mouseEvent{event._event}; |
|
mousePressEvent(mouseEvent); |
|
CORRADE_IGNORE_DEPRECATED_POP |
|
#else |
|
static_cast<void>(event); |
|
#endif |
|
} |
|
|
|
#ifdef MAGNUM_BUILD_DEPRECATED |
|
CORRADE_IGNORE_DEPRECATED_PUSH |
|
void AndroidApplication::mousePressEvent(MouseEvent&) {} |
|
CORRADE_IGNORE_DEPRECATED_POP |
|
#endif |
|
|
|
void AndroidApplication::pointerReleaseEvent(PointerEvent& event) { |
|
#ifdef MAGNUM_BUILD_DEPRECATED |
|
if(!event.isPrimary()) |
|
return; |
|
|
|
CORRADE_IGNORE_DEPRECATED_PUSH |
|
MouseEvent mouseEvent{event._event}; |
|
mouseReleaseEvent(mouseEvent); |
|
CORRADE_IGNORE_DEPRECATED_POP |
|
#else |
|
static_cast<void>(event); |
|
#endif |
|
} |
|
|
|
#ifdef MAGNUM_BUILD_DEPRECATED |
|
CORRADE_IGNORE_DEPRECATED_PUSH |
|
void AndroidApplication::mouseReleaseEvent(MouseEvent&) {} |
|
CORRADE_IGNORE_DEPRECATED_POP |
|
#endif |
|
|
|
void AndroidApplication::pointerMoveEvent(PointerMoveEvent& event) { |
|
#ifdef MAGNUM_BUILD_DEPRECATED |
|
if(!event.isPrimary()) |
|
return; |
|
|
|
const Vector2i roundedPosition{Math::round(event.position())}; |
|
|
|
/* If the event is due to some button being additionally pressed or one |
|
button from a larger set being released, delegate to a press/release |
|
event instead */ |
|
/** @todo This codepath is never used as move events with pointer() being |
|
set aren't emitted at all above. Keeping it here as that may change |
|
with API 33+. */ |
|
CORRADE_IGNORE_DEPRECATED_PUSH |
|
if(event.pointer()) { |
|
/* Android reports either a move or a press/release, so there shouldn't |
|
be any move in this case */ |
|
CORRADE_INTERNAL_ASSERT(event.relativePosition() == Vector2{}); |
|
MouseEvent mouseEvent{event._event}; |
|
event.pointers() >= *event.pointer() ? |
|
mousePressEvent(mouseEvent) : mouseReleaseEvent(mouseEvent); |
|
} else { |
|
/* Can't do just Math::round(event.relativePosition()) because if the |
|
previous position was 4.6 and the new 5.3, they both round to 5 but |
|
the relativePosition is 0.6 and rounds to 1. Conversely, if it'd be |
|
5.3 and 5.6, the positions round to 5 and 6 but relative position |
|
stays 0. */ |
|
const Vector2i previousRoundedPosition{Math::round(event.position() - event.relativePosition())}; |
|
/* Call the event only if the integer values actually changed */ |
|
if(roundedPosition != previousRoundedPosition) { |
|
MouseMoveEvent mouseEvent{event._event, roundedPosition - previousRoundedPosition}; |
|
mouseMoveEvent(mouseEvent); |
|
} |
|
} |
|
CORRADE_IGNORE_DEPRECATED_POP |
|
#else |
|
static_cast<void>(event); |
|
#endif |
|
} |
|
|
|
#ifdef MAGNUM_BUILD_DEPRECATED |
|
CORRADE_IGNORE_DEPRECATED_PUSH |
|
void AndroidApplication::mouseMoveEvent(MouseMoveEvent&) {} |
|
CORRADE_IGNORE_DEPRECATED_POP |
|
#endif |
|
|
|
template class BasicScreen<AndroidApplication>; |
|
template class BasicScreenedApplication<AndroidApplication>; |
|
|
|
}}
|
|
|