2017-12-18 22:04:17 +01:00
|
|
|
namespace pxsim.visuals {
|
|
|
|
export interface LinearGradientDefinition {
|
|
|
|
stops: LinearGradientStop[];
|
|
|
|
}
|
|
|
|
|
|
|
|
export interface LinearGradientStop {
|
|
|
|
offset: string | number;
|
|
|
|
color: string;
|
|
|
|
}
|
|
|
|
|
|
|
|
export type TouchCallback = (event: MouseEvent | TouchEvent | PointerEvent) => void;
|
|
|
|
|
2018-05-02 22:54:06 +02:00
|
|
|
export function touchEvents(e: SVGElement | SVGElement[], move?: TouchCallback, down?: TouchCallback, up?: TouchCallback) {
|
2017-12-18 22:04:17 +01:00
|
|
|
if (Array.isArray(e)) {
|
2018-05-02 22:54:06 +02:00
|
|
|
e.forEach(el => bindEvents(el, move, down, up));
|
2017-12-18 22:04:17 +01:00
|
|
|
}
|
|
|
|
else {
|
2018-05-02 22:54:06 +02:00
|
|
|
bindEvents(e, move, down, up);
|
2017-12-18 22:04:17 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-05-02 22:54:06 +02:00
|
|
|
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;
|
|
|
|
|
2017-12-18 22:04:17 +01:00
|
|
|
if ((window as any).PointerEvent) {
|
2018-05-02 22:54:06 +02:00
|
|
|
if (downEvent) e.addEventListener("pointerdown", downEvent);
|
2017-12-18 22:04:17 +01:00
|
|
|
}
|
|
|
|
else {
|
2018-05-02 22:54:06 +02:00
|
|
|
if (downEvent) e.addEventListener("mousedown", downEvent);
|
2017-12-18 22:04:17 +01:00
|
|
|
|
|
|
|
if (pxsim.svg.isTouchEnabled()) {
|
2018-05-02 22:54:06 +02:00
|
|
|
if (downEvent) e.addEventListener("touchstart", downEvent);
|
2017-12-18 22:04:17 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
}
|