From 787e071fbd80be2b61b43938541df1bb5b85b5af Mon Sep 17 00:00:00 2001 From: Dustin Swan Date: Tue, 3 Feb 2026 23:12:30 -0700 Subject: [PATCH] Adding Clip ui primitive. text boxes looking... well, still awful but getting there --- src/types.ts | 1 + src/ui-components.cg | 42 +++++++++++++++++++++++------------------- src/ui.ts | 16 ++++++++++++++++ src/valueToUI.ts | 9 +++++++++ 4 files changed, 49 insertions(+), 19 deletions(-) diff --git a/src/types.ts b/src/types.ts index ce7d205..2bc2e4e 100644 --- a/src/types.ts +++ b/src/types.ts @@ -55,6 +55,7 @@ export type UIValue = | { kind: 'padding', child: UIValue, amount: number } | { kind: 'positioned', x: number, y: number, child: UIValue } | { kind: 'opacity', child: UIValue, opacity: number } + | { kind: 'clip', child: UIValue, w: number, h: number } | { kind: 'stack', children: UIValue[] } | { kind: 'text-input', value: string, placeholder: string, x: number, y: number, w: number, h: number, focused: boolean, onInput: Value, onSubmit: Value } diff --git a/src/ui-components.cg b/src/ui-components.cg index 1f27e80..a2f4819 100644 --- a/src/ui-components.cg +++ b/src/ui-components.cg @@ -72,27 +72,31 @@ textInput2 = { cursorX = measureText textBeforeCursor; padding = 8; - Clickable { - event = config.onFocus, - child = - Stack { - children = [ - Rect { w = config.w, h = config.h, color = "rgba(240,240,240,0.9)", radius = 4 }, + Clip { + w = config.w, + h = config.h, + child = Clickable { + event = config.onFocus, + child = + Stack { + children = [ + Rect { w = config.w, h = config.h, color = "rgba(240,240,240,0.9)", radius = 4 }, - Positioned { - x = 8 - state.scrollOffset, - y = 8, - child = Text { content = state.text, x = 0, y = 17 } - }, + Positioned { + x = 8 - state.scrollOffset, + y = 8, + child = Text { content = state.text, x = 0, y = 17 } + }, - (config.focused - | True \ Positioned { - x = 8 + cursorX - state.scrollOffset, - y = 8, - child = Rect { w = 2, h = 24, color = "black" } - } - | _ \ Rect { w = 0, h = 0, color = "transparent" }) - ] + (config.focused + | True \ Positioned { + x = 8 + cursorX - state.scrollOffset, + y = 8, + child = Rect { w = 2, h = 24, color = "black" } + } + | _ \ Rect { w = 0, h = 0, color = "transparent" }) + ] + } } } }; diff --git a/src/ui.ts b/src/ui.ts index 9be96e2..8d87eeb 100644 --- a/src/ui.ts +++ b/src/ui.ts @@ -110,6 +110,16 @@ function renderUI(ui: UIValue, ctx: CanvasRenderingContext2D, x: number, y: numb break; } + case 'clip': { + ctx.save(); + ctx.beginPath(); + ctx.rect(x, y, ui.w, ui.h); + ctx.clip(); + renderUI(ui.child, ctx, x, y); + ctx.restore(); + break; + } + case 'stack': { for (const child of ui.children) { renderUI(child, ctx, x, y); @@ -184,6 +194,12 @@ function measure(ui: UIValue): { width: number, height: number } { case 'clickable': return measure(ui.child); + case 'opacity': + return measure(ui.child); + + case 'clip': + return { width: ui.w, height: ui.h }; + case 'padding': { const childSize = measure(ui.child); return { diff --git a/src/valueToUI.ts b/src/valueToUI.ts index 3296ec8..25f9c9a 100644 --- a/src/valueToUI.ts +++ b/src/valueToUI.ts @@ -95,6 +95,15 @@ export function valueToUI(value: Value): UIValue { return { kind: 'opacity', opacity: opacity.value, child: valueToUI(child) }; } + case 'Clip': { + const { child, w, h } = fields; + + if (w.kind !== 'int' || h.kind !== 'int') + throw new Error('Invalid Clip fields'); + + return { kind: 'clip', w: w.value, h: h.value, child: valueToUI(child) }; + } + case 'Stack': { const children = fields.children;