a million fixes
This commit is contained in:
parent
b1696499e5
commit
c294d7fd6a
12 changed files with 140 additions and 168 deletions
22
src/ast.ts
22
src/ast.ts
|
|
@ -17,7 +17,7 @@ export type Variable = {
|
|||
kind: 'variable'
|
||||
name: string
|
||||
line?: number
|
||||
column: number
|
||||
column?: number
|
||||
start?: number
|
||||
}
|
||||
|
||||
|
|
@ -148,24 +148,6 @@ export type Definition = {
|
|||
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 = {
|
||||
kind: 'rebind'
|
||||
target: AST
|
||||
|
|
@ -189,8 +171,6 @@ export type AST =
|
|||
| List
|
||||
| ListSpread
|
||||
| Definition
|
||||
| TypeDef
|
||||
| Import
|
||||
| Rebind
|
||||
|
||||
export function prettyPrint(ast: AST, indent = 0): string {
|
||||
|
|
|
|||
|
|
@ -53,21 +53,21 @@ box = config \
|
|||
|
||||
ui.stack {
|
||||
children = [
|
||||
# background
|
||||
# background
|
||||
ui.rect { w = c.w, h = c.h, color = c.color },
|
||||
# top border
|
||||
# top border
|
||||
ui.rect { w = c.w, h = c.borderTop, color = c.borderColor },
|
||||
# bottom border
|
||||
# bottom border
|
||||
ui.positioned { x = 0, y = c.h - c.borderBottom, child =
|
||||
ui.rect { w = c.w, h = c.borderBottom, color = c.borderColor }
|
||||
},
|
||||
# left border
|
||||
# left border
|
||||
ui.rect { w = c.borderLeft, h = c.h, color = c.borderColor },
|
||||
# right border
|
||||
# right border
|
||||
ui.positioned { x = c.w - c.borderRight, y = 0, child =
|
||||
ui.rect { w = c.borderRight, h = c.h, color = c.borderColor }
|
||||
},
|
||||
# content
|
||||
# content
|
||||
ui.positioned { x = c.paddingLeft, y = c.paddingTop, child = c.child }
|
||||
]
|
||||
};
|
||||
|
|
@ -160,7 +160,7 @@ textInput = config \ ui.stateful {
|
|||
|
||||
| Focused \ { state = state.{ focused = True }, 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 = [] },
|
||||
|
||||
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
|
||||
# itemHeight
|
||||
# items = [
|
||||
|
|
@ -30,14 +31,17 @@
|
|||
# | None \ currentIndex);
|
||||
|
||||
|
||||
paletteState = {
|
||||
query = ""
|
||||
};
|
||||
|
||||
palette = config \
|
||||
focusedIndex = 0;
|
||||
windowHeight = 400;
|
||||
windowWidth = 600;
|
||||
|
||||
results = take 8 (config.search config.state.query);
|
||||
# results = config.search config.state.query; # once you get scrolling..
|
||||
results = take 8 (config.search paletteState.query);
|
||||
# results = config.search paletteState.query; # once you get scrolling..
|
||||
|
||||
dialogPadding = 0;
|
||||
|
||||
|
|
@ -64,62 +68,74 @@ palette = config \
|
|||
}
|
||||
};
|
||||
|
||||
ui.positioned {
|
||||
x = (config.viewport.width - windowWidth) / 2,
|
||||
y = (config.viewport.height - windowHeight) / 2,
|
||||
ui.stateful {
|
||||
key = "palette",
|
||||
focusable = False,
|
||||
|
||||
child = ui.stack {
|
||||
children = [
|
||||
ui.rect { w = windowWidth, h = windowHeight, color = "#063351", radius = 0, strokeWidth = 1, strokeColor = "#1A5F80" },
|
||||
ui.padding {
|
||||
amount = dialogPadding,
|
||||
child = ui.column {
|
||||
gap = 0,
|
||||
children = [
|
||||
textInput {
|
||||
key = "palette-query",
|
||||
initialValue = config.state.query,
|
||||
initialFocus = True,
|
||||
color = "white",
|
||||
backgroundColor = "rgba(0,0,0,0.2)",
|
||||
w = contentWidth,
|
||||
h = textInputHeight,
|
||||
onChange = text \ batch [config.state.query := text, config.state.focusedIndex := 0],
|
||||
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
|
||||
},
|
||||
init = {
|
||||
focusedIndex = 0,
|
||||
},
|
||||
|
||||
ui.clip {
|
||||
w = contentWidth,
|
||||
h = listHeight,
|
||||
child = ui.column {
|
||||
gap = 1,
|
||||
children = [
|
||||
box {
|
||||
w = contentWidth,
|
||||
h = 30,
|
||||
color = "transparent",
|
||||
paddingLeft = 6,
|
||||
paddingTop = 8,
|
||||
child = ui.text { content = "Store values", color = "#bbb" },
|
||||
},
|
||||
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 = [] },
|
||||
|
||||
...(mapWithIndex (t i \ paletteRow {
|
||||
child = t,
|
||||
w = contentWidth,
|
||||
h = textInputHeight,
|
||||
selected = (config.state.focusedIndex == i),
|
||||
onClick = \ config.onSelect t
|
||||
}) results)
|
||||
]
|
||||
}
|
||||
view = state \
|
||||
ui.positioned {
|
||||
x = (config.viewport.width - windowWidth) / 2,
|
||||
y = (config.viewport.height - windowHeight) / 2,
|
||||
|
||||
child = ui.stack {
|
||||
children = [
|
||||
ui.rect { w = windowWidth, h = windowHeight, color = "#063351", radius = 0, strokeWidth = 1, strokeColor = "#1A5F80" },
|
||||
ui.padding {
|
||||
amount = dialogPadding,
|
||||
child = ui.column {
|
||||
gap = 0,
|
||||
children = [
|
||||
textInput {
|
||||
key = "palette-query",
|
||||
initialValue = "",
|
||||
initialFocus = True,
|
||||
color = "white",
|
||||
backgroundColor = "rgba(0,0,0,0.2)",
|
||||
w = contentWidth,
|
||||
h = textInputHeight,
|
||||
onChange = text \ batch [paletteState.query := text],
|
||||
},
|
||||
|
||||
ui.clip {
|
||||
w = contentWidth,
|
||||
h = listHeight,
|
||||
child = ui.column {
|
||||
gap = 1,
|
||||
children = [
|
||||
box {
|
||||
w = contentWidth,
|
||||
h = 30,
|
||||
color = "transparent",
|
||||
paddingLeft = 6,
|
||||
paddingTop = 8,
|
||||
child = ui.text { content = "Store values", color = "#bbb" },
|
||||
},
|
||||
|
||||
...(mapWithIndex (t i \ paletteRow {
|
||||
child = t,
|
||||
w = contentWidth,
|
||||
h = textInputHeight,
|
||||
selected = (state.focusedIndex == i),
|
||||
onClick = \ config.onSelect t
|
||||
}) results)
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,8 +1,6 @@
|
|||
osState = {
|
||||
palette = {
|
||||
visible = True,
|
||||
query = "",
|
||||
focusedIndex = 0,
|
||||
visible = True
|
||||
},
|
||||
inspector = {
|
||||
visible = False,
|
||||
|
|
@ -37,8 +35,9 @@ inspect = item \
|
|||
init = {};
|
||||
|
||||
update = state event \ event
|
||||
| Key { key = "p", meta = True } \ osState.palette.visible := not (osState.palette.visible)
|
||||
| _ \ state;
|
||||
| Key { key = "p", meta = True } \
|
||||
{ state = state, emit = [osState.palette.visible := not (osState.palette.visible)] }
|
||||
| _ \ { state = state, emit = [] };
|
||||
|
||||
view = state viewport \
|
||||
ui.stack {
|
||||
|
|
@ -55,13 +54,13 @@ view = state viewport \
|
|||
# keep palette at the end so it's on top
|
||||
osState.palette.visible
|
||||
| True \ palette {
|
||||
state = osState.palette,
|
||||
# state = osState.palette,
|
||||
# query = osState.palette.query,
|
||||
search = storeSearch,
|
||||
onSelect = onSelect,
|
||||
viewport = viewport,
|
||||
}
|
||||
| False \ empty,
|
||||
|
||||
]
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -85,10 +85,7 @@ export function compile(ast: AST, useStore = true, bound = new Set<string>()): s
|
|||
const path = getPath(ast.target);
|
||||
const value = compile(ast.value, useStore, bound);
|
||||
|
||||
if (!useStore || !rootName) {
|
||||
const target = compile(ast.target, useStore, bound);
|
||||
return `(() => { ${target} = ${value}; return { _tag: "Rerender" }; })()`;
|
||||
}
|
||||
if (!rootName) throw new Error('Rebind target must be a variable');
|
||||
|
||||
if (path.length === 0) {
|
||||
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 {
|
||||
const ops: Record<string, string> = {
|
||||
'add': '_rt.add', 'sub': '_rt.sub', 'mul': '_rt.mul',
|
||||
'div': '_rt.div', 'mod': '_rt.mod', 'eq': '_rt.eq',
|
||||
'cat': '_rt.cat', 'gt': '_rt.gt', 'lt': '_rt.lt',
|
||||
'gte': '_rt.gte', 'lte': '_rt.lte', 'max': '_rt.max', 'min': '_rt.min',
|
||||
'add': '_rt.add', 'sub': '_rt.sub', 'mul': '_rt.mul', 'div': '_rt.div',
|
||||
'mod': '_rt.mod', 'pow': '_rt.pow',
|
||||
'eq': '_rt.eq', 'gt': '_rt.gt', 'lt': '_rt.lt', 'gte': '_rt.gte', 'lte': '_rt.lte',
|
||||
'cat': '_rt.cat', 'max': '_rt.max', 'min': '_rt.min',
|
||||
};
|
||||
|
||||
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 (useStore) {
|
||||
|
|
|
|||
|
|
@ -165,7 +165,7 @@ export class Parser {
|
|||
let expr = this.parseInfix();
|
||||
|
||||
// Rebind
|
||||
if (this.current().kind == 'colon-equals') {
|
||||
if (this.current().kind === 'colon-equals') {
|
||||
const token = this.current();
|
||||
this.advance();
|
||||
const value = this.parseExpressionNoMatch();
|
||||
|
|
|
|||
|
|
@ -60,7 +60,6 @@ export function runAppCompiled(app: App, canvas: HTMLCanvasElement, rt: any) {
|
|||
handleEvent(e);
|
||||
}
|
||||
}
|
||||
rerender();
|
||||
} catch(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 }
|
||||
};
|
||||
}
|
||||
return expandStateful(viewUI, path, renderedKeys);
|
||||
return expandStateful(viewUI, [...path, ui.key], renderedKeys);
|
||||
}
|
||||
|
||||
case 'stack':
|
||||
|
|
@ -186,7 +185,6 @@ export function runAppCompiled(app: App, canvas: HTMLCanvasElement, rt: any) {
|
|||
|
||||
if (event._tag === 'Rebind') {
|
||||
rt.rebind(event._0, event._1, event._2);
|
||||
rerender();
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -198,8 +196,13 @@ export function runAppCompiled(app: App, canvas: HTMLCanvasElement, rt: any) {
|
|||
if (event._tag === 'NoOp')
|
||||
return;
|
||||
|
||||
state = app.update(state)(event);
|
||||
rerender();
|
||||
const result = app.update(state)(event);
|
||||
state = result.state;
|
||||
if (result.emit && Array.isArray(result.emit)) {
|
||||
for (const e of result.emit) {
|
||||
handleEvent(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
canvas.addEventListener('click', (e) => {
|
||||
|
|
@ -221,6 +224,8 @@ export function runAppCompiled(app: App, canvas: HTMLCanvasElement, rt: any) {
|
|||
handleEvent(event);
|
||||
}
|
||||
}
|
||||
|
||||
rerender();
|
||||
});
|
||||
|
||||
window.addEventListener('keydown', (e) => {
|
||||
|
|
@ -236,20 +241,37 @@ export function runAppCompiled(app: App, canvas: HTMLCanvasElement, rt: any) {
|
|||
}
|
||||
};
|
||||
|
||||
// always send to OS first
|
||||
handleEvent(event);
|
||||
console.log("keydown", e)
|
||||
console.log("componentInstances", componentInstances)
|
||||
console.log("focusedComponentKey", focusedComponentKey)
|
||||
|
||||
if (focusedComponentKey) {
|
||||
// send to focused component
|
||||
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();
|
||||
rerender();
|
||||
});
|
||||
|
||||
let resizeRAF = 0;
|
||||
window.addEventListener('resize', () => {
|
||||
setupCanvas();
|
||||
rerender();
|
||||
})
|
||||
cancelAnimationFrame(resizeRAF);
|
||||
resizeRAF = requestAnimationFrame(() => {
|
||||
setupCanvas();
|
||||
rerender();
|
||||
});
|
||||
});
|
||||
|
||||
rerender();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -14,7 +14,8 @@ export const _rt = {
|
|||
mul: (a: number) => (b: number) => a * b,
|
||||
div: (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),
|
||||
min: (a: number) => (b: number) => Math.min(a, b),
|
||||
|
||||
|
|
@ -110,7 +111,6 @@ export const _rt = {
|
|||
|
||||
batch: (events: any[]) => ({ _tag: 'Batch', _0: events }),
|
||||
noOp: { _tag: 'NoOp' },
|
||||
rerender: { _tag: 'Rerender' },
|
||||
focus: (key: string) => ({ _tag: 'Focus', _0: key }),
|
||||
|
||||
nth: (i: number) => (xs: any[] | string) => i >= 0 && i < xs.length
|
||||
|
|
@ -142,6 +142,7 @@ export const _rt = {
|
|||
return printed;
|
||||
},
|
||||
rebind: (name: string, pathOrValue: any, maybeValue?: any) => {
|
||||
console.log("rebind", store, name, pathOrValue, maybeValue);
|
||||
if (maybeValue === undefined) {
|
||||
store[name] = pathOrValue;
|
||||
} else {
|
||||
|
|
@ -202,12 +203,12 @@ export const _rt = {
|
|||
const defs = parser.parse();
|
||||
const ast = defs[0].body;
|
||||
// validate free vars
|
||||
const free = freeVars(ast);
|
||||
const allowed = new Set([
|
||||
...Object.keys(store),
|
||||
...Object.keys(_rt),
|
||||
'True'
|
||||
])
|
||||
// const free = freeVars(ast);
|
||||
// const allowed = new Set([
|
||||
// ...Object.keys(store),
|
||||
// ...Object.keys(_rt),
|
||||
// 'True'
|
||||
// ])
|
||||
|
||||
const compiled = compile(defs[0].body);
|
||||
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 {
|
||||
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 &&
|
||||
y >= region.y && y < region.y + region.height) {
|
||||
return {
|
||||
|
|
|
|||
|
|
@ -1,5 +1,3 @@
|
|||
// import type { UIValue } from './types'
|
||||
|
||||
export function valueToUI(value: any): any {
|
||||
if (!value || !value._kind)
|
||||
throw new Error(`Expected UI constructor, got: ${JSON.stringify(value)}`);
|
||||
|
|
@ -92,6 +90,6 @@ export function valueToUI(value: any): any {
|
|||
};
|
||||
|
||||
default:
|
||||
throw new Error(`Unknown UI constructor: ${value._tag}`);
|
||||
throw new Error(`Unknown UI constructor: ${value._kind}`);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue