Compare commits

..

4 Commits

Author SHA1 Message Date
Edensan
86a7fff443 Merge branch 'master' into edensan 2018-03-14 10:23:36 +08:00
Tomer Shalev
c75adb0671 solves the Android pixel bug
Summary:
emilsjolander hi,
this PR solves the following common and probable layout pixel scenario:
the older code is presented for reference:

```java
view.measure(
    View.MeasureSpec.makeMeasureSpec(
        Math.round(node.getLayoutWidth()),
        View.MeasureSpec.EXACTLY),
    View.MeasureSpec.makeMeasureSpec(
        Math.round(node.getLayoutHeight()),
        View.MeasureSpec.EXACTLY));
view.layout(
    Math.round(xOffset + node.getLayoutX()),
    Math.round(yOffset + node.getLayoutY()),
    Math.round(xOffset + node.getLayoutX() + node.getLayoutWidth()),
    Math.round(yOffset + node.getLayoutY() + node.getLayoutHeight()));

```

suppose now the following:
- `xOffset + node.getLayoutX() = 2.2`
- `node.getLayoutWidth() = 0.4` ==> `Math.round(node.getLayoutWidth()) = 0`
- `Math.round(xOffset + node.getLayoutX() + node.getLayoutWidth()) = Math.round(2.2 + 0.4) = 3`

this induces, the following measurements:
```java
view.measure(
    View.MeasureSpec.makeMeasureSpec(
        0,
        View.MeasureSpec.EXACTLY),
    View.MeasureSpec.makeMeasureSpec(
        Math.round(node.getLayoutHeight()),
        View.MeasureSpec.EXACTLY));
view.layout(
    2,
    Math.round(yOffset + node.getLayoutY()),
    3,
    Math.round(yOffset + node.getLayoutY() + node.getLayoutHeight()));

```

the width measurement of the view is 0, while the layout is `(3 - 2 = 1)`.
my proposed solution is to measure the view the way it is now, but when layouting
I use the `#getMeasuredWidth/Height()` methods, this will stop this problem
from happening.

I also want to note that this bug happens with high probability.
Closes https://github.com/facebook/yoga/pull/712

Reviewed By: emilsjolander

Differential Revision: D7231798

Pulled By: priteshrnandgaonkar

fbshipit-source-id: 171da519639dbecd75416a574bccc4456aa22f31
2018-03-13 03:13:00 -07:00
Héctor Ramos
178b8f5f64 Update config files to use LICENSE-examples
Summary: Use examples license in config files.

Reviewed By: sophiebits

Differential Revision: D7174444

fbshipit-source-id: 50c2369b18abd9d7fff9b4a66788fd67a5b40a0c
2018-03-08 10:21:51 -08:00
Edmond Maillard
df9b9849e4 Fixed rounding issues with YGRoundValueToPixelGrid and negative floats 2018-01-24 16:24:02 +08:00
5 changed files with 88 additions and 38 deletions

View File

@@ -1,10 +1,3 @@
# Copyright (c) 2014-present, Facebook, Inc.
# All rights reserved.
#
# This source code is licensed under the BSD-style license found in the
# LICENSE file in the root directory of this source tree. An additional grant
# of patent rights can be found in the PATENTS file in the same directory.
language: node_js
node_js:
- "8"

View File

@@ -1,9 +1,7 @@
# Copyright (c) 2014-present, Facebook, Inc.
# All rights reserved.
#
# This source code is licensed under the BSD-style license found in the
# LICENSE file in the root directory of this source tree. An additional grant
# of patent rights can be found in the PATENTS file in the same directory.
# This source code is licensed under the license found in the
# LICENSE-examples file in the root directory of this source tree.
load("//:yoga_defs.bzl", "ANDROID_JAVA_TARGET", "ANDROID_SAMPLE_RES_TARGET", "ANDROID_SUPPORT_TARGET", "APPCOMPAT_TARGET", "SOLOADER_TARGET", "android_library")

View File

