ref: 81d1e4e01d7001e6a0107792e6167136787295d6
dir: /pen.js/
import {toNumber, toString} from "./compile.js" import {MakeBlock} from "./model.js" import {TextSlot, NumberSlot} from "./core.js" import {ColorSlot} from "./stage.js" let svgns = "http://www.w3.org/2000/svg" export function extendStage(stage) { let canvas = document.createElement("canvas") canvas.width = 480 canvas.height = 360 let ctx = canvas.getContext("2d") ctx.translate(240, 180) ctx.imageSmoothingEnabled = false ctx.lineCap = "round" ctx.lineJoin = "round" let penElement = document.createElementNS(svgns, "foreignObject") penElement.setAttribute("x", "-240") penElement.setAttribute("y", "-180") penElement.setAttribute("width", "480") penElement.setAttribute("height", "360") penElement.append(canvas) stage.element.querySelector("g").children[1].after(penElement) return {canvas, ctx} } export function extendSprite(sprite, name) { let {canvas, ctx} = sprite.stage.extensions[name] let down = sprite.original?.extensions[name].down ?? false let color = sprite.original?.extensions[name].color ?? "#000000" let size = sprite.original?.extensions[name].size ?? 1 let blockFns = { clear: name => ctx.clearRect(-canvas.width / 2, -canvas.height / 2, canvas.width, canvas.height), penDown: () => { down = true ctx.fillStyle = color ctx.beginPath() ctx.arc(sprite.x, -sprite.y, size / 2, 0, 8) ctx.fill() }, penUp: () => down = false, setPenColor: color1 => { // todo: improve this let number = Math.round(Number(color1)) if (Number.isNaN(number)) color = toString(color1) else color = `rgb(${(number >> 16) & 0xFF}, ${(number >> 8) & 0xFF}, ${number & 0xFF})` }, setPenSize: size1 => size = Math.max(1, toNumber(size1)), changePenSize: size1 => blockFns.setPenSize(toNumber(size1) + size), stamp: () => { ctx.save() ctx.translate(sprite.x, -sprite.y) ctx.scale(sprite.size / 100, sprite.size / 100) if (sprite.rotationStyle === "full") ctx.rotate((sprite.direction - 90) * (Math.PI / 180)) if (sprite.rotationStyle === "left-right" && (sprite.direction < 0 || sprite.direction >= 180)) ctx.scale(-1, 1) if (sprite.costume.blob?.type !== "image/svg+xml") ctx.scale(0.5, 0.5) ctx.filter = sprite.getFilter() ctx.drawImage(sprite.costume.canvas, -sprite.costume.x, -sprite.costume.y) ctx.restore() }, } let before = () => { if (!down) return ctx.beginPath() ctx.moveTo(sprite.x, -sprite.y) } let after = () => { if (!down) return ctx.strokeStyle = color ctx.lineWidth = size ctx.lineTo(sprite.x, -sprite.y) ctx.stroke() } sprite.onMove({before, after}) let blocks = {} for (let name of Object.keys(blockFns)) { let upperCaseName = name[0].toUpperCase() + name.slice(1) let slots = defaults[name]?.map(n => typeof n === "number" ? NumberSlot : n.startsWith("#") ? ColorSlot : TextSlot) ?? [] blocks[upperCaseName] = MakeBlock({run: blockFns[name], slots, name: nameFns[name], defaults: defaults[name]}) } return {blocks} } let icon = `<svg viewbox="0 0 24 24" fill="#48E" stroke="#444"><path d="M3 17.46v3.04c0 .28.22.5.5.5h3.04c.13 0 .26-.05.35-.15L17.81 9.94l-3.75-3.75L3.15 17.1c-.1.1-.15.22-.15.36zM20.71 7.04c.39-.39.39-1.02 0-1.41l-2.34-2.34c-.39-.39-1.02-.39-1.41 0l-1.83 1.83 3.75 3.75 1.83-1.83z"/></svg>` function getIcon() { let span = document.createElement("span") span.insertAdjacentHTML("beforeend", icon) let svg = span.querySelector("svg") svg.style.setProperty("height", "1.5em") svg.style.setProperty("vertical-align", "middle") return span } let nameFns = { clear: () => [getIcon(), "erase all"], penDown: () => [getIcon(), "pen down"], penUp: () => [getIcon(), "pen up"], setPenColor: color => [getIcon(), "set pen color to", color], setPenSize: size => [getIcon(), "set pen size to", size], changePenSize: size => [getIcon(), "change pen size by", size], stamp: () => [getIcon(), "stamp"], } let defaults = { setPenColor: ["#BBDDFF"], setPenSize: [5], changePenSize: [5], }