Adding center and box, more color options to UI stuff, working on command palette styling

master
Dustin Swan 3 weeks ago
parent da97f53729
commit 6edf592637
Signed by: dustinswan
GPG Key ID: 30D46587E2100467

@ -1,4 +1,6 @@
import type { Value } from './types'
import { measure } from './ui'
import { valueToUI } from './valueToUI'
const measureCanvas = document.createElement('canvas');
const measureCtx = measureCanvas.getContext('2d')!;
@ -463,6 +465,23 @@ export const builtins: { [name: string]: Value } = {
}
},
'measure': {
kind: 'native',
name: 'measure',
arity: 1,
fn: (uiValue) => {
const ui = valueToUI(uiValue);
const size = measure(ui);
return {
kind: 'record',
fields: {
width: { kind: 'int', value: size.width },
height: { kind: 'int', value: size.height }
}
};
}
},
'debug': {
kind: 'native',
name: 'debug',

@ -1,61 +1,95 @@
osState = {
palette = {
query = "",
focusedPaletteIndex = 0
focusedIndex = 0,
windowHeight = 400,
windowWidth = 600,
}
};
init = {
test = ""
};
init = {};
update = state event \ event
| _ \ state;
listRow = child selected \
Clickable {
event = osState.query := "",
listRow = config \
color = (config.selected | True \ "rgba(255,255,255,0.2)" | False \ "transparent");
Clickable {
event = config.onClick,
child = Stack {
children = [
Rect { w = 300, h = 40, strokeWidth = 2, strokeColor = "black", color = "pink" }
]
children = [
Rect { w = config.w, h = config.h, color = color },
centerV config.h (
Positioned {
x = 10,
y = 0,
child = Text { content = config.child, color = "white" }
}
)
]
}
};
};
# listRow = child selected \
# pre = (selected | True \ "*" | _ \ "");
# Text { content = pre & " " & child };
palette = state viewport \
results = take 10 (storeSearch osState.palette.query);
view = state viewport \
_ = debug "focusedPalletIndex" osState.focusedPaletteIndex;
Positioned {
x = 30,
y = 30,
child = Column {
gap = 10,
children = [
textInput {
key = "query",
initialValue = osState.query,
initialFocus = True,
w = 300,
h = 40,
onChange = text \ osState.query := text,
onKeyDown = key \ key
| ArrowUp \ osState.focusedPaletteIndex := max 0 (osState.focusedPaletteIndex - 1)
| ArrowDown \ osState.focusedPaletteIndex := (osState.focusedPaletteIndex + 1)
| _ \ NoOp
},
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)
state = osState.palette;
padding = 0;
textInputHeight = 40;
contentWidth = state.windowWidth - (padding * 2);
contentHeight = state.windowHeight - (padding * 2);
listHeight = contentHeight - 40;
Positioned {
x = (viewport.width - state.windowWidth) / 2,
y = (viewport.height - state.windowHeight) / 2,
child = Stack {
children = [
Rect { w = state.windowWidth, h = state.windowHeight, color = "#063351", radius = 0, strokeWidth = 1, strokeColor = "#1A5F80" },
Padding {
amount = padding,
child = Column {
gap = 0,
children = [
textInput {
key = "query",
initialValue = osState.palette.query,
initialFocus = True,
color = "white",
backgroundColor = "rgba(0,0,0,0.2)",
w = contentWidth,
h = textInputHeight,
onChange = text \ Batch [osState.palette.query := text, osState.palette.focusedIndex := 0],
onKeyDown = key \ key
| ArrowUp \ osState.palette.focusedIndex := max 0 (osState.palette.focusedIndex - 1)
| ArrowDown \ osState.palette.focusedIndex := (osState.palette.focusedIndex + 1)
| _ \ NoOp
},
Clip {
w = contentWidth,
h = listHeight,
child = Column {
gap = 1,
children = mapWithIndex (t i \ listRow { child = t, w = contentWidth, h = textInputHeight, selected = (osState.palette.focusedIndex == i), onClick = NoOp }) results
}
}
]
}
}
}
]
}
};
]
}
};
view = state viewport \
Stack {
children = [
Rect { w = viewport.width, h = viewport.height, color = "#012" },
palette state viewport
]
};
os = { init = init, update = update, view = view }

