blob: 9f6a511fa273aa33994be816a3fa6e7da7a1f9e5 [file] [log] [blame]
// Copyright 2016 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "third_party/blink/renderer/core/animation/interpolable_length.h"
#include "third_party/blink/renderer/core/animation/underlying_value.h"
#include "third_party/blink/renderer/core/css/css_math_expression_node.h"
#include "third_party/blink/renderer/core/css/css_math_function_value.h"
#include "third_party/blink/renderer/core/css/css_numeric_literal_value.h"
#include "third_party/blink/renderer/core/css/css_to_length_conversion_data.h"
#include "third_party/blink/renderer/platform/geometry/blend.h"
#include "third_party/blink/renderer/platform/geometry/calculation_value.h"
namespace blink {
// static
std::unique_ptr<InterpolableLength> InterpolableLength::CreatePixels(
double pixels) {
CSSLengthArray length_array;
length_array.values[CSSPrimitiveValue::kUnitTypePixels] = pixels;
length_array.type_flags.set(CSSPrimitiveValue::kUnitTypePixels);
return std::make_unique<InterpolableLength>(std::move(length_array));
}
// static
std::unique_ptr<InterpolableLength> InterpolableLength::CreatePercent(
double percent) {
CSSLengthArray length_array;
length_array.values[CSSPrimitiveValue::kUnitTypePercentage] = percent;
length_array.type_flags.set(CSSPrimitiveValue::kUnitTypePercentage);
return std::make_unique<InterpolableLength>(std::move(length_array));
}
// static
std::unique_ptr<InterpolableLength> InterpolableLength::CreateNeutral() {
return std::make_unique<InterpolableLength>(CSSLengthArray());
}
// static
std::unique_ptr<InterpolableLength> InterpolableLength::MaybeConvertCSSValue(
const CSSValue& value) {
const auto* primitive_value = DynamicTo<CSSPrimitiveValue>(value);
if (!primitive_value)
return nullptr;
if (!primitive_value->IsLength() && !primitive_value->IsPercentage() &&
!primitive_value->IsCalculatedPercentageWithLength())
return nullptr;
CSSLengthArray length_array;
if (!primitive_value->AccumulateLengthArray(length_array)) {
// TODO(crbug.com/991672): Implement interpolation when CSS comparison
// functions min/max are involved.
return nullptr;
}
return std::make_unique<InterpolableLength>(std::move(length_array));
}
// static
std::unique_ptr<InterpolableLength> InterpolableLength::MaybeConvertLength(
const Length& length,
float zoom) {
if (!length.IsSpecified())
return nullptr;
if (length.IsCalculated() && length.GetCalculationValue().IsExpression()) {
// TODO(crbug.com/991672): Support interpolation on min/max results.
return nullptr;
}
PixelsAndPercent pixels_and_percent = length.GetPixelsAndPercent();
CSSLengthArray length_array;
length_array.values[CSSPrimitiveValue::kUnitTypePixels] =
pixels_and_percent.pixels / zoom;
length_array.type_flags[CSSPrimitiveValue::kUnitTypePixels] =
pixels_and_percent.pixels != 0;
length_array.values[CSSPrimitiveValue::kUnitTypePercentage] =
pixels_and_percent.percent;
length_array.type_flags[CSSPrimitiveValue::kUnitTypePercentage] =
length.IsPercentOrCalc();
return std::make_unique<InterpolableLength>(std::move(length_array));
}
// static
PairwiseInterpolationValue InterpolableLength::MergeSingles(
std::unique_ptr<InterpolableValue> start,
std::unique_ptr<InterpolableValue> end) {
// TODO(crbug.com/991672): We currently have a lot of "fast paths" that do not
// go through here, and hence, do not merge the type flags of two lengths. We
// should stop doing that.
auto& start_length = To<InterpolableLength>(*start);
auto& end_length = To<InterpolableLength>(*end);
start_length.length_array_.type_flags |= end_length.length_array_.type_flags;
end_length.length_array_.type_flags = start_length.length_array_.type_flags;
return PairwiseInterpolationValue(std::move(start), std::move(end));
}
void InterpolableLength::SubtractFromOneHundredPercent() {
for (double& value : length_array_.values)
value *= -1;
length_array_.values[CSSPrimitiveValue::kUnitTypePercentage] += 100;
length_array_.type_flags.set(CSSPrimitiveValue::kUnitTypePercentage);
}
static double ClampToRange(double x, ValueRange range) {
return (range == kValueRangeNonNegative && x < 0) ? 0 : x;
}
static CSSPrimitiveValue::UnitType IndexToUnitType(wtf_size_t index) {
return CSSPrimitiveValue::LengthUnitTypeToUnitType(
static_cast<CSSPrimitiveValue::LengthUnitType>(index));
}
Length InterpolableLength::CreateLength(
const CSSToLengthConversionData& conversion_data,
ValueRange range) const {
bool has_percentage = HasPercentage();
double pixels = 0;
double percentage = 0;
for (wtf_size_t i = 0; i < length_array_.values.size(); ++i) {
double value = length_array_.values[i];
if (value == 0)
continue;
if (i == CSSPrimitiveValue::kUnitTypePercentage) {
percentage = value;
} else {
pixels += conversion_data.ZoomedComputedPixels(value, IndexToUnitType(i));
}
}
if (percentage != 0)
has_percentage = true;
if (pixels != 0 && has_percentage) {
return Length(CalculationValue::Create(
PixelsAndPercent(clampTo<float>(pixels), clampTo<float>(percentage)),
range));
}
if (has_percentage)
return Length::Percent(ClampToRange(percentage, range));
return Length::Fixed(
CSSPrimitiveValue::ClampToCSSLengthRange(ClampToRange(pixels, range)));
}
const CSSPrimitiveValue* InterpolableLength::CreateCSSValue(
ValueRange range) const {
bool has_percentage = HasPercentage();
CSSMathExpressionNode* root_node = nullptr;
CSSNumericLiteralValue* first_value = nullptr;
for (wtf_size_t i = 0; i < length_array_.values.size(); ++i) {
double value = length_array_.values[i];
if (value == 0 &&
(i != CSSPrimitiveValue::kUnitTypePercentage || !has_percentage)) {
continue;
}
CSSNumericLiteralValue* current_value =
CSSNumericLiteralValue::Create(value, IndexToUnitType(i));
if (!first_value) {
DCHECK(!root_node);
first_value = current_value;
continue;
}
CSSMathExpressionNode* current_node =
CSSMathExpressionNumericLiteral::Create(current_value);
if (!root_node) {
root_node = CSSMathExpressionNumericLiteral::Create(first_value);
}
root_node = CSSMathExpressionBinaryOperation::Create(
root_node, current_node, CSSMathOperator::kAdd);
}
if (root_node) {
return CSSMathFunctionValue::Create(root_node, range);
}
if (first_value) {
if (range == kValueRangeNonNegative && first_value->DoubleValue() < 0)
return CSSNumericLiteralValue::Create(0, first_value->GetType());
return first_value;
}
return CSSNumericLiteralValue::Create(0,
CSSPrimitiveValue::UnitType::kPixels);
}
void InterpolableLength::ScaleAndAdd(double scale,
const InterpolableValue& other) {
const InterpolableLength& other_length = To<InterpolableLength>(other);
for (wtf_size_t i = 0; i < length_array_.values.size(); ++i) {
length_array_.values[i] =
length_array_.values[i] * scale + other_length.length_array_.values[i];
}
length_array_.type_flags |= other_length.length_array_.type_flags;
}
void InterpolableLength::AssertCanInterpolateWith(
const InterpolableValue& other) const {
DCHECK(other.IsLength());
// TODO(crbug.com/991672): Ensure that all |MergeSingles| variants that merge
// two |InterpolableLength| objects should also assign them the same shape
// (i.e. type flags) after merging into a |PairwiseInterpolationValue|. We
// currently fail to do that, and hit the following DCHECK:
// DCHECK_EQ(length_array_.type_flags,
// To<InterpolableLength>(other).length_array_.type_flags);
}
void InterpolableLength::Interpolate(const InterpolableValue& to,
const double progress,
InterpolableValue& result) const {
const CSSLengthArray& to_length_array =
To<InterpolableLength>(to).length_array_;
CSSLengthArray& result_length_array =
To<InterpolableLength>(result).length_array_;
for (wtf_size_t i = 0; i < length_array_.values.size(); ++i) {
result_length_array.values[i] =
Blend(length_array_.values[i], to_length_array.values[i], progress);
}
result_length_array.type_flags =
length_array_.type_flags | to_length_array.type_flags;
}
} // namespace blink