b69af383a6
* Use learnings from touch events in Blockly and apply to the Ev3 sim. Register move and up events on the document rather than the individual element allowing users greater flexibility where they move their mouse once it's down. * Add mouse leave events
151 lines
5.6 KiB
TypeScript
151 lines
5.6 KiB
TypeScript
namespace pxsim.visuals {
|
|
export interface LinearGradientDefinition {
|
|
stops: LinearGradientStop[];
|
|
}
|
|
|
|
export interface LinearGradientStop {
|
|
offset: string | number;
|
|
color: string;
|
|
}
|
|
|
|
export type TouchCallback = (event: MouseEvent | TouchEvent | PointerEvent) => void;
|
|
|
|
export function touchEvents(e: SVGElement | SVGElement[], move?: TouchCallback, down?: TouchCallback, up?: TouchCallback) {
|
|
if (Array.isArray(e)) {
|
|
e.forEach(el => bindEvents(el, move, down, up));
|
|
}
|
|
else {
|
|
bindEvents(e, move, down, up);
|
|
}
|
|
}
|
|
|
|
function bindEvents(e: SVGElement, move?: TouchCallback, down?: TouchCallback, up?: TouchCallback) {
|
|
|
|
const moveEvent = move ? (ev: MouseEvent) => {
|
|
move.call(this, ev);
|
|
ev.preventDefault();
|
|
ev.stopPropagation();
|
|
} : undefined;
|
|
|
|
const enterEvent = move ? (ev: MouseEvent) => {
|
|
if (ev.buttons != 1) {
|
|
// cancel all events when we re-enter without a button down
|
|
upEvent(ev);
|
|
}
|
|
} : undefined;
|
|
|
|
const upEvent = up ? (ev: MouseEvent) => {
|
|
up.call(this, ev);
|
|
ev.preventDefault();
|
|
ev.stopPropagation();
|
|
|
|
// Unregister document up and move events
|
|
if ((window as any).PointerEvent) {
|
|
if (moveEvent) document.removeEventListener("pointermove", moveEvent);
|
|
if (upEvent) document.removeEventListener("pointerup", upEvent);
|
|
if (upEvent) document.removeEventListener("pointercancel", upEvent);
|
|
if (moveEvent) document.removeEventListener("pointerenter", enterEvent);
|
|
} else {
|
|
if (moveEvent) document.removeEventListener("mousemove", moveEvent);
|
|
if (upEvent) document.removeEventListener("mouseup", upEvent);
|
|
if (moveEvent) document.removeEventListener("mouseenter", enterEvent);
|
|
if (pxsim.svg.isTouchEnabled()) {
|
|
if (moveEvent) document.removeEventListener("touchmove", moveEvent);
|
|
if (upEvent) document.removeEventListener("touchend", upEvent);
|
|
if (upEvent) document.removeEventListener("touchcancel", upEvent);
|
|
}
|
|
}
|
|
} : undefined;
|
|
|
|
const downEvent = down ? (ev: MouseEvent) => {
|
|
down.call(this, ev);
|
|
ev.preventDefault();
|
|
ev.stopPropagation();
|
|
|
|
// Register document up and move events
|
|
if ((window as any).PointerEvent) {
|
|
if (moveEvent) document.addEventListener("pointermove", moveEvent);
|
|
if (upEvent) document.addEventListener("pointerup", upEvent);
|
|
if (upEvent) document.addEventListener("pointercancel", upEvent);
|
|
if (moveEvent) document.addEventListener("pointerenter", enterEvent);
|
|
} else {
|
|
if (moveEvent) document.addEventListener("mousemove", moveEvent);
|
|
if (upEvent) document.addEventListener("mouseup", upEvent);
|
|
if (moveEvent) document.addEventListener("mouseenter", enterEvent);
|
|
|
|
if (pxsim.svg.isTouchEnabled()) {
|
|
if (moveEvent) document.addEventListener("touchmove", moveEvent);
|
|
if (upEvent) document.addEventListener("touchend", upEvent);
|
|
if (upEvent) document.addEventListener("touchcancel", upEvent);
|
|
}
|
|
}
|
|
} : undefined;
|
|
|
|
if ((window as any).PointerEvent) {
|
|
if (downEvent) e.addEventListener("pointerdown", downEvent);
|
|
}
|
|
else {
|
|
if (downEvent) e.addEventListener("mousedown", downEvent);
|
|
|
|
if (pxsim.svg.isTouchEnabled()) {
|
|
if (downEvent) e.addEventListener("touchstart", downEvent);
|
|
}
|
|
}
|
|
}
|
|
|
|
export function createGradient(id: string, opts: LinearGradientDefinition) {
|
|
const g = svg.elt("linearGradient") as SVGLinearGradientElement;
|
|
g.setAttribute("id", id);
|
|
|
|
opts.stops.forEach(stop => {
|
|
let offset: string;
|
|
|
|
if (typeof stop.offset === "number") {
|
|
offset = stop.offset + "%"
|
|
}
|
|
else {
|
|
offset = stop.offset as string;
|
|
}
|
|
|
|
svg.child(g, "stop", { offset, "stop-color": stop.color });
|
|
});
|
|
|
|
return g;
|
|
}
|
|
|
|
export function updateGradient(gradient: SVGLinearGradientElement, opts: LinearGradientDefinition) {
|
|
let j = 0;
|
|
|
|
forEachElement(gradient.childNodes, (e, i) => {
|
|
if (i < opts.stops.length) {
|
|
const stop = opts.stops[i];
|
|
e.setAttribute("offset", offsetString(stop.offset));
|
|
e.setAttribute("stop-color", stop.color);
|
|
}
|
|
else {
|
|
gradient.removeChild(e);
|
|
}
|
|
j = i + 1;
|
|
});
|
|
|
|
for (; j < opts.stops.length; j++) {
|
|
const stop = opts.stops[j];
|
|
svg.child(gradient, "stop", { offset: offsetString(stop.offset), "stop-color": stop.color });
|
|
}
|
|
}
|
|
|
|
export function forEachElement(nodes: NodeList, cb: (e: Element, i: number) => void) {
|
|
let index = 0;
|
|
for (let i = 0; i < nodes.length; i++) {
|
|
const node = nodes[i];
|
|
if (node.nodeType === Node.ELEMENT_NODE) {
|
|
cb(node as Element, index);
|
|
++index;
|
|
}
|
|
}
|
|
}
|
|
|
|
function offsetString(offset: string | number) {
|
|
return (typeof offset === "number") ? offset + "%" : offset;
|
|
}
|
|
} |