Adding center and box, more color options to UI stuff, working on command palette styling
This commit is contained in:
parent
da97f53729
commit
6edf592637
6 changed files with 166 additions and 73 deletions
|
|
@ -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',
|
||||
|
|
|
|||
130
src/os.cg
130
src/os.cg
|
|
@ -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 := "",
|
||||
child = Stack {
|
||||
children = [
|
||||
Rect { w = 300, h = 40, strokeWidth = 2, strokeColor = "black", color = "pink" }
|
||||
]
|
||||
}
|
||||
};
|
||||
listRow = config \
|
||||
color = (config.selected | True \ "rgba(255,255,255,0.2)" | False \ "transparent");
|
||||
|
||||
# listRow = child selected \
|
||||
# pre = (selected | True \ "*" | _ \ "");
|
||||
# Text { content = pre & " " & child };
|
||||
Clickable {
|
||||
event = config.onClick,
|
||||
child = Stack {
|
||||
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" }
|
||||
}
|
||||
)
|
||||
]
|
||||
}
|
||||
};
|
||||
|
||||
palette = state viewport \
|
||||
results = take 10 (storeSearch osState.palette.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 \
|
||||
_ = 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)
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
};
|
||||
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" })
|
||||
]
|
||||
|
|
|
|||
11
src/ui.ts
11
src/ui.ts
|
|
@ -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…
Add table
Add a link
Reference in a new issue