Skip to content

Commit 18304ce

Browse files
pentiumaometa-codesync[bot]
authored andcommitted
YogaNode.setNodeType selects a measured node's pixel rounding (#2046)
Summary: Pull Request resolved: #2046 X-link: meta/litho#1093 **In one sentence.** A Java `YogaNode` with a measure function can now select Yoga's default pixel rounding, through a new `setNodeType` and its JNI binding to Yoga's existing `YGNodeSetNodeType`. ## Why this is needed Installing a measure function makes a Yoga node a text node, which is rounded to the pixel grid as text: its fractional size is rounded up, and its position down, so its content is not truncated. A container measured through a callback should round as a container does, each edge to the nearest pixel. The C API can select that (`YGNodeSetNodeType`); the Java binding could not. - Measured: of three nodes flexed to a third of a 100 wide row each, the first, given a measure function, ends at 33.33 and comes out 34 wide; without one, 33. ## What this diff does **`YogaNode.setNodeType(nodeType)`,** a new public open method that does nothing by default, so a `YogaNode` subclass built against an earlier class does not fail with `AbstractMethodError`. `YogaNodeJNIBase` overrides it through a new native method, `YogaNative.jni_YGNodeSetNodeTypeJNI`, which `YGJNIVanilla.cpp` registers and implements with `YGNodeSetNodeType`. `setMeasureFunction` still sets the type (text for a function, default for null), so a caller selects the type after it. ## What a Yoga user sees **A new public Yoga API.** **The Java and native halves must ship together.** `registerNatives` is strict: a Java class declaring `jni_YGNodeSetNodeTypeJNI` with a native library that lacks it, or the reverse, fails when the class registers. Changelog: [Android][Added] - Add `YogaNode.setNodeType` to select pixel rounding for measured nodes Reviewed By: christophpurrer Differential Revision: D125292521
1 parent 7856c9b commit 18304ce

5 files changed

Lines changed: 79 additions & 0 deletions

File tree

