How would you build block-based programming visuals in Typst/CeTZ?

Funnily enough, I had the idea to create Scratch-like blocks as well, but never got to realising any part of it.

To answer the question posed in the title, I would treat them as regular Typst block elements. Thinking about it for about an hour now, I could only up with the following approach:

#set curve.line(relative: true)

#let program(title: "⚙ Program", child: none) = {
	set text(11pt, white, font: "Fira Sans")
	title = title + sym.space.nobreak
	rect(
		fill: orange,
		radius: 1pt,
		stroke: orange.darken(10%),
		inset: (right: 0pt),
		outset: (right: 1pt),
		context {
			let title-width = measure(title).width
			title
			v(-0.75em)
			if child == none {
				rect(
					fill: white,
					radius: 1pt,
					stroke: (right: none, rest: orange.darken(10%)),
					height: 1.5em,
					width: title-width,
					outset: (right: 2pt),
					curve(
						fill: white,
						stroke: (paint: orange.darken(10%), join: "round", cap: "round"),
						curve.move((0.7em, 0.99em)),
						curve.line((0.3em, 0.3em)),
						curve.line((0.3em, -0.3em)),
					)
				)
			} else {
				child
			}
		}
	)
}

#let loop(title: "⟳ Loop", child: none) = {
	set text(11pt, white, font: "Fira Sans")
	title = title + sym.space.nobreak
	rect(
		fill: orange,
		radius: 1pt,
		stroke: orange.darken(10%),
		inset: (right: 0pt),
		outset: (right: 1pt),
		context {
			let title-width = measure("⚙ Program").width
			place(
				curve(
					fill: orange,
					stroke: (paint: orange.darken(10%), join: "round", cap: "round"),
					curve.move((1.1em, -0.51em)),
					curve.line((0.3em, 0.3em)),
					curve.line((0.3em, -0.3em)),
				)
			)
			title
			v(-0.75em)
			if child == none {
				rect(
					fill: white,
					radius: 1pt,
					stroke: (right: none, rest: orange.darken(10%)),
					height: 1.5em,
					width: title-width,
					outset: (right: 2pt),
					curve(
						fill: white,
						stroke: (paint: orange.darken(10%), join: "round", cap: "round"),
						curve.move((0.7em, 0.99em)),
						curve.line((0.3em, 0.3em)),
						curve.line((0.3em, -0.3em)),
					)
				)
			} else {
				child
			}
		}
	)
}

#program(child: program(child: program()))
#program(child: loop())

Many portions of the code repeat, so it could easily be shortened when more blocks are added. The most apparent issues here are the slightly misaligned notches and somewhat hardcoded block widths, specifically the inner white block’s. On top of that, I haven’t added any of those loop count fields, for which I’m guessing similar problems would arise.

For some reason I got the feeling that custom types would be of great help in making the notch placement more flexible, as you could conditionally use something like child.with() in a given block function if that makes sense.

1 Like