mirror of
https://github.com/bryanthaboi/gen1recomp.git
synced 2026-08-21 13:09:54 +02:00
feat(android): expose secondary-screen touch events
This commit is contained in:
+5
-4
@@ -221,10 +221,11 @@ the finished `worldCanvas` and `uiCanvas` with their SGB `zones` / `worldZones`,
|
|||||||
`worldActive`, the frame metrics (`ww`, `wh`, `pw`, `ph`, `ox`, `oy`, `vpw`,
|
`worldActive`, the frame metrics (`ww`, `wh`, `pw`, `ph`, `ox`, `oy`, `vpw`,
|
||||||
`vph`, `scale`, `Sx`, `Sy`, `dpiX`, `dpiY`), `renderer:blitCanvas(...)` for a
|
`vph`, `scale`, `Sx`, `Sy`, `dpiX`, `dpiY`), `renderer:blitCanvas(...)` for a
|
||||||
palette-correct blit of either canvas into an arbitrary screen rect, and the
|
palette-correct blit of either canvas into an arbitrary screen rect, and the
|
||||||
`secondScreen` bridge (`available()` / `push(imageData, w, h)` / `setEnabled`)
|
`secondScreen` bridge (`available()` / `push(imageData, w, h)` / `pollTouch()` /
|
||||||
for driving a second physical display. This is what lets a mod lay the two
|
`setEnabled`) for driving a second physical display. `pollTouch()` returns the
|
||||||
passes out as two stacked Game Boy screens, or push one onto a second screen,
|
oldest queued event as `"action,x,y"` in submitted-frame coordinates, or `nil`.
|
||||||
without the engine knowing the layout.
|
This is what lets a mod lay the two passes out as two stacked Game Boy screens,
|
||||||
|
or push one onto a second screen, without the engine knowing the layout.
|
||||||
|
|
||||||
`screen.render_visible` receives `(next, state)` while the main screen is being
|
`screen.render_visible` receives `(next, state)` while the main screen is being
|
||||||
composed. Return `false` to omit that state from drawing, opacity selection and
|
composed. Return `false` to omit that state from drawing, opacity selection and
|
||||||
|
|||||||
@@ -1003,4 +1003,33 @@ void love_android_secondary_enable(int on)
|
|||||||
env->DeleteLocalRef(activity);
|
env->DeleteLocalRef(activity);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
extern "C" __attribute__((visibility("default")))
|
||||||
|
const char *love_android_poll_secondary_touch()
|
||||||
|
{
|
||||||
|
static thread_local std::string event;
|
||||||
|
event.clear();
|
||||||
|
JNIEnv *env = (JNIEnv*) SDL_AndroidGetJNIEnv();
|
||||||
|
jclass activity = env->FindClass("org/love2d/android/GameActivity");
|
||||||
|
jmethodID method = env->GetStaticMethodID(activity, "pollSecondaryDisplayTouch",
|
||||||
|
"()Ljava/lang/String;");
|
||||||
|
if (!method)
|
||||||
|
env->ExceptionClear();
|
||||||
|
else
|
||||||
|
{
|
||||||
|
jstring value = (jstring) env->CallStaticObjectMethod(activity, method);
|
||||||
|
if (value)
|
||||||
|
{
|
||||||
|
const char *utf = env->GetStringUTFChars(value, nullptr);
|
||||||
|
if (utf)
|
||||||
|
{
|
||||||
|
event = utf;
|
||||||
|
env->ReleaseStringUTFChars(value, utf);
|
||||||
|
}
|
||||||
|
env->DeleteLocalRef(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
env->DeleteLocalRef(activity);
|
||||||
|
return event.empty() ? nullptr : event.c_str();
|
||||||
|
}
|
||||||
|
|
||||||
#endif // LOVE_ANDROID
|
#endif // LOVE_ANDROID
|
||||||
|
|||||||
@@ -1325,6 +1325,9 @@ public class GameActivity extends SDLActivity {
|
|||||||
// in src/jni/love/src/common/android.cpp.
|
// in src/jni/love/src/common/android.cpp.
|
||||||
private static volatile SecondaryPresentation secondaryPresentation;
|
private static volatile SecondaryPresentation secondaryPresentation;
|
||||||
private static volatile boolean secondaryEnabled = false;
|
private static volatile boolean secondaryEnabled = false;
|
||||||
|
private static final int MAX_SECONDARY_TOUCHES = 32;
|
||||||
|
private static final java.util.ArrayDeque<String> secondaryTouches =
|
||||||
|
new java.util.ArrayDeque<>();
|
||||||
|
|
||||||
@Keep
|
@Keep
|
||||||
public static void setSecondaryEnabled(final boolean on) {
|
public static void setSecondaryEnabled(final boolean on) {
|
||||||
@@ -1377,6 +1380,7 @@ public class GameActivity extends SDLActivity {
|
|||||||
private static void teardownSecondaryDisplay() {
|
private static void teardownSecondaryDisplay() {
|
||||||
SecondaryPresentation p = secondaryPresentation;
|
SecondaryPresentation p = secondaryPresentation;
|
||||||
secondaryPresentation = null;
|
secondaryPresentation = null;
|
||||||
|
synchronized (secondaryTouches) { secondaryTouches.clear(); }
|
||||||
if (p != null) {
|
if (p != null) {
|
||||||
try { p.dismiss(); } catch (Throwable t) {}
|
try { p.dismiss(); } catch (Throwable t) {}
|
||||||
}
|
}
|
||||||
@@ -1395,6 +1399,13 @@ public class GameActivity extends SDLActivity {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Keep
|
||||||
|
public static String pollSecondaryDisplayTouch() {
|
||||||
|
synchronized (secondaryTouches) {
|
||||||
|
return secondaryTouches.pollFirst();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private static class SecondaryPresentation extends android.app.Presentation {
|
private static class SecondaryPresentation extends android.app.Presentation {
|
||||||
private final FrameView frameView;
|
private final FrameView frameView;
|
||||||
|
|
||||||
@@ -1461,6 +1472,7 @@ public class GameActivity extends SDLActivity {
|
|||||||
private final android.graphics.Paint paint = new android.graphics.Paint();
|
private final android.graphics.Paint paint = new android.graphics.Paint();
|
||||||
private final Object lock = new Object();
|
private final Object lock = new Object();
|
||||||
private int fw, fh;
|
private int fw, fh;
|
||||||
|
private int activePointer = -1;
|
||||||
|
|
||||||
FrameView(Context context) {
|
FrameView(Context context) {
|
||||||
super(context);
|
super(context);
|
||||||
@@ -1482,6 +1494,52 @@ public class GameActivity extends SDLActivity {
|
|||||||
postInvalidate();
|
postInvalidate();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void enqueueTouch(String event) {
|
||||||
|
synchronized (secondaryTouches) {
|
||||||
|
if (secondaryTouches.size() >= MAX_SECONDARY_TOUCHES) {
|
||||||
|
secondaryTouches.clear();
|
||||||
|
secondaryTouches.addLast("cancel,0,0");
|
||||||
|
} else {
|
||||||
|
secondaryTouches.addLast(event);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private int logicalX(float x) {
|
||||||
|
return Math.min(fw - 1, Math.max(0,
|
||||||
|
(int) ((x - dst.left) * fw / dst.width())));
|
||||||
|
}
|
||||||
|
|
||||||
|
private int logicalY(float y) {
|
||||||
|
return Math.min(fh - 1, Math.max(0,
|
||||||
|
(int) ((y - dst.top) * fh / dst.height())));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onTouchEvent(android.view.MotionEvent event) {
|
||||||
|
synchronized (lock) {
|
||||||
|
int action = event.getActionMasked();
|
||||||
|
if (action == android.view.MotionEvent.ACTION_DOWN && fw > 0
|
||||||
|
&& dst.contains((int) event.getX(), (int) event.getY())) {
|
||||||
|
activePointer = event.getPointerId(0);
|
||||||
|
enqueueTouch("down," + logicalX(event.getX()) + ","
|
||||||
|
+ logicalY(event.getY()));
|
||||||
|
} else if (action == android.view.MotionEvent.ACTION_UP
|
||||||
|
&& activePointer >= 0) {
|
||||||
|
int index = event.findPointerIndex(activePointer);
|
||||||
|
if (index >= 0 && fw > 0) {
|
||||||
|
enqueueTouch("up," + logicalX(event.getX(index)) + ","
|
||||||
|
+ logicalY(event.getY(index)));
|
||||||
|
}
|
||||||
|
activePointer = -1;
|
||||||
|
} else if (action == android.view.MotionEvent.ACTION_CANCEL) {
|
||||||
|
activePointer = -1;
|
||||||
|
enqueueTouch("cancel,0,0");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onDraw(android.graphics.Canvas canvas) {
|
protected void onDraw(android.graphics.Canvas canvas) {
|
||||||
synchronized (lock) {
|
synchronized (lock) {
|
||||||
|
|||||||
@@ -5,13 +5,15 @@
|
|||||||
|
|
||||||
local SecondScreen = {}
|
local SecondScreen = {}
|
||||||
local C = nil
|
local C = nil
|
||||||
|
local ffi = nil
|
||||||
|
|
||||||
local function log(msg)
|
local function log(msg)
|
||||||
pcall(function() require("src.core.Logger").info("SecondScreen: %s", msg) end)
|
pcall(function() require("src.core.Logger").info("SecondScreen: %s", msg) end)
|
||||||
end
|
end
|
||||||
|
|
||||||
do
|
do
|
||||||
local ok, ffi = pcall(require, "ffi")
|
local ok
|
||||||
|
ok, ffi = pcall(require, "ffi")
|
||||||
if not (ok and ffi) then
|
if not (ok and ffi) then
|
||||||
log("ffi unavailable (not LuaJIT); second display disabled")
|
log("ffi unavailable (not LuaJIT); second display disabled")
|
||||||
else
|
else
|
||||||
@@ -19,6 +21,7 @@ do
|
|||||||
int love_android_secondary_ready();
|
int love_android_secondary_ready();
|
||||||
void love_android_push_secondary(const void *rgba, int w, int h);
|
void love_android_push_secondary(const void *rgba, int w, int h);
|
||||||
void love_android_secondary_enable(int on);
|
void love_android_secondary_enable(int on);
|
||||||
|
const char *love_android_poll_secondary_touch();
|
||||||
]])
|
]])
|
||||||
local okLib, lib = pcall(ffi.load, "love")
|
local okLib, lib = pcall(ffi.load, "love")
|
||||||
if okLib and lib and pcall(function() return lib.love_android_secondary_ready end) then
|
if okLib and lib and pcall(function() return lib.love_android_secondary_ready end) then
|
||||||
@@ -51,6 +54,17 @@ function SecondScreen.push(imageData, w, h)
|
|||||||
end)
|
end)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-- Returns the oldest queued secondary-display event as "action,x,y", where
|
||||||
|
-- coordinates are in the submitted frame's pixel space.
|
||||||
|
function SecondScreen.pollTouch()
|
||||||
|
if not C then return nil end
|
||||||
|
local ok, event = pcall(function()
|
||||||
|
return C.love_android_poll_secondary_touch()
|
||||||
|
end)
|
||||||
|
if not ok or event == nil or event == ffi.NULL then return nil end
|
||||||
|
return ffi.string(event)
|
||||||
|
end
|
||||||
|
|
||||||
function SecondScreen.setEnabled(on)
|
function SecondScreen.setEnabled(on)
|
||||||
if not C then return end
|
if not C then return end
|
||||||
pcall(function() C.love_android_secondary_enable(on and 1 or 0) end)
|
pcall(function() C.love_android_secondary_enable(on and 1 or 0) end)
|
||||||
|
|||||||
@@ -0,0 +1,42 @@
|
|||||||
|
package.path = "./?.lua;./?/init.lua;" .. package.path
|
||||||
|
|
||||||
|
local T = require("tests.harness")
|
||||||
|
local name = "src.render.SecondScreen"
|
||||||
|
local oldModule = package.loaded[name]
|
||||||
|
local oldFfi = package.loaded.ffi
|
||||||
|
local oldPreload = package.preload.ffi
|
||||||
|
local null = {}
|
||||||
|
local calls = 0
|
||||||
|
local C = {
|
||||||
|
love_android_secondary_ready = function() return 1 end,
|
||||||
|
love_android_push_secondary = function() end,
|
||||||
|
love_android_secondary_enable = function() end,
|
||||||
|
love_android_poll_secondary_touch = function()
|
||||||
|
calls = calls + 1
|
||||||
|
return calls == 1 and "down,12,34" or null
|
||||||
|
end,
|
||||||
|
}
|
||||||
|
local fakeFfi = {
|
||||||
|
C = C,
|
||||||
|
NULL = null,
|
||||||
|
cdef = function() end,
|
||||||
|
load = function() return C end,
|
||||||
|
string = function(value) return value end,
|
||||||
|
}
|
||||||
|
|
||||||
|
package.loaded[name] = nil
|
||||||
|
package.loaded.ffi = nil
|
||||||
|
package.preload.ffi = function() return fakeFfi end
|
||||||
|
|
||||||
|
local SecondScreen = require(name)
|
||||||
|
T.eq(SecondScreen.pollTouch(), "down,12,34",
|
||||||
|
"secondary touch reaches the Lua facade")
|
||||||
|
T.eq(SecondScreen.pollTouch(), nil, "an empty native touch queue returns nil")
|
||||||
|
C.love_android_poll_secondary_touch = nil
|
||||||
|
T.eq(SecondScreen.pollTouch(), nil, "an older native bridge remains safe")
|
||||||
|
|
||||||
|
package.loaded[name] = oldModule
|
||||||
|
package.loaded.ffi = oldFfi
|
||||||
|
package.preload.ffi = oldPreload
|
||||||
|
|
||||||
|
T.finish("second-screen touch facade")
|
||||||
Reference in New Issue
Block a user