From 77be0e842806ee000f014b598b6186dde3d3fad3 Mon Sep 17 00:00:00 2001 From: Aron Demeter <66035744+dem4ron@users.noreply.github.com> Date: Thu, 9 Jan 2025 17:05:32 +0100 Subject: [PATCH] Add canvas coordinates tooltip (#7270) * Add canvas coordinates tooltip * Don't recalculate tooltip size on every mouse move --- .../exercises/draw/DrawExercise.tsx | 63 ++++++++++- .../SolveExercisePage/exercises/draw/utils.ts | 107 +----------------- 2 files changed, 63 insertions(+), 107 deletions(-) diff --git a/app/javascript/components/bootcamp/SolveExercisePage/exercises/draw/DrawExercise.tsx b/app/javascript/components/bootcamp/SolveExercisePage/exercises/draw/DrawExercise.tsx index 84986d6f13..519ff619a9 100644 --- a/app/javascript/components/bootcamp/SolveExercisePage/exercises/draw/DrawExercise.tsx +++ b/app/javascript/components/bootcamp/SolveExercisePage/exercises/draw/DrawExercise.tsx @@ -1,6 +1,5 @@ -import React from 'react' import { Exercise } from '../Exercise' -import { rToA } from './utils' +import { aToR, rToA } from './utils' import * as Shapes from './shapes' import type { ExecutionContext } from '@/interpreter/executor' @@ -69,6 +68,7 @@ export default class DrawExercise extends Exercise { Object.assign(this.view.style, { display: 'none', + position: 'relative', }) const grid = document.createElement('div') @@ -77,7 +77,66 @@ export default class DrawExercise extends Exercise { this.canvas = document.createElement('div') this.canvas.classList.add('canvas') + this.canvas.style.position = 'relative' this.view.appendChild(this.canvas) + + this.tooltip = document.createElement('div') + this.tooltip.classList.add('tooltip') + Object.assign(this.tooltip.style, { + whiteSpace: 'nowrap', + position: 'absolute', + background: '#333', + color: '#fff', + padding: '4px', + borderRadius: '4px', + fontSize: '12px', + pointerEvents: 'none', + display: 'none', + }) + this.view.appendChild(this.tooltip) + + this.canvas.addEventListener('mousemove', this.showTooltip.bind(this)) + this.canvas.addEventListener('mouseleave', this.hideTooltip.bind(this)) + } + + showTooltip(event: MouseEvent) { + const rect = this.canvas.getBoundingClientRect() + const canvasWidth = rect.width + const canvasHeight = rect.height + + const absX = event.clientX - rect.left + const absY = event.clientY - rect.top + + const relX = Math.round(aToR(absX, canvasWidth)) + const relY = Math.round(aToR(absY, canvasHeight)) + + let tooltipX = absX + 10 + let tooltipY = absY + 10 + + // providing these as constant values saves us from recalculating them every time + // update these values if the tooltip style changes + // measure max tooltip width/height with the fn below + // console.log(this.tooltip.getBoundingClientRect().width, this.tooltip.getBoundingClientRect().height) + const maxTooltipWidth = 75 + const maxTooltipHeight = 32 + // handle tooltip overflow-x + if (tooltipX + maxTooltipWidth + 5 > canvasWidth) { + tooltipX = absX - maxTooltipWidth - 10 + } + + // handle tooltip overflow-y + if (tooltipY + maxTooltipHeight + 5 > canvasHeight) { + tooltipY = absY - maxTooltipHeight - 10 + } + + this.tooltip.textContent = `X: ${relX}, Y: ${relY}` + this.tooltip.style.left = `${tooltipX}px` + this.tooltip.style.top = `${tooltipY}px` + this.tooltip.style.display = 'block' + } + + hideTooltip() { + this.tooltip.style.display = 'none' } public getState() { diff --git a/app/javascript/components/bootcamp/SolveExercisePage/exercises/draw/utils.ts b/app/javascript/components/bootcamp/SolveExercisePage/exercises/draw/utils.ts index 335ae6443a..d2848acf3b 100644 --- a/app/javascript/components/bootcamp/SolveExercisePage/exercises/draw/utils.ts +++ b/app/javascript/components/bootcamp/SolveExercisePage/exercises/draw/utils.ts @@ -2,7 +2,6 @@ * Relative constant number the absolute number maps to */ export const RELATIVE_SIZE = 100 - /** * * Convert relative x or y value to absolute value @@ -10,112 +9,10 @@ export const RELATIVE_SIZE = 100 export function rToA(n: number) { return (n / RELATIVE_SIZE) * 100 } - /** * * Convert absolute x or y value to absolute value */ -export function aToR(n: number) { - console.group('HERE') - //return Math.round(n / (CANVAS_SIZE / RELATIVE_SIZE)); -} -/** - * - */ -export function relativeToAbsolute(x: number, y: number) { - return { x: rToA(x), y: rToA(y) } -} -/** - * - */ -export function absoluteToRelative(x: number, y: number) { - return { x: aToR(x), y: aToR(y) } -} - -// export function showMouseCoord(p: p5) { -// const mouseX = p.mouseX; -// const mouseY = p.mouseY; - -// let textX = mouseX + 10; -// let textY = mouseY + 10; - -// const textWidth = p.textWidth(`(${mouseX}, ${mouseY})`); -// const textHeight = 16; - -// // in case of overflow -// if (textX + textWidth > p.width) { -// textX = mouseX - textWidth - 10; -// } - -// if (textY + textHeight > p.height) { -// textY = mouseY - textHeight - 10; -// } - -// if (textX < 0) { -// textX = 10; -// } - -// if (textY < 0) { -// textY = 10; -// } - -// const { x, y } = absoluteToRelative(mouseX, mouseY); -// p.text(`(${x}, ${y})`, textX, textY); - -// p.stroke(0, 0, 0, 50); -// // xline -// p.line(mouseX, 0, mouseX, p.height); -// // yline -// p.line(0, mouseY, p.width, mouseY); -// } - -// export function isMouseOverTheCanvas(p: p5) { -// return p.mouseX >= 0 && p.mouseX <= p.width && p.mouseY >= 0 && p.mouseY <= p.height; -// } - -/** - * Converts the code string to a array of dictionary of function names and their arguments - */ -function parseCode(code: string): Array<{ [key: string]: number[] }> { - const result: Array<{ [key: string]: number[] }> = [] - - const lines = code.trim().split('\n') - - const regex = /(\w+)\(([\d\s,]+)\)/ - - for (const line of lines) { - const match = line.match(regex) - if (match) { - const functionName = match[1] - const args = match[2].split(',').map((arg) => parseFloat(arg.trim())) - const obj: { [key: string]: number[] } = {} - obj[functionName] = args - result.push(obj) - } - } - - return result -} - -/** - * Calls p5 function with arguments - */ -// export function drawThings(code: string, p: p5) { -// const parsedCode = parseCode(code); -// parsedCode.forEach((line) => { -// const functionName = Object.keys(line)[0]; -// const args = line[functionName]; -// const digestedArgs = mapRelativeArgsToAbsoluteArgs(functionName, args); -// // @ts-ignore -// p[functionName](...digestedArgs); -// }); -// } - -function mapRelativeArgsToAbsoluteArgs(functionName: string, args: number[]) { - switch (functionName) { - case 'rect': - return args.map(rToA) - default: - return args - } +export function aToR(n: number, canvasSize: number) { + return (n / canvasSize) * RELATIVE_SIZE }