You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

213 lines
6.7 KiB
JavaScript

class JoyWidget extends BaseWidget {
$el;
#center = 0;
#posX = null;
#posY = null;
#pressed = 0;
#_onTouchStart;
#_onTouchMove;
#_onTouchEnd;
#_onMouseDown;
#_onMouseMove;
#_onMouseUp;
constructor(data, renderer) {
super(data, renderer);
this.makeLayout({
type: 'canvas',
name: 'el'
});
if (!this.data.color)
this.data.color = colToInt(window.getComputedStyle(document.body).getPropertyValue('--prim'));
if ("ontouchstart" in document.documentElement) {
this.#_onTouchStart = this.#onTouchStart.bind(this);
this.#_onTouchMove = this.#onTouchMove.bind(this);
this.#_onTouchEnd = this.#onTouchEnd.bind(this);
this.$el.addEventListener("touchstart", this.#_onTouchStart, { passive: false });
document.addEventListener("touchmove", this.#_onTouchMove, { passive: false });
document.addEventListener("touchend", this.#_onTouchEnd);
} else {
this.#_onMouseDown = this.#onMouseDown.bind(this);
this.#_onMouseMove = this.#onMouseMove.bind(this);
this.#_onMouseUp = this.#onMouseUp.bind(this);
this.$el.addEventListener("mousedown", this.#_onMouseDown);
document.addEventListener("mousemove", this.#_onMouseMove);
document.addEventListener("mouseup", this.#_onMouseUp);
}
this.$el.parentNode.addEventListener('resize', () => {
this.#reset();
this.#redraw();
})
this.update(data);
this.disable(this.$el, data.disable);
waitFrame().then(() => {
this.#redraw(false);
});
}
close() {
if ("ontouchstart" in document.documentElement) {
this.$el.removeEventListener("touchstart", this.#_onTouchStart, { passive: false });
document.removeEventListener("touchmove", this.#_onTouchMove, { passive: false });
document.removeEventListener("touchend", this.#_onTouchEnd);
} else {
this.$el.removeEventListener("mousedown", this.#_onMouseDown);
document.removeEventListener("mousemove", this.#_onMouseMove);
document.removeEventListener("mouseup", this.#_onMouseUp);
}
}
#reset() {
this.#posX = null;
this.#posY = null;
}
#redraw(send = true) {
const cv = this.$el;
let size = cv.parentNode.clientWidth;
if (!size) return;
cv.style.width = size + 'px';
cv.style.height = size + 'px';
size *= window.devicePixelRatio;
cv.width = size;
cv.height = size;
cv.style.cursor = 'pointer';
const r = size * 0.23;
const R = size * 0.4;
this.#center = size / 2;
if (this.#posX === null) this.#posX = this.#center;
if (this.#posY === null) this.#posY = this.#center;
this.#posX = constrain(this.#posX, r, size - r);
this.#posY = constrain(this.#posY, r, size - r);
let x = Math.round((this.#posX - this.#center) / (size / 2 - r) * 255);
let y = -Math.round((this.#posY - this.#center) / (size / 2 - r) * 255);
const cx = cv.getContext("2d");
cx.clearRect(0, 0, size, size);
cx.beginPath();
cx.arc(this.#center, this.#center, R, 0, 2 * Math.PI, false);
let grd = cx.createRadialGradient(this.#center, this.#center, R * 2 / 3, this.#center, this.#center, R);
grd.addColorStop(0, '#00000005');
grd.addColorStop(1, '#00000030');
cx.fillStyle = grd;
cx.fill();
cx.beginPath();
cx.arc(this.#posX, this.#posY, r, 0, 2 * Math.PI, false);
grd = cx.createRadialGradient(this.#posX, this.#posY, 0, this.#posX, this.#posY, r);
grd.addColorStop(0, intToCol(adjustColor(this.data.color, 0.7)));
grd.addColorStop(1, intToCol(adjustColor(this.data.color, this.#pressed ? 1.3 : 1)));
cx.fillStyle = grd;
cx.fill();
if (this.data.exp) {
x = ((x * x + 255) >> 8) * (x > 0 ? 1 : -1);
y = ((y * y + 255) >> 8) * (y > 0 ? 1 : -1);
}
if (send) {
this.set((x + 255) << 16) | (y + 255);
this.setSuffix('[' + x + ',' + y + ']');
}
}
#onTouchStart(event) {
if (this.data.disabled) return;
event.preventDefault();
this.#pressed = true;
}
#onTouchMove(event) {
if (!this.#pressed) return;
event.preventDefault();
let target = null;
for (const t of event.changedTouches) {
if (t.target === this.$el) target = t;
}
if (!target) return;
this.#posX = target.pageX;
this.#posY = target.pageY;
if (this.$el.offsetParent.tagName.toUpperCase() === "BODY") {
this.#posX -= this.$el.offsetLeft;
this.#posY -= this.$el.offsetTop;
} else {
this.#posX -= this.$el.offsetParent.offsetLeft;
this.#posY -= this.$el.offsetParent.offsetTop;
}
const ratio = window.devicePixelRatio;
this.#posX *= ratio;
this.#posY *= ratio;
this.#redraw();
}
#onTouchEnd(event) {
if (!this.#pressed) return;
let target = null;
for (const t of event.changedTouches) {
if (t.target === this.$el) target = t;
}
if (!target) return;
this.#pressed = false;
if (!this.data.keep) {
this.#posX = this.#center;
this.#posY = this.#center;
}
this.#redraw();
}
#onMouseDown() {
if (this.data.disabled) return;
this.#pressed = true;
document.body.style.userSelect = 'none';
}
#onMouseMove(event) {
if (!this.#pressed) return;
this.#posX = event.pageX;
this.#posY = event.pageY;
if (this.$el.offsetParent.tagName.toUpperCase() === "BODY") {
this.#posX -= this.$el.offsetLeft;
this.#posY -= this.$el.offsetTop;
} else {
this.#posX -= this.$el.offsetParent.offsetLeft;
this.#posY -= this.$el.offsetParent.offsetTop;
}
const ratio = window.devicePixelRatio;
this.#posX *= ratio;
this.#posY *= ratio;
this.#redraw();
}
#onMouseUp() {
if (!this.#pressed) return;
this.#pressed = false;
if (!this.data.keep) {
this.#posX = this.#center;
this.#posY = this.#center;
}
this.#redraw();
document.body.style.userSelect = '';
}
}
Renderer.register('joy', JoyWidget);
function constrain(val, min, max) {
return val < min ? min : (val > max ? max : val);
}