This commit is contained in:
りき萌 2024-02-14 23:31:39 +01:00
parent 1305ffbb16
commit 1013c53975
21 changed files with 988 additions and 43 deletions

View file

@ -87,6 +87,12 @@ body::selection {
src: url('../font/Recursive_VF_1.085.woff2');
}
@font-face {
font-family: 'RecVarMono';
src: url('../font/Recursive_VF_1.085.woff2');
font-variation-settings: "MONO" 1.0;
}
body,
pre,
@ -256,6 +262,29 @@ img.pic {
margin: 8px 0;
}
/* Image hints for tweaking rendering */
img {
&[src*='+pixel'] {
image-rendering: crisp-edges;
border-radius: 0;
}
&[src*='+width160'] {
width: 160px;
height: auto;
}
&[src*='+width640'] {
width: 640px;
height: auto;
}
/* Resources for use in JavaScript. */
&.resource {
display: none;
}
}
/* Fix the default blue and ugly purple links normally have */
a {

View file

@ -1 +1,128 @@
.tileset-cardinal-directions-demo th-bc {
& ul {
display: flex;
flex-direction: row;
}
& ul.tileset-demo {
margin-top: 16px;
}
& ul.tileset-demo::after {
display: none !important;
}
& li.full-image {
flex-shrink: 0;
}
& li.tileset-pieces {
display: flex;
flex-wrap: wrap;
align-items: center;
justify-content: center;
}
& .horizontal-tile-strip {
display: flex;
flex-direction: row;
justify-content: center;
flex-wrap: wrap;
& .metal {
width: 48px;
height: 48px;
&>span {
font-size: 12px;
}
}
}
& .metal {
display: inline-block;
width: 56px;
height: 56px;
margin: 8px;
background-image: url('../pic/01HPHVDRV0F0251MD0A2EG66C4-tilemap-heavy-metal-16+pixel+width160.png');
background-size: 400%;
image-rendering: crisp-edges;
position: relative;
}
& .east,
& .south,
& .west,
& .north {
--recursive-wght: 900;
--recursive-casl: 0.0;
--recursive-slnt: 0.0;
--recursive-mono: 1.0;
font-size: 14px;
position: absolute;
color: #d3dce9;
padding: 2px 4px;
}
& .east {
right: 0;
top: 50%;
transform: translateY(-50%);
}
& .south {
bottom: 0;
left: 50%;
transform: translateX(-50%);
}
& .west {
left: 0;
top: 50%;
transform: translateY(-50%);
}
& .north {
top: 0;
left: 50%;
transform: translateX(-50%);
}
& .off {
opacity: 0%;
}
& .x-0 {
background-position-x: 0%;
}
& .x-1 {
background-position-x: 33.3333%;
}
& .x-2 {
background-position-x: 66.6666%;
}
& .x-3 {
background-position-x: 100%;
}
& .y-0 {
background-position-y: 0%;
}
& .y-1 {
background-position-y: 33.3333%;
}
& .y-2 {
background-position-y: 66.6666%;
}
& .y-3 {
background-position-y: 100%;
}
}

View file

@ -351,7 +351,35 @@ th-bb .branch-date {
/* branch-quote class for "air quote branches"; used to separate a subtree from a parent tree
stylistically such that it's interpretable as a form of block quote. */
ul.branch-quote {
padding: 8px;
--vertical-margin: 8px;
--padding: 8px;
margin-top: var(--vertical-margin);
margin-bottom: var(--vertical-margin);
padding-top: var(--padding);
padding-bottom: var(--padding);
padding-right: var(--padding);
border: 1px solid var(--border-1);
border-radius: 8px;
position: relative;
&::before {
--recursive-wght: 900;
--recursive-casl: 0;
content: '“';
position: absolute;
right: 16px;
top: 1px;
font-size: 3rem;
opacity: 50%;
transition: opacity var(--transition-duration);
}
&:hover::before {
opacity: 0%;
}
}

View file

@ -0,0 +1,41 @@
import { defineFrame, Frame } from './framework.js';
import { TileEditor, canConnect, shouldConnect } from './tairu.js';
class CardinalDirectionsEditor extends TileEditor {
constructor() {
super();
this.colorScheme.tiles[1] = "#f96565";
}
drawConnectionText(text, enabled, tileX, tileY, hAlign, vAlign) {
this.ctx.beginPath();
this.ctx.fillStyle = enabled ? "#6c023e" : "#d84161";
this.ctx.font = `800 14px ${Frame.monoFontFace}`;
const padding = 2;
let topLeftX = tileX * this.tileSize + padding;
let topLeftY = tileY * this.tileSize + padding;
let rectSize = this.tileSize - padding * 2;
let { leftX, baselineY } = this.getTextPositionInBox(text, topLeftX, topLeftY, rectSize, rectSize, hAlign, vAlign);
this.ctx.fillText(text, leftX, baselineY);
}
drawTiles() {
super.drawTiles();
for (let y = 0; y < this.tilemap.height; ++y) {
for (let x = 0; x < this.tilemap.width; ++x) {
let tile = this.tilemap.at(x, y);
if (canConnect(tile)) {
let connectedWithEast = shouldConnect(tile, this.tilemap.at(x + 1, y));
let connectedWithSouth = shouldConnect(tile, this.tilemap.at(x, y + 1));
let connectedWithNorth = shouldConnect(tile, this.tilemap.at(x, y - 1));
let connectedWithWest = shouldConnect(tile, this.tilemap.at(x - 1, y));
this.drawConnectionText("E", connectedWithEast, x, y, "right", "center");
this.drawConnectionText("S", connectedWithSouth, x, y, "center", "bottom");
this.drawConnectionText("N", connectedWithNorth, x, y, "center", "top");
this.drawConnectionText("W", connectedWithWest, x, y, "left", "center");
}
}
}
}
}
defineFrame("tairu-editor-cardinal-directions", CardinalDirectionsEditor);

View file

@ -1,13 +1,87 @@
// A frameworking class assigning some CSS classes to the canvas to make it integrate nicer with CSS.
class Frame extends HTMLCanvasElement {
export class Frame extends HTMLCanvasElement {
static fontFace = "RecVar";
static monoFontFace = "RecVarMono";
constructor() {
super();
}
async connectedCallback() {
this.style.cssText = `
margin-top: 8px;
margin-bottom: 4px;
border-radius: 4px;
max-width: 100%;
`;
this.ctx = this.getContext("2d");
requestAnimationFrame(this.#drawLoop.bind(this));
}
#drawLoop() {
this.ctx.font = "14px RecVar";
this.draw();
requestAnimationFrame(this.#drawLoop.bind(this));
}
// Override this!
draw() { }
draw() {
throw new ReferenceError("draw() must be overridden");
}
getTextPositionInBox(text, x, y, width, height, hAlign, vAlign) {
let measurements = this.ctx.measureText(text);
let leftX;
switch (hAlign) {
case "left":
leftX = x;
break;
case "center":
leftX = x + width / 2 - measurements.width / 2;
break;
case "right":
leftX = x + width - measurements.width;
break;
}
let textHeight = measurements.fontBoundingBoxAscent;
let baselineY;
switch (vAlign) {
case "top":
baselineY = y + textHeight;
break;
case "center":
baselineY = y + height / 2 + textHeight / 2;
break;
case "bottom":
baselineY = y + height;
break;
}
return { leftX, baselineY };
}
get scaleInViewportX() {
return this.clientWidth / this.width;
}
get scaleInViewportY() {
return this.clientHeight / this.height;
}
getMousePositionFromEvent(event) {
return {
x: event.offsetX / this.scaleInViewportX,
y: event.offsetY / this.scaleInViewportY,
};
}
}
export function defineFrame(elementName, claß) { // because `class` is a keyword.
customElements.define(elementName, claß, { extends: "canvas" });
}
defineFrame("tairu--frame", Frame);

View file

@ -1,8 +1,192 @@
class TileEditor extends HTMLCanvasElement {
import { Frame, defineFrame } from "./framework.js";
import tilemapRegistry from "./tilemap-registry.js";
export function canConnect(tile) {
return tile == 1;
}
export function shouldConnect(a, b) {
return a == b;
}
export class TileEditor extends Frame {
constructor() {
super();
this.tileCursor = { x: 0, y: 0 };
this.colorScheme = {
background: "#F7F7F7",
grid: "#00000011",
tileCursor: "#222222",
tiles: [
"transparent",
"#eb134a",
],
};
this.tileColorPalette = [
"transparent",
"#eb134a",
];
}
connectedCallback() {
super.connectedCallback();
this.tileSize = parseInt(this.getAttribute("data-tile-size"));
let tilemapId = this.getAttribute("data-tilemap-id");
console.log(tilemapRegistry);
if (tilemapId != null) {
this.tilemap = tilemapRegistry[this.getAttribute("data-tilemap-id")];
} else {
throw new ReferenceError(`tilemap '${tilemapId}' does not exist`);
}
// 0st element is explicitly null because it represents the empty tile.
this.tilesets = [null];
let attachedImages = this.getElementsByTagName("img");
for (let image of attachedImages) {
if (image.hasAttribute("data-tairu-tileset")) {
let tilesetIndex = parseInt(image.getAttribute("data-tairu-tileset"));
this.tilesets[tilesetIndex] = image;
}
}
this.width = this.tilemap.width * this.tileSize;
this.height = this.tilemap.height * this.tileSize;
this.hasFocus = false;
this.paintingTile = null;
this.addEventListener("mousemove", event => this.mouseMoved(event));
this.addEventListener("mousedown", event => this.mousePressed(event));
this.addEventListener("mouseup", event => this.mouseReleased(event));
this.addEventListener("mouseenter", _ => this.hasFocus = true);
this.addEventListener("mouseleave", _ => this.hasFocus = false);
this.addEventListener("contextmenu", event => event.preventDefault());
// TODO: This should also work on mobile.
}
draw() {
this.ctx.fillStyle = this.colorScheme.background;
this.ctx.fillRect(0, 0, this.width, this.height);
this.drawTiles();
this.drawGrid();
if (this.hasFocus) {
this.drawTileCursor();
}
}
drawGrid() {
this.ctx.beginPath();
for (let x = 0; x < this.tilemap.width; ++x) {
this.ctx.moveTo(x * this.tileSize, 0);
this.ctx.lineTo(x * this.tileSize, this.height);
}
for (let y = 0; y < this.tilemap.width; ++y) {
this.ctx.moveTo(0, y * this.tileSize);
this.ctx.lineTo(this.width, y * this.tileSize);
}
this.ctx.strokeStyle = this.colorScheme.grid;
this.ctx.lineWidth = 1;
this.ctx.stroke();
}
drawTileCursor() {
this.ctx.strokeStyle = this.colorScheme.tileCursor;
this.ctx.lineWidth = 5;
this.ctx.strokeRect(this.tileCursor.x * this.tileSize, this.tileCursor.y * this.tileSize, this.tileSize, this.tileSize);
}
get hasTilesets() {
// Remember that tile 0 represents emptiness.
return this.tilesets.length > 1;
}
drawTiles() {
if (this.hasTilesets) {
this.drawTexturedTiles();
} else {
this.drawColoredTiles();
}
}
drawColoredTiles() {
for (let y = 0; y < this.tilemap.height; ++y) {
for (let x = 0; x < this.tilemap.width; ++x) {
let tile = this.tilemap.at(x, y);
if (tile != 0) {
this.ctx.fillStyle = this.colorScheme.tiles[tile];
this.ctx.fillRect(x * this.tileSize, y * this.tileSize, this.tileSize, this.tileSize);
}
}
}
}
drawTexturedTiles() {
this.ctx.imageSmoothingEnabled = false;
for (let y = 0; y < this.tilemap.height; ++y) {
for (let x = 0; x < this.tilemap.width; ++x) {
let tile = this.tilemap.at(x, y);
if (tile != 0) {
let tileset = this.tilesets[tile];
let connectedWithEast = shouldConnect(tile, this.tilemap.at(x + 1, y)) ? 0b0001 : 0;
let connectedWithSouth = shouldConnect(tile, this.tilemap.at(x, y + 1)) ? 0b0010 : 0;
let connectedWithWest = shouldConnect(tile, this.tilemap.at(x - 1, y)) ? 0b0100 : 0;
let connectedWithNorth = shouldConnect(tile, this.tilemap.at(x, y - 1)) ? 0b1000 : 0;
let tileIndex = connectedWithNorth
| connectedWithWest
| connectedWithSouth
| connectedWithEast;
let tilesetTileSize = tileset.height;
let tilesetX = tileIndex * tilesetTileSize;
let tilesetY = 0;
this.ctx.drawImage(
this.tilesets[tile],
tilesetX, tilesetY, tilesetTileSize, tilesetTileSize,
x * this.tileSize, y * this.tileSize, this.tileSize, this.tileSize,
);
}
}
}
}
mouseMoved(event) {
let mouse = this.getMousePositionFromEvent(event);
this.tileCursor.x = Math.floor(mouse.x / this.tileSize);
this.tileCursor.y = Math.floor(mouse.y / this.tileSize);
this.paintTileUnderCursor();
}
mousePressed(event) {
event.preventDefault();
if (event.button == 0) {
this.paintingTile = 1;
} else if (event.button == 2) {
this.paintingTile = 0;
}
this.paintTileUnderCursor();
}
mouseReleased() {
this.paintingTile = null;
}
paintTileUnderCursor() {
if (this.paintingTile != null) {
this.tilemap.setAt(this.tileCursor.x, this.tileCursor.y, this.paintingTile);
}
}
}
customElements.define("tairu-tile-editor", TileEditor)
defineFrame("tairu-editor", TileEditor);
console.log("tairu editor loaded");

View file

@ -0,0 +1,33 @@
import { Tilemap } from './tilemap.js';
const alphabet = " x";
function parseTilemap(lineArray) {
let tilemap = new Tilemap(lineArray[0].length, lineArray.length);
for (let y in lineArray) {
let line = lineArray[y];
for (let x = 0; x < line.length; ++x) {
let char = line.charAt(x);
tilemap.setAt(x, y, alphabet.indexOf(char));
}
}
return tilemap;
}
export default {
bitwiseAutotiling: parseTilemap([
" ",
" xxx ",
" xxx ",
" xxx ",
" ",
]),
bitwiseAutotilingChapter2: parseTilemap([
" ",
" x ",
" x ",
" xxx ",
" ",
]),
};

View file

@ -0,0 +1,30 @@
export class Tilemap {
constructor(width, height) {
this.width = width;
this.height = height;
this.tiles = new Uint8Array(width * height);
this.default = 0;
}
tileIndex(x, y) {
return x + y * this.width;
}
inBounds(x, y) {
return x >= 0 && y >= 0 && x < this.width && y < this.height;
}
at(x, y) {
if (this.inBounds(x, y)) {
return this.tiles[this.tileIndex(x, y)];
} else {
return this.default;
}
}
setAt(x, y, tile) {
if (this.inBounds(x, y)) {
this.tiles[this.tileIndex(x, y)] = tile;
}
}
}

View file

@ -112,11 +112,17 @@ class LinkedBranch extends Branch {
let styles = main.getElementsByTagName("link");
let scripts = main.getElementsByTagName("script");
this.append(...styles);
this.append(...scripts);
this.loadingText.remove();
this.innerUL.innerHTML = ul.innerHTML;
this.append(...styles);
for (let script of scripts) {
// No need to await for the import because we don't use the resulting module.
// Just fire and forger 💀
// and let them run in parallel.
import(script.src);
}
} catch (error) {
this.loadingText.innerText = error.toString();
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 284 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 313 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 313 B