diff --git a/java/com/facebook/yoga/YogaNative.kt b/java/com/facebook/yoga/YogaNative.kt index c00957b14d..9f09f26b09 100644 --- a/java/com/facebook/yoga/YogaNative.kt +++ b/java/com/facebook/yoga/YogaNative.kt @@ -311,6 +311,8 @@ public object YogaNative { @JvmStatic public external fun jni_YGNodeSetHasMeasureFuncJNI(nativePointer: Long, hasMeasureFunc: Boolean) + @JvmStatic public external fun jni_YGNodeSetNodeTypeJNI(nativePointer: Long, nodeType: Int) + @JvmStatic public external fun jni_YGNodeSetHasMinContentMeasureFuncJNI( nativePointer: Long, diff --git a/java/com/facebook/yoga/YogaNode.kt b/java/com/facebook/yoga/YogaNode.kt index 08b84232c8..754ed7a279 100644 --- a/java/com/facebook/yoga/YogaNode.kt +++ b/java/com/facebook/yoga/YogaNode.kt @@ -228,6 +228,17 @@ public abstract class YogaNode : YogaProps { abstract override fun setMeasureFunction(measureFunction: YogaMeasureFunction?) + /** + * Sets the node type, which selects how layout is rounded to the pixel grid: a + * [YogaNodeType.TEXT] node's fractional size is rounded up and its position down, so its content + * is not truncated. [setMeasureFunction] sets it ([YogaNodeType.TEXT] for a function, + * [YogaNodeType.DEFAULT] for null), so call this after it. + * + * Does nothing by default, so a subclass built against an earlier [YogaNode] keeps working, with + * the rounding [setMeasureFunction] selected. + */ + public open fun setNodeType(nodeType: YogaNodeType) {} + abstract override fun setMinContentMeasureFunction(measureFunction: YogaMeasureFunction?) abstract override fun setMinContentWidth(minContentWidth: Float) diff --git a/java/com/facebook/yoga/YogaNodeJNIBase.kt b/java/com/facebook/yoga/YogaNodeJNIBase.kt index 484865c163..9e2b1fe14e 100644 --- a/java/com/facebook/yoga/YogaNodeJNIBase.kt +++ b/java/com/facebook/yoga/YogaNodeJNIBase.kt @@ -526,6 +526,10 @@ public abstract class YogaNodeJNIBase : YogaNode, Cloneable { YogaNative.jni_YGNodeSetHasMeasureFuncJNI(nativePointer, measureFunction != null) } + override fun setNodeType(nodeType: YogaNodeType) { + YogaNative.jni_YGNodeSetNodeTypeJNI(nativePointer, nodeType.intValue()) + } + override fun setMinContentMeasureFunction(measureFunction: YogaMeasureFunction?) { this.minContentMeasureFunction = measureFunction YogaNative.jni_YGNodeSetHasMinContentMeasureFuncJNI(nativePointer, measureFunction != null) diff --git a/java/jni/YGJNIVanilla.cpp b/java/jni/YGJNIVanilla.cpp index f720c20840..d3d39a704c 100644 --- a/java/jni/YGJNIVanilla.cpp +++ b/java/jni/YGJNIVanilla.cpp @@ -685,6 +685,15 @@ static void jni_YGNodeSetHasMeasureFuncJNI( static_cast(hasMeasureFunc) ? YGJNIMeasureFunc : nullptr); } +static void jni_YGNodeSetNodeTypeJNI( + JNIEnv* /*env*/, + jobject /*obj*/, + jlong nativePointer, + jint nodeType) { + YGNodeSetNodeType( + _jlong2YGNodeRef(nativePointer), static_cast(nodeType)); +} + static YGSize YGJNIMinContentMeasureFunc( YGNodeConstRef node, float width, @@ -1139,6 +1148,7 @@ static JNINativeMethod methods[] = { "(JZ)V", (void*)jni_YGNodeSetHasMeasureFuncJNI}, // NOLINTBEGIN(cppcoreguidelines-pro-type-cstyle-cast) + {"jni_YGNodeSetNodeTypeJNI", "(JI)V", (void*)jni_YGNodeSetNodeTypeJNI}, {"jni_YGNodeSetHasMinContentMeasureFuncJNI", "(JZ)V", (void*)jni_YGNodeSetHasMinContentMeasureFuncJNI}, diff --git a/java/tests/com/facebook/yoga/YogaNodeTest.kt b/java/tests/com/facebook/yoga/YogaNodeTest.kt index 0bf494c09a..bd1ace12ca 100644 --- a/java/tests/com/facebook/yoga/YogaNodeTest.kt +++ b/java/tests/com/facebook/yoga/YogaNodeTest.kt @@ -313,6 +313,58 @@ class YogaNodeTest { assertNull(node.data) } + @Test + fun testMeasuredNodeRoundsAsText() { + assertEquals(34, firstThirdWidth { it.setMeasureFunction(fillsWidth) }) + } + + @Test + fun testSetNodeTypeDefaultRoundsMeasuredNodeToNearest() { + assertEquals( + 33, + firstThirdWidth { + it.setMeasureFunction(fillsWidth) + it.setNodeType(YogaNodeType.DEFAULT) + }, + ) + } + + @Test + fun testSetMeasureFunctionResetsNodeTypeToText() { + assertEquals( + 34, + firstThirdWidth { + it.setMeasureFunction(fillsWidth) + it.setNodeType(YogaNodeType.DEFAULT) + it.setMeasureFunction(fillsWidth) + }, + ) + } + + /** No width of its own, so flexing gives it its third. */ + private val fillsWidth = YogaMeasureFunction { _, _, _, _, _ -> YogaMeasureOutput.make(0f, 10f) } + + /** + * The rounded width of the first of three nodes flexed to a third of a 100 wide row each, set up + * by [configure]: its right edge is at 33.33, which text rounding takes up to 34. + */ + private fun firstThirdWidth(configure: (YogaNode) -> Unit): Int { + val root = createNode() + root.flexDirection = YogaFlexDirection.ROW + root.setWidth(100f) + root.setHeight(10f) + for (i in 0 until 3) { + val child = createNode() + child.flexGrow = 1f + if (i == 0) { + configure(child) + } + root.addChildAt(child, i) + } + root.calculateLayout(YogaConstants.UNDEFINED, YogaConstants.UNDEFINED) + return root.getChildAt(0).layoutWidth.toInt() + } + private fun createNode(): YogaNode = mNodeFactory.create() private fun createNode(config: YogaConfig): YogaNode = mNodeFactory.create(config)