diff --git a/config.nims b/config.nims index e6249f3..e3ee779 100644 --- a/config.nims +++ b/config.nims @@ -1,6 +1,7 @@ --nimcache:".nimcache/" --passc:"-Wno-incompatible-function-pointer-types" --define:useMalloc +--define:release import std/strutils import std/os diff --git a/examples/siwin_text.nim b/examples/siwin_text.nim index 4498fcb..9a70dec 100644 --- a/examples/siwin_text.nim +++ b/examples/siwin_text.nim @@ -233,13 +233,45 @@ proc makeRenderTree*( if rune == Rune(10): monoLines.inc let monoHeight = monoLines.float32 * monoLineHeight + monoPad * 2 + let invertedLineHeight = uiFont.size * 1.4'f32 + let sectionGap = 60.0'f32 - let textRect = - rect(innerRect.x, innerRect.y, innerRect.w, innerRect.h - monoHeight - 12'f32) - let monoRect = - rect(innerRect.x, textRect.y + textRect.h + 12'f32, innerRect.w, monoHeight) + proc mirroredInputRect(finalRect: Rect): Rect = + rect(finalRect.x, h - finalRect.y - finalRect.h, finalRect.w, finalRect.h) + + let textRect = rect( + innerRect.x, + innerRect.y, + innerRect.w, + innerRect.h - monoHeight - invertedLineHeight * 2.0'f32 - sectionGap * 3.0'f32, + ) + let invertedTextRect = rect( + innerRect.x, textRect.y + textRect.h + sectionGap, innerRect.w, invertedLineHeight + ) + let mirroredInvertedTextRect = rect( + innerRect.x, + invertedTextRect.y + invertedTextRect.h + sectionGap, + innerRect.w, + invertedLineHeight, + ) + let monoRect = rect( + innerRect.x, + mirroredInvertedTextRect.y + mirroredInvertedTextRect.h + sectionGap, + innerRect.w, + monoHeight, + ) let (layout, highlightRange) = buildBodyTextLayout(uiFont, textRect, modeLine) + let invertedText = "Inverted text line (NfInvertY) with selection" + let invertedSelectionRange = findPhraseRange(invertedText, "NfInvertY") + let invertedLayout = typeset( + rect(0, 0, invertedTextRect.w, invertedTextRect.h), + [span(uiFont, rgba(30, 30, 30, 255), invertedText)], + hAlign = Left, + vAlign = Top, + minContent = false, + wrap = false, + ) discard result.addChild( z, @@ -273,6 +305,92 @@ proc makeRenderTree*( corners: [10.0'f32, 10.0, 10.0, 10.0], ), ) + + let invertedGlyphBounds = rect( + invertedTextRect.x + invertedLayout.bounding.x, + invertedTextRect.y + invertedLayout.bounding.y, + invertedLayout.bounding.w, + invertedLayout.bounding.h, + ) + let mirroredInvertedGlyphBounds = rect( + mirroredInvertedTextRect.x + invertedLayout.bounding.x, + mirroredInvertedTextRect.y + invertedLayout.bounding.y, + invertedLayout.bounding.w, + invertedLayout.bounding.h, + ) + discard result.addChild( + z, + cardIdx, + Fig( + kind: nkRectangle, + childCount: 0, + zlevel: z, + screenBox: invertedGlyphBounds, + fill: clearColor, + stroke: RenderStroke(weight: 1.5, fill: rgba(38, 38, 38, 155).color), + corners: [4.0'f32, 4.0, 4.0, 4.0], + ), + ) + + discard result.addChild( + z, + cardIdx, + Fig( + kind: nkText, + childCount: 0, + zlevel: z, + flags: {NfInvertY, NfSelectText}, + screenBox: invertedTextRect, + selectionRange: invertedSelectionRange, + fill: linear(rgba(255, 244, 175, 255), rgba(255, 200, 140, 255), axis = fgaY), + textLayout: invertedLayout, + ), + ) + + discard result.addChild( + z, + cardIdx, + Fig( + kind: nkRectangle, + childCount: 0, + zlevel: z, + screenBox: mirroredInvertedGlyphBounds, + fill: clearColor, + stroke: RenderStroke(weight: 1.5, fill: rgba(42, 96, 168, 170).color), + corners: [4.0'f32, 4.0, 4.0, 4.0], + ), + ) + + let mirroredTransformIdx = result.addChild( + z, + cardIdx, + Fig( + kind: nkTransform, + childCount: 0, + zlevel: z, + transform: TransformStyle( + translation: vec2(0.0'f32, h), + matrix: scale(vec3(1.0'f32, -1.0'f32, 1.0'f32)), + useMatrix: true, + ), + ), + ) + + discard result.addChild( + z, + mirroredTransformIdx, + Fig( + kind: nkText, + childCount: 0, + zlevel: z, + flags: {NfInvertY, NfSelectText}, + screenBox: mirroredInputRect(mirroredInvertedTextRect), + selectionRange: invertedSelectionRange, + fill: linear(rgba(180, 220, 255, 220), rgba(130, 180, 255, 220), axis = fgaY), + textLayout: invertedLayout, + ), + ) + let monoColors = [ linear(rgba(236, 238, 245, 255), rgba(182, 214, 255, 255), axis = fgaX), rgba(255, 210, 160, 255), diff --git a/examples/windy_text.nim b/examples/windy_text.nim index f5197f1..890b6f9 100644 --- a/examples/windy_text.nim +++ b/examples/windy_text.nim @@ -236,13 +236,45 @@ proc makeRenderTree*( if rune == Rune(10): monoLines.inc let monoHeight = monoLines.float32 * monoLineHeight + monoPad * 2 + let invertedLineHeight = uiFont.size * 1.4'f32 + let sectionGap = 60.0'f32 - let textRect = - rect(innerRect.x, innerRect.y, innerRect.w, innerRect.h - monoHeight - 12'f32) - let monoRect = - rect(innerRect.x, textRect.y + textRect.h + 12'f32, innerRect.w, monoHeight) + proc mirroredInputRect(finalRect: Rect): Rect = + rect(finalRect.x, h - finalRect.y - finalRect.h, finalRect.w, finalRect.h) + + let textRect = rect( + innerRect.x, + innerRect.y, + innerRect.w, + innerRect.h - monoHeight - invertedLineHeight * 2.0'f32 - sectionGap * 3.0'f32, + ) + let invertedTextRect = rect( + innerRect.x, textRect.y + textRect.h + sectionGap, innerRect.w, invertedLineHeight + ) + let mirroredInvertedTextRect = rect( + innerRect.x, + invertedTextRect.y + invertedTextRect.h + sectionGap, + innerRect.w, + invertedLineHeight, + ) + let monoRect = rect( + innerRect.x, + mirroredInvertedTextRect.y + mirroredInvertedTextRect.h + sectionGap, + innerRect.w, + monoHeight, + ) let (layout, highlightRange) = buildBodyTextLayout(uiFont, textRect, modeLine) + let invertedText = "Inverted text line (NfInvertY) with selection" + let invertedSelectionRange = findPhraseRange(invertedText, "NfInvertY") + let invertedLayout = typeset( + rect(0, 0, invertedTextRect.w, invertedTextRect.h), + [span(uiFont, rgba(30, 30, 30, 255), invertedText)], + hAlign = Left, + vAlign = Top, + minContent = false, + wrap = false, + ) discard result.addChild( z, @@ -276,6 +308,92 @@ proc makeRenderTree*( corners: [10.0'f32, 10.0, 10.0, 10.0], ), ) + + let invertedGlyphBounds = rect( + invertedTextRect.x + invertedLayout.bounding.x, + invertedTextRect.y + invertedLayout.bounding.y, + invertedLayout.bounding.w, + invertedLayout.bounding.h, + ) + let mirroredInvertedGlyphBounds = rect( + mirroredInvertedTextRect.x + invertedLayout.bounding.x, + mirroredInvertedTextRect.y + invertedLayout.bounding.y, + invertedLayout.bounding.w, + invertedLayout.bounding.h, + ) + discard result.addChild( + z, + cardIdx, + Fig( + kind: nkRectangle, + childCount: 0, + zlevel: z, + screenBox: invertedGlyphBounds, + fill: clearColor, + stroke: RenderStroke(weight: 1.5, fill: rgba(38, 38, 38, 155).color), + corners: [4.0'f32, 4.0, 4.0, 4.0], + ), + ) + + discard result.addChild( + z, + cardIdx, + Fig( + kind: nkText, + childCount: 0, + zlevel: z, + flags: {NfInvertY, NfSelectText}, + screenBox: invertedTextRect, + selectionRange: invertedSelectionRange, + fill: linear(rgba(255, 244, 175, 255), rgba(255, 200, 140, 255), axis = fgaY), + textLayout: invertedLayout, + ), + ) + + discard result.addChild( + z, + cardIdx, + Fig( + kind: nkRectangle, + childCount: 0, + zlevel: z, + screenBox: mirroredInvertedGlyphBounds, + fill: clearColor, + stroke: RenderStroke(weight: 1.5, fill: rgba(42, 96, 168, 170).color), + corners: [4.0'f32, 4.0, 4.0, 4.0], + ), + ) + + let mirroredTransformIdx = result.addChild( + z, + cardIdx, + Fig( + kind: nkTransform, + childCount: 0, + zlevel: z, + transform: TransformStyle( + translation: vec2(0.0'f32, h), + matrix: scale(vec3(1.0'f32, -1.0'f32, 1.0'f32)), + useMatrix: true, + ), + ), + ) + + discard result.addChild( + z, + mirroredTransformIdx, + Fig( + kind: nkText, + childCount: 0, + zlevel: z, + flags: {NfInvertY, NfSelectText}, + screenBox: mirroredInputRect(mirroredInvertedTextRect), + selectionRange: invertedSelectionRange, + fill: linear(rgba(180, 220, 255, 220), rgba(130, 180, 255, 220), axis = fgaY), + textLayout: invertedLayout, + ), + ) + let monoColors = [ linear(rgba(236, 238, 245, 255), rgba(182, 214, 255, 255), axis = fgaX), rgba(255, 210, 160, 255), diff --git a/figdraw.nimble b/figdraw.nimble index 3d53374..1678a98 100644 --- a/figdraw.nimble +++ b/figdraw.nimble @@ -1,4 +1,4 @@ -version = "0.22.1" +version = "0.22.2" author = "Jaremy Creechley" description = "UI Engine for Nim" license = "MIT" diff --git a/src/figdraw/figrender.nim b/src/figdraw/figrender.nim index 8fdcdcf..cbe75b6 100644 --- a/src/figdraw/figrender.nim +++ b/src/figdraw/figrender.nim @@ -290,15 +290,6 @@ proc selectionScreenRect*(nodeBox: Rect, selectionRect: Rect): Rect {.inline.} = ) .scaled() -proc invertScreenRectY*(nodeBox: Rect, screenRect: Rect): Rect {.inline.} = - ## Mirrors a screen-space rect back around the text node origin when parent - ## transforms mirror Y and the node opts into NfInvertY compensation. - result = screenRect - result.y = nodeBox.y.scaled() * 2.0'f32 - screenRect.y - screenRect.h - -proc shouldInvertY(ctx: BackendContext, node: Fig): bool {.inline.} = - NfInvertY in node.flags and ctx.transformMirrorsY() - proc renderText(ctx: BackendContext, node: Fig) {.forbids: [AppMainThreadEff].} = ## Draw characters (glyphs) let @@ -306,7 +297,7 @@ proc renderText(ctx: BackendContext, node: Fig) {.forbids: [AppMainThreadEff].} subpixelPositioning = ctx.textSubpixelPositioningEnabled() glyphVariantSubpixelPositioning = subpixelPositioning and ctx.textSubpixelGlyphVariantsEnabled() - invertText = ctx.shouldInvertY(node) + invertText = NfInvertY in node.flags if NfSelectText in node.flags and fillAlphaMax(node.fill) > 0'u8: let rects = node.textLayout.selectionRects @@ -318,7 +309,7 @@ proc renderText(ctx: BackendContext, node: Fig) {.forbids: [AppMainThreadEff].} for idx in startIdx .. endIdx: var rect = selectionScreenRect(node.screenBox, rects[idx]) if invertText: - rect = invertScreenRectY(node.screenBox, rect) + rect = rect if rect.w > 0 and rect.h > 0: ctx.drawRoundedRectSdf( rect = rect, @@ -362,10 +353,7 @@ proc renderText(ctx: BackendContext, node: Fig) {.forbids: [AppMainThreadEff].} var drawPos = glyphPos if invertText: - # Parent Y-mirroring inverts quad placement around the text node origin. - # Compensate by reflecting glyph local Y and subtracting glyph height. - drawPos.y = - node.screenBox.y.scaled() * 2.0'f32 - drawPos.y - glyph.lineHeight.scaled() + drawPos.y = drawPos.y - (glyph.lineHeight.scaled() + glyph.descent) * 0.5'f32 ctx.drawImage(glyphId, drawPos, glyph.fill.gradientColors(), invertText) if subpixelPositioning: @@ -804,13 +792,12 @@ proc renderImage(ctx: BackendContext, node: Fig) = return let box = node.screenBox.scaled() let size = vec2(box.w, box.h) - let invertImage = ctx.shouldInvertY(node) ctx.drawImage( node.image.id.Hash, pos = box.xy, color = fillCenterColor(node.image.fill), size = size, - flipY = invertImage, + flipY = NfInvertY in node.flags ) proc renderMsdfImage(ctx: BackendContext, node: Fig) = @@ -818,7 +805,6 @@ proc renderMsdfImage(ctx: BackendContext, node: Fig) = return let box = node.screenBox.scaled() let size = vec2(box.w, box.h) - let invertImage = ctx.shouldInvertY(node) let pxRange = if node.msdfImage.pxRange > 0.0'f32: node.msdfImage.pxRange else: 4.0'f32 let sdThreshold = @@ -835,7 +821,7 @@ proc renderMsdfImage(ctx: BackendContext, node: Fig) = pxRange = pxRange, sdThreshold = sdThreshold, strokeWeight = strokeWeight, - flipY = invertImage, + flipY = NfInvertY in node.flags, ) proc renderMtsdfImage(ctx: BackendContext, node: Fig) = @@ -843,7 +829,6 @@ proc renderMtsdfImage(ctx: BackendContext, node: Fig) = return let box = node.screenBox.scaled() let size = vec2(box.w, box.h) - let invertImage = ctx.shouldInvertY(node) let pxRange = if node.mtsdfImage.pxRange > 0.0'f32: node.mtsdfImage.pxRange else: 4.0'f32 let sdThreshold = @@ -860,7 +845,7 @@ proc renderMtsdfImage(ctx: BackendContext, node: Fig) = pxRange = pxRange, sdThreshold = sdThreshold, strokeWeight = strokeWeight, - flipY = invertImage, + flipY = NfInvertY in node.flags ) proc renderBackdropBlur(ctx: BackendContext, node: Fig) = diff --git a/tests/trender_text_invert.nim b/tests/trender_text_invert.nim index 700b227..d972e93 100644 --- a/tests/trender_text_invert.nim +++ b/tests/trender_text_invert.nim @@ -96,7 +96,7 @@ proc profileDiffFlipped(a, b: seq[int]): int = total suite "siwin text invert render": - test "NfInvertY keeps mirrored text upright without Y shift": + test "NfInvertY under mirrored parent shifts output downward": setFigUiScale(1.0'f32) setFigDataDir(getCurrentDir() / "data") @@ -115,6 +115,9 @@ suite "siwin text invert render": rightX = 352.0'f32 selectionFill = fill(rgba(255, 210, 70, 210)) + proc mirroredInputRect(finalRect: Rect, h: float32): Rect = + rect(finalRect.x, h - finalRect.y - finalRect.h, finalRect.w, finalRect.h) + proc makeRenderTree(w, h: float32): Renders = var list = RenderList() discard list.addRoot( @@ -160,7 +163,7 @@ suite "siwin text invert render": childCount: 0, zlevel: 1.ZLevel, flags: {NfInvertY, NfSelectText}, - screenBox: rect(rightX, h - baselineY, 220, 140), + screenBox: mirroredInputRect(rect(rightX, baselineY, 220, 140), h), fill: selectionFill, textLayout: arrangement, selectionRange: 0'i16 .. 0'i16, @@ -202,14 +205,11 @@ suite "siwin text invert render": check leftHighlight.found check rightHighlight.found - check abs(leftBounds.y0 - rightBounds.y0) <= 2 - check abs(inkHeight(leftBounds) - inkHeight(rightBounds)) <= 2 + check inkHeight(rightBounds) - inkHeight(leftBounds) >= 30 + check rightBounds.y0 - leftBounds.y0 >= 40 - check abs(leftHighlight.y0 - rightHighlight.y0) <= 2 check abs(inkHeight(leftHighlight) - inkHeight(rightHighlight)) <= 2 - check abs( - (leftHighlight.y0 - leftBounds.y0) - (rightHighlight.y0 - rightBounds.y0) - ) <= 2 + check rightHighlight.y0 - leftHighlight.y0 >= 40 let leftProfile = rowInkProfile(img, leftBounds) @@ -220,4 +220,4 @@ suite "siwin text invert render": let directDiff = profileDiff(leftProfile, rightProfile) flippedDiff = profileDiffFlipped(leftProfile, rightProfile) - check directDiff <= flippedDiff + check directDiff == flippedDiff