shithub: scrax

ref: 81d1e4e01d7001e6a0107792e6167136787295d6
dir: /pen.js/

View raw version
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],
}