Emberveil source

mobile/public/touch.js

export function bindTouch(surface,actions,{setTimer=setTimeout,clearTimer=clearTimeout}={}) {
  const fingers=new Map();let pointer=null,pinch=0;
  const position=e=>{const r=surface.getBoundingClientRect();return {x:Math.floor((e.clientX-r.left)*765/r.width),y:Math.floor((e.clientY-r.top)*503/r.height)};};
  function cancel(){if(pointer)clearTimer(pointer.timer);pointer=null;}
  function distance(){const [a,b]=[...fingers.values()];return Math.hypot(a.x-b.x,a.y-b.y);}
  surface.addEventListener('pointerdown',e=>{
    if(!actions.ready())return;e.preventDefault();surface.setPointerCapture(e.pointerId);fingers.set(e.pointerId,{x:e.clientX,y:e.clientY});
    if(fingers.size!==1){cancel();if(fingers.size===2)pinch=distance();return;}
    const p=position(e);pointer={id:e.pointerId,...p,startX:e.clientX,startY:e.clientY,last:p,moved:false,long:false,right:e.button===2};
    pointer.timer=setTimer(()=>{if(pointer&&!pointer.moved&&!pointer.right){pointer.long=true;actions.point(p.x,p.y,2);}},450);
  });
  surface.addEventListener('pointermove',e=>{
    if(!fingers.has(e.pointerId))return;e.preventDefault();fingers.set(e.pointerId,{x:e.clientX,y:e.clientY});
    if(fingers.size===2){const d=distance();if(Math.abs(d-pinch)>20){actions.zoom(d>pinch?-1:1);pinch=d;}return;}
    if(!pointer||pointer.id!==e.pointerId)return;
    if(Math.hypot(e.clientX-pointer.startX,e.clientY-pointer.startY)>5){
      clearTimer(pointer.timer);pointer.moved=true;const p=position(e);actions.gesture(pointer.x,pointer.y,p.x-pointer.last.x,p.y-pointer.last.y);pointer.last=p;
    }
  });
  surface.addEventListener('pointerup',e=>{
    fingers.delete(e.pointerId);if(!pointer||pointer.id!==e.pointerId)return;clearTimer(pointer.timer);
    if(!pointer.moved&&!pointer.long){const p=position(e);actions.point(p.x,p.y,pointer.right||actions.options()?2:1);actions.clearOptions();}pointer=null;
  });
  for(const name of ['pointercancel','lostpointercapture'])surface.addEventListener(name,e=>{fingers.delete(e.pointerId);if(pointer?.id===e.pointerId)cancel();});
  surface.addEventListener('wheel',e=>{if(!actions.ready())return;e.preventDefault();const p=position(e);actions.wheel(p.x,p.y,Math.sign(e.deltaY));},{passive:false});
  surface.addEventListener('contextmenu',e=>e.preventDefault());
  return ()=>{fingers.clear();cancel();};
}

export function viewportSize(width,height,compact,typingHeight=0){
  const availableWidth=Math.max(120,Math.min(980,width-18)-(compact?100:0));
  const availableHeight=Math.max(100,height-(compact?42:180)-typingHeight);
  const scale=Math.min(availableWidth/765,availableHeight/503,1.5);
  return {width:765*scale,height:503*scale,scale};
}