diff --git a/src/cg/01-stdlib.cg b/src/cg/01-stdlib.cg index 9ba23ce..6a12a52 100644 --- a/src/cg/01-stdlib.cg +++ b/src/cg/01-stdlib.cg @@ -16,6 +16,15 @@ mapWithIndex = f list \ f2 = acc x \ { i = acc.i + 1, result = [...acc.result, f x acc.i] }; (fold f2 init list).result; +# index : a \ List a \ Maybe Int +index = x xs \ + go = i rest \ rest + | [] \ None + | [y, ...ys] \ (x == y + | True \ Some i + | False \ go (i + 1) ys); + go 0 xs; + # filter : (a \ Bool) \ List a \ List a filter = f list \ list | [] \ [] diff --git a/src/cg/10-os.cg b/src/cg/10-os.cg index 5d5b33a..606859e 100644 --- a/src/cg/10-os.cg +++ b/src/cg/10-os.cg @@ -18,13 +18,25 @@ openWindow = title content \ osState.palette.visible := False ]; -closeFocused = _ \ - i = osState.wm.focusedIndex; +closeWindowByIndex = i \ + focused = osState.wm.focusedIndex; + newFocused = (i < focused + | True \ focused - 1 + | False \ (i == focused + | True \ max 0 (focused - 1) + | False \ focused)); + batch [ osState.windows := (take i osState.windows) & (drop (i + 1) osState.windows), - osState.wm.focusedIndex := max 0 (osState.wm.focusedIndex - 1) + osState.wm.focusedIndex := min newFocused (len osState.windows - 2) ]; +closeFocusedWindow = _ \ closeWindowByIndex osState.wm.focusedIndex; + +closeWindowById = id \ + i = index id (map (w \ w.id) osState.windows); + closeWindowByIndex (unwrapOr 0 i); + onSelect = input \ result = eval input; result @@ -33,7 +45,6 @@ onSelect = input \ | Err msg \ openWindow "Error" (ui.text { content = msg, color = "red" }); renderWindow = window isActive \ - _ = debug "renderWindow" window.title; titleBarHeight = 30; ui.stack { children = [ @@ -45,8 +56,29 @@ renderWindow = window isActive \ x = 0, y = 0, child = ui.stack { children = [ + # background ui.rect { w = window.width, h = titleBarHeight, color = (isActive | True \ "#a15f80" | False \ "#0f3348") }, - ui.positioned { x = 8, y = 8, child = ui.text { content = window.title, color = "white" } }, + ui.row { + children = [ + # close button + ui.clickable { + event = closeWindowById window.id, + child = ui.stack { + children = [ + # button background + ui.rect { w = titleBarHeight, h = titleBarHeight, color = "rgba(255,255,255,0.2)" }, + # button text + ui.positioned { + x = 9, y = 7, child = ui.text { content = "x" } + } + ] + } + }, + + # title + ui.positioned { x = 8, y = 8, child = ui.text { content = window.title, color = "white" } }, + ] + } ] } }, @@ -63,12 +95,23 @@ renderWindow = window isActive \ ] }; +windowComponent = config \ ui.stateful { + key = "window-" & (show config.index), + focusable = True, + init = {}, + update = state event \ event + | Focused \ { state = state, emit = [osState.wm.focusedIndex := config.index] } + | ChildFocused \ { state = state, emit = [osState.wm.focusedIndex := config.index] } + | _ \ { state = state, emit = [] }, + view = state \ renderWindow config.window config.isActive +}; + renderWindows = _ \ windows = osState.windows; focused = osState.wm.focusedIndex; ui.row { gap = 1, - children = mapWithIndex (w i \ renderWindow w (i == focused)) windows + children = mapWithIndex (w i \ windowComponent { window = w, index = i, isActive = (i == focused) }) windows }; os = ui.stateful { @@ -85,8 +128,8 @@ os = ui.stateful { | Key { key = "ArrowRight", meta = True } \ { state = state, emit = [osState.wm.focusedIndex := min (len osState.windows - 1) (osState.wm.focusedIndex + 1)] } | Key { key = "d", meta = True } \ - { state = state, emit = [closeFocused 0] } - | _ \ _ = debug "key" []; { state = state, emit = [] }, + { state = state, emit = [closeFocusedWindow 0] } + | _ \ { state = state, emit = [] }, view = state \ ui.stack { diff --git a/src/compiler.ts b/src/compiler.ts index dc7bc02..7ed3107 100644 --- a/src/compiler.ts +++ b/src/compiler.ts @@ -71,8 +71,8 @@ export function compile(ast: AST, useStore = true, bound = new Set(), to case 'let': const newBound = new Set([...bound, ast.name]); - return `((${sanitizeName(ast.name)}) => - ${compile(ast.body, useStore, newBound, topLevel)})(${compile(ast.value, useStore, bound, topLevel)})`; + return `(() => { let ${sanitizeName(ast.name)} = ${compile(ast.value, useStore, newBound, topLevel)}; + return ${compile(ast.body, useStore, newBound, topLevel)}; })()`; case 'match': return compileMatch(ast, useStore, bound, topLevel); diff --git a/src/runtime-compiled.ts b/src/runtime-compiled.ts index 7491402..fcde0f5 100644 --- a/src/runtime-compiled.ts +++ b/src/runtime-compiled.ts @@ -24,6 +24,7 @@ export function runAppCompiled(canvas: HTMLCanvasElement, store: any) { function setFocus(componentKey: string | null) { if (focusedComponentKey === componentKey) return; + const oldFocus = focusedComponentKey; focusedComponentKey = componentKey; @@ -36,6 +37,15 @@ export function runAppCompiled(canvas: HTMLCanvasElement, store: any) { if (componentKey && componentInstances.has(componentKey)) { handleComponentEvent(componentKey, { _tag: 'Focused' }); } + + // Notify ancestors + if (componentKey) { + for (const key of componentInstances.keys()) { + if (key !== componentKey && componentKey.startsWith(key + '.')) { + handleComponentEvent(key, { _tag: 'ChildFocused' }); + } + } + } } function handleComponentEvent(componentKey: string, event: any) { diff --git a/src/ui.ts b/src/ui.ts index 5867c85..eb2d370 100644 --- a/src/ui.ts +++ b/src/ui.ts @@ -88,8 +88,10 @@ function renderUI(ui: UIValue, ctx: CanvasRenderingContext2D, x: number, y: numb case 'row': { let offsetX = 0; for (const child of ui.children) { + const size = measure(child); + console.log("row child: ", child.kind, "width: ", size.width) renderUI(child, ctx, x + offsetX, y); - offsetX += measure(child).width + ui.gap; + offsetX += measure(child).width + (ui.gap || 0); } break; } @@ -98,7 +100,7 @@ function renderUI(ui: UIValue, ctx: CanvasRenderingContext2D, x: number, y: numb let offsetY = 0; for (const child of ui.children) { renderUI(child, ctx, x, y + offsetY); - offsetY += measure(child).height + ui.gap; + offsetY += measure(child).height + (ui.gap || 0); } break; } @@ -146,8 +148,16 @@ function renderUI(ui: UIValue, ctx: CanvasRenderingContext2D, x: number, y: numb } export function measure(ui: UIValue): { width: number, height: number } { + const result = _measure(ui); + if (isNaN(result.width) || isNaN(result.height)) { + console.warn('NaN from:', ui.kind, ui); + } + return result; +} + +export function _measure(ui: UIValue): { width: number, height: number } { switch (ui.kind) { - case 'rect': return { width: ui.w, height: ui.h }; + case 'rect': return { width: ui.w || 0, height: ui.h || 0 }; case 'text': return { width: ui.content.length * 10, height: 20 }; // TODO @@ -159,7 +169,7 @@ export function measure(ui: UIValue): { width: number, height: number } { totalWidth += size.width; maxHeight = Math.max(maxHeight, size.height); } - totalWidth += ui.gap * (ui.children.length - 1); + totalWidth += (ui.gap || 0) * (ui.children.length - 1); return { width: totalWidth, height: maxHeight }; } @@ -171,7 +181,7 @@ export function measure(ui: UIValue): { width: number, height: number } { totalHeight += size.height; maxWidth = Math.max(maxWidth, size.width); } - totalHeight += ui.gap * (ui.children.length - 1); + totalHeight += (ui.gap || 0) * (ui.children.length - 1); return { width: maxWidth, height: totalHeight }; } @@ -209,6 +219,8 @@ export function measure(ui: UIValue): { width: number, height: number } { case 'stateful': throw new Error('Stateful components cannot be measured'); + + default: return { width: 0, height: 0 }; } }