@ -54,7 +54,7 @@ export type NativeFunction = {
export type UIValue =
| { kind: 'rect', w: number, h: number, color?: string, strokeColor?: string, strokeWidth?: number, radius?: number }
| { kind: 'text', content: string }
| { kind: 'text', content: string, color?: string }
| { kind: 'row', children: UIValue[], gap: number }
| { kind: 'column', children: UIValue[], gap: number }
| { kind: 'clickable', child: UIValue, event: Value }

@ -1,17 +1,48 @@
centerH = parentW child \
childW = (measure child).width;
Positioned { x = (parentW - childW) / 2, y = 0, child = child };
centerV = parentH child \
childH = (measure child).height;
Positioned { y = (parentH - childH) / 2, x = 0, child = child };
center = parentW parentH child \
childSize = measure child;
Positioned { x = (parentW - childSize.width) / 2, y = (parentH - childSize.height) / 2, child = child };
# button : Record -> UI
button = config \
Clickable {
event = config.event,
child = Stack {
children = [
Rect { w = 100, h = 40, color = "#eee" },
Text { content = config.label, x = 10, y = 25, color = "#222" }
]
}
};
Clickable {
event = config.event,
child = Stack {
children = [
Rect { w = 100, h = 40, color = "#eee" },
Text { content = config.label, x = 10, y = 25, color = "#222" }
]
}
};
box = config \ Stack {
children = [
# background
Rect { w = config.w, h = config.h, color = config.color },
# top border
Rect { w = config.w, h = config.borderTop, color = config.borderColor },
# bottom border
Positioned { x = 0, y = config.h - config.borderBottom, child =
Rect { w = config.w, h = config.borderBottom, color = config.borderColor }
},
# left border
Rect { w = config.borderLeft, h = config.h, color = config.borderColor },
# right border
Positioned { x = config.w - config.borderRight, y = 0, child =
Rect { w = config.borderRight, h = config.h, color = config.borderColor }
},
# content
Positioned { x = config.paddingLeft, y = config.paddingTop, child = config.child }
]};
insertChar = text pos char \
# _ = debug "insertChar" char;
before = slice text 0 pos;
after = slice text pos (len text);
before & char & after;
@ -66,33 +97,33 @@ textInput = config \ Stateful {
update = state event \ event
| ArrowLeft \
newCursorPos = max 0 (state.cursorPos - 1);
newScroll = calcScrollOffset state.text newCursorPos state.scrollOffset 284;
newScroll = calcScrollOffset state.text newCursorPos state.scrollOffset config.w;
newState = state.{ text = state.text, cursorPos = newCursorPos, scrollOffset = newScroll };
{ state = newState, emit = [config.onKeyDown] }
| ArrowRight \
newCursorPos = min (len state.text) (state.cursorPos + 1);
newScroll = calcScrollOffset state.text newCursorPos state.scrollOffset 284;
newScroll = calcScrollOffset state.text newCursorPos state.scrollOffset config.w;
newState = state.{ text = state.text, cursorPos = newCursorPos, scrollOffset = newScroll };
{ state = newState, emit = [config.onKeyDown] }
| Backspace \
newText = deleteChar state.text state.cursorPos;
newCursorPos = max 0 (state.cursorPos - 1);
newScroll = calcScrollOffset newText newCursorPos state.scrollOffset 284;
newScroll = calcScrollOffset newText newCursorPos state.scrollOffset config.w;
newState = state.{ text = newText, cursorPos = newCursorPos, scrollOffset = newScroll };
{ state = newState, emit = [config.onChange newText, config.onKeyDown] }
| Char c \
newText = insertChar state.text state.cursorPos c;
newCursorPos = state.cursorPos + 1;
newScroll = calcScrollOffset newText newCursorPos state.scrollOffset 284;
newScroll = calcScrollOffset newText newCursorPos state.scrollOffset config.w;
newState = state.{ text = newText, cursorPos = newCursorPos, scrollOffset = newScroll };
{ state = newState, emit = [config.onChange newText, config.onKeyDown] }
| Clicked coords \
newCursorPos = findCursorPos state.text coords.x state.scrollOffset 8;
newScroll = calcScrollOffset state.text newCursorPos state.scrollOffset 284;
newScroll = calcScrollOffset state.text newCursorPos state.scrollOffset config.w;
newState = state.{ text = state.text, cursorPos = newCursorPos, scrollOffset = newScroll };
{ state = newState, emit = [] }
@ -110,19 +141,19 @@ textInput = config \ Stateful {
h = config.h,
child = Stack {
children = [
Rect { w = config.w, h = config.h, color = "rgba(240,240,240,0.9)", radius = 4 },
Rect { w = config.w, h = config.h, color = config.backgroundColor, radius = 0 },
Positioned {
x = 8 - state.scrollOffset,
y = 8,
child = Positioned { x = 0, y = 17, child = Text { content = state.text } }
y = 0,
child = Positioned { x = 0, y = 12, child = Text { content = state.text, color = config.color } }
},
(state.focused
| True \ Positioned {
x = 8 + cursorX - state.scrollOffset,
y = 8,
child = Rect { w = 2, h = 24, color = "black" }
child = Rect { w = 2, h = 24, color = config.color }
}
| _ \ Rect { w = 0, h = 0, color = "transparent" })
]

@ -31,6 +31,9 @@ function renderUI(ui: UIValue, ctx: CanvasRenderingContext2D, x: number, y: numb
if (ui.radius && ui.radius > 0) {
const r = Math.min(ui.radius, ui.w / 2, ui.h / 2);
const inset = ui.strokeWidth ? ui.strokeWidth / 2 : 0;
// TODO
ctx.beginPath();
ctx.moveTo(x + r, y);
ctx.lineTo(x + ui.w - r, y);
@ -56,7 +59,8 @@ function renderUI(ui: UIValue, ctx: CanvasRenderingContext2D, x: number, y: numb
if (ui.strokeColor && ui.strokeWidth) {
ctx.strokeStyle = ui.strokeColor;
ctx.lineWidth = ui.strokeWidth;
ctx.strokeRect(x, y, ui.w, ui.h);;
const inset = ui.strokeWidth / 2;
ctx.strokeRect(x + inset, y + inset, ui.w - ui.strokeWidth, ui.h - ui.strokeWidth);
}
}
@ -64,8 +68,9 @@ function renderUI(ui: UIValue, ctx: CanvasRenderingContext2D, x: number, y: numb
}
case 'text':
ctx.fillStyle = 'black';
ctx.fillStyle = ui.color || 'black';
ctx.font = '16px "SF Mono", "Monaco", "Menlo", "Consolas", "Courier New", monospace';
ctx.textBaseline = 'top';
ctx.fillText(ui.content, x, y);
break;
@ -129,7 +134,7 @@ function renderUI(ui: UIValue, ctx: CanvasRenderingContext2D, x: number, y: numb
}
}
function measure(ui: UIValue): { width: number, height: number } {
export function measure(ui: UIValue): { width: number, height: number } {
switch (ui.kind) {
case 'rect': return { width: ui.w, height: ui.h };

@ -29,12 +29,16 @@ export function valueToUI(value: Value): UIValue {
}
case 'Text': {
const content = fields.content;
const { content, color } = fields;
if (content.kind !== 'string')
throw new Error('Invalid Text fields');
return { kind: 'text', content: content.value };
return {
kind: 'text',
content: content.value,
color: color && color.kind === 'string' ? color.value : undefined
};
}
case 'Column': {

Loading…
Cancel
Save