diff --git a/src/builtins.ts b/src/builtins.ts index 5128d7b..deef558 100644 --- a/src/builtins.ts +++ b/src/builtins.ts @@ -468,7 +468,8 @@ export const builtins: { [name: string]: Value } = { name: 'debug', arity: 2, fn: (label, value) => { - expectString(label, 'debug'); + const str = expectString(label, 'debug'); + console.log(str, value); return value; } } diff --git a/src/main.ts b/src/main.ts index ca9e423..0d2599d 100644 --- a/src/main.ts +++ b/src/main.ts @@ -1,5 +1,4 @@ import type { Env } from './env' -import type { Value } from './types' import { evaluate } from './interpreter' import { tokenize } from './lexer' import { Parser } from './parser' diff --git a/src/os.cg b/src/os.cg index 3c2d8c6..60939b0 100644 --- a/src/os.cg +++ b/src/os.cg @@ -1,7 +1,6 @@ osState = { query = "", - debug = "DEBUG", - selectedPaletteIndex = 0 + focusedPaletteIndex = 0 }; init = { @@ -11,7 +10,23 @@ init = { update = state event \ event | _ \ state; +listRow = child selected \ +Clickable { + event = osState.query := "", + child = Stack { + children = [ + Rect { w = 300, h = 40, strokeWidth = 2, strokeColor = "black", color = "pink" } + ] + } +}; + +# listRow = child selected \ +# pre = (selected | True \ "*" | _ \ ""); +# Text { content = pre & " " & child }; + view = state viewport \ + _ = debug "focusedPalletIndex" osState.focusedPaletteIndex; + Positioned { x = 30, y = 30, @@ -26,13 +41,18 @@ view = state viewport \ h = 40, onChange = text \ osState.query := text, onKeyDown = key \ key - | ArrowUp \ osState.selectedPaletteIndex := max 0 (osState.selectedPaletteIndex + 1) - | ArrowDown \ osState.selectedPaletteIndex := osState.selectedPaletteIndex - 1 + | ArrowUp \ osState.focusedPaletteIndex := max 0 (osState.focusedPaletteIndex - 1) + | ArrowDown \ osState.focusedPaletteIndex := (osState.focusedPaletteIndex + 1) + | _ \ NoOp }, - Text { content = osState.debug, x = 8, y = 16 }, - Column { - gap = 10, - children = map (t \ Text { content = t, x = 8, y = 16 }) (storeSearch osState.query) + Clip { + w = 300, + h = 300, + child = Column { + gap = 10, + # children = mapWithIndex (t i \ Text { content = str i & " " & t }) (storeSearch osState.query) + children = mapWithIndex (t i \ listRow t (osState.focusedPaletteIndex == i)) (storeSearch osState.query) + } } ] } diff --git a/src/parser.ts b/src/parser.ts index caad6d2..f9786a7 100644 --- a/src/parser.ts +++ b/src/parser.ts @@ -168,7 +168,7 @@ export class Parser { if (this.current().kind == 'colon-equals') { const token = this.current(); this.advance(); - const value = this.parseExpression(); + const value = this.parseExpressionNoMatch(); return { kind: 'rebind', target: expr, value, ...this.getPos(token) }; } diff --git a/src/runtime.ts b/src/runtime.ts index 7a9310f..dcc743f 100644 --- a/src/runtime.ts +++ b/src/runtime.ts @@ -154,6 +154,10 @@ export function runApp(app: App, canvas: HTMLCanvasElement, source: string, env: view: ui.view }; componentInstances.set(fullKey, instance); + } else { + // refresh closures, pick up new values + instance.update = ui.update; + instance.view = ui.view; } if (instance.view.kind !== 'closure') diff --git a/src/stdlib.cg b/src/stdlib.cg index e308b9c..b33ce6e 100644 --- a/src/stdlib.cg +++ b/src/stdlib.cg @@ -3,6 +3,12 @@ map = f list \ list | [] \ [] | [x, ...xs] \ [f x, ...map f xs]; +# mapWithIndex : (a \ Number \ b) \ List a \ List b +mapWithIndex = f list \ + init = { i = 0, result = [] }; + f2 = acc x \ { i = acc.i + 1, result = [...acc.result, f x acc.i] }; + (fold f2 init list).result; + # filter : (a \ Bool) \ List a \ List a filter = f list \ list | [] \ [] diff --git a/src/types.ts b/src/types.ts index fca7310..68c4399 100644 --- a/src/types.ts +++ b/src/types.ts @@ -53,8 +53,8 @@ export type NativeFunction = { } export type UIValue = - | { kind: 'rect', w: number, h: number, color: string, radius?: number } - | { kind: 'text', content: string, x: number, y: number } + | { kind: 'rect', w: number, h: number, color?: string, strokeColor?: string, strokeWidth?: number, radius?: number } + | { kind: 'text', content: string } | { kind: 'row', children: UIValue[], gap: number } | { kind: 'column', children: UIValue[], gap: number } | { kind: 'clickable', child: UIValue, event: Value } diff --git a/src/ui-components.cg b/src/ui-components.cg index a088ff9..106835a 100644 --- a/src/ui-components.cg +++ b/src/ui-components.cg @@ -115,7 +115,7 @@ textInput = config \ Stateful { Positioned { x = 8 - state.scrollOffset, y = 8, - child = Text { content = state.text, x = 0, y = 17 } + child = Positioned { x = 0, y = 17, child = Text { content = state.text } } }, (state.focused diff --git a/src/ui.ts b/src/ui.ts index 4b50e15..c853246 100644 --- a/src/ui.ts +++ b/src/ui.ts @@ -27,7 +27,7 @@ export function render(ui: UIValue, canvas: HTMLCanvasElement) { function renderUI(ui: UIValue, ctx: CanvasRenderingContext2D, x: number, y: number) { switch (ui.kind) { case 'rect': { - ctx.fillStyle = ui.color; + ctx.fillStyle = ui.color || 'transparent'; if (ui.radius && ui.radius > 0) { const r = Math.min(ui.radius, ui.w / 2, ui.h / 2); @@ -43,8 +43,21 @@ function renderUI(ui: UIValue, ctx: CanvasRenderingContext2D, x: number, y: numb ctx.arcTo(x, y, x + r, y, r); ctx.closePath(); ctx.fill(); + + if (ui.strokeColor && ui.strokeWidth) { + ctx.strokeStyle = ui.strokeColor; + ctx.lineWidth = ui.strokeWidth; + ctx.stroke(); + } + } else { ctx.fillRect(x, y, ui.w, ui.h); + + if (ui.strokeColor && ui.strokeWidth) { + ctx.strokeStyle = ui.strokeColor; + ctx.lineWidth = ui.strokeWidth; + ctx.strokeRect(x, y, ui.w, ui.h);; + } } break; @@ -53,7 +66,7 @@ function renderUI(ui: UIValue, ctx: CanvasRenderingContext2D, x: number, y: numb case 'text': ctx.fillStyle = 'black'; ctx.font = '16px "SF Mono", "Monaco", "Menlo", "Consolas", "Courier New", monospace'; - ctx.fillText(ui.content, x + ui.x, y + ui.y); + ctx.fillText(ui.content, x, y); break; case 'row': { diff --git a/src/valueToUI.ts b/src/valueToUI.ts index ab5951a..3148146 100644 --- a/src/valueToUI.ts +++ b/src/valueToUI.ts @@ -12,29 +12,29 @@ export function valueToUI(value: Value): UIValue { switch (value.name) { case 'Rect': { - const { w, h, color, radius } = fields; + const { w, h, color, radius, strokeColor, strokeWidth } = fields; - if (w.kind !== 'int' || h.kind !== 'int' || color.kind !== 'string') + if (w.kind !== 'int' || h.kind !== 'int') throw new Error('Invalid Rect fields'); return { kind: 'rect', w: w.value, h: h.value, - color: color.value, + color: color && color.kind === 'string' ? color.value : undefined, + strokeColor: strokeColor && strokeColor.kind === 'string' ? strokeColor.value : undefined, + strokeWidth: strokeWidth && strokeWidth.kind === 'int' ? strokeWidth.value : undefined, radius: radius && radius.kind === 'int' ? radius.value : 0 }; } case 'Text': { - const x = fields.x; - const y = fields.y; const content = fields.content; - if (content.kind !== 'string' || x.kind !== 'int' || y.kind !== 'int') + if (content.kind !== 'string') throw new Error('Invalid Text fields'); - return { kind: 'text', x: x.value, y: y.value, content: content.value }; + return { kind: 'text', content: content.value }; } case 'Column': {