You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
cg/src/cg/03-ui-components.cg

335 lines
9.8 KiB
Plaintext

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 \
textSize = ui.measureText config.label;
defaults = {
h = 30,
w = textSize + 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 = ui.text { content = config.label, color = c.textColor }
}
]
}
};
# inputButton (button that turns into an input)
inputButton = config \
textSize = ui.measureText config.label;
defaults = {
h = 30,
w = textSize + 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 = 8,
child = ui.text { content = config.label, color = c.textColor }
}
]
}
}
};
# 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;
vBarWidth = max 20 (c.w * c.w / c.totalWidth);
vBarX = 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 = c.h - 4,
y = hBarX,
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 < 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);
widthNext = ui.measureText (slice text 0 (index + 1));
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;
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 } }
},
(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 = 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
};