From f426573f96262660f040ecb9abdd1cf3d0f366c0 Mon Sep 17 00:00:00 2001 From: Dustin Swan Date: Sun, 22 Feb 2026 13:49:26 -0700 Subject: [PATCH] Saving glyphs --- src/cg/06-pixelEditor.cg | 62 +++++++++++++++++++++++++++++++++++++--- src/parser.ts | 17 ++++++++--- src/runtime-js.ts | 8 +++++- 3 files changed, 78 insertions(+), 9 deletions(-) diff --git a/src/cg/06-pixelEditor.cg b/src/cg/06-pixelEditor.cg index 220a433..1e7ed9e 100644 --- a/src/cg/06-pixelEditor.cg +++ b/src/cg/06-pixelEditor.cg @@ -1,5 +1,9 @@ pixelEditor = config \ - c = config; + defaults = { + path = "newGlyph" + }; + + c = { ...defaults, ...config }; upArrow = state \ ( newRow = max 0 (state.selectedRow - 1); @@ -17,13 +21,19 @@ pixelEditor = config \ newCol = min (state.pixelWidth - 1) (state.selectedCol + 1); { state = state.{ selectedCol = newCol }, emit = [] }); + saveGlyph = state \ + glyph = { w = state.pixelWidth, h = state.pixelHeight, map = state.map }; + [rebindAt c.path glyph]; + toggleFocused = state \ ( row = state.selectedRow; col = state.selectedCol; newMap = contains { x = col, y = row } state.map | True \ filter (e \ e != { x = col, y = row }) state.map | False \ [...state.map, { x = col, y = row }]; - { state = state.{ map = newMap }, emit = [] }); + + newState = state.{ map = newMap }; + { state = newState, emit = saveGlyph newState }); size \ ui.stateful { focusable = True, @@ -54,10 +64,17 @@ pixelEditor = config \ | Key { key = "ArrowRight" } \ rightArrow state | Key { key = "l" } \ rightArrow state + | UpdateWidth w \ ( + newState = state.{ pixelWidth = (int w) }; + { state = newState, emit = saveGlyph newState }) + + | UpdateHeight h \ ( + newState = state.{ pixelHeight = (int h) }; + { state = newState, emit = saveGlyph newState }) + | _ \ { state = state, emit = [] }, view = state emit \ - # onToggle = path \ emit (Toggle path); grid = ui.column { children = map (rIdx \ @@ -78,5 +95,42 @@ pixelEditor = config \ ) (range 0 state.pixelHeight) }; - center size.w size.h grid + headerHeight = 30; + + header = ui.row { + gap = 10, + + children = [ + ui.positioned { x = 0, y = 8, child = ui.text { content = c.path, color = "#fff" } }, + + textInput { + key = "width-input", + w = 40, + h = headerHeight, + color = "#fff", + backgroundColor = "rgba(0,0,0,0.2)", + onSubmit = v \ emit (UpdateWidth v), + initialValue = (show state.pixelWidth) + }, + + ui.positioned { x = 0, y = 8, child = ui.text { content = "x", color = "#aaa" } }, + + textInput { + key = "height-input", + w = 40, + h = headerHeight, + color = "#fff", + backgroundColor = "rgba(0,0,0,0.2)", + onSubmit = v \ emit (UpdateHeight v), + initialValue = (show state.pixelHeight) + } + ] + }; + + ui.column { + children = [ + header, + center size.w size.h grid + ] + } }; diff --git a/src/parser.ts b/src/parser.ts index 4ba3717..aa46414 100644 --- a/src/parser.ts +++ b/src/parser.ts @@ -42,6 +42,15 @@ export class Parser { return this.advance(); } + private expectIdent(): Token { + const token = this.current(); + if (token.kind === 'ident' || token.kind === 'type-ident') { + this.advance(); + return token; + } + throw this.error(`Expected identifier, got ${token.kind}`); + } + private getPos(token: Token) { return { line: token.line, @@ -284,7 +293,7 @@ export class Parser { // Spread if (this.current().kind === 'dot-dot-dot') { this.advance(); - const nameToken = this.expect('ident'); + const nameToken = this.expectIdent(); spreadName = (nameToken as { value: string }).value; break; } @@ -440,7 +449,7 @@ export class Parser { this.advance(); const items = this.parseCommaSeparated('close-brace', () => { - const keyToken = this.expect('ident'); + const keyToken = this.expectIdent(); const key = (keyToken as { value: string }).value; this.expect('equals'); return { key, value: this.parseExpression() }; @@ -453,7 +462,7 @@ export class Parser { } else { // Record access - const fieldToken = this.expect('ident'); + const fieldToken = this.expectIdent(); const field = (fieldToken as { value: string }).value; expr = { kind: 'record-access', record: expr, field, ...this.getPos(token) }; } @@ -499,7 +508,7 @@ export class Parser { this.advance(); return { kind: 'spread' as const, expr: this.parseExpression() }; } - const keyToken = this.expect('ident'); + const keyToken = this.expectIdent(); const key = (keyToken as { value: string }).value; this.expect('equals'); return { kind: 'field' as const, key, value: this.parseExpression() }; diff --git a/src/runtime-js.ts b/src/runtime-js.ts index 6f8ef3b..3b25315 100644 --- a/src/runtime-js.ts +++ b/src/runtime-js.ts @@ -62,6 +62,7 @@ export const _rt = { : { _tag: 'None' }, len: (xs: any[] | string) => xs.length, // str: (x: any) => String(x), + int: (x: any) => typeof x === 'number' ? Math.floor(x) : parseInt(x, 10) || 0, show: (value: any): string => { if (value === null || value === undefined) return "None"; if (typeof value === 'string') return value; @@ -126,8 +127,12 @@ export const _rt = { store[name] = pathOrValue; } else { const path = pathOrValue as string[]; + if (store[name] === undefined) store[name] = {}; let obj = store[name]; for (let i = 0; i < path.length - 1; i++) { + if (obj[path[i]] === undefined || obj[path[i]] === null) { + obj[path[i]] = {}; + } obj = obj[path[i]]; } obj[path[path.length - 1]] = maybeValue; @@ -327,7 +332,8 @@ function valueToAst(value: any): AST { } export function syncToAst(name: string) { - if (definitions.has(name)) { + // if (definitions.has(name)) { + if (name in store) { definitions.set(name, valueToAst(store[name])); const source = prettyPrint({ kind: 'definition', name, body: definitions.get(name)! }); appendChangeLog(name, source);