Thank you for your this lead. I’m gonna try to look into it further!
In the meantime, I’ve tried to reduce the problematic code to its bare minimum in order to reproduce the issue and locate its origin. It’s slightly different from my original project but it behaves exactly the same.
If anyone wanna take a look, here’s a zip file: Solar2D-TouchIssue.zip (5.1 KB)
The source code will also be added at the end of this post.
What I’m basically doing :
-
I have a button that is inserted into a group with createButtons().
This button (and every other button created after) is added to a buttonList.
-
A gaussion blur filter is applied to the whole group (render.lua)
with applyShaderEffect().
Since it’s using graphics.newTexture
, touch events can’t work properly.
-
A touch overlay is being created with createTouchZone()
: it receives every touch inputs and, after scanning the buttonList based on the coordinates of each button, it redispatches each event accordingly.
It works… Well, almost. It not consistent and seems pretty random, but after interacting with the button, the console randomly throws the “Lua Runtime Error: lua_pcall failed with status: 2, error message is: D:\a\corona\corona\platform\resources\init.lua:-1: attempt to call a table value” error.
Based on what you’ve described and from what my own investigations, the closest thing that may be related to the issue revolves around main.lua at line 191
:
target:dispatchEvent(event)
But I’m not sure how to deal with it.
Here’s a video of my issue : https://streamable.com/dzs109
The safest way to reproduce the bug is to click and hold on the white rectangle, move the cursor and release. It’s not consistant and multiples tries -and even relaunch of the simulator- may be needed to reproduce the error.
All of this has been made with the help of @Kan98 that I’m allowing myself to mention if by any chances he has time to take a look!
MAIN.LUA
local _common = require("common")
local screenOriginX = math.abs(display.screenOriginX)
local screenOriginY = math.abs(display.screenOriginY)
local realWidth = (display.contentWidth+screenOriginX*2)
local realHeight = (display.contentHeight+screenOriginY*2)
local _W = realWidth*0.5
local _H = realHeight*0.5
---------------------------------------------------------------------------------------------------------------
-- GENERAL GROUP
---------------------------------------------------------------------------------------------------------------
_common.generalGroup = display.newGroup()
_common.generalGroup.x = _W
_common.generalGroup.y = _H
---------------------------------------------------------------------------------------------------------------
-- BUTTON CREATION
---------------------------------------------------------------------------------------------------------------
local buttonList = {}
function createButtons()
-- Button creation
local button = display.newRect(0,0,200,200)
_common.generalGroup:insert(button)
button.name = "testButton"
-- Button touch listener (basically does nothing)
local function touchListener(event)
print("touchListener() - event.phase = " .. event.phase)
if event.phase == "began" then
display.getCurrentStage():setFocus(event.target)
return true
elseif event.phase == "moved" then
elseif event.phase == "ended" or event.phase == "cancelled" then
display.getCurrentStage():setFocus(setFocus)
return true
end
end
button:addEventListener("touch", touchListener)
-- Adding the button to the buttonList
local slot = #buttonList + 1
buttonList[slot] = {}
buttonList[slot].obj = button
buttonList[slot].objId = slot
end
---------------------------------------------------------------------------------------------------------------
-- GET ROTATED RECTANGLE BOUNDS
---------------------------------------------------------------------------------------------------------------
local function getRotatedRectangleBounds(centerX, centerY, width, height, rotationAngle)
-- Convert angle to radians
local angle = math.rad(rotationAngle)
local cos = math.cos(angle)
local sin = math.sin(angle)
-- Calculate half dimensions
local halfW = width / 2
local halfH = height / 2
-- Calculate the four corners relative to center (before rotation)
local corners = {
{x = -halfW, y = -halfH},
{x = halfW, y = -halfH},
{x = halfW, y = halfH},
{x = -halfW, y = halfH}
}
-- Rotate each corner and find min/max values
local xMin, xMax, yMin, yMax
for i, corner in ipairs(corners) do
-- Apply rotation
local x = centerX + corner.x * cos - corner.y * sin
local y = centerY + corner.x * sin + corner.y * cos
-- Initialize or compare
if i == 1 then
xMin, xMax = x, x
yMin, yMax = y, y
else
xMin = math.min(xMin, x)
xMax = math.max(xMax, x)
yMin = math.min(yMin, y)
yMax = math.max(yMax, y)
end
end
return xMin, xMax, yMin, yMax
end
---------------------------------------------------------------------------------------------------------------
-- VERIFY IF INBOUNDS
---------------------------------------------------------------------------------------------------------------
local function inBounds(event, obj, order)
if not event or not event.x or not event.y then
return false
end
local ex = _common.mouseX
local ey = _common.mouseY
local bounds = obj.contentBounds or {}
local objRealX, objRealY = obj:localToContent(0,0)
local xMin, xMax, yMin, yMax = getRotatedRectangleBounds(objRealX, objRealY, obj.width, obj.height, obj.rotation)
local coordComparisonstring = xMin .. " < " .. _common.mouseX .. " < " .. xMax .. " - " .. yMin .. " < " .. _common.mouseY .. " < " .. yMax
if bounds then
if ex < xMin or ex > xMax or ey < yMin or ey > yMax then
--print(coordComparisonstring .. " : OUTSIDE OBJ - " .. obj.name)
return false
else
--print(coordComparisonstring .. " : INSIDE OBJ - " .. obj.name)
local bounds = {
xMin = xMin,
xMax = xMax,
yMin = yMin,
yMax = yMax,
}
return true, bounds
end
return false
end
end
---------------------------------------------------------------------------------------------------------------
-- TRANSPARENT OVERLAY THAT RECEIVES EVERY TOUCH EVENTS AND DISPATCHES THEM TO ANY OTHER BUTTON
---------------------------------------------------------------------------------------------------------------
function createTouchZone(details)
-- touchZone
touchZone = display.newRect(0,0,display.contentWidth+screenOriginX*2, display.contentHeight+screenOriginY*2)
touchZone.anchorX = 0
touchZone.anchorY = 0
touchZone.isHitTestable = true
touchZone:setFillColor(1,0,1,0.0)
-- touchListener
local function touchObjectListener(event)
_common.mouseX = event.x
_common.mouseY = event.y
-------------------------------------------------------------------------------------------------------
-- REDISPATCHINGH EVENT
-------------------------------------------------------------------------------------------------------
--print("redispatchEvent()")
local objs = {}
-- Find "touchables"
for i=#buttonList, 1, -1 do
local obj = buttonList[i].obj
local isInBounds
isInBounds, obj.bounds = inBounds(event, obj, i)
if isInBounds then
objs[#objs + 1] = obj
end
end
for i=1, #objs do
local target = objs[i]
event.target = target
local handled = false
if target.dispatchEvent then
handled = target:dispatchEvent(event)
end
if handled then
--print("redispatchEvent() - event.phase:" .. string.upper(event.phase) .. " - " .. target.name)
--print("1) name:" .. target.name .. " - phase:" .. event.phase .. " - _common.mouseX,_common.mouseY: " .. _common.mouseX, _common.mouseY .. " || " .. realXMin, realXMax, realYMin, realYMax)
return target
end
end
return true
end
touchZone:addEventListener("touch", touchObjectListener)
-- Keep the touchZone in front
local function movetoFront(event)
touchZone:toFront()
end
Runtime:addEventListener("enterFrame", movetoFront)
end
---------------------------------------------------------------------------------------------------------------
-- APPLYING THE BLUR SHADER
---------------------------------------------------------------------------------------------------------------
function applyShaderEffect()
require("render")
end
---------------------------------------------------------------------------------------------------------------
-- START
---------------------------------------------------------------------------------------------------------------
createButtons()
applyShaderEffect()
createTouchZone()
RENDER.LUA
local _common = require("common")
local screenOriginX = math.abs(display.screenOriginX)
local screenOriginY = math.abs(display.screenOriginY)
local realWidth = (display.contentWidth+screenOriginX*2)
local realHeight = (display.contentHeight+screenOriginY*2)
local _W = realWidth*0.5
local _H = realHeight*0.5
local stage = display.getCurrentStage()
local render, view
local group = _common.generalGroup
local setFocus = stage.setFocus
local focusedObj
-------------------------------------------------------------------------------------------------------
-- RESIZE
-------------------------------------------------------------------------------------------------------
local function resize()
local aw = realWidth
local ah = realHeight
local width = aw
local height = ah
local centerX = aw*0.5
local centerY = ah*0.5
display.remove(render)
render = graphics.newTexture({type="canvas", width=aw, height=ah})
render.anchorX = -(centerX / aw)
render.anchorY = -(centerY / ah)
display.remove(view)
view = display.newImageRect(render.filename, render.baseDir, width, height)
view.anchorX = 0
view.anchorY = 0
view.x = 0
view.y = 0
view.fill.effect = "filter.blurGaussian" -- your custom Filter Effect goes here
view.fill.effect.horizontal.blurSize = 256
view.fill.effect.horizontal.sigma = 8
view.fill.effect.vertical.blurSize = 256
view.fill.effect.vertical.sigma = 8
view._isRenderer = true
_common.view = view
end
-------------------------------------------------------------------------------------------------------
-- ENTERFRAME
-------------------------------------------------------------------------------------------------------
local function enterFrame(event)
render:invalidate("cache")
-- add anything new to the render queue
for i = 1, _common.generalGroup.numChildren do
local child = _common.generalGroup[i]
if child and not child._isRenderer then
group:insert(child)
end
end
-- render!
render:draw(group)
render:invalidate("cache")
end
-------------------------------------------------------------------------------------------------------
-- SHOW/HIDE EFFECT
-------------------------------------------------------------------------------------------------------
local effect = true
local function key(event)
local phase = event.phase
local name = event.keyName
if phase == "up" then
if name == "f2" then
effect = not effect
print("SWITCH CRT")
if effect then
view.fill.effect = "filter.custom.crt"
else
view.fill.effect = nil
end
end
end
end
-- Events
group._isRenderer = true
resize()
Runtime:addEventListener("key", key)
Runtime:addEventListener("resize", resize)
Runtime:addEventListener("enterFrame", enterFrame)
COMMON.LUA
local _common = {}
return _common