Adding Stack and TextInput to the UI
This commit is contained in:
parent
50bb15e974
commit
223eea72e3
6 changed files with 226 additions and 11 deletions
125
src/ui.ts
125
src/ui.ts
|
|
@ -8,13 +8,26 @@ type ClickRegion = {
|
|||
event: string;
|
||||
};
|
||||
|
||||
type TextInputRegion = {
|
||||
x: number;
|
||||
y: number;
|
||||
width: number;
|
||||
height: number;
|
||||
inputId: string;
|
||||
submitId: string;
|
||||
}
|
||||
|
||||
let clickRegions: ClickRegion[] = [];
|
||||
let textInputRegions: TextInputRegion[] = [];
|
||||
let focusedInput: string | null = null;
|
||||
let focusedInputSubmit: string | null = null;
|
||||
|
||||
export function render(ui: UIValue, canvas: HTMLCanvasElement) {
|
||||
const ctx = canvas.getContext('2d');
|
||||
if (ctx) {
|
||||
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
||||
clickRegions = [];
|
||||
textInputRegions = [];
|
||||
renderUI(ui, ctx, 0, 0);
|
||||
}
|
||||
}
|
||||
|
|
@ -56,16 +69,58 @@ function renderUI(ui: UIValue, ctx: CanvasRenderingContext2D, x: number, y: numb
|
|||
renderUI(ui.child, ctx, x, y);
|
||||
break;
|
||||
}
|
||||
|
||||
case 'padding':
|
||||
renderUI(ui.child, ctx, x + ui.amount, y + ui.amount);
|
||||
break;
|
||||
|
||||
case 'stack': {
|
||||
for (const child of ui.children) {
|
||||
renderUI(child, ctx, x, y);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case 'text-input': {
|
||||
ctx.fillStyle = ui.value ? '#000000' : '#999999';
|
||||
ctx.font = '16px monospace';
|
||||
ctx.fillText(
|
||||
ui.value || ui.placeholder,
|
||||
x + ui.x + 8,
|
||||
y + ui.y + ui.h / 2 + 6
|
||||
);
|
||||
|
||||
// Draw cursor
|
||||
if (ui.focused) {
|
||||
const textWidth = ctx.measureText(ui.value).width;
|
||||
ctx.fillStyle = '#000000';
|
||||
ctx.fillRect(x + ui.x + 8 + textWidth, y + ui.y + 8, 2, ui.h - 16);
|
||||
}
|
||||
|
||||
textInputRegions.push({
|
||||
x: x + ui.x,
|
||||
y: y + ui.y,
|
||||
width: ui.w,
|
||||
height: ui.h,
|
||||
inputId: ui.onInput,
|
||||
submitId: ui.onSubmit
|
||||
});
|
||||
|
||||
if (ui.focused && ui.onInput === focusedInput) {
|
||||
currentInputValue = ui.value;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function measure(ui: UIValue): { width: number, height: number } {
|
||||
switch (ui.kind) {
|
||||
case 'rect': return { width: ui.w, height: ui.h };
|
||||
|
||||
case 'text': return { width: ui.content.length * 10, height: 20 }; // TODO
|
||||
|
||||
case 'row': {
|
||||
let totalWidth = 0;
|
||||
let maxHeight = 0;
|
||||
|
|
@ -77,6 +132,7 @@ function measure(ui: UIValue): { width: number, height: number } {
|
|||
totalWidth += ui.gap * (ui.children.length - 1);
|
||||
return { width: totalWidth, height: maxHeight };
|
||||
}
|
||||
|
||||
case 'column': {
|
||||
let totalHeight = 0;
|
||||
let maxWidth = 0;
|
||||
|
|
@ -88,8 +144,10 @@ function measure(ui: UIValue): { width: number, height: number } {
|
|||
totalHeight += ui.gap * (ui.children.length - 1);
|
||||
return { width: maxWidth, height: totalHeight };
|
||||
}
|
||||
|
||||
case 'clickable':
|
||||
return measure(ui.child);
|
||||
|
||||
case 'padding': {
|
||||
const childSize = measure(ui.child);
|
||||
return {
|
||||
|
|
@ -97,6 +155,21 @@ function measure(ui: UIValue): { width: number, height: number } {
|
|||
height: childSize.height + ui.amount * 2,
|
||||
}
|
||||
}
|
||||
|
||||
case 'stack': {
|
||||
let maxWidth = 0;
|
||||
let maxHeight = 0;
|
||||
for (const child of ui.children) {
|
||||
const size = measure(child);
|
||||
maxWidth = Math.max(maxWidth, size.width);
|
||||
maxHeight = Math.max(maxHeight, size.height);
|
||||
}
|
||||
|
||||
return { width: maxWidth, height: maxHeight };
|
||||
}
|
||||
|
||||
case 'text-input':
|
||||
return { width: ui.w, height: ui.h };
|
||||
}
|
||||
|
||||
// return { width: 0, height: 0 };
|
||||
|
|
@ -111,3 +184,55 @@ export function hitTest(x: number, y: number): string | null {
|
|||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
let currentInputValue: string = '';
|
||||
|
||||
export function hitTestTextInput(x: number, y: number): boolean {
|
||||
for (const region of textInputRegions) {
|
||||
if (x >= region.x && x < region.x + region.width &&
|
||||
y >= region.y && y < region.y + region.height) {
|
||||
focusedInput = region.inputId;
|
||||
focusedInputSubmit = region.submitId;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
focusedInput = null;
|
||||
focusedInputSubmit = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
export function getFocusedInput(): string | null {
|
||||
return focusedInput;
|
||||
}
|
||||
|
||||
export function storeInputValue(inputId: string, value: string) {
|
||||
if (inputId === focusedInput) {
|
||||
currentInputValue = value;
|
||||
}
|
||||
}
|
||||
|
||||
export function handleKeyboard(key: string): { event: string, value: string } | null {
|
||||
if (!focusedInput) return null;
|
||||
|
||||
if (key === 'Enter') {
|
||||
return focusedInputSubmit
|
||||
? { event: focusedInputSubmit, value: currentInputValue }
|
||||
: null;
|
||||
}
|
||||
|
||||
if (key === 'Backspace') {
|
||||
const newValue = currentInputValue.slice(0, -1);
|
||||
currentInputValue = newValue;
|
||||
return { event: focusedInput, value: newValue };
|
||||
}
|
||||
|
||||
// Character
|
||||
if (key.length === 1) {
|
||||
const newValue = currentInputValue + key;
|
||||
currentInputValue = newValue;
|
||||
return { event: focusedInput, value: newValue };
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue