a million fixes

master
Dustin Swan 2 weeks ago
parent b1696499e5
commit c294d7fd6a
Signed by: dustinswan
GPG Key ID: 30D46587E2100467

@ -33,7 +33,7 @@ canvas {
<body> <body>
<div id="app"></div> <div id="app"></div>
<button id="clear-storage">Clear Storage</button> <!-- <button id="clear-storage">Clear Storage</button> -->
<script type="module" src="/src/main.ts"></script> <script type="module" src="/src/main.ts"></script>
</body> </body>
</html> </html>

@ -17,7 +17,7 @@ export type Variable = {
kind: 'variable' kind: 'variable'
name: string name: string
line?: number line?: number
column: number column?: number
start?: number start?: number
} }
@ -148,24 +148,6 @@ export type Definition = {
start?: number start?: number
} }
export type TypeDef = {
kind: 'typedef'
name: string
variants: Array<{ name: string, args: string[] }>
line?: number
column?: number
start?: number
}
export type Import = {
kind: 'import'
module: string
items: string[] | 'all'
line?: number
column?: number
start?: number
}
export type Rebind = { export type Rebind = {
kind: 'rebind' kind: 'rebind'
target: AST target: AST
@ -189,8 +171,6 @@ export type AST =
| List | List
| ListSpread | ListSpread
| Definition | Definition
| TypeDef
| Import
| Rebind | Rebind
export function prettyPrint(ast: AST, indent = 0): string { export function prettyPrint(ast: AST, indent = 0): string {

@ -160,7 +160,7 @@ textInput = config \ ui.stateful {
| Focused \ { state = state.{ focused = True }, emit = [] } | Focused \ { state = state.{ focused = True }, emit = [] }
| Blurred \ { state = state.{ focused = False }, emit = [] } | Blurred \ { state = state.{ focused = False }, emit = [] }
| Key { key = k } \ { state = state, emit = [\ config.onKeyDown k ] } # | Key { key = k } \ { state = state, emit = [\ config.onKeyDown k ] }
| _ \ { state = state, emit = [] }, | _ \ { state = state, emit = [] },
view = state \ view = state \

@ -1,43 +0,0 @@
# standard library testing
list = [1, 2, 3, 4, 5];
add1 = x \ x + 1;
mapped = map add1 list;
isEven = x \ x % 2 == 0;
sumList = list \ fold add 0 list;
filtered = filter isEven mapped;
summed = sumList mapped;
listLen = len list;
reversed = reverse list;
take3 = take 3 list;
drop3 = drop 3 list;
list2 = [6, 7, 8];
zipped = zipWith add list list2;
anded = and True False;
ored = or True False;
notted = not False;
ranged = range 3 14;
anyEven = any isEven list;
allEven = all isEven list;
flattened = flatten [[1, 2, 3], [4, 5, 6]];
contains3 = contains 3 list;
findEven = find isEven list;
stdlibTestResult = {
filtered = filtered,
summed = summed,
listLen = listLen,
reversed = reversed,
take3 = take3,
drop3 = drop3,
zipped = zipped,
anded = anded,
ored = ored,
notted = notted,
ranged = ranged,
anyEven = anyEven,
allEven = allEven,
flattened = flattened,
contains3 = contains3,
findEven = findEven
};

@ -1,4 +1,5 @@
# TODO: Section labels - flat list with Section/Item types, # TODO
# Section labels - flat list with Section/Item types,
# skip sections in keyboard nav, when scrolling with keyboard # skip sections in keyboard nav, when scrolling with keyboard
# itemHeight # itemHeight
# items = [ # items = [
@ -30,14 +31,17 @@
# | None \ currentIndex); # | None \ currentIndex);
paletteState = {
query = ""
};
palette = config \ palette = config \
focusedIndex = 0; focusedIndex = 0;
windowHeight = 400; windowHeight = 400;
windowWidth = 600; windowWidth = 600;
results = take 8 (config.search config.state.query); results = take 8 (config.search paletteState.query);
# results = config.search config.state.query; # once you get scrolling.. # results = config.search paletteState.query; # once you get scrolling..
dialogPadding = 0; dialogPadding = 0;
@ -64,6 +68,22 @@ palette = config \
} }
}; };
ui.stateful {
key = "palette",
focusable = False,
init = {
focusedIndex = 0,
},
update = state event \ event
| Key { printable = True } \ { state = state.{ focusedIndex = 0 }, emit = [] }
| Key { key = "ArrowUp" } \ { state = state.{ focusedIndex = max 0 (state.focusedIndex - 1) }, emit = [] }
| Key { key = "ArrowDown" } \ { state = state.{ focusedIndex = min (len results - 1) (state.focusedIndex + 1) }, emit = [] }
| Key { key = "Enter" } \ _ = debug "Enter" []; { state = state, emit = [config.onSelect (unwrapOr "" (nth state.focusedIndex results))] }
| e \ _ = debug "event" e; { state = state, emit = [] },
view = state \
ui.positioned { ui.positioned {
x = (config.viewport.width - windowWidth) / 2, x = (config.viewport.width - windowWidth) / 2,
y = (config.viewport.height - windowHeight) / 2, y = (config.viewport.height - windowHeight) / 2,
@ -78,18 +98,13 @@ palette = config \
children = [ children = [
textInput { textInput {
key = "palette-query", key = "palette-query",
initialValue = config.state.query, initialValue = "",
initialFocus = True, initialFocus = True,
color = "white", color = "white",
backgroundColor = "rgba(0,0,0,0.2)", backgroundColor = "rgba(0,0,0,0.2)",
w = contentWidth, w = contentWidth,
h = textInputHeight, h = textInputHeight,
onChange = text \ batch [config.state.query := text, config.state.focusedIndex := 0], onChange = text \ batch [paletteState.query := text],
onKeyDown = key \ key
| "ArrowUp" \ config.state.focusedIndex := max 0 (config.state.focusedIndex - 1)
| "ArrowDown" \ config.state.focusedIndex := min (len results - 1) (config.state.focusedIndex + 1)
| "Enter" \ (\ config.onSelect (unwrapOr "" (nth config.state.focusedIndex results)))
| _ \ noOp
}, },
ui.clip { ui.clip {
@ -111,7 +126,7 @@ palette = config \
child = t, child = t,
w = contentWidth, w = contentWidth,
h = textInputHeight, h = textInputHeight,
selected = (config.state.focusedIndex == i), selected = (state.focusedIndex == i),
onClick = \ config.onSelect t onClick = \ config.onSelect t
}) results) }) results)
] ]
@ -122,4 +137,5 @@ palette = config \
} }
] ]
} }
}
}; };

@ -1,8 +1,6 @@
osState = { osState = {
palette = { palette = {
visible = True, visible = True
query = "",
focusedIndex = 0,
}, },
inspector = { inspector = {
visible = False, visible = False,
@ -37,8 +35,9 @@ inspect = item \
init = {}; init = {};
update = state event \ event update = state event \ event
| Key { key = "p", meta = True } \ osState.palette.visible := not (osState.palette.visible) | Key { key = "p", meta = True } \
| _ \ state; { state = state, emit = [osState.palette.visible := not (osState.palette.visible)] }
| _ \ { state = state, emit = [] };
view = state viewport \ view = state viewport \
ui.stack { ui.stack {
@ -55,13 +54,13 @@ view = state viewport \
# keep palette at the end so it's on top # keep palette at the end so it's on top
osState.palette.visible osState.palette.visible
| True \ palette { | True \ palette {
state = osState.palette, # state = osState.palette,
# query = osState.palette.query,
search = storeSearch, search = storeSearch,
onSelect = onSelect, onSelect = onSelect,
viewport = viewport, viewport = viewport,
} }
| False \ empty, | False \ empty,
] ]
}; };

@ -85,10 +85,7 @@ export function compile(ast: AST, useStore = true, bound = new Set<string>()): s
const path = getPath(ast.target); const path = getPath(ast.target);
const value = compile(ast.value, useStore, bound); const value = compile(ast.value, useStore, bound);
if (!useStore || !rootName) { if (!rootName) throw new Error('Rebind target must be a variable');
const target = compile(ast.target, useStore, bound);
return `(() => { ${target} = ${value}; return { _tag: "Rerender" }; })()`;
}
if (path.length === 0) { if (path.length === 0) {
return `({ _tag: "Rebind", _0: "${rootName}", _1: ${value} })`; return `({ _tag: "Rebind", _0: "${rootName}", _1: ${value} })`;
@ -105,15 +102,15 @@ export function compile(ast: AST, useStore = true, bound = new Set<string>()): s
function sanitize(name: string, useStore = true): string { function sanitize(name: string, useStore = true): string {
const ops: Record<string, string> = { const ops: Record<string, string> = {
'add': '_rt.add', 'sub': '_rt.sub', 'mul': '_rt.mul', 'add': '_rt.add', 'sub': '_rt.sub', 'mul': '_rt.mul', 'div': '_rt.div',
'div': '_rt.div', 'mod': '_rt.mod', 'eq': '_rt.eq', 'mod': '_rt.mod', 'pow': '_rt.pow',
'cat': '_rt.cat', 'gt': '_rt.gt', 'lt': '_rt.lt', 'eq': '_rt.eq', 'gt': '_rt.gt', 'lt': '_rt.lt', 'gte': '_rt.gte', 'lte': '_rt.lte',
'gte': '_rt.gte', 'lte': '_rt.lte', 'max': '_rt.max', 'min': '_rt.min', 'cat': '_rt.cat', 'max': '_rt.max', 'min': '_rt.min',
}; };
if (ops[name]) return ops[name]; if (ops[name]) return ops[name];
const natives = ['eval', 'measure', 'measureText', 'storeSearch', 'getSource', 'debug', 'nth', 'len', 'slice', 'join', 'chars', 'split', 'str', 'redefine', 'undefine', 'stateful', 'batch', 'noOp', 'rerender', 'focus', 'ui']; const natives = ['eval', 'measure', 'measureText', 'storeSearch', 'getSource', 'debug', 'nth', 'len', 'slice', 'join', 'chars', 'split', 'str', 'redefine', 'undefine', 'batch', 'noOp', 'focus', 'ui'];
if (natives.includes(name)) return `_rt.${name}`; if (natives.includes(name)) return `_rt.${name}`;
if (useStore) { if (useStore) {

@ -165,7 +165,7 @@ export class Parser {
let expr = this.parseInfix(); let expr = this.parseInfix();
// Rebind // Rebind
if (this.current().kind == 'colon-equals') { if (this.current().kind === 'colon-equals') {
const token = this.current(); const token = this.current();
this.advance(); this.advance();
const value = this.parseExpressionNoMatch(); const value = this.parseExpressionNoMatch();

@ -60,7 +60,6 @@ export function runAppCompiled(app: App, canvas: HTMLCanvasElement, rt: any) {
handleEvent(e); handleEvent(e);
} }
} }
rerender();
} catch(error) { } catch(error) {
console.error('Component event error:', error); console.error('Component event error:', error);
} }
@ -102,7 +101,7 @@ export function runAppCompiled(app: App, canvas: HTMLCanvasElement, rt: any) {
event: { _tag: 'FocusAndClick', _0: fullKey } event: { _tag: 'FocusAndClick', _0: fullKey }
}; };
} }
return expandStateful(viewUI, path, renderedKeys); return expandStateful(viewUI, [...path, ui.key], renderedKeys);
} }
case 'stack': case 'stack':
@ -186,7 +185,6 @@ export function runAppCompiled(app: App, canvas: HTMLCanvasElement, rt: any) {
if (event._tag === 'Rebind') { if (event._tag === 'Rebind') {
rt.rebind(event._0, event._1, event._2); rt.rebind(event._0, event._1, event._2);
rerender();
return; return;
} }
@ -198,8 +196,13 @@ export function runAppCompiled(app: App, canvas: HTMLCanvasElement, rt: any) {
if (event._tag === 'NoOp') if (event._tag === 'NoOp')
return; return;
state = app.update(state)(event); const result = app.update(state)(event);
rerender(); state = result.state;
if (result.emit && Array.isArray(result.emit)) {
for (const e of result.emit) {
handleEvent(e);
}
}
} }
canvas.addEventListener('click', (e) => { canvas.addEventListener('click', (e) => {
@ -221,6 +224,8 @@ export function runAppCompiled(app: App, canvas: HTMLCanvasElement, rt: any) {
handleEvent(event); handleEvent(event);
} }
} }
rerender();
}); });
window.addEventListener('keydown', (e) => { window.addEventListener('keydown', (e) => {
@ -236,20 +241,37 @@ export function runAppCompiled(app: App, canvas: HTMLCanvasElement, rt: any) {
} }
}; };
// always send to OS first console.log("keydown", e)
handleEvent(event); console.log("componentInstances", componentInstances)
console.log("focusedComponentKey", focusedComponentKey)
if (focusedComponentKey) { if (focusedComponentKey) {
// send to focused component
handleComponentEvent(focusedComponentKey, event); handleComponentEvent(focusedComponentKey, event);
// bubble up to ancestors
for (const key of componentInstances.keys()) {
if (key !== focusedComponentKey && focusedComponentKey.startsWith(key + '.')) {
handleComponentEvent(key, event);
}
}
} }
// OS root
handleEvent(event);
e.preventDefault(); e.preventDefault();
rerender();
}); });
let resizeRAF = 0;
window.addEventListener('resize', () => { window.addEventListener('resize', () => {
cancelAnimationFrame(resizeRAF);
resizeRAF = requestAnimationFrame(() => {
setupCanvas(); setupCanvas();
rerender(); rerender();
}) });
});
rerender(); rerender();
} }

