<< All versions

Skill v1.0.1

currentAutomated scan100/100
tapwithus/tap-ios-sdk/tapkit-dpad

+4 new

──Details
PublishedOctober 26, 2026 at 12:34 AM
Content Hashsha256:3543355303574fca...
Git SHAa584c83998c2
Bump Typepatch
Compare with v1.0.0
──Files
Files (1 file, 5.4 KB)
SKILL.md5.4 KBactive
SKILL.md · 123 lines · 5.4 KB

version: "1.0.1" name: tapkit-dpad description: Build a D-Pad style controller on TapXR or TapBand. Swipes move, short clicks select, a pinch-hold locks into rotate or drag, and a fist hides. Use for menus, select and move, drag, rotate, or game controls.


D-Pad (swipe, pinch, hold to rotate or drag)

TapXR (current developer firmware) and TapBand. It needs tapXRAirGestured and IMU motion. Same interaction as the D-Pad page in the TAP_EV sandbox (https://tapwithus.github.io/TAP_EV/). TapBand is coming soon, same BLE API: https://www.tapwithus.com/tapband-waitlist/

The iOS SDK already collapses the raw gesture stream into edges. Drop is the release and FistEnd is the show, so this controller does not count a run of "none" packets. It still ignores a second copy of the same edge within 40 ms. See tapkit-air-gestures for raw class indexes vs the processed layer.

Interaction

User doesTAPXRAirGestureEvent
Swipe left / right / up / downSwipeLeft 17, SwipeRight 18, SwipeUp 19, SwipeDown 20.direction("left") and so on
Short pinch thumb + index / middle / ring / pinkyClickIndex…ClickPinky (1–4).pinch(0...3), only when nothing is held
Hold a pinchDragIndex…DragPinky (5–8).hold(i), mode "pending"
…and twist 25° or more within 1 sroll from tapDidChangeOrientation.mode("rotate"), then .rotate(degrees)
…and do not twist—after 1 s .mode("drag"), then .drag
Relax the handDrop (9).release(i)
Make a fist / relax the fistFistBegin 14 / FistEnd 15.hide / .show

A repeat of the same swipe, click, drag, or fist-begin within 40 ms is ignored.

mermaid
stateDiagram-v2
idle --> pending: DragIndex..DragPinky
pending --> rotate: roll travel >= 25 deg
pending --> drag: 1 s without enough roll
rotate --> idle: Drop
drag --> idle: Drop
idle --> hidden: FistBegin
hidden --> idle: FistEnd

Use the template

Copy scripts/DpadController.swift. It has no Bluetooth in it. TAPKit-Example compiles that same file into the Knob / D-Pad screen (GestureSampleViewController). The example target is Swift 4, and the script stays on that syntax.

  • onGesture(rawValue, now:) takes gesture.rawValue and ProcessInfo.processInfo.systemUptime (or any monotonic seconds).
  • onMotion(dx:dy:roll:now:) takes moused deltas and orientation roll.
  • tick(now:) locks drag if motion packets stop before the window ends.
  • held, mode ("pending", "rotate", "drag", or nil), and hidden are the state to draw.

Required device setup, after start() and tapConnected. Same as the knob: controller mode, model detection, IMU motion, air-gesture model, stream op mode. On TapXR and TapBand, setTAPXRState(.airMouse()) covers the vision model.

swift
private var latestDelta = (dx: 0, dy: 0)
func tapXRAirGestured(identifier: String, gesture: TAPXRAirGesture) {
apply(dpad.onGesture(gesture.rawValue, now: ProcessInfo.processInfo.systemUptime), identifier)
}
func moused(identifier: String, velocityX: Int16, velocityY: Int16, isMouse: Bool) {
guard isMouse else { return }
latestDelta = (Int(velocityX), Int(velocityY))
}
func tapDidChangeOrientation(roll: Int, pitch: Int, yaw: Int) {
let sample = latestDelta
latestDelta = (0, 0)
apply(
dpad.onMotion(dx: sample.dx, dy: sample.dy, roll: Double(roll), now: ProcessInfo.processInfo.systemUptime),
nil
)
}
func apply(_ events: [DpadEvent], _ identifier: String?) {
for event in events {
switch event {
case .direction(let name):
move(name)
case .pinch(let index):
select(index)
buzz(identifier)
case .hold(_), .mode(_):
buzz(identifier)
case .rotate(let degrees):
rotate(degrees)
case .drag(let dx, let dy):
drag(dx, dy)
case .release(_):
snapBack()
case .hide:
hideControls()
case .show:
showControls()
}
}
}
func buzz(_ identifier: String?) {
guard let identifier else { return }
TAPKit.sharedKit.vibrate(durations: [40], forIdentifiers: [identifier])
}

move, select, rotate, drag, snapBack, hideControls, and showControls are the app's own functions. Orientation has no device identifier; keep a single active Tap.

Tuning

ArgumentDefaultEffect
rotateLockDegrees25Roll travel that chooses rotate. Raise it if drag is chosen too rarely.
lockWindow1.0Seconds to decide between rotate and drag.
debounce0.040Ignore a repeat of the same edge inside this window.
  • Drag deltas are raw device units. Multiply by a gain (TAP_EV uses about 0.45 points per unit) and clamp to the screen.
  • Rotate degrees are relative to the start of the hold. TAP_EV multiplies by about 1.8 for a stronger visual turn.
  • If a drag should not stay after release, snap the item back on .release.

PotentialDragOrClick* (raw 10–13) is ignored. Wait for the click, drag, or drop.

Ideas

  • Four shapes, each selected by its pinch. Hold to rotate or move one of them.
  • Menu: swipes move a highlight, ClickIndex confirms, ClickMiddle goes back, fist closes.
  • Photo viewer: swipes change the photo, hold and twist rotates, hold and drag pans.

See also tapkit-knob and tapkit-air-gestures.

← v1.0.0All versions