@@ -119,7 +119,7 @@ public struct DeltaSkinView: View {
119119 @State private var processedTouches : Set < ObjectIdentifier > = [ ]
120120
121121 // Track touch IDs that are actively touching the NDS bottom screen (for continuous updates and release)
122- @State private var touchToNDSScreenMap : [ ObjectIdentifier : Bool ] = [ : ]
122+ @State private var ndsScreenTouches : Set < ObjectIdentifier > = [ ]
123123
124124 // Track the current preview size
125125 @State private var previewSize : CGSize = . zero
@@ -865,7 +865,7 @@ public struct DeltaSkinView: View {
865865
866866 // Check if this touch is associated with a D-pad or NDS screen (needs continuous processing)
867867 let isDPadTouch = touchToDPadMap [ touch. id] != nil
868- let isNDSScreenTouch = touchToNDSScreenMap [ touch. id] != nil
868+ let isNDSScreenTouch = ndsScreenTouches . contains ( touch. id)
869869
870870 // Check if touch has moved significantly (more than 1 point)
871871 let hasMoved : Bool
@@ -941,18 +941,18 @@ public struct DeltaSkinView: View {
941941 }
942942
943943 // Release NDS bottom screen touch if this was a screen touch
944- if touchToNDSScreenMap [ touch. id] != nil {
944+ if ndsScreenTouches . contains ( touch. id) {
945945 DLOG ( " Releasing NDS screen touch for touch \( touch. id) " )
946- touchToNDSScreenMap . removeValue ( forKey : touch. id)
946+ ndsScreenTouches . remove ( touch. id)
947947 // Only signal release when the last screen touch lifts
948- if touchToNDSScreenMap . isEmpty {
948+ if ndsScreenTouches . isEmpty {
949949 inputHandler. ndsBottomScreenTouchReleased ( )
950950 }
951951 }
952952 }
953953
954954 // If all touches are gone, ensure everything is reset
955- if touchToButtonMap. isEmpty && touchToDPadMap. isEmpty && touchToNDSScreenMap . isEmpty {
955+ if touchToButtonMap. isEmpty && touchToDPadMap. isEmpty && ndsScreenTouches . isEmpty {
956956 DLOG ( " All touches ended, cleaning up " )
957957
958958 // Clear active buttons to ensure visual feedback is removed
@@ -1261,15 +1261,19 @@ public struct DeltaSkinView: View {
12611261
12621262 // NDS bottom-screen: if this touch is already tracked as an NDS screen touch,
12631263 // update the touch position or release if it has moved off the screen area.
1264- if touchToNDSScreenMap [ touchId] != nil {
1264+ if ndsScreenTouches . contains ( touchId) {
12651265 if let normalizedPoint = mapToNDSBottomScreen ( location, in: size) {
12661266 DLOG ( " DS bottom screen touch moved: normalized= \( normalizedPoint) " )
12671267 inputHandler. ndsBottomScreenTouched ( at: normalizedPoint)
12681268 } else {
1269- // Touch dragged off the bottom screen — release and fall through to button detection
1270- DLOG ( " DS bottom screen touch left screen area, releasing " )
1271- touchToNDSScreenMap. removeValue ( forKey: touchId)
1272- inputHandler. ndsBottomScreenTouchReleased ( )
1269+ // Touch dragged off the bottom screen — potentially release and fall through to button detection
1270+ DLOG ( " DS bottom screen touch left screen area for touchId= \( touchId) , removing mapping " )
1271+ ndsScreenTouches. remove ( touchId)
1272+ // Only release when this was the last active NDS bottom-screen touch
1273+ if ndsScreenTouches. isEmpty {
1274+ DLOG ( " No remaining DS bottom screen touches, releasing stylus " )
1275+ inputHandler. ndsBottomScreenTouchReleased ( )
1276+ }
12731277 }
12741278 return
12751279 }
@@ -1464,7 +1468,7 @@ public struct DeltaSkinView: View {
14641468 // Check if it is on the NDS bottom screen touchscreen area.
14651469 if let normalizedPoint = mapToNDSBottomScreen ( location, in: size) {
14661470 DLOG ( " DS bottom screen touch began: normalized= \( normalizedPoint) " )
1467- touchToNDSScreenMap [ touchId] = true
1471+ ndsScreenTouches . insert ( touchId)
14681472 inputHandler. ndsBottomScreenTouched ( at: normalizedPoint)
14691473 return
14701474 }
@@ -1596,12 +1600,38 @@ public struct DeltaSkinView: View {
15961600
15971601 guard let screen = bottomScreen, let outputFrame = screen. outputFrame else { return nil }
15981602
1603+ // Normalize outputFrame into 0–1 space if it is specified in mappingSize pixels.
1604+ // Some skins (e.g. DefaultDeltaSkin) already use normalized coordinates (0–1),
1605+ // while others may specify absolute positions in the skin's mapping space.
1606+ let isPixelBased = outputFrame. maxX > 1 || outputFrame. maxY > 1
1607+ let normalizedFrame : CGRect
1608+ if isPixelBased, mappingSize. width > 0 , mappingSize. height > 0 {
1609+ normalizedFrame = CGRect (
1610+ x: outputFrame. minX / mappingSize. width,
1611+ y: outputFrame. minY / mappingSize. height,
1612+ width: outputFrame. width / mappingSize. width,
1613+ height: outputFrame. height / mappingSize. height
1614+ )
1615+ } else {
1616+ normalizedFrame = outputFrame
1617+ }
1618+
1619+ // Convert normalized frame back into mapping-space pixels so that
1620+ // calculateButtonTransform (which works in mappingSize coordinates)
1621+ // can be applied consistently for both normalized and pixel-based skins.
1622+ let mappingFrame = CGRect (
1623+ x: normalizedFrame. minX * mappingSize. width,
1624+ y: normalizedFrame. minY * mappingSize. height,
1625+ width: normalizedFrame. width * mappingSize. width,
1626+ height: normalizedFrame. height * mappingSize. height
1627+ )
1628+
15991629 let ( scaleX, scaleY, xOffset, yOffset) = calculateButtonTransform ( in: size, mappingSize: mappingSize)
16001630 let scaledFrame = CGRect (
1601- x: outputFrame . minX * scaleX + xOffset,
1602- y: yOffset + outputFrame . minY * scaleY,
1603- width: outputFrame . width * scaleX,
1604- height: outputFrame . height * scaleY
1631+ x: mappingFrame . minX * scaleX + xOffset,
1632+ y: yOffset + mappingFrame . minY * scaleY,
1633+ width: mappingFrame . width * scaleX,
1634+ height: mappingFrame . height * scaleY
16051635 )
16061636
16071637 guard scaledFrame. contains ( location) , scaledFrame. width > 0 , scaledFrame. height > 0 else {
0 commit comments