Skip to content

Commit

Permalink
[wip] yea na
Browse files Browse the repository at this point in the history
  • Loading branch information
braebo committed Feb 5, 2024
1 parent a35f89a commit 9e1e437
Show file tree
Hide file tree
Showing 2 changed files with 181 additions and 50 deletions.
10 changes: 4 additions & 6 deletions src/lib/gui/Folder.ts
Original file line number Diff line number Diff line change
@@ -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'
Expand Down Expand Up @@ -294,11 +295,8 @@ export class Folder {
return folder
}

addInput(options: Omit<InputOptions, 'folder'> & { folder?: Folder }) {
const input = new Input({
folder: this,
...options,
})
add<VT = any, IT extends InputView = InferInputType<VT>>(options: InputOptions<VT, IT>) {
const input = new Input<VT, IT>(options, this)
this.controls.set(input.title, input)
this.elements.content.appendChild(input.element)
}
Expand Down
221 changes: 177 additions & 44 deletions src/lib/gui/Input.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 = InputType> = 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<T = InputType, V = InputValue<T>> {
value: V
title: string
type?: string
folder: Folder

// prettier-ignore
export type InferInputOptions<IT extends InputView, VT> =
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<string> : '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, U = unknown> =
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 InputView> =
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<T> {
options: T[]
}

export interface ButtonInputOptions {
onClick: (value: boolean) => void
}

export class Input<T = InputType, V = InputValue<T>> {
state: State<V>
export interface FolderInputOptions {
children: Folder[]
}

export type InputOptions<VT, IT extends InputView = InferInputType<VT>> = {
title: string
type: string
folder: Folder
element: HTMLElement
value: VT
type?: IT
} & InferInputOptions<IT, VT>

export class Input<
VT = any,
IT extends InputView = InferInputType<VT>,
ET extends Element = InferElementType<IT>,
> {
state: State<VT>
title: string
type: IT

container: HTMLElement
element!: ET

#log = new Logger('Input', { fg: 'cyan' })

constructor(
options: InputOptions<IT>,
public folder: Folder,
) {
this.state = state(options.value) as unknown as State<VT>

constructor(options: InputOptions<T, V>) {
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<HTMLInputElement>('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,
})

Expand Down

0 comments on commit 9e1e437

Please sign in to comment.