Enhance CourtDrawing components with additional target selection and arrow drawing features
Added new target selection buttons for main and additional strokes in CourtDrawingTool.vue, allowing users to explicitly choose target positions. Updated CourtDrawingRender.vue to support drawing up to three additional arrows, alternating between left and right sides, with distinct colors for each stroke. Improved the logic for determining the next stroke side and updated related methods for better clarity and functionality.
This commit is contained in:
@@ -65,6 +65,8 @@ export default {
|
||||
arrows: {
|
||||
primaryColor: '#d32f2f', // rechts -> target (rot)
|
||||
secondaryColor: '#1565c0', // zurück (blau)
|
||||
tertiaryColor: '#2e7d32', // dritter Schlag (grün)
|
||||
quaternaryColor: '#6a1b9a', // vierter Schlag (violett)
|
||||
width: 6,
|
||||
headLength: 24,
|
||||
vhOffsetX: 5,
|
||||
@@ -336,25 +338,39 @@ export default {
|
||||
const startCenter = this.getStartCircleCenter();
|
||||
this.drawLabelBelow(ctx, sourceLabel, startCenter);
|
||||
}
|
||||
|
||||
// Second arrow (optional): from right source to left target
|
||||
const leftTarget = this.drawingData.nextStrokeTargetPosition ? Number(this.drawingData.nextStrokeTargetPosition) : null;
|
||||
if (tp && leftTarget) {
|
||||
// source near previous right target
|
||||
|
||||
// Additional arrows (up to 3), alternating left/right, starting from right target
|
||||
const extras = Array.isArray(this.drawingData.additionalStrokes) ? this.drawingData.additionalStrokes : [];
|
||||
if (tp && extras.length) {
|
||||
const colors = [
|
||||
this.config.arrows.secondaryColor,
|
||||
this.config.arrows.tertiaryColor,
|
||||
this.config.arrows.quaternaryColor
|
||||
];
|
||||
const max = Math.min(3, extras.length);
|
||||
const sourceRightCenter = this.computeRightTargetPosition(tp);
|
||||
// left target mapping: mirror scheme to left half
|
||||
const toLeftCenter = this.computeLeftTargetPosition(leftTarget);
|
||||
// Zielmarkierung links
|
||||
this.drawHitMarker(ctx, toLeftCenter);
|
||||
const side = this.drawingData.nextStrokeSide || '';
|
||||
const type = this.drawingData.nextStrokeType || '';
|
||||
// Text gehört ans Ziel (ohne "extra target")
|
||||
const targetLabel = `${side} ${type}`.trim();
|
||||
const sourceRight = { x: sourceRightCenter.x - this.config.targetCircles.radius, y: sourceRightCenter.y };
|
||||
const toLeft = { x: toLeftCenter.x + this.config.leftTargetCircles.radius, y: toLeftCenter.y };
|
||||
this.drawArrow(ctx, sourceRight, toLeft, this.config.arrows.secondaryColor);
|
||||
// Unter dem rechten Ziel (target der ersten Linie) beschriften
|
||||
this.drawLabelBelow(ctx, targetLabel, sourceRightCenter);
|
||||
let prevPoint = { x: sourceRightCenter.x - this.config.targetCircles.radius, y: sourceRightCenter.y };
|
||||
for (let i = 0; i < max; i++) {
|
||||
const stroke = extras[i];
|
||||
const side = i % 2 === 0 ? 'left' : 'right';
|
||||
if (side === 'left') {
|
||||
const leftNum = Number(stroke.targetPosition);
|
||||
const toLeftCenter = this.computeLeftTargetPosition(leftNum);
|
||||
// Zielmarkierung links
|
||||
this.drawHitMarker(ctx, toLeftCenter);
|
||||
const toLeft = { x: toLeftCenter.x + this.config.leftTargetCircles.radius, y: toLeftCenter.y };
|
||||
this.drawArrow(ctx, prevPoint, toLeft, colors[i]);
|
||||
prevPoint = toLeft; // beginnt an der Pfeilspitze des vorherigen
|
||||
} else {
|
||||
const rightNum = Number(stroke.targetPosition);
|
||||
const toRightCenter = this.computeRightTargetPosition(rightNum);
|
||||
// Zielmarkierung rechts
|
||||
this.drawHitMarker(ctx, toRightCenter);
|
||||
const toRight = { x: toRightCenter.x - this.config.targetCircles.radius, y: toRightCenter.y };
|
||||
this.drawArrow(ctx, prevPoint, toRight, colors[i]);
|
||||
prevPoint = toRight;
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
getStartCircleCenter() {
|
||||
|
||||
@@ -89,6 +89,22 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Zielposition für Hauptschlag: explizite Auswahl 1–9 als Buttons -->
|
||||
<div class="target-selection" v-if="spinType">
|
||||
<span>Zielposition:</span>
|
||||
<div class="target-buttons">
|
||||
<button
|
||||
v-for="n in 9"
|
||||
:key="`main-target-${n}`"
|
||||
type="button"
|
||||
:class="['btn-small', { 'btn-primary': targetPosition === String(n), 'btn-secondary': targetPosition !== String(n) }]"
|
||||
@click="targetPosition = String(n)"
|
||||
>
|
||||
{{ n }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<!-- Zusätzliche Schläge hinzufügen -->
|
||||
<div class="additional-strokes" v-if="strokeType && spinType && targetPosition">
|
||||
@@ -113,6 +129,22 @@
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Zielposition für Zusatzschlag: explizite Auswahl 1–9 als Buttons -->
|
||||
<div class="next-target-selection">
|
||||
<span>Zielposition:</span>
|
||||
<div class="target-buttons">
|
||||
<button
|
||||
v-for="n in 9"
|
||||
:key="`next-target-${n}`"
|
||||
type="button"
|
||||
:class="['btn-small', { 'btn-primary': nextStrokeTargetPosition === String(n), 'btn-secondary': nextStrokeTargetPosition !== String(n) }]"
|
||||
@click="nextStrokeTargetPosition = String(n)"
|
||||
>
|
||||
{{ n }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="next-stroke-buttons">
|
||||
<button
|
||||
type="button"
|
||||
@@ -277,7 +309,7 @@ export default {
|
||||
textColor: '#000000'
|
||||
},
|
||||
arrow: {
|
||||
color: '#ff0000',
|
||||
color: '#ff0000', // Hauptschlag immer rot
|
||||
width: 6,
|
||||
cap: 'round',
|
||||
headSize: 8,
|
||||
@@ -588,9 +620,9 @@ export default {
|
||||
this.drawLeftSideTargetCircles(ctx, tableX, tableY, tableWidth, tableHeight);
|
||||
}
|
||||
|
||||
// Zusätzlichen Pfeil vom rechten Source zum linken Target zeichnen (wenn linker Target ausgewählt)
|
||||
if (this.nextStrokeTargetPosition && this.targetPosition) {
|
||||
this.drawArrowToLeftTarget(ctx, tableX, tableY, tableWidth, tableHeight);
|
||||
// Zeichne Kette zusätzlicher Schläge (bis zu 3)
|
||||
if (this.targetPosition) {
|
||||
this.drawAdditionalArrows(ctx, tableX, tableY, tableWidth, tableHeight);
|
||||
}
|
||||
},
|
||||
|
||||
@@ -821,6 +853,109 @@ export default {
|
||||
ctx.stroke();
|
||||
},
|
||||
|
||||
// Ermittelt, welche Tischseite (left/right) als nächstes für Zusatzschlag benutzt wird
|
||||
getNextAdditionalSide() {
|
||||
const idx = this.additionalStrokes.length; // 0,1,2
|
||||
return idx % 2 === 0 ? 'left' : 'right';
|
||||
},
|
||||
|
||||
// Berechne Mittelpunkt eines rechten Zielkreises (1..9)
|
||||
computeRightTargetCenter(tableX, tableY, tableWidth, tableHeight, number) {
|
||||
const cfg = this.config.targetCircles;
|
||||
const x1 = tableX + tableWidth - cfg.rightXOffset;
|
||||
const x3 = tableX + tableWidth/2 + cfg.middleXOffset;
|
||||
const xdiff = x3 - x1;
|
||||
const x2 = x3 - xdiff/2;
|
||||
const yTop = tableY + cfg.topYOffset;
|
||||
const yMid = tableY + tableHeight/2;
|
||||
const yBot = tableY + tableHeight - cfg.bottomYOffset;
|
||||
const map = {
|
||||
1: { x: x1, y: yTop }, 2: { x: x1, y: yMid }, 3: { x: x1, y: yBot },
|
||||
4: { x: x2, y: yTop }, 5: { x: x2, y: yMid }, 6: { x: x2, y: yBot },
|
||||
7: { x: x3, y: yTop }, 8: { x: x3, y: yMid }, 9: { x: x3, y: yBot }
|
||||
};
|
||||
return map[number] || null;
|
||||
},
|
||||
// Berechne Mittelpunkt eines linken Zielkreises (1..9 gespiegelt)
|
||||
computeLeftTargetCenter(tableX, tableY, tableWidth, tableHeight, number) {
|
||||
const cfg = this.config.leftTargetCircles;
|
||||
const x1 = tableX + cfg.leftXOffset;
|
||||
const x3 = tableX + tableWidth / 2 - cfg.rightXOffset;
|
||||
const xdiff = x3 - x1;
|
||||
const x2 = x3 - xdiff/2;
|
||||
const yTop = tableY + cfg.topYOffset;
|
||||
const yMid = tableY + tableHeight/2;
|
||||
const yBot = tableY + tableHeight - cfg.bottomYOffset;
|
||||
const map = {
|
||||
1: { x: x1, y: yBot }, 2: { x: x1, y: yMid }, 3: { x: x1, y: yTop },
|
||||
4: { x: x2, y: yBot }, 5: { x: x2, y: yMid }, 6: { x: x2, y: yTop },
|
||||
7: { x: x3, y: yBot }, 8: { x: x3, y: yMid }, 9: { x: x3, y: yTop }
|
||||
};
|
||||
return map[number] || null;
|
||||
},
|
||||
|
||||
// Zeichnet zusätzliche Pfeile abwechselnd rechts/links aus additionalStrokes
|
||||
drawAdditionalArrows(ctx, tableX, tableY, tableWidth, tableHeight) {
|
||||
if (!this.additionalStrokes || this.additionalStrokes.length === 0) return;
|
||||
const max = this.additionalStrokes.length;
|
||||
// 1: rot, 2: blau, 3: gelb, 4: violett, ab 5: pink
|
||||
const colors = ['#007bff', '#FFD700', '#6a1b9a', '#ff69b4'];
|
||||
const rightRadius = this.config.targetCircles.radius;
|
||||
const leftRadius = this.config.leftTargetCircles.radius;
|
||||
|
||||
// Quelle: Ende des ersten Pfeils (rechts)
|
||||
const firstNum = parseInt(this.targetPosition);
|
||||
const firstCenter = this.computeRightTargetCenter(tableX, tableY, tableWidth, tableHeight, firstNum);
|
||||
let prevPoint = { x: firstCenter.x - rightRadius, y: firstCenter.y }; // linke Kante des rechten Kreises
|
||||
let prevSide = 'right';
|
||||
|
||||
for (let i = 0; i < max; i++) {
|
||||
const stroke = this.additionalStrokes[i];
|
||||
const side = i % 2 === 0 ? 'left' : 'right';
|
||||
const targetNum = parseInt(stroke.targetPosition);
|
||||
// Farben: 2. blau, 3. gelb, 4. violett, ab dem 5. pink
|
||||
const color = (i < colors.length) ? colors[i] : '#ff69b4';
|
||||
let toCenter, toPoint;
|
||||
if (side === 'left') {
|
||||
toCenter = this.computeLeftTargetCenter(tableX, tableY, tableWidth, tableHeight, targetNum);
|
||||
toPoint = { x: toCenter.x + leftRadius, y: toCenter.y }; // rechte Kante des linken Kreises
|
||||
} else {
|
||||
toCenter = this.computeRightTargetCenter(tableX, tableY, tableWidth, tableHeight, targetNum);
|
||||
toPoint = { x: toCenter.x - rightRadius, y: toCenter.y }; // linke Kante des rechten Kreises
|
||||
}
|
||||
// Linie
|
||||
ctx.strokeStyle = color;
|
||||
ctx.fillStyle = color;
|
||||
ctx.lineWidth = this.config.arrow.width;
|
||||
ctx.lineCap = this.config.arrow.cap;
|
||||
// Nummer
|
||||
ctx.font = this.config.arrow.counterFont;
|
||||
ctx.textAlign = 'center';
|
||||
ctx.textBaseline = 'middle';
|
||||
const midX = (prevPoint.x + toPoint.x) / 2;
|
||||
const midY = (prevPoint.y + toPoint.y) / 2 - this.config.arrow.counterOffset;
|
||||
ctx.fillText(String(i + 2), midX, midY);
|
||||
// Linie zeichnen
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(prevPoint.x, prevPoint.y);
|
||||
ctx.lineTo(toPoint.x, toPoint.y);
|
||||
ctx.stroke();
|
||||
// Pfeilkopf
|
||||
const angle = Math.atan2(toPoint.y - prevPoint.y, toPoint.x - prevPoint.x);
|
||||
const sz = this.config.arrow.headSize;
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(toPoint.x, toPoint.y);
|
||||
ctx.lineTo(toPoint.x - sz * Math.cos(angle - Math.PI / 6), toPoint.y - sz * Math.sin(angle - Math.PI / 6));
|
||||
ctx.moveTo(toPoint.x, toPoint.y);
|
||||
ctx.lineTo(toPoint.x - sz * Math.cos(angle + Math.PI / 6), toPoint.y - sz * Math.sin(angle + Math.PI / 6));
|
||||
ctx.stroke();
|
||||
|
||||
// nächste Quelle ist die Pfeilspitze dieses Schlages
|
||||
prevPoint = toPoint;
|
||||
prevSide = side;
|
||||
}
|
||||
},
|
||||
|
||||
drawLeftSideTargetCircles(ctx, tableX, tableY, tableWidth, tableHeight) {
|
||||
const config = this.config.leftTargetCircles;
|
||||
// Kreise auf der linken Tischhälfte (spiegelbildlich zu rechts)
|
||||
@@ -885,22 +1020,31 @@ export default {
|
||||
// Prüfe ob auf eine Zielposition geklickt wurde
|
||||
const clickedTarget = this.checkTargetPositionClick(clickX, clickY);
|
||||
if (clickedTarget) {
|
||||
this.targetPosition = clickedTarget;
|
||||
|
||||
// Wenn keine Startposition ausgewählt ist, setze standardmäßig die mittlere Startposition (AS2)
|
||||
if (!this.selectedStartPosition) {
|
||||
this.selectedStartPosition = 'AS2';
|
||||
this.selectedCirclePosition = 'middle';
|
||||
// Wenn noch kein Hauptziel gesetzt: setze es
|
||||
if (!this.targetPosition) {
|
||||
this.targetPosition = clickedTarget;
|
||||
// Wenn keine Startposition ausgewählt ist, setze standardmäßig die mittlere Startposition (AS2)
|
||||
if (!this.selectedStartPosition) {
|
||||
this.selectedStartPosition = 'AS2';
|
||||
this.selectedCirclePosition = 'middle';
|
||||
}
|
||||
this.drawCourt();
|
||||
this.emitDrawingData();
|
||||
this.updateTextFields();
|
||||
return;
|
||||
}
|
||||
// Andernfalls: wenn wir einen Zusatzschlag auf die rechte Seite wählen sollen
|
||||
if (this.targetPosition && this.additionalStrokes.length < 3 && this.getNextAdditionalSide() === 'right') {
|
||||
this.nextStrokeTargetPosition = clickedTarget;
|
||||
this.drawCourt();
|
||||
this.emitDrawingData();
|
||||
this.updateTextFields();
|
||||
return;
|
||||
}
|
||||
|
||||
this.drawCourt(); // Neu zeichnen für Zielposition-Hervorhebung
|
||||
this.emitDrawingData();
|
||||
this.updateTextFields();
|
||||
return;
|
||||
}
|
||||
|
||||
// Prüfe ob auf eine linke Zielposition geklickt wurde (nur wenn Schlag für rechte Seite ausgewählt)
|
||||
if (this.nextStrokeType && this.nextStrokeSide && this.targetPosition) {
|
||||
// Prüfe ob auf eine linke Zielposition geklickt wurde (nur wenn Zusatzschlag links erwartet)
|
||||
if (this.nextStrokeType && this.nextStrokeSide && this.targetPosition && this.getNextAdditionalSide() === 'left') {
|
||||
const clickedLeftTarget = this.checkLeftTargetPositionClick(clickX, clickY);
|
||||
if (clickedLeftTarget) {
|
||||
this.nextStrokeTargetPosition = clickedLeftTarget;
|
||||
@@ -1321,6 +1465,8 @@ export default {
|
||||
|
||||
// Counter erhöhen
|
||||
this.exerciseCounter++;
|
||||
// Auswahl für nächsten Zusatzschlag zurücksetzen
|
||||
this.nextStrokeTargetPosition = '';
|
||||
|
||||
// Textfelder aktualisieren
|
||||
this.updateTextFields();
|
||||
|
||||
Reference in New Issue
Block a user