@ -14,7 +14,8 @@ export const _rt = {
mul: (a: number) => (b: number) => a * b, mul: (a: number) => (b: number) => a * b,
div: (a: number) => (b: number) => a / b, div: (a: number) => (b: number) => a / b,
mod: (a: number) => (b: number) => a % b, mod: (a: number) => (b: number) => a % b,
cat: (a: string) => (b: string) => a + b, pow: (a: number) => (b: number) => a ** b,
cat: (a: any) => (b: any) => Array.isArray(a) ? [...a, ...b] : a + b,
max: (a: number) => (b: number) => Math.max(a, b), max: (a: number) => (b: number) => Math.max(a, b),
min: (a: number) => (b: number) => Math.min(a, b), min: (a: number) => (b: number) => Math.min(a, b),
@ -110,7 +111,6 @@ export const _rt = {
batch: (events: any[]) => ({ _tag: 'Batch', _0: events }), batch: (events: any[]) => ({ _tag: 'Batch', _0: events }),
noOp: { _tag: 'NoOp' }, noOp: { _tag: 'NoOp' },
rerender: { _tag: 'Rerender' },
focus: (key: string) => ({ _tag: 'Focus', _0: key }), focus: (key: string) => ({ _tag: 'Focus', _0: key }),
nth: (i: number) => (xs: any[] | string) => i >= 0 && i < xs.length nth: (i: number) => (xs: any[] | string) => i >= 0 && i < xs.length
@ -142,6 +142,7 @@ export const _rt = {
return printed; return printed;
}, },
rebind: (name: string, pathOrValue: any, maybeValue?: any) => { rebind: (name: string, pathOrValue: any, maybeValue?: any) => {
console.log("rebind", store, name, pathOrValue, maybeValue);
if (maybeValue === undefined) { if (maybeValue === undefined) {
store[name] = pathOrValue; store[name] = pathOrValue;
} else { } else {
@ -202,12 +203,12 @@ export const _rt = {
const defs = parser.parse(); const defs = parser.parse();
const ast = defs[0].body; const ast = defs[0].body;
// validate free vars // validate free vars
const free = freeVars(ast); // const free = freeVars(ast);
const allowed = new Set([ // const allowed = new Set([
...Object.keys(store), // ...Object.keys(store),
...Object.keys(_rt), // ...Object.keys(_rt),
'True' // 'True'
]) // ])
const compiled = compile(defs[0].body); const compiled = compile(defs[0].body);
const fn = new Function('_rt', 'store', `return ${compiled}`); const fn = new Function('_rt', 'store', `return ${compiled}`);

@ -213,7 +213,9 @@ export function measure(ui: UIValue): { width: number, height: number } {
} }
export function hitTest(x: number, y: number): { event: any, relativeX: number, relativeY: number } | null { export function hitTest(x: number, y: number): { event: any, relativeX: number, relativeY: number } | null {
for (const region of clickRegions) { for (let i = clickRegions.length - 1; i >= 0; i--) {
const region = clickRegions[i];
if (x >= region.x && x < region.x + region.width && if (x >= region.x && x < region.x + region.width &&
y >= region.y && y < region.y + region.height) { y >= region.y && y < region.y + region.height) {
return { return {

@ -1,5 +1,3 @@
// import type { UIValue } from './types'
export function valueToUI(value: any): any { export function valueToUI(value: any): any {
if (!value || !value._kind) if (!value || !value._kind)
throw new Error(`Expected UI constructor, got: ${JSON.stringify(value)}`); throw new Error(`Expected UI constructor, got: ${JSON.stringify(value)}`);
@ -92,6 +90,6 @@ export function valueToUI(value: any): any {
}; };
default: default:
throw new Error(`Unknown UI constructor: ${value._tag}`); throw new Error(`Unknown UI constructor: ${value._kind}`);
} }
} }

Loading…
Cancel
Save