@@ -7,9 +7,6 @@
package com.facebook.yoga.android;
import java.util.HashMap;
import java.util.Map;
import android.content.Context;
import android.content.res.Configuration;
import android.content.res.TypedArray;
@@ -21,9 +18,6 @@ import android.util.SparseArray;
import android.util.TypedValue;
import android.view.View;
import android.view.ViewGroup;
import android.util.Log;
import com.facebook.yoga.android.R;
import com.facebook.yoga.YogaAlign;
import com.facebook.yoga.YogaConstants;
import com.facebook.yoga.YogaDirection;
@@ -35,10 +29,11 @@ import com.facebook.yoga.YogaMeasureFunction;
import com.facebook.yoga.YogaMeasureMode;
import com.facebook.yoga.YogaMeasureOutput;
import com.facebook.yoga.YogaNode;
import com.facebook.yoga.YogaNode;
import com.facebook.yoga.YogaOverflow;
import com.facebook.yoga.YogaPositionType;
import com.facebook.yoga.YogaWrap;
import java.util.HashMap;
import java.util.Map;
/**
* A {@code ViewGroup} based on the Yoga layout engine.
@@ -291,6 +286,8 @@ public class YogaLayout extends ViewGroup {
if (view.getVisibility() == GONE) {
return;
}
int left = Math.round(xOffset + node.getLayoutX());
int top = Math.round(yOffset + node.getLayoutY());
view.measure(
View.MeasureSpec.makeMeasureSpec(
Math.round(node.getLayoutWidth()),
@@ -298,11 +295,7 @@ public class YogaLayout extends ViewGroup {
View.MeasureSpec.makeMeasureSpec(
Math.round(node.getLayoutHeight()),
View.MeasureSpec.EXACTLY));
view.layout(
Math.round(xOffset + node.getLayoutX()),
Math.round(yOffset + node.getLayoutY()),
Math.round(xOffset + node.getLayoutX() + node.getLayoutWidth()),
Math.round(yOffset + node.getLayoutY() + node.getLayoutHeight()));
view.layout(left, top, left + view.getMeasuredWidth(), top + view.getMeasuredHeight());
}
final int childrenCount = node.getChildCount();

View File

@@ -18,6 +18,14 @@ TEST(YogaTest, rounding_value) {
ASSERT_FLOAT_EQ(6.0, YGRoundValueToPixelGrid(5.999999, 2.0, true, false));
ASSERT_FLOAT_EQ(6.0, YGRoundValueToPixelGrid(5.999999, 2.0, false, true));
// Test with negative numbers
ASSERT_FLOAT_EQ(-6.0, YGRoundValueToPixelGrid(-6.000001, 2.0, false, false));
ASSERT_FLOAT_EQ(-6.0, YGRoundValueToPixelGrid(-6.000001, 2.0, true, false));
ASSERT_FLOAT_EQ(-6.0, YGRoundValueToPixelGrid(-6.000001, 2.0, false, true));
ASSERT_FLOAT_EQ(-6.0, YGRoundValueToPixelGrid(-5.999999, 2.0, false, false));
ASSERT_FLOAT_EQ(-6.0, YGRoundValueToPixelGrid(-5.999999, 2.0, true, false));
ASSERT_FLOAT_EQ(-6.0, YGRoundValueToPixelGrid(-5.999999, 2.0, false, true));
// Test that numbers with fraction are rounded correctly accounting for ceil/floor flags
ASSERT_FLOAT_EQ(6.0, YGRoundValueToPixelGrid(6.01, 2.0, false, false));
ASSERT_FLOAT_EQ(6.5, YGRoundValueToPixelGrid(6.01, 2.0, true, false));
@@ -25,4 +33,45 @@ TEST(YogaTest, rounding_value) {
ASSERT_FLOAT_EQ(6.0, YGRoundValueToPixelGrid(5.99, 2.0, false, false));
ASSERT_FLOAT_EQ(6.0, YGRoundValueToPixelGrid(5.99, 2.0, true, false));
ASSERT_FLOAT_EQ(5.5, YGRoundValueToPixelGrid(5.99, 2.0, false, true));
ASSERT_FLOAT_EQ(6.0, YGRoundValueToPixelGrid(6.49, 1.0, false, false));
ASSERT_FLOAT_EQ(7.0, YGRoundValueToPixelGrid(6.50, 1.0, false, false));
ASSERT_FLOAT_EQ(7.0, YGRoundValueToPixelGrid(6.51, 1.0, false, false));
ASSERT_FLOAT_EQ(7.0, YGRoundValueToPixelGrid(6.50, 1.0, true, false));
ASSERT_FLOAT_EQ(6.0, YGRoundValueToPixelGrid(6.50, 1.0, false, true));
// Test with negative numbers
ASSERT_FLOAT_EQ(-6.0, YGRoundValueToPixelGrid(-6.01, 2.0, false, false));
ASSERT_FLOAT_EQ(-6.0, YGRoundValueToPixelGrid(-6.01, 2.0, true, false));
ASSERT_FLOAT_EQ(-6.5, YGRoundValueToPixelGrid(-6.01, 2.0, false, true));
ASSERT_FLOAT_EQ(-6.0, YGRoundValueToPixelGrid(-5.99, 2.0, false, false));
ASSERT_FLOAT_EQ(-5.5, YGRoundValueToPixelGrid(-5.99, 2.0, true, false));
ASSERT_FLOAT_EQ(-6.0, YGRoundValueToPixelGrid(-5.99, 2.0, false, true));
ASSERT_FLOAT_EQ(-6.0, YGRoundValueToPixelGrid(-6.49, 1.0, false, false));
ASSERT_FLOAT_EQ(-6.0, YGRoundValueToPixelGrid(-6.50, 1.0, false, false));
ASSERT_FLOAT_EQ(-7.0, YGRoundValueToPixelGrid(-6.51, 1.0, false, false));
ASSERT_FLOAT_EQ(-6.0, YGRoundValueToPixelGrid(-6.50, 1.0, true, false));
ASSERT_FLOAT_EQ(-7.0, YGRoundValueToPixelGrid(-6.50, 1.0, false, true));
// Do a simple translation test to ensure that a distance between 2 values
// stays consistent during an animation, even after rounding them.
const int LAPS = 3;
const int LAP_CLOSEST = 0;
const int LAP_CEIL = 1;
const int LAP_FLOOR = 2;
for (int currentLap = LAP_CLOSEST; currentLap < LAPS; ++currentLap)
{
float left = -2.0f;
float right = 2.0f;
const float distance = right-left;
float totalDistance = 1.1f;
const float step = 0.01f;
while (totalDistance >= 0.0f) {
left += step;
right += step;
const float snappedLeft = YGRoundValueToPixelGrid(left, 1.0, currentLap==LAP_CEIL, currentLap==LAP_FLOOR);
const float snappedRight = YGRoundValueToPixelGrid(right, 1.0, currentLap==LAP_CEIL, currentLap==LAP_FLOOR);
ASSERT_FLOAT_EQ(distance, (snappedRight-snappedLeft));
totalDistance -= step;
}
}
}

View File

@@ -3180,24 +3180,41 @@ float YGRoundValueToPixelGrid(const float value,
const bool forceCeil,
const bool forceFloor) {
float scaledValue = value * pointScaleFactor;
float fractial = fmodf(scaledValue, 1.0);
if (YGFloatsEqual(fractial, 0)) {
// First we check if the value is already rounded
const float fractial = fmodf(scaledValue, 1.0f);
const float absoluteFractial = fabs(fractial);
// 1. Remove any remainder from the scaledValue
scaledValue = scaledValue - fractial;
} else if (YGFloatsEqual(fractial, 1.0)) {
scaledValue = scaledValue - fractial + 1.0;
// 2. Figure out rounding
// Note: It is important that the following rounding algorithm
// ensures that both positive and negative values are treated exactly the same.
if (YGFloatsEqual(absoluteFractial, 0.0f)) {
// Already whole (or close enough), skip rounding
} else if (YGFloatsEqual(absoluteFractial, 1.0f)) {
// Already whole (or close enough), skip rounding
scaledValue += (fractial < 0.0f) ? -1.0f : 1.0f;
} else if (forceCeil) {
// Next we check if we need to use forced rounding
scaledValue = scaledValue - fractial + 1.0f;
// Force round to upper whole value
if (fractial > 0.0f) {
scaledValue += 1.0f;
}
} else if (forceFloor) {
scaledValue = scaledValue - fractial;
// Force round to lower whole value
if (fractial < 0.0f) {
scaledValue -= 1.0f;
}
} else {
// Finally we just round the value
scaledValue = scaledValue - fractial +
(!YGFloatIsUndefined(fractial) &&
(fractial > 0.5f || YGFloatsEqual(fractial, 0.5f))
? 1.0f
: 0.0f);
// Round to closest whole value
if (fractial > 0.0f) {
if (absoluteFractial > 0.5f || YGFloatsEqual(absoluteFractial, 0.5f)) {
scaledValue += 1.0f;
}
} else {
if (absoluteFractial > 0.5f && !YGFloatsEqual(absoluteFractial, 0.5f)) {
scaledValue -= 1.0f;
}
}
}
return (YGFloatIsUndefined(scaledValue) ||
YGFloatIsUndefined(pointScaleFactor))