From 9e1e4370327cf680d6856acf61b122793d5a62bb Mon Sep 17 00:00:00 2001 From: Braden Wiggins Date: Mon, 5 Feb 2024 02:08:42 -0700 Subject: [PATCH] [wip] yea na --- src/lib/gui/Folder.ts | 10 +- src/lib/gui/Input.ts | 221 +++++++++++++++++++++++++++++++++--------- 2 files changed, 181 insertions(+), 50 deletions(-) diff --git a/src/lib/gui/Folder.ts b/src/lib/gui/Folder.ts index 7579e06e..b42cc784 100644 --- a/src/lib/gui/Folder.ts +++ b/src/lib/gui/Folder.ts @@ -1,6 +1,7 @@ +import type { InferInputType, InputOptions, InputView, InferElementType } from './Input' import type { Gui } from './Gui' -import { Input, type InputOptions } from './Input' +import { Input } from './Input' import { create } from '../utils/create' import { nanoid } from '../utils/nanoid' import { Logger } from '../utils/logger' @@ -294,11 +295,8 @@ export class Folder { return folder } - addInput(options: Omit & { folder?: Folder }) { - const input = new Input({ - folder: this, - ...options, - }) + add>(options: InputOptions) { + const input = new Input(options, this) this.controls.set(input.title, input) this.elements.content.appendChild(input.element) } diff --git a/src/lib/gui/Input.ts b/src/lib/gui/Input.ts index c0a9d119..ae0a223d 100644 --- a/src/lib/gui/Input.ts +++ b/src/lib/gui/Input.ts @@ -2,64 +2,197 @@ import type { State } from '../utils/state' import type { Folder } from './Folder' import { create } from '../utils/create' +import { Logger } from '../utils/logger' import { state } from '../utils/state' -export type InputType = - | 'Text' - | 'Number' - | 'Boolean' +// prettier-ignore +export type InputValueType = + | 'number' + | 'boolean' + | 'color' + | 'string' + | 'object' + | 'array' + | 'function' + +// prettier-ignore +export type InputView = + | 'Slider' + | 'Checkbox' | 'Color' + | 'Text' + | 'TextArea' | 'Range' | 'Select' | 'Button' - | 'Folder' - | 'Textarea' - -export type InputValue = T extends 'Text' - ? string - : T extends 'Number' - ? number - : T extends 'Boolean' - ? boolean - : T extends 'Color' - ? string - : T extends 'Range' - ? number - : T extends 'Select' - ? string - : T extends 'Button' - ? void - : T extends 'Folder' - ? Folder - : T extends 'Textarea' - ? string - : never - -export interface InputOptions> { - value: V - title: string - type?: string - folder: Folder + +// prettier-ignore +export type InferInputOptions = + IT extends 'Slider' ? VT extends number ? NumberInputOptions : 'Value must be a number for Number inputs.' : + IT extends 'Checkbox' ? VT extends boolean ? BooleanInputOptions : 'Value must be a boolean for Boolean inputs.' : + IT extends 'Text' ? VT extends string ? StringInputOptions : 'Value must be a string for String inputs.' : + IT extends 'TextArea' ? VT extends string ? StringInputOptions : 'Value must be a string for String inputs.' : + IT extends 'Color' ? VT extends string ? ColorInputOptions : 'Value must be a string for Color inputs.' : + IT extends 'Range' ? VT extends number ? RangeInputOptions : 'Value must be a number for Range inputs.' : + IT extends 'Select' ? VT extends string ? SelectInputOptions : 'Value must be a string for Select inputs.' : + IT extends 'Button' ? VT extends Function ? ButtonInputOptions : 'Value must be a function for Button inputs.' : + never; + +// prettier-ignore +export type InferInputType = + VT extends number ? 'Slider' : + VT extends boolean ? 'Checkbox' : + VT extends HexColor ? 'Color' : + VT extends string ? 'Text' : + VT extends string ? 'TextArea' : + VT extends {min: number, max: number} ? 'Range' : + VT extends U[] ? 'Select' : + VT extends Function ? 'Button' : + never; + +// prettier-ignore +export type InferElementType = + IT extends 'Slider' ? HTMLInputElement : + IT extends 'Checkbox' ? HTMLInputElement : + IT extends 'Text' ? HTMLInputElement : + IT extends 'TextArea' ? HTMLInputElement : + IT extends 'Color' ? HTMLInputElement : + IT extends 'Range' ? HTMLInputElement : + IT extends 'Select' ? HTMLSelectElement : + IT extends 'Button' ? HTMLButtonElement : + never + +export interface NumberInputOptions { + min?: number + max?: number + step?: number +} + +const NUMBER_INPUT_DEFAULTS: NumberInputOptions = { + min: 0, + max: 1, + step: 0.01, +} as const + +export interface BooleanInputOptions {} + +export interface StringInputOptions { + maxLength?: number + view?: 'text' | 'textarea' +} + +// todo ({ r: number, g: number, b: number }) / three.js color / etc +export type HexColor = `#${string}` + +export interface ColorInputOptions {} + +export interface RangeInputOptions { + min?: number + max?: number + step?: number +} + +export interface SelectInputOptions { + options: T[] +} + +export interface ButtonInputOptions { + onClick: (value: boolean) => void } -export class Input> { - state: State +export interface FolderInputOptions { + children: Folder[] +} + +export type InputOptions> = { title: string - type: string - folder: Folder - element: HTMLElement + value: VT + type?: IT +} & InferInputOptions + +export class Input< + VT = any, + IT extends InputView = InferInputType, + ET extends Element = InferElementType, +> { + state: State + title: string + type: IT + + container: HTMLElement + element!: ET + + #log = new Logger('Input', { fg: 'cyan' }) + + constructor( + options: InputOptions, + public folder: Folder, + ) { + this.state = state(options.value) as unknown as State - constructor(options: InputOptions) { - this.state = state(options.value) this.title = options.title - this.type = options.type ?? typeof options.value - this.folder = options.folder - this.element = this.#createElement() + + this.type = (options.type ?? typeof options.value) as unknown as IT + this.type.charAt(0).toUpperCase() + this.type.slice(1) // capitalize + + this.container = this.#createContainer() + + this.#log.fn('constructor').info(this) + + this.#createInput() + } + + #createInput() { + switch (this.type) { + case 'Slider': + this.number() + break + // case 'Checkbox': + // this.boolean() + // break + // case 'Text': + // case 'TextArea': + // this.string() + // break + // case 'Color': + // this.color() + // break + // case 'Range': + // this.range() + // break + // case 'Select': + // this.select() + // break + // case 'Button': + // this.button() + // break + } + } + + number(options?: NumberInputOptions) { + const { min, max, step } = { ...NUMBER_INPUT_DEFAULTS, ...options } + + // const input = document.createElement('input') + // input.classList.add('gui-number-input') + const input = create('input', { + classes: ['gui-number-input'], + parent: this.container, + type: 'number', + min: String(min), + max: String(max), + step: String(step), + value: String(this.state.value), + }) + + this.element = input as unknown as ET + this.#log.fn('number').info(this.element) + + return this } - #createElement() { + #createContainer() { const element = create('div', { - classes: ['gui-control'], + classes: ['gui-input'], parent: this.folder.elements.content, })