‎java/com/facebook/yoga/YogaNative.kt‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -311,6 +311,8 @@ public object YogaNative {
311311
@JvmStatic
312312
public external fun jni_YGNodeSetHasMeasureFuncJNI(nativePointer: Long, hasMeasureFunc: Boolean)
313313

314+
@JvmStatic public external fun jni_YGNodeSetNodeTypeJNI(nativePointer: Long, nodeType: Int)
315+
314316
@JvmStatic
315317
public external fun jni_YGNodeSetHasMinContentMeasureFuncJNI(
316318
nativePointer: Long,

‎java/com/facebook/yoga/YogaNode.kt‎

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -228,6 +228,17 @@ public abstract class YogaNode : YogaProps {
228228

229229
abstract override fun setMeasureFunction(measureFunction: YogaMeasureFunction?)
230230

231+
/**
232+
* Sets the node type, which selects how layout is rounded to the pixel grid: a
233+
* [YogaNodeType.TEXT] node's fractional size is rounded up and its position down, so its content
234+
* is not truncated. [setMeasureFunction] sets it ([YogaNodeType.TEXT] for a function,
235+
* [YogaNodeType.DEFAULT] for null), so call this after it.
236+
*
237+
* Does nothing by default, so a subclass built against an earlier [YogaNode] keeps working, with
238+
* the rounding [setMeasureFunction] selected.
239+
*/
240+
public open fun setNodeType(nodeType: YogaNodeType) {}
241+
231242
abstract override fun setMinContentMeasureFunction(measureFunction: YogaMeasureFunction?)
232243

233244
abstract override fun setMinContentWidth(minContentWidth: Float)

‎java/com/facebook/yoga/YogaNodeJNIBase.kt‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -526,6 +526,10 @@ public abstract class YogaNodeJNIBase : YogaNode, Cloneable {
526526
YogaNative.jni_YGNodeSetHasMeasureFuncJNI(nativePointer, measureFunction != null)
527527
}
528528

529+
override fun setNodeType(nodeType: YogaNodeType) {
530+
YogaNative.jni_YGNodeSetNodeTypeJNI(nativePointer, nodeType.intValue())
531+
}
532+
529533
override fun setMinContentMeasureFunction(measureFunction: YogaMeasureFunction?) {
530534
this.minContentMeasureFunction = measureFunction
531535
YogaNative.jni_YGNodeSetHasMinContentMeasureFuncJNI(nativePointer, measureFunction != null)

‎java/jni/YGJNIVanilla.cpp‎

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -685,6 +685,15 @@ static void jni_YGNodeSetHasMeasureFuncJNI(
685685
static_cast<bool>(hasMeasureFunc) ? YGJNIMeasureFunc : nullptr);
686686
}
687687

688+
static void jni_YGNodeSetNodeTypeJNI(
689+
JNIEnv* /*env*/,
690+
jobject /*obj*/,
691+
jlong nativePointer,
692+
jint nodeType) {
693+
YGNodeSetNodeType(
694+
_jlong2YGNodeRef(nativePointer), static_cast<YGNodeType>(nodeType));
695+
}
696+
688697
static YGSize YGJNIMinContentMeasureFunc(
689698
YGNodeConstRef node,
690699
float width,
@@ -1139,6 +1148,7 @@ static JNINativeMethod methods[] = {
11391148
"(JZ)V",
11401149
(void*)jni_YGNodeSetHasMeasureFuncJNI},
11411150
// NOLINTBEGIN(cppcoreguidelines-pro-type-cstyle-cast)
1151+
{"jni_YGNodeSetNodeTypeJNI", "(JI)V", (void*)jni_YGNodeSetNodeTypeJNI},
11421152
{"jni_YGNodeSetHasMinContentMeasureFuncJNI",
11431153
"(JZ)V",
11441154
(void*)jni_YGNodeSetHasMinContentMeasureFuncJNI},

‎java/tests/com/facebook/yoga/YogaNodeTest.kt‎

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -313,6 +313,58 @@ class YogaNodeTest {
313313
assertNull(node.data)
314314
}
315315

316+
@Test
317+
fun testMeasuredNodeRoundsAsText() {
318+
assertEquals(34, firstThirdWidth { it.setMeasureFunction(fillsWidth) })
319+
}
320+
321+
@Test
322+
fun testSetNodeTypeDefaultRoundsMeasuredNodeToNearest() {
323+
assertEquals(
324+
33,
325+
firstThirdWidth {
326+
it.setMeasureFunction(fillsWidth)
327+
it.setNodeType(YogaNodeType.DEFAULT)
328+
},
329+
)
330+
}
331+
332+
@Test
333+
fun testSetMeasureFunctionResetsNodeTypeToText() {
334+
assertEquals(
335+
34,
336+
firstThirdWidth {
337+
it.setMeasureFunction(fillsWidth)
338+
it.setNodeType(YogaNodeType.DEFAULT)
339+
it.setMeasureFunction(fillsWidth)
340+
},
341+
)
342+
}
343+
344+
/** No width of its own, so flexing gives it its third. */
345+
private val fillsWidth = YogaMeasureFunction { _, _, _, _, _ -> YogaMeasureOutput.make(0f, 10f) }
346+
347+
/**
348+
* The rounded width of the first of three nodes flexed to a third of a 100 wide row each, set up
349+
* by [configure]: its right edge is at 33.33, which text rounding takes up to 34.
350+
*/
351+
private fun firstThirdWidth(configure: (YogaNode) -> Unit): Int {
352+
val root = createNode()
353+
root.flexDirection = YogaFlexDirection.ROW
354+
root.setWidth(100f)
355+
root.setHeight(10f)
356+
for (i in 0 until 3) {
357+
val child = createNode()
358+
child.flexGrow = 1f
359+
if (i == 0) {
360+
configure(child)
361+
}
362+
root.addChildAt(child, i)
363+
}
364+
root.calculateLayout(YogaConstants.UNDEFINED, YogaConstants.UNDEFINED)
365+
return root.getChildAt(0).layoutWidth.toInt()
366+
}
367+
316368
private fun createNode(): YogaNode = mNodeFactory.create()
317369

318370
private fun createNode(config: YogaConfig): YogaNode = mNodeFactory.create(config)

0 commit comments

Comments
 (0)