Compare commits
3 Commits
b1122bdbf5
...
9d6c8b6888
| Author | SHA1 | Date | |
|---|---|---|---|
| 9d6c8b6888 | |||
| 717e73d32d | |||
| c9a65e3bcf |
60
color-input.js
Normal file
60
color-input.js
Normal file
@ -0,0 +1,60 @@
|
||||
const controlGrid = document.getElementById("control-parent");
|
||||
|
||||
function parseColor(c)
|
||||
{
|
||||
const groups = c.match(/^#([0-9a-fA-F]{2})([0-9a-fA-F]{2})([0-9a-fA-F]{2})$/i);
|
||||
if (groups == null) {
|
||||
throw new Error("invalid color", c);
|
||||
} else {
|
||||
return [parseInt(groups[1], 16), parseInt(groups[2], 16), parseInt(groups[3], 16)];
|
||||
}
|
||||
}
|
||||
|
||||
class ColorInput {
|
||||
constructor(label, defaultValue)
|
||||
{
|
||||
const parent = controlGrid;
|
||||
|
||||
const labelE = document.createElement("label");
|
||||
labelE.innerHTML = label;
|
||||
|
||||
const inputE = document.createElement("input");
|
||||
inputE.type = "color";
|
||||
inputE.className = "span-2";
|
||||
|
||||
const rowE = document.createElement("row");
|
||||
const sepE = document.createElement("row-separator");
|
||||
|
||||
rowE.appendChild(labelE);
|
||||
rowE.appendChild(inputE);
|
||||
|
||||
parent.appendChild(rowE);
|
||||
parent.appendChild(sepE);
|
||||
|
||||
this.label = label;
|
||||
this.input = inputE;
|
||||
|
||||
this.lastValue = undefined;
|
||||
|
||||
const storageValue = localStorage.getItem(this.label);
|
||||
if (storageValue !== null) {
|
||||
this.input.value = storageValue;
|
||||
} else {
|
||||
this.input.value = defaultValue;
|
||||
}
|
||||
|
||||
this.update();
|
||||
}
|
||||
|
||||
update()
|
||||
{
|
||||
const rawValue = this.input.value;
|
||||
if (rawValue !== this.lastValue) {
|
||||
this.lastValue = rawValue;
|
||||
localStorage.setItem(this.label, rawValue);
|
||||
}
|
||||
return parseColor(rawValue);
|
||||
}
|
||||
};
|
||||
|
||||
export { ColorInput };
|
||||
47
font.js
47
font.js
@ -110,6 +110,18 @@ class FontRenderer {
|
||||
}
|
||||
}
|
||||
|
||||
font_layout__draw(s)
|
||||
{
|
||||
const length = Math.min(s.length, this.maxStringLength - 1);
|
||||
for (let i = 0; i < length; i++) {
|
||||
const c = s.charCodeAt(i) % 256;
|
||||
this.stringBuffer[i] = c;
|
||||
}
|
||||
this.stringBuffer[length] = 0;
|
||||
|
||||
this.exports.font_layout__draw(this.fontLayoutAddress, this.stringBufferAddress);
|
||||
}
|
||||
|
||||
constructor(device, canvasFormat, viewUniformBuffer, wgsl, module, fontBufferSrc)
|
||||
{
|
||||
const label = "font";
|
||||
@ -127,6 +139,12 @@ class FontRenderer {
|
||||
this.maxGlyphs = 1024;
|
||||
this.fontLayoutAddress = this.exports.font_layout__create(this.fontBufferAddress, this.maxGlyphs);
|
||||
|
||||
// text buffer
|
||||
this.maxStringLength = 1024;
|
||||
this.stringBufferAddress = this.exports.mem_alloc(this.maxStringLength);
|
||||
this.stringBuffer = new Uint8Array(this.module.memory.buffer, this.stringBufferAddress);
|
||||
this.lastString = undefined;
|
||||
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
// shader module
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
@ -257,7 +275,7 @@ class FontRenderer {
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
|
||||
this.sliders = makeSliders();
|
||||
this.sliderArray = new Float32Array(4 * 4);
|
||||
this.sliderArray = new Float32Array(4 * 6);
|
||||
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
// buffers
|
||||
@ -281,6 +299,21 @@ class FontRenderer {
|
||||
|
||||
update(device, frameNumber)
|
||||
{
|
||||
const configuration = updateSliders(this.sliders);
|
||||
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
// string draw
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
|
||||
if (this.lastString === undefined || this.lastString !== configuration.text) {
|
||||
this.lastString = configuration.text;
|
||||
this.font_layout__draw(configuration.text);
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
// layout buffer
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
|
||||
this.instanceCount = this.exports.font_layout__layout_buffer_index(this.fontLayoutAddress);
|
||||
const layoutBufferSize = this.instanceCount * 4 * 4;
|
||||
|
||||
@ -291,8 +324,6 @@ class FontRenderer {
|
||||
// sliders
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
|
||||
const configuration = updateSliders(this.sliders);
|
||||
|
||||
this.sliderArray[0] = configuration.mode;
|
||||
this.sliderArray[1] = configuration.scale;
|
||||
this.sliderArray[2] = configuration.translateX;
|
||||
@ -313,6 +344,14 @@ class FontRenderer {
|
||||
this.sliderArray[14] = configuration.outlineThreshold;
|
||||
this.sliderArray[15] = configuration.outlineScale;
|
||||
|
||||
this.sliderArray[16] = configuration.insideColor[0] / 255.0;
|
||||
this.sliderArray[17] = configuration.insideColor[1] / 255.0;
|
||||
this.sliderArray[18] = configuration.insideColor[2] / 255.0;
|
||||
|
||||
this.sliderArray[20] = configuration.outlineColor[0] / 255.0;
|
||||
this.sliderArray[21] = configuration.outlineColor[1] / 255.0;
|
||||
this.sliderArray[22] = configuration.outlineColor[2] / 255.0;
|
||||
|
||||
device.queue.writeBuffer(this.frames[frameNumber].configurationBuffer, 0,
|
||||
this.sliderArray);
|
||||
|
||||
@ -342,8 +381,6 @@ async function loadFont(device, canvasFormat, viewUniformBuffer, module)
|
||||
|
||||
const renderer = new FontRenderer(device, canvasFormat, viewUniformBuffer, fontWgsl, module, liberationBuffer);
|
||||
|
||||
module.instance.exports.font_layout__draw(renderer.fontLayoutAddress);
|
||||
|
||||
return renderer;
|
||||
}
|
||||
|
||||
|
||||
11
font.wgsl
11
font.wgsl
@ -26,6 +26,9 @@ struct Config {
|
||||
supersampleOffset: f32,
|
||||
outlineThreshold: f32,
|
||||
outlineScale: f32,
|
||||
|
||||
insideColor: vec4f,
|
||||
outlineColor: vec4f,
|
||||
};
|
||||
|
||||
struct GlyphBuffer {
|
||||
@ -154,8 +157,8 @@ const offsets = array(
|
||||
|
||||
fn supersampleColor(texture: vec2f, s: vec2f, p: vec2f) -> vec4f
|
||||
{
|
||||
let insideColor = vec4f(1, 1, 1, 1);
|
||||
let outlineColor = vec4f(0, 0, 0, 1);
|
||||
let insideColor = vec4f(config.insideColor.xyz, 1);
|
||||
let outlineColor = vec4f(config.outlineColor.xyz, 1);
|
||||
let outsideColor = vec4f(0, 0, 0, 0);
|
||||
|
||||
let dx = dpdx(texture.x * s.x);
|
||||
@ -223,11 +226,13 @@ fn fragmentMain(input: VertexOutput) -> FragmentOutput
|
||||
|
||||
var output: FragmentOutput;
|
||||
|
||||
if (config.mode == 0) {
|
||||
if (config.mode == 1) {
|
||||
output.color = subpixelColor(input.texture, s, p);
|
||||
} else {
|
||||
output.color = supersampleColor(input.texture, s, p);
|
||||
}
|
||||
|
||||
output.color = vec4f(output.color.xyz / output.color.w, output.color.w);
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
@ -1,13 +1,15 @@
|
||||
import { Slider } from "./slider.js"
|
||||
import { Select } from "./select.js"
|
||||
import { RowHeader } from "./row-header.js"
|
||||
import { TextInput } from "./text-input.js"
|
||||
import { ColorInput } from "./color-input.js"
|
||||
|
||||
function makeSliders()
|
||||
{
|
||||
const sliders = {
|
||||
subpixelOptions: new RowHeader("global options"),
|
||||
mode: new Select("mode", ["subpixel", "supersampling"]),
|
||||
scale: new Slider("scale", 0.0, 0.01),
|
||||
mode: new Select("mode", ["supersampling", "subpixel"]),
|
||||
scale: new Slider("scale", 0.0, 0.05),
|
||||
translateX: new Slider("translate x", -1, 1),
|
||||
translateY: new Slider("translate y", -1, 1),
|
||||
multisampleCount: new Slider("multisampleCount", 1, 4, 1),
|
||||
@ -15,8 +17,10 @@ function makeSliders()
|
||||
distanceThreshold: new Slider("distance threshold", 0.25, 0.75),
|
||||
distanceScale: new Slider("distance scale", 0.0, 20.0),
|
||||
aliasingEnable: new Slider("aliasing enable", 0, 1, 1),
|
||||
//rampMin: new Slider("ramp min", 0, 1.0),
|
||||
//rampMax: new Slider("ramp max", 0, 1.0),
|
||||
|
||||
text: new TextInput("text"),
|
||||
insideColor: new ColorInput("inside color", "#ffffff"),
|
||||
outlineColor: new ColorInput("outline color", "#000000"),
|
||||
|
||||
subpixelOptions: new RowHeader("subpixel mode options"),
|
||||
subpixelEnable: new Slider("subpixel enable", 0, 1, 1),
|
||||
@ -34,13 +38,13 @@ function makeSliders()
|
||||
//colorOffset: new Slider("color offset", 0.0, 3.1415),
|
||||
};
|
||||
|
||||
const defaults = {"undefined":"0","subpixel offset":"62","text":"supersampling","bone length":"32","outline scale":"208","ramp min":"0","dist. threshold":"513","velocity acc.":"385","gamma":"494","aliasing enable":"1","outline threshold":"335","outline color":"#000000","threshold offset":"265","outline. threshold":"512","outline offset":"0","inside color":"#ffffff","color mix":"0","scale":"89","supersample count":"1","distance threshold":"494","supersamples":"0","distance offset":"513","translate y":"381","ramp max":"1024","multisampleCount":"1","supersample offset":"248","subpixel enable":"1","translate x":"581","color offset":"118","velocity damp.":"796","distance scale":"49","threshold scale":"739","bone count":"40","color scale":"148","subpixel strength":"348"};
|
||||
/*
|
||||
const defaults = {"color scale":"148","bone length":"32","outline scale":"37","dist. threshold":"562","velocity acc.":"385","translate x":"464","outline threshold":"433","translate y":"495","bone count":"40","outline offset":"482","scale":"229","outline. threshold":"512","color offset":"118","distance offset":"676","threshold offset":"265","velocity damp.":"796","distance scale":"404","threshold scale":"739","color mix":"0"};
|
||||
for (let slider of Object.values(sliders)) {
|
||||
if (slider.label in defaults) {
|
||||
slider.input.value = defaults[slider.label];
|
||||
}
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
return sliders;
|
||||
|
||||
22
index.html
22
index.html
@ -61,13 +61,27 @@
|
||||
border-bottom: 2px solid #888 !important;
|
||||
}
|
||||
|
||||
select, option {
|
||||
select, option, input[type="text"] {
|
||||
font: 0.8rem sans-serif;
|
||||
}
|
||||
input[type="range"] {
|
||||
|
||||
input[type="button"] {
|
||||
width: calc(100% - 1.1em);
|
||||
grid-column: 1 / span 3;
|
||||
}
|
||||
|
||||
input[type="range"], input[type="text"] {
|
||||
height: 1rem;
|
||||
}
|
||||
input[type="color"] {
|
||||
height: 1.25rem;
|
||||
}
|
||||
input[type="range"] {
|
||||
width: calc(100% - 0.5em);
|
||||
}
|
||||
input[type="text"] {
|
||||
width: calc(100% - 1em);
|
||||
}
|
||||
.span-2 {
|
||||
grid-column: span 2;
|
||||
width: calc(100% - 0.5em);
|
||||
@ -91,6 +105,10 @@
|
||||
<control-root>
|
||||
<control-main>
|
||||
<control-grid id="control-parent">
|
||||
<row>
|
||||
<input id="download-render-buffer" type="button" value="download render buffer"></input>
|
||||
</row>
|
||||
<row-separator></row-separator>
|
||||
</control-grid>
|
||||
</control-main>
|
||||
</control-root>
|
||||
|
||||
130
index.js
130
index.js
@ -56,6 +56,7 @@ module.memory = memory;
|
||||
|
||||
var depthTexture = undefined;
|
||||
var multisampleTexture = undefined;
|
||||
var copyTexture = undefined;
|
||||
|
||||
const eyeValueX = document.getElementById("eye-value-x");
|
||||
const eyeValueY = document.getElementById("eye-value-y");
|
||||
@ -64,6 +65,9 @@ const eyeValueZ = document.getElementById("eye-value-z");
|
||||
const yawValue = document.getElementById("yaw");
|
||||
const pitchValue = document.getElementById("pitch");
|
||||
|
||||
var downloadRenderBufferPending = false;
|
||||
var tempBuffer = undefined;
|
||||
|
||||
function recreateDepth(canvasTexture, sampleCount)
|
||||
{
|
||||
const depthResized = depthTexture === undefined || canvasTexture.width != depthTexture.width || canvasTexture.height != depthTexture.height;
|
||||
@ -81,6 +85,33 @@ function recreateDepth(canvasTexture, sampleCount)
|
||||
});
|
||||
}
|
||||
|
||||
const tempBufferSize = canvasTexture.width * canvasTexture.height * 4;
|
||||
const tempResized = tempBuffer === undefined || tempBufferSize != tempBuffer.size;
|
||||
if (tempResized) {
|
||||
if (tempBuffer !== undefined) {
|
||||
tempBuffer.destroy();
|
||||
}
|
||||
tempBuffer = device.createBuffer({
|
||||
label: "temp buffer",
|
||||
size: tempBufferSize,
|
||||
usage: GPUBufferUsage.MAP_READ | GPUBufferUsage.COPY_DST,
|
||||
});
|
||||
}
|
||||
|
||||
const copyResized = copyTexture === undefined || canvasTexture.width !== copyTexture.width || canvasTexture.height !== copyTexture.height;
|
||||
if (copyResized) {
|
||||
if (copyTexture !== undefined) {
|
||||
copyTexture.destroy();
|
||||
}
|
||||
|
||||
copyTexture = device.createTexture({
|
||||
format: canvasTexture.format,
|
||||
usage: GPUTextureUsage.RENDER_ATTACHMENT | GPUTextureUsage.COPY_SRC,
|
||||
size: [canvasTexture.width, canvasTexture.height],
|
||||
sampleCount: 1,
|
||||
});
|
||||
}
|
||||
|
||||
if (sampleCount > 1) {
|
||||
const multisampleResized = multisampleTexture === undefined || canvasTexture.width !== multisampleTexture.width || canvasTexture.height !== multisampleTexture.height;
|
||||
if (multisampleResized || multisampleTexture.sampleCount !== sampleCount) {
|
||||
@ -177,9 +208,18 @@ function handleKeyup(e)
|
||||
}
|
||||
}
|
||||
|
||||
function handleDownloadButton(e)
|
||||
{
|
||||
downloadRenderBufferPending = true;
|
||||
console.log("download framebuffer");
|
||||
}
|
||||
|
||||
window.addEventListener('keydown', handleKeydown, false);
|
||||
window.addEventListener('keyup', handleKeyup, false);
|
||||
|
||||
const downloadRenderBufferButton = document.getElementById("download-render-buffer");
|
||||
downloadRenderBufferButton.addEventListener("click", handleDownloadButton, false);
|
||||
|
||||
function updateView()
|
||||
{
|
||||
const lu = keyState[KEY.W] === true;
|
||||
@ -260,7 +300,30 @@ canvas.addEventListener("mousemove", onMouseMove);
|
||||
var canRender = false;
|
||||
var frameNumber = 0;
|
||||
|
||||
var tempBuffer = undefined;
|
||||
function tgaBlob(texture, mappedRange)
|
||||
{
|
||||
const tgaHeaderSize = 18;
|
||||
const tgaBuffer = new ArrayBuffer(mappedRange.byteLength + tgaHeaderSize);
|
||||
const tgaView = new DataView(tgaBuffer);
|
||||
tgaView.setUint8(0, 0); // idLength
|
||||
tgaView.setUint8(1, 0); // colorMapType
|
||||
tgaView.setUint8(2, 2); // imageTypeCode: 2 uncompressed RGB
|
||||
tgaView.setUint16(3, 0, true); // colorMap origin
|
||||
tgaView.setUint16(5, 0, true); // colorMap length
|
||||
tgaView.setUint8(7, 0); // colorMap depth
|
||||
tgaView.setUint16(8, 0, true); // xOrigin
|
||||
tgaView.setUint16(10, 0, true); // yOrigin
|
||||
tgaView.setUint16(12, texture.width, true); // width
|
||||
tgaView.setUint16(14, texture.height, true); // height
|
||||
tgaView.setUint8(16, 32); // bits per pixel
|
||||
const descriptor = ((8 << 0) | // number of attribute bits
|
||||
(1 << 5) | // origin in upper left
|
||||
(0 << 6)); // non-interleaved
|
||||
tgaView.setUint8(17, descriptor); // descriptor
|
||||
const tgaU8 = new Uint8Array(tgaBuffer);
|
||||
tgaU8.set(new Uint8Array(mappedRange), 18);
|
||||
return new Blob([tgaBuffer]);
|
||||
}
|
||||
|
||||
function render2()
|
||||
{
|
||||
@ -281,16 +344,19 @@ function render2()
|
||||
|
||||
updateView();
|
||||
|
||||
const clearValue = { r: 0.2, g: 0.2, b: 0.4, a: 1.0 };
|
||||
//const clearValue = ;
|
||||
const clearValue = (downloadRenderBufferPending) ? { r: 0.0, g: 0.0, b: 0.0, a: 0.0 } : { r: 0.2, g: 0.2, b: 0.4, a: 1.0 };
|
||||
|
||||
const viewTexture = (downloadRenderBufferPending) ? copyTexture : canvasTexture;
|
||||
|
||||
const attachment0 = (sampleCount == 1) ? {
|
||||
view: canvasTexture.createView(),
|
||||
view: viewTexture.createView(),
|
||||
loadOp: "clear",
|
||||
clearValue: clearValue,
|
||||
storeOp: "store",
|
||||
} : {
|
||||
view: multisampleTexture.createView(),
|
||||
resolveTarget: canvasTexture.createView(),
|
||||
resolveTarget: viewTexture.createView(),
|
||||
loadOp: "clear",
|
||||
clearValue: clearValue,
|
||||
storeOp: "store",
|
||||
@ -318,44 +384,40 @@ function render2()
|
||||
|
||||
renderPass.end();
|
||||
|
||||
/*
|
||||
encoder.copyTextureToBuffer({
|
||||
texture: multisampleTexture,
|
||||
}, {
|
||||
buffer: tempBuffer,
|
||||
bytesPerRow: canvasTexture.width * 4 * 4,
|
||||
}, {
|
||||
width: canvasTexture.width,
|
||||
height: canvasTexture.height,
|
||||
depthOrArrayLayers: 1,
|
||||
if (downloadRenderBufferPending) {
|
||||
encoder.copyTextureToBuffer({
|
||||
texture: copyTexture,
|
||||
}, {
|
||||
buffer: tempBuffer,
|
||||
bytesPerRow: canvasTexture.width * 4,
|
||||
}, {
|
||||
width: copyTexture.width,
|
||||
height: copyTexture.height,
|
||||
depthOrArrayLayers: 1,
|
||||
});
|
||||
*/
|
||||
}
|
||||
|
||||
const commandBuffer = encoder.finish();
|
||||
device.queue.submit([commandBuffer]);
|
||||
|
||||
frameNumber = (frameNumber + 1) % 2;
|
||||
|
||||
/*
|
||||
tempBuffer.mapAsync(GPUMapMode.READ).then(() => {
|
||||
const readBuffer = tempBuffer.getMappedRange(); // arraybuffer
|
||||
//console.log(mousePositionClick);
|
||||
const x = mousePositionClick[0];
|
||||
const y = mousePositionClick[1];
|
||||
const f32ReadBuffer = new Float32Array(readBuffer);
|
||||
for (let i = 0; i < 2; i++) {
|
||||
const a = f32ReadBuffer[((y + i) * canvasTexture.width + x) * 4 + 0];
|
||||
const b = f32ReadBuffer[((y + i) * canvasTexture.width + x) * 4 + 1];
|
||||
const c = f32ReadBuffer[((y + i) * canvasTexture.width + x) * 4 + 2];
|
||||
const d = f32ReadBuffer[((y + i) * canvasTexture.width + x) * 4 + 3];
|
||||
//console.log(a, b, c, d);
|
||||
}
|
||||
|
||||
tempBuffer.unmap();
|
||||
requestAnimationFrame(render2);
|
||||
if (downloadRenderBufferPending) {
|
||||
tempBuffer.mapAsync(GPUMapMode.READ).then(() => {
|
||||
const mappedRange = tempBuffer.getMappedRange(); // arraybuffer
|
||||
const blob = tgaBlob(copyTexture, mappedRange);
|
||||
const url = URL.createObjectURL(blob);
|
||||
const a = document.createElement("a");
|
||||
a.setAttribute("href", url);
|
||||
a.setAttribute("download", "framebuffer.tga");
|
||||
a.click();
|
||||
tempBuffer.unmap();
|
||||
requestAnimationFrame(render2);
|
||||
});
|
||||
*/
|
||||
requestAnimationFrame(render2);
|
||||
downloadRenderBufferPending = false;
|
||||
} else {
|
||||
requestAnimationFrame(render2);
|
||||
}
|
||||
} else {
|
||||
requestAnimationFrame(render2);
|
||||
}
|
||||
|
||||
14
select.js
14
select.js
@ -8,34 +8,34 @@ class Select {
|
||||
const labelE = document.createElement("label");
|
||||
labelE.innerHTML = label;
|
||||
|
||||
const selectE = document.createElement("select");
|
||||
selectE.className = "span-2";
|
||||
const inputE = document.createElement("select");
|
||||
inputE.className = "span-2";
|
||||
|
||||
for (let i = 0; i < options.length; i++) {
|
||||
const option = options[i];
|
||||
const optionE = document.createElement("option");
|
||||
optionE.setAttribute("value", i);
|
||||
optionE.innerHTML = option;
|
||||
selectE.appendChild(optionE);
|
||||
inputE.appendChild(optionE);
|
||||
}
|
||||
|
||||
const rowE = document.createElement("row");
|
||||
const sepE = document.createElement("row-separator");
|
||||
|
||||
rowE.appendChild(labelE);
|
||||
rowE.appendChild(selectE);
|
||||
rowE.appendChild(inputE);
|
||||
|
||||
parent.appendChild(rowE);
|
||||
parent.appendChild(sepE);
|
||||
|
||||
this.select = selectE;
|
||||
this.input = inputE;
|
||||
this.options = options;
|
||||
|
||||
this.lastValue = undefined;
|
||||
|
||||
const storageValue = localStorage.getItem(this.label);
|
||||
if (storageValue !== null) {
|
||||
this.select.value = storageValue;
|
||||
this.input.value = storageValue;
|
||||
}
|
||||
|
||||
this.update();
|
||||
@ -43,7 +43,7 @@ class Select {
|
||||
|
||||
update()
|
||||
{
|
||||
const rawValue = parseInt(this.select.value);
|
||||
const rawValue = parseInt(this.input.value);
|
||||
if (rawValue !== this.lastValue) {
|
||||
this.lastValue = rawValue;
|
||||
localStorage.setItem(this.label, rawValue);
|
||||
|
||||
@ -49,8 +49,9 @@ extern "C" {
|
||||
return font_layout->layout_buffer_index;
|
||||
}
|
||||
|
||||
void font_layout__draw(FontLayout * font_layout)
|
||||
void font_layout__draw(FontLayout * font_layout, char const * s)
|
||||
{
|
||||
font_layout->draw_string("hexagon grid");
|
||||
font_layout->layout_buffer_index = 0;
|
||||
font_layout->draw_string(s);
|
||||
}
|
||||
};
|
||||
|
||||
50
text-input.js
Normal file
50
text-input.js
Normal file
@ -0,0 +1,50 @@
|
||||
const controlGrid = document.getElementById("control-parent");
|
||||
|
||||
class TextInput {
|
||||
constructor(label)
|
||||
{
|
||||
const parent = controlGrid;
|
||||
|
||||
const labelE = document.createElement("label");
|
||||
labelE.innerHTML = label;
|
||||
|
||||
const inputE = document.createElement("input");
|
||||
inputE.type = "text";
|
||||
inputE.className = "span-2";
|
||||
|
||||
const rowE = document.createElement("row");
|
||||
const sepE = document.createElement("row-separator");
|
||||
|
||||
rowE.appendChild(labelE);
|
||||
rowE.appendChild(inputE);
|
||||
|
||||
parent.appendChild(rowE);
|
||||
parent.appendChild(sepE);
|
||||
|
||||
this.label = label;
|
||||
this.input = inputE;
|
||||
|
||||
this.lastValue = undefined;
|
||||
|
||||
const storageValue = localStorage.getItem(this.label);
|
||||
if (storageValue !== null) {
|
||||
this.input.value = storageValue;
|
||||
} else {
|
||||
this.input.value = "hexagon grid";
|
||||
}
|
||||
|
||||
this.update();
|
||||
}
|
||||
|
||||
update()
|
||||
{
|
||||
const rawValue = this.input.value;
|
||||
if (rawValue !== this.lastValue) {
|
||||
this.lastValue = rawValue;
|
||||
localStorage.setItem(this.label, rawValue);
|
||||
}
|
||||
return rawValue;
|
||||
}
|
||||
};
|
||||
|
||||
export { TextInput };
|
||||
Loading…
x
Reference in New Issue
Block a user