467 lines
14 KiB
Text
467 lines
14 KiB
Text
empty = ui.rect { w = 0, h = 0 };
|
|
|
|
centerH = parentW child \
|
|
childW = (ui.measure child).width;
|
|
ui.positioned { x = (parentW - childW) / 2, y = 0, child = child };
|
|
|
|
centerV = parentH child \
|
|
childH = (ui.measure child).height;
|
|
ui.positioned { y = (parentH - childH) / 2, x = 0, child = child };
|
|
|
|
center = parentW parentH child \
|
|
childSize = ui.measure child;
|
|
ui.positioned { x = (parentW - childSize.width) / 2, y = (parentH - childSize.height) / 2, child = child };
|
|
|
|
# button : Record -> ui
|
|
button = config \
|
|
label = renderText { content = config.label };
|
|
labelSize = ui.measure label;
|
|
labelWidth = labelSize.width;
|
|
|
|
defaults = {
|
|
h = 30,
|
|
w = labelWidth + 20,
|
|
strokeWidth = 1,
|
|
strokeColor = "#fff",
|
|
textColor = "#fff",
|
|
label = ""
|
|
};
|
|
|
|
c = { ...defaults, ...config };
|
|
|
|
ui.clickable {
|
|
onClick = config.event,
|
|
child = ui.stack {
|
|
children = [
|
|
ui.rect { w = c.w, h = c.h, strokeColor = c.strokeColor, strokeWidth = c.strokeWidth },
|
|
ui.positioned {
|
|
x = 10, y = 8,
|
|
child = renderText { content = config.label, color = c.textColor }
|
|
}
|
|
]
|
|
}
|
|
};
|
|
|
|
# inputButton (button that turns into an input)
|
|
inputButton = config \
|
|
label = renderText { content = config.label };
|
|
labelSize = ui.measure label;
|
|
labelWidth = labelSize.width;
|
|
|
|
defaults = {
|
|
h = 30,
|
|
w = labelWidth + 16,
|
|
strokeWidth = 1,
|
|
strokeColor = "#fff",
|
|
textColor = "#fff",
|
|
backgroundColor = "transparent",
|
|
label = ""
|
|
};
|
|
|
|
c = { ...defaults, ...config };
|
|
|
|
ui.stateful {
|
|
key = c.key,
|
|
focusable = True,
|
|
|
|
init = { editing = False },
|
|
|
|
update = state event \ event
|
|
| Activate \ { state = state.{ editing = True }, emit = [] }
|
|
| Submit text \ { state = state.{ editing = False }, emit = [c.onSubmit text] }
|
|
| Key { key = "Escape" } \ { state = state.{ editing = False }, emit = [] }
|
|
| _ \ { state = state, emit = [] },
|
|
|
|
view = state emit \
|
|
state.editing
|
|
| True \ textInput {
|
|
key = c.key & "-input",
|
|
autoFocus = True,
|
|
w = c.w, h = c.h,
|
|
strokeWidth = 1,
|
|
strokeColor = "#fff",
|
|
color = c.textColor,
|
|
backgroundColor = c.backgroundColor,
|
|
onSubmit = text \ emit (Submit text)
|
|
}
|
|
| False \ ui.clickable {
|
|
onClick = \ emit Activate,
|
|
child = ui.stack {
|
|
children = [
|
|
ui.rect { w = c.w, h = c.h, strokeColor = c.strokeColor, strokeWidth = c.strokeWidth },
|
|
ui.positioned {
|
|
x = 8, y = 4,
|
|
child = label
|
|
}
|
|
]
|
|
}
|
|
}
|
|
};
|
|
|
|
# scrollable
|
|
scrollable = config \
|
|
defaults = {
|
|
scrollX = 0,
|
|
scrollY = 0,
|
|
onScroll = _ \ noOp
|
|
};
|
|
c = { ...defaults, ...config };
|
|
|
|
showVBar = c.totalHeight > c.h;
|
|
vBarHeight = max 20 (c.h * c.h / c.totalHeight);
|
|
vBarY = c.scrollY * c.h / c.totalHeight;
|
|
|
|
showHBar = c.totalWidth > c.w;
|
|
hBarWidth = max 20 (c.w * c.w / c.totalWidth);
|
|
hBarX = c.scrollX * c.w / c.totalWidth;
|
|
|
|
ui.stack {
|
|
children = [
|
|
ui.scrollable {
|
|
w = c.w,
|
|
h = c.h,
|
|
scrollX = c.scrollX,
|
|
scrollY = c.scrollY,
|
|
onScroll = c.onScroll,
|
|
child = c.child
|
|
},
|
|
...(showVBar
|
|
| True \ [ui.positioned {
|
|
x = c.w - 4,
|
|
y = vBarY,
|
|
child = ui.rect { w = 4, h = vBarHeight, color = "rgba(255,255,255,0.3)", radius = 2 }
|
|
}]
|
|
| False \ []),
|
|
...(showHBar
|
|
| True \ [ui.positioned {
|
|
x = hBarX,
|
|
y = c.h - 4,
|
|
child = ui.rect { h = 4, w = hBarWidth, color = "rgba(255,255,255,0.3)", radius = 2 }
|
|
}]
|
|
| False \ [])
|
|
]
|
|
};
|
|
|
|
box = config \
|
|
defaults = {
|
|
w = 100,
|
|
h = 100,
|
|
color = "white",
|
|
borderTop = 0,
|
|
borderBottom = 0,
|
|
borderLeft = 0,
|
|
borderRight = 0,
|
|
borderColor = "transparent",
|
|
paddingTop = 0,
|
|
paddingLeft = 0
|
|
};
|
|
|
|
c = { ...defaults, ...config };
|
|
|
|
ui.stack {
|
|
children = [
|
|
# background
|
|
ui.rect { w = c.w, h = c.h, color = c.color },
|
|
# top border
|
|
ui.rect { w = c.w, h = c.borderTop, color = c.borderColor },
|
|
# 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
|
|
ui.rect { w = c.borderLeft, h = c.h, color = c.borderColor },
|
|
# right border
|
|
ui.positioned { x = c.w - c.borderRight, y = 0, child =
|
|
ui.rect { w = c.borderRight, h = c.h, color = c.borderColor }
|
|
},
|
|
# content
|
|
ui.positioned { x = c.paddingLeft, y = c.paddingTop, child = c.child }
|
|
]
|
|
};
|
|
|
|
textInput = config \
|
|
defaults = {
|
|
onSubmit = _ \ noOp,
|
|
onChange = _ \ noOp,
|
|
initialValue = "",
|
|
autoFocus = False,
|
|
strokeWidth = 0,
|
|
strokeColor = "transparent"
|
|
};
|
|
|
|
c = { ...defaults, ...config };
|
|
|
|
insertChar = text pos char \
|
|
before = slice text 0 pos;
|
|
after = slice text pos (len text);
|
|
before & char & after;
|
|
|
|
deleteChar = text pos \
|
|
(pos == 0
|
|
| True \ text
|
|
| False \
|
|
(before = slice text 0 (pos - 1);
|
|
after = slice text pos (len text);
|
|
before & after));
|
|
|
|
calcScrollOffset = text cursorPos scrollOffset inputWidth \
|
|
textBeforeCursor = slice text 0 cursorPos;
|
|
# cursorX = ui.measureText textBeforeCursor;
|
|
cursorX = (measureText { content = textBeforeCursor, scale = 2 }).width;
|
|
(cursorX < scrollOffset
|
|
| True \ max 0 (cursorX - 20)
|
|
| False \
|
|
(cursorX > (scrollOffset + inputWidth)
|
|
| True \ cursorX - inputWidth + 20
|
|
| False \ scrollOffset));
|
|
|
|
findPosHelper = text targetX index \
|
|
(index >= len text)
|
|
| True \ len text
|
|
| False \ (
|
|
# widthSoFar = ui.measureText (slice text 0 index);
|
|
widthSoFar = (measureText { content = (slice text 0 index), scale = 2 }).width;
|
|
# widthNext = ui.measureText (slice text 0 (index + 1));
|
|
widthNext = (measureText { content = (slice text 0 (index + 1)), scale = 2 }).width;
|
|
midpoint = (widthSoFar + widthNext) / 2;
|
|
(targetX < midpoint
|
|
| True \ index
|
|
| False \ findPosHelper text targetX (index + 1))
|
|
);
|
|
|
|
findCursorPos = text clickX scrollOffset inputPadding \
|
|
adjustedX = clickX + scrollOffset - inputPadding;
|
|
findPosHelper text adjustedX 0;
|
|
|
|
ui.stateful {
|
|
key = c.key,
|
|
focusable = True,
|
|
autoFocus = c.autoFocus,
|
|
|
|
# init : State
|
|
init = {
|
|
text = c.initialValue,
|
|
focused = c.autoFocus,
|
|
cursorPos = len c.initialValue,
|
|
scrollOffset = 0
|
|
},
|
|
|
|
# update : State \ Event \ State
|
|
update = state event \
|
|
s = (hasField "value" c
|
|
| True \ (c.value == state.text
|
|
| True \ state
|
|
| False \ state.{ text = c.value, cursorPos = len c.value, scrollOffset = 0 })
|
|
| False \ state);
|
|
event
|
|
| Key { key = key, printable = True, meta = False, ctrl = False } \ (
|
|
newText = insertChar s.text s.cursorPos key;
|
|
newCursorPos = s.cursorPos + 1;
|
|
newScroll = calcScrollOffset newText newCursorPos s.scrollOffset c.w;
|
|
newState = s.{ text = newText, cursorPos = newCursorPos, scrollOffset = newScroll };
|
|
{ state = newState, emit = [c.onChange newText] })
|
|
|
|
| Key { key = "ArrowLeft" } \ (
|
|
newCursorPos = max 0 (s.cursorPos - 1);
|
|
newScroll = calcScrollOffset s.text newCursorPos s.scrollOffset c.w;
|
|
newState = s.{ text = s.text, cursorPos = newCursorPos, scrollOffset = newScroll };
|
|
{ state = newState, emit = [] })
|
|
|
|
| Key { key = "ArrowRight" } \ (
|
|
newCursorPos = min (len s.text) (s.cursorPos + 1);
|
|
newScroll = calcScrollOffset s.text newCursorPos s.scrollOffset c.w;
|
|
newState = s.{ text = s.text, cursorPos = newCursorPos, scrollOffset = newScroll };
|
|
{ state = newState, emit = [] })
|
|
|
|
| Key { key = "Backspace" } \ (
|
|
newText = deleteChar s.text s.cursorPos;
|
|
newCursorPos = max 0 (s.cursorPos - 1);
|
|
newScroll = calcScrollOffset newText newCursorPos s.scrollOffset c.w;
|
|
newState = s.{ text = newText, cursorPos = newCursorPos, scrollOffset = newScroll };
|
|
{ state = newState, emit = [c.onChange newText] })
|
|
|
|
| Key { key = "Enter" } \ { state = s, emit = [c.onSubmit s.text] }
|
|
|
|
| Clicked coords \ (
|
|
newCursorPos = findCursorPos s.text coords.x s.scrollOffset 8;
|
|
newScroll = calcScrollOffset s.text newCursorPos s.scrollOffset c.w;
|
|
newState = s.{ text = s.text, cursorPos = newCursorPos, scrollOffset = newScroll };
|
|
{ state = newState, emit = [] })
|
|
|
|
| Focused \ { state = state.{ focused = True }, emit = [] }
|
|
| Blurred \ { state = state.{ focused = False }, emit = [] }
|
|
| SetText newText \ (
|
|
newState = s.{ text = newText, cursorPos = len newText, scrollOffset = 0 };
|
|
{ state = newState, emit = [c.onChange newText] })
|
|
| _ \ { state = s, emit = [] },
|
|
|
|
view = state emit \
|
|
text = (hasField "value" c | True \ c.value | False \ state.text);
|
|
cursorPos = (text == state.text | True \ state.cursorPos | False \ len text);
|
|
textBeforeCursor = slice text 0 cursorPos;
|
|
# cursorX = ui.measureText textBeforeCursor;
|
|
cursorX = (measureText { content = textBeforeCursor, scale = 2 }).width;
|
|
padding = 8;
|
|
|
|
ui.clip {
|
|
w = c.w,
|
|
h = c.h,
|
|
child = ui.stack {
|
|
children = [
|
|
ui.rect { w = c.w, h = c.h, color = c.backgroundColor, radius = 0, strokeWidth = c.strokeWidth, strokeColor = c.strokeColor },
|
|
|
|
ui.positioned {
|
|
x = 8 - state.scrollOffset,
|
|
y = 0,
|
|
# child = ui.positioned { x = 0, y = 12, child = ui.text { content = text, color = c.color } }
|
|
child = ui.positioned { x = 0, y = 12, child = renderText { content = text, color = c.color } }
|
|
},
|
|
|
|
(state.focused
|
|
| True \ ui.positioned {
|
|
x = 8 + cursorX - state.scrollOffset,
|
|
y = 8,
|
|
child = ui.rect { w = 2, h = 24, color = c.color }
|
|
}
|
|
| _ \ empty)
|
|
]
|
|
}
|
|
}
|
|
};
|
|
|
|
glyphView = config \
|
|
defaults = { scale = 1, color = "#fff" };
|
|
c = { ...defaults, ...config };
|
|
ui.stack {
|
|
children = [
|
|
ui.rect { w = c.glyph.w * c.scale, h = c.glyph.h * c.scale, color = "transparent" },
|
|
...map (pixel \
|
|
ui.positioned {
|
|
x = pixel.x * c.scale,
|
|
y = pixel.y * c.scale,
|
|
child = ui.rect { w = c.scale, h = c.scale, color = c.color }
|
|
}
|
|
) c.glyph.map
|
|
]
|
|
};
|
|
|
|
# renderText : TextConfig \ UI
|
|
renderText = config \
|
|
defaults = { content = "", scale = 2, color = "#fff", font = "myFont2" };
|
|
c = { ...defaults, ...config };
|
|
font = getAt [c.font] | Some f \ f | None \ { glyphs = {} };
|
|
charW = font.charWidth;
|
|
gap = c.scale;
|
|
chars = split "" c.content;
|
|
|
|
chars == []
|
|
| True \ ui.rect { w = 0, h = 12 * c.scale, color = "transparent" }
|
|
| False \ ui.row {
|
|
children = map (char \
|
|
getField char font.glyphs
|
|
| Some g \ glyphView { glyph = g, scale = c.scale, color = c.color }
|
|
| None \ ui.rect { w = charW * c.scale, h = 12 * c.scale }
|
|
) chars,
|
|
gap = gap
|
|
};
|
|
|
|
# text : String \ UI
|
|
text = content \ renderText { content = content };
|
|
|
|
measureText = config \
|
|
defaults = { content = "", scale = 2, font = "myFont2" };
|
|
c = { ...defaults, ...config };
|
|
font = getAt [c.font] | Some f \ f | None \ { charWidth = 5 };
|
|
charW = font.charWidth;
|
|
gap = c.scale;
|
|
strLen = len config.content;
|
|
{
|
|
width = strLen * charW * c.scale + max 0 (strLen - 1) * gap,
|
|
height = 12 * c.scale
|
|
};
|
|
|
|
sizedText = content \
|
|
size = measureText { content = content };
|
|
{ ui = renderText { content = content }, w = size.width, h = size.height };
|
|
|
|
# new CG layout
|
|
|
|
LayoutChild = Fixed Int (Int \ Int \ UI) | Flex Int (Int \ Int \ UI);
|
|
Alignment = Start | Center | End;
|
|
Direction = Col | Row;
|
|
|
|
# @layout
|
|
#
|
|
# layoutDir
|
|
# : Direction \ { w : Int, h : Int } \ List LayoutChild \ { ui : UI, w : Int, h : Int }
|
|
# = dir constraints children \
|
|
# mainSize = dir | Col \ constraints.h | Row \ constraints.w;
|
|
# crossSize = dir | Col \ constraints.w | Row \ constraints.h;
|
|
#
|
|
# # Pass 1: sum fixed heights, total flex weight
|
|
# info = fold (acc child \ child
|
|
# | Fixed s _ \ acc.{ fixedMain = acc.fixedMain + s }
|
|
# | Flex n _ \ acc.{ totalFlex = acc.totalFlex + n }
|
|
# ) { fixedMain = 0, totalFlex = 0 } children;
|
|
#
|
|
# remaining = mainSize - info.fixedMain;
|
|
#
|
|
# # Pass 2
|
|
# result = fold (acc child \
|
|
# allocated = (child
|
|
# | Fixed s view \ { s = s, view = view }
|
|
# | Flex n view \ { s = remaining * n / info.totalFlex, view = view });
|
|
#
|
|
# childW = dir | Col \ crossSize | Row \ allocated.s;
|
|
# childH = dir | Col \ allocated.s | Row \ crossSize;
|
|
# posX = dir | Col \ 0 | Row \ acc.pos;
|
|
# posY = dir | Col \ acc.pos | Row \ 0;
|
|
#
|
|
# rendered = ui.positioned {
|
|
# x = posX, y = posY,
|
|
# child = allocated.view childW childH
|
|
# };
|
|
#
|
|
# acc.{ pos = acc.pos + allocated.s, children = [...acc.children, rendered] }
|
|
# ) { pos = 0, children = [] } children;
|
|
#
|
|
# { ui = ui.stack { children = result.children }, w = constraints.w, h = constraints.h };
|
|
#
|
|
# col = layoutDir Col;
|
|
# row = layoutDir Row;
|
|
#
|
|
# align = hAlign vAlign w h child \
|
|
# x = hAlign
|
|
# | Start \ 0
|
|
# | Center \ (w - child.w) / 2
|
|
# | End \ w - child.w;
|
|
# y = vAlign
|
|
# | Start \ 0
|
|
# | Center \ (h - child.h) / 2
|
|
# | End \ h - child.h;
|
|
# { ui = ui.positioned { x = x, y = y, child = child.ui }, w = w, h = h };
|
|
#
|
|
# @
|
|
#
|
|
# testApp = name \
|
|
# { view = ctx \
|
|
# col { w = ctx.w, h = ctx.h } [
|
|
# Fixed 40 (w h \ box { w = w, h = h, color = "red", child = text "Header" }),
|
|
# Flex 1 (w h \ box { w = w, h = h, color = "#1a1a2e", child = text "Main content" }),
|
|
# Fixed 30 (w h \ box { w = w, h = h, color = "blue", child = text "Footer" }),
|
|
# ]
|
|
# ~ (e \ e.ui)
|
|
# };
|
|
#
|
|
# testApp2 = _ \
|
|
# { view = ctx \
|
|
# col { w = ctx.w, h = ctx.h } [
|
|
# Fixed 40 (w h \ (align Center Center w h (sizedText "Header")).ui),
|
|
# Flex 1 (w h \ (row { w = w, h = h } [
|
|
# Fixed 200 (w h \ box { w = w, h = h, color = "#2a2a3e", child = text "Sidebar" }),
|
|
# Flex 1 (w h \ box { w = w, h = h, color = "#1a1a2e", child = text "Main" }),
|
|
# ]).ui),
|
|
# Fixed 30 (w h \ (align Center Center w h (sizedText "Footer")).ui),
|
|
# ]
|
|
# ~ (e \ e.ui)
|
|
# };
|