diff --git a/src/fidget.nim b/src/fidget.nim index 4c439815..668924f6 100644 --- a/src/fidget.nim +++ b/src/fidget.nim @@ -41,7 +41,6 @@ proc preNode(kind: NodeKind, id: string) = current.textStyle = parent.textStyle current.cursorColor = parent.cursorColor current.highlightColor = parent.highlightColor - current.transparency = parent.transparency nodeStack.add(current) inc parent.diffIndex @@ -273,13 +272,6 @@ proc orgBox*(x, y, w, h: int|float32|float32) = current.orgBox.w = float32 w current.orgBox.h = float32 h -proc box*(x, y, w, h: float32) = - ## Sets the box dimensions. - current.box.x = x - current.box.y = y - current.box.w = w - current.box.h = h - proc box*( x: int|float32|float64, y: int|float32|float64, @@ -287,14 +279,53 @@ proc box*( h: int|float32|float64 ) = ## Sets the box dimensions with integers + current.box.x = x.float32 + current.box.y = y.float32 + current.box.w = w.float32 + current.box.h = h.float32 ## Always set box before orgBox when doing constraints. - box(float32 x, float32 y, float32 w, float32 h) orgBox(float32 x, float32 y, float32 w, float32 h) proc box*(rect: Rect) = ## Sets the box dimensions with integers box(rect.x, rect.y, rect.w, rect.h) +proc x*(x: int|float32|float64) = + current.box.x = x.float32 + current.orgBox.x = x.float32 + +proc y*(y: int|float32|float64) = + current.box.y = y.float32 + current.orgBox.y = y.float32 + +proc w*(w: int|float32|float64) = + current.box.w = w.float32 + current.orgBox.w = w.float32 + +proc h*(h: int|float32|float64) = + current.box.h = h.float32 + current.orgBox.h = h.float32 + +proc xy*(x, y: int|float32|float64) = + current.box.x = x.float32 + current.box.y = y.float32 + current.orgBox.x = x.float32 + current.orgBox.y = y.float32 + +proc xy*(xy: Vec2) = + current.box.xy = xy + current.orgBox.xy = xy + +proc wh*(w, h: int|float32|float64) = + current.box.w = w.float32 + current.box.h = h.float32 + current.orgBox.w = w.float32 + current.orgBox.h = h.float32 + +proc wh*(wh: Vec2) = + current.box.wh = wh + current.orgBox.wh = wh + proc rotation*(rotationInDeg: float32) = ## Sets rotation in degrees. current.rotation = rotationInDeg @@ -412,6 +443,14 @@ proc layoutAlign*(mode: LayoutAlign) = ## Set the layout alignment mode. current.layoutAlign = mode +proc layoutWeight*(weight: float32) = + ## Set the layout weight. + current.layoutWeight = weight + +proc wrapContent*(flag: bool) = + ## Set the whether wrap content or not. + current.wrapContent = flag + proc layout*(mode: LayoutMode) = ## Set the layout mode. current.layoutMode = mode @@ -448,6 +487,10 @@ template binding*(stringVariable: untyped) = keyboard.focus(current) onClickOutside: keyboard.unFocus(current) + refresh() + if not current.multiline and buttonRelease[ENTER]: + keyboard.unFocus(current) + refresh() onInput: if stringVariable != keyboard.input: stringVariable = keyboard.input diff --git a/src/fidget/common.nim b/src/fidget/common.nim index 5c70be59..4e9d0efc 100644 --- a/src/fidget/common.nim +++ b/src/fidget/common.nim @@ -3,7 +3,7 @@ import chroma, input, sequtils, tables, vmath, json, bumpy when defined(js): import dom2, html/ajax else: - import typography, typography/textboxes, tables, asyncfutures + import typography, typography/textboxes, asyncfutures const clearColor* = color(0, 0, 0, 0) @@ -31,8 +31,9 @@ type TextAutoResize* = enum ## Should text element resize and how. tsNone - tsWidthAndHeight + tsWidth tsHeight + tsWidthAndHeight TextStyle* = object ## Holder for text styles. @@ -124,6 +125,8 @@ type constraintsHorizontal*: Constraint constraintsVertical*: Constraint layoutAlign*: LayoutAlign + layoutWeight*: float32 + wrapContent*: bool layoutMode*: LayoutMode counterAxisSizingMode*: CounterAxisSizingMode horizontalPadding*: float32 @@ -206,6 +209,7 @@ var mouse* = Mouse() keyboard* = Keyboard() requestedFrame*: bool + requestedTick*: bool numNodes*: int popupActive*: bool inPopup*: bool @@ -286,7 +290,7 @@ proc resetToDefault*(node: Node)= # node.screenBox = rect(0,0,0,0) node.textOffset = vec2(0, 0) node.fill = color(0, 0, 0, 0) - node.transparency = 0 + node.transparency = 1 node.strokeWeight = 0 node.stroke = color(0, 0, 0, 0) node.zLevel = 0 @@ -313,6 +317,8 @@ proc resetToDefault*(node: Node)= node.constraintsVertical = cMin node.layoutAlign = laMin node.layoutMode = lmNone + node.layoutWeight = 0.0 + node.wrapContent = false node.counterAxisSizingMode = csAuto node.horizontalPadding = 0 node.verticalPadding = 0 @@ -329,6 +335,7 @@ proc setupRoot*() = root.uid = newUId() root.highlightColor = parseHtmlColor("#3297FD") root.cursorColor = rgba(0, 0, 0, 255).color + root.transparency = 1 nodeStack = @[root] current = root root.diffIndex = 0 @@ -368,10 +375,6 @@ proc consume*(mouse: Mouse) = buttonPress[MOUSE_LEFT] = false proc computeLayout*(parent, node: Node) = - ## Computes constraints and auto-layout. - for n in node.nodes: - computeLayout(node, n) - # Constraints code. case node.constraintsVertical: of cMin: discard @@ -412,6 +415,9 @@ proc computeLayout*(parent, node: Node) = of tsNone: # Fixed sized text node. discard + of tsWidth: + # Text will grow down. + node.box.w = node.textLayoutWidth of tsHeight: # Text will grow down. node.box.h = node.textLayoutHeight @@ -420,6 +426,13 @@ proc computeLayout*(parent, node: Node) = node.box.w = node.textLayoutWidth node.box.h = node.textLayoutHeight + ## Computes constraints and auto-layout. + for n in node.nodes: + if node.layoutMode != lmNone and + (n.layoutWeight != 0 or n.layoutAlign == laStretch): + continue + computeLayout(node, n) + # Auto-layout code. if node.layoutMode == lmVertical: if node.counterAxisSizingMode == csAuto: @@ -429,13 +442,33 @@ proc computeLayout*(parent, node: Node) = if n.layoutAlign != laStretch: maxW = max(maxW, n.box.w) node.box.w = maxW + node.horizontalPadding * 2 - + + var remainHeight = node.box.h - node.verticalPadding * 2 - node.itemSpacing * (node.nodes.len - 1).float32 + var totalWeight: float32 + + for i, n in node.nodes.pairs: + # set width of stretch + if n.layoutAlign == laStretch: + n.box.w = node.box.w - node.horizontalPadding * 2 + if n.id == "16": echo n.box.w + if n.layoutWeight == 0: computeLayout(node, n) + # record weight + if n.layoutWeight == 0: remainHeight -= n.box.h + else: totalWeight += n.layoutWeight + var at = 0.0 at += node.verticalPadding - for i, n in node.nodes.reversePairs: - if i > 0: - at += node.itemSpacing + for i, n in node.nodes: + if i > 0: at += node.itemSpacing + n.box.y = at + + if n.layoutWeight != 0: + n.box.h = + if remainHeight <= 0: 0.0 + else: remainHeight * n.layoutWeight / totalWeight + computeLayout(node, n) + case n.layoutAlign: of laMin: n.box.x = node.horizontalPadding @@ -445,12 +478,10 @@ proc computeLayout*(parent, node: Node) = n.box.x = node.box.w - n.box.w - node.horizontalPadding of laStretch: n.box.x = node.horizontalPadding - n.box.w = node.box.w - node.horizontalPadding * 2 - # Redo the layout for child node. - computeLayout(node, n) + at += n.box.h at += node.verticalPadding - node.box.h = at + if node.wrapContent: node.box.h = at if node.layoutMode == lmHorizontal: if node.counterAxisSizingMode == csAuto: @@ -461,12 +492,32 @@ proc computeLayout*(parent, node: Node) = maxH = max(maxH, n.box.h) node.box.h = maxH + node.verticalPadding * 2 + var remainWidth = node.box.w - node.horizontalPadding * 2 - node.itemSpacing * (node.nodes.len - 1).float32 + var totalWeight: float32 + + for i, n in node.nodes.pairs: + # set width of stretch + if n.layoutAlign == laStretch: + n.box.h = node.box.h - node.verticalPadding * 2 + if n.layoutWeight == 0: computeLayout(node, n) + # record weight + if n.layoutWeight == 0: remainWidth -= n.box.w + else: totalWeight += n.layoutWeight + + var at = 0.0 at += node.horizontalPadding - for i, n in node.nodes.reversePairs: - if i > 0: - at += node.itemSpacing + for i, n in node.nodes: + if i > 0: at += node.itemSpacing + n.box.x = at + + if n.layoutWeight != 0: + n.box.w = + if remainWidth <= 0: 0.0 + else: remainWidth * n.layoutWeight / totalWeight + computeLayout(node, n) + case n.layoutAlign: of laMin: n.box.y = node.verticalPadding @@ -476,12 +527,10 @@ proc computeLayout*(parent, node: Node) = n.box.y = node.box.h - n.box.h - node.verticalPadding of laStretch: n.box.y = node.verticalPadding - n.box.h = node.box.h - node.verticalPadding * 2 - # Redo the layout for child node. - computeLayout(node, n) + at += n.box.w at += node.horizontalPadding - node.box.w = at + if node.wrapContent: node.box.w = at proc computeScreenBox*(parent, node: Node) = ## Setups screenBoxes for the whole tree. diff --git a/src/fidget/opengl/base.nim b/src/fidget/opengl/base.nim index 70c186ca..f75dc205 100644 --- a/src/fidget/opengl/base.nim +++ b/src/fidget/opengl/base.nim @@ -47,6 +47,8 @@ var cursorGrab*: CursorHandle cursorNSResize*: CursorHandle +proc getWindow*(): staticglfw.Window {.inline.} = window + proc setCursor*(cursor: CursorHandle) = echo "set cursor" window.setCursor(cursor) @@ -130,18 +132,17 @@ proc updateLoop*(poll = true) = of RepaintOnEvent: if poll: pollEvents() - if not requestedFrame or minimized: - # Only repaint when necessary - when not defined(emscripten): - sleep(16) + if minimized or not (requestedFrame or (tickMain != nil and requestedTick)): return - requestedFrame = false preInput() - if tickMain != nil: + if tickMain != nil and requestedTick: + requestedTick = false preTick() tickMain() postTick() - drawAndSwap() + if requestedFrame: + requestedFrame = false + drawAndSwap() postInput() of RepaintOnFrame: @@ -247,13 +248,13 @@ proc onSetKey( textBox.delete(shift) of LETTER_C: # copy if ctrl: - base.window.setClipboardString(textBox.copy()) + base.window.setClipboardString(textBox.copy().cstring) of LETTER_V: # paste if ctrl: textBox.paste($base.window.getClipboardString()) of LETTER_X: # cut if ctrl: - base.window.setClipboardString(textBox.cut()) + base.window.setClipboardString(textBox.cut().cstring) of LETTER_A: # select all if ctrl: textBox.selectAll() @@ -302,7 +303,7 @@ proc onSetCharCallback(window: staticglfw.Window, character: cuint) {.cdecl.} = keyboard.state = KeyState.Press keyboard.keyString = Rune(character).toUTF8() -proc start*(openglVersion: (int, int), msaa: MSAA, mainLoopMode: MainLoopMode) = +proc start*(openglVersion: (int, int), msaa: MSAA, mainLoopMode: MainLoopMode, hints: openArray[array[0 .. 1, int]]) = if init() == 0: quit("Failed to intialize GLFW.") @@ -317,6 +318,8 @@ proc start*(openglVersion: (int, int), msaa: MSAA, mainLoopMode: MainLoopMode) = windowHint(CONTEXT_VERSION_MAJOR, openglVersion[0].cint) windowHint(CONTEXT_VERSION_MINOR, openglVersion[1].cint) + for hint in hints: windowHint(hint[0].cint, hint[1].cint) + if fullscreen: let monitor = getPrimaryMonitor() diff --git a/src/fidget/opengl/context.nim b/src/fidget/opengl/context.nim index a0b25a2f..db9f8841 100644 --- a/src/fidget/opengl/context.nim +++ b/src/fidget/opengl/context.nim @@ -424,7 +424,7 @@ proc getOrLoadImageRect(ctx: Context, imagePath: string | Hash): Rect = filePath.add ".png" if hash(filePath) notin ctx.entries: # Need to load imagePath, check to see if the .flippy file is around - echo "[load] ", filePath + # echo "[load] ", filePath if not fileExists(filePath): raise newException(Exception, &"Image '{filePath}' not found") let flippyFilePath = filePath.changeFileExt(".flippy") @@ -519,14 +519,16 @@ proc fillRect*(ctx: Context, rect: Rect, color: Color) = uvRect.xy + uvRect.wh / 2, color ) -proc fillRoundedRect*(ctx: Context, rect: Rect, color: Color, radius: float32) = +proc fillRoundedRect*(ctx: Context, rect: Rect, color: Color, nw, ne, se, sw: float32) = # TODO: Make this a 9 patch - let radius = min(radius, min(rect.w/2, rect.h/2)) let hash = hash(( 6118, rect.w.int, rect.h.int, - (radius*100).int + (nw*100).int, + (ne*100).int, + (se*100).int, + (sw*100).int, )) let @@ -539,7 +541,7 @@ proc fillRoundedRect*(ctx: Context, rect: Rect, color: Color, radius: float32) = c.fillStyle = rgba(255, 255, 255, 255) c.fillRoundedRect( rect(0, 0, rect.w, rect.h), - radius + nw, ne, se, sw ) ctx.putImage(hash, image) @@ -555,15 +557,17 @@ proc fillRoundedRect*(ctx: Context, rect: Rect, color: Color, radius: float32) = ) proc strokeRoundedRect*( - ctx: Context, rect: Rect, color: Color, weight: float32, radius: float32 + ctx: Context, rect: Rect, color: Color, weight: float32, nw, ne, se, sw: float32 ) = - let radius = min(radius, min(rect.w/2, rect.h/2)) # TODO: Make this a 9 patch let hash = hash(( 8349, rect.w.int, rect.h.int, - (radius*100).int + (nw*100).int, + (ne*100).int, + (se*100).int, + (sw*100).int, )) let @@ -577,7 +581,7 @@ proc strokeRoundedRect*( c.lineWidth = weight c.strokeRoundedRect( rect(weight / 2, weight / 2, rect.w - weight, rect.h - weight), - radius + nw, ne, se, sw ) ctx.putImage(hash, image) let diff --git a/src/fidget/openglbackend.nim b/src/fidget/openglbackend.nim index e4c7c481..a97bc1b4 100644 --- a/src/fidget/openglbackend.nim +++ b/src/fidget/openglbackend.nim @@ -39,6 +39,7 @@ computeTextLayout = proc(node: Node) = hAlignMode(node.textStyle.textAlignHorizontal), vAlignMode(node.textStyle.textAlignVertical), clip = false, + wrap = false, boundsMin = boundsMin, boundsMax = boundsMax ) @@ -49,6 +50,9 @@ proc refresh*() = ## Request the screen be redrawn requestedFrame = true +proc doTick*() = + requestedTick = true + proc focus*(keyboard: Keyboard, node: Node) = if keyboard.focusNode != node: keyboard.onUnFocusNode = keyboard.focusNode @@ -67,7 +71,7 @@ proc focus*(keyboard: Keyboard, node: Node) = hAlignMode(node.textStyle.textAlignHorizontal), vAlignMode(node.textStyle.textAlignVertical), node.multiline, - worldWrap = true, + worldWrap = node.multiline, ) textBox.editable = node.editableText textBox.scrollable = true @@ -193,7 +197,7 @@ proc drawText(node: Node) = let glyphOffset = glyphOffsets[hashFill] - charPos = vec2(pos.rect.x + glyphOffset.x, pos.rect.y + glyphOffset.y) + charPos = pos.rect.xy + glyphOffset if node.strokeWeight > 0 and node.stroke.a > 0: ctx.drawImage( @@ -247,7 +251,11 @@ proc draw*(node: Node) = ctx.fillRoundedRect(rect( 0, 0, node.screenBox.w, node.screenBox.h - ), rgba(255, 0, 0, 255).color, node.cornerRadius[0]) + ), rgba(255, 0, 0, 255).color, + node.cornerRadius[0], + node.cornerRadius[1], + node.cornerRadius[2], + node.cornerRadius[3]) else: ctx.fillRect(rect( 0, 0, @@ -264,7 +272,11 @@ proc draw*(node: Node) = ctx.fillRoundedRect(rect( 0, 0, node.screenBox.w, node.screenBox.h - ), node.fill, node.cornerRadius[0]) + ), node.fill, + node.cornerRadius[0], + node.cornerRadius[1], + node.cornerRadius[2], + node.cornerRadius[3]) else: ctx.fillRect(rect( 0, 0, @@ -275,11 +287,18 @@ proc draw*(node: Node) = ctx.strokeRoundedRect(rect( 0, 0, node.screenBox.w, node.screenBox.h - ), node.stroke, node.strokeWeight, node.cornerRadius[0]) + ), node.stroke, node.strokeWeight, + node.cornerRadius[0], + node.cornerRadius[1], + node.cornerRadius[2], + node.cornerRadius[3]) if node.imageName != "": let path = dataDir / node.imageName - ctx.drawImage(path, size = vec2(node.screenBox.w, node.screenBox.h)) + ctx.drawImage( + path, + color = color(1, 1, 1, node.transparency), + size = vec2(node.screenBox.w, node.screenBox.h)) ctx.restoreTransform() @@ -298,11 +317,12 @@ proc setupFidget( msaa: MSAA, mainLoopMode: MainLoopMode, pixelate: bool, - forcePixelScale: float32 + forcePixelScale: float32, + windowHints: openArray[array[0 .. 1, int]] ) = pixelScale = forcePixelScale - base.start(openglVersion, msaa, mainLoopMode) + base.start(openglVersion, msaa, mainLoopMode, windowHints) setWindowTitle(windowTitle) ctx = newContext(pixelate = pixelate, pixelScale = pixelScale) requestedFrame = true @@ -388,7 +408,8 @@ proc startFidget*( msaa = msaaDisabled, mainLoopMode: MainLoopMode = RepaintOnEvent, pixelate = false, - pixelScale = 1.0 + pixelScale = 1.0, + windowHints: openArray[array[0 .. 1, int]] = [] ) = ## Starts Fidget UI library common.fullscreen = fullscreen @@ -397,7 +418,7 @@ proc startFidget*( drawMain = draw tickMain = tick loadMain = load - setupFidget(openglVersion, msaa, mainLoopMode, pixelate, pixelScale) + setupFidget(openglVersion, msaa, mainLoopMode, pixelate, pixelScale, windowHints) mouse.pixelScale = pixelScale when defined(emscripten): # Emscripten can't block so it will call this callback instead.