diff --git a/src/builtins.ts b/src/builtins.ts index 2d73896..ed88f64 100644 --- a/src/builtins.ts +++ b/src/builtins.ts @@ -472,5 +472,39 @@ export const builtins: { [name: string]: NativeFunction } = { console.log(`[DEBUG] ${l}: `, value); return value; } - } + }, + + 'store': { + kind: 'native', + name: 'store', + arity: 1, + fn: (initialValue) => { + return { kind: 'ref', value: initialValue }; + } + }, + + 'get': { + kind: 'native', + name: 'get', + arity: 1, + fn: (ref) => { + if (ref.kind !== 'ref') + throw new Error('get expects a Ref'); + + return ref.value; + } + }, + + 'update': { + kind: 'native', + name: 'update', + arity: 2, + fn: (ref, transformFn) => { + return { + kind: 'constructor', + name: 'Update', + args: [ref, transformFn] + }; + } + }, } diff --git a/src/main.ts b/src/main.ts index f622c14..a098919 100644 --- a/src/main.ts +++ b/src/main.ts @@ -11,13 +11,18 @@ import designTokensCode from './design-tokens.cg?raw'; import uiComponentsCode from './ui-components.cg?raw'; import textInputCode from './textinput-test.cg?raw'; +import refTest from './test-ref.cg?raw'; // import testCode from './test.cg?raw'; // import counterApp from './counter.cg?raw'; const canvas = document.createElement('canvas') as HTMLCanvasElement; document.body.appendChild(canvas); -const cgCode = stdlibCode + '\n' + designTokensCode + '\n' + uiComponentsCode + '\n' + textInputCode; +const cgCode = stdlibCode + '\n' + + designTokensCode + '\n' + + uiComponentsCode + '\n' + + textInputCode + '\n' + // refTest + '\n'; try { const tokens = tokenize(cgCode); diff --git a/src/runtime.ts b/src/runtime.ts index 5bcd47c..fa14471 100644 --- a/src/runtime.ts +++ b/src/runtime.ts @@ -77,6 +77,9 @@ export function runApp(app: App, canvas: HTMLCanvasElement, source: string) { handleEvent(event); } } + + rerender(); + } catch(error) { if (error instanceof CGError) { console.error(error.format()); @@ -120,7 +123,7 @@ export function runApp(app: App, canvas: HTMLCanvasElement, source: string) { child: viewUI, event: { kind: 'constructor', - name: 'Focus', + name: 'FocusAndClick', args: [{ kind: 'string', value: fullKey }] } }; @@ -201,9 +204,41 @@ export function runApp(app: App, canvas: HTMLCanvasElement, source: string) { } function handleEvent(event: Value) { - if (event.kind === 'constructor' && event.name === 'Focus') { - if (event.args.length > 0 && event.args[0].kind === 'string') { - setFocus(event.args[0].value); + if (event.kind === 'constructor' && event.name === 'FocusAndClick') { + if (event.args.length === 2 && event.args[0].kind === 'string') { + const componentKey = event.args[0].value; + const coords = event.args[1]; + + setFocus(componentKey); + + handleComponentEvent(componentKey, { + kind: 'constructor', + name: 'Clicked', + args: [coords] + }); + + return; + } + } + + if (event.kind === 'constructor' && event.name === 'Update') { + if (event.args.length === 2) { + const ref = event.args[0]; + const transformFn = event.args[1]; + + if (ref.kind !== 'ref') + throw new Error('Update event expects a Ref') + + if (transformFn.kind !== 'closure') + throw new Error('Update event expects a Ref') + + const callEnv = new Map(transformFn.env); + callEnv.set(transformFn.params[0], ref.value); + const newValue = evaluate(transformFn.body, callEnv, source); + + ref.value = newValue; + + rerender(); return; } } @@ -240,19 +275,22 @@ export function runApp(app: App, canvas: HTMLCanvasElement, source: string) { if (hitResult) { const { event, relativeX, relativeY } = hitResult; - if (event.kind === 'constructor' && event.name === 'Focus') { + if (event.kind === 'constructor' && (event.name === 'Focus' || event.name === 'Update')) { handleEvent(event); - } else if (event.kind === 'constructor') { + } else if (event.kind === 'constructor' && event.name === 'FocusAndClick') { const eventWithCoords: Value = { kind: 'constructor', name: event.name, - args: [{ - kind: 'record', - fields: { - x: { kind: 'int', value: Math.floor(relativeX) }, - y: { kind: 'int', value: Math.floor(relativeY) }, + args: [ + event.args[0], + { + kind: 'record', + fields: { + x: { kind: 'int', value: Math.floor(relativeX) }, + y: { kind: 'int', value: Math.floor(relativeY) }, + } } - }] + ] }; handleEvent(eventWithCoords); } else { diff --git a/src/test-ref.cg b/src/test-ref.cg new file mode 100644 index 0000000..34bb75c --- /dev/null +++ b/src/test-ref.cg @@ -0,0 +1,17 @@ + counter = store 0; + + view = state viewport \ + currentCount = get counter; + + Column { + gap = 10, + children = [ + Text { content = str currentCount, x = 30, y = 30 }, + Clickable { + event = update counter (n \ n + 1), + child = Rect { w = 100, h = 40, color = "blue" } + } + ] + }; + + { init = {}, update = state event \ state, view = view } diff --git a/src/types.ts b/src/types.ts index 6229020..b86b7ed 100644 --- a/src/types.ts +++ b/src/types.ts @@ -46,6 +46,11 @@ export type NativeFunction = { fn: (...args: Value[]) => Value } +export type RefValue = { + kind: 'ref' + value: Value +} + export type UIValue = | { kind: 'rect', w: number, h: number, color: string, radius?: number } | { kind: 'text', content: string, x: number, y: number } @@ -59,4 +64,4 @@ export type UIValue = | { kind: 'stack', children: UIValue[] } | { kind: 'stateful', key: string, focusable: boolean, init: Value, update: Value, view: Value } -export type Value = IntValue | FloatValue | StringValue | Closure | ListValue | RecordValue | ConstructorValue | NativeFunction; +export type Value = IntValue | FloatValue | StringValue | Closure | ListValue | RecordValue | ConstructorValue | NativeFunction | RefValue; diff --git a/src/ui-components.cg b/src/ui-components.cg index 6caba9c..b6f3566 100644 --- a/src/ui-components.cg +++ b/src/ui-components.cg @@ -68,14 +68,15 @@ textInput = config \ Stateful { newCursorPos = max 0 (state.cursorPos - 1); newScroll = calcScrollOffset state.text newCursorPos state.scrollOffset 284; newState = state.{ text = state.text, cursorPos = newCursorPos, scrollOffset = newScroll }; - return { state = newState, emit = [] } + _ = debug "ArrowLeft" []; + { state = newState, emit = [] } ) | ArrowRight \ ( newCursorPos = min (len state.text) (state.cursorPos + 1); newScroll = calcScrollOffset state.text newCursorPos state.scrollOffset 284; newState = state.{ text = state.text, cursorPos = newCursorPos, scrollOffset = newScroll }; - return { state = newState, emit = [] } + { state = newState, emit = [] } ) | Backspace \ ( @@ -86,19 +87,18 @@ textInput = config \ Stateful { { state = newState, emit = [config.onChange newText] } ) - | Char c \ ( + | Char c \ _ = debug c; newText = insertChar state.text state.cursorPos c; newCursorPos = state.cursorPos + 1; newScroll = calcScrollOffset newText newCursorPos state.scrollOffset 284; newState = state.{ text = newText, cursorPos = newCursorPos, scrollOffset = newScroll }; { state = newState, emit = [config.onChange newText] } - ) | Clicked coords \ ( newCursorPos = findCursorPos state.text coords.x state.scrollOffset 8; newScroll = calcScrollOffset state.text newCursorPos state.scrollOffset 284; - newSatte = state.{ text = state.text, cursorPos = newCursorPos, scrollOffset = newScroll }; + newState = state.{ text = state.text, cursorPos = newCursorPos, scrollOffset = newScroll }; { state = newState, emit = [] } )