ref: 86a1bffad7063d1f51dfe44c2126bbdf8b7fe5c8
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],
}