On Sun, 1 Sep 2024 12:31:06 GMT, Michael Strauß <mstra...@openjdk.org> wrote:

>> This PR completes the CSS Transitions story (see #870) by adding 
>> interpolation support for backgrounds and borders, making them targetable by 
>> transitions.
>> 
>> `Background` and `Border` objects are deeply immutable, but not 
>> interpolatable. Consider the following `Background`, which describes the 
>> background of a `Region`:
>> 
>> 
>> Background {
>>     fills = [
>>         BackgroundFill {
>>             fill = Color.RED
>>         }
>>     ]
>> }
>> 
>> 
>> Since backgrounds are deeply immutable, changing the region's background to 
>> another color requires the construction of a new `Background`, containing a 
>> new `BackgroundFill`, containing the new `Color`.
>> 
>> Animating the background color using a CSS transition therefore requires the 
>> entire Background object graph to be interpolatable in order to generate 
>> intermediate backgrounds.
>> 
>> More specifically, the following types will now implement `Interpolatable`.
>> 
>> - `Insets`
>> - `Background`
>> - `BackgroundFill`
>> - `BackgroundImage`
>> - `BackgroundPosition`
>> - `BackgroundSize`
>> - `Border`
>> - `BorderImage`
>> - `BorderStroke`
>> - `BorderWidths`
>> - `CornerRadii`
>> - `Stop`
>> - `Paint` and all of its subclasses
>> - `Margins` (internal type)
>> - `BorderImageSlices` (internal type)
>> 
>> ## Interpolation of composite objects
>> 
>> As of now, only `Color`, `Point2D`, and `Point3D` are interpolatable. Each 
>> of these classes is an aggregate of `double` values, which are combined 
>> using linear interpolation. However, many of the new interpolatable classes 
>> comprise of not only `double` values, but a whole range of other types. This 
>> requires us to more precisely define what we mean by "interpolation".
>> 
>> Mirroring the CSS specification, the `Interpolatable` interface defines 
>> several types of component interpolation:
>> 
>> | Interpolation type | Description |
>> |---|---|
>> | default | Component types that implement `Interpolatable` are interpolated 
>> by calling the `interpolate(Object, double)}` method. |
>> | linear | Two components are combined by linear interpolation such that `t 
>> = 0` produces the start value, and `t = 1` produces the end value. This 
>> interpolation type is usually applicable for numeric components. |
>> | discrete | If two components cannot be meaningfully combined, the 
>> intermediate component value is equal to the start value for `t < 0.5` and 
>> equal to the end value for `t >= 0.5`. |
>> | pairwise | Two lists are combined by pairwise interpolation. If the start 
>> list has fewer elements than the target list, the...
>
> Michael Strauß has updated the pull request with a new target base due to a 
> merge or a rebase. The incremental webrev excludes the unrelated changes 
> brought in by the merge/rebase. The pull request contains 48 additional 
> commits since the last revision:
> 
>  - Merge branch 'master' into feature/interpolatable
>  - remove StyleConverter.WithReconstructionSupport
>  - fix line separators
>  - StyleableStringProperty should be transitionable
>  - non-interpolatable values should always transition discretely
>  - only call get() when necessary
>  - add more documentation
>  - replace reconstruction annotation with interface
>  - interpolate integers in real number space
>  - replace StyleConverter.SupportsDeconstruction interface with annotation
>  - ... and 38 more: https://git.openjdk.org/jfx/compare/ca9f51c5...2337ca98

modules/javafx.graphics/src/main/java/javafx/animation/Interpolatable.java line 
57:

> 55:  *             </td>
> 56:  *         </tr>
> 57:  *         <tr><td style="white-space: nowrap; vertical-align: top">(see 
> prose)</td>

This could just be a comment below the table?  Not sure what "(see prose)" 
means here.

modules/javafx.graphics/src/main/java/javafx/css/StyleableLongProperty.java 
line 137:

> 135:         public void onUpdate(double progress) {
> 136:             // Longs are interpolated in real number space and rounded 
> to the nearest long.
> 137:             set(progress < 1 ? Math.round(startValue + (endValue - 
> startValue) * progress) : endValue);

I don't think this will produce correct start values for all longs, and it may 
produce values outside [start,end] range I think.

Here is a bad start value for example:

    double progress = 0;
    long startValue = Long.MAX_VALUE - 1;
    long endValue = 0;

    System.out.println(progress < 1 ? Math.round(startValue + (endValue - 
startValue) * progress) : endValue);
    System.out.println(startValue);

The calculated value will be `Long.MAX_VALUE` which is outside the [startValue, 
endValue] range.

Furthermore, when the longs are large (>49 bits), then interpolation will be 
terrible:

    public static void main(String[] args) {
      long startValue = 1000000000000000000L;
      long endValue =   1000000000000000100L;
  
      for (double progress : new double[] {0.0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 
0.7, 0.8, 0.9, 1.0}) {
        System.out.println(progress < 1 ? Math.round(startValue + (endValue - 
startValue) * progress) : endValue);
      }
    }

Prints:


1000000000000000000
1000000000000000000
1000000000000000000
1000000000000000000
1000000000000000000
1000000000000000000
1000000000000000000
1000000000000000128
1000000000000000128
1000000000000000128
1000000000000000128


I think if you make sure that the subtraction and addition are done as `long` 
(instead of as `double`) it will be far more accurate:

       long diff = endValue - startValue;
       long result = startValue + Math.round(progress * diff);

You still will need to ensure the result is clamped I think, and I you'll need 
to make an exception for `1.0` as it is the result of a double calculation + 
rounding which can be off.  The start value should always be correct I think, 
even if `progress` is some tiny value like `0.0000000000000000001`.

    public static void main(String[] args) {
        long startValue = 1000000000000000333L;
        long endValue = 2000000000000000100L;

        for (double progress : new double[] {0.0000000000000000001, 0.1, 0.2, 
0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0}) {
            long diff = endValue - startValue;
            long interpolatedValue = startValue + Math.round(progress * diff);
            System.out.println(interpolatedValue);
        }
    }

Prints:


1000000000000000333
1100000000000000317
1200000000000000301
1300000000000000269
1400000000000000269
1500000000000000205
1600000000000000205
1700000000000000077
1800000000000000205
1900000000000000077
2000000000000000077


So the final `1.0` is not calculated correctly, and so should be handled as a 
special case.

modules/javafx.graphics/src/main/java/javafx/css/TransitionEvent.java line 112:

> 110:         super(Objects.requireNonNull(eventType, "eventType cannot be 
> null"));
> 111:         this.property = Objects.requireNonNull(property, "property 
> cannot be null");
> 112:         this.propertyName = Objects.requireNonNull(propertyName, 
> "propertyName cannot be null");

Can this be different from `property.getCssMetadata().getProperty()`?  What 
does it mean if it is?

-------------

PR Review Comment: https://git.openjdk.org/jfx/pull/1522#discussion_r1741355798
PR Review Comment: https://git.openjdk.org/jfx/pull/1522#discussion_r1741361742
PR Review Comment: https://git.openjdk.org/jfx/pull/1522#discussion_r1741376154

Reply via email to