Add initial Pico-8 cartridges for testing

- Created test_cart.p8 with basic drawing functionality and a print statement.
- Created test_unix.p8 with a simple initialization function.
This commit is contained in:
2026-03-21 18:38:43 +11:00
parent 71354ad463
commit f917cf6ad5
56 changed files with 5200 additions and 688 deletions

View File

@@ -0,0 +1,184 @@
/**
* Ordered PICO-8 palette hex strings, indices 015.
*
* @type {ReadonlyArray<string>}
*/
const PICO8_COLORS = Object.freeze([
'#000002', '#1D2B53', '#7E2553', '#008751',
'#AB5236', '#5F574F', '#C2C3C7', '#FFF1E8',
'#FF004D', '#FFA300', '#FFEC27', '#00E436',
'#29ADFF', '#83769C', '#FF77A8', '#FFCCAA',
]);
/**
* @typedef {{ element: HTMLElement, setValue: (index: number) => void }} ColorSwatchPickerInstance
*/
/**
* Builds an inline PICO-8 color swatch picker control.
*
* Renders a colored trigger button that opens a 4×4 grid of all 16 PICO-8
* palette swatches. Clicking a swatch fires `onChange` with the selected
* palette index (015) and closes the dropdown.
*
* @param {{
* id?: string,
* value: number,
* onChange: (index: number) => void,
* }} options Picker configuration.
* @returns {ColorSwatchPickerInstance}
*/
export function createColorSwatchPicker({ id, value, onChange }) {
const wrapper = document.createElement('div');
wrapper.className = 'color-swatch-picker';
const trigger = document.createElement('button');
trigger.type = 'button';
trigger.className = 'color-swatch-trigger';
if (id) {
trigger.id = id;
}
trigger.setAttribute('aria-haspopup', 'menu');
trigger.setAttribute('aria-expanded', 'false');
wrapper.appendChild(trigger);
const dropdown = document.createElement('div');
dropdown.className = 'color-swatch-dropdown';
dropdown.setAttribute('role', 'menu');
// Appended to body on open so it escapes overflow:hidden on node cards.
/** @type {Array<HTMLButtonElement>} */
const swatchButtons = [];
/** @type {((event: MouseEvent) => void) | null} */
let outsideClickHandler = null;
/** @type {(() => void) | null} */
let scrollAbortHandler = null;
const repositionDropdown = () => {
const rect = trigger.getBoundingClientRect();
dropdown.style.top = `${rect.bottom + 4}px`;
dropdown.style.left = `${rect.left}px`;
};
const closeDropdown = () => {
if (dropdown.isConnected) {
dropdown.remove();
}
trigger.setAttribute('aria-expanded', 'false');
if (outsideClickHandler) {
document.removeEventListener('click', outsideClickHandler, true);
outsideClickHandler = null;
}
if (scrollAbortHandler) {
document.removeEventListener('scroll', scrollAbortHandler, true);
scrollAbortHandler = null;
}
};
const openDropdown = () => {
repositionDropdown();
document.body.appendChild(dropdown);
trigger.setAttribute('aria-expanded', 'true');
outsideClickHandler = (event) => {
if (
!(event.target instanceof Node) ||
(!wrapper.contains(event.target) && !dropdown.contains(event.target))
) {
closeDropdown();
}
};
scrollAbortHandler = () => closeDropdown();
document.addEventListener('click', outsideClickHandler, true);
document.addEventListener('scroll', scrollAbortHandler, true);
};
let currentIndex = clampColorIndex(value);
/**
* Updates the trigger button to reflect the supplied palette index.
*
* @param {number} index Palette index (015).
*/
const updateTrigger = (index) => {
const hex = PICO8_COLORS[index] ?? '#000000';
trigger.style.backgroundColor = hex;
trigger.title = `Color ${index}`;
trigger.setAttribute('aria-label', `Color ${index}`);
};
PICO8_COLORS.forEach((hex, index) => {
const swatch = document.createElement('button');
swatch.type = 'button';
swatch.className = 'color-swatch-option';
swatch.style.backgroundColor = hex;
swatch.dataset.colorIndex = String(index);
swatch.title = String(index);
swatch.setAttribute('role', 'menuitemradio');
swatch.setAttribute('aria-label', `Color ${index}`);
swatch.setAttribute('aria-checked', 'false');
swatch.addEventListener('pointerdown', (event) => {
event.stopPropagation();
});
swatch.addEventListener('click', (event) => {
event.preventDefault();
event.stopPropagation();
const selected = index;
closeDropdown();
if (selected !== currentIndex) {
currentIndex = selected;
updateTrigger(selected);
swatchButtons.forEach((btn, i) => {
btn.setAttribute('aria-checked', i === selected ? 'true' : 'false');
});
onChange(selected);
}
});
dropdown.appendChild(swatch);
swatchButtons.push(swatch);
});
trigger.addEventListener('pointerdown', (event) => {
event.stopPropagation();
});
trigger.addEventListener('click', (event) => {
event.stopPropagation();
if (dropdown.isConnected) {
closeDropdown();
} else {
openDropdown();
}
});
/**
* Updates the picker display without firing `onChange`.
*
* @param {number} newIndex Palette index (015).
*/
const setValue = (newIndex) => {
currentIndex = clampColorIndex(newIndex);
updateTrigger(currentIndex);
swatchButtons.forEach((btn, i) => {
btn.setAttribute('aria-checked', i === currentIndex ? 'true' : 'false');
});
};
setValue(currentIndex);
return { element: wrapper, setValue };
}
/**
* Clamps an arbitrary value to a valid PICO-8 palette index (015).
*
* @param {unknown} value Raw index candidate.
* @returns {number}
*/
function clampColorIndex(value) {
const n = typeof value === 'number' ? value : Number(value);
const safe = Number.isFinite(n) ? Math.round(n) : 0;
return Math.max(0, Math.min(15, safe));
}