Skip to content
Snippets Groups Projects
Commit 69a0de70 authored by Alexander Rose's avatar Alexander Rose
Browse files

added utilites for handling mouse input

parent 6e6a0d96
Branches
Tags
No related merge requests found
/**
* Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
/*
* This code has been modified from https://github.com/mikolalysenko/mouse-change,
* copyright (c) 2015 Mikola Lysenko. MIT License
*/
import * as mouse from './mouse-event'
export type MouseModifiers = {
shift: boolean,
alt: boolean,
control: boolean,
meta: boolean
}
export type MouseChangeCallback = (buttonState: number, x: number, y: number, mods: MouseModifiers) => void
export default function mouseListen (element: Element, callback: MouseChangeCallback) {
let buttonState = 0
let x = 0
let y = 0
const mods: MouseModifiers = {
shift: false,
alt: false,
control: false,
meta: false
}
let attached = false
function updateMods (event: MouseEvent | KeyboardEvent) {
let changed = false
if ('altKey' in event) {
changed = changed || event.altKey !== mods.alt
mods.alt = !!event.altKey
}
if ('shiftKey' in event) {
changed = changed || event.shiftKey !== mods.shift
mods.shift = !!event.shiftKey
}
if ('ctrlKey' in event) {
changed = changed || event.ctrlKey !== mods.control
mods.control = !!event.ctrlKey
}
if ('metaKey' in event) {
changed = changed || event.metaKey !== mods.meta
mods.meta = !!event.metaKey
}
return changed
}
function handleEvent (nextButtons: number, event: MouseEvent) {
const nextX = mouse.x(event)
const nextY = mouse.y(event)
if ('buttons' in event) {
nextButtons = event.buttons | 0
}
if (nextButtons !== buttonState || nextX !== x || nextY !== y || updateMods(event) ) {
buttonState = nextButtons | 0
x = nextX || 0
y = nextY || 0
callback && callback(buttonState, x, y, mods)
}
}
function clearState (event: MouseEvent) {
handleEvent(0, event)
}
function handleBlur () {
if (buttonState || x || y || mods.shift || mods.alt || mods.meta || mods.control) {
x = y = 0
buttonState = 0
mods.shift = mods.alt = mods.control = mods.meta = false
callback && callback(0, 0, 0, mods)
}
}
function handleMods (event: MouseEvent | KeyboardEvent) {
if (updateMods(event)) {
callback && callback(buttonState, x, y, mods)
}
}
function handleMouseMove (event: MouseEvent) {
if (mouse.buttons(event) === 0) {
handleEvent(0, event)
} else {
handleEvent(buttonState, event)
}
}
function handleMouseDown (event: MouseEvent) {
handleEvent(buttonState | mouse.buttons(event), event)
}
function handleMouseUp (event: MouseEvent) {
handleEvent(buttonState & ~mouse.buttons(event), event)
}
function attachListeners () {
if (attached) return
attached = true
element.addEventListener('mousemove', handleMouseMove as EventListener)
element.addEventListener('mousedown', handleMouseDown as EventListener)
element.addEventListener('mouseup', handleMouseUp as EventListener)
element.addEventListener('mouseleave', clearState as EventListener)
element.addEventListener('mouseenter', clearState as EventListener)
element.addEventListener('mouseout', clearState as EventListener)
element.addEventListener('mouseover', clearState as EventListener)
element.addEventListener('blur', handleBlur)
element.addEventListener('keyup', handleMods as EventListener)
element.addEventListener('keydown', handleMods as EventListener)
element.addEventListener('keypress', handleMods as EventListener)
if (!(element instanceof Window)) {
window.addEventListener('blur', handleBlur)
window.addEventListener('keyup', handleMods)
window.addEventListener('keydown', handleMods)
window.addEventListener('keypress', handleMods)
}
}
function detachListeners () {
if (!attached) return
attached = false
element.removeEventListener('mousemove', handleMouseMove as EventListener)
element.removeEventListener('mousedown', handleMouseDown as EventListener)
element.removeEventListener('mouseup', handleMouseUp as EventListener)
element.removeEventListener('mouseleave', clearState as EventListener)
element.removeEventListener('mouseenter', clearState as EventListener)
element.removeEventListener('mouseout', clearState as EventListener)
element.removeEventListener('mouseover', clearState as EventListener)
element.removeEventListener('blur', handleBlur)
element.removeEventListener('keyup', handleMods as EventListener)
element.removeEventListener('keydown', handleMods as EventListener)
element.removeEventListener('keypress', handleMods as EventListener)
if (!(element instanceof Window)) {
window.removeEventListener('blur', handleBlur)
window.removeEventListener('keyup', handleMods)
window.removeEventListener('keydown', handleMods)
window.removeEventListener('keypress', handleMods)
}
}
// Attach listeners
attachListeners()
const result = {
element: element
}
Object.defineProperties(result, {
enabled: {
get: function () { return attached },
set: function (f) {
if (f) {
attachListeners()
} else {
detachListeners()
}
},
enumerable: true
},
buttons: {
get: function () { return buttonState },
enumerable: true
},
x: {
get: function () { return x },
enumerable: true
},
y: {
get: function () { return y },
enumerable: true
},
mods: {
get: function () { return mods },
enumerable: true
}
})
return result
}
\ No newline at end of file
/**
* Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
/*
* This code has been modified from https://github.com/mikolalysenko/mouse-event,
* copyright (c) 2015 Mikola Lysenko. MIT License
*/
export function buttons(event: MouseEvent) {
if (typeof event === 'object') {
if ('buttons' in event) {
return event.buttons
} else if ('which' in event) {
const b = (event as any).which // 'any' to support older browsers
if (b === 2) {
return 4
} else if (b === 3) {
return 2
} else if (b > 0) {
return 1<<(b-1)
}
} else if ('button' in event) {
const b = (event as any).button // 'any' to support older browsers
if (b === 1) {
return 4
} else if (b === 2) {
return 2
} else if (b >= 0) {
return 1<<b
}
}
}
return 0
}
export function element(event: MouseEvent) {
return event.target as Element || event.srcElement || window
}
export function x(event: MouseEvent) {
if (typeof event === 'object') {
if ('offsetX' in event) {
return event.offsetX
}
const target = element(event)
const bounds = target.getBoundingClientRect()
return (event as any).clientX - bounds.left // 'any' to support older browsers
}
return 0
}
export function y(event: MouseEvent) {
if (typeof event === 'object') {
if ('offsetY' in event) {
return event.offsetY
}
const target = element(event)
const bounds = target.getBoundingClientRect()
return (event as any).clientY - bounds.top // 'any' to support older browsers
}
return 0
}
\ No newline at end of file
/**
* Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
/*
* This code has been modified from https://github.com/mikolalysenko/mouse-wheel,
* copyright (c) 2015 Mikola Lysenko. MIT License
*/
import toPixels from './to-pixels'
export type MouseWheelCallback = (dx: number, dy: number, dz: number, event: MouseWheelEvent) => void
export default function mouseWheelListen(element: Element, callback: MouseWheelCallback, noScroll = false) {
const lineHeight = toPixels('ex', element)
const listener = function (event: MouseWheelEvent) {
if (noScroll) {
event.preventDefault()
}
const mode = event.deltaMode
let dx = event.deltaX || 0
let dy = event.deltaY || 0
let dz = event.deltaZ || 0
let scale = 1
switch (mode) {
case 1: scale = lineHeight; break
case 2: scale = window.innerHeight; break
}
dx *= scale
dy *= scale
dz *= scale
if (dx || dy || dz) {
return callback(dx, dy, dz, event)
}
}
element.addEventListener('wheel', listener)
return listener
}
\ No newline at end of file
/**
* Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
/*
* This code has been modified from https://github.com/mattdesl/parse-unit,
* copyright (c) 2014 Matt DesLauriers. MIT License
*/
const reUnit = /[\d.\-\+]*\s*(.*)/
export default function parseUnit(str: string, out: [number, string] = [ 0, '' ]) {
str = String(str)
const num = parseFloat(str)
out[0] = num
const m = str.match(reUnit)
if (m) out[1] = m[1] || ''
return out
}
\ No newline at end of file
/**
* Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
/*
* This code has been modified from https://github.com/mikolalysenko/to-px,
* copyright (c) 2015 Mikola Lysenko. MIT License
*/
import parseUnit from './parse-unit'
const PIXELS_PER_INCH = 96
function getPropertyInPX(element: Element, prop: string) {
const parts = parseUnit(getComputedStyle(element).getPropertyValue(prop))
return parts[0] * toPixels(parts[1], element)
}
// This brutal hack is needed
function getSizeBrutal(unit: string, element: Element) {
const testDIV = document.createElement('div')
testDIV.style.setProperty('font-size', '128' + unit)
element.appendChild(testDIV)
const size = getPropertyInPX(testDIV, 'font-size') / 128
element.removeChild(testDIV)
return size
}
export default function toPixels(str: string, element: Element = document.body): number {
str = (str || 'px').trim().toLowerCase()
switch (str) {
case '%': // Ambiguous, not sure if we should use width or height
return element.clientHeight / 100.0
case 'ch':
case 'ex':
return getSizeBrutal(str, element)
case 'em':
return getPropertyInPX(element, 'font-size')
case 'rem':
return getPropertyInPX(document.body, 'font-size')
case 'vw':
return window.innerWidth/100
case 'vh':
return window.innerHeight/100
case 'vmin':
return Math.min(window.innerWidth, window.innerHeight) / 100
case 'vmax':
return Math.max(window.innerWidth, window.innerHeight) / 100
case 'in':
return PIXELS_PER_INCH
case 'cm':
return PIXELS_PER_INCH / 2.54
case 'mm':
return PIXELS_PER_INCH / 25.4
case 'pt':
return PIXELS_PER_INCH / 72
case 'pc':
return PIXELS_PER_INCH / 6
}
return 1
}
\ No newline at end of file
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment