313 lines
7.1 KiB
JavaScript
313 lines
7.1 KiB
JavaScript
class HippieCrosshair {
|
|
constructor(canvas, options = {}) {
|
|
this.canvas = canvas;
|
|
this.ctx = canvas.getContext('2d');
|
|
|
|
this.debug = options.debug || false;
|
|
|
|
const defaults = {
|
|
crosshair: {
|
|
size: 16,
|
|
thickness: 2,
|
|
color: '#000',
|
|
gapSize: 8,
|
|
style: 'cross'
|
|
},
|
|
connector: {
|
|
distance: 128, // Distance to draw next symbol
|
|
spacing: 64, // Space between symbols
|
|
size: 8,
|
|
color: '#000',
|
|
style: 'arrow',
|
|
visibility: true
|
|
},
|
|
line: {
|
|
color: 'rgba(0, 0, 0, 0.1)',
|
|
width: 1
|
|
}
|
|
};
|
|
const merged = this.mergeOptions(defaults, options);
|
|
const { crosshair, connector, line } = merged;
|
|
|
|
// Crosshair options
|
|
this.size = crosshair.size;
|
|
this.thickness = crosshair.thickness;
|
|
this.color = crosshair.color;
|
|
this.gapSize = crosshair.gapSize;
|
|
this.style = crosshair.style;
|
|
|
|
// Connector options
|
|
this.distance = connector.distance;
|
|
this.spacing = connector.spacing;
|
|
this.connectorSize = connector.size;
|
|
this.connectorColor = connector.color;
|
|
this.connectorStyle = connector.style;
|
|
this.connectorShow = connector.visibility;
|
|
|
|
// Line options
|
|
this.lineColor = line.color || '#fff';
|
|
this.lineWidth = line.width || 1;
|
|
|
|
this.mouseX = canvas.width / 2;
|
|
this.mouseY = canvas.height / 2;
|
|
|
|
// Animation control
|
|
this.isAnimating = true;
|
|
this.animationFrameId = null;
|
|
|
|
this.setupEventListeners();
|
|
this.animate();
|
|
}
|
|
|
|
mergeOptions(defaults, options) {
|
|
const merged = JSON.parse(JSON.stringify(defaults));
|
|
|
|
if (options.crosshair) {
|
|
Object.assign(merged.crosshair, options.crosshair);
|
|
}
|
|
if (options.connector) {
|
|
Object.assign(merged.connector, options.connector);
|
|
}
|
|
if (options.line) {
|
|
Object.assign(merged.line, options.line);
|
|
}
|
|
|
|
return merged;
|
|
}
|
|
|
|
setupEventListeners() {
|
|
document.addEventListener('mousemove', (event) => {
|
|
this.mouseX = event.clientX;
|
|
this.mouseY = event.clientY;
|
|
});
|
|
}
|
|
|
|
animate() {
|
|
if (!this.isAnimating) {
|
|
this.animationFrameId = requestAnimationFrame(() => this.animate());
|
|
return;
|
|
}
|
|
|
|
this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
|
|
|
|
if (this.connectorShow) {
|
|
if (this.debug) this.drawLine();
|
|
this.drawConnector();
|
|
}
|
|
|
|
// TODO: Autom. Zug zum Zentrum hin ermöglichen
|
|
this.drawCrosshair();
|
|
|
|
this.animationFrameId = requestAnimationFrame(() => this.animate());
|
|
}
|
|
|
|
drawLine() {
|
|
const centerX = this.canvas.width / 2;
|
|
const centerY = this.canvas.height / 2;
|
|
|
|
this.ctx.strokeStyle = this.lineColor;
|
|
this.ctx.lineWidth = this.lineWidth;
|
|
this.ctx.beginPath();
|
|
this.ctx.moveTo(centerX, centerY);
|
|
this.ctx.lineTo(this.mouseX, this.mouseY);
|
|
this.ctx.stroke();
|
|
}
|
|
|
|
// TODO: Ausblenden nach Distanz
|
|
drawConnector() {
|
|
const centerX = this.canvas.width / 2;
|
|
const centerY = this.canvas.height / 2;
|
|
|
|
const dx = this.mouseX - centerX;
|
|
const dy = this.mouseY - centerY;
|
|
const delta = Math.sqrt(dx * dx + dy * dy);
|
|
|
|
// Only draw connectors if cursor is far enough from center
|
|
if (delta < this.distance) {
|
|
return;
|
|
}
|
|
|
|
const angle = Math.atan2(dy, dx);
|
|
const count = Math.floor((delta - this.distance) / this.spacing);
|
|
|
|
for (let i = 0; i < count; i++) {
|
|
const distance = this.distance + (i * this.spacing);
|
|
const x = centerX + Math.cos(angle) * distance;
|
|
const y = centerY + Math.sin(angle) * distance;
|
|
|
|
this.drawSymbol(x, y, angle);
|
|
}
|
|
}
|
|
|
|
drawSymbol(x, y, angle = 0) {
|
|
this.ctx.fillStyle = this.connectorColor;
|
|
this.ctx.strokeStyle = this.connectorColor;
|
|
this.ctx.lineWidth = 1;
|
|
|
|
switch (this.connectorStyle) {
|
|
case 'circle':
|
|
this.ctx.beginPath();
|
|
this.ctx.arc(x, y, this.connectorSize / 2, 0, Math.PI * 2);
|
|
this.ctx.fill();
|
|
break;
|
|
case 'diamond':
|
|
const size = this.connectorSize - (this.connectorSize / 4);
|
|
this.ctx.beginPath();
|
|
this.ctx.moveTo(x, y - size);
|
|
this.ctx.lineTo(x + size, y);
|
|
this.ctx.lineTo(x, y + size);
|
|
this.ctx.lineTo(x - size, y);
|
|
this.ctx.closePath();
|
|
this.ctx.fill();
|
|
break;
|
|
case 'square':
|
|
this.ctx.fillRect(
|
|
x - this.connectorSize / 2,
|
|
y - this.connectorSize / 2,
|
|
this.connectorSize,
|
|
this.connectorSize
|
|
);
|
|
break;
|
|
case 'arrow':
|
|
this.arrow(x, y, angle);
|
|
break;
|
|
}
|
|
}
|
|
|
|
arrow(x, y, angle) {
|
|
const size = this.connectorSize - (this.connectorSize / 4);
|
|
|
|
this.ctx.save();
|
|
this.ctx.translate(x, y);
|
|
this.ctx.rotate(angle);
|
|
|
|
// Arrow pointing right
|
|
this.ctx.beginPath();
|
|
this.ctx.moveTo(size, 0); // Tip
|
|
this.ctx.lineTo(-size, -size); // Back left
|
|
// this.ctx.lineTo(-size * 0.4, 0); // Middle
|
|
this.ctx.lineTo(-size, size); // Back right
|
|
this.ctx.closePath();
|
|
this.ctx.fill();
|
|
|
|
this.ctx.restore();
|
|
}
|
|
|
|
drawCrosshair() {
|
|
this.ctx.strokeStyle = this.color;
|
|
this.ctx.lineWidth = this.thickness;
|
|
this.ctx.lineCap = 'round';
|
|
|
|
switch (this.style) {
|
|
case 'cross':
|
|
this.cross();
|
|
break;
|
|
case 'circle':
|
|
this.circle();
|
|
break;
|
|
case 'dot':
|
|
this.dot();
|
|
break;
|
|
case 'level':
|
|
this.level();
|
|
break;
|
|
}
|
|
}
|
|
|
|
cross() {
|
|
// Horizontal line
|
|
this.ctx.beginPath();
|
|
this.ctx.moveTo(this.mouseX - this.size, this.mouseY);
|
|
this.ctx.lineTo(this.mouseX - this.gapSize, this.mouseY);
|
|
this.ctx.stroke();
|
|
|
|
this.ctx.beginPath();
|
|
this.ctx.moveTo(this.mouseX + this.gapSize, this.mouseY);
|
|
this.ctx.lineTo(this.mouseX + this.size, this.mouseY);
|
|
this.ctx.stroke();
|
|
|
|
// Vertical line
|
|
this.ctx.beginPath();
|
|
this.ctx.moveTo(this.mouseX, this.mouseY - this.size);
|
|
this.ctx.lineTo(this.mouseX, this.mouseY - this.gapSize);
|
|
this.ctx.stroke();
|
|
|
|
this.ctx.beginPath();
|
|
this.ctx.moveTo(this.mouseX, this.mouseY + this.gapSize);
|
|
this.ctx.lineTo(this.mouseX, this.mouseY + this.size);
|
|
this.ctx.stroke();
|
|
}
|
|
|
|
circle() {
|
|
// Outer circle
|
|
this.ctx.beginPath();
|
|
this.ctx.arc(this.mouseX, this.mouseY, this.size, 0, Math.PI * 2);
|
|
this.ctx.stroke();
|
|
|
|
// Inner dot
|
|
this.ctx.fillStyle = this.color;
|
|
this.ctx.beginPath();
|
|
this.ctx.arc(this.mouseX, this.mouseY, this.thickness, 0, Math.PI * 2);
|
|
this.ctx.fill();
|
|
}
|
|
|
|
dot() {
|
|
this.ctx.fillStyle = this.color;
|
|
this.ctx.beginPath();
|
|
this.ctx.arc(this.mouseX, this.mouseY, this.size / 4, 0, Math.PI * 2);
|
|
this.ctx.fill();
|
|
}
|
|
|
|
level() {
|
|
this.ctx.beginPath();
|
|
this.ctx.moveTo(this.mouseX - this.size * 2, this.mouseY);
|
|
this.ctx.lineTo(this.mouseX - this.gapSize * 2, this.mouseY);
|
|
this.ctx.stroke();
|
|
|
|
this.ctx.beginPath();
|
|
this.ctx.arc(this.mouseX, this.mouseY, this.size, 0, Math.PI);
|
|
this.ctx.stroke();
|
|
|
|
this.ctx.beginPath();
|
|
this.ctx.moveTo(this.mouseX + this.gapSize * 2, this.mouseY);
|
|
this.ctx.lineTo(this.mouseX + this.size * 2, this.mouseY);
|
|
this.ctx.stroke();
|
|
|
|
this.ctx.fillStyle = this.color;
|
|
this.ctx.beginPath();
|
|
this.ctx.arc(this.mouseX, this.mouseY, this.thickness, 0, Math.PI * 2);
|
|
this.ctx.fill();
|
|
}
|
|
|
|
setCrosshairStyle(style) {
|
|
this.style = style;
|
|
}
|
|
|
|
setCrosshairColor(color) {
|
|
this.color = color;
|
|
}
|
|
|
|
setConnectorStyle(style) {
|
|
this.connectorStyle = style;
|
|
}
|
|
|
|
setConnectorColor(color) {
|
|
this.connectorColor = color;
|
|
}
|
|
|
|
setConnectorVisibility(visible) {
|
|
this.connectorShow = visible;
|
|
}
|
|
|
|
startAnimation() {
|
|
this.isAnimating = true;
|
|
}
|
|
|
|
stopAnimation() {
|
|
this.isAnimating = false;
|
|
}
|
|
|
|
toggleAnimation() {
|
|
this.isAnimating = !this.isAnimating;
|
|
}
|
|
}
|