Code: Select all
; ==============================
; Long-Press on Gadget Desktop/Mobile
; 09/2025
; ==============================
Enumeration #PB_Event_FirstCustomValue
#PB_Event_MouseLongPress
EndEnumeration
Global G_Win_LongPress.i
Global G_Timer_LongPress.i = 1
Global G_LongPressActive.i = #False
Global G_MoveThreshold.i = 10 ; px: Bewegung > Threshold => cancel
; ---- Timer feuert Long-Press
Procedure onMouseLongpress()
If EventTimer() = G_Timer_LongPress And G_LongPressActive
G_LongPressActive = #False
RemoveWindowTimer(G_Win_LongPress, G_Timer_LongPress)
PostEvent(#PB_Event_MouseLongPress, G_Win_LongPress, G_Timer_LongPress)
Debug "onMouseLongpress() -> LONG PRESS"
EndIf
EndProcedure
; ---- Abbruch bei Loslassen
Procedure onMouseUp()
If G_LongPressActive
G_LongPressActive = #False
RemoveWindowTimer(G_Win_LongPress, G_Timer_LongPress)
Debug "onMouseUp() -> Timer Removed"
EndIf
EndProcedure
; ---- Start (Down)
Procedure onMouseDown()
If G_LongPressActive = #False
G_LongPressActive = #True
AddWindowTimer(G_Win_LongPress, G_Timer_LongPress, 600) ; 0.6 s
Debug "onMouseDown() -> Timer Added"
EndIf
EndProcedure
; ---- Sonstiger Abbruch
Procedure onMouseCancel()
If G_LongPressActive
G_LongPressActive = #False
RemoveWindowTimer(G_Win_LongPress, G_Timer_LongPress)
Debug "onMouseCancel() -> Timer Cancelled"
EndIf
EndProcedure
; ---- Initialisierung für EIN Gadget
; Aufruf: onMouseInit(GadgetID(#Gadget)) ODER onMouseInit(#Gadget)
Procedure onMouseInit(Gadget)
EnableJS
(function(){
// ---------- 1) Gadget-Handle -> echtes DOM-Element ----------
function resolveGadgetElement(g){
// a) schon ein DOM-Element?
if (g && g.nodeType === 1) return g;
// b) SpiderBasic-Wrapper mit .div?
if (g && g.div && g.div.nodeType === 1) return g.div;
// c) Nummer/Handle -> spider_GadgetID(...)
try {
if (typeof g === 'number' && typeof spider_GadgetID === 'function') {
var obj = spider_GadgetID(g);
if (obj) {
if (obj.div && obj.div.nodeType === 1) return obj.div;
// Fallbacks für ältere/andere Builds:
if (obj.nodeType === 1) return obj;
if (obj.element && obj.element.nodeType === 1) return obj.element;
if (obj.root && obj.root.nodeType === 1) return obj.root;
}
}
} catch(e) {}
return null;
}
var gadgetInput = v_gadget; // PB-Param (wird zu v_gadget)
var target = resolveGadgetElement(gadgetInput);
if (!target) {
console.warn('[LongPress] Konnte DOM-Element nicht resolven aus', gadgetInput);
return;
}
// Pro Gadget nur einmal binden
if (target.__lp_bound) return;
target.__lp_bound = true;
target.classList.add('lp-target');
// ---------- 2) iOS/Safari: CSS + touch-action ----------
if (!document.__lp_css_injected) {
document.__lp_css_injected = true;
var css = "html,body{-webkit-touch-callout:none;-webkit-user-select:none;user-select:none}.lp-target{touch-action:none;-ms-touch-action:none}";
var style = document.createElement('style');
style.type = 'text/css';
style.appendChild(document.createTextNode(css));
document.head.appendChild(style);
}
// ---------- 3) State + Hilfsfunktionen ----------
var startX=0, startY=0, moved=false, scrollAbortAttached=false;
function getXY(e){
if (e.touches && e.touches[0]) return {x:e.touches[0].clientX, y:e.touches[0].clientY};
return {x:(e.clientX||0), y:(e.clientY||0)};
}
function insideTarget(evt){
var t = evt.target;
return (t === target) || target.contains(t);
}
function startPressFromEvent(e){
var p = getXY(e); startX=p.x; startY=p.y; moved=false;
f_onmousedown();
if (!scrollAbortAttached) {
scrollAbortAttached = true;
window.addEventListener('scroll', onScrollCancel, {passive:true, once:true});
}
}
function onMoveCheck(e){
if (!g_g_longpressactive) return;
var p = getXY(e);
if (Math.abs(p.x - startX) > g_g_movethreshold || Math.abs(p.y - startY) > g_g_movethreshold) {
moved = true;
f_onmousecancel();
}
}
function onScrollCancel(){ f_onmousecancel(); scrollAbortAttached=false; }
// ---------- 4) Listener im CAPTURE-Mode an DOCUMENT ----------
var doc = target.ownerDocument || document;
var hasPointer = !!window.PointerEvent;
if (hasPointer) {
doc.addEventListener('pointerdown', function(e){
if (!insideTarget(e)) return;
var isMouseLeft = (e.pointerType === 'mouse' && e.button === 0);
var isTouch = (e.pointerType === 'touch');
if (!(isMouseLeft || isTouch)) return;
try { e.preventDefault(); } catch(_){}
startPressFromEvent(e);
}, {capture:true, passive:false}); // <-- capture:true ist der Schlüssel!
doc.addEventListener('pointermove', function(e){
if (!insideTarget(e)) return;
onMoveCheck(e);
}, {capture:true, passive:true});
doc.addEventListener('pointerup', function(e){
if (!insideTarget(e)) return;
var isMouseLeft = (e.pointerType === 'mouse' && e.button === 0);
var isTouch = (e.pointerType === 'touch');
if (isMouseLeft || isTouch) f_onmouseup();
}, {capture:true, passive:true});
doc.addEventListener('pointercancel', function(e){
if (!insideTarget(e)) return;
f_onmousecancel();
}, {capture:true, passive:true});
doc.addEventListener('pointerleave', function(e){
if (!insideTarget(e)) return;
f_onmousecancel();
}, {capture:true, passive:true});
} else {
// ---- Touch-Fallback (ältere Browser)
doc.addEventListener('touchstart', function(e){
if (!insideTarget(e)) return;
try { e.preventDefault(); } catch(_){}
startPressFromEvent(e);
}, {capture:true, passive:false});
doc.addEventListener('touchmove', function(e){
if (!insideTarget(e)) return;
onMoveCheck(e);
}, {capture:true, passive:true});
doc.addEventListener('touchend', function(e){
if (!insideTarget(e)) return;
f_onmouseup();
}, {capture:true, passive:true});
doc.addEventListener('touchcancel', function(e){
if (!insideTarget(e)) return;
f_onmousecancel();
}, {capture:true, passive:true});
// Maus-Fallback (Desktop)
doc.addEventListener('mousedown', function(e){
if (!insideTarget(e)) return;
if (e.button !== 0) return;
try { e.preventDefault(); } catch(_){}
startPressFromEvent(e);
}, {capture:true, passive:false});
doc.addEventListener('mousemove', function(e){
if (!insideTarget(e)) return;
onMoveCheck(e);
}, {capture:true, passive:true});
doc.addEventListener('mouseup', function(e){
if (!insideTarget(e)) return;
if (e.button === 0) f_onmouseup();
}, {capture:true, passive:true});
}
// Kontextmenü am Ziel abschalten (optional global)
target.addEventListener('contextmenu', function(e){ e.preventDefault(); }, {passive:false});
})();
DisableJS
; Timer-Callback nur EINMAL binden
Static timerBound.i
If timerBound = 0
BindEvent(#PB_Event_Timer, @onMouseLongpress())
timerBound = 1
EndIf
EndProcedure
; ==============================
; Demo
; ==============================
CompilerIf #PB_Compiler_IsMainFile
Procedure onLongPressDebug()
Debug "LongPress detected (Custom Event)"
Protected n, txt.s = ""
n = GetGadgetState(0)
If n >= 0
If GetGadgetItemText(0, n, 0) = ""
txt = "⭐"
EndIf
SetGadgetItemText(0, n, txt, 0)
Endif
EndProcedure
G_Win_LongPress = OpenWindow(#PB_Any, 0, 0, 420, 320, "Long Press Demo", #PB_Window_SystemMenu | #PB_Window_ScreenCentered)
; ButtonGadget(0, 20, 20, 180, 44, "Press me ~ 1 Sec.")
ListIconGadget(0, 20, 20, 360, 200, "", 100)
AddGadgetColumn(0, 1, "Name", 240)
AddGadgetItem(0, -1, #LF$ + "Jane Doe")
AddGadgetItem(0, -1, #LF$ + "John Doe")
TextGadget(1, 20, 250, 360, 50, "Press and hold the item to mark it as a favorite.")
onMouseInit(GadgetID(0))
BindEvent(#PB_Event_MouseLongPress, @onLongPressDebug())
CompilerEndIf