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 type { Value } from './types'
import { measure } from './ui'
import { valueToUI } from './valueToUI'
const measureCanvas = document.createElement('canvas'); const measureCanvas = document.createElement('canvas');
const measureCtx = measureCanvas.getContext('2d')!; 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': { 'debug': {
kind: 'native', kind: 'native',
name: 'debug', name: 'debug',

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

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

@ -1,3 +1,15 @@
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 : Record -> UI
button = config \ button = config \
Clickable { Clickable {
@ -10,8 +22,27 @@ button = config \
} }
}; };
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 \ insertChar = text pos char \
# _ = debug "insertChar" char;
before = slice text 0 pos; before = slice text 0 pos;
after = slice text pos (len text); after = slice text pos (len text);
before & char & after; before & char & after;
@ -66,33 +97,33 @@ textInput = config \ Stateful {
update = state event \ event update = state event \ event
| ArrowLeft \ | ArrowLeft \
newCursorPos = max 0 (state.cursorPos - 1); 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 }; newState = state.{ text = state.text, cursorPos = newCursorPos, scrollOffset = newScroll };
{ state = newState, emit = [config.onKeyDown] } { state = newState, emit = [config.onKeyDown] }
| ArrowRight \ | ArrowRight \
newCursorPos = min (len state.text) (state.cursorPos + 1); 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 }; newState = state.{ text = state.text, cursorPos = newCursorPos, scrollOffset = newScroll };
{ state = newState, emit = [config.onKeyDown] } { state = newState, emit = [config.onKeyDown] }
| Backspace \ | Backspace \
newText = deleteChar state.text state.cursorPos; newText = deleteChar state.text state.cursorPos;
newCursorPos = max 0 (state.cursorPos - 1); 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 }; newState = state.{ text = newText, cursorPos = newCursorPos, scrollOffset = newScroll };
{ state = newState, emit = [config.onChange newText, config.onKeyDown] } { state = newState, emit = [config.onChange newText, config.onKeyDown] }
| Char c \ | Char c \
newText = insertChar state.text state.cursorPos c; newText = insertChar state.text state.cursorPos c;
newCursorPos = state.cursorPos + 1; 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 }; newState = state.{ text = newText, cursorPos = newCursorPos, scrollOffset = newScroll };
{ state = newState, emit = [config.onChange newText, config.onKeyDown] } { state = newState, emit = [config.onChange newText, config.onKeyDown] }
| Clicked coords \ | Clicked coords \
newCursorPos = findCursorPos state.text coords.x state.scrollOffset 8; 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 }; newState = state.{ text = state.text, cursorPos = newCursorPos, scrollOffset = newScroll };
{ state = newState, emit = [] } { state = newState, emit = [] }
@ -110,19 +141,19 @@ textInput = config \ Stateful {
h = config.h, h = config.h,
child = Stack { child = Stack {
children = [ 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 { Positioned {
x = 8 - state.scrollOffset, x = 8 - state.scrollOffset,
y = 8, y = 0,
child = Positioned { x = 0, y = 17, child = Text { content = state.text } } child = Positioned { x = 0, y = 12, child = Text { content = state.text, color = config.color } }
}, },
(state.focused (state.focused
| True \ Positioned { | True \ Positioned {
x = 8 + cursorX - state.scrollOffset, x = 8 + cursorX - state.scrollOffset,
y = 8, 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" }) | _ \ 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) { if (ui.radius && ui.radius > 0) {
const r = Math.min(ui.radius, ui.w / 2, ui.h / 2); const r = Math.min(ui.radius, ui.w / 2, ui.h / 2);
const inset = ui.strokeWidth ? ui.strokeWidth / 2 : 0;
// TODO
ctx.beginPath(); ctx.beginPath();
ctx.moveTo(x + r, y); ctx.moveTo(x + r, y);
ctx.lineTo(x + ui.w - 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) { if (ui.strokeColor && ui.strokeWidth) {
ctx.strokeStyle = ui.strokeColor; ctx.strokeStyle = ui.strokeColor;
ctx.lineWidth = ui.strokeWidth; 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': case 'text':
ctx.fillStyle = 'black'; ctx.fillStyle = ui.color || 'black';
ctx.font = '16px "SF Mono", "Monaco", "Menlo", "Consolas", "Courier New", monospace'; ctx.font = '16px "SF Mono", "Monaco", "Menlo", "Consolas", "Courier New", monospace';
ctx.textBaseline = 'top';
ctx.fillText(ui.content, x, y); ctx.fillText(ui.content, x, y);
break; 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) { switch (ui.kind) {
case 'rect': return { width: ui.w, height: ui.h }; case 'rect': return { width: ui.w, height: ui.h };

@ -29,12 +29,16 @@ export function valueToUI(value: Value): UIValue {
} }
case 'Text': { case 'Text': {
const content = fields.content; const { content, color } = fields;
if (content.kind !== 'string') if (content.kind !== 'string')
throw new Error('Invalid Text fields'); 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': { case 'Column': {

Loading…
Cancel
Save